一.Spring data JPA的多數(shù)據(jù)源實現(xiàn)
- 將數(shù)據(jù)源對象作為參數(shù)贴彼,傳遞到調(diào)用方法內(nèi)部,這種方式增加額外的編碼。
- 將Repository操作接口分包存放,Spring掃描不同的包吐葵,自動注入不同的數(shù)據(jù)源。這種方式實現(xiàn)簡單桥氏,也是一種“約定大于配置”思想的典型應(yīng)用温峭。
- 使用Spring AOP面向切面編程,然后在持久層接口方法上面加注解字支,不同的注解使用表示使用不同的數(shù)據(jù)源凤藏。
本文將以第二種方式實現(xiàn)JPA的多數(shù)據(jù)源支持。
1.1 修改application.yml配置多數(shù)據(jù)源
配置2個數(shù)據(jù)源堕伪,primary數(shù)據(jù)源:testdb數(shù)據(jù)庫揖庄,secondary數(shù)據(jù)源:testdb2數(shù)據(jù)庫。
spring:
datasource:
primary:
jdbc-url: jdbc:mysql://localhost:3306/testdb?useUnicode=true&characterEncoding=utf-8&useSSL=false
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
secondary:
jdbc-url: jdbc:mysql://localhost:3306/testdb2?useUnicode=true&characterEncoding=utf-8&useSSL=false
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
jpa:
database-platform: org.hibernate.dialect.MySQL5InnoDBDialect
hibernate:
ddl-auto: update
database: mysql
show-sql: true
testdb數(shù)據(jù)庫中存在article表欠雌,testdb2中存在comment表
1.2 testdb數(shù)據(jù)庫的數(shù)據(jù)持久層配置
- 創(chuàng)建cn.lsp.springboot.model.testdb包蹄梢,將Article實體放入此包中;
- 創(chuàng)建cn.lsp.springboot.repository.testdb包桨昙,將ArticleRepository放入此包中检号;
- testdb數(shù)據(jù)庫的JPA數(shù)據(jù)持久層配置,需要配置:
- DataSource數(shù)據(jù)源
- EntityManager 實體管理器
- EntityManagerFactoryBean 實體管理器工廠
- PlatformTransactionManager 事務(wù)管理器
import org.springframework.boot.autoconfigure.orm.jpa.HibernateProperties;
import org.springframework.boot.autoconfigure.orm.jpa.HibernateSettings;
import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.annotation.Resource;
import javax.persistence.EntityManager;
import javax.sql.DataSource;
import java.util.Map;
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
entityManagerFactoryRef="entityManagerFactoryPrimary",
transactionManagerRef="transactionManagerPrimary",
basePackages= {"cn.lsp.springboot.repository.testdb"})
public class JPAPrimaryConfig2 {
@Resource
private JpaProperties jpaProperties;
@Resource
private HibernateProperties hibernateProperties;
@Primary
@Bean(name = "primaryDataSource")
@ConfigurationProperties(prefix="spring.datasource.primary") //使用application.yml的primary數(shù)據(jù)源配置
public DataSource primaryDataSource() {
return DataSourceBuilder.create().build();
}
@Primary
@Bean(name = "entityManagerPrimary") //primary實體管理器
public EntityManager entityManager(EntityManagerFactoryBuilder builder) {
return entityManagerFactoryPrimary(builder).getObject().createEntityManager();
}
@Primary
@Bean(name = "entityManagerFactoryPrimary") //primary實體工廠
public LocalContainerEntityManagerFactoryBean entityManagerFactoryPrimary (EntityManagerFactoryBuilder builder) {
Map<String,Object> properties =
hibernateProperties.determineHibernateProperties(
jpaProperties.getProperties(),
new HibernateSettings());
return builder.dataSource(primaryDataSource())
.properties(properties)
.packages("cn.lsp.springboot.model.testdb")
.persistenceUnit("primaryPersistenceUnit")
.build();
}
@Primary
@Bean(name = "transactionManagerPrimary") //primary事務(wù)管理器
public PlatformTransactionManager transactionManagerPrimary(EntityManagerFactoryBuilder builder) {
return new JpaTransactionManager(entityManagerFactoryPrimary(builder).getObject());
}
}
1.3 testdb2數(shù)據(jù)庫的數(shù)據(jù)持久層配置
- 創(chuàng)建cn.lsp.springboot.model.testdb2包,將Comment實體放入此包中蛙酪;
- 創(chuàng)建cn.lsp.springboot.repository.testdb2包齐苛,將CommentRepository放入此包中;
- testdb2數(shù)據(jù)庫的JPA數(shù)據(jù)持久層配置桂塞,因為這一組配置不是默認配置凹蜂,該組數(shù)據(jù)源不是默認數(shù)據(jù)源,沒有@Primary注解阁危。
import org.springframework.boot.autoconfigure.orm.jpa.HibernateProperties;
import org.springframework.boot.autoconfigure.orm.jpa.HibernateSettings;
import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import java.util.Map;
import javax.annotation.Resource;
import javax.persistence.EntityManager;
import javax.sql.DataSource;
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
entityManagerFactoryRef="entityManagerFactorySecondary",
transactionManagerRef="transactionManagerSecondary",
basePackages= { "cn.lsp.springboot.repository.testdb2" })
public class JPASecondaryConfig {
@Resource
private JpaProperties jpaProperties;
@Resource
private HibernateProperties hibernateProperties;
@Bean(name = "secondaryDataSource")
@ConfigurationProperties(prefix="spring.datasource.secondary") //使用application.yml的secondary數(shù)據(jù)源配置
public DataSource secondaryDataSource() {
return DataSourceBuilder.create().build();
}
@Bean(name = "entityManagerSecondary") //secondary實體管理器
public EntityManager entityManager(EntityManagerFactoryBuilder builder) {
return entityManagerFactorySecondary(builder).getObject().createEntityManager();
}
@Bean(name = "entityManagerFactorySecondary")
public LocalContainerEntityManagerFactoryBean entityManagerFactorySecondary (EntityManagerFactoryBuilder builder) {
Map<String,Object> properties =
hibernateProperties.determineHibernateProperties(
jpaProperties.getProperties(),
new HibernateSettings());
return builder
.dataSource(secondaryDataSource())
.properties(properties)
.packages("cn.lsp.springboot.model.testdb2")
.persistenceUnit("secondaryPersistenceUnit")
.build();
}
@Bean(name = "transactionManagerSecondary")
PlatformTransactionManager transactionManagerSecondary(EntityManagerFactoryBuilder builder) {
return new JpaTransactionManager(entityManagerFactorySecondary(builder).getObject());
}
}
1.4 多數(shù)據(jù)源測試
@Slf4j
@SpringBootTest
public class JpaJtaTest {
@Resource
private ArticleRepository articleRepository;
@Resource
private CommentRepository commentRepository;
@Test
public void testJpaJta(){
Article article = new Article();
article.setTitle("SpringBoot實戰(zhàn)");
article.setContent("詳細介紹SpringBoot的各種姿勢");
article.setAuthor("William");
article.setCreateTime(new Date());
Article savedArticle = articleRepository.save(article);
Comment comment = new Comment();
comment.setName("Tom");
comment.setContent("內(nèi)容詳實玛痊,偏實戰(zhàn)");
comment.setCreateTime(new Date());
comment.setArticleId(savedArticle.getId());
commentRepository.save(comment);
}
}
二.JPA+atomikos實現(xiàn)分布式事務(wù)
2.1整合JTA(atomikos)
通過maven坐標(biāo)引入JTA atomikos。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jta-atomikos</artifactId>
</dependency>
在第一節(jié)的配置基礎(chǔ)上狂打,把jdbc-url改成url擂煞,然后在spring前綴下面加:
spring:
jta:
atomikos:
datasource:
max-pool-size: 20
borrow-connection-timeout: 60
connectionfactory:
max-pool-size: 20
borrow-connection-timeout: 60
- max-pool-size表示數(shù)據(jù)連接池最大連接數(shù),請根據(jù)自己的應(yīng)用規(guī)模進行調(diào)整
- borrow-connection-timeout表示連接從連接池“借出”之后的超時時間趴乡,超時將拋出異常
2.2分布式事務(wù)管理器
刪掉第一節(jié)的數(shù)據(jù)源配置及事務(wù)配置代碼对省,加入如下代碼,AtomikosJtaPlatform為固定代碼不需修改晾捏;
public class AtomikosJtaPlatform extends AbstractJtaPlatform {
private static final long serialVersionUID = 1L;
static TransactionManager transactionManager;
static UserTransaction transaction;
@Override
protected TransactionManager locateTransactionManager() {
return transactionManager;
}
@Override
protected UserTransaction locateUserTransaction() {
return transaction;
}
}
事務(wù)管理器的配置JPAAtomikosTransactionConfig 蒿涎,以下除了設(shè)置JPA特性的部分,為固定代碼不需修改;
@Configuration
@ComponentScan
@EnableTransactionManagement
public class JPAAtomikosTransactionConfig {
@Bean
public PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
//設(shè)置JPA特性
@Bean
public JpaVendorAdapter jpaVendorAdapter() {
HibernateJpaVendorAdapter hibernateJpaVendorAdapter = new HibernateJpaVendorAdapter();
//顯示sql
hibernateJpaVendorAdapter.setShowSql(true);
//自動生成/更新表
hibernateJpaVendorAdapter.setGenerateDdl(true);
//設(shè)置數(shù)據(jù)庫類型
hibernateJpaVendorAdapter.setDatabase(Database.MYSQL);
return hibernateJpaVendorAdapter;
}
@Bean(name = "userTransaction")
public UserTransaction userTransaction() throws Throwable {
UserTransactionImp userTransactionImp = new UserTransactionImp();
userTransactionImp.setTransactionTimeout(10000);
return userTransactionImp;
}
@Bean(name = "atomikosTransactionManager", initMethod = "init", destroyMethod = "close")
public TransactionManager atomikosTransactionManager() throws Throwable {
UserTransactionManager userTransactionManager = new UserTransactionManager();
userTransactionManager.setForceShutdown(false);
AtomikosJtaPlatform.transactionManager = userTransactionManager;
return userTransactionManager;
}
@Bean(name = "transactionManager")
@DependsOn({"userTransaction", "atomikosTransactionManager"})
public PlatformTransactionManager transactionManager() throws Throwable {
UserTransaction userTransaction = userTransaction();
AtomikosJtaPlatform.transaction = userTransaction;
TransactionManager atomikosTransactionManager = atomikosTransactionManager();
return new JtaTransactionManager(userTransaction, atomikosTransactionManager);
}
}
2.3數(shù)據(jù)源及實體管理配置(多數(shù)據(jù)源多份)
設(shè)置第一個數(shù)據(jù)庫的primary數(shù)據(jù)源及實體掃描管理(掃描testdb目錄)惦辛,實體管理器劳秋、數(shù)據(jù)源都要加上primary,以示區(qū)分:
@Configuration
@DependsOn("transactionManager")
@EnableJpaRepositories(basePackages = "cn.lsp.springboot.repository.testdb", //注意這里
entityManagerFactoryRef = "primaryEntityManager",
transactionManagerRef = "transactionManager")
public class JPAPrimaryConfig {
@Autowired
private JpaVendorAdapter jpaVendorAdapter;
//primary
@Primary
@Bean(name = "primaryDataSourceProperties")
@ConfigurationProperties(prefix = "spring.datasource.primary") //注意這里
public DataSourceProperties primaryDataSourceProperties() {
return new DataSourceProperties();
}
@Primary
@Bean(name = "primaryDataSource", initMethod = "init", destroyMethod = "close")
@ConfigurationProperties(prefix = "spring.datasource.primary")
public DataSource primaryDataSource() throws SQLException {
MysqlXADataSource mysqlXaDataSource = new MysqlXADataSource();
mysqlXaDataSource.setUrl(primaryDataSourceProperties().getUrl());
mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true);
mysqlXaDataSource.setPassword(primaryDataSourceProperties().getPassword());
mysqlXaDataSource.setUser(primaryDataSourceProperties().getUsername());
AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean();
xaDataSource.setXaDataSource(mysqlXaDataSource);
xaDataSource.setUniqueResourceName("primary");
xaDataSource.setBorrowConnectionTimeout(60);
xaDataSource.setMaxPoolSize(20);
return xaDataSource;
}
@Primary
@Bean(name = "primaryEntityManager")
@DependsOn("transactionManager")
public LocalContainerEntityManagerFactoryBean primaryEntityManager() throws Throwable {
Map<String, Object> properties = new HashMap<>();
properties.put("hibernate.transaction.jta.platform", AtomikosJtaPlatform.class.getName());
properties.put("javax.persistence.transactionType", "JTA");
LocalContainerEntityManagerFactoryBean entityManager = new LocalContainerEntityManagerFactoryBean();
entityManager.setJtaDataSource(primaryDataSource());
entityManager.setJpaVendorAdapter(jpaVendorAdapter);
//這里要修改成主數(shù)據(jù)源的掃描包
entityManager.setPackagesToScan("cn.lsp.springboot.model.testdb");
entityManager.setPersistenceUnitName("primaryPersistenceUnit");
entityManager.setJpaPropertyMap(properties);
return entityManager;
}
}
設(shè)置第二個數(shù)據(jù)庫的數(shù)據(jù)源及實體掃描管理(掃描testdb2目錄):
@Configuration
@DependsOn("transactionManager")
@EnableJpaRepositories(basePackages = "cn.lsp.springboot.repository.testdb2", //注意這里
entityManagerFactoryRef = "secondaryEntityManager",
transactionManagerRef = "transactionManager")
public class JPASecondaryConfig2 {
@Autowired
private JpaVendorAdapter jpaVendorAdapter;
@Bean(name = "secondaryDataSourceProperties")
@ConfigurationProperties(prefix = "spring.datasource.secondary") //注意這里
public DataSourceProperties masterDataSourceProperties() {
return new DataSourceProperties();
}
@Bean(name = "secondaryDataSource", initMethod = "init", destroyMethod = "close")
@ConfigurationProperties(prefix = "spring.datasource.secondary")
public DataSource masterDataSource() throws SQLException {
MysqlXADataSource mysqlXaDataSource = new MysqlXADataSource();
mysqlXaDataSource.setUrl(masterDataSourceProperties().getUrl());
mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true);
mysqlXaDataSource.setPassword(masterDataSourceProperties().getPassword());
mysqlXaDataSource.setUser(masterDataSourceProperties().getUsername());
AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean();
xaDataSource.setXaDataSource(mysqlXaDataSource);
xaDataSource.setUniqueResourceName("secondary");
xaDataSource.setBorrowConnectionTimeout(60);
xaDataSource.setMaxPoolSize(20);
return xaDataSource;
}
@Bean(name = "secondaryEntityManager")
@DependsOn("transactionManager")
public LocalContainerEntityManagerFactoryBean masterEntityManager() throws Throwable {
Map<String, Object> properties = new HashMap<>();
properties.put("hibernate.transaction.jta.platform", AtomikosJtaPlatform.class.getName());
properties.put("javax.persistence.transactionType", "JTA");
LocalContainerEntityManagerFactoryBean entityManager = new LocalContainerEntityManagerFactoryBean();
entityManager.setJtaDataSource(masterDataSource());
entityManager.setJpaVendorAdapter(jpaVendorAdapter);
//這里要修改成主數(shù)據(jù)源的掃描包
entityManager.setPackagesToScan("cn.lsp.springboot.model.testdb2");
entityManager.setPersistenceUnitName("secondaryPersistenceUnit");
entityManager.setJpaPropertyMap(properties);
return entityManager;
}
}
- DataSourceProperties數(shù)據(jù)源配置、Datasource數(shù)據(jù)源玻淑、EntityManager實體管理器都是2套嗽冒。分別是primary和secondary
- 實體和Repository的掃描目錄也是2組,分別是testdb和testdb2
- 但是事務(wù)管理器只有一個岁忘,那就是transactionManager辛慰,是基于atomikos實現(xiàn)的。事務(wù)管理器只有一個干像,決定了不同的數(shù)據(jù)源使用同一個事務(wù)管理器帅腌,從而實現(xiàn)分布式事務(wù)。
2.4 JPA分布式事務(wù)測試
service代碼:
@Slf4j
@Service
public class ArticleServiceImpl implements ArticleService {
@Resource
private ArticleRepository articleRepository;
@Resource
private CommentRepository commentRepository;
@Override
@Transactional
public Article saveArticleAndComment(Article article, Comment comment) {
Article savedArticle = articleRepository.save(article);
comment.setArticleId(savedArticle.getId());
commentRepository.save(comment);
// int a= 2/0;
return article;
}
}
test代碼:
@Slf4j
@SpringBootTest
public class JpaJtaTest {
@Resource
private ArticleService articleService;
@Test
public void saveArticle() {
Article article = new Article();
article.setTitle("SpringBoot實戰(zhàn)");
article.setContent("詳細介紹SpringBoot的各種姿勢");
article.setAuthor("William");
article.setCreateTime(new Date());
Comment comment = new Comment();
comment.setName("Tom");
comment.setContent("內(nèi)容詳實麻汰,偏實戰(zhàn)");
comment.setCreateTime(new Date());
articleService.saveArticleAndComment(article, comment);
}
}
service里面分別向testdb插入article速客,testdb2插入comment,數(shù)據(jù)插入都成功五鲫。人為制造一個被除數(shù)為0的異常溺职,數(shù)據(jù)插入都失敗。