程序猿學社的GitHub,歡迎Star
https://github.com/ITfqyd/cxyxs
本文已記錄到github,形成對應專題。
文章目錄
前言
隨着業務的不斷增加,我們的系統會越來越龐大,因此,一個項目中使用多個數據源,是我們可能會遇到的問題。本來就來,看看springboot多數據源是怎麼搭建的。
多數據源,如何劃分?
分爲兩種。
- 分包,分包主要是根據業務劃分。
- 註解方式,實際上就是通過aop進行攔截,不同的註解裏面的值,指向不同的數據源。
環境
爲了減少寫sql,更好的提高開發效率,引入mybatis-plus
名稱 | 作用 |
---|---|
springboot2.0 | 簡化配置,快速構建一個項目 |
mybatis-plus | 簡化sql的一個插件,可以實現通過操作實體類,操作數據庫的表。 |
mysql | 數據庫(兩個庫pro和pro1) |
單個數據源
爲什麼要說一說搭建單個數據源,社長,多數據源勒?來自某某社友的吐槽,你個糟老頭子,壞的很。
先搭建單個數據源也是爲了簡化難度。先保證單個數據源沒有問題後,我們再來挑戰多個數據源,不要,還沒頭學會走路,就想着跑,還是得一步一個腳印。才能纔不會摔倒。也不會遇到各種各樣的報錯。
- 小技巧:可以把一個複雜的問題,拆分成多個小的問題,一個個的解決。
sql腳本
創建一個數據庫pro
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for emp
-- ----------------------------
DROP TABLE IF EXISTS `emp`;
CREATE TABLE `emp` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`age` int(11) NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of emp
-- ----------------------------
INSERT INTO `emp` VALUES (1, '后羿', 21);
INSERT INTO `emp` VALUES (2, '韓信', 40);
INSERT INTO `emp` VALUES (3, '蘭陵王', 11);
SET FOREIGN_KEY_CHECKS = 1;
搭建項目
創建一個springboot項目
可以發現有一個test1包和test2包,我們先用test1包實現調用數據庫成功。
pom.xml依賴
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.5.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.cloudtech</groupId>
<artifactId>moredatasource</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>moredatasource</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!--web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 增加thymeleaf座標 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!--簡化實體類-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- 熱部署 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
<!--swagger2-->
<dependency>
<groupId>com.spring4all</groupId>
<artifactId>spring-boot-starter-swagger</artifactId>
<version>1.5.1.RELEASE</version>
</dependency>
<!--MP插件,簡化sql操作-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.3.0</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.18</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.6</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
- springboot是2.0以上,mp的版本是3.0以上,如果不一樣,可能會有驚喜。
application.yml
server:
port: 8888
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
url: jdbc:mysql://localhost:3306/pro?useUnicode=true&characterEncoding=utf8&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=GMT%2B8
username: root
password: root
mybatis-plus:
##mapper.xml文件存放的路徑
mapper-locations: classpath*:/com/cxyxs/moredatasource/test1/mapper/*.xml
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
- 這個也沒有什麼好說的,log-impl是打印sql日誌。
@Data
public class Emp {
@TableId(type = IdType.AUTO)
private Integer id;
private String name;
private Integer age;
}
- 可以發現我們的代碼沒有set get,這是因爲我們引入lombok插件
- @TableId(type = IdType.AUTO) 數據庫ID自增,國產的源碼看起來就是舒服,中文註釋,對我這種英文不好的,很好使。
dao
@Mapper
@Repository
public interface EmpMapper1 extends BaseMapper<Emp> {
}
- @Repository 爲了解決報紅錯的,但是,這個紅色的波浪線,不會影響代碼的運行,但是,對於我這種有強迫症的人,看起來十分的不舒服。
controller
package com.cxyxs.moredatasource.controller;
import com.cxyxs.moredatasource.entity.Emp;
import com.cxyxs.moredatasource.test1.dao.EmpMapper1;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
* Description:
* Author: 程序猿學社
* Date: 2020/3/7 12:15
* Modified By:
*/
@RestController
public class TestController {
@Autowired
private EmpMapper1 empMapper;
@GetMapping("/getKing")
public List getKing(){
List<Emp> emps = empMapper.selectList(null);
return emps;
};
}
- 操作數據庫用到mybatis-plus的一些知識,可以看看springboot集成mybatis-plus
- @RestController註解,實際上就是兩個註解的效果。我們看一看的源碼就知道是怎麼一回事。
- @GetMapping 使用的RESTful風格,等價於@RequestMapping(method = RequestMethod.GET)
springboot入口
package com.cxyxs.moredatasource;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
@SpringBootApplication
@ComponentScan(basePackages = {"com.cxyxs"})
@MapperScan("com.cxyxs.moredatasource.test1.dao")
public class MoredatasourceApplication {
public static void main(String[] args) {
SpringApplication.run(MoredatasourceApplication.class, args);
}
}
- @SpringBootApplication實際上就是三個註解的組合
- @MapperScan 掃描的Mapper類所在包的路徑,也就是我們所謂的dao包。
- ComponentScan 自動掃描當前包及子包下所有類,得理解這句話
訪問網站
輸入http://localhost:8080/getKing
- 有不少社友在問,社長,社長,返回的結果集,你界面好直觀,數據結構很清楚。而我的都堆積在一起,閱讀起來很累。那是因爲我使用的谷歌瀏覽器,所以,我直接百度上搜索"谷歌json插件"。
到這裏單個數據源,我們已經搞定,可以給自己獎勵一個雞腿!
下面,我們繼續多數據源的搭建。話不多說,走起,盤他
多個數據源(分包)
mybatis-plus(簡稱MP),以後的文章中,寫MP,就是表示mybatis-plus
利用單個數據源的sql,再重新搭建一個數據庫pro1
- 上面是兩個數據庫表的對應數據,爲了方便後面區分,age是有明顯的區別的。第一個表是123,第二個庫的表是456.
表關係
庫 | 包 |
---|---|
pro | test1 |
pro1 | test2 |
項目改造
在單個數據源上改造
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.5.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.cloudtech</groupId>
<artifactId>moredatasource</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>moredatasource</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!--web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 增加thymeleaf座標 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!--簡化實體類-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- 熱部署 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
<!--swagger2-->
<dependency>
<groupId>com.spring4all</groupId>
<artifactId>spring-boot-starter-swagger</artifactId>
<version>1.5.1.RELEASE</version>
</dependency>
<!--MP插件,簡化sql操作-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.3.0</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.18</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.6</version>
</dependency>
<!--swagger2-->
<dependency>
<groupId>com.spring4all</groupId>
<artifactId>spring-boot-starter-swagger</artifactId>
<version>1.5.1.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<!--解決編譯後,xml文件沒有過去的問題-->
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
- 相對於單個數據源,多引入了一個swagger,主要是實現api接口文檔可視化。
applicaion.yml
server:
port: 8888
spring:
datasource:
test1:
# 使用druid數據源
type: com.alibaba.druid.pool.DruidDataSource
jdbc-url: jdbc:mysql://localhost:3306/pro?useUnicode=true&characterEncoding=utf8&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=GMT%2B8
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
test2:
# 使用druid數據源
type: com.alibaba.druid.pool.DruidDataSource
jdbc-url: jdbc:mysql://localhost:3306/pro1?useUnicode=true&characterEncoding=utf8&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=GMT%2B8
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis-plus:
configuration:
##打印sql日誌,本地測試使用,生產環境不要使用,注意、注意、注意
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
####掃描swagger註解
swagger:
base-package: com.cxyxs
- jdbc-url和driver-class-name這兩個參數得注意,在多數據源配置中,有變動,我們熟悉的是url,所以不少人都會有困惑,同樣的代碼,爲什麼,社長你的啓動就不報錯,難道是社長人品太好的原因。
- 前方高能,注意警惕,我們可以發現配置連接數據庫的那塊代碼,有了明顯的變動,增加了一個test1和test2。spring.datasource.test1和spring.datasource.test2後面會用到,這個是前綴,type,jdbc-url等等這些是後綴,是固定的。
- swagger是需要配置掃描範圍的
config配置
數據源配置1
package com.cxyxs.moredatasource.config;
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import org.apache.ibatis.plugin.Interceptor;
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.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import javax.sql.DataSource;
/**
* Description:
* Author: 程序猿學社
* Date: 2020/3/7 18:14
* Modified By:
*/
@Configuration
@MapperScan(basePackages= {"com.cxyxs.moredatasource.test1.dao"},sqlSessionFactoryRef="test1SqlSessionFactory")
public class DataSourceConfig1 {
@Bean(name="test1DataSource")
@ConfigurationProperties(prefix="spring.datasource.test1")
public DataSource testDataSource() {
return DataSourceBuilder.create().build();
}
@Bean(name="test1SqlSessionFactory")
public SqlSessionFactory testSqlSessionFactory(@Qualifier("test1DataSource") DataSource dataSource,@Qualifier("testPaginationInterceptor") PaginationInterceptor paginationInterceptor) throws Exception {
MybatisSqlSessionFactoryBean bean=new MybatisSqlSessionFactoryBean ();
bean.setDataSource(dataSource);
Resource[] resources = new PathMatchingResourcePatternResolver()
.getResources("classpath*:/com/cxyxs/moredatasource/test1/mapper/*.xml");
bean.setMapperLocations(resources);
Interceptor[] plugins = new Interceptor[]{paginationInterceptor};
bean.setPlugins(plugins);
return bean.getObject();
}
@Bean(name="test1TransactionManager")//配置事務
public DataSourceTransactionManager testTransactionManager(@Qualifier("test1DataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
@Bean("testPaginationInterceptor")
public PaginationInterceptor paginationInterceptor(){
return new PaginationInterceptor();
}
@Bean(name="test1SqlSessionTemplate")
public SqlSessionTemplate testSqlSessionTemplate(@Qualifier("test1SqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
-
springboot2.0以上會有一個錯誤,需要你指定一個默認數據源,在springboot2.0以後就修復了這個問題,如果springboot版本在2.0一下的社友可以在每個@Bean註解下面增加一句代碼@Primary
-
paginationInterceptor註解是配置MP分頁插件,不瞭解的社友可以看看這篇文章
springboot集成mybatis-plus -
配置好分頁插件的bean,是不是要跟SqlSessionFactory建立聯繫。在單個數據源中,我們是不需要建立聯繫的。
-
下面代碼是配置mybatis配置文件在哪裏。還記得我們單數據源配置的時候,配置在哪裏嗎?配置在yml裏面。
-
分享一下,個人搭建過程中,遇到的問題,最初我使用的是SqlSessionFactory這個對象,在測試過程中,發現,所有的mybatis調用數據庫的操作都沒有毛病,但是,我一調用MP提供的一些insert方法時,發現,提示找不到這個方法。就在想,是不是又要建立什麼聯繫,查了一些資料,才發現MP自己實現了一個工廠MybatisSqlSessionFactoryBean,換成這個對象,問題就解決了,從這,我得到一個啓示,學習一個東西,不應該簡單的會使用,而應該深入瞭解他的一個思想。進行一些簡單的源碼學習。
//MP的工廠
MybatisSqlSessionFactoryBean bean=new MybatisSqlSessionFactoryBean ();
//mybatis的工廠
//SqlSessionFactory bean=new MybatisSqlSessionFactoryBean ();
數據源配置2
直接把數據源配置1的類copy一下,全文把test1替換成test2。把類名的1改成2.放在注入到springboot中報錯。
- boot2.0一下版本的社友,把數據源配置1代碼copy過來的時候,記得去掉@Primary註解,不然springboot會犯困惑,到底要加載那個數據源,會有疑惑,有疑惑,那隻能給你報錯。
package com.cxyxs.moredatasource.config;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
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.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import javax.sql.DataSource;
/**
* Description:
* Author: 程序猿學社
* Date: 2020/3/7 18:14
* Modified By:
*/
@Configuration
@MapperScan(basePackages= {"com.cxyxs.moredatasource.test2.dao"},sqlSessionFactoryRef="test2SqlSessionFactory")
public class DataSourceConfig2 {
@Bean(name="test2DataSource")
@ConfigurationProperties(prefix="spring.datasource.test2")
public DataSource testDataSource() {
return DataSourceBuilder.create().build();
}
@Bean(name="test2SqlSessionFactory")
public SqlSessionFactory testSqlSessionFactory(@Qualifier("test2DataSource") DataSource dataSource) throws Exception {
MybatisSqlSessionFactoryBean bean=new MybatisSqlSessionFactoryBean();
bean.setDataSource(dataSource);
Resource[] resources = new PathMatchingResourcePatternResolver()
.getResources("classpath*:/com/cxyxs/moredatasource/test2/mapper/*.xml");
bean.setMapperLocations(resources);
return bean.getObject();
}
@Bean(name="test2TransactionManager")//配置事務
public DataSourceTransactionManager testTransactionManager(@Qualifier("test2DataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
@Bean(name="test2SqlSessionTemplate")
public SqlSessionTemplate testSqlSessionTemplate(@Qualifier("test2SqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
實體類
package com.cxyxs.moredatasource.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.Data;
import org.omg.CORBA.IDLType;
/**
* Description:
* Author: 程序猿學社
* Date: 2020/3/7 12:03
* Modified By:
*/
@Data
public class Emp {
@TableId(type = IdType.AUTO)
private Integer id;
private String name;
private Integer age;
}
dao類和mapper
test1包
@Repository
public interface EmpMapper1 extends BaseMapper<Emp>{
@Select("select * from emp")
public List<Emp> selectList();
/**
* 測試mapper
* @return
*/
public List<Emp> getAll();
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.cxyxs.moredatasource.test1.dao.EmpMapper1">
<!-- 根據區域名稱獲取區域代碼-->
<select id="getAll" resultType="com.cxyxs.moredatasource.entity.Emp">
select * from emp
</select>
</mapper>
test2包
package com.cxyxs.moredatasource.test2.dao;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.cxyxs.moredatasource.entity.Emp;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import org.springframework.stereotype.Repository;
import java.util.List;
/**
* Description:
* Author: 程序猿學社
* Date: 2020/3/7 12:01
* Modified By:
*/
@Repository
public interface EmpMapper2 extends BaseMapper<Emp>{
@Select("select * from emp")
public List<Emp> selectList();
/**
* 測試mapper
* @return
*/
public List<Emp> getAll();
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.cxyxs.moredatasource.test2.dao.EmpMapper2">
<!-- 根據區域名稱獲取區域代碼-->
<select id="getAll" resultType="com.cxyxs.moredatasource.entity.Emp">
select * from emp
</select>
</mapper>
- 在這裏給大家推薦一款提高開發效率的工具,不是打廣告,MybatisX 是一款基於 IDEA 的快速開發插件,爲效率而生。他也是MP官方推薦的,簡單說一下他的作用。我們再dao類寫上如下代碼。使用MybatisX插件會幫我們生成對應的xml文件。
public List<Emp> getAll();
插件自動生成的
<select id="getAll" resultType="com.cxyxs.moredatasource.entity.Emp"></select>
controll類
package com.cxyxs.moredatasource.controller;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import com.cxyxs.moredatasource.entity.Emp;
import com.cxyxs.moredatasource.test1.dao.EmpMapper1;
import com.cxyxs.moredatasource.test2.dao.EmpMapper2;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
* Description:
* Author: 程序猿學社
* Date: 2020/3/7 12:15
* Modified By:
*/
@RestController
@Api("測試多數據源接口")
public class TestController {
@Autowired
private EmpMapper1 empMapper1;
@Autowired
private EmpMapper2 empMapper2;
@ApiOperation("測試mybatis@select註解,通過test1數據庫實現")
@GetMapping("/getKing1")
public List getKing1(){
List<Emp> emps = empMapper1.selectList();
return emps;
};
@ApiOperation("測試mybatis@select註解,通過test2數據庫實現")
@GetMapping("/getKing2")
public List getKing2(){
List<Emp> emps = empMapper2.selectList();
return emps;
};
@ApiOperation("測試mybatis的mapper.xml文件調用,通過test1數據庫實現")
@GetMapping("/getKing3")
public List getKing3(){
List<Emp> emps = empMapper1.getAll();
return emps;
};
@ApiOperation("測試mybatis的mapper.xml文件調用,通過test2數據庫實現")
@GetMapping("/getKing4")
public List getKing4(){
List<Emp> emps = empMapper2.getAll();
return emps;
};
@ApiOperation("通過mp調用test1數據庫實現查詢")
@GetMapping("/getKing5")
public List getKing5(){
List<Emp> emps = empMapper1.selectList(null);
return emps;
};
@ApiOperation("通過mp調用test2數據庫實現查詢")
@GetMapping("/getKing6")
public List getKing6(){
List<Emp> emps = empMapper2.selectList(null);
return emps;
};
}
- @api等等註解都是swgger提供的一些註解,也就是讓我們通過頁面,可以看到這些註釋,方便前端開發調用,不至於,動不動就問後臺,提供開發效率。這不是本文的重點,不瞭解swagger的社友,可以去學習一下。
- 模擬了調用2個庫,通過三種方式實現。第一種,@select註解,第二種,通過mybatis的xml文件調用,第三種,通過MP提供的公有方法實現。也是考慮到,實際項目開發過程中,可能每一種都有可能用到。
啓動類
package com.cxyxs.moredatasource;
import com.spring4all.swagger.EnableSwagger2Doc;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration;
import org.springframework.context.annotation.ComponentScan;
@SpringBootApplication
@ComponentScan(basePackages = {"com.cxyxs"})
@EnableSwagger2Doc
public class MoredatasourceApplication {
public static void main(String[] args) {
SpringApplication.run(MoredatasourceApplication.class, args);
}
}
- @EnableSwagger2Doc 啓動swagger
測試
輸入網址http://localhost:8080/swagger-ui.html#/
- 選擇某個請求點擊try out
- 往下來,可以查看Response Body,獲取對應後端返回的結果。
到這裏我們springboot+mybatis-plus+多數據源+swagger就實現了。
多數據源事務
爲什麼要使用事務?
- 例如隔壁小王給社長轉賬的100元,他的業務可以分爲2步
- 第一步:隔壁小王扣100元
- 第二步:社長+100元。
如果第一步和第二步之間就報錯勒,可能會存在隔壁小王扣了100,但是社長,也沒有收到錢,隔壁小王賴上社長,這我跳到黃河都說不清。
爲了保證社長的一世清白,這是我們的主角事務開始隆重登場。
簡單的理解事務流程
- START TRANSACTION開啓事務
- 隔壁小王update
- 社長 update
- commit 提交事務或者rollback回滾事務。
給多數據源代碼添加事務
不指定某個事務
之前的內容,我們都是讀的操作,不涉及到事務,本文,我們來試試寫操作,並增加對應的事務來試試。
在TestController類中增加一個方法
@ApiOperation("測試插入數據")
@PostMapping("/saveEmp1")
@Transactional
public String saveEmp(Emp emp) {
int insert = empMapper1.insert(emp);
if(insert > 0){
return "插入成功";
}else{
return "插入失敗";
}
};
- id是數據庫自增主鍵,所以不需要設置值。
- @Transactional我們都知道這個關鍵字是設置事務的。各位社友,覺得我直接啓動項目,訪問這個方法,可能會出現什麼樣的結果?
保存成功?
{
"timestamp": "2020-03-08T11:53:42.234+0000",
"status": 500,
"error": "Internal Server Error",
"message": "No qualifying bean of type 'org.springframework.transaction.TransactionManager' available: expected single matching bean but found 2: test1TransactionManager,test2TransactionManager",
"trace": "org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'org.springframework.transaction.TransactionManager' available: expected single matching bean but found 2: test1TransactionManager,test2TransactionManager\r\n\tat org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveNamedBean(DefaultListableBeanFactory.java:1180)\r\n\tat org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveBean(DefaultListableBeanFactory.java:416)\r\n\tat org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:349)\r\n\tat org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:342)\r\n\tat org.springframework.transaction.interceptor.TransactionAspectSupport.determineTransactionManager(TransactionAspectSupport.java:480)\r\n\tat org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:335)\r\n\tat org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:99)\r\n\tat org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)\r\n\tat org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:747)\r\n\tat org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:689)\r\n\tat com.cxyxs.moredatasource.controller.TestController$$EnhancerBySpringCGLIB$$11aec56f.saveEmp(<generated>)\r\n\tat sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\r\n\tat sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)\r\n\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)\r\n\tat java.lang.reflect.Method.invoke(Method.java:498)\r\n\tat org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:190)\r\n\tat org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:138)\r\n\tat org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:106)\r\n\tat org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:879)\r\n\tat org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:793)\r\n\tat org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)\r\n\tat org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1040)\r\n\tat org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:943)\r\n\tat org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)\r\n\tat org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:909)\r\n\tat javax.servlet.http.HttpServlet.service(HttpServlet.java:660)\r\n\tat org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)\r\n\tat javax.servlet.http.HttpServlet.service(HttpServlet.java:741)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\r\n\tat org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\r\n\tat org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)\r\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\r\n\tat org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)\r\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\r\n\tat org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)\r\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\r\n\tat org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202)\r\n\tat org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)\r\n\tat org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:541)\r\n\tat org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139)\r\n\tat org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)\r\n\tat org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)\r\n\tat org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343)\r\n\tat org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:367)\r\n\tat org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)\r\n\tat org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:868)\r\n\tat org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1639)\r\n\tat org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)\r\n\tat java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)\r\n\tat java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)\r\n\tat org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)\r\n\tat java.lang.Thread.run(Thread.java:745)\r\n",
"path": "/saveEmp1"
}
- org.springframework.transaction.TransactionManager’ available: expected single matching bean but found 2,這個事關鍵的信息,意思說找到兩個事務,就有點犯困惑,到底運行那個事務。
- 還記得DataSourceConfig1類方法裏面,我們配置了test1TransactionManager,我們指定一下,使用那個事務。
指定某個事務
還是我們的TestController類中增加一個方法
@ApiOperation("測試給test1插入數據,增加指定某個事務的代碼")
@PostMapping("/saveEmp2")
@Transactional(value = "test1TransactionManager")
public String saveEmp2(Emp emp) {
int insert = empMapper1.insert(emp);
if(insert > 0){
return "插入成功";
}else{
return "插入失敗";
}
};
指定事務,模擬報錯
@ApiOperation("測試給test1插入數據,增加指定某個事務的代碼,並故意在代碼中報錯")
@PostMapping("/saveEmp3")
@Transactional(value = "test1TransactionManager")
public String saveEmp3(Emp emp) {
int insert = empMapper1.insert(emp);
//故意報錯
String str= null;
System.out.println(str.toString()); //這裏會報錯
if(insert > 0){
return "插入成功";
}else{
return "插入失敗";
}
};
- str爲null,再調用null.toString,肯定會報空指針,我們來斷點跟蹤一下。
- insert的值爲1,是不是表示,這個記錄就寫入到數據庫了?
那我們來檢查一下數據庫表的身體,看看是否手腳乾淨。
- 在方法上增加了事務,表示需要這個方法執行完,確保沒問題後,才能調用commit方法,同步到數據庫裏。
繼續向下執行。
-這裏直接報錯勒,到這裏整個方法已經跑完,各位社友覺得數據是否寫入成功?
再次檢查數據庫對應表的身體
- 很乾淨,說明配置事務後,如果方法中間拋出異常,事務會回滾。
本文還未完,後續會持續更新
下次更新內容
如何解決多數據源事務問題,在實際項目開發過程中,會存在,在一個項目中,操作多個數據源的問題,在調用多個數據源過程中,我們如何保證事務的一致性,實際上,這個版本的代碼,你可以再調用test1的插入方法,再調用test2庫,執行插入方法。這兩者調用之間弄一個報錯,可以發現,test1庫新增了一條數據,test2插入失敗。
關注公衆號"程序猿學社",回覆關鍵字999,獲取源碼
程序猿學社的GitHub,歡迎Star
https://github.com/ITfqyd/cxyxs
本文已記錄到github,形成對應專題。