最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

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)文章

  • SpringBoot @Scope與@RefreshScope注解使用詳解

    SpringBoot @Scope與@RefreshScope注解使用詳解

    spring的bean管理中,每個(gè)bean都有對(duì)應(yīng)的scope。在BeanDefinition中就已經(jīng)指定scope,默認(rèn)的RootBeanDefinition的scope是prototype類(lèi)型,使用@ComponentScan掃描出的BeanDefinition會(huì)指定是singleton,最常使用的也是singleton
    2022-11-11
  • Java PriorityQueue優(yōu)點(diǎn)和缺點(diǎn)面試精講

    Java PriorityQueue優(yōu)點(diǎn)和缺點(diǎn)面試精講

    這篇文章主要為大家介紹了Java面試中PriorityQueue的優(yōu)點(diǎn)和缺點(diǎn)及使用注意詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-10-10
  • Java中Collections.sort()排序方法舉例詳解

    Java中Collections.sort()排序方法舉例詳解

    很多時(shí)候都需要對(duì)一些數(shù)據(jù)進(jìn)行排序的操作,這篇文章主要給大家介紹了關(guān)于Java中Collections.sort()方法舉例詳解的相關(guān)資料,使用Collections.sort()可以使用其sort()方法來(lái)對(duì)List、Set等集合進(jìn)行排序,需要的朋友可以參考下
    2024-02-02
  • springmvc實(shí)現(xiàn)json交互-requestBody和responseBody

    springmvc實(shí)現(xiàn)json交互-requestBody和responseBody

    本文主要介紹了springmvc實(shí)現(xiàn)json交互-requestBody和responseBody的相關(guān)知識(shí)。具有很好的參考價(jià)值。下面跟著小編一起來(lái)看下吧
    2017-03-03
  • 關(guān)于feign.codec.DecodeException異常的解決方案

    關(guān)于feign.codec.DecodeException異常的解決方案

    這篇文章主要介紹了關(guān)于feign.codec.DecodeException異常的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-03-03
  • JVM類(lèi)加載,垃圾回收

    JVM類(lèi)加載,垃圾回收

    這篇文章主要介紹了JVM的幾種垃圾回收器,文中通過(guò)示例代碼介紹的很詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2021-07-07
  • springboot整合mybatis將sql打印到日志的實(shí)例詳解

    springboot整合mybatis將sql打印到日志的實(shí)例詳解

    這篇文章主要介紹了springboot整合mybatis將sql打印到日志的實(shí)例詳解,需要的朋友可以參考下
    2017-12-12
  • 在springboot中如何使用filter設(shè)置要排除的URL

    在springboot中如何使用filter設(shè)置要排除的URL

    這篇文章主要介紹了在springboot中如何使用filter設(shè)置要排除的URL,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-12-12
  • Java反射(Class類(lèi),Class對(duì)象獲取)

    Java反射(Class類(lèi),Class對(duì)象獲取)

    下面是對(duì)Java反射機(jī)制是在程序的運(yùn)行過(guò)程中,Java語(yǔ)言的反射機(jī)制的超詳細(xì)解說(shuō),點(diǎn)進(jìn)來(lái)的小伙伴不要錯(cuò)過(guò)奧
    2021-08-08
  • IDEA實(shí)現(xiàn)自定義背景圖方式

    IDEA實(shí)現(xiàn)自定義背景圖方式

    本文詳細(xì)介紹了在Idea中修改背景圖的步驟,包括點(diǎn)擊File、設(shè)置、外觀和行為、外觀、下拉找到背景圖片、獲取文件地址、調(diào)節(jié)透明度和選擇覆蓋整個(gè)編輯器
    2026-03-03

最新評(píng)論

沙雅县| 隆尧县| 台南县| 长宁县| 广南县| 修水县| 大足县| 光山县| 福鼎市| 托克逊县| 麦盖提县| 萍乡市| 会昌县| 阳高县| 普定县| 济源市| 东兴市| 馆陶县| 万年县| 台湾省| 思南县| 信阳市| 赤壁市| 深泽县| 辽宁省| 武乡县| 缙云县| 济南市| 元朗区| 台北县| 兴国县| 蒲江县| 得荣县| 安义县| 化州市| 台中县| 绍兴县| 灯塔市| 金溪县| 长春市| 遵义市|