SpringBoot整合MongoDB,在多數據源下實現事務回滾。

項目中用到了MongoDB,準備用來存儲業務數據,前提是要實現事務,保證數據一致性,MongoDB從4.0開始支持事務,提供了面向複製集的多文檔事務特性。能知足在多個操做,文檔,集合,數據庫之間的事務性,事務的特性。多文檔事務在4.0版本僅支持複製集,對分片集羣的事務性支持計劃在4.2版本中實現。因爲我也算是一個java小白,沒怎麼弄清java事務機制,因而先建了個測試項目進行測試。在本例中能夠看到多數據源下事務的使用,請重點關注後面記錄的爬坑記。html

代碼已上傳到github 傳送門 https://github.com/devmuyuer/trans-demojava

Mongo Transaction

項目介紹

  • springboot 2.1.3git

  • MongoDB 4.0.3github

  • 本項目主要爲了測試MongoDB事務,因爲正式項目還用了其它數據源,因此加入了 Oracle, MySQL的事務,包括多數據源的配置和使用web

使用說明

  • 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的值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;
}
  • 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 地址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

在實際應用中爬過的坑

  • 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的分析

參考文檔

相關文章
相關標籤/搜索