程序猿学社的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,形成对应专题。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:http://bianchenghao.cn/39079.html