项目中用到了MongoDB,准备用来存储业务数据,前提是要实现事务,保证数据一致性,MongoDB从4.0开始支持事务,提供了面向复制集的多文档事务特性。能知足在多个操做,文档,集合,数据库之间的事务性,事务的特性。多文档事务在4.0版本仅支持复制集,对分片集群的事务性支持计划在4.2版本中实现。因为我也算是一个java小白,没怎么弄清java事务机制,因而先建了个测试项目进行测试。在本例中能够看到多数据源下事务的使用,请重点关注后面记录的爬坑记。html
代码已上传到github 传送门 https://github.com/devmuyuer/trans-demojava
springboot 2.1.3git
MongoDB 4.0.3github
本项目主要为了测试MongoDB事务,因为正式项目还用了其它数据源,因此加入了 Oracle, MySQL的事务,包括多数据源的配置和使用web
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-mongodb</artifactId> </dependency>
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
当id设置为 ObjectId 类型和添加 @Id 注解时时,MongoDB数据库会自动生成主键,咱们在保存对象时就不用设置id的值spring
MongoUnitsql
/** * 用户 * @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; }
MongoUsermongodb
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; }
只需继承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> { }
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()); } } }
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 地址api
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
8.有人说:注解必须是@Transactional(rollbackFor = { Exception.class }) 测试并不须要rollbackFor = { Exception.class },由于本例中自定义异常类继承自RuntimeException spring boot事物默认在遇到RuntimeException不论rollbackFor的异常是啥,都会进行事务的回滚,加上rollbackFor=Exception.class,可让事物在遇到非运行时异常时也回滚
具体rollbackFor用法可参考: