SpringBoot整合mybatisPlus及分布式事务实现

一.整合mybatisPlus操作数据库

1.1 MyBatis-Plus简介

MyBatis-Plus(简称 MP)是一个MyBatis的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。

1.2SpringBoot集成MybatisPlus

1.通过maven坐标将mybatis-plus-boot-starter以及数据库驱动引入到Spring Boot项目里面来。

<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里面有数据库连接的配置。

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类文件的包扫描路径

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

1.3Mapper继承实现

如果我们操作数据库中的article表,我们需要按照article表的结构创建一个实体类。

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

    // id随数据库自增
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;

    private String author;

    private String title;

    private String content;

    private Date createTime;
}

然后写一个接口ArticleMapper ,继承自BaseMapper,泛型是Article实体类。

public interface ArticleMapper extends BaseMapper<Article> {

}

BaseMapper中默认帮我们提供了若干的增删改查基础实现,由于ArticleMapper 继承自BaseMapper,所以ArticleMapper 可以使用这些方法去操作数据库的article表。

1.4通过Mapper实现增删查改

@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测试MybatisPlus

@Slf4j
@SpringBootTest
public class MybatisPlusTest {

    @Resource
    private ArticleService articleService;

    @Test
    public void testSaveArticle() {
        Article article = new Article();
        article.setTitle("SpringBoot实战");
        article.setContent("详细介绍SpringBoot的各种姿势");
        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实现分布式事务

2.1 Mybatis plus多数据源以及分布式事务实现方式

  • 方案一:采用Mybatis Plus官网上实现的基于AOP以及注解的动态数据源切换方案。基于AOP以及注解的动态数据源切换方案。这个方案的优点是:数据源灵活切换。但缺点也同样明显:

    • 需要为每一个类或者持久层方法指定数据源,如果编码人员素质一般,很容易错误的使用数据源。
    • 动态切换数据源,也就意味着“从使用的角度”出错的概率变大。从而导致错误的配置使用分布式事务。版本兼容问题有可能此起彼伏。
  • 方案二:我们仍然采用最简的实现方式。就是将不同的数据库操作Mapper分包存放,分包注入使用不同的数据源。这种方式实现逻辑简单,万变不离其宗,是“约定大于配置”思想的体现,约定好了该放哪就放哪。虽然不灵活,但是使用方便,也不容易出错。即使出错,也容易发现(在package层面发现问题,比到代码里面去找Bug要容易的多)。
    本文主要讲解方案二的实现方式。

2.2 整合jta-atomikos

1.增加相关依赖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.多数据源配置
两个数据源的名称分别是:primary和secondary。分别访问testdb和testdb2数据库,驱动类是MysqlXADataSource(支持分布式事务)

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,用于检测数据库连接是否存活。

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,用于检测数据库连接是否存活。

3.创建XXXMapper类、实体类、和XXXMapper.xml文件


文件结构

2.2配置多数据源

数据源DataSource、SqlSessionFactory、SqlSessionTemplate、扫描路径,对于primarydb和secondarydb都是自己一套,需要分别配置。
数据源一:primarydb

@Configuration
//数据源primary-testdb库接口存放目录
@MapperScan(basePackages = "cn.lsp.springboot.mapper.testdb",
            sqlSessionTemplateRef = "primarySqlSessionTemplate")
public class PrimaryDataSourceConfig {

  @Bean(name = "primaryDataSource")
  @ConfigurationProperties(prefix = "primarydb")   //数据源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);
    //设置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);
  }
}

数据源二: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);
        //设置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统一事务管理器

虽然我们将数据源及其相关配置分成了两组,但这两组数据源使用的事务管理器必须是同一个,这样才能实现分布式事务。下面是事务管理器的配置。固定代码,不用修改。

@Configuration
@EnableTransactionManagement
public class XATransactionManagerConfig {

     //User事务
    @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);
        return userTransactionManager;
    }
    //事务管理器
    @Bean(name = "transactionManager")
    @DependsOn({ "userTransaction", "atomikosTransactionManager" })
    public PlatformTransactionManager transactionManager() throws Throwable {
        return new JtaTransactionManager(userTransaction(),atomikosTransactionManager());
    }
}

2.3多数据源及分布式事务测试

1.service类,在1.4节的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.测试类

@Slf4j
@SpringBootTest
public class MybatisPlusJtaTest {

    @Resource
    private ArticleService articleService;

    @Test
    public void testSaveArticleAndComment() {
        Article article = new Article();
        article.setTitle("SpringBoot实战");
        article.setContent("详细介绍SpringBoot的各种姿势");
        article.setAuthor("William");
        article.setCreateTime(new Date());

        Comment comment = new Comment();
        comment.setName("Tom");
        comment.setContent("内容详实,偏实战");
        comment.setCreateTime(new Date());

        articleService.saveArticleAndComment(article, comment);
    }
}

正常情况下,两组数据分别插入到testdb的article表和testdb2的comment表。如果我们人为制造一个异常(如上面代码),事务回滚,二者均无法插入数据。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 230,825评论 6 546
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 99,814评论 3 429
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 178,980评论 0 384
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 64,064评论 1 319
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 72,779评论 6 414
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 56,109评论 1 330
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 44,099评论 3 450
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 43,287评论 0 291
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 49,799评论 1 338
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 41,515评论 3 361
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 43,750评论 1 375
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 39,221评论 5 365
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 44,933评论 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 35,327评论 0 28
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 36,667评论 1 296
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 52,492评论 3 400
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 48,703评论 2 380

推荐阅读更多精彩内容