【问题标题】:Replacing SchemaExport(Configuration) in Hibernate 5在 Hibernate 5 中替换 SchemaExport(Configuration)
【发布时间】:2018-05-06 00:56:34
【问题描述】:

在从 Hibernate 4 迁移到 5 时,我遇到了 deprecation 并最终删除了 SchemaExport(Configuration) 构造函数。 Hibernate 5 中有什么好的替代方案?

用例

在测试期间,我们创建了一个 SchemaExport 实例,其配置具有一些属性集并定义了映射资源:

// somewhere else `Properties` are filled and passed as a parameter
Properties properties = new Properties();
properties.put("hibernate.dialect", "org.hibernate.dialect.HSQLDialect");
properties.put("hibernate.connection.driver_class", "org.hsqldb.jdbc.JDBCDriver");
// more properties ...

Configuration configuration = new Configuration();
configuration.setProperties(testProperties);
// parameter `String... mappingResources`
for (final String mapping : mappingResources) {
    configuration.addResource(mapping);
}

// this doesn't compile
SchemaExport schemaExport = new SchemaExport(configuration);

最后一行在 Hibernate 5 上无法编译,因为构造函数已被删除。

选项

弃用建议使用SchemaExport(MetadataImplementor) 构造函数,但我很难找到创建MetadataImplementor 实例的好方法。我找到了一些选项,但在我看来它们都很可疑。

我能在 Hibernate 中找到的唯一具体实现是 MetadataImplInFlightMetadataCollectorImpl,但两者都在 org.hibernate.boot.internal 中,所以我认为我不应该使用它们。此外,MetadataImpl 有一个庞大的构造函数,我需要在其中提供每一个小细节,InFlightMetadataCollectorImpl 需要一个MetadataBuildingOptions,它与MetadataImplementor 有相同的问题(实现是内部的,难以构造)。

另外,看起来MetadataBuilderImpl 可能是构造MetadataImplementor 的便捷方式,但它也是内部的。

无论哪种方式,我都不知道如何在MetadataImplementor(或MetadataBuilderImpl)上设置Properties(或他们的条目)。

问题

创建SchemaExport 真的需要MetadataImplementor 吗?如果是这样,我如何从受支持的 API 中获取一个以及如何设置Properties

最终我们想用execute 执行一个脚本,但这里的签名也发生了变化。我看到它现在需要ServiceRegistry,所以也许这是一条出路?

编辑

啊,我刚刚在 5.2(我想使用)中看到 SchemaExport 甚至不再使用 MetadataImplementor - 只有无参数的构造函数仍然存在。现在呢?

【问题讨论】:

标签: hibernate hibernate-5.x


【解决方案1】:

在 Hibernate 中,我们有这个基础测试类:

public class BaseNonConfigCoreFunctionalTestCase extends BaseUnitTestCase {
    public static final String VALIDATE_DATA_CLEANUP = "hibernate.test.validateDataCleanup";

    private StandardServiceRegistry serviceRegistry;
    private MetadataImplementor metadata;
    private SessionFactoryImplementor sessionFactory;

    private Session session;

    protected Dialect getDialect() {
        if ( serviceRegistry != null ) {
            return serviceRegistry.getService( JdbcEnvironment.class ).getDialect();
        }
        else {
            return BaseCoreFunctionalTestCase.getDialect();
        }
    }

    protected StandardServiceRegistry serviceRegistry() {
        return serviceRegistry;
    }

    protected MetadataImplementor metadata() {
        return metadata;
    }

    protected SessionFactoryImplementor sessionFactory() {
        return sessionFactory;
    }

    protected Session openSession() throws HibernateException {
        session = sessionFactory().openSession();
        return session;
    }

    protected Session openSession(Interceptor interceptor) throws HibernateException {
        session = sessionFactory().withOptions().interceptor( interceptor ).openSession();
        return session;
    }

    protected Session getSession() {
        return session;
    }

    protected void rebuildSessionFactory() {
        releaseResources();
        buildResources();
    }

    protected void cleanupCache() {
        if ( sessionFactory != null ) {
            sessionFactory.getCache().evictAllRegions();
        }
    }

    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    // JUNIT hooks

    @BeforeClassOnce
    @SuppressWarnings( {"UnusedDeclaration"})
    protected void startUp() {
        buildResources();
    }

    protected void buildResources() {
        final StandardServiceRegistryBuilder ssrb = constructStandardServiceRegistryBuilder();

        serviceRegistry = ssrb.build();
        afterStandardServiceRegistryBuilt( serviceRegistry );

        final MetadataSources metadataSources = new MetadataSources( serviceRegistry );
        applyMetadataSources( metadataSources );
        afterMetadataSourcesApplied( metadataSources );

        final MetadataBuilder metadataBuilder = metadataSources.getMetadataBuilder();
        initialize( metadataBuilder );
        configureMetadataBuilder( metadataBuilder );

        metadata = (MetadataImplementor) metadataBuilder.build();
        applyCacheSettings( metadata );
        afterMetadataBuilt( metadata );

        final SessionFactoryBuilder sfb = metadata.getSessionFactoryBuilder();
        initialize( sfb, metadata );
        configureSessionFactoryBuilder( sfb );

        sessionFactory = (SessionFactoryImplementor) sfb.build();
        afterSessionFactoryBuilt( sessionFactory );
    }

    protected final StandardServiceRegistryBuilder constructStandardServiceRegistryBuilder() {
        final BootstrapServiceRegistryBuilder bsrb = new BootstrapServiceRegistryBuilder();
        bsrb.applyClassLoader( getClass().getClassLoader() );
        // by default we do not share the BootstrapServiceRegistry nor the StandardServiceRegistry,
        // so we want the BootstrapServiceRegistry to be automatically closed when the
        // StandardServiceRegistry is closed.
        bsrb.enableAutoClose();
        configureBootstrapServiceRegistryBuilder( bsrb );

        final BootstrapServiceRegistry bsr = bsrb.build();
        afterBootstrapServiceRegistryBuilt( bsr );

        final Map settings = new HashMap();
        addSettings( settings );

        final StandardServiceRegistryBuilder ssrb = new StandardServiceRegistryBuilder( bsr );
        initialize( ssrb );
        ssrb.applySettings( settings );
        configureStandardServiceRegistryBuilder( ssrb );
        return ssrb;
    }

    protected void addSettings(Map settings) {
    }

    /**
     * Apply any desired config to the BootstrapServiceRegistryBuilder to be incorporated
     * into the built BootstrapServiceRegistry
     *
     * @param bsrb The BootstrapServiceRegistryBuilder
     */
    @SuppressWarnings({"SpellCheckingInspection", "UnusedParameters"})
    protected void configureBootstrapServiceRegistryBuilder(BootstrapServiceRegistryBuilder bsrb) {
    }

    /**
     * Hook to allow tests to use the BootstrapServiceRegistry if they wish
     *
     * @param bsr The BootstrapServiceRegistry
     */
    @SuppressWarnings("UnusedParameters")
    protected void afterBootstrapServiceRegistryBuilt(BootstrapServiceRegistry bsr) {
    }

    @SuppressWarnings("SpellCheckingInspection")
    private void initialize(StandardServiceRegistryBuilder ssrb) {
        final Dialect dialect = BaseCoreFunctionalTestCase.getDialect();

        ssrb.applySetting( AvailableSettings.CACHE_REGION_FACTORY, CachingRegionFactory.class.getName() );
        ssrb.applySetting( AvailableSettings.USE_NEW_ID_GENERATOR_MAPPINGS, "true" );
        if ( createSchema() ) {
            ssrb.applySetting( AvailableSettings.HBM2DDL_AUTO, "create-drop" );
            final String secondSchemaName = createSecondSchema();
            if ( StringHelper.isNotEmpty( secondSchemaName ) ) {
                if ( !H2Dialect.class.isInstance( dialect ) ) {
                    // while it may be true that only H2 supports creation of a second schema via
                    // URL (no idea whether that is accurate), every db should support creation of schemas
                    // via DDL which SchemaExport can create for us.  See how this is used and
                    // whether that usage could not just leverage that capability
                    throw new UnsupportedOperationException( "Only H2 dialect supports creation of second schema." );
                }
                Helper.createH2Schema( secondSchemaName, ssrb.getSettings() );
            }
        }
        ssrb.applySetting( AvailableSettings.DIALECT, dialect.getClass().getName() );
    }

