随手整合多数据源项目

930次阅读
没有评论

共计 28211 个字符,预计需要花费 71 分钟才能阅读完成。

1.前言

多数据源现象很常见,大体分为两种:第一种由于业务量大,分库分表而衍生的多数据源;第二种是实实在在的两个数据源,数据源之间没有逻辑关系,由于业务关系需要交互,而此时又会引入分布式事务的问题,两个数据源归属不同的系统,系统架构也不一样,牵一发而动全身,折衷方案是一方添加多数据源方案,避免了多节点的分布式事务问题,统一由一方业务系统管理事务。

2.整合Mybatis及JPA实现多数据源支持

都为Mybatis架构的多数据源整合,笔者就不提了,毕竟业界有了不少实现。首先我们定义Mybatis数据源的自动配置模块mybatis-module

@Configuration
@ConditionalOnProperty(name = "spring.multiple-datasource",havingValue = "true")
@MapperScan(value = {"cloud.mysteriousman.mybatis.dao"},sqlSessionFactoryRef = "sqlSessionFactoryOne")
@EnableConfigurationProperties(MybatisProperties.class)
@AutoConfigureBefore(value = DataSourceAutoConfiguration.class)
@ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class })
public class MyMybatisAutoConfiguration implements InitializingBean {
    private static final Logger logger = LoggerFactory.getLogger(MyMybatisAutoConfiguration.class);

    private final MybatisProperties properties;

    private final Interceptor[] interceptors;

    private final TypeHandler[] typeHandlers;

    private final LanguageDriver[] languageDrivers;

    private final ResourceLoader resourceLoader;

    private final DatabaseIdProvider databaseIdProvider;

    private final List<ConfigurationCustomizer> configurationCustomizers;

    public MyMybatisAutoConfiguration(MybatisProperties properties, ObjectProvider<Interceptor[]> interceptorsProvider,
                                           ObjectProvider<TypeHandler[]> typeHandlersProvider, ObjectProvider<LanguageDriver[]> languageDriversProvider,
                                           ResourceLoader resourceLoader, ObjectProvider<DatabaseIdProvider> databaseIdProvider,
                                           ObjectProvider<List<ConfigurationCustomizer>> configurationCustomizersProvider) {
        this.properties = properties;
        this.interceptors = interceptorsProvider.getIfAvailable();
        this.typeHandlers = typeHandlersProvider.getIfAvailable();
        this.languageDrivers = languageDriversProvider.getIfAvailable();
        this.resourceLoader = resourceLoader;
        this.databaseIdProvider = databaseIdProvider.getIfAvailable();
        this.configurationCustomizers = configurationCustomizersProvider.getIfAvailable();
    }
    @Override
    public void afterPropertiesSet() {
        checkConfigFileExists();
    }

    private void checkConfigFileExists() {
        if (this.properties.isCheckConfigLocation() && StringUtils.hasText(this.properties.getConfigLocation())) {
            Resource resource = this.resourceLoader.getResource(this.properties.getConfigLocation());
            Assert.state(resource.exists(),
                    "Cannot find config location: " + resource + " (please add config file or check your Mybatis configuration)");
        }
    }

    @Bean
    @Primary
    @ConfigurationProperties("spring.datasource.one")
    public DataSource dataSourceOne() {
        return DataSourceBuilder.create().build();
    }

    @Bean(name = "transactionManagerOne")
    @Primary
    public DataSourceTransactionManager transactionManagerOne(@Qualifier(value = "dataSourceOne") DataSource dataSource,
                                                    ObjectProvider<TransactionManagerCustomizers> transactionManagerCustomizers) {
        DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(dataSource);
        transactionManagerCustomizers.ifAvailable((customizers) -> customizers.customize(transactionManager));
        return transactionManager;
    }

    @Bean
    public SqlSessionFactory sqlSessionFactoryOne(@Qualifier(value = "dataSourceOne") DataSource dataSource) throws Exception {
        SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
        factory.setDataSource(dataSource);
        factory.setVfs(SpringBootVFS.class);
        if (StringUtils.hasText(this.properties.getConfigLocation())) {
            factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation()));
        }
        applyConfiguration(factory);
        if (this.properties.getConfigurationProperties() != null) {
            factory.setConfigurationProperties(this.properties.getConfigurationProperties());
        }
        if (!ObjectUtils.isEmpty(this.interceptors)) {
            factory.setPlugins(this.interceptors);
        }
        if (this.databaseIdProvider != null) {
            factory.setDatabaseIdProvider(this.databaseIdProvider);
        }
        if (StringUtils.hasLength(this.properties.getTypeAliasesPackage())) {
            factory.setTypeAliasesPackage(this.properties.getTypeAliasesPackage());
        }
        if (this.properties.getTypeAliasesSuperType() != null) {
            factory.setTypeAliasesSuperType(this.properties.getTypeAliasesSuperType());
        }
        if (StringUtils.hasLength(this.properties.getTypeHandlersPackage())) {
            factory.setTypeHandlersPackage(this.properties.getTypeHandlersPackage());
        }
        if (!ObjectUtils.isEmpty(this.typeHandlers)) {
            factory.setTypeHandlers(this.typeHandlers);
        }
        if (!ObjectUtils.isEmpty(this.properties.resolveMapperLocations())) {
            factory.setMapperLocations(this.properties.resolveMapperLocations());
        }
        Set<String> factoryPropertyNames = Stream
                .of(new BeanWrapperImpl(SqlSessionFactoryBean.class).getPropertyDescriptors()).map(PropertyDescriptor::getName)
                .collect(Collectors.toSet());
        Class<? extends LanguageDriver> defaultLanguageDriver = this.properties.getDefaultScriptingLanguageDriver();
        if (factoryPropertyNames.contains("scriptingLanguageDrivers") && !ObjectUtils.isEmpty(this.languageDrivers)) {
            // Need to mybatis-spring 2.0.2+
            factory.setScriptingLanguageDrivers(this.languageDrivers);
            if (defaultLanguageDriver == null && this.languageDrivers.length == 1) {
                defaultLanguageDriver = this.languageDrivers[0].getClass();
            }
        }
        if (factoryPropertyNames.contains("defaultScriptingLanguageDriver")) {
            // Need to mybatis-spring 2.0.2+
            factory.setDefaultScriptingLanguageDriver(defaultLanguageDriver);
        }

        return factory.getObject();
    }

    private void applyConfiguration(SqlSessionFactoryBean factory) {
        org.apache.ibatis.session.Configuration configuration = this.properties.getConfiguration();
        if (configuration == null && !StringUtils.hasText(this.properties.getConfigLocation())) {
            configuration = new org.apache.ibatis.session.Configuration();
        }
        if (configuration != null && !CollectionUtils.isEmpty(this.configurationCustomizers)) {
            for (ConfigurationCustomizer customizer : this.configurationCustomizers) {
                customizer.customize(configuration);
            }
        }
        factory.setConfiguration(configuration);
    }

    @Bean
    public SqlSessionTemplate sqlSessionTemplateOne(@Qualifier(value = "sqlSessionFactoryOne") SqlSessionFactory sqlSessionFactory) {
        ExecutorType executorType = this.properties.getExecutorType();
        if (executorType != null) {
            return new SqlSessionTemplate(sqlSessionFactory, executorType);
        } else {
            return new SqlSessionTemplate(sqlSessionFactory);
        }
    }
}

接下来新建spring.factories并添加一行配置

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
cloud.mysteriousman.configuration.MyMybatisAutoConfiguration

至此Mybatis数据源的自动装配模块完成,我们只需要将Mybatis的Mapper接口定义到cloud.mysteriousman.mybatis.dao这个包下就可以了。

接下来添加JPA的自动装配模块jpa-module,JPA规范比较常见的实现就是Hibernate,笔者以此为例。

@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(name = "spring.multiple-datasource",havingValue = "true")
public class MyDataSourceAutoConfiguration {
    @Bean(name = "dataSourceTwo")
    @ConfigurationProperties(prefix = "spring.datasource.two")
    public DataSource dataSourceTwo() {
        return DataSourceBuilder.create().build();
    }
}
class MyDataSourceInitializedPublisher implements BeanPostProcessor {

    @Autowired
    private ApplicationContext applicationContext;

    private DataSource dataSource;

    private JpaProperties jpaProperties;

    private HibernateProperties hibernateProperties;

    private MyDataSourceInitializedPublisher.DataSourceSchemaCreatedPublisher schemaCreatedPublisher;

    private MyDataSourceInitializedPublisher.DataSourceInitializationCompletionListener initializationCompletionListener;

    MyDataSourceInitializedPublisher(MyDataSourceInitializedPublisher.DataSourceInitializationCompletionListener completionListener) {
        this.initializationCompletionListener = completionListener;
    }

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        if (bean instanceof LocalContainerEntityManagerFactoryBean) {
            LocalContainerEntityManagerFactoryBean factory = (LocalContainerEntityManagerFactoryBean) bean;
            if (factory.getBootstrapExecutor() != null && factory.getJpaVendorAdapter() != null) {
                this.schemaCreatedPublisher = new MyDataSourceInitializedPublisher.DataSourceSchemaCreatedPublisher(factory);
                factory.setJpaVendorAdapter(this.schemaCreatedPublisher);
            }
        }
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (bean instanceof DataSource) {
            // Normally this will be the right DataSource
            this.dataSource = (DataSource) bean;
        }
        if (bean instanceof JpaProperties) {
            this.jpaProperties = (JpaProperties) bean;
        }
        if (bean instanceof HibernateProperties) {
            this.hibernateProperties = (HibernateProperties) bean;
        }
        if (bean instanceof LocalContainerEntityManagerFactoryBean && this.schemaCreatedPublisher == null) {
            LocalContainerEntityManagerFactoryBean factoryBean = (LocalContainerEntityManagerFactoryBean) bean;
            EntityManagerFactory entityManagerFactory = factoryBean.getNativeEntityManagerFactory();
            publishEventIfRequired(factoryBean, entityManagerFactory);
        }
        return bean;
    }

    private void publishEventIfRequired(LocalContainerEntityManagerFactoryBean factoryBean,
                                        EntityManagerFactory entityManagerFactory) {
        DataSource dataSource = findDataSource(factoryBean, entityManagerFactory);
        if (dataSource != null && isInitializingDatabase(dataSource)) {
            this.applicationContext.publishEvent(new DataSourceSchemaCreatedEvent(dataSource));
        }
    }

    private DataSource findDataSource(LocalContainerEntityManagerFactoryBean factoryBean,
                                      EntityManagerFactory entityManagerFactory) {
        Object dataSource = entityManagerFactory.getProperties().get("javax.persistence.nonJtaDataSource");
        if (dataSource == null) {
            dataSource = factoryBean.getPersistenceUnitInfo().getNonJtaDataSource();
        }
        return (dataSource instanceof DataSource) ? (DataSource) dataSource : this.dataSource;
    }

    private boolean isInitializingDatabase(DataSource dataSource) {
        if (this.jpaProperties == null || this.hibernateProperties == null) {
            return true; // better safe than sorry
        }
        Supplier<String> defaultDdlAuto = () -> (EmbeddedDatabaseConnection.isEmbedded(dataSource) ? "create-drop"
                : "none");
        Map<String, Object> hibernate = this.hibernateProperties.determineHibernateProperties(
                this.jpaProperties.getProperties(), new HibernateSettings().ddlAuto(defaultDdlAuto));
        return hibernate.containsKey("hibernate.hbm2ddl.auto") || !hibernate
                .getOrDefault("javax.persistence.schema-generation.database.action", "none").equals("none");
    }

    /**
     * {@link ApplicationListener} that, upon receiving {@link ContextRefreshedEvent},
     * blocks until any asynchronous DataSource initialization has completed.
     */
    static class DataSourceInitializationCompletionListener
            implements ApplicationListener<ContextRefreshedEvent>, Ordered, ApplicationContextAware {

        private volatile ApplicationContext applicationContext;

        private volatile Future<?> dataSourceInitialization;

        @Override
        public void onApplicationEvent(ContextRefreshedEvent event) {
            if (!event.getApplicationContext().equals(this.applicationContext)) {
                return;
            }
            Future<?> dataSourceInitialization = this.dataSourceInitialization;
            if (dataSourceInitialization != null) {
                try {
                    dataSourceInitialization.get();
                }
                catch (Exception ex) {
                    throw new RuntimeException(ex);
                }
            }
        }

        @Override
        public int getOrder() {
            return Ordered.HIGHEST_PRECEDENCE;
        }

        @Override
        public void setApplicationContext(ApplicationContext applicationContext) {
            this.applicationContext = applicationContext;
        }

    }

    /**
     * {@link ImportBeanDefinitionRegistrar} to register the
     * {@link MyDataSourceInitializedPublisher} without causing early bean instantiation
     * issues.
     */
    static class Registrar implements ImportBeanDefinitionRegistrar {

        private static final String PUBLISHER_BEAN_NAME = "dataSourceInitializedPublisher";

        private static final String COMPLETION_LISTENER_BEAN_BEAN = MyDataSourceInitializedPublisher.DataSourceInitializationCompletionListener.class
                .getName();

        @Override
        public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
                                            BeanDefinitionRegistry registry) {
            if (!registry.containsBeanDefinition(PUBLISHER_BEAN_NAME)) {
                MyDataSourceInitializedPublisher.DataSourceInitializationCompletionListener completionListener = new MyDataSourceInitializedPublisher.DataSourceInitializationCompletionListener();
                MyDataSourceInitializedPublisher publisher = new MyDataSourceInitializedPublisher(completionListener);
                AbstractBeanDefinition publisherDefinition = BeanDefinitionBuilder
                        .genericBeanDefinition(MyDataSourceInitializedPublisher.class, () -> publisher)
                        .getBeanDefinition();
                publisherDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
                // We don't need this one to be post processed otherwise it can cause a
                // cascade of bean instantiation that we would rather avoid.
                publisherDefinition.setSynthetic(true);
                registry.registerBeanDefinition(PUBLISHER_BEAN_NAME, publisherDefinition);
                AbstractBeanDefinition listenerDefinition = BeanDefinitionBuilder.genericBeanDefinition(
                        MyDataSourceInitializedPublisher.DataSourceInitializationCompletionListener.class, () -> completionListener).getBeanDefinition();
                listenerDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
                // We don't need this one to be post processed otherwise it can cause a
                // cascade of bean instantiation that we would rather avoid.
                listenerDefinition.setSynthetic(true);
                registry.registerBeanDefinition(COMPLETION_LISTENER_BEAN_BEAN, listenerDefinition);
            }
        }

    }

    final class DataSourceSchemaCreatedPublisher implements JpaVendorAdapter {

        private final LocalContainerEntityManagerFactoryBean factoryBean;

        private final JpaVendorAdapter delegate;

        private DataSourceSchemaCreatedPublisher(LocalContainerEntityManagerFactoryBean factoryBean) {
            this.factoryBean = factoryBean;
            this.delegate = factoryBean.getJpaVendorAdapter();
        }

        @Override
        public PersistenceProvider getPersistenceProvider() {
            return this.delegate.getPersistenceProvider();
        }

        @Override
        public String getPersistenceProviderRootPackage() {
            return this.delegate.getPersistenceProviderRootPackage();
        }

        @Override
        public Map<String, ?> getJpaPropertyMap(PersistenceUnitInfo pui) {
            return this.delegate.getJpaPropertyMap(pui);
        }

        @Override
        public Map<String, ?> getJpaPropertyMap() {
            return this.delegate.getJpaPropertyMap();
        }

        @Override
        public JpaDialect getJpaDialect() {
            return this.delegate.getJpaDialect();
        }

        @Override
        public Class<? extends EntityManagerFactory> getEntityManagerFactoryInterface() {
            return this.delegate.getEntityManagerFactoryInterface();
        }

        @Override
        public Class<? extends EntityManager> getEntityManagerInterface() {
            return this.delegate.getEntityManagerInterface();
        }

        @Override
        public void postProcessEntityManagerFactory(EntityManagerFactory entityManagerFactory) {
            this.delegate.postProcessEntityManagerFactory(entityManagerFactory);
            AsyncTaskExecutor bootstrapExecutor = this.factoryBean.getBootstrapExecutor();
            if (bootstrapExecutor != null) {
                MyDataSourceInitializedPublisher.this.initializationCompletionListener.dataSourceInitialization = bootstrapExecutor
                        .submit(() -> MyDataSourceInitializedPublisher.this.publishEventIfRequired(this.factoryBean,
                                entityManagerFactory));
            }
        }

    }
}
class MyHibernateDefaultDdlAutoProvider implements SchemaManagementProvider {

    private final Iterable<SchemaManagementProvider> providers;

    MyHibernateDefaultDdlAutoProvider(Iterable<SchemaManagementProvider> providers) {
        this.providers = providers;
    }

    String getDefaultDdlAuto(DataSource dataSource) {
        if (!EmbeddedDatabaseConnection.isEmbedded(dataSource)) {
            return "none";
        }
        SchemaManagement schemaManagement = getSchemaManagement(dataSource);
        if (SchemaManagement.MANAGED.equals(schemaManagement)) {
            return "none";
        }
        return "create-drop";
    }

    @Override
    public SchemaManagement getSchemaManagement(DataSource dataSource) {
        return StreamSupport.stream(this.providers.spliterator(), false)
                .map((provider) -> provider.getSchemaManagement(dataSource)).filter(SchemaManagement.MANAGED::equals)
                .findFirst().orElse(SchemaManagement.UNMANAGED);
    }

}
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(JpaProperties.class)
@Import(MyDataSourceInitializedPublisher.Registrar.class)
abstract class MyJpaBaseConfiguration implements BeanFactoryAware {

    private final DataSource dataSource;

    private final JpaProperties properties;

    private final JtaTransactionManager jtaTransactionManager;

    private ConfigurableListableBeanFactory beanFactory;

    protected MyJpaBaseConfiguration(DataSource dataSource, JpaProperties properties,
                                   ObjectProvider<JtaTransactionManager> jtaTransactionManager) {
        this.dataSource = dataSource;
        this.properties = properties;
        this.jtaTransactionManager = jtaTransactionManager.getIfAvailable();
    }

