Spring Boot -- Spring Boot之@Async異步調用、Mybatis、事務管理等


這一節將在上一節的基礎上,繼續深入學習Spring Boot相關知識,其中主要包括@Async異步調用,@Value自定義參數、Mybatis、事務管理等。

本節所使用的代碼是在上一節項目代碼中,繼續追加的,因此需要先學習上一節內容。

一、使用@Async實現異步調用

要在springboot中使用異步調用方法,只要在被調用的方法上面加上@Async就可以了;

1.1.准備工作

准備一個Spring Boot項目,在App類上加上@EnableAsync注解開啟異步:

package com.goldwind; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.scheduling.annotation.EnableAsync; /** * @Author: zy * @Description: 啟動代碼 * @Date: 2020-2-2 */ @SpringBootApplication @EnableAsync public class App { public static void main(String[] args){ //整個程序入口 啟動Spring Boot項目
        SpringApplication.run(App.class,args); } }

這個注解如果不加,@Async注解失效。

1.2、controller

在包com.goldwind.controller下,創建文件HelloController.java:

package com.goldwind.controller; import com.goldwind.service.HelloService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.time.Instant; /** * @Author: zy * @Description: 用於演示@Async異步調用 * @Date: 2020-2-4 */ @RestController @RequestMapping("/hello") public class HelloController { @Autowired private HelloService helloService; /** * 同步方法 * @return
     */ @RequestMapping("/sync") public String getSyncHello(){ long n = Instant.now().toEpochMilli(); //異步
        String s = helloService.syncSayHello(); long f = Instant.now().toEpochMilli(); return s + " 時間: " + (f-n); } /** * 異步方法 * @return
     */ @RequestMapping("/async") public String getAsyncHello(){ long n = Instant.now().toEpochMilli(); //異步
        String s = helloService.asyncSayHello(); long f = Instant.now().toEpochMilli(); return s + "時間:" + (f-n); } }

1.3、Service

新建包com.goldwind.service,並在包下創建HelloService.java文件:

package com.goldwind.service; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; /** * @Author: zy * @Description: 用於演示@Async異步調用 * @Date: 2020-2-4 */ @Service @Slf4j public class HelloService { @Autowired private SleepService sleepService; /** * 同步方法 * @return
     */
    public String syncSayHello() { try { sleepService.syncSleep(); return "hello world,這是同步方法"; } catch (InterruptedException e) { log.error(e.getMessage(),e); return "error"; } } /** * 異步方法 * @return
     */
    public String asyncSayHello() { try { log.info("主線程 " + Thread.currentThread().getName()); sleepService.asyncSleep(); return "hello world,這是異步方法"; } catch (InterruptedException e) { log.error(e.getMessage(),e); return "error"; } } }

這里為了模擬應用場景,將耗時的方法放在另一個service里面,就叫SleepService,兩個方法都是休眠3秒,aysncSleep方法上面有一個@Async注解。

package com.goldwind.service; import lombok.extern.slf4j.Slf4j; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; /** * @Author: zy * @Description: 用於演示@Async異步調用 * @Date: 2020-2-4 */ @Service @Slf4j public class SleepService { /** * 同步方法 * @throws InterruptedException */
    public void syncSleep() throws InterruptedException { log.info("線程名: " +Thread.currentThread().getName()); log.info("開始同步休眠3秒"); Thread.sleep(3000); log.info("同步休眠結束"); } /** * 異步方法 * @throws InterruptedException */ @Async public void asyncSleep() throws InterruptedException { log.info("次線程 "+Thread.currentThread().getName()); log.info("開始異步休眠3秒"); Thread.sleep(3000); log.info("異步休眠休眠結束"); } }

1.4、測試

同步:訪問 http://localhost:8080/hello/sync需要3秒的時間才能收到響應;

異步:訪問 http://localhost:8080/hello/asyn,可見主線程和次線程打印出來的線程名不一樣,也就是Spring Boot幫我們開啟了一個線程去處理。

注意事項

  • 必須要加@EnableAsync注解;
  • 不能在同一類下調用@Async注解的方法,比如A類下有a和b方法,b方法有@Async注解,不能直接這樣a調用b,要把b放到其他類中;
  • @Async也可以打在類上,這樣類下面的所有方法都是異步的(被其他類調用的時候);

二、@Value自定義參數

@Value在Spring中,功能非常強大,可以注入一個配置項,可以引用容器中的Bean(調用其方法),也可以做一些簡單的運算。下面通過@Value引用一個配置項。在配置文件application.properties中配置如下內容:

name=www.goldwind.com

修改HelloController.java文件,添加如下代碼:

  //初始化的時候加載
    @Value("${name}") private String name; @RequestMapping("/getName") public String getName(){ return name; }

此時訪問http://localhost:8080/hello/getName,將會返回配置文件中name屬性的值。

此外@configurationproperties也具有類似的作用,具體可以查看博客springboot @value和@configurationproperties注解的區別

三、多環境配置

軟件開發中經常有開發環境、測試環境、預發布環境、生產環境,而且一般這些環境配置會各不相同,手動改配置麻煩且容易出錯,如何管理不同環境的配置參數呢?Spring Boot + maven可以解決不同環境獨立配置不同參數的問題。

不同環境的配置yml(或者properties,yml比properties配置文件更加節約、簡約)文件名不一樣:

  • application-dev.yml(開發環境);
  • application-test.yml(測試環境);
  • application-uat.yml(預發布);
  • application-pro.yml(生產環境);

eg:

application-dev.yml配置示例:

info:
  build:
    name: ${project.artifactId}
    groupId: ${project.groupId}
    artifactId: ${project.artifactId}
    version: ${project.version}

server:
  port: 8081

endpoints:
  enabled: true
  sensitive: false

data:
  test:
    envName: dev
    envconfig: 127.0.0.1:8081

注意屬性名: 屬性值,在:后有一個空格;

application.properties文件追加:

spring.profiles.active=dev

如果要切換不同環境,只需要修改spring.profiles.active即可。

如果想讀取開發環境中的配置信息,可以通過如下方式:

@Setter @Getter @NoArgsConstructor @AllArgsConstructor @Component @ConfigurationProperties(prefix = "data.test") @Service public class DataConfig { private String envName; private String envconfig; }

驗證環境參數: 

@Api("home controller") @RestController public class HomeController { @Autowired private DataConfig dataConfig; @RequestMapping("/env") @ApiOperation("env") public Object testEnv() { return dataConfig; } }

四、Mybatis

4.1、創建數據表

這里測試采用的是mysql數據庫,首先創建goldwind數據庫:

--創建數據庫
DROP DATABASE IF EXISTS GLODWIND; CREATE DATABASE IF NOT EXISTS GOLDWIND; --選擇數據庫
USE  GOLDWIND;

創建student數據表、並插入數據:

--創建表
CREATE TABLE student( ID INT PRIMARY KEY AUTO_INCREMENT , NAME VARCHAR(20), AGE SMALLINT, SEX BIT, CLASS VARCHAR(50), ADDRESS VARCHAR(100)) default charset=utf8; --插入
insert into student(name,age,sex,class,address) values('鄭洋','24',1,'計算機(1)班','江蘇省無錫市濱湖區'); --查詢
select * from student;

4.2、pom文件導入依賴

<!-- mybatis -->
<dependency>
   <groupId>org.mybatis.spring.boot</groupId>
   <artifactId>mybatis-spring-boot-starter</artifactId>
   <version>2.1.1</version>
</dependency>

<!-- mysql依賴 -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.47</version>
</dependency>

4.3、配置mybatis

修改appliction.properties,追加sql配置信息:

#mybati sql配置 #MySQL的JDBC URL編寫方式:jdbc:mysql://主機名稱:連接端口/數據庫的名稱 默認使用goldwind數據庫
spring.datasource.driver-class-name=com.mysql.jdbc.Driver spring.datasource.url=jdbc:mysql://localhost:3306/goldwind?serverTimezone=UTC&characterEncoding=utf8&useUnicode=true&useSSL=false
spring.datasource.username=root spring.datasource.password=123456aa spring.datasource.max-idle=10 spring.datasource.max-wait=10000 spring.datasource.min-idle=5

4.4、代碼

在包com.goldwind.entity下新建StudentEntity.java文件:

package com.goldwind.entity; import lombok.Data; /** * @Author: zy * @Description: student實體類 * @Date: 2020-2-4 */ @Data public class StudentEntity { /** * id 主鍵、自增 */
    private Integer id; /** * 姓名 */
    private String name; /** * 年齡 */
    private Integer age; /** * 性別 */
    private Boolean sex; /** * 班級 */
    private String className; /** * 地址 */
    private String address; }

新建包com.goldwind.mapper,並在包下新建StudentMapper.java文件:

package com.goldwind.mapper; import com.goldwind.entity.StudentEntity; import org.apache.ibatis.annotations.Insert; import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Select; import org.springframework.stereotype.Service; /** * @Author: zy * @Description: student Mapper類 * @Date: 2020-2-4 */ @Service public interface StudentMapper { //查詢語句
    @Select("SELECT id,name,age,sex,`class` as className,address FROM STUDENT WHERE ID = #{id}") StudentEntity getStudentById(@Param("id") Integer id); //新增語句
    @Insert("INSERT INTO STUDENT(name,age,sex) VALUES(#{name}, #{age},#{sex})") int insertStudent(@Param("name") String name, @Param("age") Integer age,@Param("sex") Boolean sex); }

在包com.goldwind.service下新建StudentService.java文件:

package com.goldwind.service; import com.goldwind.mapper.StudentMapper; import com.goldwind.entity.StudentEntity; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; /** * @Author: zy * @Description: student業務邏輯 * @Date: 2020-2-4 */ @Service @Slf4j public class StudentService { @Autowired private StudentMapper studentMapper; /** * 新增學生 * @param name:姓名 * @param age:年齡 * @param sex:性別 * @return:返回受影響的行數 */
    public int insertStudent(String name,Integer age,Boolean sex){ int ret = studentMapper.insertStudent(name,age,sex); log.info("#####新增學生###########",ret); return ret; } /** * 根據id獲取學生信息 * @param id:學生id * @return:返回學生信息 */
    public StudentEntity getStudentById(Integer id){ log.info("#####獲取學生信息###########",id); return studentMapper.getStudentById(id); } }

在包com.goldwind.controller下新建StudentController.java文件:

package com.goldwind.controller; import com.goldwind.entity.StudentEntity; import com.goldwind.service.StudentService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; /** * @Author: zy * @Description: student控制器 測試mybatis * @Date: 2020-2-4$ */ @RestController public class StudentController { @Autowired private StudentService studentService; /** * 新增學生 這里沒有對參數進行校驗 127.0.0.1:8081/insertStudent?name=李艷&age=25&sex=1 * @param name:姓名 * @param age:年齡 * @param sex:性別 * @return:返回受影響的行數 */ @RequestMapping("/insertStudent") public int insertStudent(@RequestParam String name,@RequestParam Integer age,@RequestParam Boolean sex){ return studentService.insertStudent(name,age,sex); } /** * 根據id獲取學生信息 * @param id:學生id * @return:返回學生信息 */ @RequestMapping("/getStudent") public StudentEntity getStudentById(@RequestParam Integer id){ return studentService.getStudentById(id); } }

修改App.java文件,加入mapper掃包范圍:

package com.goldwind; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.scheduling.annotation.EnableAsync; /** * @Author: zy * @Description: 啟動代碼 * mybatis啟動方式有兩種: * 1、在mapper層添加@Mapper注解 * 2、在啟動類上加@MapperScan指定掃包范圍 * @Date: 2020-2-2 */ @SpringBootApplication @MapperScan(basePackages = {"com.goldwind.mapper"}) @EnableAsync public class App { public static void main(String[] args){ //整個程序入口 啟動Spring Boot項目
        SpringApplication.run(App.class,args); } }

啟動程序,訪問http://127.0.0.1:8081/getStudent?id=1,輸出結果如下:

4.5、mybatis整合分頁插件

(1)、pageHelper

PageHelper 是一款好用的開源免費的 Mybatis 第三方物理分頁插件,它具有以下特點:

  • 物理分頁;
  • 支持常見的 12 種數據庫,Oracle,MySql,MariaDB,SQLite,DB2,PostgreSQL,SqlServer 等;
  • 支持多種分頁方式;
  • 支持常見的 RowBounds(PageRowBounds),PageHelper.startPage 方法調用,Mapper 接口參數調用;

(2)、新增依賴

<!-- springboot 整合 pagehelper -->
<dependency>
       <groupId>com.github.pagehelper</groupId>
       <artifactId>pagehelper-spring-boot-starter</artifactId>
        <version>1.2.5</version>
</dependency>

(3)、新增配置信息

在application.properties文件中追加如下配置:

logging.level.com.example.demo.dao=DEBUG
pagehelper.helperDialect=mysql
pagehelper.reasonable=true
pagehelper.supportMethodsArguments=true
pagehelper.params=count=countSql
pagehelper.page-size-zero=true

(4)、代碼

修改StudentMapper.java,添加查詢全部學生信息的方法:

package com.goldwind.mapper; import com.goldwind.entity.StudentEntity; import org.apache.ibatis.annotations.Insert; import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Select; import org.springframework.stereotype.Service; import java.util.List; /** * @Author: zy * @Description: student Mapper類 * @Date: 2020-2-4 */ @Service public interface StudentMapper { //查詢語句
    @Select("SELECT id,name,age,sex,`class` as className,address FROM STUDENT WHERE ID = #{id}") StudentEntity getStudentById(@Param("id") Integer id); //查詢全部
    @Select("SELECT id,name,age,sex,`class` as className,address FROM STUDENT") List<StudentEntity> getStudentList(); //新增語句
    @Insert("INSERT INTO STUDENT(name,age,sex) VALUES(#{name}, #{age},#{sex})") int insertStudent(@Param("name") String name, @Param("age") Integer age, @Param("sex") Boolean sex); }

修改StudentService.java:

package com.goldwind.service; import com.github.pagehelper.PageHelper; import com.github.pagehelper.PageInfo; import com.goldwind.mapper.StudentMapper; import com.goldwind.entity.StudentEntity; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.List; /** * @Author: zy * @Description: student業務邏輯 * @Date: 2020-2-4 */ @Slf4j @Service public class StudentService { @Autowired private StudentMapper studentMapper; /** * 新增學生 * @param name:姓名 * @param age:年齡 * @param sex:性別 * @return:返回受影響的行數 */ @Transactional public int insertStudent(String name,Integer age,Boolean sex){ int ret = studentMapper.insertStudent(name,age,sex); int i = 1/age; log.info("#####新增學生###########",ret); return ret; } /** * 根據id獲取學生信息 * @param id:學生id * @return:返回學生信息 */
    public StudentEntity getStudentById(Integer id){ log.info("#####獲取學生信息###########",id); return studentMapper.getStudentById(id); } /** * 分頁查詢 獲取學生信息 * @param page: 當前頁 * @param pageSize: 每頁記錄數目 * @return: 返回學生信息 */
    public  PageInfo<StudentEntity>  getStudentList(int page,int pageSize){ //mysql查詢 limit //pageHelper 幫我們生成分頁語句 底層實現原理:利用AOP、改寫sql語句
 PageHelper.startPage(page,pageSize); List<StudentEntity> listStudent = studentMapper.getStudentList(); //返回給客戶端展示
        PageInfo<StudentEntity> pageInfoStudent = new PageInfo<StudentEntity>(listStudent); return pageInfoStudent; } }

修改StudentController.java:

package com.goldwind.controller; import com.github.pagehelper.PageInfo; import com.goldwind.entity.StudentEntity; import com.goldwind.service.StudentService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; /** * @Author: zy * @Description: student控制器 測試mybatis * @Date: 2020-2-4$ */ @RestController public class StudentController { @Autowired private StudentService studentService; /** * 新增學生 這里沒有對參數進行校驗 127.0.0.1:8081/insertStudent?name=李艷&age=25&sex=1 * @param name:姓名 * @param age:年齡 * @param sex:性別 * @return:返回受影響的行數 */ @RequestMapping("/insertStudent") public int insertStudent(@RequestParam String name,@RequestParam Integer age,@RequestParam Boolean sex){ return studentService.insertStudent(name,age,sex); } /** * 根據id獲取學生信息 127.0.0.1:8081/getStudent?id=1 * @param id:學生id * @return:返回學生信息 */ @RequestMapping("/getStudent") public StudentEntity getStudentById(@RequestParam Integer id){ return studentService.getStudentById(id); } /** * 分頁查詢 獲取學生信息 127.0.0.1:8081/student?page=1&pageSize=2 * @param page: 當前頁 * @param pageSize: 每頁記錄數目 * @return: 返回學生信息 */ @RequestMapping("/student") public PageInfo<StudentEntity> getStudentList(@RequestParam  int page,@RequestParam  int pageSize){ return studentService.getStudentList(page,pageSize); } }

運行程序,訪問http://127.0.0.1:8081/student?page=1&pageSize=2,輸出如下:

五、Spring事務管理

按是否通過編程分為聲明式事務和編程式事務:

  • 聲明式事務:通過XML配置或者注解實現;
  • 編程式事務:通過編程代碼在業務邏輯時需要時自行實現,粒度更小;

Spring Boot默認集成事物,只要在方法上加上@Transactional即可。

5.1、@Transactional使用

我們給類StudentService的insertStudent()方法加上@Transactional注解,並修改代碼如下:

    /** * 新增學生 * @param name:姓名 * @param age:年齡 * @param sex:性別 * @return:返回受影響的行數 */ @Transactional public int insertStudent(String name,Integer age,Boolean sex){ int ret = studentMapper.insertStudent(name,age,sex); int i = 1/age; log.info("#####新增學生###########",ret); return ret; }

當我們運行程序,訪問http://127.0.0.1:8081/insertStudent?name=李艷&age=25&sex=1,此時會將數據成功插入數據庫。而當我們訪問http://127.0.0.1:8081/insertStudent?name=李艷&age=0&sex=1,此時事務將會執行失敗,執行回歸操作,數據不會插入數據庫。

 

使用Spring事務注意事項:不要try,為什么不要try,因為需要將異常拋出給外層;

六、Spring Boot整合多數據源

什么是多數據源,說白了就是一個項目使用到多個數據庫。