    protected boolean createSchema() {
        return true;
    }

    protected String createSecondSchema() {
        // poorly named, yes, but to keep migration easy for existing BaseCoreFunctionalTestCase
        // impls I kept the same name from there
        return null;
    }

    /**
     * Apply any desired config to the StandardServiceRegistryBuilder to be incorporated
     * into the built StandardServiceRegistry
     *
     * @param ssrb The StandardServiceRegistryBuilder
     */
    @SuppressWarnings({"SpellCheckingInspection", "UnusedParameters"})
    protected void configureStandardServiceRegistryBuilder(StandardServiceRegistryBuilder ssrb) {
    }

    /**
     * Hook to allow tests to use the StandardServiceRegistry if they wish
     *
     * @param ssr The StandardServiceRegistry
     */
    @SuppressWarnings("UnusedParameters")
    protected void afterStandardServiceRegistryBuilt(StandardServiceRegistry ssr) {
    }

    protected void applyMetadataSources(MetadataSources metadataSources) {
        for ( String mapping : getMappings() ) {
            metadataSources.addResource( getBaseForMappings() + mapping );
        }

        for ( Class annotatedClass : getAnnotatedClasses() ) {
            metadataSources.addAnnotatedClass( annotatedClass );
        }

        for ( String annotatedPackage : getAnnotatedPackages() ) {
            metadataSources.addPackage( annotatedPackage );
        }

        for ( String ormXmlFile : getXmlFiles() ) {
            metadataSources.addInputStream( Thread.currentThread().getContextClassLoader().getResourceAsStream( ormXmlFile ) );
        }
    }

    protected static final String[] NO_MAPPINGS = new String[0];

    protected String[] getMappings() {
        return NO_MAPPINGS;
    }

    protected String getBaseForMappings() {
        return "org/hibernate/test/";
    }

    protected static final Class[] NO_CLASSES = new Class[0];

    protected Class[] getAnnotatedClasses() {
        return NO_CLASSES;
    }

    protected String[] getAnnotatedPackages() {
        return NO_MAPPINGS;
    }

    protected String[] getXmlFiles() {
        return NO_MAPPINGS;
    }

    protected void afterMetadataSourcesApplied(MetadataSources metadataSources) {
    }

    protected void initialize(MetadataBuilder metadataBuilder) {
        metadataBuilder.enableNewIdentifierGeneratorSupport( true );
        metadataBuilder.applyImplicitNamingStrategy( ImplicitNamingStrategyLegacyJpaImpl.INSTANCE );
    }

    protected void configureMetadataBuilder(MetadataBuilder metadataBuilder) {
    }

    protected boolean overrideCacheStrategy() {
        return true;
    }

    protected String getCacheConcurrencyStrategy() {
        return null;
    }

    protected final void applyCacheSettings(Metadata metadata) {
        if ( !overrideCacheStrategy() ) {
            return;
        }

        if ( getCacheConcurrencyStrategy() == null ) {
            return;
        }

        for ( PersistentClass entityBinding : metadata.getEntityBindings() ) {
            if ( entityBinding.isInherited() ) {
                continue;
            }

            boolean hasLob = false;

            final Iterator props = entityBinding.getPropertyClosureIterator();
            while ( props.hasNext() ) {
                final Property prop = (Property) props.next();
                if ( prop.getValue().isSimpleValue() ) {
                    if ( isLob( ( (SimpleValue) prop.getValue() ).getTypeName() ) ) {
                        hasLob = true;
                        break;
                    }
                }
            }

            if ( !hasLob ) {
                ( ( RootClass) entityBinding ).setCacheConcurrencyStrategy( getCacheConcurrencyStrategy() );
            }
        }

        for ( Collection collectionBinding : metadata.getCollectionBindings() ) {
            boolean isLob = false;

            if ( collectionBinding.getElement().isSimpleValue() ) {
                isLob = isLob( ( (SimpleValue) collectionBinding.getElement() ).getTypeName() );
            }

            if ( !isLob ) {
                collectionBinding.setCacheConcurrencyStrategy( getCacheConcurrencyStrategy() );
            }
        }
    }

