1.正如官网所说的:LCN并不生产事务,LCN只是本地事务的协调工!
Lcn本身不会产生事务,也不会涉及到某些业务代码!他对事务的操作本身就依赖一个事务协调者服务
如上图所说的一样 他分为4个步骤
创建表
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,
`remark` varchar(4096) NULL DEFAULT NULL,
`ex_state` tinyint(4) NULL DEFAULT NULL COMMENT '0 未解决 1已解决',
`create_time` datetime(0) NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
github:https://github.com/codingapi/tx-lcn.git
将标记的项目加入IDEA,并修改配置文件
spring.application.name=tx-manager
server.port=7970
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/tx-manager?characterEncoding=UTF-8
spring.datasource.username=root
spring.datasource.password=root
mybatis.configuration.map-underscore-to-camel-case=true
mybatis.configuration.use-generated-keys=true
#tx-lcn.logger.enabled=true
# TxManager Host Ip
#tx-lcn.manager.host=127.0.0.1
# TxClient连接请求端口
#tx-lcn.manager.port=8070
# 心跳检测时间(ms)
#tx-lcn.manager.heart-time=15000
# 分布式事务执行总时间
#tx-lcn.manager.dtx-time=30000
#参数延迟删除时间单位ms
#tx-lcn.message.netty.attr-delay-time=10000
#tx-lcn.manager.concurrent-level=128
# 开启日志
#tx-lcn.logger.enabled=true
#logging.level.com.codingapi=debug
#redis 主机
#spring.redis.host=127.0.0.1
#redis 端口
#spring.redis.port=6379
#redis 密码
#spring.redis.password=
以上的配置详情介绍
spring.application.name=TransactionManager
server.port=7970
# JDBC 数据库配置
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/tx-manager?characterEncoding=UTF-8
spring.datasource.username=root
spring.datasource.password=123456
# 数据库方言
spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect
# 第一次运行可以设置为: create, 为TM创建持久化数据库表
spring.jpa.hibernate.ddl-auto=validate
# TM监听IP. 默认为 127.0.0.1
tx-lcn.manager.host=127.0.0.1
# TM监听Socket端口. 默认为 ${server.port} - 100
tx-lcn.manager.port=8070
# 心跳检测时间(ms). 默认为 300000
tx-lcn.manager.heart-time=300000
# 分布式事务执行总时间(ms). 默认为36000
tx-lcn.manager.dtx-time=8000
# 参数延迟删除时间单位ms 默认为dtx-time值
tx-lcn.message.netty.attr-delay-time=${tx-lcn.manager.dtx-time}
# 事务处理并发等级. 默认为机器逻辑核心数5倍
tx-lcn.manager.concurrent-level=160
# TM后台登陆密码,默认值为codingapi
tx-lcn.manager.admin-key=codingapi
# 分布式事务锁超时时间 默认为-1,当-1时会用tx-lcn.manager.dtx-time的时间
tx-lcn.manager.dtx-lock-time=${tx-lcn.manager.dtx-time}
# 雪花算法的sequence位长度,默认为12位.
tx-lcn.manager.seq-len=12
# 异常回调开关。开启时请制定ex-url
tx-lcn.manager.ex-url-enabled=false
# 事务异常通知(任何http协议地址。未指定协议时,为TM提供内置功能接口)。默认是邮件通知
tx-lcn.manager.ex-url=/provider/email-to/***@**.com
# 开启日志,默认为false
tx-lcn.logger.enabled=true
tx-lcn.logger.enabled=false
tx-lcn.logger.driver-class-name=${spring.datasource.driver-class-name}
tx-lcn.logger.jdbc-url=${spring.datasource.url}
tx-lcn.logger.username=${spring.datasource.username}
tx-lcn.logger.password=${spring.datasource.password}
# redis 的设置信息. 线上请用Redis Cluster
spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.password=
一、上个文章里面已经启动了事务协调者 现在我们来开发一个demo来测试一下协调者是否生效了吧!
分别在两个不同的库中创建两张表
CREATE DATABASE /*!32312 IF NOT EXISTS*/`server1` /*!40100 DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_bin */;
USE `server1`;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for server1
-- ----------------------------
DROP TABLE IF EXISTS `server1`;
CREATE TABLE `server1` (
`id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
SET FOREIGN_KEY_CHECKS = 1;
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
CREATE DATABASE /*!32312 IF NOT EXISTS*/`server2` /*!40100 DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_bin */;
USE `server2`;
-- ----------------------------
-- Table structure for server2
-- ----------------------------
DROP TABLE IF EXISTS `server2`;
CREATE TABLE `server2` (
`id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
SET FOREIGN_KEY_CHECKS = 1;
pom文件
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<groupId>com.lcngroupId>
<artifactId>lcn-testartifactId>
<packaging>pompackaging>
<version>1.0-SNAPSHOTversion>
<modules>
<module>server1module>
<module>public-resourcesmodule>
<module>server2module>
<module>eurekamodule>
modules>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.1.1.RELEASEversion>
parent>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-dependenciesartifactId>
<version>Finchley.RELEASEversion>
<type>pomtype>
<scope>importscope>
dependency>
dependencies>
dependencyManagement>
<dependencies>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
dependencies>
project>
pom文件
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>lcn-testartifactId>
<groupId>com.lcngroupId>
<version>1.0-SNAPSHOTversion>
parent>
<modelVersion>4.0.0modelVersion>
<groupId>com.lcngroupId>
<artifactId>eurekaartifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-serverartifactId>
dependency>
dependencies>
project>
配置文件 application.yml
server:
port: 8761
spring:
application:
name: eureka-server
eureka:
server:
peer-eureka-nodes-update-interval-ms: 60000
enable-self-preservation: false
eviction-interval-timer-in-ms: 5000
client:
service-url:
defaultZone: http://localhost:8761/eureka/
register-with-eureka: false
fetch-registry: true
启动类
package com.eureka.server;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
/**
* @author huangfu
*/
@SpringBootApplication
@EnableEurekaServer
public class EurekaServer {
public static void main(String[] args) {
new SpringApplicationBuilder(EurekaServer.class).run(args);
}
}
创建serve1实体类
package com.pojo;
import lombok.Data;
/**
* @author huangfu
*/
@Data
public class ServerOne {
private String id;
private String name;
public ServerOne() {
}
public ServerOne(String id, String name) {
this.id = id;
this.name = name;
}
}
创建server2
package com.pojo;
import lombok.Data;
/**
* @author huangfu
*/
@Data
public class ServerTwo {
private String id;
private String name;
public ServerTwo() {
}
public ServerTwo(String id, String name) {
this.id = id;
this.name = name;
}
}
项目结构
配置文件 appplication.yml
server:
port: 8080
spring:
application:
name: server1
main:
allow-bean-definition-overriding: true
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/lcn-server1?useUnicode=true&characterEncoding=utf8
username: root
password: root
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
instance:
prefer-ip-address: true
feign:
hystrix:
enabled : true
# 在feign中开启hystrix功能,默认情况下feign不开启hystrix功能
mybatis:
configuration:
map-underscore-to-camel-case: true
tx.properties
注意:必须添加,用来指定事务协调者的访问位置
# 默认之配置为TM的本机默认端口
tx-lcn.client.manager-address=127.0.0.1:8070
启动类 注意:启动类必须添加
@EnableDistributedTransaction
注解 启动分布式事务
package com.order;
import com.codingapi.txlcn.tc.config.EnableDistributedTransaction;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
/**
* @author huangfu
*/
@EnableEurekaClient
@SpringBootApplication
@EnableFeignClients
@MapperScan("com.order.mapper")
@EnableDistributedTransaction
public class ServerOneApplication {
public static void main(String[] args) {
SpringApplication.run(ServerOneApplicatin.class);
}
}
Mapper类 开发
package com.order.mapper;
import com.pojo.ServerOne;
import org.apache.ibatis.annotations.Insert;
/**
* @author huangfu
*/
public interface ServerOneMapper {
/**
* 插入数据
* @param serverOne
*/
@Insert("insert into server1 values(#{id},#{name})")
void insertData(ServerOne serverOne);
}
service类开发
package com.order.server;
import com.pojo.ServerOne;
/**
* @author huangfu
*/
public interface ServerOneService {
/**
* 插入数据
* @param serverOne
*/
void insertData(ServerOne serverOne,String id);
}
注意:涉及到分布式事务的一定要添加
@LcnTransaction
注解
package com.order.server.impl;
import com.codingapi.txlcn.tc.annotation.LcnTransaction;
import com.order.client.ServerTwoClient;
import com.order.mapper.ServerOneMapper;
import com.order.server.ServerOneService;
import com.pojo.ServerOne;
import com.pojo.ServerTwo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
* @author huangfu
*/
@Service
@SuppressWarnings("all")
public class ServerOneServiceImpl implements ServerOneService {
@Autowired
private ServerOneMapper serverOneMapper;
@Autowired
private ServerTwoClient serverTwoClient;
@Override
@LcnTransaction
@Transactional(rollbackFor = Exception.class)
public void insertData(ServerOne serverOne,String id) {
serverOneMapper.insertData(serverOne);
ServerTwo serverTwo = new ServerTwo(serverOne.getId(),serverOne.getName());
serverTwoClient.addData2(serverTwo);
if("1".equals(id)){
throw new RuntimeException("自定义异常");
}
System.out.println("---------------服务一执行完成---------------");
}
}
feign远程调用Server2服务
package com.order.client;
import com.codingapi.txlcn.tc.annotation.LcnTransaction;
import com.pojo.ServerTwo;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
/**
* @author huangfu
*/
@FeignClient(value = "server2")
public interface ServerTwoClient {
@RequestMapping(value = "/addData2",method = RequestMethod.POST)
public void addData2(@RequestBody ServerTwo serverTwo);
}
package com.order.client.impl;
import com.order.client.ServerTwoClient;
import com.pojo.ServerTwo;
import org.springframework.stereotype.Component;
/**
* @author huangfu
*/
@Component
public class ServerTwoClientImpl implements ServerTwoClient {
@Override
public void addData2(ServerTwo serverTwo) {
System.out.println("------断路器-------------");
}
}
开发Controller
package com.order.controller;
import com.order.server.ServerOneService;
import com.pojo.ServerOne;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.UUID;
/**
* @author huangfu
*/
@RestController
public class ServerOneController {
private final ServerOneService serverOneService;
@Autowired
public ServerOneController(ServerOneService serverOneService) {
this.serverOneService = serverOneService;
}
@RequestMapping("addDataOne")
public String addDataOne(String id){
ServerOne serverOne = new ServerOne();
serverOne.setId(UUID.randomUUID().toString());
serverOne.setName("张三");
serverOneService.insertData(serverOne,id);
return "success";
}
}
项目结构
配置文件 appplication.yml
server:
port: 8080
spring:
application:
name: server2
main:
allow-bean-definition-overriding: true
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/lcn-server2?useUnicode=true&characterEncoding=utf8
username: root
password: root
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
instance:
prefer-ip-address: true
feign:
hystrix:
enabled : true
# 在feign中开启hystrix功能,默认情况下feign不开启hystrix功能
mybatis:
configuration:
map-underscore-to-camel-case: true
tx.properties
注意:必须添加,用来指定事务协调者的访问位置
# 默认之配置为TM的本机默认端口
tx-lcn.client.manager-address=127.0.0.1:8070
启动类 注意:启动类必须添加
@EnableDistributedTransaction
注解 启动分布式事务
package com.server2;
import com.codingapi.txlcn.tc.config.EnableDistributedTransaction;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
/**
* @author huangfu
*/
@EnableEurekaClient
@SpringBootApplication
@EnableFeignClients
@MapperScan("com.server2.mapper")
@EnableDistributedTransaction
public class ServerTwoApplication {
public static void main(String[] args) {
SpringApplication.run(ServerTwoApplication.class);
}
}
mapper
package com.server2.mapper;
import com.pojo.ServerOne;
import com.pojo.ServerTwo;
import org.apache.ibatis.annotations.Insert;
/**
* @author huangfu
*/
public interface ServerTwoMapper {
/**
* 插入数据
* @param serverTwo
*/
@Insert("insert into server2 values(#{id},#{name})")
void insertData(ServerTwo serverTwo);
}
service类开发
package com.server2.server;
import com.pojo.ServerOne;
import com.pojo.ServerTwo;
/**
* @author huangfu
*/
public interface ServerTwoService {
/**
* 插入数据
* @param serverTwo
*/
void insertData(ServerTwo serverTwo);
}
注意:涉及到分布式事务的一定要添加
@LcnTransaction
注解
package com.server2.server.impl;
import com.codingapi.txlcn.tc.annotation.LcnTransaction;
import com.pojo.ServerOne;
import com.pojo.ServerTwo;
import com.server2.mapper.ServerTwoMapper;
import com.server2.server.ServerTwoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
* @author huangfu
*/
@Service
@SuppressWarnings("all")
public class ServerTwoServiceImpl implements ServerTwoService {
@Autowired
private ServerTwoMapper serverTwoMapper;
@Override
@LcnTransaction
@Transactional(rollbackFor = Exception.class)
public void insertData(ServerTwo serverTwo) {
serverTwoMapper.insertData(serverTwo);
System.out.println("---------------服务二执行完成---------------");
}
}
Controller开发
package com.server2.controller;
import com.pojo.ServerTwo;
import com.server2.server.ServerTwoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author huangfu
*/
@RestController
public class ServerTwoController {
@Autowired
private ServerTwoService serverTwoService;
@PostMapping("addData2")
public void addData(@RequestBody ServerTwo serverTwo){
serverTwoService.insertData(serverTwo);
}
}
server2项目结构
http://localhost:8080/addDataOne?id=2 当id=1时出现异常,测试各数据库是否回滚