项目中用到了MongoDB,准备用来存储业务数据,前提是要实现事务,保证数据一致性,MongoDB从4.0开始支持事务,提供了面向复制集的多文档事务特性。能满足在多个操作,文档,集合,数据库之间的事务性,事务的特性。多文档事务在4.0版本仅支持复制集,对分片集群的事务性支持计划在4.2版本中实现。由于我也算是一个java小白,没怎么弄清java事务机制,于是先建了个测试项目进行测试。在本例中可以看到多数据源下事务的使用,请重点关注后面记录的爬坑记。

代码已上传到github 传送门 https://github.com/devmuyuer/trans-demo

Mongo Transaction

项目介绍

  • springboot 2.1.3

  • MongoDB 4.0.3

  • 本项目主要为了测试MongoDB事务,由于正式项目还用了其它数据源,所以加入了 Oracle, MySQL的事务,包括多数据源的配置和使用

使用说明

  • 1.导入MongoDB的依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
  • 2.配置MongoDB的连接
spring:
# mongodb 连接
data:
mongodb:
uri: mongodb://192.168.0.68:27017,192.168.0.69:27017,192.168.0.70:27017/glcloud?replicaSet=rs0
database: glcloud
  • 3.编写entity类

当id设置为 ObjectId 类型和添加 @Id 注解时时,MongoDB数据库会自动生成主键,我们在保存对象时就不用设置id的值

MongoUnit

/**
* 用户
* @author muyuer 182443947@qq.com
* @version 1.0
* @date 2019-02-25 09:10
*/
@Data
@Document(collection = "test_unit")
public class MongoUnit { private static final long serialVersionUID = 1L; /**
* Id
*/
@Id
private ObjectId id;
/**
* unitId
*/
private String unitId; /**
* unitName
*/
private String unitName; }

MongoUser

package com.example.demo.entity.mongo;

import lombok.Data;
import org.bson.types.ObjectId;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document; /**
* 用户
* @author muyuer 182443947@qq.com
* @version 1.0
* @date 2019-02-25 09:10
*/
@Data
@Document(collection = "test_user")
public class MongoUser { private static final long serialVersionUID = 1L; /**
* Id
*/
@Id
private ObjectId id;
/**
* userId
*/
private String userId; /**
* userName
*/
private String userName; /**
* unitId 关联testUser
*/
private String unitId;
}
  • 4.编写dao层的方法

只需继承MongoRepository即可。

package com.example.demo.repository.mongo;

import com.example.demo.entity.mongo.MongoUser;
import org.springframework.data.mongodb.repository.MongoRepository; /**
* @author muyuer 182443947@qq.com
* @version 1.0
* @date 2019-02-25 09:10
*/
public interface MongoUserRepository extends MongoRepository<MongoUser, String> { }
package com.example.demo.repository.mongo;

import com.example.demo.entity.mongo.MongoUnit;
import org.springframework.data.mongodb.repository.MongoRepository; /**
* @author muyuer 182443947@qq.com
* @version 1.0
* @date 2019-02-25 09:10
*/
public interface MongoUnitRepository extends MongoRepository<MongoUnit, String> { }
  • 5.Service层
package com.example.demo.service.mongo.impl;

import com.example.demo.common.SystemException;
import com.example.demo.entity.mongo.MongoUser;
import com.example.demo.repository.mongo.MongoUserRepository;
import com.example.demo.service.mongo.MongoUserService;
import com.example.demo.common.R;
import com.example.demo.common.RUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional; /**
* @author muyuer 182443947@qq.com
* @version 1.0
* @date 2019-02-25 09:10
*/
@Service
@Slf4j
public class MongoUserServiceImpl implements MongoUserService { @Autowired
MongoUserRepository mongoUserRepository; /**
* 新增
* @param mongoUser
* @return
*/
@Override
public R save(MongoUser mongoUser) {
MongoUser mongoUserSave = mongoUserRepository.save(mongoUser);
log.info("用户信息保存:testUserSave = "+ mongoUserSave);
return RUtil.success("");
} @Override
@Transactional(value = "MONGO_TRANSACTION_MANAGER", propagation = Propagation.REQUIRED)
public R bathSave(String unitId, Boolean rollBack) {
for (int i = 0; i <= 10; i++) { //注释这段则可以正常添加数据,测试回滚则throw异常信息
if (unitId.equals("003") && rollBack) {
throw new SystemException("测试回滚故意抛出的异常");
} MongoUser user = new MongoUser();
user.setUserId(unitId + "U0" + i);
user.setUserName("用户" + i);
user.setUnitId(unitId);
save(user);
}
return RUtil.success("");
}
}
package com.example.demo.service.mongo.impl;