    private boolean isLob(String typeName) {
        return "blob".equals( typeName )
                || "clob".equals( typeName )
                || "nclob".equals( typeName )
                || Blob.class.getName().equals( typeName )
                || Clob.class.getName().equals( typeName )
                || NClob.class.getName().equals( typeName )
                || BlobType.class.getName().equals( typeName )
                || ClobType.class.getName().equals( typeName )
                || NClobType.class.getName().equals( typeName );
    }

    protected void afterMetadataBuilt(Metadata metadata) {
    }

    private void initialize(SessionFactoryBuilder sfb, Metadata metadata) {
        // todo : this is where we need to apply cache settings to be like BaseCoreFunctionalTestCase
        //      it reads the class/collection mappings and creates corresponding
        //      CacheRegionDescription references.
        //
        //      Ultimately I want those to go on MetadataBuilder, and in fact MetadataBuilder
        //      already defines the needed method.  But for the [pattern used by the
        //      tests we need this as part of SessionFactoryBuilder
    }

    protected void configureSessionFactoryBuilder(SessionFactoryBuilder sfb) {
    }

    protected void afterSessionFactoryBuilt(SessionFactoryImplementor sessionFactory) {
    }

    @AfterClassOnce
    @SuppressWarnings( {"UnusedDeclaration"})
    protected void shutDown() {
        releaseResources();
    }

    protected void releaseResources() {
        if ( sessionFactory != null ) {
            try {
                sessionFactory.close();
            }
            catch (Exception e) {
                System.err.println( "Unable to release SessionFactory : " + e.getMessage() );
                e.printStackTrace();
            }
        }
        sessionFactory = null;

        if ( serviceRegistry != null ) {
            try {
                StandardServiceRegistryBuilder.destroy( serviceRegistry );
            }
            catch (Exception e) {
                System.err.println( "Unable to release StandardServiceRegistry : " + e.getMessage() );
                e.printStackTrace();
            }
        }
        serviceRegistry=null;
    }

    @OnFailure
    @OnExpectedFailure
    @SuppressWarnings( {"UnusedDeclaration"})
    public void onFailure() {
        if ( rebuildSessionFactoryOnError() ) {
            rebuildSessionFactory();
        }
    }

    protected boolean rebuildSessionFactoryOnError() {
        return true;
    }

    @Before
    public final void beforeTest() throws Exception {
        prepareTest();
    }

    protected void prepareTest() throws Exception {
    }

    @After
    public final void afterTest() throws Exception {
        completeStrayTransaction();

        if ( isCleanupTestDataRequired() ) {
            cleanupTestData();
        }
        cleanupTest();

        cleanupSession();

        assertAllDataRemoved();
    }

    private void completeStrayTransaction() {
        if ( session == null ) {
            // nothing to do
            return;
        }

        if ( ( (SessionImplementor) session ).isClosed() ) {
            // nothing to do
            return;
        }

        if ( !session.isConnected() ) {
            // nothing to do
            return;
        }

        final TransactionCoordinator.TransactionDriver tdc =
                ( (SessionImplementor) session ).getTransactionCoordinator().getTransactionDriverControl();

        if ( tdc.getStatus().canRollback() ) {
            session.getTransaction().rollback();
        }
    }

    protected boolean isCleanupTestDataRequired() {
        return false;
    }

    protected void cleanupTestData() throws Exception {
        doInHibernate(this::sessionFactory, s -> {
            s.createQuery("delete from java.lang.Object").executeUpdate();
        });
    }


    private void cleanupSession() {
        if ( session != null && ! ( (SessionImplementor) session ).isClosed() ) {
            session.close();
        }
        session = null;
    }

    public class RollbackWork implements Work {
        public void execute(Connection connection) throws SQLException {
            connection.rollback();
        }
    }

    protected void cleanupTest() throws Exception {
    }

    @SuppressWarnings( {"UnnecessaryBoxing", "UnnecessaryUnboxing"})
    protected void assertAllDataRemoved() {
        if ( !createSchema() ) {
            return; // no tables were created...
        }
        if ( !Boolean.getBoolean( VALIDATE_DATA_CLEANUP ) ) {
            return;
        }

        Session tmpSession = sessionFactory.openSession();
        try {
            List list = tmpSession.createQuery( "select o from java.lang.Object o" ).list();

            Map<String,Integer> items = new HashMap<String,Integer>();
            if ( !list.isEmpty() ) {
                for ( Object element : list ) {
                    Integer l = items.get( tmpSession.getEntityName( element ) );
                    if ( l == null ) {
                        l = 0;
                    }
                    l = l + 1 ;
                    items.put( tmpSession.getEntityName( element ), l );
                    System.out.println( "Data left: " + element );
                }
                fail( "Data is left in the database: " + items.toString() );
            }
        }
        finally {
            try {
                tmpSession.close();
            }
            catch( Throwable t ) {
                // intentionally empty
            }
        }
    }



    public void inSession(Consumer<SessionImplementor> action) {
        log.trace( "#inSession(action)" );
        inSession( sessionFactory(), action );
    }

    public void inTransaction(Consumer<SessionImplementor> action) {
        log.trace( "#inTransaction(action)" );
        inTransaction( sessionFactory(), action );
    }

    public void inSession(SessionFactoryImplementor sfi, Consumer<SessionImplementor> action) {
        log.trace( "##inSession(SF,action)" );

        try (SessionImplementor session = (SessionImplementor) sfi.openSession()) {
            log.trace( "Session opened, calling action" );
            action.accept( session );
            log.trace( "called action" );
        }
        finally {
            log.trace( "Session close - auto-close lock" );
        }
    }

    public void inTransaction(SessionFactoryImplementor factory, Consumer<SessionImplementor> action) {
        log.trace( "#inTransaction(factory, action)");


        try (SessionImplementor session = (SessionImplementor) factory.openSession()) {
            log.trace( "Session opened, calling action" );
            inTransaction( session, action );
            log.trace( "called action" );
        }
        finally {
            log.trace( "Session close - auto-close lock" );
        }
    }

    public void inTransaction(SessionImplementor session, Consumer<SessionImplementor> action) {
        log.trace( "inTransaction(session,action)" );

        final Transaction txn = session.beginTransaction();
        log.trace( "Started transaction" );

        try {
            log.trace( "Calling action in txn" );
            action.accept( session );
            log.trace( "Called action - in txn" );

            log.trace( "Committing transaction" );
            txn.commit();
            log.trace( "Committed transaction" );
        }
        catch (Exception e) {
            log.tracef(
                    "Error calling action: %s (%s) - rolling back",
                    e.getClass().getName(),
                    e.getMessage()
            );
            try {
                txn.rollback();
            }
            catch (Exception ignore) {
                log.trace( "Was unable to roll back transaction" );
                // really nothing else we can do here - the attempt to
                //      rollback already failed and there is nothing else
                //      to clean up.
            }

            throw e;
        }
    }
}

引导MetadataServiceRegistry

所以,我们可以像这样调用SchemaExport

new SchemaExport().create( EnumSet.of( TargetType.DATABASE ), metadata() );

【讨论】:

  • 我的课程路径上没有ServiceRegistryBuilder,只有StandardServiceRegistryBuilder。看起来MetadataImplementor 的强制转换在 5.2 中是不必要的,因为 SchemaExport 接受 Metadata
  • 你是对的。我更新了我们正在使用的整个课程。
