springboot:⽤dynamic-datasource-spring-boot-st。。。⼀,dynamic-datasource-spring-boot-starter的优势?
1,dynamic-datasource-spring-boot-starter 是⼀个基于springboot的快速集成多数据源的启动器
它由苞⽶⾖团队出品,集成多数据源时⾮常⽅便
2,官⽅站及⽂档:
官⽅站
mybatis.plus/
官⽅代码站:
gitee/baomidou/dynamic-datasource-spring-boot-starter
官⽅⽂档站:
mybatis.plus/guide/dynamic-datasource.html
3,seata的⽤途:
Seata:Simpe Extensible Autonomous Transcaction Architecture,
是阿⾥中间件,开源的分布式事务解决⽅案
官⽅站:
seata.io/zh-cn/
说明:刘宏缔的架构森林是⼀个专注架构的博客,地址:
对应的源码可以访问这⾥获取:
说明:作者:刘宏缔邮箱: 371125307@qq
⼆,seata-server的安装:
参见:
wwwblogs/architectforest/p/13507695.html
三,演⽰项⽬的相关信息
1,项⽬地址:
github/liuhongdi/dynamicseata
2,项⽬功能说明:
⽤dynamic-datasource-spring-boot-starter整合两个数据源+mybatis+druid+seata实现分布式事务
springboot aop3,项⽬结构:如图:
4,⽤到的数据库:
四,配置⽂件说明
l
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--seata begin-->
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>1.1.0</version>
</dependency>
<!--dynamic datasource begin-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<version>3.2.0</version>
</dependency>
<!--dynamic datasource end-->
<!--druid begin-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.23</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
<dependency>
<groupId>com.lmax</groupId>
<artifactId>disruptor</artifactId>
<version>3.4.2</version>
</dependency>
<!--druid end-->
<!--mybatis begin-->
<dependency>
<groupId&batis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.3</version>
</dependency>
<!--mybatis end-->
<!--mysql begin-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!--mysql end-->
2,application.properties
#error
#error
springframework.web=trace
#name
spring.application.name = my_test_tx
# orderdb设置为主数据源
spring.datasource.dynamic.primary = orderdb
spring.datasource.dynamic.seata = true
# orderdb数据源配置
spring.datasource.derdb.url = jdbc:mysql://127.0.0.1:3306/orderdb?useSSL=false&useUnicode=true&characterEncoding=UTF-8 spring.datasource.derdb.driver-class-name = sql.cj.jdbc.Driver
spring.datasource.derdb.username = root
spring.datasource.derdb.password = lhddemo
spring.datasource.pe= com.alibaba.druid.pool.DruidDataSource
spring.datasource.derdb.druid.initial-size=5
spring.datasource.derdb.druid.max-active=20
spring.datasource.derdb.druid.min-idle=5
spring.datasource.derdb.druid.max-wait=60000
spring.datasource.derdb.druid.min-evictable-idle-time-millis=300000
spring.datasource.derdb.druid.max-evictable-idle-time-millis=300000
spring.datasource.derdb.druid.time-between-eviction-runs-millis=60000
spring.datasource.derdb.druid.validation-query=select 1
spring.datasource.derdb.druid.validation-query-timeout=-1
spring.datasource.st-on-borrow=false
spring.datasource.st-on-return=false
spring.datasource.st-while-idle=true
spring.datasource.derdb.druid.pool-prepared-statements=true
spring.datasource.derdb.druid.filters=stat,wall,log4j2
spring.datasource.derdb.druid.share-prepared-statements=true
# goodsdb数据源配置
spring.datasource.dsdb.url = jdbc:mysql://127.0.0.1:3306/store?useSSL=false&useUnicode=true&characterEncoding=UTF-8 spring.datasource.dsdb.driver-class-name = sql.cj.jdbc.Driver
spring.datasource.dsdb.username = root
spring.datasource.dsdb.password = lhddemo
spring.datasource.pe= com.alibaba.druid.pool.DruidDataSource
spring.datasource.dsdb.druid.initial-size=5
spring.datasource.dsdb.druid.max-active=20
spring.datasource.dsdb.druid.min-idle=5
spring.datasource.dsdb.druid.max-wait=60000
spring.datasource.dsdb.druid.min-evictable-idle-time-millis=300000
spring.datasource.dsdb.druid.max-evictable-idle-time-millis=300000
spring.datasource.dsdb.druid.time-between-eviction-runs-millis=60000
spring.datasource.dsdb.druid.validation-query=select 1
spring.datasource.dsdb.druid.validation-query-timeout=-1
spring.datasource.st-on-borrow=false
spring.datasource.st-on-return=false
spring.datasource.st-while-idle=true
spring.datasource.dsdb.druid.pool-prepared-statements=true
spring.datasource.dsdb.druid.filters=stat,wall,log4j2
spring.datasource.dsdb.druid.share-prepared-statements=true
# 配置监控统计拦截的filters,去掉后监控界⾯sql⽆法统计,'wall'⽤于防⽕墙
#spring.datasource.druid.filters = stat,wall,log4j2
spring.datasource.druid.maxPoolPreparedStatementPerConnectionSize = 20
spring.datasource.druid.useGlobalDataSourceStat = true
spring.tionProperties = Sql=true;druid.stat.slowSqlMillis=500
#druid sql firewall monitor
spring.datasource.druid.abled=true
#druid sql monitor
spring.datasource.druid.abled=true
spring.datasource.druid.filter.stat.log-slow-sql=true
spring.datasource.druid.filter.stat.slow-sql-millis=10000
spring.datasource.druid.-sql=true
#druid uri monitor
spring.datasource.abled=true
spring.datasource.druid.web-stat-filter.url-pattern=/*
spring.datasource.lusions=*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*
#druid session monitor
spring.datasource.druid.web-stat-filter.session-stat-enable=true
spring.datasource.druid.web-stat-filter.profile-enable=true
#druid spring monitor
spring.datasource.druid.aop-patterns=com.druid.*
#monintor,druid login user config
spring.datasource.abled=true
spring.datasource.druid.stat-view-servlet.login-username=root
spring.datasource.druid.stat-view-servlet.login-password=root
# IP⽩名单 (没有配置或者为空,则允许所有访问)
spring.datasource.druid.stat-view-servlet.allow = 127.0.0.1,192.168.163.1
# IP⿊名单 (存在共同时,deny优先于allow)
spring.datasource.druid.stat-view-servlet.deny = 192.168.10.1
#mybatis
mybatis.mapper-locations=classpath:/mapper/*l
#log
>>>>>>[seata配置]>>>>>>>>>>#
seata.application-id=my_test_tx
<-service-group=my_test_tx_group
seata._test_tx_group=default
uplist.default=127.0.0.1:8091
说明:因为是⽤dynamic-datasource来整合seata,需要配置:
spring.datasource.dynamic.seata = true
<-service-group ⽤来指定所属事务的分组,⼀台seata server 可管理多个事务组,
seata._test_tx_group=default:把服务组命名为default
uplist.default=127.0.0.1:8091:指定服务组的server地址和端⼝
3,数据库的相关业务表:
goods表
CREATE TABLE `goods` (
`goodsId` bigint(11) unsigned NOT NULL AUTO_INCREMENT COMMENT 'id',
`goodsName` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '' COMMENT 'name', `subject` varchar(200) NOT NULL DEFAULT '' COMMENT '标题',
`price` decimal(15,2) NOT NULL DEFAULT '0.00' COMMENT '价格',
`stock` int(11) NOT NULL DEFAULT '0' COMMENT 'stock',
PRIMARY KEY (`goodsId`)
) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='商品表' goods表中的数据:
INSERT INTO `goods` (`goodsId`, `goodsName`, `subject`, `price`, `stock`) VALUES
(3, '100分电动⽛刷', '好⽤到让你爱上刷⽛', '59.00', 100);
order表:
CREATE TABLE `orderinfo` (
`orderId` bigint(11) unsigned NOT NULL AUTO_INCREMENT COMMENT 'id',
`orderSn` varchar(100) NOT NULL DEFAULT '' COMMENT '编号',
`orderTime` timestamp NOT NULL DEFAULT '1971-01-01 00:00:01' COMMENT '下单时间',
`orderStatus` tinyint(4) NOT NULL DEFAULT '0' COMMENT '状态:0,未⽀付,1,已⽀付,2,已发货,3,已退货,4,已过期',
`userId` int(12) NOT NULL DEFAULT '0' COMMENT '⽤户id',
`price` decimal(10,0) NOT NULL DEFAULT '0' COMMENT '价格',
`addressId` int(12) NOT NULL DEFAULT '0' COMMENT '地址',
PRIMARY KEY (`orderId`),
UNIQUE KEY `orderSn` (`orderSn`)
) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='订单表'每个库中seata要使⽤的事务恢复⽇志表:
CREATE TABLE `undo_log` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'increment id',
`branch_id` bigint(20) NOT NULL COMMENT 'branch transaction id',
`xid` varchar(100) NOT NULL COMMENT 'global transaction id',
`context` varchar(128) NOT NULL COMMENT 'undo_log context,such as serialization',
`rollback_info` longblob NOT NULL COMMENT 'rollback info',
`log_status` int(11) NOT NULL COMMENT '0:normal status,1:defense status',
`log_created` datetime NOT NULL COMMENT 'create datetime',
`log_modified` datetime NOT NULL COMMENT 'modify datetime',
PRIMARY KEY (`id`),
UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8 COMMENT='AT transaction mode undo table'
五,java代码说明
1,SeataFilter.java
@Component
public class SeataFilter implements Filter {
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { HttpServletRequest req = (HttpServletRequest) servletRequest;
String xid = Header(RootContext.LowerCase());
System.out.println("xid:"+xid);
boolean isBind = false;
if (StringUtils.isNotBlank(xid)) {
//如果xid不为空,则RootContext需要绑定xid,供给seata识别这是同⼀个分布式事务
RootContext.bind(xid);
isBind = true;
}
try {
filterChain.doFilter(servletRequest, servletResponse);
} finally {
if (isBind) {
RootContext.unbind();
}
}
}
@Override
public void destroy() {
}
}
通过rest⽅式访问url时,分布式事务需要传递事务的xid
2,GoodsController.java
@RestController
@RequestMapping("/goods")
public class GoodsController {
private static final String SUCCESS = "SUCCESS";
private static final String FAIL = "FAIL";
@Resource
private GoodsMapper goodsMapper;
//更新商品库存参数:商品id
@RequestMapping("/goodsstock/{goodsId}/{count}")
@ResponseBody
@DS("goodsdb")
public String goodsStock(@PathVariable Long goodsId,
@PathVariable int count) {
int res = goodsMapper.updateGoodsStock(goodsId,count);
System.out.println("res:"+res);
if (res>0) {
return SUCCESS;
} else {
return FAIL;
}
}
//商品详情参数:商品id
@GetMapping("/goodsinfo")
@ResponseBody
@DS("goodsdb")
public Goods goodsInfo(@RequestParam(value="goodsid",required = true,defaultValue = "0") Long goodsId) {
Goods goods = goodsMapper.selectOneGoods(goodsId);
return goods;
}
}
3,OrderController.java
@RestController
@RequestMapping("/order")
public class OrderController {
private static final String SUCCESS = "SUCCESS";
private static final String FAIL = "FAIL";
@Resource
private OrderMapper orderMapper;
//添加⼀个订单参数:商品id,购买的数量
@RequestMapping("/orderadd/{goodsId}/{count}")
@ResponseBody
public String orderAdd(@PathVariable Long goodsId,
@PathVariable int count) {
Order order = new Order();
//得到sn
String orderSn = new SimpleDateFormat("yyyyMMddHHmmssSSS").format(new Date());
order.setOrderSn(orderSn);
order.setOrderStatus(0);
order.setPrice(new BigDecimal(100.00));
order.setUserId(8);
int orderId = orderMapper.insertOneOrder(order);
if (orderId>0) {
return SUCCESS;
} else {
return FAIL;
}
}
//订单详情,参数:订单id
@GetMapping("/orderinfo")
@ResponseBody
public Order orderInfo(@RequestParam(value="orderid",required = true,defaultValue = "0") Long orderId) {
Order order = orderMapper.selectOneOrder(orderId);
return order;
}
}
4,HomeController.java
@RestController
@RequestMapping("/home")
public class HomeController {
private static final String SUCCESS = "SUCCESS";
private static final String FAIL = "FAIL";
private OrderMapper orderMapper;
@Resource
private GoodsService goodsService;
//添加⼀个订单,直接访问数据库
@GlobalTransactional(timeoutMills = 300000,rollbackFor = Exception.class)
@GetMapping("/addorderseata")
public String addOrderSeata(@RequestParam(value="isfail",required = true,defaultValue = "0") int isFail) {
String goodsId = "3";
String goodsNum = "1";
Order order = new Order();
//增加⼀条订单的记录
String orderSn = new SimpleDateFormat("yyyyMMddHHmmssSSS").format(new Date());
order.setOrderSn(orderSn);
order.setOrderStatus(0);
order.setPrice(new BigDecimal(100.00));
order.setUserId(8);
int orderId = orderMapper.insertOneOrder(order);
//修改数据库中商品的库存
int goodsUPNum = -1;
String res = dsStock(Long.parseLong(goodsId),goodsUPNum);
//是否要引发异常
if (isFail == 1) {
int divide = 0;
int resul = 100 / divide;
}
return SUCCESS;
}
//添加⼀个订单,rest访问url⽅式
@GlobalTransactional(timeoutMills = 300000,rollbackFor = Exception.class)
@GetMapping("/addorderseatarest")
public String addOrderSeataRest(@RequestParam(value="isfail",required = true,defaultValue = "0") int isFail) { String goodsId = "3";
String goodsNum = "1";
RestTemplate restTemplate = new RestTemplate();
//得到事务的xid
String xid = XID();
System.out.println("xid before send:"+xid);
if (StringUtils.isEmpty(xid)) {
System.out.println("xid is null,return");
return FAIL;
}
//增加⼀条订单的记录
HttpHeaders headers = new HttpHeaders();
headers.add(RootContext.KEY_XID, xid);
System.out.println("xid not null");
String urlAddOrder = "127.0.0.1:8080/order/orderadd/"+goodsId+"/"+goodsNum+"/";
String resultAdd = restTemplate.postForObject(urlAddOrder,new HttpEntity<String>(headers),String.class);
if (!SUCCESS.equals(resultAdd)) {
throw new RuntimeException();
}
//修改数据库中商品的库存
String goodsUPNum = "-1";
String urlUpStock = "127.0.0.1:8080/goods/goodsstock/"+goodsId+"/"+goodsUPNum+"/";
String resultUp = restTemplate.postForObject(urlUpStock,new HttpEntity<String>(headers),String.class);
if (!SUCCESS.equals(resultUp)) {
throw new RuntimeException();
}
//是否要引发异常
if (isFail == 1) {
int divide = 0;
int resul = 100 / divide;
}
return SUCCESS;
}
}
说明:@GlobalTransactional 注解⽤来⽣成分布式事务
@DS注解⽤来指定goodsdb这个库,
因为orderdb被设置为了primary,所以⽆需指定
5,DemoApplication.java
@SpringBootApplication(exclude = DruidDataSourceAutoConfigure.class)
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
说明:因为我们使⽤了druid-spring-boot-starter依赖包,
druid会⾃动检查数据库的url配置,⽽我们使⽤了多个数据源,
所以要exclude掉DruidDataSourceAutoConfigure这个class
6,GoodsService.java
@Service
public class GoodsService {
private static final String SUCCESS = "SUCCESS";
private static final String FAIL = "FAIL";
@Resource
private GoodsMapper goodsMapper;
//更新数据库
@DS("goodsdb")
public String goodsStock(Long goodsId, int count) {
int res = goodsMapper.updateGoodsStock(goodsId,count);
System.out.println("res:"+res);
if (res>0) {
return SUCCESS;
} else {
return FAIL;
}
}
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
springBootjpa多数据源的动态切换
« 上一篇
基于Springboot的AES报文解密
下一篇 »
发表评论