博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
如何在SpringBoot中使用Atomikos实现分布式事务
阅读量:2168 次
发布时间:2019-05-01

本文共 37222 字,大约阅读时间需要 124 分钟。

1.前言

Java规范对分布式事务定义了标准的规范Java事务API和Java事务服务,分别是JTA和JTS一个分布式事务必须包括一个事务管理器和多个资源管理器。

资源管理器是任意类型的持久化数据存储;

而事务管理器则是承担着所有事务参与单元者的相互通讯的责任。

JTA的规范制定了分布式事务的实现的整套流程框架,定义了各个接口且只有接口,而实现分别交给事务管理器的实现方和资源管理器的实现方

对于资源管理器而言,主要包括数据库连接,JMS等,还有很多了解的不清楚。

对于事务管理器而言,从网上了解主要是应用服务器,包括JBOSS,WEBLOGIC等应用服务器,也就是说事务管理器的实现方是应用服务器,用来管理事务的通讯和协调。

对于大多数谈的数据库了解,事务管理器需要从数据库获得XAConnection , XAResource等对象,而这些对象是数据库驱动程序需要提供的,所以如果要实现分布式事务还必须有支持分布式事务的数据库服务器以及数据库驱动程序。

对Mysql而言,在mysql5.0以上的版本已经支持了分布式事务,另外常用的mysql-connector-java-5.1.25-bin.jar也是支持分布式事务的,可以在jar包的com.mysql.jdbc.jdbc2.optional中找到XA对象的实现

上面介绍了事务管理器和资源管理器的实现方式,在学习研究过程中发现对于事务管理器,特别强调了tomcat等服务器是不支持的,这句话的意思应该是在tomcat容器内
并没有分布式事务管理器的实现对象。而在JBOSS或者WEBLOGIC等商业服务器应该内置了分布式事务管理器的实现对象,应用程序可以通过JNDI方式获取UserTransaction
和TransactionManager等分布式事务环境中所需要用到的对象。

通常,应用程序服务器(Application Server)提供了应用程序可以使用的多种服务。在谈到分布式事务时,该服务就称作 XA Resource。当然,在应用程序可以使用 XA Resource 之前,首先要在应用程序服务器中注册和配置 XA Resource。

事务管理器作为管理和协调分布式事务的关键处理中心非常重要,所以应用服务器可以单独只用过事务管理器。

2.在SpringBoot中使用分布式事务

上面主要是一些基本的概念,在学习研究中总结出来的,可能不太全面,下面主要介绍一下在使用Spring使用分布式事务中的心得,这种做法也是将事务管理器嵌入应用中。

开始准备Spring的时候,Spring官网-SpringBoot文档第38章介绍了Atomikos和Bitronix 等工具,实际上这些工具都是取代应用服务器对事务管理器的支持,负责实现事务管理器对象。由于Atomikos介绍在Bitronix 之前,所以直接使用Atomikos进行测试。

2.1.盲点解释

要理解 JTA 的实现原理首先需要了解其架构:它包括事务管理器(Transaction Manager)和一个或多个支持 XA 协议的资源管理器 ( Resource Manager ) 两部分, 我们可以将资源管理器看做任意类型的持久化数据存储;事务管理器则承担着所有事务参与单元的协调与控制。 根据所面向对象的不同,我们可以将 JTA 的事务管理器和资源管理器理解为两个方面:面向开发人员的使用接口(事务管理器)和面向服务提供商的实现接口(资源管理器)。其中开发接口的主要部分即为上述示例中引用的 UserTransaction 对象,开发人员通过此接口在信息系统中实现分布式事务;而实现接口则用来规范提供商(如数据库连接提供商)所提供的事务服务,它约定了事务的资源管理功能,使得 JTA 可以在异构事务资源之间执行协同沟通。以数据库为例,IBM 公司提供了实现分布式事务的数据库驱动程序,Oracle 也提供了实现分布式事务的数据库驱动程序, 在同时使用 DB2 和 Oracle 两种数据库连接时, JTA 即可以根据约定的接口协调者两种事务资源从而实现分布式事务。正是基于统一规范的不同实现使得 JTA 可以协调与控制不同数据库或者 JMS 厂商的事务资源,其架构如下图所示:

在这里插入图片描述
图 2.1 JTA体系结构

开发人员使用开发人员接口,实现应用程序对全局事务的支持;各提供商(数据库,JMS 等)依据提供商接口的规范提供事务资源管理功能;事务管理器( TransactionManager )将应用对分布式事务的使用映射到实际的事务资源并在事务资源间进行协调与控制。 下面,本文将对包括 UserTransaction、Transaction 和 TransactionManager 在内的三个主要接口以及其定义的方法进行介绍。

在这里插入图片描述

  • UserTransaction:面向开发人员的接口,开发人员通常只使用此接口实现 JTA 事务管理
  • Transaction:代表了一个物理意义上的事务
  • TransactionManager:本身并不承担实际的事务处理功能,它更多的是充当用户接口和实现接口之间的桥梁
  • UserTransaction 对象不会对事务进行任何控制,所有的事务方法都是通过 TransactionManager 传递到实际的事务资源即 Transaction 对象上

2.2.确保mysql开启XA事务支持

SHOW VARIABLES LIKE '%xa%'

如果innodb_support_xa的值是ON就说明mysql已经开启对XA事务的支持了。

如果不是就执行:

SET innodb_support_xa = ON

2.3.重要Maven依赖

mysql
mysql-connector-java
runtime
org.springframework.boot
spring-boot-starter-jta-atomikos
com.alibaba
druid-spring-boot-starter
1.1.10
org.mybatis.spring.boot
mybatis-spring-boot-starter
1.3.2

2.4.配置Atomikos, Druid, MyBatis

首先,要使下面的代码配置生效要先确保你在项目工程中引入了spring-boot-starter-jta-atomikos, druid-spring-boot-starter这两个依赖。

第二,SpringBoot会自动配置Atomikos的事务管理配置,无需做其它的配置。

2.4.1.application.properties

第15-19行代码表示实现javax.sql.XADataSource接口的com.alibaba.druid.pool.xa.DruidXADataSource的特有属性, 并不是Atomikos的属性.

spring.application.name=learn-jta-atomikos# 开启下划线-驼峰命名转换mybatis.configuration.map-underscore-to-camel-case=truespring.aop.proxy-target-class=true## jta相关参数配置# 如果你在JTA环境中,并且仍然希望使用本地事务,你可以设置spring.jta.enabled属性为false以禁用JTA自动配置。spring.jta.enabled=true# 必须配置唯一的资源名spring.jta.atomikos.datasource.one.unique-resource-name=jta-personal# 配置Druid的属性 https://github.com/alibaba/druid/wiki/DruidDataSource%E9%85%8D%E7%BD%AE%E5%B1%9E%E6%80%A7%E5%88%97%E8%A1%A8spring.jta.atomikos.datasource.one.xa-data-source-class-name=com.alibaba.druid.pool.xa.DruidXADataSourcespring.jta.atomikos.datasource.one.xa-properties.url=jdbc:mysql://localhost:3306/personal?characterEncoding=utf-8&useSSL=false&allowMultiQueries=truespring.jta.atomikos.datasource.one.xa-properties.username=rootspring.jta.atomikos.datasource.one.xa-properties.password=123456spring.jta.atomikos.datasource.one.xa-properties.filters=slf4j,stat,wall,config#spring.jta.atomikos.datasource.one.xa-properties.connectionProperties=config.decrypt=true;config.decrypt.key=${druid.publickey}spring.jta.atomikos.datasource.two.unique-resource-name=jta-bookspring.jta.atomikos.datasource.two.max-pool-size=8spring.jta.atomikos.datasource.two.xa-data-source-class-name=com.alibaba.druid.pool.xa.DruidXADataSourcespring.jta.atomikos.datasource.two.xa-properties.url=jdbc:mysql://localhost:3306/secondary?characterEncoding=utf-8&useSSL=false&&allowMultiQueries=truespring.jta.atomikos.datasource.two.xa-properties.username=rootspring.jta.atomikos.datasource.two.xa-properties.password=123456spring.jta.atomikos.datasource.two.xa-properties.filters=slf4j,stat,wall,config#spring.jta.atomikos.datasource.two.xa-properties.connectionProperties=config.decrypt=true;config.decrypt.key=${druid.publickey}## Druid监控设置spring.datasource.druid.web-stat-filter.exclusions=*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*spring.datasource.druid.stat-view-servlet.url-pattern=/druid/*spring.datasource.druid.stat-view-servlet.reset-enable=truespring.datasource.druid.stat-view-servlet.login-username=adminspring.datasource.druid.stat-view-servlet.login-password=adminspring.datasource.druid.aop-patterns=com.example.atomikos.service.*

2.4.2.配置Atomikos数据源与MyBatis集成

这里只给出默认数据源的Atomikos与MyBatis的集成,其余的数据源的配置与它大同小异(见第2.5章实例)。

注意!第18行代码,这里指定com.example.atomikos.dao.one这个包路径下Mapper接口的MyBatis的会话工厂,不同的数据源指定不同的会话工厂!!!
然后在使用dao层的时候,正常使用即可,详细代码见(见第2.5章实例)。

package com.example.atomikos.config;import com.atomikos.jdbc.AtomikosDataSourceBean;import org.apache.ibatis.session.SqlSessionFactory;import org.mybatis.spring.SqlSessionFactoryBean;import org.mybatis.spring.annotation.MapperScan;import org.springframework.boot.context.properties.ConfigurationProperties;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.context.annotation.Primary;import javax.sql.DataSource;/** * 设置JTA(Atomikos)数据源,Mybatis * * @author fengxuechao */@Configuration@MapperScan(basePackages = "com.example.atomikos.dao.one", sqlSessionFactoryRef = "oneSqlSessionFactory")public class OneDatabaseConfig {
/** * 设置JTA(Atomikos)数据源 * * @return {@link AtomikosDataSourceBean} */ @Primary @Bean @ConfigurationProperties(prefix = "spring.jta.atomikos.datasource.one") public DataSource oneDataSource() {
return new AtomikosDataSourceBean(); } /** * 设置Mybatis的会话工厂类 * * @param dataSource JTA(Atomikos)数据源 * @return {@link SqlSessionFactoryBean#getObject()} * @throws Exception */ @Primary @Bean(name = "oneSqlSessionFactory") public SqlSessionFactory oneSqlSessionFactory(DataSource dataSource) throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean(); bean.setDataSource(dataSource); return bean.getObject(); }}

2.4.3.配置声明式事务(tx+aop)

Java配置声明式事务AOP

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 fengxuechao  */  @Configuration  public class TransactionConfig {
private static final int TX_METHOD_TIMEOUT = 5; private static final String AOP_POINTCUT_EXPRESSION = "execution (* com.example.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
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()); } }

等同于下面的Spring XML配置

2.5.实例

2.5.1.项目结构

项目工程结构

2.5.2.数据库

personal.sql

create table if not exists article  (    id bigint unsigned auto_increment      primary key,    content varchar(255) null,    title varchar(255) null,    url varchar(255) null  );    create table if not exists user  (    id bigint unsigned auto_increment primary key,    username varchar(255) charset utf8 null,    password varchar(255) charset utf8 null  );

secondary.sql

create table book  (    id         bigint unsigned auto_increment primary key,    name       varchar(255)    null,    article_id bigint unsigned null,    user_id    bigint unsigned null  );

2.5.3.Maven依赖

4.0.0
com.example
learn-jta-atomikos-SpringBoot
0.0.1-SNAPSHOT
jar
learn-jta-atomikos
Demo project for Spring Boot
org.springframework.boot
spring-boot-starter-parent
2.0.5.RELEASE
UTF-8
UTF-8
1.8
org.mybatis.spring.boot
mybatis-spring-boot-starter
1.3.2
spring-boot-starter-logging
org.springframework.boot
org.springframework.boot
spring-boot-devtools
runtime
mysql
mysql-connector-java
runtime
com.alibaba
druid-spring-boot-starter
1.1.10
org.springframework.boot
spring-boot-starter-jta-atomikos
org.aspectj
aspectjweaver
1.9.2
org.springframework.boot
spring-boot-starter-log4j2
com.lmax
disruptor
3.4.2
org.projectlombok
lombok
org.springframework.boot
spring-boot-starter-actuator
spring-boot-starter-logging
org.springframework.boot
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-configuration-processor
true
org.springframework.boot
spring-boot-starter-test
test
org.springframework.boot
spring-boot-maven-plugin

2.5.4.配置

application.properties

spring.application.name=learn-jta-atomikos    # 开启下划线-驼峰命名转换  mybatis.configuration.map-underscore-to-camel-case=true    spring.aop.proxy-target-class=true    ## jta相关参数配置  # 如果你在JTA环境中,并且仍然希望使用本地事务,你可以设置spring.jta.enabled属性为false以禁用JTA自动配置。  spring.jta.enabled=true  # 必须配置唯一的资源名  spring.jta.atomikos.datasource.one.unique-resource-name=jta-personal  # 配置Druid的属性 https://github.com/alibaba/druid/wiki/DruidDataSource%E9%85%8D%E7%BD%AE%E5%B1%9E%E6%80%A7%E5%88%97%E8%A1%A8  spring.jta.atomikos.datasource.one.xa-data-source-class-name=com.alibaba.druid.pool.xa.DruidXADataSource  spring.jta.atomikos.datasource.one.xa-properties.url=jdbc:mysql://localhost:3306/personal?characterEncoding=utf-8&useSSL=false&allowMultiQueries=true  spring.jta.atomikos.datasource.one.xa-properties.username=root  spring.jta.atomikos.datasource.one.xa-properties.password=123456  spring.jta.atomikos.datasource.one.xa-properties.filters=slf4j,stat,wall,config  #spring.jta.atomikos.datasource.one.xa-properties.connectionProperties=config.decrypt=true;config.decrypt.key=${druid.publickey}    spring.jta.atomikos.datasource.two.unique-resource-name=jta-book  spring.jta.atomikos.datasource.two.max-pool-size=8  spring.jta.atomikos.datasource.two.xa-data-source-class-name=com.alibaba.druid.pool.xa.DruidXADataSource  spring.jta.atomikos.datasource.two.xa-properties.url=jdbc:mysql://localhost:3306/secondary?characterEncoding=utf-8&useSSL=false&&allowMultiQueries=true  spring.jta.atomikos.datasource.two.xa-properties.username=root  spring.jta.atomikos.datasource.two.xa-properties.password=123456  spring.jta.atomikos.datasource.two.xa-properties.filters=slf4j,stat,wall,config  #spring.jta.atomikos.datasource.two.xa-properties.connectionProperties=config.decrypt=true;config.decrypt.key=${druid.publickey}    ## Druid监控设置  spring.datasource.druid.web-stat-filter.exclusions=*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*  spring.datasource.druid.stat-view-servlet.url-pattern=/druid/*  spring.datasource.druid.stat-view-servlet.reset-enable=true  spring.datasource.druid.stat-view-servlet.login-username=admin  spring.datasource.druid.stat-view-servlet.login-password=admin  spring.datasource.druid.aop-patterns=com.example.atomikos.service.*

OneDatabaseConfig