【解决方案2】:

从其他答案中提取,我必须执行以下操作:

        StandardServiceRegistry standardRegistry = new StandardServiceRegistryBuilder()
            .applySetting("hibernate.hbm2ddl.auto", "create")
            .applySetting("hibernate.dialect", "org.hibernate.dialect.MySQLDialect")
            .applySetting("hibernate.id.new_generator_mappings", "true")
            .build();
    MetadataSources sources = new MetadataSources(standardRegistry);
    managedClassNames.forEach(sources::addAnnotatedClass);
    MetadataImplementor metadata = (MetadataImplementor) sources
            .getMetadataBuilder()
            .build();

    SchemaExport export = new SchemaExport(metadata);

希望对你有所帮助

【讨论】:

    【解决方案3】:

    我喜欢已经发布的稀释答案。但这里有一些更详细的信息,包括一些新的内置 Hibernate 枚举,它们允许您以更编程的方式使用 SchemaExport 类创建数据库表,而无需依赖像 hbm2ddl 这样的属性文件类型设置。

    对属性使用HashMap

    通常我喜欢在Properties 对象类型中包含所有数据库设置,例如Hashtable or HashMap。然后将HashMap 传递给ServiceRegistry

    Map<String, String> settings = new HashMap<>();
    settings.put("connection.driver_class", "com.mysql.jdbc.Driver");
    settings.put("dialect", "org.hibernate.dialect.MySQLDialect");
    settings.put("hibernate.connection.url", "jdbc:mysql://localhost/hibernate_examples");
    settings.put("hibernate.connection.username", "root");
    settings.put("hibernate.connection.password", "password");
    ServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder().applySettings(settings).build();
    

    将 JPA 实体添加到 MetadataSources

    然后将所有 JPA 注释类添加到 MetadataSources 对象:

    MetadataSources metadata = new MetadataSources(serviceRegistry);
    metadata.addAnnotatedClass(Player.class);
    

    使用ActionTargetType 枚举

    所有这些都完成之后,是时候创建SchemaExport 类并调用它的execute 方法了。这样做时,您可以使用TargetType.DATABASE 枚举和Action.BOTH 枚举,而不是将hbm2ddl 放入如下设置:

    applySetting("hibernate.hbm2ddl.auto", "create")
    

    它的外观如下:

    EnumSet<TargetType> enumSet = EnumSet.of(TargetType.DATABASE);
    SchemaExport schemaExport = new SchemaExport();
    schemaExport.execute(enumSet, Action.BOTH, metadata.buildMetadata());
    

    很抱歉,如果这太长了,但这是完整的版本 5 Hibernate SchemaExport 代码:

    Map<String, String> settings = new HashMap<>();
    settings.put("connection.driver_class", "com.mysql.jdbc.Driver");
    settings.put("dialect", "org.hibernate.dialect.MySQLDialect");
    settings.put("hibernate.connection.url", "jdbc:mysql://localhost/jpa");
    settings.put("hibernate.connection.username", "root");
    settings.put("hibernate.connection.password", "password");
    settings.put("hibernate.show_sql", "true");
    settings.put("hibernate.format_sql", "true");
    
    ServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder().applySettings(settings).build();
    
    MetadataSources metadata = new MetadataSources(serviceRegistry);
    //metadata.addAnnotatedClass(Player.class);
    
    EnumSet<TargetType> enumSet = EnumSet.of(TargetType.DATABASE);
    SchemaExport schemaExport = new SchemaExport();
    schemaExport.execute(enumSet, Action.BOTH, metadata.buildMetadata());
    

    源代码在GitHub 上提供。

    【讨论】:

      猜你喜欢
      • 2016-04-20
      • 2015-11-12
      • 2016-04-09
      • 1970-01-01
      • 2012-12-19
      • 1970-01-01
      • 2012-04-08
      • 2012-10-20
      • 1970-01-01
      相关资源
      最近更新 更多