【问题标题】:Using @Table with schema name in Hibernate 3.3.1ga and HSQLDB在 Hibernate 3.3.1ga 和 HSQLDB 中使用带有模式名称的 @Table
【发布时间】:2010-10-29 07:04:35
【问题描述】:

如何使用 Hibernate 3.3.1ga 和 HSQLDB 在单元测试中完成这项工作:

@Entity
@Table(name="CATEGORY", schema="TEST")
public static class Category { ... }

问题在于 Hibernate 期望架构存在。第二个问题是 Hibernate 在我的任何代码运行之前发出CREATE TABLE TEST.CATEGORY(这发生在 Spring 的测试设置的深处),所以我无法在 Hibernate 之前获得与数据库的连接并手动创建模式。

但我需要架构,因为我必须在真实代码中访问不同的数据库。我该怎么办?

休眠 3.3.1ga、HSQLDB、Spring 2.5

【问题讨论】:

    标签: spring hibernate schema hsqldb


    【解决方案1】:

    你可以写一个实现InitializingBean的类:

    public class SchemaCreator implements InitializingBean {
    
        private String schema;
        private DataSource dataSource;
    
        public String getSchema() {
            return schema;
        }
    
        public void setSchema(String schema) {
            this.schema = schema;
        }
    
        public DataSource getDataSource() {
            return dataSource;
        }
    
        public void setDataSource(DataSource dataSource) {
            this.dataSource = dataSource;
        }
    
        @Override
        public void afterPropertiesSet() throws Exception {
            JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
            jdbcTemplate.execute("CREATE SCHEMA " + schema + " AUTHORIZATION DBA");
        }
    
    }
    

    然后,您必须在此类的 bean 定义文件中定义一个 bean(我正在暗中拍摄您现有 bean 定义的样子)。

    <bean id="dataSource" class="...">
        <property name="driverClassName" value="org.hsqldb.jdbcDriver"/>
        <property name="url" value="jdbc:hsqldb:mem:test"/>
        <property name="username" value="sa"/>
        <property name="password" value=""/>
    </bean>
    
    <bean id="sessionFactory" depends-on="schemaCreator" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        ...
    </bean>
    
    <bean id="schemaCreator" class="SchemaCreator">
        <property name="dataSource" ref="dataSource"/>
        <property name="schema" value="TEST"/>
    </bean>
    

    通过使用Hibernate bean 的depends-on 属性,Spring 将确保schemaCreator bean 将首先被初始化,从而使schema 及时存在。这也应该让你的意图更清晰。

    【讨论】:

      【解决方案2】:

      我目前的解决方案是这样的:

      @Override
      protected String[] getConfigLocations() {
          createHSQLDBSchemas ();
      
          return new String[]{
                  "test-spring-config.xml"
          };
      }
      
      private static boolean hsqldbSchemasCreated = false;
      
      public static void createHSQLDBSchemas ()
      {
          if (hsqldbSchemasCreated)
              return;
      
          try
          {
              log.info ("createHSQLDBSchemas");
      
              Class.forName("org.hsqldb.jdbcDriver").newInstance();
              Connection c = DriverManager.getConnection("jdbc:hsqldb:mem:test", "sa", "");
              Statement stmt = c.createStatement ();
      
              String sql;
              sql = "CREATE SCHEMA xxx AUTHORIZATION DBA";
              log.info (sql);
              stmt.execute (sql);
      
              stmt.close ();
              c.close ();
          }
          catch (Exception e)
          {
              throw new ShouldNotHappenException (e);
          }
      
          hsqldbSchemasCreated = true;
      }
      

      但这感觉就像一个非常丑陋的黑客攻击。没有更好的解决方案吗?

      【讨论】:

        【解决方案3】:

        以下是如何使用测试 hslqdb 创建 spring 配置的示例 它会自动从 @Table(schema =...) 检测您的所有模式并为您创建它们。

        如果只是为了测试,这应该适合你:

        import org.reflections.Reflections; //maven artifact: 'org.reflections:reflections:0.9.9-RC1'
        import org.springframework.context.annotation.Bean;
        import org.springframework.context.annotation.ComponentScan;
        import org.springframework.context.annotation.Configuration;
        import org.springframework.context.annotation.Lazy;
        import org.springframework.core.io.ClassPathResource;
        import org.springframework.jdbc.core.JdbcTemplate;
        import org.springframework.jdbc.datasource.DriverManagerDataSource;
        import org.springframework.orm.hibernate4.LocalSessionFactoryBean;
        
        import javax.persistence.Table;
        import java.util.HashSet;
        import java.util.Properties;
        import java.util.Set;
        
        @Configuration
        @ComponentScan("com.test.collection")
        public class CollectionConfig {
        
        private static final String[] ENTITY_PACKAGES = { "com.test.collection.domain.dao" };
        private static final String CONFIGURATION_LOCATION = "/movie-collection-hibernate.cfg.xml";
        
        @Bean( name = "testSessionFactory" )
        @Lazy
        public LocalSessionFactoryBean getTestSessionFactory() {
            LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean();
            sessionFactory.setPackagesToScan( ENTITY_PACKAGES );
        
            Properties hibernateProperties = getHibernateHsqlTestDbProperties();
            sessionFactory.setHibernateProperties( hibernateProperties );
        
            createNonStandardSchemas( hibernateProperties );
        
            return sessionFactory;
        }
        
        private void createNonStandardSchemas( Properties properties ) {
            final String DEFAULT_SCHEMA = "";
        
            Set<String> schemas = new HashSet<>();
            Reflections reflections = new Reflections( ENTITY_PACKAGES );
            Set<Class<?>> annotatedClasses =
                    reflections.getTypesAnnotatedWith( Table.class );
        
            for ( Class<?> clazz : annotatedClasses ) {
                Table table = clazz.getAnnotation( Table.class );
                if ( !DEFAULT_SCHEMA.equals( table.schema() ) ) {
                    schemas.add( table.schema() );
                }
            }
        
            if ( !schemas.isEmpty() ) {
                DriverManagerDataSource driverManager = new DriverManagerDataSource();
                driverManager.setDriverClassName( properties.getProperty( "hibernate.connection.driver_class" ) );
                driverManager.setUrl( properties.getProperty( "hibernate.connection.url" ) );
                driverManager.setUsername( properties.getProperty( "hibernate.connection.username" ) );
                driverManager.setPassword( properties.getProperty( "hibernate.connection.password" ) );
        
                JdbcTemplate jdbcTemplate = new JdbcTemplate( driverManager );
        
                for ( String schemaName : schemas ) {
                    jdbcTemplate.execute(
                            String.format( "DROP SCHEMA IF EXISTS %s", schemaName)
                    );
                    jdbcTemplate.execute(
                            String.format( "CREATE SCHEMA %s AUTHORIZATION DBA", schemaName)
                    );
                }
            }
        }
        
        
        private Properties getHibernateHsqlTestDbProperties() {
            Properties prop = new Properties();
            prop.setProperty( "hibernate.connection.driver_class", "org.hsqldb.jdbcDriver" );
            prop.setProperty( "hibernate.connection.url", "jdbc:hsqldb:mem:test" );
            prop.setProperty( "hibernate.connection.username", "sa" );
            prop.setProperty( "hibernate.connection.password", "test" );
            prop.setProperty( "hibernate.connection.pool_size", "5" );
            prop.setProperty( "hibernate.dialect", "org.hibernate.dialect.HSQLDialect" );
            prop.setProperty( "hibernate.current_session_context_class", "thread" );
            prop.setProperty( "hibernate.cache.provider_class", "org.hibernate.cache.internal.NoCachingRegionFactory" );
            prop.setProperty( "hibernate.show_sql", "false" );
            prop.setProperty( "hibernate.format_sql", "false" );
            prop.setProperty( "hibernate.use_sql_comments", "false" );
            prop.setProperty( "hibernate.hbm2ddl.auto", "create-drop" );
            return prop;
        }
        
        
        }
        

        这是一个测试样本:

        @ContextConfiguration( classes = CollectionConfig.class )
        @DirtiesContext( classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD )
        public class DaoMappingTest extends AbstractTestNGSpringContextTests {
        
        @Autowired
        private SessionFactory testSessionFactory;
        
        @Test
        public void thatMovieIsSaved() {
            Movie killBill = getKillBillMovie0();
        
            saveToDb( Arrays.asList(killBill) );
        
            Session querySession = testSessionFactory.openSession();
            List<Movie> movies = querySession.createQuery( "from Movie" ).list();
            querySession.close();
        
            assertThat( movies ).containsExactly( killBill );
        }
        
        @Test
        public void that2MoviesIsSaved() {
            Movie killBill = getKillBillMovie0();
            Movie terminator = getTerminatorMovie1();
        
            saveToDb( Arrays.asList( killBill, terminator ) );
        
            Session querySession = testSessionFactory.openSession();
            List<Movie> movies = querySession.createQuery( "from Movie" ).list();
            querySession.close();
        
            assertThat( movies ).containsOnly( killBill, terminator );
        }
        
        private void saveToDb( List<?> objects ) {
            Session session = testSessionFactory.openSession();
            session.beginTransaction();
        
            for( Object obj : objects) {
                session.save( obj );
            }
        
            session.getTransaction().commit();
            session.close();
        }
        
        @AfterSuite
        public void tearDown() {
            testSessionFactory.close();
        }
        }
        

        【讨论】:

          【解决方案4】:

          在我看来,您在 Hibernate DDL 创建代码中存在可重现的错误。你应该report a bug - 这是一个长期的解决方案,但它是开源的方式。当然你可能想制作一个补丁,但我从来没有发现 Hibernate 代码库很容易破解。

          【讨论】:

          • 已经存在漏洞:opensource.atlassian.com/projects/hibernate/browse/HHH-1853 但很明显,开发人员不喜欢这个补丁(现在它已经开放了 三年)。这告诉我:永远不会有解决办法。他们只是不在乎。所以我需要一个解决方法。
          • 这很遗憾,但确实发生了:| .您正在寻找什么样的解决方案?什么是您的次优 - 您必须创建架构或将其放置(我从代码中假设)在您的测试代码中的事实?
          • 我很不高兴我必须在 getConfigLocations() 中执行此操作 - 此方法完全执行其他操作,如果有人正在寻找此代码,这将是最后一个查看的地方。
          【解决方案5】:

          我遇到了同样的问题,MS SQL Server 想要定义目录和架构,但 HSQLDB 没有。我的解决方案是为设置目录和架构的 MS SQL Server 加载自定义 orm.xml 文件(通过 persistence.xml)。

          1.仅为您的实体指定@Table 名称(省略任何目录或架构信息):

          @Entity
          @Table(name="CATEGORY")
          public static class Category { ... }
          

          2.在您的 META-INF/persistence.xml 文件中指定两个持久性单元节点

          <persistence version="2.0"
              xmlns="http://java.sun.com/xml/ns/persistence" 
              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
              xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
          
              <!--
               | For production and integration testing we use MS SQL Server, which needs  
               | the catalog and schema set (see orm-mssql.xml).
               |-->
              <persistence-unit name="com.mycompany.prod">
                  <mapping-file>META-INF/orm-mssql.xml</mapping-file>
              </persistence-unit>
          
              <!--
               | For unit testing we use HSQLDB, which does not need the catalog or schema.
               |-->
              <persistence-unit name="com.mycompany.test" />
          
          </persistence>
          

          3.在 orm-mssql.xml 文件中指定默认目录和架构:

          <entity-mappings version="2.0"
              xmlns="http://java.sun.com/xml/ns/persistence/orm" 
              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
              xsi:schemaLocation="http://java.sun.com/xml/ns/persistence/orm orm_2_0.xsd">
          
              <persistence-unit-metadata>
          
                  <!--
                   | Set the catalog and schema for MS SQL Server
                   |-->
                  <persistence-unit-defaults>
                      <schema>MYSCHEMA</schema>
                      <catalog>MYCATALOG</catalog>
                  </persistence-unit-defaults>
          
              </persistence-unit-metadata>
          
          </entity-mappings>
          

          4.我使用 Spring 来配置 JPA,所以我使用 property-placeholder 作为 persistenceUnitName 的值:

          <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
              <property name="dataSource" ref="dataSource" />
              <property name="jpaVendorAdapter" ref="jpaVendorAdapter" />
              <property name="persistenceUnitName" value="${entityManagerFactory.persistenceUnitName}" />
          </bean>
          

          对于单元测试,使用“com.mycompany.test”,对于集成测试/生产部署,使用“com.mycompany.prod”。

          【讨论】:

            猜你喜欢
            • 2011-08-05
            • 1970-01-01
            • 2012-08-17
            • 2015-04-18
            • 2022-11-18
            • 1970-01-01
            • 2016-02-25
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多