在一個項目中多數據源如何划分:分包名(業務)或者注解方式。

采用分包的方式划分如:

  • com.goldwind.datasource1---- datasource1
  • com.goldwind.datasource2 ---- datasource2

為了演示分包方式,我們將會新建一個項目springboot-multidatasource。

6.1、創建數據庫、數據表

--創建數據庫1
DROP DATABASE IF EXISTS DATABASE1; CREATE DATABASE IF NOT EXISTS DATABASE1; --選擇數據庫
USE DATABASE1; --創建表
CREATE TABLE student1( ID INT PRIMARY KEY AUTO_INCREMENT , NAME VARCHAR(20), AGE SMALLINT, SEX BIT, CLASS VARCHAR(50), ADDRESS VARCHAR(100)) default charset=utf8; --創建數據庫2
DROP DATABASE IF EXISTS DATABASE2; CREATE DATABASE IF NOT EXISTS DATABASE2; --選擇數據庫
USE DATABASE2; --創建表
CREATE TABLE student2( ID INT PRIMARY KEY AUTO_INCREMENT , NAME VARCHAR(20), AGE SMALLINT, SEX BIT, CLASS VARCHAR(50), ADDRESS VARCHAR(100)) default charset=utf8;

6.2、配置mybatis

新建appliction.properties,追加多數據源sql配置信息:

###datasource1 spring.datasource.datasource1.jdbc-url = jdbc:mysql://localhost:3306/database1
spring.datasource.datasource1.username = root spring.datasource.datasource1.password = 123456aa spring.datasource.datasource1.driver-class-name = com.mysql.jdbc.Driver ###datasource2 spring.datasource.datasource2.jdbc-url = jdbc:mysql://localhost:3306/database2
spring.datasource.datasource2.username = root spring.datasource.datasource2.password = 123456aa spring.datasource.datasource2.driver-class-name = com.mysql.jdbc.Driver

6.3、pom文件追加依賴

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

    <groupId>com.goldwind.com</groupId>
    <artifactId>springboot-multidatasource</artifactId>
    <version>1.0-SNAPSHOT</version>
    <modelVersion>4.0.0</modelVersion>

    <!-- spring-boot-starter-parent 整合第三方常用框架依賴信息(各種引來信息)-->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.2.RELEASE</version>
    </parent>

    <!-- spring-boot-starter-web 是Spring Boot整合Spring MVC Web -->
    <!-- 相當於把第三方常用Maven依賴信息,在parent項目中封裝好了,使用Spring Boot提供依賴信息關聯整合的Jar包 -->
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <!-- 為什么不需要版本號,在parent里面已經封裝好了版本號 -->
        </dependency>

        <!-- 日志管理 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
            <exclusions>
                <!-- 排除自帶的logback依賴 -->
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-logging</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-log4j</artifactId>
            <version>1.3.8.RELEASE</version>
        </dependency>

        <!-- lombok使用 -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.16.20</version>
            <scope>provided</scope>
        </dependency>

        <!-- mybatis -->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.1</version>
        </dependency>

        <!-- mysql依賴 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.47</version>
        </dependency>

    </dependencies>

</project>

6.4、代碼

新建包com.goldwind.entity,新建StudentEntity.java文件:

package com.goldwind.entity; import lombok.Data; /** * @Author: zy * @Description: student實體類 * @Date: 2020-2-4 */ @Data public class StudentEntity { /** * id 主鍵、自增 */
    private Integer id; /** * 姓名 */
    private String name; /** * 年齡 */
    private Integer age; /** * 性別 */
    private Boolean sex; /** * 班級 */
    private String className; /** * 地址 */
    private String address; }
View Code

新建包com.goldwind.mapper.datasource1.,並新建StudentMapper01.java文件:

package com.goldwind.mapper.datasource1; import com.goldwind.entity.StudentEntity; import org.apache.ibatis.annotations.Insert; import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Select; import org.springframework.stereotype.Service; /** * @Author: zy * @Description: student Mapper類 * @Date: 2020-2-4 */ @Service public interface StudentMapper01 { //查詢語句
    @Select("SELECT id,name,age,sex,`class` as className,address FROM STUDENT1 WHERE ID = #{id}") StudentEntity getStudentById(@Param("id") Integer id); //新增語句
    @Insert("INSERT INTO STUDENT1(name,age,sex) VALUES(#{name}, #{age},#{sex})") int insertStudent(@Param("name") String name, @Param("age") Integer age,@Param("sex") Boolean sex); }

新建包com.goldwind.mapper.datasource2.,並新建StudentMapper02.java文件:

package com.goldwind.mapper.datasource2; import com.goldwind.entity.StudentEntity; import org.apache.ibatis.annotations.Insert; import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Select; import org.springframework.stereotype.Service; /** * @Author: zy * @Description: student Mapper類 * @Date: 2020-2-4 */ @Service public interface StudentMapper02 { //查詢語句
    @Select("SELECT id,name,age,sex,`class` as className,address FROM STUDENT2 WHERE ID = #{id}") StudentEntity getStudentById(@Param("id") Integer id); //新增語句
    @Insert("INSERT INTO STUDENT2(name,age,sex) VALUES(#{name}, #{age},#{sex})") int insertStudent(@Param("name") String name, @Param("age") Integer age, @Param("sex") Boolean sex); }

新建包com.goldwind.service.datasource1,並新建StudentService01.java文件:

package com.goldwind.service.datasource1; import com.goldwind.mapper.datasource1.StudentMapper01; import com.goldwind.entity.StudentEntity; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; /** * @Author: zy * @Description: student業務邏輯 * @Date: 2020-2-4 */ @Service @Slf4j public class StudentService01 { @Autowired private StudentMapper01 studentMapper01; /** * 新增學生 * @param name:姓名 * @param age:年齡 * @param sex:性別 * @return:返回受影響的行數 */
    public int insertStudent(String name,Integer age,Boolean sex){ int ret = studentMapper01.insertStudent(name,age,sex); log.info("#####新增學生###########",ret); return ret; } /** * 根據id獲取學生信息 * @param id:學生id * @return:返回學生信息 */
    public StudentEntity getStudentById(Integer id){ log.info("#####獲取學生信息###########",id); return studentMapper01.getStudentById(id); } }

