項目中用到了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用法可參考: