您现在的位置是:首页 > 文章详情

Spring Boot2中整合atomikos来实现不同类型数据库的分布式事务一致性

日期:2019-09-10点击:318

由于需要重构一个老的系统(Oracle),业务侧要求老系统和新系统(Mysql)并行运行半年,证明重构系统的稳定性才能上线,在这半年期间,新系统用来查询,全文检索,图数据库查询,老系依然办理业务,因此就存在在一个事务提交中,同时写Mysql和Oracle,比较了一下方案,最终选择了atomikos来做分布式事务。先说缺点:
1、性能比原来的单纯的德鲁伊连接池慢。
2、卡,A库没提交会导致B库也卡主,体验非常不好。
3、德鲁伊的连接池驱动jar的版本,需要和对应的数据库特定版本的驱动保持一致,否则有些方法在德鲁伊连接池都还没实现。
4、扩展性不好,如果再需要一个BI的库做分析,那么又得要CDC方案从A库同步业务数据到B库,这个数据同步的实时性达不到业务要求。

先上代码,把工程能跑起来先:
pom.xml

<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.example</groupId> <artifactId>learn-jta-atomikos-SpringBoot</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>learn-jta-atomikos</name> <description>Demo project for Spring Boot</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.8.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencies> <!-- MyBatis --> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.1.0</version> <exclusions> <exclusion> <artifactId>spring-boot-starter-logging</artifactId> <groupId>org.springframework.boot</groupId> </exclusion> </exclusions> </dependency> <!-- 热部署 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> </dependency> <!-- 数据库连接 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <!--<scope>runtime</scope>--> <version>8.0.11</version> <!--分布式事物的驱动,对版本有要求的,不同的数据库,还不一样--> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1.20</version> </dependency> <!-- 分布式事务atomikos --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jta-atomikos</artifactId> </dependency> <!-- tx + aop --> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.4</version> </dependency> <!-- 添加Log4j2 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-log4j2</artifactId> </dependency> <!-- 为log4j2添加异步支持 --> <dependency> <groupId>com.lmax</groupId> <artifactId>disruptor</artifactId> <version>3.4.2</version> </dependency> <!-- 简化代码 --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <!-- 用于监控与管理 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> <exclusions> <exclusion> <artifactId>spring-boot-starter-logging</artifactId> <groupId>org.springframework.boot</groupId> </exclusion> </exclusions> </dependency> <!-- WEB --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- 配合@ConfigurationProperties编译生成元数据文件(IDEA编辑器的属性提示) --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> </dependency> <!-- 测试 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>javax.jms</groupId> <artifactId>javax.jms-api</artifactId> <version>2.0.1</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>

application.yml

spring: application: name: learn-jta-atomikos aop: proxy-target-class: true ## jta相关参数配置 # 如果你在JTA环境中,并且仍然希望使用本地事务,你可以设置spring.jta.enabled属性为false以禁用JTA自动配置。 jta: enabled:true # 必须配置唯一的资源名 mysql: #db1(分布式的第一个库) test1: url: jdbc:mysql://localhost:3306/db1?useUnicode=true&characterEncoding=utf-8 username: root password: xxxx minPoolSize: 3 maxPoolSize: 25 maxLifetime: 20000 borrowConnectionTimeout: 30 loginTimeout: 30 maintenanceInterval: 60 maxIdleTime: 60 #db2(分布式的第二个库) test2: url: jdbc:mysql://localhost:3306/db2?useUnicode=true&characterEncoding=utf-8 username: root password: xxxxx minPoolSize: 3 maxPoolSize: 25 maxLifetime: 20000 borrowConnectionTimeout: 30 loginTimeout: 30 maintenanceInterval: 60 maxIdleTime: 60 ## Druid监控设置 datasource: druid: #web-stat-filter.exclusions: *.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/* stat-view-servlet.url-pattern: /druid/* stat-view-servlet.reset-enable: true stat-view-servlet.login-username: admin stat-view-servlet.login-password: admin aop-patterns: com.example.atomikos.service.* # 开启下划线 mybatis: configuration: map-underscore-to-camel-case: true

Application.java

package com.atomikos; import com.atomikos.config.pojo.DBConfig1; import com.atomikos.config.pojo.DBConfig2; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.context.properties.EnableConfigurationProperties; /** * 将来这个类也是可以被Junit集成起来进行测试的 */ @SpringBootApplication @MapperScan("com.atomikos") //其他项目中,这个是可以不用的,可是在则个分布式的新项目中,这个扫描Mmapper类是必须的,标记了mapper还是找不到,只好把这里打开 @EnableConfigurationProperties(value={DBConfig1.class,DBConfig2.class}) //值对象必须加,否则后续扫描不到这个类 public class Application { public static void main(String[] args) { SpringApplication.run(Application.class,args); } } 

MyBatisConfig1.java

package com.atomikos.config; import java.sql.SQLException; import javax.sql.DataSource; import com.atomikos.config.pojo.DBConfig1; import com.mysql.cj.jdbc.MysqlXADataSource; import org.apache.ibatis.session.SqlSessionFactory; import org.mybatis.spring.SqlSessionFactoryBean; import org.mybatis.spring.SqlSessionTemplate; import org.mybatis.spring.annotation.MapperScan; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import com.atomikos.jdbc.AtomikosDataSourceBean; import org.springframework.context.annotation.Primary; @Configuration // basePackages 最好分开配置 如果放在同一个文件夹可能会报错 @MapperScan(basePackages = "com.atomikos.dao.db1", sqlSessionTemplateRef = "testSqlSessionTemplate") public class MyBatisConfig1 { @Primary //这个primary必须加,否则spring在两个sessionfactory的时候,不知道用哪个? // 配置数据源 @Bean(name = "testDataSource") public DataSource testDataSource(DBConfig1 testConfig) throws SQLException { // 这里直接针对mysql的分布式驱动,进行硬编码了 MysqlXADataSource mysqlXaDataSource = new MysqlXADataSource(); mysqlXaDataSource.setUrl(testConfig.getUrl()); mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true); mysqlXaDataSource.setPassword(testConfig.getPassword()); mysqlXaDataSource.setUser(testConfig.getUsername()); mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true); // 将本地事务注册到创 Atomikos全局事务 AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean(); xaDataSource.setXaDataSource(mysqlXaDataSource); xaDataSource.setUniqueResourceName("testDataSource"); //硬编码,这里也是可以考虑落到配置文件中的 xaDataSource.setMinPoolSize(testConfig.getMinPoolSize()); xaDataSource.setMaxPoolSize(testConfig.getMaxPoolSize()); xaDataSource.setMaxLifetime(testConfig.getMaxLifetime()); xaDataSource.setBorrowConnectionTimeout(testConfig.getBorrowConnectionTimeout()); xaDataSource.setLoginTimeout(testConfig.getLoginTimeout()); xaDataSource.setMaintenanceInterval(testConfig.getMaintenanceInterval()); xaDataSource.setMaxIdleTime(testConfig.getMaxIdleTime()); xaDataSource.setTestQuery(testConfig.getTestQuery()); return xaDataSource; } @Primary @Bean(name = "testSqlSessionFactory") public SqlSessionFactory testSqlSessionFactory(@Qualifier("testDataSource") DataSource dataSource) throws Exception { SqlSessionFactoryBean bean = new SqlSessionFactoryBean(); bean.setDataSource(dataSource); return bean.getObject(); } @Primary @Bean(name = "testSqlSessionTemplate") public SqlSessionTemplate testSqlSessionTemplate( @Qualifier("testSqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception { return new SqlSessionTemplate(sqlSessionFactory); } } 

MyBatisConfig2.java

