当前位置: 首页 > 工具软件 > tx-lcn > 使用案例 >

TX-LCN(分布式事务框架)

刘辰钊
2023-12-01

一、环境搭建(搭建TxManager)

(1)导入依赖

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.3.12.RELEASE</version>
</parent>

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>Hoxton.SR12</version>
            <scope>import</scope>
            <type>pom</type>
        </dependency>
        <dependency>
            <groupId>com.codingapi.txlcn</groupId>
            <artifactId>txlcn-tm</artifactId>
            <version>5.0.2.RELEASE</version>
        </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>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.2.0</version>
        </dependency>

        <dependency>
            <groupId>com.codingapi.txlcn</groupId>
            <artifactId>txlcn-tm</artifactId>
        </dependency>
    </dependencies>
</dependencyManagement>

(2)编写配置文件

spring.application.name=TransactionManager
server.port=7970
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/tx-manager?characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=root

# redis配置
spring.redis.host=192.168.8.128

# TM服务器IP修改, TC访问TM时,使用的IP地址。必须精确匹配。默认127.0.0.1。代表TC和TM必须在同一个主机中。
tx-lcn.manager.host=192.168.41.252

# TM服务器日志系统配置,默认关闭日志系统。需要独立配置日志的存储数据库连接
tx-lcn.logger.enabled=false
# 配置TM服务器日志系统数据库连接
tx-lcn.logger.driver-class-name=com.mysql.cj.jdbc.Driver
tx-lcn.logger.jdbc-url=jdbc:mysql://localhost:3306/tx-manager?characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
tx-lcn.logger.username=root
tx-lcn.logger.password=root
# 自动创建表格的配置项。可以自动创建日志表格。
spring.jpa.hibernate.ddl-auto=update

# 修改TM服务器的WEB控制台登录密码, 默认登录密码是  codingapi
tx-lcn.manager.admin-key=bjsxt

# TM事务管理端口。默认 0. 是server.port + 100计算得到。
# tx-lcn.manager.port=7971

(3)编写启动项

/**
 * EnableTransactionManagerServer - 开启TM服务器功能。
 *
 * 必须提供配置文件。注意:配置文件只能是properties格式。
 * 原因:txlcn-tm中包含一个配置文件,命名是application.properties。
 *      读取优先级高于yml配置文件。所以定义yml配置文件,会被忽略。
 *
 */
@SpringBootApplication
@EnableTransactionManagerServer
public class MyTMApp {
    public static void main(String[] args) {
        SpringApplication.run(MyTMApp.class, args);
    }
}

(4)导入SQL