新建包com.goldwind.service.datasource2,並新建StudentService02.java文件:

package com.goldwind.service.datasource2; import com.goldwind.mapper.datasource2.StudentMapper02; import com.goldwind.entity.StudentEntity; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; /** * @Author: zy * @Description: student業務邏輯 * @Date: 2020-2-4 */ @Service @Slf4j public class StudentService02 { @Autowired private StudentMapper02 studentMapper02; /** * 新增學生 * @param name:姓名 * @param age:年齡 * @param sex:性別 * @return:返回受影響的行數 */
    public int insertStudent(String name,Integer age,Boolean sex){ int ret = studentMapper02.insertStudent(name,age,sex); log.info("#####新增學生###########",ret); return ret; } /** * 根據id獲取學生信息 * @param id:學生id * @return:返回學生信息 */
    public StudentEntity getStudentById(Integer id){ log.info("#####獲取學生信息###########",id); return studentMapper02.getStudentById(id); } }

新建包com.goldwind.datasource,在包下新建Datasource1Config.java:

package com.goldwind.datasource; import org.apache.ibatis.session.SqlSessionFactory; import org.mybatis.spring.SqlSessionFactoryBean; import org.mybatis.spring.SqlSessionTemplate; import org.mybatis.spring.annotation.MapperScan; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.jdbc.DataSourceBuilder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import org.springframework.jdbc.datasource.DataSourceTransactionManager; import javax.sql.DataSource; /** * @Author: zy * @Description: 讀取數據源datasource1配置信息 * @Configuration用於定義配置類,可替換xml配置文件,被注解的類內部包含有一個或多個被@Bean注解的方法,這些方法將會被AnnotationConfigApplicationContext或AnnotationConfigWebApplicationContext類進行掃描,並用於構建bean定義,初始化Spring容器 * @Configuation等價於<Beans></Beans> * @Bean等價於<Bean></Bean> * @ComponentScan等價於<context:component-scan base-package="com.goldwind.datasource1"/> * @Date: 2020-2-5 */
//表示這個類為一個配置類
@Configuration //配置mybatis的接口類放的地方
@MapperScan(basePackages = "com.goldwind.mapper.datasource1", sqlSessionFactoryRef = "datasource1SqlSessionFactory") public class Datasource1Config { /** * 功能描述:(配置datasource1數據庫) * @return:@return DataSource */
    //創建一個bean對象,並注入到Spring容器中
    @Bean(name = "datasource1DataSource") // 讀取application.properties中的配置參數映射成為一個對象
    @ConfigurationProperties(prefix = "spring.datasource.datasource1") //表示這個數據源是默認數據源
 @Primary public DataSource testDataSource() { return DataSourceBuilder.create().build(); } /** * 功能描述:(datasource1 sql會話工廠) * @param dataSource * @return * @throws Exception */ @Bean(name = "datasource1SqlSessionFactory") //表示這個數據源是默認數據源
 @Primary //@Qualifier表示查找Spring容器中名字為datasource1DataSource的對象
    public SqlSessionFactory testSqlSessionFactory(@Qualifier("datasource1DataSource") DataSource dataSource) throws Exception { SqlSessionFactoryBean bean = new SqlSessionFactoryBean(); bean.setDataSource(dataSource); return bean.getObject(); } /** * 功能描述:(datasource1 事物管理) * @param dataSource * @return
     */ @Bean(name = "datasource1TransactionManager") //表示這個數據源是默認數據源
 @Primary public DataSourceTransactionManager testTransactionManager(@Qualifier("datasource1DataSource") DataSource dataSource) { return new DataSourceTransactionManager(dataSource); } @Bean(name = "datasource1SqlSessionTemplate") //表示這個數據源是默認數據源
 @Primary public SqlSessionTemplate testSqlSessionTemplate( @Qualifier("datasource1SqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception { return new SqlSessionTemplate(sqlSessionFactory); } }

在包com.goldwind.datasource下,新建Datasource2Config.java:

package com.goldwind.datasource; import org.apache.ibatis.session.SqlSessionFactory; import org.mybatis.spring.SqlSessionFactoryBean; import org.mybatis.spring.SqlSessionTemplate; import org.mybatis.spring.annotation.MapperScan; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.jdbc.DataSourceBuilder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import org.springframework.jdbc.datasource.DataSourceTransactionManager; import javax.sql.DataSource; /** * @Author: zy * @Description: 讀取數據源datasource2配置信息 * @Configuration用於定義配置類,可替換xml配置文件,被注解的類內部包含有一個或多個被@Bean注解的方法,這些方法將會被AnnotationConfigApplicationContext或AnnotationConfigWebApplicationContext類進行掃描,並用於構建bean定義,初始化Spring容器 * @Configuation等價於<Beans></Beans> * @Bean等價於<Bean></Bean> * @ComponentScan等價於<context:component-scan base-package="com.goldwind.datasource2"/> * @Date: 2020-2-5 */
//表示這個類為一個配置類
@Configuration //配置mybatis的接口類放的地方
@MapperScan(basePackages = "com.goldwind.mapper.datasource2", sqlSessionFactoryRef = "datasource2SqlSessionFactory") public class Datasource2Config { /** * 功能描述:(配置datasource2數據庫) * @return:@return DataSource */
    //創建一個bean對象,並注入到Spring容器中
    @Bean(name = "datasource2DataSource") // 讀取application.properties中的配置參數映射成為一個對象
    @ConfigurationProperties(prefix = "spring.datasource.datasource2") public DataSource testDataSource() { return DataSourceBuilder.create().build(); } /** * 功能描述:(datasource2 sql會話工廠) * @param dataSource * @return * @throws Exception */ @Bean(name = "datasource2SqlSessionFactory") //@Qualifier表示查找Spring容器中名字為datasource2DataSource的對象
    public SqlSessionFactory testSqlSessionFactory(@Qualifier("datasource2DataSource") DataSource dataSource) throws Exception { SqlSessionFactoryBean bean = new SqlSessionFactoryBean(); bean.setDataSource(dataSource); return bean.getObject(); } /** * 功能描述:(datasource2 事物管理) * @param dataSource * @return
     */ @Bean(name = "datasource2TransactionManager") public DataSourceTransactionManager testTransactionManager(@Qualifier("datasource2DataSource") DataSource dataSource) { return new DataSourceTransactionManager(dataSource); } @Bean(name = "datasource2SqlSessionTemplate") public SqlSessionTemplate testSqlSessionTemplate( @Qualifier("datasource2SqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception { return new SqlSessionTemplate(sqlSessionFactory); } }

新建包com.goldwind.controller,並新建MyBatisMultilDataSourceController.java文件:

package com.goldwind.controller; import com.goldwind.service.datasource1.StudentService01; import com.goldwind.service.datasource2.StudentService02; import com.goldwind.entity.StudentEntity; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; /** * @Author: zy * @Description: 多數據源控制器 * @Date: 2020-2-5 */ @RestController public class MyBatisMultilDataSourceController { @Autowired private StudentService01 studentService01; /** * 新增學生 這里沒有對參數進行校驗 127.0.0.1:8080/insertStudent01?name=01&age=25&sex=1 * @param name:姓名 * @param age:年齡 * @param sex:性別 * @return:返回受影響的行數 */ @RequestMapping("/insertStudent01") public int insertStudent01(@RequestParam String name, @RequestParam Integer age, @RequestParam Boolean sex){ return studentService01.insertStudent(name,age,sex); } /** * 根據id獲取學生信息 * @param id:學生id * @return:返回學生信息 */ @RequestMapping("/getStudent01") public StudentEntity getStudentById01(@RequestParam Integer id){ return studentService01.getStudentById(id); } @Autowired private StudentService02 studentService02; /** * 新增學生 這里沒有對參數進行校驗 127.0.0.1:8080/insertStudent02?name=02&age=25&sex=1 * @param name:姓名 * @param age:年齡 * @param sex:性別 * @return:返回受影響的行數 */ @RequestMapping("/insertStudent02") public int insertStudent02(@RequestParam String name, @RequestParam Integer age, @RequestParam Boolean sex){ return studentService02.insertStudent(name,age,sex); } /** * 根據id獲取學生信息 * @param id:學生id * @return:返回學生信息 */ @RequestMapping("/getStudent02") public StudentEntity getStudentById02(@RequestParam Integer id){ return studentService02.getStudentById(id); } }

在包com.goldwind下新建程序入口文件App.java:

package com.goldwind; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.scheduling.annotation.EnableAsync; /** * @Author: zy * @Description: 啟動代碼 * @Date: 2020-2-2 */ @SpringBootApplication public class App { public static void main(String[] args){ //整個程序入口 啟動Spring Boot項目
        SpringApplication.run(App.class,args); } }

最終項目結構如下:

運行程序,訪問http://127.0.0.1:8080/insertStudent01?name=01&age=25&sex=1,查看數據庫database1表student1:

運行程序,訪問http://127.0.0.1:8080/insertStudent02?name=02&age=25&sex=1,查看數據庫database2表student2:

6.5、多數據源事務管理

 在多數據源的情況下,使用@Transactional注解時,應該指定事務管理者:

@Transactional(transactionManager = "datasource1TransactionManager")

我們修改MyBatisMultilDataSourceController.java,添加如下函數:

    /** * 新增學生 這里沒有對參數進行校驗 127.0.0.1:8080/insertStudent?name=00&age=25&sex=1 * @param name:姓名 * @param age:年齡 * @param sex:性別 * @return:返回受影響的行數 */ @RequestMapping("/insertStudent") @Transactional(transactionManager = "datasource1TransactionManager") public int insertStudent01AndStudent02(@RequestParam String name, @RequestParam Integer age, @RequestParam Boolean sex){ //第一個數據源
        int ret1 = studentService01.insertStudent(name,age,sex); //第二個數據源
        int ret2 = studentService02.insertStudent(name,age,sex); int i=1/0; return ret1+ret2; }

當我們訪問http://127.0.0.1:8080/insertStudent?name=00&age=25&sex=1,我們會發現database2數據庫student2表數據插入成功,而database1數據庫student1表由於事務的特性,將會進行回滾,數據將不會插入成功。

七、分布式事務

隨着微服務的拆分,肯定設計到分庫分表,但這之中肯定設計到分布式事務。最典型的例子就是銀行轉賬,比如銀行A給銀行B轉賬500 塊錢,流程肯定是銀行A-500,銀行B+500,在這個過程要么都成功,要么都成仁。

首先銀行A和銀行B的數肯定是在不同的數據庫,如果在轉賬的過程中,銀行A首先-500庫錢之后,在銀行B+500的時候出現了問題,如果事務不回滾,那么就會出現500塊錢丟失的問題,也就是出現了事務一致性問題。

可以通過JTA + Atomikos解決分布式事務。

JTA(java Transaction API)是JavaEE 13 個開發規范之一。Java 事務API,允許應用程序執行分布式事務處理——在兩個或多個網絡計算機資源上訪問並且更新數據。JDBC驅動程序的JTA支持極大地增強了數據訪問能力。事務最簡單最直接的目的就是保證數據的有效性,數據的一致性。

Atomikos是一個為Java平台提供增值服務的並且開源類事務管理器。

7.1、創建項目

我們首先拷貝一份springboot-multidatasource代碼,修改項目名稱為springboot-atomikos。

我們繼續以6.5小節中的insertStudent01AndStudent02()函數為例,我們想事務執行函數中的代碼。

我們首先添加ita-atomikos依賴:

<!-- 分布式事務 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jta-atomikos</artifactId>
</dependency>

7.2、新增配置文件信息

修改application.properties文件,配置信息如下:

###mysql1 mysql.datasource.datasource1.url = jdbc:mysql://localhost:3306/database1?serverTimezone=UTC&characterEncoding=utf8&useUnicode=true&useSSL=false mysql.datasource.datasource1.username = root mysql.datasource.datasource1.password = 123456aa mysql.datasource.datasource1.minPoolSize = 3 mysql.datasource.datasource1.maxPoolSize = 25 mysql.datasource.datasource1.maxLifetime = 20000 mysql.datasource.datasource1.borrowConnectionTimeout = 30 mysql.datasource.datasource1.loginTimeout = 30 mysql.datasource.datasource1.maintenanceInterval = 60 mysql.datasource.datasource1.maxIdleTime = 60 ###mysql2 mysql.datasource.datasource2.url = jdbc:mysql://localhost:3306/database2?serverTimezone=UTC&characterEncoding=utf8&useUnicode=true&useSSL=false mysql.datasource.datasource2.username = root mysql.datasource.datasource2.password = 123456aa mysql.datasource.datasource2.minPoolSize = 3 mysql.datasource.datasource2.maxPoolSize = 25 mysql.datasource.datasource2.maxLifetime = 20000 mysql.datasource.datasource2.borrowConnectionTimeout = 30 mysql.datasource.datasource2.loginTimeout = 30 mysql.datasource.datasource2.maintenanceInterval = 60 mysql.datasource.datasource2.maxIdleTime = 60

7.3、讀取配置文件信息

新建包com.goldwind.config,並新建DBConfig1.java:

package com.goldwind.config; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; /** * @Author: zy * @Description: 讀取配置文件信息 * @Date: 2020-2-5 */ @Data @ConfigurationProperties(prefix = "mysql.datasource.datasource1") public class DBConfig1 { private String url; private String username; private String password; private int minPoolSize; private int maxPoolSize; private int maxLifetime; private int borrowConnectionTimeout; private int loginTimeout; private int maintenanceInterval; private int maxIdleTime; private String testQuery; }

在包com.goldwind.config下,新建DBConfig2.java:

package com.goldwind.config; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; /** * @Author: zy * @Description: 讀取配置文件信息 * @Date: 2020-2-5 */ @Data @ConfigurationProperties(prefix = "mysql.datasource.datasource2") public class DBConfig2 { private String url; private String username; private String password; private int minPoolSize; private int maxPoolSize; private int maxLifetime; private int borrowConnectionTimeout; private int loginTimeout; private int maintenanceInterval; private int maxIdleTime; private String testQuery; }

刪除包com.goldwind.datasource下的所有文件,新建MyBatisConfig1.java:

package com.goldwind.datasource; import com.goldwind.config.DBConfig1; import com.mysql.jdbc.jdbc2.optional.MysqlXADataSource; import org.apache.ibatis.session.SqlSessionFactory; import org.mybatis.spring.SqlSessionFactoryBean; import org.mybatis.spring.SqlSessionTemplate; import org.mybatis.spring.annotation.MapperScan; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.jta.atomikos.AtomikosDataSourceBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import javax.sql.DataSource; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; /** * @Author: zy * @Description: 讀取數據源datasource1配置信息 * @Date: 2020-2-5 */
//表示這個類為一個配置類
@Configuration //配置mybatis的接口類放的地方
@MapperScan(basePackages = "com.goldwind.mapper.datasource1", sqlSessionTemplateRef = "testSqlSessionTemplate1") public class MyBatisConfig1 { //創建一個bean對象,並注入到Spring容器中
    @Bean(name = "testDataSource1") public DataSource testDataSource(DBConfig1 testConfig) throws SQLException { MysqlXADataSource mysqlXaDataSource = new MysqlXADataSource(); mysqlXaDataSource.setUrl(testConfig.getUrl()); mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true); mysqlXaDataSource.setPassword(testConfig.getPassword()); mysqlXaDataSource.setUser(testConfig.getUsername()); mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true); //將本地事務注冊到Atomikos全局事務
        AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean(); xaDataSource.setXaDataSource(mysqlXaDataSource); xaDataSource.setUniqueResourceName("testDataSource1"); xaDataSource.setMinPoolSize(testConfig.getMinPoolSize()); xaDataSource.setMaxPoolSize(testConfig.getMaxPoolSize()); xaDataSource.setMaxLifetime(testConfig.getMaxLifetime()); xaDataSource.setBorrowConnectionTimeout(testConfig.getBorrowConnectionTimeout()); xaDataSource.setLoginTimeout(testConfig.getLoginTimeout()); xaDataSource.setMaintenanceInterval(testConfig.getMaintenanceInterval()); xaDataSource.setMaxIdleTime(testConfig.getMaxIdleTime()); xaDataSource.setTestQuery(testConfig.getTestQuery()); // Connection conn = xaDataSource.getConnection(); // PreparedStatement ps = conn.prepareStatement("select * from student1"); // ResultSet res = ps.executeQuery(); // System.out.println(res); // conn.close();
        return xaDataSource; } /** * 功能描述:(datasource1 sql會話工廠) * @param dataSource * @return * @throws Exception */ @Bean(name = "testSqlSessionFactory1") //@Qualifier表示查找Spring容器中名字為datasource1DataSource的對象
    public SqlSessionFactory testSqlSessionFactory(@Qualifier("testDataSource1") DataSource dataSource) throws Exception { SqlSessionFactoryBean bean = new SqlSessionFactoryBean(); bean.setDataSource(dataSource); return bean.getObject(); } @Bean(name = "testSqlSessionTemplate1") public SqlSessionTemplate testSqlSessionTemplate( @Qualifier("testSqlSessionFactory1") SqlSessionFactory sqlSessionFactory) throws Exception { return new SqlSessionTemplate(sqlSessionFactory); } }

新建MyBatisConfig2.java:

package com.goldwind.datasource; import com.goldwind.config.DBConfig2; import com.mysql.jdbc.jdbc2.optional.MysqlXADataSource; import org.apache.ibatis.session.SqlSessionFactory; import org.mybatis.spring.SqlSessionFactoryBean; import org.mybatis.spring.SqlSessionTemplate; import org.mybatis.spring.annotation.MapperScan; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.jta.atomikos.AtomikosDataSourceBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import javax.sql.DataSource; import java.sql.SQLException; /** * @Author: zy * @Description: 讀取數據源datasource2配置信息 * @Date: 2020-2-5 */
//表示這個類為一個配置類
@Configuration //配置mybatis的接口類放的地方
@MapperScan(basePackages = "com.goldwind.mapper.datasource2", sqlSessionTemplateRef = "testSqlSessionTemplate2") public class MyBatisConfig2 { //創建一個bean對象,並注入到Spring容器中
    @Bean(name = "testDataSource2") public DataSource testDataSource(DBConfig2 testConfig) throws SQLException { MysqlXADataSource mysqlXaDataSource = new MysqlXADataSource(); mysqlXaDataSource.setUrl(testConfig.getUrl()); mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true); mysqlXaDataSource.setPassword(testConfig.getPassword()); mysqlXaDataSource.setUser(testConfig.getUsername()); mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true); //將本地事務注冊到Atomikos全局事務
        AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean(); xaDataSource.setXaDataSource(mysqlXaDataSource); xaDataSource.setUniqueResourceName("testDataSource2"); xaDataSource.setMinPoolSize(testConfig.getMinPoolSize()); xaDataSource.setMaxPoolSize(testConfig.getMaxPoolSize()); xaDataSource.setMaxLifetime(testConfig.getMaxLifetime()); xaDataSource.setBorrowConnectionTimeout(testConfig.getBorrowConnectionTimeout()); xaDataSource.setLoginTimeout(testConfig.getLoginTimeout()); xaDataSource.setMaintenanceInterval(testConfig.getMaintenanceInterval()); xaDataSource.setMaxIdleTime(testConfig.getMaxIdleTime()); xaDataSource.setTestQuery(testConfig.getTestQuery()); return xaDataSource; } /** * 功能描述:(datasource2 sql會話工廠) * @param dataSource * @return * @throws Exception */ @Bean(name = "testSqlSessionFactory2") //@Qualifier表示查找Spring容器中名字為datasource2DataSource的對象
    public SqlSessionFactory testSqlSessionFactory(@Qualifier("testDataSource2") DataSource dataSource) throws Exception { SqlSessionFactoryBean bean = new SqlSessionFactoryBean(); bean.setDataSource(dataSource); return bean.getObject(); } @Bean(name = "testSqlSessionTemplate2") public SqlSessionTemplate testSqlSessionTemplate( @Qualifier("testSqlSessionFactory2") SqlSessionFactory sqlSessionFactory) throws Exception { return new SqlSessionTemplate(sqlSessionFactory); } }

7.4、代碼

修改代碼MyBatisMultilDataSourceController.java:

package com.goldwind.controller; import com.goldwind.service.datasource1.StudentService01; import com.goldwind.service.datasource2.StudentService02; import com.goldwind.entity.StudentEntity; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; /** * @Author: zy * @Description: 多數據源控制器 * @Date: 2020-2-5 */ @RestController public class MyBatisMultilDataSourceController { @Autowired private StudentService01 studentService01; /** * 新增學生 這里沒有對參數進行校驗 127.0.0.1:8080/insertStudent01?name=01&age=25&sex=1 * @param name:姓名 * @param age:年齡 * @param sex:性別 * @return:返回受影響的行數 */ @RequestMapping("/insertStudent01") public int insertStudent01(@RequestParam String name, @RequestParam Integer age, @RequestParam Boolean sex){ return studentService01.insertStudent(name,age,sex); } /** * 根據id獲取學生信息 * @param id:學生id * @return:返回學生信息 */ @RequestMapping("/getStudent01") public StudentEntity getStudentById01(@RequestParam Integer id){ return studentService01.getStudentById(id); } @Autowired private StudentService02 studentService02; /** * 新增學生 這里沒有對參數進行校驗 127.0.0.1:8080/insertStudent02?name=02&age=25&sex=1 * @param name:姓名 * @param age:年齡 * @param sex:性別 * @return:返回受影響的行數 */ @RequestMapping("/insertStudent02") public int insertStudent02(@RequestParam String name, @RequestParam Integer age, @RequestParam Boolean sex){ return studentService02.insertStudent(name,age,sex); } /** * 根據id獲取學生信息 * @param id:學生id * @return:返回學生信息 */ @RequestMapping("/getStudent02") public StudentEntity getStudentById02(@RequestParam Integer id){ return studentService02.getStudentById(id); } /** * 新增學生 這里沒有對參數進行校驗 127.0.0.1:8080/insertStudent?name=00&age=25&sex=1 * @param name:姓名 * @param age:年齡 * @param sex:性別 * @return:返回受影響的行數 */ @RequestMapping("/insertStudent") @Transactional public int insertStudent01AndStudent02(@RequestParam String name, @RequestParam Integer age, @RequestParam Boolean sex){ //第一個數據源
        int ret1 = studentService01.insertStudent(name,age,sex); //第二個數據源
        int ret2 = studentService02.insertStudent(name,age,sex); int i=1/age; //要不全部插入 要不全部不插入
        return ret1+ret2; } }

修改App.java代碼:

package com.goldwind; import com.goldwind.config.DBConfig1; import com.goldwind.config.DBConfig2; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.scheduling.annotation.EnableAsync; /** * @Author: zy * @Description: 啟動代碼 * @Date: 2020-2-2 */ @SpringBootApplication //開啟讀取配置文件
@EnableConfigurationProperties(value = { DBConfig1.class, DBConfig2.class }) public class App { public static void main(String[] args){ //整個程序入口 啟動Spring Boot項目
        SpringApplication.run(App.class,args); } }

最終項目結構如下:

運行程序,訪問http://127.0.0.1:8080/insertStudent?name=00&age=25&sex=1,結果: database1和database2數據庫都插入數據。

訪問http://127.0.0.1:8080/insertStudent?name=00&age=25&sex=1,結果: database1和database2數據庫都未插入數據,因此事務執行失敗了。

八、源碼下載

源碼放置在github:

https://github.com/Zhengyang550/springboot-helloworld

https://github.com/Zhengyang550/springboot-multidatasource

https://github.com/Zhengyang550/springboot-atomikos

參考文章:

[1] 使用SpringBoot的@Async實現異步調用方法,以及自己開啟新線程異步調用

[2] springboot 多環境配置

[3] 深入理解Spring事務的基本原理、傳播屬性、隔離級別

[4] SpringAOP原理分析

[5] spring4.0之二:@Configuration的使用

[6] @ConfigurationProperties 注解使用姿勢,這一篇就夠了

[7] springboot-mybatis多數據源的兩種整合方法

[8] 一文詳解 SpringBoot 多數據源中的分布式事務

[9] 4.0 atomikos JTA/XA全局事務

[10] java技術驛站

[11] 深入淺出Mybatis系列(一)---Mybatis入門


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM