7)分布式事务 实战:LCN 事务模式-6250字匠心出品

LCN 事务模式

  • 1.创建数据库表
  • 2.创建项目
    • 2.1 配置 pom.xml
  • 3.新建 pojo 项目
  • 4.创建项目 teacher_insert
    • 4.1 配置 pom.xml
    • 4.2 编写配置文件
    • 4.3 新建 mapper
    • 4.4 新建 service 及实现类
    • 4.5 新建控制器
    • 4.6 新建启动器
  • 5.新建项目 student_insert
    • 5.1 编写 pom.xml
    • 5.2 创建配置文件
    • 5.3 新建 Feign 接口
    • 5.4 新建 Mapper
    • 5.5 新建 service 及实现类
    • 5.6 新建控制器
    • 5.7 新建启动类
  • 6.测试结果

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,并且数据库中没有新增数据,说明分布式事务成功了。