package com.example.atomikos.config;    import com.atomikos.jdbc.AtomikosDataSourceBean;  import org.apache.ibatis.session.SqlSessionFactory;  import org.mybatis.spring.SqlSessionFactoryBean;  import org.mybatis.spring.annotation.MapperScan;  import org.springframework.boot.context.properties.ConfigurationProperties;  import org.springframework.context.annotation.Bean;  import org.springframework.context.annotation.Configuration;  import org.springframework.context.annotation.Primary;    import javax.sql.DataSource;    /**  * 设置JTA(Atomikos)数据源,Mybatis  *  * @author fengxuechao  */  @Configuration  @MapperScan(basePackages = "com.example.atomikos.dao.one", sqlSessionFactoryRef = "oneSqlSessionFactory")  public class OneDatabaseConfig {
/** * 设置JTA(Atomikos)数据源 * * @return {@link AtomikosDataSourceBean} */ @Primary @Bean @ConfigurationProperties(prefix = "spring.jta.atomikos.datasource.one") public DataSource oneDataSource() {
return new AtomikosDataSourceBean(); } /** * 设置Mybatis的会话工厂类 * * @param dataSource JTA(Atomikos)数据源 * @return {@link SqlSessionFactoryBean#getObject()} * @throws Exception */ @Primary @Bean(name = "oneSqlSessionFactory") public SqlSessionFactory oneSqlSessionFactory(DataSource dataSource) throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean(); bean.setDataSource(dataSource); return bean.getObject(); } }

TwoDatabaseConfig

package com.example.atomikos.config;    import com.atomikos.jdbc.AtomikosDataSourceBean;  import org.apache.ibatis.session.SqlSessionFactory;  import org.mybatis.spring.SqlSessionFactoryBean;  import org.mybatis.spring.annotation.MapperScan;  import org.springframework.beans.factory.annotation.Qualifier;  import org.springframework.boot.context.properties.ConfigurationProperties;  import org.springframework.context.annotation.Bean;  import org.springframework.context.annotation.Configuration;    import javax.sql.DataSource;    /**  * 设置JTA(Atomikos)数据源,Mybatis  *  * @author fengxuechao  */  @Configuration  @MapperScan(basePackages = "com.example.atomikos.dao.two", sqlSessionFactoryRef = "twoSqlSessionFactory")  public class TwoDatabaseConfig {
/** * 设置JTA(Atomikos)数据源 * * @return {@link AtomikosDataSourceBean} */ @Bean(name = "twoAtomikosDataSource") @ConfigurationProperties(prefix = "spring.jta.atomikos.datasource.two") public DataSource oneDataSource() {
return new AtomikosDataSourceBean(); } /** * 设置Mybatis的会话工厂类 * * @param dataSource JTA(Atomikos)数据源 * @return {@link SqlSessionFactoryBean#getObject()} * @throws Exception */ @Bean(name = "twoSqlSessionFactory") public SqlSessionFactory oneSqlSessionFactory(@Qualifier("twoAtomikosDataSource") DataSource dataSource) throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean(); bean.setDataSource(dataSource); return bean.getObject(); } }

TransactionConfig

package com.example.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 fengxuechao  */  @Configuration  public class TransactionConfig {
private static final int TX_METHOD_TIMEOUT = 5; private static final String AOP_POINTCUT_EXPRESSION = "execution (* com.example.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
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()); } }

2.5.5.实体类

package com.example.atomikos.entity;    import lombok.AllArgsConstructor;  import lombok.Data;  import lombok.NoArgsConstructor;    import java.io.Serializable;    /**  * 文章  *  * @author fengxuechao  */  @Data  @NoArgsConstructor  @AllArgsConstructor  public class ArticleDO implements Serializable {
private static final long serialVersionUID = 3971756585655871603L; private Long id; private String title; private String content; private String url; }
package com.example.atomikos.entity;    import lombok.AllArgsConstructor;  import lombok.Data;  import lombok.NoArgsConstructor;    import java.io.Serializable;    /**  * 书  *  * @author fengxuechao  */  @Data  @NoArgsConstructor  @AllArgsConstructor  public class BookDO implements Serializable {
private static final long serialVersionUID = 3231762613546697469L; private Long id; private String name; private Long articleId; private Long userId; }
package com.example.atomikos.entity;    import lombok.Data;    @Data  public class BookVo extends BookDO {
private UserDO user; }
package com.example.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; private String username; private String password; }

2.5.6.Dao层

UserDao