CREATE DATABASE IF NOT EXISTS  `tx-manager` DEFAULT CHARSET utf8 COLLATE utf8_general_ci;
USE `tx-manager`;

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for t_tx_exception
-- ----------------------------
DROP TABLE IF EXISTS `t_tx_exception`;
CREATE TABLE `t_tx_exception`  (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `group_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  `unit_id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  `mod_id` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  `transaction_state` tinyint(4) NULL DEFAULT NULL,
  `registrar` tinyint(4) NULL DEFAULT NULL,
  `ex_state` tinyint(4) NULL DEFAULT NULL COMMENT '0 待处理 1已处理',
  `remark` varchar(10240) NULL DEFAULT NULL COMMENT '备注',
  `create_time` datetime(0) NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 967 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;

SET FOREIGN_KEY_CHECKS = 1;

(5)访问管理平台

通过浏览器访问 http://localhost:7970 。在界面中输入登录密码:bjsxt(默认密码是codingapi)。

二、LCN事务模式

(1)导入依赖

<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>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    <dependency>
        <groupId>com.codingapi.txlcn</groupId>
        <artifactId>txlcn-tc</artifactId>
    </dependency>
    <dependency>
        <groupId>com.codingapi.txlcn</groupId>
        <artifactId>txlcn-txmsg-netty</artifactId>
    </dependency>
</dependencies>

(2)编写配置文件

server:
  port: 8001
spring:
  application:
    name: tx-book-manager
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/tx-manager?serverTimezone=Asia/Shanghai
    username: root
    password: root
# 配置TM服务器地址。格式是 ip:port。 ip和端口查看TM WEB控制台中的IP和端口。
tx-lcn:
  client:
    manager-address: 192.168.41.252:8070

(3)实体类

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Book implements Serializable {
    private Long id;
    private String name;
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private Date publishTime;
    private Integer studentId; // 书籍所属学生主键
}

(4)数据访问接口

/**
 * 书籍数据访问对象
 */
@Mapper
public interface BookMapper {
    /**
     * 新增书籍
     * @param book
     * @return
     */
    @Insert("insert into tb_book (id, name, publish_time, student_id) " +
            "values(#{id}, #{name}, #{publishTime}, #{studentId})")
    int insert(Book book);

    /**
     * 根据学生主键,查询对应的所有书籍
     * @param studentId
     * @return
     */
    @Select("select id, name, publish_time as publishTime, student_id as studentId " +
            "from tb_book where student_id = #{studentId}")
    List<Book> selectByStudent(Integer studentId);
}

(5)编写服务层

@Service
public class BookServiceImpl implements BookService {
    @Autowired
    private BookMapper bookMapper;

    /**
     * 1. 生成主键
     * 2. 新增数据
     * @param book
     * @return
     */
    @Override
    @LcnTransaction
    @Transactional
    public String addBook(Book book) {
        book.setId(System.currentTimeMillis());

        int rows = bookMapper.insert(book);
        if(rows != 1){
            throw new RuntimeException("新增书籍失败");
        }
        return "新增书籍成功";
    }

    /**
     * 根据学生查询书籍
     * @param studentId
     * @return
     */
    @Override
    public List<Book> getBooksByStudent(Integer studentId) {
        return bookMapper.selectByStudent(studentId);
    }
}

(6)编写控制层

/**
 * 书籍控制器
 */
@RestController
public class BookController {
    @Autowired
    private BookService bookService;

    @RequestMapping("/addBook")
    public String addBook(Book book){
        return bookService.addBook(book);
    }

    @RequestMapping("/getBooksByStudentId")
    public List<Book> getBooksByStudentId(Integer studentId){
        return bookService.getBooksByStudent(studentId);
    }
}

(7)编写启动类

/**
 * EnableDistributedTransaction - 开启分布式事务管理逻辑。
 *  支持TX-LCN框架的所有分布式事务管理模式。
 */
@SpringBootApplication
@EnableDistributedTransaction
public class TxBooksManagerApp {
    public static void main(String[] args) {
        SpringApplication.run(TxBooksManagerApp.class, args);
    }
}

三、TCC事务模式

(1)导入依赖

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    <dependency>
        <groupId>com.codingapi.txlcn</groupId>
        <artifactId>txlcn-tc</artifactId>
    </dependency>
    <dependency>
        <groupId>com.codingapi.txlcn</groupId>
        <artifactId>txlcn-txmsg-netty</artifactId>
    </dependency>
    <!-- tc依赖要求,必须连接数据库。
         TX-LCN是基于数据库中的表格t_tx_exception,控制事务组
         tc依赖,没有数据库连接相关资源。需要独立依赖配置。
     -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-jdbc</artifactId>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
</dependencies>

(2)编写配置文件

server:
  port: 8002
spring:
  application:
    name: tx-phone-manager
  redis:
    host: 192.168.8.128
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/tx-manager?serverTimezone=Asia/Shanghai
    username: root
    password: root
tx-lcn:
  client:
    manager-address: 192.168.41.252:8070
phone:
  manager:
    keyPref: 'student:phone::'

(3)编辑实体类

/**
 * 保存在Redis中。
 * key是 前缀 + 学生主键。
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Phone implements Serializable {
    private String phoneNo; // 电话号码
    private Integer studentId; // 电话所属学生主键
}

(4)编辑数据访问实体类

@Repository
public class RedisDaoImpl implements RedisDao {
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @Override
    public void del(String key) {
        redisTemplate.delete(key);
    }

    @Override
    public void set(String key, Object value) {
        redisTemplate.opsForValue()
                .set(key, value);
    }

    @Override
    public <T> T get(String key) {
        T value = (T) redisTemplate.opsForValue()
                            .get(key);
        return value;
    }
}

(5)编写配置类

@Configuration
public class PhoneManagerConfiguration {
    /**
     * redis连接工厂由spring-boot-starter-data-redis自动创建。
     * 根据配置文件,连接服务器。
     * @param factory
     * @return
     */
    protected RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory){
        RedisTemplate<String, Object> redisTemplate =
                new RedisTemplate<>();

        // 配置,访问Redis服务器时,如何序列化键值对数据对象。
        // key使用字符串序列化方式。就是字符串原值。
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        // value使用JSON序列化方式。就是把Java对象,转换成JSON字符串并保存。
        redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        // hash数据的key序列化方案。字符串原值
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        // hash数据的value序列化方案,JSON格式字符串
        redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());

        // 设置Redis连接工厂
        redisTemplate.setConnectionFactory( factory );

        return redisTemplate;
    }
}