    @Bean(name = "transactionManagerTwo")
    public PlatformTransactionManager transactionManagerTwo(
            ObjectProvider<TransactionManagerCustomizers> transactionManagerCustomizers) {
        JpaTransactionManager transactionManager = new JpaTransactionManager();
        transactionManagerCustomizers.ifAvailable((customizers) -> customizers.customize(transactionManager));
        return transactionManager;
    }

    @Bean
    @ConditionalOnMissingBean
    public JpaVendorAdapter jpaVendorAdapter() {
        AbstractJpaVendorAdapter adapter = createJpaVendorAdapter();
        adapter.setShowSql(this.properties.isShowSql());
        if (this.properties.getDatabase() != null) {
            adapter.setDatabase(this.properties.getDatabase());
        }
        if (this.properties.getDatabasePlatform() != null) {
            adapter.setDatabasePlatform(this.properties.getDatabasePlatform());
        }
        adapter.setGenerateDdl(this.properties.isGenerateDdl());
        return adapter;
    }

    @Bean
    @ConditionalOnMissingBean
    public EntityManagerFactoryBuilder entityManagerFactoryBuilder(JpaVendorAdapter jpaVendorAdapter,
                                                                   ObjectProvider<PersistenceUnitManager> persistenceUnitManager,
                                                                   ObjectProvider<EntityManagerFactoryBuilderCustomizer> customizers) {
        EntityManagerFactoryBuilder builder = new EntityManagerFactoryBuilder(jpaVendorAdapter,
                this.properties.getProperties(), persistenceUnitManager.getIfAvailable());
        customizers.orderedStream().forEach((customizer) -> customizer.customize(builder));
        return builder;
    }

    @Bean
    @Primary
    @ConditionalOnMissingBean({ LocalContainerEntityManagerFactoryBean.class, EntityManagerFactory.class })
    public LocalContainerEntityManagerFactoryBean entityManagerFactory(EntityManagerFactoryBuilder factoryBuilder) {
        Map<String, Object> vendorProperties = getVendorProperties();
        customizeVendorProperties(vendorProperties);
        return factoryBuilder.dataSource(this.dataSource).packages(getPackagesToScan()).properties(vendorProperties)
                .mappingResources(getMappingResources()).jta(isJta()).build();
    }

    protected abstract AbstractJpaVendorAdapter createJpaVendorAdapter();

    protected abstract Map<String, Object> getVendorProperties();

    /**
     * Customize vendor properties before they are used. Allows for post processing (for
     * example to configure JTA specific settings).
     * @param vendorProperties the vendor properties to customize
     */
    protected void customizeVendorProperties(Map<String, Object> vendorProperties) {
    }

    protected String[] getPackagesToScan() {
        List<String> packages = EntityScanPackages.get(this.beanFactory).getPackageNames();
        if (packages.isEmpty() && AutoConfigurationPackages.has(this.beanFactory)) {
            packages = AutoConfigurationPackages.get(this.beanFactory);
        }
        return StringUtils.toStringArray(packages);
    }

    private String[] getMappingResources() {
        List<String> mappingResources = this.properties.getMappingResources();
        return (!ObjectUtils.isEmpty(mappingResources) ? StringUtils.toStringArray(mappingResources) : null);
    }

    /**
     * Return the JTA transaction manager.
     * @return the transaction manager or {@code null}
     */
    protected JtaTransactionManager getJtaTransactionManager() {
        return this.jtaTransactionManager;
    }

    /**
     * Returns if a JTA {@link PlatformTransactionManager} is being used.
     * @return if a JTA transaction manager is being used
     */
    protected final boolean isJta() {
        return (this.jtaTransactionManager != null);
    }

    /**
     * Return the {@link JpaProperties}.
     * @return the properties
     */
    protected final JpaProperties getProperties() {
        return this.properties;
    }

    /**
     * Return the {@link DataSource}.
     * @return the data source
     */
    protected final DataSource getDataSource() {
        return this.dataSource;
    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        this.beanFactory = (ConfigurableListableBeanFactory) beanFactory;
    }

    @Configuration(proxyBeanMethods = false)
    @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
    @ConditionalOnClass(WebMvcConfigurer.class)
    @ConditionalOnMissingBean({ OpenEntityManagerInViewInterceptor.class, OpenEntityManagerInViewFilter.class })
    @ConditionalOnMissingFilterBean(OpenEntityManagerInViewFilter.class)
    @ConditionalOnProperty(prefix = "spring.jpa", name = "open-in-view", havingValue = "true", matchIfMissing = true)
    protected static class JpaWebConfiguration {

        private static final Log logger = LogFactory.getLog(JpaWebConfiguration.class);

        private final JpaProperties jpaProperties;

        protected JpaWebConfiguration(JpaProperties jpaProperties) {
            this.jpaProperties = jpaProperties;
        }

        @Bean
        public OpenEntityManagerInViewInterceptor openEntityManagerInViewInterceptor() {
            if (this.jpaProperties.getOpenInView() == null) {
                logger.warn("spring.jpa.open-in-view is enabled by default. "
                        + "Therefore, database queries may be performed during view "
                        + "rendering. Explicitly configure spring.jpa.open-in-view to disable this warning");
            }
            return new OpenEntityManagerInViewInterceptor();
        }

        @Bean
        public WebMvcConfigurer openEntityManagerInViewInterceptorConfigurer(
                OpenEntityManagerInViewInterceptor interceptor) {
            return new WebMvcConfigurer() {

                @Override
                public void addInterceptors(InterceptorRegistry registry) {
                    registry.addWebRequestInterceptor(interceptor);
                }

            };
        }

    }

}
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(HibernateProperties.class)
class MyHibernateJpaConfiguration extends MyJpaBaseConfiguration {

    private static final Log logger = LogFactory.getLog(MyHibernateJpaConfiguration.class);

    private static final String JTA_PLATFORM = "hibernate.transaction.jta.platform";

    private static final String PROVIDER_DISABLES_AUTOCOMMIT = "hibernate.connection.provider_disables_autocommit";

    /**
     * {@code NoJtaPlatform} implementations for various Hibernate versions.
     */
    private static final String[] NO_JTA_PLATFORM_CLASSES = {
            "org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform",
            "org.hibernate.service.jta.platform.internal.NoJtaPlatform" };

    private final HibernateProperties hibernateProperties;

    private final MyHibernateDefaultDdlAutoProvider defaultDdlAutoProvider;

    private DataSourcePoolMetadataProvider poolMetadataProvider;

    private final List<HibernatePropertiesCustomizer> hibernatePropertiesCustomizers;

    MyHibernateJpaConfiguration(@Qualifier(value = "dataSourceTwo") DataSource dataSource, JpaProperties jpaProperties,
                                    ConfigurableListableBeanFactory beanFactory, ObjectProvider<JtaTransactionManager> jtaTransactionManager,
                                    HibernateProperties hibernateProperties,
                                    ObjectProvider<Collection<DataSourcePoolMetadataProvider>> metadataProviders,
                                    ObjectProvider<SchemaManagementProvider> providers,
                                    ObjectProvider<PhysicalNamingStrategy> physicalNamingStrategy,
                                    ObjectProvider<ImplicitNamingStrategy> implicitNamingStrategy,
                                    ObjectProvider<HibernatePropertiesCustomizer> hibernatePropertiesCustomizers) {
        super(dataSource, jpaProperties, jtaTransactionManager);
        this.hibernateProperties = hibernateProperties;
        this.defaultDdlAutoProvider = new MyHibernateDefaultDdlAutoProvider(providers);
        this.poolMetadataProvider = new CompositeDataSourcePoolMetadataProvider(metadataProviders.getIfAvailable());
        this.hibernatePropertiesCustomizers = determineHibernatePropertiesCustomizers(
                physicalNamingStrategy.getIfAvailable(), implicitNamingStrategy.getIfAvailable(), beanFactory,
                hibernatePropertiesCustomizers.orderedStream().collect(Collectors.toList()));
    }

