07、分布式事务 实战 - LCN 事务模式-6250字匠心出品

1.创建数据库表

  • 注意:不要给 student 表添加外键约束。如果添加会导致分布式事务执行时 student 新增失败,因为 teacher 没有提交时 student 的 tid 值无法获取。
     

2.创建项目

  • 案例使用聚合项目进行演示。
  • 创建父项目,名称为 LcnParent

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>

3.新建 pojo 项目

  • 把实体类提出来
  • 新建两个实体类。
  • 新建 com.dqcgm.pojo.Teacher
@Data 
public class Teacher {
   
      
	private Long id; 
	private String name; 
}

  • 新建 com.dqcgm.pojo.Student
@Data 
public class Student {
   
      
	private Long id; 
	private String name; 
	private Long tid; 
}

4.创建项目 teacher_insert

  • 新建 teacher_insert 项目

4.1 配置 pom.xml

  • 依赖 pojo
<dependencies> 
	<dependency> 
		<artifactId>pojo</artifactId> 
		<groupId>com.dqcgm</groupId> 
		<version>0.0.1-SNAPSHOT</version> 
	</dependency> 
</dependencies>

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

4.3 新建 mapper

  • 新建 com.dqcgm.mapper.TeacherMapper
@Mapper 
public interface TeacherMapper {
   
      
	@Insert("insert into teacher values(#{id},#{name})") 
	int insert(Teacher teacher); 
}

4.4 新建 service 及实现类

  • 新建 com.dqcgm.service.TeacherService 及实现类。
  • 方法上@Transactional 一定要有。本地事务控制。
  • @LcnTransaction 表示当前方法加入到分布式事务控制。
  • @LcnTransaction 属性 propagation 可取值
    1、 DTXPropagation.REQUIRED:默认值,表示如果当前没有事务组创建事务组,如果有事务组,加入事务组多用在事务发起方;
    DTXPropagation.SUPPORTS:如果当前没有事务组以本地事务运行,如果当前有事务组加入事务组。多用在事务参与方法。
public interface TeacherService {
   
      
	int insert(Teacher teacher); 
}

@Service 
public class TeacherServiceImpl implements TeacherService {
   
     
	@Autowired
	private TeacherMapper teacherMapper;

	@Override 
	@LcnTransaction 
	@Transactional
	public int insert(Teacher teacher) {
   
      
		return teacherMapper.insert(teacher); 
	} 
}

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); 
	} 
}

4.6 新建启动器

  • 新建 com.dqcgm.TeacherInsertApplication。
  • 一定要有注解@EnableDistributedTransaction 表示启动分布式事务
@SpringBootApplication 
@EnableDistributedTransaction 
public class TeacherInsertApplication {
   
     
	public static void main(String[] args) {
   
     
		SpringApplication.run(TeacherInsertApplication.class,args); 
	} 
}

5.新建项目 student_insert

5.1 编写 pom.xml

  • 添加对 pojo 依赖
<dependencies> 
	<dependency> 
		<artifactId>pojo</artifactId> 
		<groupId>com.dqcgm</groupId> 
		<version>0.0.1-SNAPSHOT</version> 
	</dependency> 
</dependencies>

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

5.3 新建 Feign 接口

  • 新建 com.dqcgm.feign.TeacherInsertFeign
@FeignClient("teacher-insert") 
public interface TeacherInsertFeign {
   
      
	@RequestMapping("/insert") int insert(Teacher teacher); 
}

5.4 新建 Mapper

  • 新建 com.dqcgm.mapper.StudentMapper
@Mapper 
public interface StudentMapper {
   
      
	@Insert("insert into student values(#{id},#{name},#{tid})") 
	int insert(Student student); 
}

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);
	}
}

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); 
	} 
}

5.7 新建启动类

  • 新建 com.dqcgm.StudentInsertApplication
@SpringBootApplication 
@EnableDistributedTransaction 
@EnableFeignClients 
public class StudentInsertApplication {
   
     
	public static void main(String[] args) {
   
     
		SpringApplication.run(StudentInsertApplication.class,args);
	}
}

6.测试结果

  • 在浏览器中输入 http://localhost:8081/insert?name=dqcgm后,如果页面显示 1 并且数据库 teacher 表和 student 表各增加一条数据表示新增成功。
  • 为了测试分布式事务效果,在 student_insert 项目实现类方法 return 上面添加 int i =5/0; 的算术异常,再次访问 url 页面会报 500,并且数据库中没有新增数据,说明分布式事务成功了。