(6)编写服务层

@Service
public class PhoneServiceImpl implements PhoneService {
    @Autowired
    private RedisDao redisDao;
    @Value("${phone.manager.keyPref}")
    private String keyPrefix;

    /**
     * 新增电话
     * 1. 拼接key
     * 2. 保存到Redis数据库
     *
     * 分布式事务参与方法,需要使用注解@TccTransaction注解修饰
     * 建议:为提示开发者和后期的维护人员,当前方法有事务管理,
     *  在方法上增加事务管理注解@Transactional。
     *
     * 使用TCC事务管理模式,做事务控制。
     * 要求定义confirm和cancel方法。
     *
     * confirm方法定义要求。实现事务提交逻辑。
     *  方法名: 是 confirm + try方法名称首字母转大写
     *  访问修饰符、参数表、返回值、抛出异常,和try方法一致
     *
     * cancel方法定义要求。实现事务回滚逻辑
     *  方法名: 是 cancel + try方法名称首字母转大写
     *  访问修饰符、参数表、返回值、抛出异常,和try方法一致
     * @param phone
     * @return
     */
    @Override
    @TccTransaction
    @Transactional
    public String addPhone(Phone phone) {
        String key = keyPrefix + phone.getStudentId();
        // 保存到redis
        redisDao.set(key, phone);
        return "新增电话成功";
    }

    public String confirmAddPhone(Phone phone){
        System.out.println("事务提交,新增电话结束");
        return "新增电话成功";
    }

    public String cancelAddPhone(Phone phone){
        // 回滚,就是删除新增的数据
        String key = keyPrefix + phone.getStudentId();
        redisDao.del(key);
        System.out.println("事务回滚,新增的电话已删除");
        return "新增电话失败";
    }

    /**
     * 根据学生主键,查询电话
     * 1. 拼接key
     * 2. 访问redis数据库,查询数据
     */
    @Override
    public Phone getPhoneByStudent(Integer studentId) {
        String key = keyPrefix + studentId;
        Phone phone = redisDao.get(key);
        return phone;
    }
}

(7)编辑控制层

/**
 * 电话管理控制器
 */
@RestController
public class PhoneController {
    @Autowired
    private PhoneService phoneService;

    /**
     * 新增电话号
     * @param phone
     * @return
     */
    @RequestMapping("/addPhone")
    public String addPhone(Phone phone){
        return phoneService.addPhone(phone);
    }

    /**
     * 根据学生主键,查询电话
     * @param studentId
     * @return
     */
    @RequestMapping("/getPhoneByStudent")
    public Phone getPhoneByStudent(Integer studentId){
        return phoneService.getPhoneByStudent(studentId);
    }
}

(8)编辑启动类

@SpringBootApplication
@EnableDistributedTransaction
public class TxPhoneManagerApp {
    public static void main(String[] args) {
        SpringApplication.run(TxPhoneManagerApp.class, args);
    }
}
 类似资料: