共计 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.结束语
成功整合!