    private List<HibernatePropertiesCustomizer> determineHibernatePropertiesCustomizers(
            PhysicalNamingStrategy physicalNamingStrategy, ImplicitNamingStrategy implicitNamingStrategy,
            ConfigurableListableBeanFactory beanFactory,
            List<HibernatePropertiesCustomizer> hibernatePropertiesCustomizers) {
        List<HibernatePropertiesCustomizer> customizers = new ArrayList<>();
        if (ClassUtils.isPresent("org.hibernate.resource.beans.container.spi.BeanContainer",
                getClass().getClassLoader())) {
            customizers.add((properties) -> properties.put(AvailableSettings.BEAN_CONTAINER,
                    new SpringBeanContainer(beanFactory)));
        }
        if (physicalNamingStrategy != null || implicitNamingStrategy != null) {
            customizers.add(
                    new MyHibernateJpaConfiguration.NamingStrategiesHibernatePropertiesCustomizer(physicalNamingStrategy, implicitNamingStrategy));
        }
        customizers.addAll(hibernatePropertiesCustomizers);
        return customizers;
    }

    @Override
    protected AbstractJpaVendorAdapter createJpaVendorAdapter() {
        return new HibernateJpaVendorAdapter();
    }

    @Override
    protected Map<String, Object> getVendorProperties() {
        Supplier<String> defaultDdlMode = () -> this.defaultDdlAutoProvider.getDefaultDdlAuto(getDataSource());
        return new LinkedHashMap<>(this.hibernateProperties
                .determineHibernateProperties(getProperties().getProperties(), new HibernateSettings()
                        .ddlAuto(defaultDdlMode).hibernatePropertiesCustomizers(this.hibernatePropertiesCustomizers)));
    }

    @Override
    protected void customizeVendorProperties(Map<String, Object> vendorProperties) {
        super.customizeVendorProperties(vendorProperties);
        if (!vendorProperties.containsKey(JTA_PLATFORM)) {
            configureJtaPlatform(vendorProperties);
        }
        if (!vendorProperties.containsKey(PROVIDER_DISABLES_AUTOCOMMIT)) {
            configureProviderDisablesAutocommit(vendorProperties);
        }
    }

    private void configureJtaPlatform(Map<String, Object> vendorProperties) throws LinkageError {
        JtaTransactionManager jtaTransactionManager = getJtaTransactionManager();
        // Make sure Hibernate doesn't attempt to auto-detect a JTA platform
        if (jtaTransactionManager == null) {
            vendorProperties.put(JTA_PLATFORM, getNoJtaPlatformManager());
        }
        // As of Hibernate 5.2, Hibernate can fully integrate with the WebSphere
        // transaction manager on its own.
        else if (!runningOnWebSphere()) {
            configureSpringJtaPlatform(vendorProperties, jtaTransactionManager);
        }
    }

    private void configureProviderDisablesAutocommit(Map<String, Object> vendorProperties) {
        if (isDataSourceAutoCommitDisabled() && !isJta()) {
            vendorProperties.put(PROVIDER_DISABLES_AUTOCOMMIT, "true");
        }
    }

    private boolean isDataSourceAutoCommitDisabled() {
        DataSourcePoolMetadata poolMetadata = this.poolMetadataProvider.getDataSourcePoolMetadata(getDataSource());
        return poolMetadata != null && Boolean.FALSE.equals(poolMetadata.getDefaultAutoCommit());
    }

    private boolean runningOnWebSphere() {
        return ClassUtils.isPresent("com.ibm.websphere.jtaextensions.ExtendedJTATransaction",
                getClass().getClassLoader());
    }

    private void configureSpringJtaPlatform(Map<String, Object> vendorProperties,
                                            JtaTransactionManager jtaTransactionManager) {
        try {
            vendorProperties.put(JTA_PLATFORM, new SpringJtaPlatform(jtaTransactionManager));
        }
        catch (LinkageError ex) {
            // NoClassDefFoundError can happen if Hibernate 4.2 is used and some
            // containers (e.g. JBoss EAP 6) wrap it in the superclass LinkageError
            if (!isUsingJndi()) {
                throw new IllegalStateException(
                        "Unable to set Hibernate JTA platform, are you using the correct version of Hibernate?", ex);
            }
            // Assume that Hibernate will use JNDI
            if (logger.isDebugEnabled()) {
                logger.debug("Unable to set Hibernate JTA platform : " + ex.getMessage());
            }
        }
    }

    private boolean isUsingJndi() {
        try {
            return JndiLocatorDelegate.isDefaultJndiEnvironmentAvailable();
        }
        catch (Error ex) {
            return false;
        }
    }

    private Object getNoJtaPlatformManager() {
        for (String candidate : NO_JTA_PLATFORM_CLASSES) {
            try {
                return Class.forName(candidate).newInstance();
            }
            catch (Exception ex) {
                // Continue searching
            }
        }
        throw new IllegalStateException(
                "No available JtaPlatform candidates amongst " + Arrays.toString(NO_JTA_PLATFORM_CLASSES));
    }

    private static class NamingStrategiesHibernatePropertiesCustomizer implements HibernatePropertiesCustomizer {

        private final PhysicalNamingStrategy physicalNamingStrategy;

        private final ImplicitNamingStrategy implicitNamingStrategy;

        NamingStrategiesHibernatePropertiesCustomizer(PhysicalNamingStrategy physicalNamingStrategy,
                                                      ImplicitNamingStrategy implicitNamingStrategy) {
            this.physicalNamingStrategy = physicalNamingStrategy;
            this.implicitNamingStrategy = implicitNamingStrategy;
        }

        @Override
        public void customize(Map<String, Object> hibernateProperties) {
            if (this.physicalNamingStrategy != null) {
                hibernateProperties.put("hibernate.physical_naming_strategy", this.physicalNamingStrategy);
            }
            if (this.implicitNamingStrategy != null) {
                hibernateProperties.put("hibernate.implicit_naming_strategy", this.implicitNamingStrategy);
            }
        }

    }

}
@Configuration(proxyBeanMethods = false)
@AutoConfigureAfter(value = MyDataSourceAutoConfiguration.class)
@EnableConfigurationProperties(JpaProperties.class)
@ConditionalOnProperty(name = "spring.multiple-datasource",havingValue = "true")
@EnableJpaRepositories(
        basePackages = {"cloud.mysteriousman.jpa.repository"},
        transactionManagerRef = "transactionManagerTwo"
)
@EntityScan("cloud.mysteriousman.jpa.entity")
@Import(value = {MyHibernateJpaConfiguration.class})
public class MyJpaAutoConfiguration {

}

接下来新建spring.factories并添加一行配置

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
cloud.mysteriousman.configuration.MyDataSourceAutoConfiguration,\
cloud.mysteriousman.configuration.MyJpaAutoConfiguration

至此JPA数据源的自动装配模块完成,我们只需要将JPA的Repository接口定义到cloud.mysteriousman.jpa.repository这个包下,同时JPA实体定义到cloud.mysteriousman.jpa.entity包就可以了。

3.事务管理

到这里只要我们业务系统微服务同时依赖mybatis-module和jpa-module就可以多数据源了,只要添加配置

 

[success]

spring.multiple-datasource=true

spring.datasource.one…

spring.datasource.two…

[/success]

 

就可以正常使用了,但是我们还缺少最重要的一步

@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(name = "spring.multiple-datasource",havingValue = "true")
public class MultipleTransactionConfiguration {

@Lazy
@Bean(name = "chainedTransactionManager")
public ChainedTransactionManager transactionManager(@Qualifier("transactionManagerOne") PlatformTransactionManager platformTransactionManagerOne,
                                                    @Qualifier("transactionManagerTwo") PlatformTransactionManager platformTransactionManagerTwo) {
            return new ChainedTransactionManager(platformTransactionManagerOne, platformTransactionManagerTwo);
}
}

我们需要一个链式的事务管理器,这样在需要的时候两个数据源事务可以同时回滚。

4.结束语

成功整合!

正文完
 
mysteriousman
版权声明:本站原创文章,由 mysteriousman 2022-04-17发表,共计28211字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。
评论(没有评论)