6 TxManager 搭建
6.1 创建项目,添加依赖
- 新建项目 TxManager,并添加依赖
- 依赖包含了 Spring-boot 的依赖,版本是 2.0.5,如果希望把版本改变成 2.2.2 或其他版本只需要添加 spring-boot-starter-parent 继承即可
<dependencies> <dependency> <groupId>com.codingapi.txlcn</groupId> <artifactId>txlcn-tm</artifactId> <version>5.0.2.RELEASE</version> </dependency> </dependencies>
6.2 执行 SQL 文件
- 执行 tx-manager.sql 文件(在任意的数据库下执行即可)
- tx-manager.sql 在 txlcn-tm-5.0.2.RELEASE.zip 压缩包中。
- 在 MySQL 生成 tx-manager 的数据库,在数据库中新建 t_tx_exception 的表,此表用作存储事务组信息
- 注意:
- 默认情况下 tx-manager 需要记录日志信息的,需要在项目中配置日志连接数据库相关参数,其中日志存储数据库没有要求,可以存储到任意数据库中,当运行后会自动在数据库中生成一个日志表。如果不希望记录日志可以直接设置 tx-lcn.logger.enabled=false,关闭日志功能,下面的日志连接数据库参数也可以不用配置。
- 在实际案例演示中会把所有的日志记录功能关闭。如果希望记录记录日志需要把下面代码在所有引用 tx-lcn 的项目的配置文件中进行配置
tx-lcn.logger.enabled=true tx-lcn.logger.driver-class-name=com.mysql.jdbc.Driver tx-lcn.logger.jdbc-url=jdbc:mysql://192.168.8.131:3306/tx-manager?characterEncoding=UTF-8 tx-lcn.logger.username=root tx-lcn.logger.password=root
6.3 配置配置文件
- 在 TxManager 项目的 resource 下新建 application.properties。tx-lcn 在当前版本有个 bug 只能使用 properties 文件,使用 yml 文件会导致配置文件无法被加载的问题
- 配置文件中内容上半部分是 tx-manager 数据库的连接信息。中间包含 redis 连接信息(此处连接的是 redis 单机版,端口默认,没有密码),下面是关闭日志记录功能
- 小提示:
- 依赖 Redis,所以需要安装 Redis。
- 7970 是客户端访问端口,是 Txmanager 可视化界面访问端口,此端口任意。
spring.application.name=TransactionManager server.port=7970 spring.datasource.driver-class-name=com.mysql.jdbc.Driver spring.datasource.url=jdbc:mysql://localhost:3306/tx-manager?characterEncoding=UTF-8 spring.datasource.username=root spring.datasource.password=root spring.redis.host=192.168.8.129 tx-lcn.logger.enabled=false
6.4 新建启动类
- 注意注解@EnableTrasactionManagerServer 必须有
@SpringBootApplication @EnableTransactionManagerServer public class MyApplication { public static void main(String[] args) { SpringApplication.run(MyApplication.class,args); } }
6.5 访问管理界面
- 在浏览器输入:http://localhost:7970 访问。
- 密码默认是 codingapi
- 可以在配置文件中修改登录密码
tx-lcn.manager.admin-key=dqcgm
7 LCN 事务模式
7.1 创建数据库表
注意:不要给 student 表添加外键约束。如果添加会导致分布式事务执行时 student 新增失败,因为 teacher 没有提交时 student 的 tid 值无法获取。
7.2 创建项目
- 案例使用聚合项目进行演示。
- 创建父项目,名称为 LcnParent
7.2.1 配置 pom.xml
- txlcn-tc 是 TX-LCN 的客户端包
- txlcn-txmsg-netty 是 LCN 客户端连接 TxManager 需要的包
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.2.6.RELEASE</version> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.1.2</version> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.48</version> <scope>runtime</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>com.codingapi.txlcn</groupId> <artifactId>txlcn-tc</artifactId> <version>5.0.2.RELEASE</version> </dependency> <dependency> <groupId>com.codingapi.txlcn</groupId> <artifactId>txlcn-txmsg-netty</artifactId> <version>5.0.2.RELEASE</version> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Hoxton.SR4</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>
7.3 新建 pojo 项目
- 把实体类提出来
- 新建两个实体类。
- 新建 com.dqcgm.pojo.Teacher
@Data public class Teacher { private Long id; private String name; } 12345
- 新建 com.dqcgm.pojo.Student
@Data public class Student { private Long id; private String name; private Long tid; }
7.4 创建项目 teacher_insert
- 新建 teacher_insert 项目
7.4.1 配置 pom.xml
- 依赖 pojo
<dependencies> <dependency> <artifactId>pojo</artifactId> <groupId>com.dqcgm</groupId> <version>0.0.1-SNAPSHOT</version> </dependency> </dependencies>
7.4.2 编写配置文件
- 新建 application.yml.
- 数据源连接的是 Teacher 表所在数据库
- eureka 单机版可以省略。
- manager-address 配置 TxManager 项目的 ip 及端口。端口是内部访问端口,而不是可视化页面的端口
spring: datasource: url: jdbc:mysql://localhost:3306/microservice driver-class-name: com.mysql.jdbc.Driver username: root password: root application: name: teacher-insert server: port: 8080 eureka: client: service-url: defaultZone: http://localhost:8761/eureka/ tx-lcn: client: manager-address: 127.0.0.1:8070
7.4.3 新建 mapper
- 新建 com.dqcgm.mapper.TeacherMapper
@Mapper public interface TeacherMapper { @Insert("insert into teacher values(#{id},#{name})") int insert(Teacher teacher); } 12345
7.4.4 新建 service 及实现类
- 新建 com.dqcgm.service.TeacherService 及实现类。
- 方法上@Transactional 一定要有。本地事务控制。
- @LcnTransaction 表示当前方法加入到分布式事务控制。
@LcnTransaction 属性 propagation 可取值
1.DTXPropagation.REQUIRED:默认值,表示如果当前没有事务组创建事务组,如果有事务组,加入事务组。多用在事务发起方。
DTXPropagation.SUPPORTS:如果当前没有事务组以本地事务运行,如果当前有事务组加入事务组。多用在事务参与方法。
public interface TeacherService { int insert(Teacher teacher); } 123 @Service public class TeacherServiceImpl implements TeacherService { @Autowired private TeacherMapper teacherMapper; @Override @LcnTransaction @Transactional public int insert(Teacher teacher) { return teacherMapper.insert(teacher); } }
7.4.5 新建控制器
- 新建 com.dqcgm.controller.TeacherController。
- 由于在 student_insert 中通过 OpenFeign 进行条件,参数使用请求体数据,所以控制器方法的参数需要添加@RequestBody
@Controller public class TeacherController { @Autowired private TeacherService teacherService; @RequestMapping("/insert") @ResponseBody public int insert(@RequestBody Teacher teacher){ System.out.println("taecher"+teacher); return teacherService.insert(teacher); } }
7.4.6 新建启动器
- 新建 com.dqcgm.TeacherInsertApplication。
- 一定要有注解@EnableDistributedTransaction 表示启动分布式事务
@SpringBootApplication @EnableDistributedTransaction public class TeacherInsertApplication { public static void main(String[] args) { SpringApplication.run(TeacherInsertApplication.class,args); } }
7.5 新建项目 student_insert
7.5.1 编写 pom.xml
- 添加对 pojo 依赖
<dependencies> <dependency> <artifactId>pojo</artifactId> <groupId>com.dqcgm</groupId> <version>0.0.1-SNAPSHOT</version> </dependency> </dependencies>
7.5.2 创建配置文件
- 新建 application.yml
spring: datasource: url: jdbc:mysql://localhost:3306/microservice driver-class-name: com.mysql.jdbc.Driver username: root password: root application: name: student-insert server: port: 8081 eureka: client: service-url: defaultZone: http://localhost:8761/eureka/ tx-lcn: client: manager-address: 127.0.0.1:8070
7.5.3 新建 Feign 接口
- 新建 com.dqcgm.feign.TeacherInsertFeign
@FeignClient("teacher-insert") public interface TeacherInsertFeign { @RequestMapping("/insert") int insert(Teacher teacher); }
7.5.4 新建 Mapper
- 新建 com.dqcgm.mapper.StudentMapper
@Mapper public interface StudentMapper { @Insert("insert into student values(#{id},#{name},#{tid})") int insert(Student student); }
7.5.5 新建 service 及实现类
- 新建 com.dqcgm.service.StudentService
- 实现类中对 Teacher 和 Student 的主键都是随机数,为了测试时多次测试方便,所以没有给固定值
- Student 的姓名通过客户端请求参数传递的,其他都不需要通过参数设置
public interface StudentService { int insert(Student student); } @Service public class StudentServiceImpl implements StudentService { @Autowired private StudentMapper studentMapper; @Autowired private TeacherInsertFeign teacherInsertFeign; @Override @LcnTransaction @Transactional public int insert(Student student) { Teacher teacher = new Teacher(); Random random = new Random(); teacher.setId((long)random.nextInt(10000)); teacher.setName("随意的名称"); student.setTid(teacher.getId()); student.setId((long)random.nextInt(10000)); teacherInsertFeign.insert(teacher); return studentMapper.insert(student); } }
7.5.6 新建控制器
- 新建 com.dqcgm.controller.StudentController
@Controller public class StudentController { @Autowired private StudentService studentService; @RequestMapping("/insert") @ResponseBody public int insert(Student student){ return studentService.insert(student); } }
7.5.7 新建启动类
- 新建 com.dqcgm.StudentInsertApplication
@SpringBootApplication @EnableDistributedTransaction @EnableFeignClients public class StudentInsertApplication { public static void main(String[] args) { SpringApplication.run(StudentInsertApplication.class,args); } }
7.6 测试结果
- 在浏览器中输入 http://localhost:8081/insert?name=dqcgm后,如果页面显示 1 并且数据库 teacher 表和 student 表各增加一条数据表示新增成功。
为了测试分布式事务效果,在 student_insert 项目实现类方法 return 上面添加 int i =5/0; 的算术异常,再次访问 url 页面会报 500,并且数据库中没有新增数据,说明分布式事务成功了。
8 TCC 事务模式(多模式混合使用)
- 在上面 LCN 事务模式代码基础上进行修改
8.1 新建项目 mongodb_insert
8.1.1 修改 pom.xml
- 在当前项目中引入 mongodb 的依赖。如果在父项目中进行引入,则所有的子项目都需要配置 mongodb 的相关属性
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-mongodb</artifactId> <version>2.2.6.RELEASE</version> </dependency> </dependencies>
8.1.2 新建配置文件
- 新建 application.yml。
- 虽然当前项目是连接 Mongodb 但是也需要配置 MySQL 数据源,因为 LCN 需要在 MySQL 中记录异常信息等
- 配置文件中比别的项目多了 MongoDB 的配置
spring: datasource: url: jdbc:mysql://localhost:3306/microservice driver-class-name: com.mysql.jdbc.Driver username: root password: root application: name: mongodb-insert data: mongodb: authentication-database: admin username: dqcgm password: dqcgmpwd database: lcn host: 192.168.8.139 port: 27017 server: port: 8082 eureka: client: service-url: defaultZone: http://localhost:8761/eureka/ tx-lcn: client: manager-address: 127.0.0.1:8070
8.1.3 新建实体类
- 新建 com.dqcgm.pojo.People
@Data public class People { private String id; private String name; }
8.1.4 新建 service 及实现类
- 新建 com.dqcgm.service.PeopleService 及实现类
- 具有@TccTransaction 注解的方法有以下特性
1.可以没有@Transactional
2.如果整个分布式事务所有方法执行都没有异常,会回调名称为:confirm+方法名首字母大写的方法。insert 方法的回调方法叫做 confirmInsert()。同时方法参数也可以传递给回调方法
- 3.只要整个分布式事务中有一个方法出现异常,会回调 cancel+方法名首字母大写的回调方法。需要在这个方法中编写事务回滚的业务。
- @TccTransaction 注解属性说明:
- cancelMethod:明确指定失败的回调方法名
confirmMethod:明确指定成功的回调方法名
public interface PeopleService { int insert(People people); } @Service public class PeopleServiceImpl implements PeopleService { @Autowired private MongoTemplate mongoTemplate; @Override @TccTransaction public int insert(People people) { People result = mongoTemplate.insert(people); if(result!=null){ return 1; } return 0; } public void cancelInsert(People people){ System.out.println("执行了 cancel 方法"+people); mongoTemplate.remove(people); System.out.println("所谓的事务回滚就是删除新增的数据"); } public void confirmInsert(People people){ System.out.println("执行了 confirm 方法"+people); } }
8.1.5 新建控制器
- 新建 com.dqcgm.controller.PeopleController
@Controller public class PeopleController { @Autowired private PeopleService peopleService; @RequestMapping("/insert") @ResponseBody public int insert(People people){ return peopleService.insert(people); } }
8.1.6 测试
- 在浏览器输入 http://localhost:8082/insert?name=dqcgm观察 mongodb 中是否出现了 lcn 的 数据库,数据库中是否出现 People 的集合,people 集合中 name 属性值为 dqcgm
8.2 修改 student_insert
8.2.1 新建 feign 接口
- 新建 com.dqcgm.feign.MongodbInsertFeign。
- 为了传递普通表单数据,insert 方法参数由@RequestParam。mongodb_insert 控制器方法参数就可以使用 String name 或 People 进行接收
@FeignClient("mongodb-insert") public interface MongodbInsertFeign { @RequestMapping("/insert") int insert(@RequestParam String name); }
8.2.2 修改 service 实现类
- 修改 com.dqcgm.service.impl.StudentServiceImpl。
- 在实现类中调用 feign 接口的方法
- 当前方法依然使用 LCN 事务模式。方法上面加什么事务模式注解只考虑当前方法本地事务,不考虑调用远程方法的事务。如果当前方法中没有本地事务,全是调用远程方法,那么当前方法使用 LCN 或 TCC 事务模式都可以,但是必须要有事务模式,因为如果没有注解就不会想 TxManager 中创建事务组。
@Service public class StudentServiceImpl implements StudentService { @Autowired private StudentMapper studentMapper; @Autowired private TeacherInsertFeign teacherInsertFeign; @Autowired private MongodbInsertFeign mongodbInsertFeign; @Override @LcnTransaction @Transactional public int insert(Student student) { Teacher teacher = new Teacher(); Random random = new Random(); teacher.setId((long)random.nextInt(10000)); teacher.setName("随意的名称"); student.setTid(teacher.getId()); student.setId((long)random.nextInt(10000)); teacherInsertFeign.insert(teacher); mongodbInsertFeign.insert("随意的名称"); return studentMapper.insert(student); } }