一.整合mybatisPlus操作数据库
1.1 MyBatis-Plus简介
MyBatis-Plus(简称 MP)是一个MyBatis的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
- 官网文档地址: https://mybatis.plus/guide/
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表。如果我们人为制造一个异常(如上面代码),事务回滚,二者均无法插入数据。