Spring Data JPA的多數(shù)據(jù)源及分布式事務(wù)實現(xiàn)

一.Spring data JPA的多數(shù)據(jù)源實現(xiàn)

  1. 將數(shù)據(jù)源對象作為參數(shù),傳遞到調(diào)用方法內(nèi)部,這種方式增加額外的編碼。
  2. 將Repository操作接口分包存放,Spring掃描不同的包,自動注入不同的數(shù)據(jù)源。這種方式實現(xiàn)簡單,也是一種“約定大于配置”思想的典型應(yīng)用。
  3. 使用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ù)持久層配置

  1. 創(chuàng)建cn.lsp.springboot.model.testdb包,將Article實體放入此包中;
  2. 創(chuàng)建cn.lsp.springboot.repository.testdb包,將ArticleRepository放入此包中;
  3. 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ù)持久層配置

  1. 創(chuàng)建cn.lsp.springboot.model.testdb2包,將Comment實體放入此包中;
  2. 創(chuàng)建cn.lsp.springboot.repository.testdb2包,將CommentRepository放入此包中;
  3. testdb2數(shù)據(jù)庫的JPA數(shù)據(jù)持久層配置,因為這一組配置不是默認(rèn)配置,該組數(shù)據(jù)源不是默認(rèn)數(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("詳細(xì)介紹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ī)模進(jìn)行調(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("詳細(xì)介紹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ù)插入都失敗。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容