import com.example.demo.enums.REnum;
import com.example.demo.common.SystemException;
import com.example.demo.entity.mongo.MongoUnit;
import com.example.demo.repository.mongo.MongoUnitRepository;
import com.example.demo.service.mongo.MongoUnitService;
import com.example.demo.service.mongo.MongoUserService;
import com.example.demo.common.R;
import com.example.demo.common.RUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
* @author muyuer 182443947@qq.com
* @version 1.0
* @date 2019-02-25 09:10
*/
@Service
@Slf4j
public class MongoUnitServiceImpl implements MongoUnitService { @Autowired
MongoUnitRepository mongoUnitRepository;
@Autowired
MongoUserService mongoUserService; /**
* 新增
*
* @param unit
* @return
*/
@Override
public R save(MongoUnit unit) {
MongoUnit mongoUnitSave = mongoUnitRepository.save(unit);
log.info("单位信息保存:testUnitSave = " + mongoUnitSave);
return RUtil.success("");
} @Override
@Transactional(value = "MONGO_TRANSACTION_MANAGER")
public R bathSave(Boolean rollBack) {
try {
for (int i = 0; i < 4; i++) { MongoUnit unit = new MongoUnit();
unit.setUnitId("00" + i);
unit.setUnitName("单位" + i);
mongoUserService.bathSave(unit.getUnitId(),rollBack); save(unit);
}
return RUtil.success("");
} catch (SystemException e) {
log.error("保存数据失败:msg: {}", e.getMessage());
throw new SystemException(REnum.ERROR.getCode(), "保存数据失败 Error:" + e.getMessage());
}
}
}
  • 6.Controller
package com.example.demo.controller;

import com.example.demo.enums.DbTypeEnum;
import com.example.demo.service.mongo.MongoUserService;
import com.example.demo.common.R;
import com.example.demo.service.primary.PrimaryUserService;
import com.example.demo.service.slave.SlaveUserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*; /**
* @author muyuer 182443947@qq.com
* @date 2019-02-25 10:59
*/
@RestController
@Slf4j
@RequestMapping(path="test/user")
public class TestUserController { @Autowired
MongoUserService mongoUserService;
@Autowired
PrimaryUserService primaryUserService;
@Autowired
SlaveUserService slaveUserService; /**
* 新增
* @param dbType
* @param unitId
* @param rollBack
* @return
*/
@PostMapping("/bathSave/{dbType}/{unitId}/{rollBack}")
public R bathSave(@PathVariable DbTypeEnum dbType, @PathVariable String unitId, @PathVariable Boolean rollBack){
switch (dbType) {
case MONGO:
return mongoUserService.bathSave(unitId, rollBack);
case PRIMARY:
return primaryUserService.bathSave(unitId, rollBack);
default:
return slaveUserService.bathSave(unitId, rollBack);
}
}
}
package com.example.demo.controller;

import com.example.demo.enums.DbTypeEnum;
import com.example.demo.service.mongo.MongoUnitService;
import com.example.demo.common.R;
import com.example.demo.service.primary.PrimaryUnitService;
import com.example.demo.service.slave.SlaveUnitService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*; /**
* @author muyuer 182443947@qq.com
* @date 2019-02-25 10:59
*/
@RestController
@Slf4j
@RequestMapping(path="test/unit")
public class TestUnitController { @Autowired
MongoUnitService mongoUnitService;
@Autowired
PrimaryUnitService primaryUnitService;
@Autowired
SlaveUnitService slaveUnitService; /**
* 新增
* @param dbType 数据库
* @param rollBack 是否回滚
* @return
*/
@PostMapping("/bathSave/{dbType}/{rollBack}")
public R bathSave(@PathVariable DbTypeEnum dbType, @PathVariable Boolean rollBack) {
switch (dbType) {
case MONGO:
return mongoUnitService.bathSave(rollBack);
case PRIMARY:
return primaryUnitService.bathSave(rollBack);
default:
return slaveUnitService.bathSave(rollBack);
}
}
}

测试

PostMan post 地址

MONGO库 不回滚 http://localhost:8077/test/unit/bathSave/MONGO/0

MONGO库 回滚 http://localhost:8077/test/unit/bathSave/MONGO/1

Oracle库 不回滚 http://localhost:8077/test/unit/bathSave/PRIMARY/0

Oracle库 回滚 http://localhost:8077/test/unit/bathSave/PRIMARY/1

MySQL库 不回滚 http://localhost:8077/test/unit/bathSave/SLAVE/0

MySQL库 回滚 http://localhost:8077/test/unit/bathSave/SLAVE/1

在实际应用中爬过的坑

  • 1.MongoDB的版本必须是4.0

  • 2.MongoDB事务功能必须是在多副本集的情况下才能使用,否则报错"Sessions are not supported by the MongoDB cluster to which this client is connected",4.2版本会支持分片事务。

  • 3.事务控制只能用在已存在的集合中,也就是集合需要手工添加不会由jpa创建会报错"Cannot create namespace glcloud.test_user in multi-document transaction."

  • 4.多数据源时需要指定事务 @Transactional(value = "transactionManager") 如果只有1个数据源不需要指定value

  • 5.事务注解到类上时,该类的所有 public 方法将都具有该类型的事务属性,但一般都是注解到方法上便于实现更精确的事务控制

  • 6.事务传递性,事务子方法上不必添加事务注解,如果子方法也提供api调用可用注解propagation = Propagation.REQUIRED也就是继承调用它的事务,如果没有事务则新起一个事务

  • 7.启动类上的@EnableTransactionManagement注解,并不是像网上所说必需添加的注解,因为spring boot 默认开始了这个注解的。

  • 8.有人说:注解必须是@Transactional(rollbackFor = { Exception.class }) 测试并不需要rollbackFor = { Exception.class },因为本例中自定义异常类继承自RuntimeException spring boot事物默认在遇到RuntimeException不论rollbackFor的异常是啥,都会进行事务的回滚,加上rollbackFor=Exception.class,可以让事物在遇到非运行时异常时也回滚

    具体rollbackFor用法可参考:

    Spring中的@Transactional(rollbackFor = Exception.class)属性详解

    一次Spring Transactional嵌套事务使用不同的rollbackFor的分析

参考文档

SpringBoot整合MongoDB,在多数据源下实现事务回滚。的更多相关文章

  1. SpringBoot整合MyBatisPlus配置动态数据源

    目录 SpringBoot整合MyBatisPlus配置动态数据源 SpringBoot整合MyBatisPlus配置动态数据源 推文:2018开源中国最受欢迎的中国软件MyBatis-Plus My ...

  2. SpringBoot 整合mongoDB并自定义连接池

    SpringBoot 整合mongoDB并自定义连接池 得力于SpringBoot的特性,整合mongoDB是很容易的,我们整合mongoDB的目的就是想用它给我们提供的mongoTemplate,它 ...

  3. java操作mongodb & springboot整合mongodb

    简单的研究原生API操作MongoDB以及封装的工具类操作,最后也会研究整合spring之后作为dao层的完整的操作. 1.原生的API操作 pom.xml <!-- https://mvnre ...

  4. SpringBoot整合mongoDB

    MongoDB 是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰富,最像关系数据库的. 这一片文章介绍一个springboot整合mongodb,如果你了解整合mysql之类的 ...

  5. Springboot整合MongoDB的Docker开发,其它应用也类似

    1 前言 Docker是容器开发的事实标准,而Springboot是Java微服务常用框架,二者必然是会走到一起的.本文将讲解如何开发Springboot项目,把它做成Docker镜像,并运行起来. ...

  6. 8、SpringBoot整合之SpringBoot整合MongoDB

    SpringBoot整合MongoDB 一.创建项目,选择依赖 仅选择Spring Web.Spring Data MongoDB即可 二.引入相关依赖(非必要) 这里只是为了实体类的创建方便而引入l ...

  7. Springboot 整合 MongoDB

    Springboot 整合 MongoDB 这节我们将整合 Spring Boot 与 Mongo DB 实现增删改查的功能,并且实现序列递增. Mongo DB 的基本介绍和增删改查的用法可以参考我 ...

  8. SpringBoot 整合 MongoDB 实战介绍

    一.介绍 在前面的文章中,我们详细的介绍了 MongoDB 的配置和使用,如果你对 MongoDB 还不是很了解,也没关系,在 MongoDB 中有三个比较重要的名词:数据库.集合.文档! 数据库(D ...

  9. springboot 事务回滚

    在springboot中,使用事务回滚时,添加@Transactional注解,然后在try-catch块中,发生异常时,在catch中 添加 TransactionAspectSupport.cur ...

随机推荐

  1. MySql 5.7.26(MySQL8)安装教程

    近期更换服务器,在此再记录一遍mysql 安装教程 1.下载 https://cdn.mysql.com//Downloads/MySQLInstaller/mysql-installer-commu ...

  2. RzPageControl(pagecontrol)

    实现多标签的动态添加,切换,关闭 使用RzPageControl来实现多标签页使用菜单来打开标签页,通过标签页的caption来判断将标签页是否已经被打开过了.1.创建标签页,并判断是否是已经打开过的 ...

  3. 2019 牛客暑期多校 第一场 H XOR (线性基)

    题目:https://ac.nowcoder.com/acm/contest/881/H 题意:求一个集合内所有子集异或和为0的长度之和 思路:首先集合内异或和,这是线性基的一个明显标志,然后我们不管 ...

  4. 2018icpc南京/gym101981 K Kangaroo Puzzle 随机化

    题意: 有一个棋盘上,1是空格,0是障碍物,一开始每个空格里都有一只袋鼠,你可以命令所有袋鼠一起向上下左右一个方向走一格,一旦碰到边界或障碍物,袋鼠就不动,如果它后面有袋鼠这两个袋鼠就会挤进一个格子, ...

  5. svn提交代码失败提示清理(清理失败并且报错信息乱码解决办法)

    原因是;svn的数据库队列原因 1,下载sqlite3.exe,  sqlite官网http://www.sqlite.org/download.html) 2.在Windows的D盘中新建tools ...

  6. Windows-添加环境变量(path)

    使用命令提示符(cmd.批处理.Batch..bat)添加环境变量 永久环境变量 命令提示符下修改 注意:要使用管理员身份运行 cmd set PATH=%PATH%;要添加的路径 reg add & ...

  7. ajax请求在参数中添加时间戳

    ajax请求在参数中添加时间戳 参考网址

  8. Linux命令 shutdown

    1. 简介 shutdown是用来让计算机处于暂停,关机,重启的指令 2.语法 shutdown [krhHPc]  [时间] [警告信息] 时间的格式: hh:mm 表示多长时间以后执行       ...

  9. Ajax,ajax封装

    /** * Created by liyinghao on 2016/8/23. */ /*仿jQuery中的ajax方法,简单版实现;封装ajax的工具函数*/ /* * 1 请求方式 type g ...

  10. python学习笔记:itsdangerous模块

    使用itsdangerous生成临时身份令牌 安装 pip install itsdangerous 使用 import itsdangerous salt='sdaf'#加盐 t=itsdanger ...