注意包名,UserDao对应的配置为OneDatabaseConfig

package com.example.atomikos.dao.one;    import com.example.atomikos.entity.UserDO;  import org.apache.ibatis.annotations.*;  import org.springframework.stereotype.Repository;    import java.util.List;    /**  * @author fengxuechao  * @date 2018/11/30  */  @Repository  public interface UserDao {
/** * 根据主键查询一条记录 * * @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
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); }

BookDao

注意包名,UserDao对应的配置为TwoDatabaseConfig

package com.example.atomikos.dao.two;    import com.example.atomikos.entity.BookDO;  import org.apache.ibatis.annotations.*;  import org.springframework.stereotype.Repository;    import java.util.List;    /**  * @author fengxuechao  * @date 2018/11/30  */  @Mapper  @Repository  public interface BookDao {
/** * 分页查询 * * @param page 页码 * @param size 每页记录数 * @return */ @Select("select id, name, article_id as articleId, user_id as userId from book limit ${page}, ${size}") List
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); }

2.5.7.Service层

Bookservice

package com.example.atomikos.service;    import com.example.atomikos.entity.BookDO;  import com.example.atomikos.entity.UserDO;    import java.util.List;    /**  * 主要目的是测试分布式事务  *  * @author fengxuechao  */  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
list(Integer page, Integer size); }

BookServiceImpl

请注意,其中有些代码故意抛出异常是为了测试的目的。

package com.example.atomikos.service.impl;    import com.example.atomikos.dao.one.UserDao;  import com.example.atomikos.dao.two.BookDao;  import com.example.atomikos.entity.BookDO;  import com.example.atomikos.entity.UserDO;  import com.example.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 fengxuechao  */  @Service  public class BookServiceImpl implements BookService {
@Autowired private BookDao bookDao; @Autowired private UserDao userDao; /** * 保存书本和文章, 使用声明式事务(tx+aop形式) * * @param book {@link BookDO} * @param user {@link UserDO} * @return */ @Override public BookDO save(BookDO book, UserDO user) {
int userSave = userDao.save(user); if (userSave == 0) {
return null; } book.setUserId(user.getId()); int bookSave = bookDao.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 = bookDao.get(id); UserDO user = userDao.get(book.getUserId()); return new BookDO(book.getId(), book.getName(), book.getArticleId(), user.getId()); } /** * 分页查询 * * @param page * @param size * @return */ @Override public List
list(Integer page, Integer size) {
page = (page < 1 ? 0 : page - 1) * size; return bookDao.list(page, size); } /** * 修改书本和文章, 使用声明式事务(注解形式) * * @param book * @param user * @return */ @Transactional(rollbackFor = Exception.class) public BookDO update(BookDO book, UserDO user) {
int bookUpdate = bookDao.update(book); if (bookUpdate != 1) {
return null; } int userUpdate = userDao.update(user); if (userUpdate != 1) {
return null; } throw new RuntimeException("测试分布式事务(注解形式)"); // return book; } /** * 删除书本和文章 * * @param id * @return */ public int delete(Long id) {
BookDO book = bookDao.get(id); System.err.println(book); Long userId = book.getUserId(); int userDelete = userDao.delete(userId); if (userDelete != 1) {
return 0; } int bookDelete = bookDao.delete(id); if (bookDelete != 1) {
return 0; } throw new RuntimeException("测试没有添加分布式事务管理)"); // return 1; } }

2.5.8.Controller层

package com.example.atomikos.controller;    import com.example.atomikos.entity.BookDO;  import com.example.atomikos.entity.BookVo;  import com.example.atomikos.service.BookService;  import com.example.atomikos.service.impl.BookServiceImpl;  import org.springframework.beans.factory.annotation.Autowired;  import org.springframework.web.bind.annotation.*;    import java.util.List;    /**  * @author fengxuechao  */  @RestController  @RequestMapping("/books")  public class BookController {
@Autowired private BookService bookService; @GetMapping public List
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); } }

2.5.9.单元测试

BookServiceImplTest

由于故意抛出异常,故单元测试失败,查看数据库,数据库中数据保持原样

package com.example.atomikos.service.impl;    import com.example.atomikos.entity.BookDO;  import com.example.atomikos.entity.UserDO;  import com.example.atomikos.service.BookService;  import org.junit.Assert;  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.test.context.junit4.SpringRunner;    /**  * 测试分布式事务:切面拦截形式, 注解式  */  @RunWith(SpringRunner.class)  @SpringBootTest  public class BookServiceImplTest {
@Autowired BookService bookService; /** * 测试分布式事务(切面拦截形式) */ @Test public void save() {
BookDO book = new BookDO(); book.setName("Book Name - 001"); book.setArticleId(69L); UserDO user = new UserDO(); user.setUsername("username - 001"); user.setPassword("password - 001"); BookDO bookDO = bookService.save(book, user); System.out.println(bookDO); } /** * 测试分布式事务(注解式) */ @Test public void update() {
BookDO book = new BookDO(); book.setId(10L); book.setName("Book Name - 002"); book.setArticleId(69L); UserDO user = new UserDO(); user.setId(18L); user.setUsername("username - 002"); user.setPassword("password - 002"); ((BookServiceImpl)bookService).update(book, user); } /** * 没有事务管理 */ @Test public void delete() {
int delete = ((BookServiceImpl) bookService).delete(11L); Assert.assertEquals(1, delete); } }

BookControllerTest

package com.example.atomikos.controller;    import com.example.atomikos.dao.one.UserDao;  import com.example.atomikos.entity.BookVo;  import com.example.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;    /**  * 测试分布式事务  */  @RunWith(SpringRunner.class)  @SpringBootTest  public class BookControllerTest {
private MockMvc mockMvc; private ObjectMapper objectMapper = new ObjectMapper(); @Autowired private UserDao userDao; @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("username - 002"); user.setPassword("password - 002"); BookVo book = new BookVo(); book.setName("Book Name - 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("Book Name - 002"))) .andExpect(jsonPath("$.articleId", is(69))) .andDo(print()); } /** * 注解式 * * @throws Exception */ @Test public void update() throws Exception {
UserDO user = userDao.get(3L); assert user != null; user.setUsername("username - 003"); user.setPassword("password - 003"); BookVo book = new BookVo(); book.setId(3L); book.setName("Book Name - 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("Book Name - 003"))) .andExpect(jsonPath("$.articleId", is(69))) .andDo(print()); } /** * 没有事务管理 * * @throws Exception */ @Test public void delete() throws Exception {
this.mockMvc.perform( MockMvcRequestBuilders.delete("/books/4")) .andExpect(status().isOk()) .andDo(print()); } }

3.引用

转载地址:http://xvxzb.baihongyu.com/

你可能感兴趣的文章
Go语言学习Part4-1:方法和接口
查看>>
Leetcode Go 《精选TOP面试题》20200628 69.x的平方根
查看>>
leetcode 130. Surrounded Regions
查看>>
【托业】【全真题库】TEST2-语法题
查看>>
博客文格式优化
查看>>
【托业】【新托业全真模拟】疑难语法题知识点总结(01~05)
查看>>
【SQL】group by 和order by 的区别。
查看>>
【Python】详解Python多线程Selenium跨浏览器测试
查看>>
Jmeter之参数化
查看>>
Shell 和Python的区别。
查看>>
Python 列表(list)、字典(dict)、字符串(string)常用基本操作小结
查看>>
Loadrunner之https协议录制回放报错如何解决?(九)
查看>>
python中xrange和range的异同
查看>>
列表、元组、集合、字典
查看>>
【Python】easygui小甲鱼
查看>>
【Python】关于Python多线程的一篇文章转载
查看>>
【Pyton】【小甲鱼】文件
查看>>
【Pyton】【小甲鱼】永久存储:腌制一缸美味的泡菜
查看>>
【Pyton】【小甲鱼】异常处理:你不可能总是对的
查看>>
APP性能测试工具
查看>>