欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

SpringBoot同一個(gè)方法操作多個(gè)數(shù)據(jù)源保證事務(wù)一致性

 更新時(shí)間:2024年11月22日 11:40:23   作者:編程經(jīng)驗(yàn)分享  
本文探討了在Spring Boot應(yīng)用中,如何在同一個(gè)方法中操作多個(gè)數(shù)據(jù)源并保證事務(wù)的一致性,由于聲明式事務(wù)的限制,直接使用@Transactional注解無(wú)法滿足需求,文章介紹了解決方案:編程式事務(wù),它允許在代碼級(jí)別更靈活地管理事務(wù),確保多數(shù)據(jù)源操作的事務(wù)一致性

前言

工作中開(kāi)發(fā)過(guò)多數(shù)據(jù)源的系統(tǒng),比如資產(chǎn)清查系統(tǒng),數(shù)據(jù)的存儲(chǔ)分成了兩個(gè)庫(kù),一個(gè)當(dāng)前庫(kù)和歸檔庫(kù),系統(tǒng)就需要配置兩個(gè)數(shù)據(jù)源來(lái)滿足業(yè)務(wù)需求。在常規(guī)的業(yè)務(wù)場(chǎng)景下,對(duì)兩個(gè)庫(kù)的業(yè)務(wù)操作是分開(kāi)的,井水不犯河水。但是有一個(gè)功能實(shí)現(xiàn)是個(gè)例外,就是歸檔。將當(dāng)前庫(kù)的數(shù)據(jù)進(jìn)行歸檔,需要修改當(dāng)前庫(kù)數(shù)據(jù)的狀態(tài),并將當(dāng)前庫(kù)數(shù)據(jù)插入到歸檔庫(kù)中,這就需要在同一個(gè)方法實(shí)現(xiàn)中同時(shí)操作兩個(gè)數(shù)據(jù)源,直接使用聲明式事務(wù)@Transcational注解是無(wú)法保證兩個(gè)事務(wù)的一致性的。

聲明式事務(wù)則只能做到方法級(jí)別的顆粒度,而且每個(gè)方法只能配置一個(gè)事務(wù)管理器,雖然可以將邏輯拆分到多個(gè)方法中,再為每個(gè)方法加上@Transactional注解,但還是會(huì)存在問(wèn)題,無(wú)法很好地處理多事務(wù)的業(yè)務(wù)場(chǎng)景。而這種問(wèn)題可以使用編程式事務(wù)來(lái)解決,編程式事務(wù)可以將做到代碼級(jí)別的顆粒度,更加的靈活。

前置環(huán)境

JDK8 + SringBoot2 + MySQL8

數(shù)據(jù)庫(kù)

分別創(chuàng)建數(shù)據(jù)庫(kù) test1 test2

分別在兩個(gè)數(shù)據(jù)庫(kù)中創(chuàng)建 user 表

create table user (
    id int auto_increment primary key,
    username varchar(255),
    password varchar(255)
);

pom

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
 
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
 
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
 
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
    <dependencies>

yml

server:
  port: 8888

spring:
  datasource:
    primary:
      url: jdbc:mysql://localhost:3306/test1?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true
      username: root
      password: mysql
      driver-class-name: com.mysql.cj.jdbc.Driver
    secondary:
      url: jdbc:mysql://localhost:3306/test2?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true
      username: root
      password: mysql
      driver-class-name: com.mysql.cj.jdbc.Driver

  jpa:
    primary:
      show-sql: true
      properties:
        hibernate:
          hbm2ddl:
            auto: update
          dialect: org.hibernate.dialect.MySQL5InnoDBDialect
    secondary:
      show-sql: true
      properties:
        hibernate:
          hbm2ddl:
            auto: update
          dialect: org.hibernate.dialect.MySQL5InnoDBDialect

Config

這里主要注入主庫(kù)和從庫(kù)各自的JDBCTemplateTransactionManager,以便后續(xù)使用

主庫(kù)數(shù)據(jù)源配置

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories (
        basePackages = PrimaryDatasourceAndJpaConfig.REPOSITORY_PACKAGE,
        entityManagerFactoryRef = "primaryEntityManagerFactory",
        transactionManagerRef = "primaryTransactionManager"
)
public class PrimaryDatasourceAndJpaConfig {

    private static final String REPOSITORY_PACKAGE = "com.jpa.dao.primary";
    private static final String ENTITY_PACKAGE = "com.jpa.entity.primary";

    //--------------數(shù)據(jù)源配置-------------------

    /**
     * 掃描spring.datasource.primary開(kāi)頭的配置信息
     *
     * @return 數(shù)據(jù)源配置信息
     */
    @Primary
    @Bean(name = "primaryDataSourceProperties")
    @ConfigurationProperties(prefix = "spring.datasource.primary")
    public DataSourceProperties dataSourceProperties() {
        return new DataSourceProperties();
    }

    /**
     * 取主庫(kù)數(shù)據(jù)源對(duì)象
     *
     * @param dataSourceProperties 注入名為primaryDataSourceProperties的bean
     * @return 數(shù)據(jù)源對(duì)象
     */
    @Primary
    @Bean(name = "primaryDataSource")
    public DataSource dataSource(@Qualifier("primaryDataSourceProperties") DataSourceProperties dataSourceProperties) {
        return dataSourceProperties.initializeDataSourceBuilder().build();
    }

    /**
     * 該方法僅在需要使用JdbcTemplate對(duì)象時(shí)選用
     *
     * @param dataSource 注入名為primaryDataSource的bean
     * @return 數(shù)據(jù)源JdbcTemplate對(duì)象
     */
    @Primary
    @Bean(name = "primaryJdbcTemplate")
    public JdbcTemplate jdbcTemplate(@Qualifier("primaryDataSource") DataSource dataSource) {
        return new JdbcTemplate(dataSource);
    }

    /**
     * 掃描spring.jpa.primary開(kāi)頭的配置信息
     *
     * @return jpa配置信息
     */
    @Primary
    @Bean (name = "primaryJpaProperties")
    @ConfigurationProperties (prefix = "spring.jpa.primary")
    public JpaProperties jpaProperties() {
        return new JpaProperties();
    }

    /**
     * 獲取主庫(kù)實(shí)體管理工廠對(duì)象
     *
     * @param primaryDataSource 注入名為primaryDataSource的數(shù)據(jù)源
     * @param jpaProperties     注入名為primaryJpaProperties的jpa配置信息
     * @param builder           注入EntityManagerFactoryBuilder
     * @return 實(shí)體管理工廠對(duì)象
     */
    @Primary
    @Bean(name = "primaryEntityManagerFactory")
    public LocalContainerEntityManagerFactoryBean entityManagerFactoryBean(
            @Qualifier ("primaryDataSource") DataSource primaryDataSource,
            @Qualifier("primaryJpaProperties") JpaProperties jpaProperties,
            EntityManagerFactoryBuilder builder
    ) {
        return builder
                // 設(shè)置數(shù)據(jù)源
                .dataSource(primaryDataSource)
                // 設(shè)置jpa配置
                .properties(jpaProperties.getProperties())
                // 設(shè)置實(shí)體包名
                .packages(ENTITY_PACKAGE)
                // 設(shè)置持久化單元名,用于@PersistenceContext注解獲取EntityManager時(shí)指定數(shù)據(jù)源
                .persistenceUnit("primaryPersistenceUnit").build();
    }

    /**
     * 獲取實(shí)體管理對(duì)象
     *
     * @param factory 注入名為primaryEntityManagerFactory的bean
     * @return 實(shí)體管理對(duì)象
     */
    @Primary
    @Bean(name = "primaryEntityManager")
    public EntityManager entityManager(@Qualifier("primaryEntityManagerFactory") EntityManagerFactory factory) {
        return factory.createEntityManager();
    }

    /**
     * 獲取主庫(kù)事務(wù)管理對(duì)象
     *
     * @param factory 注入名為primaryEntityManagerFactory的bean
     * @return 事務(wù)管理對(duì)象
     */
    @Primary
    @Bean(name = "primaryTransactionManager")
    public JpaTransactionManager transactionManager(@Qualifier("primaryEntityManagerFactory") EntityManagerFactory factory) {
        return new JpaTransactionManager(factory);
    }
}

從庫(kù)數(shù)據(jù)源配置

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
        basePackages = SecondaryDatasourceAndJpaConfig.REPOSITORY_PACKAGE,
        entityManagerFactoryRef = "secondaryEntityManagerFactory",
        transactionManagerRef = "secondaryTransactionManager"
)
public class SecondaryDatasourceAndJpaConfig {
    
    static final String REPOSITORY_PACKAGE = "com.jpa.dao.secondary";
    static final String ENTITY_PACKAGE = "com.jpa.entity.secondary";

    //--------------數(shù)據(jù)源配置-------------------

    /**
     * 掃描spring.datasource.secondary開(kāi)頭的配置信息
     *
     * @return 數(shù)據(jù)源配置信息
     */
    @Bean(name = "secondaryDataSourceProperties")
    @ConfigurationProperties(prefix = "spring.datasource.secondary")
    public DataSourceProperties dataSourceProperties() {
        return new DataSourceProperties();
    }

    /**
     * 獲取次數(shù)據(jù)源對(duì)象
     *
     * @param dataSourceProperties 注入名為secondaryDataSourceProperties的bean
     * @return 數(shù)據(jù)源對(duì)象
     */
    @Bean("secondaryDataSource")
    public DataSource dataSource(@Qualifier("secondaryDataSourceProperties") DataSourceProperties dataSourceProperties) {
        return dataSourceProperties.initializeDataSourceBuilder().build();
    }

    /**
     * 該方法僅在需要使用JdbcTemplate對(duì)象時(shí)選用
     *
     * @param dataSource 注入名為secondaryDataSource的bean
     * @return 數(shù)據(jù)源JdbcTemplate對(duì)象
     */
    @Bean(name = "secondaryJdbcTemplate")
    public JdbcTemplate jdbcTemplate(@Qualifier("secondaryDataSource") DataSource dataSource) {
        return new JdbcTemplate(dataSource);
    }

    /**
     * 掃描spring.jpa.secondary
     *
     * @return jpa配置信息
     */
    @Bean(name = "secondaryJpaProperties")
    @ConfigurationProperties(prefix = "spring.jpa.secondary")
    public JpaProperties jpaProperties() {
        return new JpaProperties();
    }

    /**
     * 獲取次庫(kù)實(shí)體管理工廠對(duì)象
     *
     * @param secondaryDataSource 注入名為secondaryDataSource的數(shù)據(jù)源
     * @param jpaProperties       注入名為secondaryJpaProperties的jpa配置信息
     * @param builder             注入EntityManagerFactoryBuilder
     * @return 實(shí)體管理工廠對(duì)象
     */
    @Bean(name = "secondaryEntityManagerFactory")
    public LocalContainerEntityManagerFactoryBean entityManagerFactory(
            @Qualifier("secondaryDataSource") DataSource secondaryDataSource,
            @Qualifier("secondaryJpaProperties") JpaProperties jpaProperties,
            EntityManagerFactoryBuilder builder
    ) {
        return builder
                // 設(shè)置數(shù)據(jù)源
                .dataSource(secondaryDataSource)
                // 設(shè)置jpa配置
                .properties(jpaProperties.getProperties())
                // 設(shè)置實(shí)體包名
                .packages(ENTITY_PACKAGE)
                // 設(shè)置持久化單元名,用于@PersistenceContext注解獲取EntityManager時(shí)指定數(shù)據(jù)源
                .persistenceUnit("secondaryPersistenceUnit").build();
    }

    /**
     * 獲取實(shí)體管理對(duì)象
     *
     * @param factory 注入名為secondaryEntityManagerFactory的bean
     * @return 實(shí)體管理對(duì)象
     */
    @Bean(name = "secondaryEntityManager")
    public EntityManager entityManager(@Qualifier("secondaryEntityManagerFactory") EntityManagerFactory factory) {
        return factory.createEntityManager();
    }

    /**
     * 獲取事務(wù)管理對(duì)象
     *
     * @param factory 注入名為secondaryEntityManagerFactory的bean
     * @return 事務(wù)管理對(duì)象
     */
    @Bean(name = "secondaryTransactionManager")
    public JpaTransactionManager transactionManager(@Qualifier("secondaryEntityManagerFactory") EntityManagerFactory factory) {
        return new JpaTransactionManager(factory);
    }
}

聲明式事務(wù)

錯(cuò)誤寫(xiě)法

@Service
public class TestService {

    @Resource
    JdbcTemplate primaryJdbcTemplate;

    @Resource
    JdbcTemplate secondaryJdbcTemplate;

    @Transactional
    public void method() {

        //do something 1
        
        primaryJdbcTemplate.execute("insert into user(username, password) values('張三', '123456')");

        //do something 2

        secondaryJdbcTemplate.execute("insert into user(username, password) values('李四', '123456');");

        //do something 3
    }
}

@Transactional中沒(méi)有指定事務(wù)管理器,這在單數(shù)據(jù)源系統(tǒng)中就不會(huì)有任何問(wèn)題,在單數(shù)據(jù)源系統(tǒng)中,整個(gè)Spring容器中只定義了一個(gè)事務(wù)管理器,Spring啟動(dòng)事務(wù)的時(shí)候,默認(rèn)會(huì)按類(lèi)型在容器中查找事務(wù)管理器,而容器中就只有一個(gè)事務(wù)管理器,正好拿來(lái)用,不會(huì)有問(wèn)題。

但是在多數(shù)據(jù)源系統(tǒng)中,Spring容器中是會(huì)存在多個(gè)事務(wù)管理器的,如果不指定事務(wù)管理器,如果使用的事務(wù)管理器和實(shí)際操作的數(shù)據(jù)源不一致的話,是管理不了事務(wù)的(由于配置主庫(kù)數(shù)據(jù)源使用@primary注解,所有默認(rèn)會(huì)使用主庫(kù)的事務(wù)管理器),所以在數(shù)據(jù)源系統(tǒng)中使用聲明式事務(wù),必須指定事務(wù)管理器

上面代碼將兩個(gè)數(shù)據(jù)庫(kù)操作都放在同一個(gè)方法中,無(wú)論拿到了哪個(gè)事務(wù)管理器,只要 do something 3 處發(fā)生了異常,那么其中的一個(gè)事務(wù)是不會(huì)回滾的

改進(jìn)寫(xiě)法

@Service
public class TestService {

    @Resource
    JdbcTemplate primaryJdbcTemplate;

    @Resource
    JdbcTemplate secondaryJdbcTemplate;

    @Transactional(value = "primaryTransactionManager")
    public void method1() {

        //do something 1

        primaryJdbcTemplate.execute("insert into user(username, password) values('張三', '123456')");

        //do something 2

        method2();

        //do something 5
    }

    @Transactional(value = "secondaryTransactionManager")
    public void method2() {

        //do something 3

        secondaryJdbcTemplate.execute("insert into user(username, password) values('李四', '123456');");

        //do something 4
    }
}

改進(jìn)的寫(xiě)法,將不同數(shù)據(jù)源的操作拆到不同的方法中,分別加上了@Transactional注解,并指定了對(duì)應(yīng)的事務(wù)管理器。這種寫(xiě)法相對(duì)之前的就規(guī)范了不少,但是還是存在問(wèn)題,如果在 do something 5 處發(fā)生了異常,因?yàn)?method2 方法已經(jīng)執(zhí)行結(jié)束了,事務(wù)已經(jīng)提交了,所以還是無(wú)法做到一起回滾。

編程式事務(wù)

@Service
public class TestService {

    @Resource
    JdbcTemplate primaryJdbcTemplate;

    @Resource
    JdbcTemplate secondaryJdbcTemplate;

    @Resource
    PlatformTransactionManager primaryTransactionManager;

    @Resource
    PlatformTransactionManager secondaryTransactionManager;

    public void method() {
        TransactionDefinition primaryDef = new DefaultTransactionDefinition();
        TransactionStatus primaryStatus = primaryTransactionManager.getTransaction(primaryDef);

        TransactionDefinition secondaryDef = new DefaultTransactionDefinition();
        TransactionStatus secondaryStatus = secondaryTransactionManager.getTransaction(secondaryDef);

        try {
            //do something 1

            primaryJdbcTemplate.execute("insert into user(username, password) values('張三', '123456')");

            //do something 2

            secondaryJdbcTemplate.execute("insert into user(username, password) values('李四', '123456');");

            //do something 3

            primaryTransactionManager.commit(primaryStatus);
            secondaryTransactionManager.commit(secondaryStatus);
        } catch (Exception e) {
            primaryTransactionManager.rollback(primaryStatus);
            secondaryTransactionManager.rollback(secondaryStatus);
            throw new RuntimeException(e.getMessage());
        }
    }
}

編程式事務(wù)的顆粒度時(shí)代碼級(jí)別的,可以嵌入到方法里面,這樣可以控制不同數(shù)據(jù)源的事務(wù)同時(shí)開(kāi)啟,一旦出現(xiàn)異常,則兩個(gè)事務(wù)一起回滾,這樣就保證了多數(shù)據(jù)事務(wù)的一致性。

這種實(shí)現(xiàn)實(shí)際上和分布式事務(wù)的XA模式思想一樣,只不過(guò)分布式事務(wù)管理的是分布式系統(tǒng)中不同服務(wù)不同的數(shù)據(jù)源,而這里是一個(gè)服務(wù)同一個(gè)方法中操作多個(gè)數(shù)據(jù)源。本質(zhì)上都是處理管理多數(shù)據(jù)源的事務(wù)。

到此這篇關(guān)于SpringBoot同一個(gè)方法操作多個(gè)數(shù)據(jù)源保證事務(wù)一致性的文章就介紹到這了,更多相關(guān)SpringBoot 事務(wù)一致性內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • java ConcurrentHashMap分段加鎖提高并發(fā)效率

    java ConcurrentHashMap分段加鎖提高并發(fā)效率

    這篇文章主要為大家介紹了java ConcurrentHashMap分段加鎖提高并發(fā)效率,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-12-12
  • 解決springcloud中Feign導(dǎo)入依賴(lài)為unknow的情況

    解決springcloud中Feign導(dǎo)入依賴(lài)為unknow的情況

    這篇文章主要介紹了解決springcloud中Feign導(dǎo)入依賴(lài)為unknow的情況,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-03-03
  • SpringBoot版本升級(jí)容易遇到的一些問(wèn)題

    SpringBoot版本升級(jí)容易遇到的一些問(wèn)題

    由于項(xiàng)目需求,需要將nacos 1.4.6版本升級(jí)到2.x版本,由此引發(fā)的springboot、springcloud、springcloud Alibaba一系列版本變更,本文給大家總結(jié)一下SpringBoot版本升級(jí)容易遇到的一些問(wèn)題,需要的朋友可以參考下
    2023-12-12
  • Spring Cloud Alibaba Nacos Config進(jìn)階使用

    Spring Cloud Alibaba Nacos Config進(jìn)階使用

    這篇文章主要介紹了Spring Cloud Alibaba Nacos Config進(jìn)階使用,文中使用企業(yè)案例,圖文并茂的展示了Nacos Config的使用,感興趣的小伙伴可以看一看
    2021-08-08
  • 基于JavaMail的Java實(shí)現(xiàn)復(fù)雜郵件發(fā)送功能

    基于JavaMail的Java實(shí)現(xiàn)復(fù)雜郵件發(fā)送功能

    這篇文章主要為大家詳細(xì)介紹了基于JavaMail的Java實(shí)現(xiàn)復(fù)雜郵件發(fā)送功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-09-09
  • SpringCloud中的灰度路由使用詳解

    SpringCloud中的灰度路由使用詳解

    這篇文章主要介紹了SpringCloud中的灰度路由使用詳解,在微服務(wù)中,?通常為了高可用,?同一個(gè)服務(wù)往往采用集群方式部署,?即同時(shí)存在幾個(gè)相同的服務(wù),而灰度的核心就?是路由,?通過(guò)我們特定的策略去調(diào)用目標(biāo)服務(wù)線路,需要的朋友可以參考下
    2023-08-08
  • Java GUI圖形界面開(kāi)發(fā)實(shí)現(xiàn)小型計(jì)算器流程詳解

    Java GUI圖形界面開(kāi)發(fā)實(shí)現(xiàn)小型計(jì)算器流程詳解

    本文章向大家介紹Java GUI圖形界面開(kāi)發(fā)實(shí)現(xiàn)小型計(jì)算器,主要包括布局管理器使用實(shí)例、應(yīng)用技巧、基本知識(shí)點(diǎn)總結(jié)和需要注意事項(xiàng),具有一定的參考價(jià)值,需要的朋友可以參考一下
    2022-08-08
  • 最新評(píng)論