近期给团队小伙伴做技术支持时,其从网上copy的jpa多数据源配置项目遇到了各种数据库连接异常抑或事务失效的情况,故专门新建了一个纯净对jpa多数据源配置进行了整理。本文主要展示Jpa多数据源不使用jta和使用jta分布式事务的项目配置,并编写了一些示例验证事务特性。本文演示不详尽之处,文末会附上整个项目源码下载链接,并欢迎批评指正。
springboot选择了2.3.2.RELEASE版本,在较低版本(测试了2.1.4)下会出现jta配置异常找不到jtaPlatform的错误。
spring:
main:
allow-bean-definition-overriding: true
# 使用springboot默认的hikari数据库连接池,配置比较简单明了
datasource:
common: &ds_common
minimun-idle: 5
maximum-pool-size: 15
max-life-time: 600000
idle-timeout: 300000
validation-timeout: 5000
connection-timeout: 5000
#门架原始数据查询数据库
main:
<<: *ds_common
pool-name: mainDataSource
jdbc-url: jdbc:mysql:///main?useUnicode=true&characterEncoding=UTF8&autoReconnect=true&useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: root
connection-test-query: select 1
slave:
<<: *ds_common
pool-name: slaveDataSource
jdbc-url: jdbc:mysql:///slave?useUnicode=true&characterEncoding=UTF8&autoReconnect=true&useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: root
connection-test-query: select 1
jpa:
common: &jpa_common
show-sql: true
generate-ddl: true
properties:
hibernate:
hbm2ddl:
auto: update
mysql:
<<: *jpa_common
database: mysql
database-platform: org.hibernate.dialect.MySQL57Dialect
# 非jta事务
jta:
enabled: false
logging:
file:
path: D:\demo\logs\multi-datasource
# 调试所需
# level:
# org:
# hibernate: trace
server:
port: 8888
此处有些配置是默认也列出来重复配置了一遍,方便后期项目调优调试。
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(entityManagerFactoryRef = "mainEntityManagerFactory",
transactionManagerRef = "mainTransactionManager",
// 此处的packages用于repository扫描管理
basePackages = {"com.kyq.multids.modules.main"})
public class MainDataSourceConfig {
@Value("${spring.jta.enabled: false}")
private boolean jta;
@Primary
@Bean("mysqlJpaProperties")
@ConfigurationProperties(prefix = "spring.jpa.mysql")
public JpaProperties mysqlJpaProperties() {
return new JpaProperties();
}
@Primary
@Bean("mainDataSource")
@ConfigurationProperties(prefix = "spring.datasource.main")
public DataSource mainDataSource(){
return DataSourceBuilder.create().build();
}
@Primary
@Bean("mainEntityManagerFactory")
public LocalContainerEntityManagerFactoryBean mainFactoryBean(@Qualifier("mainDataSource") DataSource dataSource,
@Qualifier("mysqlJpaProperties") JpaProperties jpaProperties){
HibernateJpaVendorAdapter jpaVendorAdapter = new HibernateJpaVendorAdapter();
jpaVendorAdapter.setGenerateDdl(jpaProperties.isGenerateDdl());
jpaVendorAdapter.setDatabasePlatform(jpaProperties.getDatabasePlatform());
jpaVendorAdapter.setShowSql(jpaProperties.isShowSql());
jpaVendorAdapter.setDatabase(jpaProperties.getDatabase());
return new EntityManagerFactoryBuilder(jpaVendorAdapter, jpaProperties.getProperties(), null)
.dataSource(dataSource)
.jta(jta)
.persistenceUnit("mainEntityManagerFactory")
// 此处的packages用于domain扫描管理
.packages("com.kyq.multids.modules.main")
.build();
}
@Primary
@Bean
public JdbcTemplate mainJdbcTemplate(@Qualifier("mainDataSource") DataSource mainDataSource){
JdbcTemplate jdbcTemplate = new JdbcTemplate(mainDataSource);
return jdbcTemplate;
}
@Primary
@Bean("mainTransactionManager")
public PlatformTransactionManager mainTransactionManager(@Qualifier("mainEntityManagerFactory") EntityManagerFactory entityManagerFactory){
return new JpaTransactionManager(entityManagerFactory);
}
// @Primary
// @Bean
// public EntityManager mainEntityManager(@Qualifier("mainEntityManagerFactory") LocalContainerEntityManagerFactoryBean entityManagerFactoryBean){
// return entityManagerFactoryBean.getObject().createEntityManager();
// }
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(entityManagerFactoryRef = "slaveEntityManagerFactory",
transactionManagerRef = "slaveTransactionManager",
basePackages = {"com.kyq.multids.modules.slave"})
public class SlaveDataSourceConfig {
@Value("${spring.jta.enabled: false}")
private boolean jta;
@Bean("slaveDataSource")
@ConfigurationProperties(prefix = "spring.datasource.slave")
public DataSource slaveDataSource(){
return DataSourceBuilder.create().build();
}
@Bean("slaveEntityManagerFactory")
public LocalContainerEntityManagerFactoryBean slaveFactoryBean(@Qualifier("slaveDataSource") DataSource dataSource,
@Qualifier("mysqlJpaProperties") JpaProperties jpaProperties){
HibernateJpaVendorAdapter jpaVendorAdapter = new HibernateJpaVendorAdapter();
jpaVendorAdapter.setGenerateDdl(jpaProperties.isGenerateDdl());
jpaVendorAdapter.setDatabasePlatform(jpaProperties.getDatabasePlatform());
jpaVendorAdapter.setShowSql(jpaProperties.isShowSql());
jpaVendorAdapter.setDatabase(jpaProperties.getDatabase());
return new EntityManagerFactoryBuilder(jpaVendorAdapter, jpaProperties.getProperties(), null)
.dataSource(dataSource)
.jta(jta)
.persistenceUnit("slaveEntityManagerFactory")
.packages("com.kyq.multids.modules.slave")
.build();
}
@Bean("slaveTransactionManager")
public PlatformTransactionManager slaveTransactionManager(@Qualifier("slaveEntityManagerFactory")EntityManagerFactory entityManagerFactory){
return new JpaTransactionManager(entityManagerFactory);
}
@Bean
public JdbcTemplate slaveJdbcTemplate(@Qualifier("slaveDataSource")DataSource slaveDataSource){
return new JdbcTemplate(slaveDataSource);
}
}
//MultiDataSourceTestServiceImpl.java
/**
* Description: com.kyq.multids.modules.index.service.impl
* 注意,因为本项目有着多个transactionManager各自管理各自的数据库,
* nonjta项目的MultiDataSourceTestServiceImpl是没有在class上添加@Transactional的。
*
* CopyRight: © 2015 CSTC. All rights reserved.
* Company: cstc
*
* @author kyq1024
* @version 1.0
* @timestamp 2021-08-18 14:27
*/
@Service
public class MultiDataSourceTestServiceImpl implements MultiDataSourceTestService {
......
/**
* 此处Qualifier注解指向的是一个beanFactory对象而不是一个entityManger对象,对此有兴趣可参见我的另一篇博客对此有详细说明。
* */
@Autowired
@Qualifier("mainEntityManagerFactory")
EntityManager mainEntityManager;
@PersistenceContext(unitName = "slaveEntityManagerFactory")
EntityManager slaveEntityManager;
/**
* 测试使用原生SQL语句跨数据库查询数据
*
* @return*/
public Map<String, Object> findDataAcrossDs(){
Map<String, Object> ret = new HashMap<String, Object>(4);
Query userQuery = mainEntityManager.createNativeQuery("select * from base_user")
.unwrap(NativeQuery.class)
.setResultTransformer(AliasToEntityMapResultTransformer.INSTANCE);
List userList = userQuery.getResultList();
Query dictQuery = slaveEntityManager.createNativeQuery("select * from base_dict")
.unwrap(NativeQuery.class)
.setResultTransformer(AliasToEntityMapResultTransformer.INSTANCE);
List dictList = dictQuery.getResultList();
ret.put("user", userList);
ret.put("dict", dictList);
return ret;
}
/**
* 测试跨数据库保存数据
* case1: 使用jdbcTemplate进行数据库修改操作,指定txManager为transactionManager时,user和dict都不会保存,回滚正常;
* case2: 指定txManager为slaveTransactionManager时,user会保存成功;
*
* 触发了两次txInfo,分别是addDictByJdbcOnError和addByTemplate
* */
// @Transactional(rollbackFor = RuntimeException.class)
@Transactional(transactionManager = "slaveTransactionManager", rollbackFor = RuntimeException.class)
public void addByTemplate(){
userService.addUserByJdbc();
dictService.addDictByJdbcOnError();
}
......
}
经过上述简单配置,已经可以实现jpa的多数据源操作,且在有需要也可使用jdbcTemplate进行原生sql操作并保证单数据源事务回滚。如果需要实现多数据源的分布式事务,则需要引入jta进行事务管理即可实现,本文就不再详述,有兴趣可以下载附件Demo查看完整的配置源码(含本文演示的源码和事务测试示例)。