SpringBoot整合mybatisPlus及分布式事務(wù)實(shí)現(xiàn)

一.整合mybatisPlus操作數(shù)據(jù)庫(kù)

1.1 MyBatis-Plus簡(jiǎn)介

MyBatis-Plus(簡(jiǎn)稱(chēng) MP)是一個(gè)MyBatis的增強(qiáng)工具,在 MyBatis 的基礎(chǔ)上只做增強(qiáng)不做改變,為簡(jiǎn)化開(kāi)發(fā)、提高效率而生。

1.2SpringBoot集成MybatisPlus

1.通過(guò)maven坐標(biāo)將mybatis-plus-boot-starter以及數(shù)據(jù)庫(kù)驅(qū)動(dòng)引入到Spring Boot項(xiàng)目里面來(lái)。

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.3.2</version>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>

2.保證application.yml里面有數(shù)據(jù)庫(kù)連接的配置。

spring:
  datasource:
    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

3.配置Mybatis的Mapper類(lèi)文件的包掃描路徑

@MapperScan(basePackages = {"cn.lsp.springboot.mapper"})
@SpringBootApplication
public class SpringBootMybatisPlusApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringBootMybatisPlusApplication.class, args);
    }
}

1.3Mapper繼承實(shí)現(xiàn)

如果我們操作數(shù)據(jù)庫(kù)中的article表,我們需要按照article表的結(jié)構(gòu)創(chuàng)建一個(gè)實(shí)體類(lèi)。

@Data
@Builder
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class Article {

    // id隨數(shù)據(jù)庫(kù)自增
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;

    private String author;

    private String title;

    private String content;

    private Date createTime;
}

然后寫(xiě)一個(gè)接口ArticleMapper ,繼承自BaseMapper,泛型是Article實(shí)體類(lèi)。

public interface ArticleMapper extends BaseMapper<Article> {

}

BaseMapper中默認(rèn)幫我們提供了若干的增刪改查基礎(chǔ)實(shí)現(xiàn),由于ArticleMapper 繼承自BaseMapper,所以ArticleMapper 可以使用這些方法去操作數(shù)據(jù)庫(kù)的article表。

1.4通過(guò)Mapper實(shí)現(xiàn)增刪查改

@Slf4j
@Service
public class ArticleServiceImpl implements ArticleService {

    @Resource
    private ArticleMapper articleMapper;

    @Override
    public void saveArticle(Article article) {
        articleMapper.insert(article);
    }

    @Override
    public void deleteArticle(Long id){
        articleMapper.deleteById(id);
    }

    @Override
    public void updateArticle(Article article){
        articleMapper.updateById(article);
    }

    @Override
    public Article getArticle(Long id){
        return articleMapper.selectById(id);
    }
}

1.5測(cè)試MybatisPlus

@Slf4j
@SpringBootTest
public class MybatisPlusTest {

    @Resource
    private ArticleService articleService;

    @Test
    public void testSaveArticle() {
        Article article = new Article();
        article.setTitle("SpringBoot實(shí)戰(zhàn)");
        article.setContent("詳細(xì)介紹SpringBoot的各種姿勢(shì)");
        article.setAuthor("William");
        article.setCreateTime(new Date());
        articleService.saveArticle(article);

        log.info("articleId={}", article.getId());
    }

    @Test
    public void testGet() {
        Long id = 1L;
        Article article = articleService.getArticle(id);
        log.info(article.toString());
    }
}

二.mybatisplus+atomikos實(shí)現(xiàn)分布式事務(wù)

2.1 Mybatis plus多數(shù)據(jù)源以及分布式事務(wù)實(shí)現(xiàn)方式

  • 方案一:采用Mybatis Plus官網(wǎng)上實(shí)現(xiàn)的基于AOP以及注解的動(dòng)態(tài)數(shù)據(jù)源切換方案。基于AOP以及注解的動(dòng)態(tài)數(shù)據(jù)源切換方案。這個(gè)方案的優(yōu)點(diǎn)是:數(shù)據(jù)源靈活切換。但缺點(diǎn)也同樣明顯:

    • 需要為每一個(gè)類(lèi)或者持久層方法指定數(shù)據(jù)源,如果編碼人員素質(zhì)一般,很容易錯(cuò)誤的使用數(shù)據(jù)源。
    • 動(dòng)態(tài)切換數(shù)據(jù)源,也就意味著“從使用的角度”出錯(cuò)的概率變大。從而導(dǎo)致錯(cuò)誤的配置使用分布式事務(wù)。版本兼容問(wèn)題有可能此起彼伏。
  • 方案二:我們?nèi)匀徊捎米詈?jiǎn)的實(shí)現(xiàn)方式。就是將不同的數(shù)據(jù)庫(kù)操作Mapper分包存放,分包注入使用不同的數(shù)據(jù)源。這種方式實(shí)現(xiàn)邏輯簡(jiǎn)單,萬(wàn)變不離其宗,是“約定大于配置”思想的體現(xiàn),約定好了該放哪就放哪。雖然不靈活,但是使用方便,也不容易出錯(cuò)。即使出錯(cuò),也容易發(fā)現(xiàn)(在package層面發(fā)現(xiàn)問(wèn)題,比到代碼里面去找Bug要容易的多)。
    本文主要講解方案二的實(shí)現(xiàn)方式。

2.2 整合jta-atomikos

1.增加相關(guān)依賴mybatis-plus、jta-atomikos。

<dependency>
   <groupId>com.baomidou</groupId>
   <artifactId>mybatis-plus-boot-starter</artifactId>
   <version>3.3.2</version>
</dependency>

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-jta-atomikos</artifactId>
</dependency>

<dependency>
   <groupId>mysql</groupId>
   <artifactId>mysql-connector-java</artifactId>
</dependency>

2.多數(shù)據(jù)源配置
兩個(gè)數(shù)據(jù)源的名稱(chēng)分別是:primary和secondary。分別訪問(wèn)testdb和testdb2數(shù)據(jù)庫(kù),驅(qū)動(dòng)類(lèi)是MysqlXADataSource(支持分布式事務(wù))

primarydb:
  uniqueResourceName: primary
  xaDataSourceClassName: com.mysql.cj.jdbc.MysqlXADataSource
  xaProperties:
    url: jdbc:mysql://localhost:3306/testdb?useUnicode=true&characterEncoding=utf-8&useSSL=false
    user: root
    password: root
  exclusiveConnectionMode: true
  minPoolSize: 3
  maxPoolSize: 10
  testQuery: SELECT 1 from dual #由于采用HikiriCP,用于檢測(cè)數(shù)據(jù)庫(kù)連接是否存活。

secondarydb:
  uniqueResourceName: secondary
  xaDataSourceClassName: com.mysql.cj.jdbc.MysqlXADataSource
  xaProperties:
    url: jdbc:mysql://localhost:3306/testdb2?useUnicode=true&characterEncoding=utf-8&useSSL=false
    user: root
    password: root
  exclusiveConnectionMode: true
  minPoolSize: 3
  maxPoolSize: 10
  testQuery: SELECT 1 from dual #由于采用HikiriCP,用于檢測(cè)數(shù)據(jù)庫(kù)連接是否存活。

3.創(chuàng)建XXXMapper類(lèi)、實(shí)體類(lèi)、和XXXMapper.xml文件


文件結(jié)構(gòu)

2.2配置多數(shù)據(jù)源

數(shù)據(jù)源DataSource、SqlSessionFactory、SqlSessionTemplate、掃描路徑,對(duì)于primarydb和secondarydb都是自己一套,需要分別配置。
數(shù)據(jù)源一:primarydb

@Configuration
//數(shù)據(jù)源primary-testdb庫(kù)接口存放目錄
@MapperScan(basePackages = "cn.lsp.springboot.mapper.testdb",
            sqlSessionTemplateRef = "primarySqlSessionTemplate")
public class PrimaryDataSourceConfig {

  @Bean(name = "primaryDataSource")
  @ConfigurationProperties(prefix = "primarydb")   //數(shù)據(jù)源primary配置
  @Primary
  public DataSource primaryDataSource() {
    return new AtomikosDataSourceBean();
  }

  @Bean(name = "primarySqlSessionFactory")
  @Primary
  public SqlSessionFactory primarySqlSessionFactory(
          @Qualifier("primaryDataSource") DataSource dataSource)
          throws Exception {
    MybatisSqlSessionFactoryBean bean = new MybatisSqlSessionFactoryBean();
    bean.setDataSource(dataSource);
    //設(shè)置XML文件存放位置
    bean.setMapperLocations(new PathMatchingResourcePatternResolver()
            .getResources("classpath:mapper/testdb/*.xml")); //注意這里testdb目錄
    return bean.getObject();
  }

  @Bean(name = "primarySqlSessionTemplate")
  @Primary
  public SqlSessionTemplate primarySqlSessionTemplate(
          @Qualifier("primarySqlSessionFactory") SqlSessionFactory sqlSessionFactory)
          throws Exception {
    return new SqlSessionTemplate(sqlSessionFactory);
  }
}

數(shù)據(jù)源二:secondarydb。

@Configuration
@MapperScan(basePackages = "cn.lsp.springboot.mapper.testdb2",     //注意這里testdb2目錄
            sqlSessionTemplateRef = "secondarySqlSessionTemplate")
public class SecondaryDataSourceConfig {

    @Bean(name = "secondaryDataSource")
    @ConfigurationProperties(prefix = "secondarydb")    //注意這里secondary配置
    public DataSource secondaryDataSource() {
        return new AtomikosDataSourceBean();
    }

    @Bean(name = "secondarySqlSessionFactory")
    public SqlSessionFactory secondarySqlSessionFactory(
                        @Qualifier("secondaryDataSource") DataSource dataSource)
                        throws Exception {
        MybatisSqlSessionFactoryBean bean = new MybatisSqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        //設(shè)置XML文件存放位置
        bean.setMapperLocations(new PathMatchingResourcePatternResolver()
                .getResources("classpath:mapper/testdb2/*.xml")); //注意這里testdb2目錄
        return bean.getObject();
    }

    @Bean(name = "secondarySqlSessionTemplate")
    public SqlSessionTemplate secondarySqlSessionTemplate(
                        @Qualifier("secondarySqlSessionFactory") SqlSessionFactory sqlSessionFactory)
                        throws Exception {
        return new SqlSessionTemplate(sqlSessionFactory);
    }
}

2.3統(tǒng)一事務(wù)管理器

雖然我們將數(shù)據(jù)源及其相關(guān)配置分成了兩組,但這兩組數(shù)據(jù)源使用的事務(wù)管理器必須是同一個(gè),這樣才能實(shí)現(xiàn)分布式事務(wù)。下面是事務(wù)管理器的配置。固定代碼,不用修改。

@Configuration
@EnableTransactionManagement
public class XATransactionManagerConfig {

     //User事務(wù)
    @Bean(name = "userTransaction")
    public UserTransaction userTransaction() throws Throwable {
        UserTransactionImp userTransactionImp = new UserTransactionImp();
        userTransactionImp.setTransactionTimeout(10000);
        return userTransactionImp;
    }
    //分布式事務(wù)
    @Bean(name = "atomikosTransactionManager", initMethod = "init", destroyMethod = "close")
    public TransactionManager atomikosTransactionManager() throws Throwable {
        UserTransactionManager userTransactionManager = new UserTransactionManager();
        userTransactionManager.setForceShutdown(false);
        return userTransactionManager;
    }
    //事務(wù)管理器
    @Bean(name = "transactionManager")
    @DependsOn({ "userTransaction", "atomikosTransactionManager" })
    public PlatformTransactionManager transactionManager() throws Throwable {
        return new JtaTransactionManager(userTransaction(),atomikosTransactionManager());
    }
}

2.3多數(shù)據(jù)源及分布式事務(wù)測(cè)試

1.service類(lèi),在1.4節(jié)的ArticleService上增加了saveArticleAndComment方法和CommentMapper注入

@Slf4j
@Service
public class ArticleServiceImpl implements ArticleService {

    @Resource
    private ArticleMapper articleMapper;

    @Resource
    private CommentMapper commentMapper;

    @Override
    public void saveArticle(Article article) {
        articleMapper.insert(article);
    }

    @Override
    @Transactional
    public Long saveArticleAndComment(Article article, Comment comment) {
        articleMapper.insert(article);
        comment.setArticleId(article.getId());
        commentMapper.insert(comment);
//        int a = 2 / 0;
        return article.getId();
    }

    @Override
    public void deleteArticle(Long id){
        articleMapper.deleteById(id);
    }

    @Override
    public void updateArticle(Article article){
        articleMapper.updateById(article);
    }

    @Override
    public Article getArticle(Long id){
        return articleMapper.selectById(id);
    }
}

2.測(cè)試類(lèi)

@Slf4j
@SpringBootTest
public class MybatisPlusJtaTest {

    @Resource
    private ArticleService articleService;

    @Test
    public void testSaveArticleAndComment() {
        Article article = new Article();
        article.setTitle("SpringBoot實(shí)戰(zhàn)");
        article.setContent("詳細(xì)介紹SpringBoot的各種姿勢(shì)");
        article.setAuthor("William");
        article.setCreateTime(new Date());

        Comment comment = new Comment();
        comment.setName("Tom");
        comment.setContent("內(nèi)容詳實(shí),偏實(shí)戰(zhàn)");
        comment.setCreateTime(new Date());

        articleService.saveArticleAndComment(article, comment);
    }
}

正常情況下,兩組數(shù)據(jù)分別插入到testdb的article表和testdb2的comment表。如果我們?nèi)藶橹圃煲粋€(gè)異常(如上面代碼),事務(wù)回滾,二者均無(wú)法插入數(shù)據(jù)。

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

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

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