一、环境搭建(搭建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@EnableTransactionManagerServerpublic 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@AllArgsConstructorpublic class Book implements Serializable {private Long id;private String name;@DateTimeFormat(pattern = "yyyy-MM-dd")private Date publishTime;private Integer studentId; // 书籍所属学生主键}
(4)数据访问接口
/*** 书籍数据访问对象*/@Mapperpublic 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)编写服务层
@Servicepublic class BookServiceImpl implements BookService {@Autowiredprivate BookMapper bookMapper;/*** 1. 生成主键* 2. 新增数据* @param book* @return*/@Override@LcnTransaction@Transactionalpublic String addBook(Book book) {book.setId(System.currentTimeMillis());int rows = bookMapper.insert(book);if(rows != 1){throw new RuntimeException("新增书籍失败");}return "新增书籍成功";}/*** 根据学生查询书籍* @param studentId* @return*/@Overridepublic List<Book> getBooksByStudent(Integer studentId) {return bookMapper.selectByStudent(studentId);}}
(6)编写控制层
/*** 书籍控制器*/@RestControllerpublic class BookController {@Autowiredprivate 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@EnableDistributedTransactionpublic 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@AllArgsConstructorpublic class Phone implements Serializable {private String phoneNo; // 电话号码private Integer studentId; // 电话所属学生主键}
(4)编辑数据访问实体类
@Repositorypublic class RedisDaoImpl implements RedisDao {@Autowiredprivate RedisTemplate<String, Object> redisTemplate;@Overridepublic void del(String key) {redisTemplate.delete(key);}@Overridepublic void set(String key, Object value) {redisTemplate.opsForValue().set(key, value);}@Overridepublic <T> T get(String key) {T value = (T) redisTemplate.opsForValue().get(key);return value;}}
(5)编写配置类
@Configurationpublic 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)编写服务层
@Servicepublic class PhoneServiceImpl implements PhoneService {@Autowiredprivate 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@Transactionalpublic String addPhone(Phone phone) {String key = keyPrefix + phone.getStudentId();// 保存到redisredisDao.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数据库,查询数据*/@Overridepublic Phone getPhoneByStudent(Integer studentId) {String key = keyPrefix + studentId;Phone phone = redisDao.get(key);return phone;}}
(7)编辑控制层
/*** 电话管理控制器*/@RestControllerpublic class PhoneController {@Autowiredprivate 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@EnableDistributedTransactionpublic class TxPhoneManagerApp {public static void main(String[] args) {SpringApplication.run(TxPhoneManagerApp.class, args);}}