package com.atomikos.config; import java.sql.SQLException; import javax.sql.DataSource; import com.atomikos.config.pojo.DBConfig2; import com.mysql.cj.jdbc.MysqlXADataSource; import org.apache.ibatis.session.SqlSessionFactory; import org.mybatis.spring.SqlSessionFactoryBean; import org.mybatis.spring.SqlSessionTemplate; import org.mybatis.spring.annotation.MapperScan; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import com.atomikos.jdbc.AtomikosDataSourceBean; @Configuration @MapperScan(basePackages = "com.atomikos.dao.db2", sqlSessionTemplateRef = "test2SqlSessionTemplate") public class MyBatisConfig2 { // 配置数据源 @Bean(name = "test2DataSource") public DataSource testDataSource(DBConfig2 testConfig) throws SQLException { MysqlXADataSource mysqlXaDataSource = new MysqlXADataSource(); mysqlXaDataSource.setUrl(testConfig.getUrl()); mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true); mysqlXaDataSource.setPassword(testConfig.getPassword()); mysqlXaDataSource.setUser(testConfig.getUsername()); mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true); AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean(); xaDataSource.setXaDataSource(mysqlXaDataSource); xaDataSource.setUniqueResourceName("test2DataSource"); xaDataSource.setMinPoolSize(testConfig.getMinPoolSize()); xaDataSource.setMaxPoolSize(testConfig.getMaxPoolSize()); xaDataSource.setMaxLifetime(testConfig.getMaxLifetime()); xaDataSource.setBorrowConnectionTimeout(testConfig.getBorrowConnectionTimeout()); xaDataSource.setLoginTimeout(testConfig.getLoginTimeout()); xaDataSource.setMaintenanceInterval(testConfig.getMaintenanceInterval()); xaDataSource.setMaxIdleTime(testConfig.getMaxIdleTime()); xaDataSource.setTestQuery(testConfig.getTestQuery()); return xaDataSource; } @Bean(name = "test2SqlSessionFactory") public SqlSessionFactory testSqlSessionFactory(@Qualifier("test2DataSource") DataSource dataSource) throws Exception { SqlSessionFactoryBean bean = new SqlSessionFactoryBean(); bean.setDataSource(dataSource); return bean.getObject(); } @Bean(name = "test2SqlSessionTemplate") public SqlSessionTemplate testSqlSessionTemplate( @Qualifier("test2SqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception { return new SqlSessionTemplate(sqlSessionFactory); } } 

TransactionConfig.java

package com.atomikos.config; import org.springframework.aop.Advisor; import org.springframework.aop.aspectj.AspectJExpressionPointcut; import org.springframework.aop.support.DefaultPointcutAdvisor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.TransactionDefinition; import org.springframework.transaction.interceptor.*; import java.util.Collections; import java.util.HashMap; import java.util.Map; /** * 配置声明式事务 切面拦截(本次演示中,没啥用) * * @author yehaibo */ @Configuration public class TransactionConfig { private static final int TX_METHOD_TIMEOUT = 5; private static final String AOP_POINTCUT_EXPRESSION = "execution (* com.atomikos.service.*.*(..))"; @Autowired private PlatformTransactionManager transactionManager; @Bean public TransactionInterceptor txAdvice() { NameMatchTransactionAttributeSource source = new NameMatchTransactionAttributeSource(); /* 只读事务,不做更新操作 */ RuleBasedTransactionAttribute readOnlyTx = new RuleBasedTransactionAttribute(); readOnlyTx.setReadOnly(true); readOnlyTx.setPropagationBehavior(TransactionDefinition.PROPAGATION_SUPPORTS); /* 当前存在事务就使用当前事务,当前不存在事务就创建一个新的事务 */ RuleBasedTransactionAttribute requiredTx = new RuleBasedTransactionAttribute(); requiredTx.setRollbackRules(Collections.singletonList(new RollbackRuleAttribute(Exception.class))); requiredTx.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); requiredTx.setTimeout(TX_METHOD_TIMEOUT); Map<String, TransactionAttribute> txMap = new HashMap<>(10); txMap.put("add*", requiredTx); txMap.put("save*", requiredTx); txMap.put("insert*", requiredTx); txMap.put("update*", requiredTx); txMap.put("delete*", requiredTx); txMap.put("get*", readOnlyTx); txMap.put("query*", readOnlyTx); txMap.put("list*", readOnlyTx); txMap.put("find*", readOnlyTx); source.setNameMap(txMap); return new TransactionInterceptor(transactionManager, source); } /** * 切点 * * @return */ @Bean public Advisor txAdviceAdvisor() { AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut(); pointcut.setExpression(AOP_POINTCUT_EXPRESSION); return new DefaultPointcutAdvisor(pointcut, txAdvice()); } } 

DBConfig1.java

package com.atomikos.config.pojo; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; @Data /** * 将application.properties配置文件中配置自动封装到实体类字段中 * @author yehaibo */ @ConfigurationProperties(prefix = "spring.mysql.test1") // 注意这个前缀要和application.yml文件的前缀一样 public class DBConfig1 { private String url; // 比如这个url在properties中是这样子的mysql.datasource.test1.username = root private String username; public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public int getMinPoolSize() { return minPoolSize; } public void setMinPoolSize(int minPoolSize) { this.minPoolSize = minPoolSize; } public int getMaxPoolSize() { return maxPoolSize; } public void setMaxPoolSize(int maxPoolSize) { this.maxPoolSize = maxPoolSize; } public int getMaxLifetime() { return maxLifetime; } public void setMaxLifetime(int maxLifetime) { this.maxLifetime = maxLifetime; } public int getBorrowConnectionTimeout() { return borrowConnectionTimeout; } public void setBorrowConnectionTimeout(int borrowConnectionTimeout) { this.borrowConnectionTimeout = borrowConnectionTimeout; } public int getLoginTimeout() { return loginTimeout; } public void setLoginTimeout(int loginTimeout) { this.loginTimeout = loginTimeout; } public int getMaintenanceInterval() { return maintenanceInterval; } public void setMaintenanceInterval(int maintenanceInterval) { this.maintenanceInterval = maintenanceInterval; } public int getMaxIdleTime() { return maxIdleTime; } public void setMaxIdleTime(int maxIdleTime) { this.maxIdleTime = maxIdleTime; } public String getTestQuery() { return testQuery; } public void setTestQuery(String testQuery) { this.testQuery = testQuery; } private String password; private int minPoolSize; private int maxPoolSize; private int maxLifetime; private int borrowConnectionTimeout; private int loginTimeout; private int maintenanceInterval; private int maxIdleTime; private String testQuery; } 

DBConfig2.java

package com.atomikos.config.pojo; import org.springframework.boot.context.properties.ConfigurationProperties; import lombok.Data; @Data /** * 将application.properties配置文件中配置自动封装到实体类字段中 * @author yehaibo */ @ConfigurationProperties(prefix = "spring.mysql.test2")// 注意这个前缀要和application.yml文件的前缀一样 public class DBConfig2 { private String url; // 比如这个url在properties中是这样子的mysql.datasource.test1.username = root private String username; public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public int getMinPoolSize() { return minPoolSize; } public void setMinPoolSize(int minPoolSize) { this.minPoolSize = minPoolSize; } public int getMaxPoolSize() { return maxPoolSize; } public void setMaxPoolSize(int maxPoolSize) { this.maxPoolSize = maxPoolSize; } public int getMaxLifetime() { return maxLifetime; } public void setMaxLifetime(int maxLifetime) { this.maxLifetime = maxLifetime; } public int getBorrowConnectionTimeout() { return borrowConnectionTimeout; } public void setBorrowConnectionTimeout(int borrowConnectionTimeout) { this.borrowConnectionTimeout = borrowConnectionTimeout; } public int getLoginTimeout() { return loginTimeout; } public void setLoginTimeout(int loginTimeout) { this.loginTimeout = loginTimeout; } public int getMaintenanceInterval() { return maintenanceInterval; } public void setMaintenanceInterval(int maintenanceInterval) { this.maintenanceInterval = maintenanceInterval; } public int getMaxIdleTime() { return maxIdleTime; } public void setMaxIdleTime(int maxIdleTime) { this.maxIdleTime = maxIdleTime; } public String getTestQuery() { return testQuery; } public void setTestQuery(String testQuery) { this.testQuery = testQuery; } private String password; private int minPoolSize; private int maxPoolSize; private int maxLifetime; private int borrowConnectionTimeout; private int loginTimeout; private int maintenanceInterval; private int maxIdleTime; private String testQuery; }

TractionController.java

package com.atomikos.controller; import com.atomikos.entity.BookDO; import com.atomikos.entity.BookVo; import com.atomikos.service.BookService; import com.atomikos.service.impl.BookServiceImpl; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import java.util.List; /** * 页面对接的controll类 * @author yehaibo */ @RestController @RequestMapping("/books") public class TractionController { @Autowired private BookService bookService; @GetMapping public List<BookDO> list( @RequestParam(defaultValue = "1") Integer page, @RequestParam(defaultValue = "10") Integer size) { return bookService.list(page, size); } @GetMapping("/{id}") public BookDO get(@PathVariable Long id) { return bookService.get(id); } @PostMapping public BookDO save(@RequestBody BookVo book) { return bookService.save(book, book.getUser()); } @PutMapping public BookDO update(@RequestBody BookVo book) { return ((BookServiceImpl) bookService).update(book, book.getUser()); } @DeleteMapping("/{id}") public int delete(@PathVariable Long id) { return ((BookServiceImpl) bookService).delete(id); } } 

UserMapper.java

package com.atomikos.dao.db1; import com.atomikos.entity.UserDO; import org.apache.ibatis.annotations.*; import org.springframework.stereotype.Repository; import java.util.List; /** * mybatic有两种写法,这里是mapper的写法,不是dao的写法 */ @Mapper @Repository public interface UserMapper { /** * 根据主键查询一条记录 * * @param id * @return */ @Select("select id, username, password from user where id = #{id}") UserDO get(Long id); /** * 分页列表查询 * * @param page * @param size * @return */ @Select("select id, username, password from user limit #{page}, #{size}") List<UserDO> list(Integer page, Integer size); /** * 保存 * * @param userDO * @return 自增主键 */ @Insert("insert into user(username, password) values(#{username}, #{password})") @Options(useGeneratedKeys = true, keyColumn = "id") int save(UserDO userDO); /** * 修改一条记录 * * @param user * @return */ @Update("update user set username = #{username}, password = #{password} where id = #{id}") int update(UserDO user); /** * 删除一条记录 * * @param id 主键 * @return */ @Delete("delete from user where id = #{id}") int delete(Long id); } 

BookMapper.java

package com.atomikos.dao.db2; import com.atomikos.entity.BookDO; import org.apache.ibatis.annotations.*; import org.springframework.stereotype.Repository; import java.util.List; /** * @author yehaibo * @date 2019/9/11 */ @Mapper @Repository public interface BookMapper { /** * 分页查询 * * @param page 页码 * @param size 每页记录数 * @return */ @Select("select id, name, article_id as articleId, user_id as userId from book limit ${page}, ${size}") List<BookDO> list(@Param("page") Integer page, @Param("size") Integer size); /** * 根据主键查询单条记录 * * @param id * @return */ @Select("select id, name, article_id as articleId, user_id as userId from book where id = #{id}") BookDO get(Long id); /** * 添加一条记录 * * @param book * @return 自增主键 */ @Insert("insert into book(name, article_id, user_id) values(#{name}, #{articleId}, #{userId})") @Options(useGeneratedKeys = true, keyProperty = "id", keyColumn = "id") int save(BookDO book); /** * 修改一条记录 * * @param book * @return */ @Update("update book set name = #{name}, article_id = #{articleId}, user_id = #{userId} where id = #{id}") int update(BookDO book); /** * 删除一条记录 * * @param id 主键 * @return */ @Delete("delete from book where id = #{id}") int delete(Long id); } 

ArticleDO.java

package com.atomikos.entity; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.io.Serializable; /** * 文章 * * @author yehaibo */ @Data @NoArgsConstructor @AllArgsConstructor public class ArticleDO implements Serializable { private static final long serialVersionUID = 3971756585655871603L; private Long id; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public String getContent() { return content; } public void setContent(String content) { this.content = content; } public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } private String title; private String content; private String url; } 

BookDO.java

package com.atomikos.entity; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.io.Serializable; /** * 书 * * @author yehaibo */ @Data @NoArgsConstructor @AllArgsConstructor public class BookDO implements Serializable { private static final long serialVersionUID = 3231762613546697469L; private Long id; // // public BookDO(Long BookId, String Name, Long ArticleId, Long UserId) { // this.id = BookId; // this.name = Name; // this.articleId = ArticleId; // this.userId = UserId; // } public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Long getArticleId() { return articleId; } public void setArticleId(Long articleId) { this.articleId = articleId; } public Long getUserId() { return userId; } public void setUserId(Long userId) { this.userId = userId; } private String name; private Long articleId; private Long userId; } 

BookVo.java

package com.atomikos.entity; import lombok.Data; /** * POJO的值对象 */ @Data public class BookVo extends BookDO { public UserDO getUser() { return user; } public void setUser(UserDO user) { this.user = user; } private UserDO user; } 

UserDO.java

package com.atomikos.entity; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.io.Serializable; /** * 用户 * * @author fengxuechao */ @Data @NoArgsConstructor @AllArgsConstructor public class UserDO implements Serializable { private static final long serialVersionUID = 469663920369239035L; private Long id; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } private String username; private String password; }

BookService.java

package com.atomikos.service; import com.atomikos.entity.BookDO; import com.atomikos.entity.UserDO; import java.util.List; /** * 主要目的是测试分布式事务 * * @author yehaibo */ public interface BookService { /** * 保存 * * @param book * @param user * @return */ BookDO save(BookDO book, UserDO user); /** * 单条查询 * * @param id * @return */ BookDO get(Long id); /** * 分页查询 * * @param page * @param size * @return */ List<BookDO> list(Integer page, Integer size); BookDO update(BookDO book, UserDO user); } 

BookServiceImpl.java

package com.atomikos.service.impl; import com.atomikos.dao.db1.UserMapper; import com.atomikos.dao.db2.BookMapper; import com.atomikos.entity.BookDO; import com.atomikos.entity.UserDO; import com.atomikos.service.BookService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.List; /** * @author yehaibo */ @Service("yehaibo") public class BookServiceImpl implements BookService { @Autowired //主要是唯一(例如类型唯一匹配上了,也是可以注入的) private BookMapper bookMapper; @Autowired private UserMapper userMapper; /** * 保存书本和文章, 使用声明式事务(tx+aop形式) * * @param book {@link BookDO} * @param user {@link UserDO} * @return */ @Override public BookDO save(BookDO book, UserDO user) { int userSave = userMapper.save(user); if (userSave == 0) { return null; } book.setUserId(user.getId()); int bookSave = bookMapper.save(book); if (bookSave == 0) { return null; } // throw new RuntimeException("测试分布式事务(tx+aop形式)"); return book; } /** * 单条查询 * * @param id * @return */ @Override public BookDO get(Long id) { BookDO book = bookMapper.get(id); UserDO user = userMapper.get(book.getUserId()); //????? //BookDO(book.getId(), book.getName(), book.getArticleId(), user.getId()) return book; } /** * 分页查询 * * @param page * @param size * @return */ @Override public List<BookDO> list(Integer page, Integer size) { page = (page < 1 ? 0 : page - 1) * size; return bookMapper.list(page, size); } /** * 修改书本和文章, 使用声明式事务(注解形式) * * @param book * @param user * @return */ @Transactional(rollbackFor = Exception.class) public BookDO update(BookDO book, UserDO user) { int bookUpdate = bookMapper.update(book); if (bookUpdate != 1) { return null; } int userUpdate = userMapper.update(user); if (userUpdate != 1) { return null; } throw new RuntimeException("测试分布式事务(注解形式)"); // return book; } /** * 删除书本和文章 * * @param id * @return */ public int delete(Long id) { BookDO book = bookMapper.get(id); System.err.println(book); if (book == null) { throw new RuntimeException("没有可以删除的书本"); } Long userId = book.getUserId(); int userDelete = userMapper.delete(userId); if (userDelete != 1) { return 0; } int bookDelete = bookMapper.delete(id); if (bookDelete != 1) { return 0; } throw new RuntimeException("测试没有添加分布式事务管理)"); // return 1; } } 

BookServiceImplTest.java

package com; import com.atomikos.entity.BookDO; import com.atomikos.entity.UserDO; import com.atomikos.service.BookService; import com.atomikos.service.impl.BookServiceImpl; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; /** * 从service层发起的测试分布式事务:切面拦截形式, 注解式 * Junit会自己启动springboot的框架进行测试的 */ @RunWith(SpringRunner.class) @SpringBootTest(classes = com.atomikos.Application.class) //这个必须要加的,否则Junit无法启动spring框架进行测试 public class BookServiceImplTest { @Autowired @Qualifier("yehaibo") BookService bookService; /** * 测试分布式事务(切面拦截形式) */ @Test public void save() { BookDO book = new BookDO(); book.setName("测试封面名称 - 001"); book.setArticleId(69L); UserDO user = new UserDO(); user.setUsername("用户名 - 001"); user.setPassword("密码 - 001"); BookDO bookDO = bookService.save(book, user); System.out.println(bookDO); } /** * 测试分布式事务(注解式) */ @Test public void update() { BookDO book = new BookDO(); book.setId(10L); book.setName("测试封面名称 - 002"); book.setArticleId(69L); UserDO user = new UserDO(); user.setId(18L); user.setUsername("月用户名 - 002"); user.setPassword("密码 - 002"); //((BookServiceImpl)bookService).update(book, user); //已经明确指定了,不需要这样转换了 bookService.update(book, user); } /** * 没有事务管理 */ @Test public void delete() { int delete = ((BookServiceImpl) bookService).delete(12L); Assert.assertEquals(1, delete); } } 

TractionControllerTest.java

package com; import com.atomikos.dao.db1.UserMapper; import com.atomikos.entity.BookVo; import com.atomikos.entity.UserDO; import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.http.MediaType; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.web.context.WebApplicationContext; import static org.hamcrest.Matchers.is; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * 从controller层发起的mock测试分布式事务 * 有了这种方式,就不需要postman了 */ @RunWith(SpringRunner.class) @SpringBootTest(classes = com.atomikos.Application.class) //这个必须要加的,否则Junit无法启动spring框架进行测试 public class TractionControllerTest { private MockMvc mockMvc; private ObjectMapper objectMapper = new ObjectMapper(); @Autowired private UserMapper userMapper; @Autowired private WebApplicationContext context; @Before public void setUp() { this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context).build(); } /** * 申明式 * * @throws Exception */ @Test public void save() throws Exception { UserDO user = new UserDO(); user.setUsername("用户名 - 002"); user.setPassword("密码 - 002"); BookVo book = new BookVo(); book.setName("书本名称 - 002"); book.setArticleId(69L); book.setUser(user); String json = objectMapper.writeValueAsString(book); this.mockMvc.perform( post("/books") .contentType(MediaType.APPLICATION_JSON_UTF8) .content(json)) .andExpect(status().isOk()) .andExpect(jsonPath("$.name", is("测试封面名称 - 002"))) .andExpect(jsonPath("$.articleId", is(69))) .andDo(print()); } /** * 注解式 * * @throws Exception */ @Test public void update() throws Exception { UserDO user = userMapper.get(3L); assert user != null; user.setUsername("用户名- 003"); user.setPassword("密码 - 003"); BookVo book = new BookVo(); book.setId(9L); book.setName("测试封面名称 - 003"); book.setArticleId(69L); book.setUser(user); String json = objectMapper.writeValueAsString(book); this.mockMvc.perform( put("/books") .contentType(MediaType.APPLICATION_JSON_UTF8) .content(json)) .andExpect(status().isOk()) .andExpect(jsonPath("$.name", is("测试封面名称 - 003"))) .andExpect(jsonPath("$.articleId", is(87))) .andDo(print()); } /** * 没有事务管理 * * @throws Exception */ @Test public void delete() throws Exception { this.mockMvc.perform( MockMvcRequestBuilders.delete("/books/4")) .andExpect(status().isOk()) .andDo(print()); } } 
原文链接:https://yq.aliyun.com/articles/718134
关注公众号

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。

持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。

转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。

文章评论

共有0条评论来说两句吧...

文章二维码

扫描即可查看该文章

点击排行

推荐阅读

最新文章