一.整合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ā)、提高效率而生。
- 官網(wǎng)文檔地址: https://mybatis.plus/guide/
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文件

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ù)。