欢送拜访我的 GitHub
https://github.com/zq2599/blog_demos
内容:所有原创文章分类汇总及配套源码,波及 Java、Docker、Kubernetes、DevOPS 等;
对于 druid 多数据源
本文是《MyBatis 高级实战》系列的第四篇,一个 springboot 利用同时操作两个数据库的场景,在平时也会遇到,明天要实战的就是通过 druid 配置两个数据源,让一个 springboot 利用同时应用这两个数据源;
多数据源配置的基本思路
- 首先要明确的是:数据源是通过配置类实现的,因而要去掉 springboot 中和数据源相干的主动拆卸;
- 最外围的问题有两个,第一个是确定表和数据源的关系,这个关系是在 SqlSessionFactory 实例中确立的,代码如下所示:
@Bean(name = "secondSqlSessionFactory")
public SqlSessionFactory sqlSessionFactory(@Qualifier("secondDataSource") DataSource dataSource) throws Exception {SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mappers/second/**/*Mapper.xml"));
return bean.getObject();}
- 第二个外围问题是包扫描,即指定的 mapper 接口要应用指定的 sqlSessionTemplat,这个关系在 SqlSessionTemplate 配置类中 (相当于旧版的 xml 配置 bean),如下图所示:
- 从上述代码可见,如果下层的业务代码想操作 </font>secondDataSource</font> 这个数据源的表,只有把对应的 *Mapper.xml 文件和 Mapper 接口文件对应的目录下即可;
- 整个配置的关键步骤如下图所示:
实战概览
本次实战的内容如下:
- 一共有两个数据库:<font color=”blue”>mybatis</font> 和 <font color=”blue”>mybatis_second</font>;
- mybatis 中有名为 <font color=”blue”>user</font> 的表,mybatis_second 中有名为 <font color=”blue”>address</font> 的表;
- 新建名为 <font color=”red”>druidtwosource</font> 的 springboot 利用,外面有两个 controller,能够别离对 user、address 这两个表进行操作;
- 编写单元测试用例,通过调用 controller 接口验证利用性能失常;
- 启动 springboot 利用,通过 swagger 验证性能失常;
- 进入 druid 监控页面;
源码下载
- 如果您不想编码,能够在 GitHub 下载所有源码,地址和链接信息如下表所示 (https://github.com/zq2599/blo…:
名称 | 链接 | 备注 |
---|---|---|
我的项目主页 | https://github.com/zq2599/blo… | 该我的项目在 GitHub 上的主页 |
git 仓库地址 (https) | https://github.com/zq2599/blo… | 该我的项目源码的仓库地址,https 协定 |
git 仓库地址 (ssh) | git@github.com:zq2599/blog_demos.git | 该我的项目源码的仓库地址,ssh 协定 |
- 这个 git 我的项目中有多个文件夹,本章的利用在 <font color=”blue”>mybatis</font> 文件夹下,如下图红框所示:
创立数据库和表
- 创立名为 <font color=”blue”>mybatis</font> 的数据库,建表语句如下:
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (`id` int(32) NOT NULL AUTO_INCREMENT,
`name` varchar(32) NOT NULL,
`age` int(32) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
- 创立名为 <font color=”blue”>mybatis_second</font> 的数据库,建表语句如下:
DROP TABLE IF EXISTS `address`;
CREATE TABLE `address` (`id` int(32) NOT NULL AUTO_INCREMENT,
`city` varchar(32) NOT NULL,
`street` varchar(32) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
编码
- 前文《MyBatis 高级实战之一:Spring Boot 集成》创立了父工程 mybatis,本文持续在此工程中新增子工程,名为 <font color=”blue”>druidtwosource</font>,先提前看整个子工程文件构造,如下图,要留神的是红框 1 中的 mapper 接口,以及红框 2 中的 mapper 映射文件,这两处都依照数据库的不同放入各自文件夹:
- druidtwosource 工程的 pom.xml 内容如下:
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.bolingcavalry</groupId>
<artifactId>mybatis</artifactId>
<version>1.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<groupId>com.bolingcavalry</groupId>
<artifactId>druidtwosource</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>druidtwosource</name>
<description>Demo project for Mybatis Druid (two datasource) in Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</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>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
</dependency>
<!-- swagger-ui -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
- 配置文件 application.yml,可见这外面有 <font color=”red”>first</font> 和 <font color=”red”>second</font> 两个数据源配置,而 druid 的 <font color=”blue”>web-stat-filter</font> 和 <font color=”blue”>stat-view-servlet</font> 这两个配置是专用的:
server:
port: 8080
spring:
#1.JDBC 数据源
datasource:
druid:
first:
username: root
password: 123456
url: jdbc:mysql://192.168.50.43:3306/mybatis?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC
driver-class-name: com.mysql.cj.jdbc.Driver
#初始化连接池的连贯数量 大小,最小,最大
initial-size: 5
min-idle: 5
max-active: 20
#配置获取连贯期待超时的工夫
max-wait: 60000
#配置距离多久才进行一次检测,检测须要敞开的闲暇连贯,单位是毫秒
time-between-eviction-runs-millis: 60000
# 配置一个连贯在池中最小生存的工夫,单位是毫秒
min-evictable-idle-time-millis: 30000
# 配置一个连贯在池中最大生存的工夫,单位是毫秒
max-evictable-idle-time-millis: 300000
validation-query: SELECT 1 FROM user
test-while-idle: true
test-on-borrow: true
test-on-return: false
# 是否缓存 preparedStatement,也就是 PSCache 官网倡议 MySQL 下倡议敞开 集体倡议如果想用 SQL 防火墙 倡议关上
pool-prepared-statements: true
max-pool-prepared-statement-per-connection-size: 20
# 配置监控统计拦挡的 filters,去掉后监控界面 sql 无奈统计,'wall' 用于防火墙
filters: stat,wall,slf4j
filter:
stat:
merge-sql: true
slow-sql-millis: 5000
second:
username: root
password: 123456
url: jdbc:mysql://192.168.50.43:3306/mybatis_second?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC
driver-class-name: com.mysql.cj.jdbc.Driver
#初始化连接池的连贯数量 大小,最小,最大
initial-size: 5
min-idle: 5
max-active: 20
#配置获取连贯期待超时的工夫
max-wait: 60000
#配置距离多久才进行一次检测,检测须要敞开的闲暇连贯,单位是毫秒
time-between-eviction-runs-millis: 60000
# 配置一个连贯在池中最小生存的工夫,单位是毫秒
min-evictable-idle-time-millis: 30000
# 配置一个连贯在池中最大生存的工夫,单位是毫秒
max-evictable-idle-time-millis: 300000
validation-query: SELECT 1 FROM user
test-while-idle: true
test-on-borrow: true
test-on-return: false
# 是否缓存 preparedStatement,也就是 PSCache 官网倡议 MySQL 下倡议敞开 集体倡议如果想用 SQL 防火墙 倡议关上
pool-prepared-statements: true
max-pool-prepared-statement-per-connection-size: 20
# 配置监控统计拦挡的 filters,去掉后监控界面 sql 无奈统计,'wall' 用于防火墙
filters: stat,wall,slf4j
filter:
stat:
merge-sql: true###
slow-sql-millis: 5000
#3. 根底监控配置
web-stat-filter:
enabled: true
url-pattern: /*
#设置不统计哪些 URL
exclusions: "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*"
session-stat-enable: true
session-stat-max-count: 100
stat-view-servlet:
enabled: true
url-pattern: /druid/*
reset-enable: true
#设置监控页面的登录名和明码
login-username: admin
login-password: admin
allow: 127.0.0.1
#deny: 192.168.1.100
# 日志配置
logging:
level:
root: INFO
com:
bolingcavalry:
druidtwosource:
mapper: debug
- user 的映射配置,请留神文件地位:
<?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.bolingcavalry.druidtwosource.mapper.first.UserMapper">
<!-- 新增单条记录 -->
<insert id="insertWithFields" useGeneratedKeys="true" keyProperty="id">
insert into user (id, name, age) values (#{id}, #{name}, #{age})
</insert>
<!-- 依照名称查找 -->
<select id="findByName" parameterType="String" resultType="com.bolingcavalry.druidtwosource.entity.User">
select id, name, age from user where name like concat('%', #{name}, '%')
</select>
<!-- 删除指定数据 -->
<delete id="delete">
delete from user where id= #{id}
</delete>
</mapper>
- address 的映射配置:
<?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.bolingcavalry.druidtwosource.mapper.second.AddressMapper">
<!-- 新增单条记录 -->
<insert id="insertWithFields" useGeneratedKeys="true" keyProperty="id">
insert into address (id, city, street) values (#{id}, #{city}, #{street})
</insert>
<!-- 依照名称查找 -->
<select id="findByCityName" parameterType="String" resultType="com.bolingcavalry.druidtwosource.entity.Address">
select id, city, street from address where city like concat('%', #{cityname}, '%')
</select>
<!-- 删除指定数据 -->
<delete id="delete">
delete from address where id= #{id}
</delete>
</mapper>
- user 表的实体类,留神 swagger 用到的注解:
package com.bolingcavalry.druidtwosource.entity;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
@ApiModel(description = "用户实体类")
public class User {@ApiModelProperty(value = "用户 ID")
private Integer id;
@ApiModelProperty(value = "用户名", required = true)
private String name;
@ApiModelProperty(value = "用户地址", required = false)
private Integer age;
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
'}';
}
... 省略 get 和 set 办法
}
- address 表的实体类:
package com.bolingcavalry.druidtwosource.entity;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
@ApiModel(description = "地址实体类")
public class Address {@ApiModelProperty(value = "地址 ID")
private Integer id;
@ApiModelProperty(value = "城市名", required = true)
private String city;
@ApiModelProperty(value = "街道名", required = true)
private String street;
@Override
public String toString() {
return "Address{" +
"id=" + id +
", city='" + city + '\'' +
", street='" + street + '\'' +
'}';
}
... 省略 get 和 set 办法
}
- 启动类 DuridTwoSourceApplication.java,要留神的是 <font color=”blue”> 排除掉数据源和事务的主动拆卸 </font>,因为前面会手动编码执行这些配置:
package com.bolingcavalry.druidtwosource;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration;
@SpringBootApplication(exclude={
DataSourceAutoConfiguration.class,
DataSourceTransactionManagerAutoConfiguration.class,
})
public class DuridTwoSourceApplication {public static void main(String[] args) {SpringApplication.run(DuridTwoSourceApplication.class, args);
}
}
- swagger 配置:
package com.bolingcavalry.druidtwosource;
import springfox.documentation.service.Contact;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Tag;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
/**
* @Description: swagger 配置类
* @author: willzhao E-mail: zq2599@gmail.com
* @date: 2020/8/11 7:54
*/
@Configuration
@EnableSwagger2
public class SwaggerConfig {
@Bean
public Docket createRestApi() {return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.tags(new Tag("UserController", "用户服务"),
new Tag("AddressController", "地址服务"))
.select()
// 以后包门路
.apis(RequestHandlerSelectors.basePackage("com.bolingcavalry.druidtwosource.controller"))
.paths(PathSelectors.any())
.build();}
// 构建 api 文档的详细信息函数, 留神这里的注解援用的是哪个
private ApiInfo apiInfo() {return new ApiInfoBuilder()
// 页面题目
.title("MyBatis CURD 操作")
// 创建人
.contact(new Contact("程序员欣宸", "https://github.com/zq2599/blog_demos", "zq2599@gmail.com"))
// 版本号
.version("1.0")
// 形容
.description("API 形容")
.build();}
}
- 数据源配置 TwoDataSourceConfig.java,可见是通过 <font color=”blue”>ConfigurationProperties</font> 注解来确定配置信息,另外不要遗记在默认数据源上增加 <font color=”blue”>Primary</font> 注解:
package com.bolingcavalry.druidtwosource;
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import javax.sql.DataSource;
/**
* @Description: druid 配置类
* @author: willzhao E-mail: zq2599@gmail.com
* @date: 2020/8/18 08:12
*/
@Configuration
public class TwoDataSourceConfig {
@Primary
@Bean(name = "firstDataSource")
@ConfigurationProperties("spring.datasource.druid.first")
public DataSource first() {return DruidDataSourceBuilder.create().build();}
@Bean(name = "secondDataSource")
@ConfigurationProperties("spring.datasource.druid.second")
public DataSource second() {return DruidDataSourceBuilder.create().build();}
}
- 第一个数据源的 mybatis 配置类 DruidConfigFirst.java,能够联合本篇的第一幅图来看,留神 MapperScan 注解的两个属性 <font color=”blue”>basePackages</font> 和 <font color=”blue”>sqlSessionTemplateRef</font> 是要害,<font color=”red”> 它们最终决定了哪些 mapper 接口应用哪个数据源 </font>,另外留神 <font color=”red”> 要带上 </font>Primary 注解:
package com.bolingcavalry.druidtwosource;
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.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import javax.sql.DataSource;
/**
* @Description: druid 配置类
* @author: willzhao E-mail: zq2599@gmail.com
* @date: 2020/8/18 08:12
*/
@Configuration
@MapperScan(basePackages = "com.bolingcavalry.druidtwosource.mapper.first", sqlSessionTemplateRef = "firstSqlSessionTemplate")
public class DruidConfigFirst {@Bean(name = "firstSqlSessionFactory")
@Primary
public SqlSessionFactory sqlSessionFactory(@Qualifier("firstDataSource") DataSource dataSource) throws Exception {SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mappers/first/**/*Mapper.xml"));
return bean.getObject();}
@Bean(name = "firstTransactionManager")
@Primary
public DataSourceTransactionManager transactionManager(@Qualifier("firstDataSource") DataSource dataSource) {return new DataSourceTransactionManager(dataSource);
}
@Bean(name = "firstSqlSessionTemplate")
@Primary
public SqlSessionTemplate sqlSessionTemplate(@Qualifier("firstSqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {return new SqlSessionTemplate(sqlSessionFactory);
}
}
- 第二个数据源的 mybatis 配置 DruidConfigSecond.java,留神 <font color=”red”> 不要带 </font>Primary 注解:
package com.bolingcavalry.druidtwosource;
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.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import javax.sql.DataSource;
/**
* @Description: druid 配置类
* @author: willzhao E-mail: zq2599@gmail.com
* @date: 2020/8/18 08:12
*/
@Configuration
@MapperScan(basePackages = "com.bolingcavalry.druidtwosource.mapper.second", sqlSessionTemplateRef = "secondSqlSessionTemplate")
public class DruidConfigSecond {@Bean(name = "secondSqlSessionFactory")
public SqlSessionFactory sqlSessionFactory(@Qualifier("secondDataSource") DataSource dataSource) throws Exception {SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mappers/second/**/*Mapper.xml"));
return bean.getObject();}
@Bean(name = "secondTransactionManager")
public DataSourceTransactionManager transactionManager(@Qualifier("secondDataSource") DataSource dataSource) {return new DataSourceTransactionManager(dataSource);
}
@Bean(name = "secondSqlSessionTemplate")
public SqlSessionTemplate sqlSessionTemplate(@Qualifier("secondSqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {return new SqlSessionTemplate(sqlSessionFactory);
}
}
- user 表的 mapper 接口类很简略,只有三个接口,留神 package 地位:
package com.bolingcavalry.druidtwosource.mapper.first;
import com.bolingcavalry.druidtwosource.entity.User;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface UserMapper {int insertWithFields(User user);
List<User> findByName(String name);
int delete(int id);
}
- address 表的 Mapper 接口类:
package com.bolingcavalry.druidtwosource.mapper.second;
import com.bolingcavalry.druidtwosource.entity.Address;
import org.springframework.stereotype.Repository;
import java.util.List;
/**
* @Description: 地址实体的接口类
* @author: willzhao E-mail: zq2599@gmail.com
* @date: 2020/8/4 8:32
*/
@Repository
public interface AddressMapper {int insertWithFields(Address address);
List<Address> findByCityName(String cityName);
int delete(int id);
}
- user 表的 service 类:
package com.bolingcavalry.druidtwosource.service;
import com.bolingcavalry.druidtwosource.entity.User;
import com.bolingcavalry.druidtwosource.mapper.first.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
public class UserService {
@Autowired
UserMapper userMapper;
public User insertWithFields(User user) {userMapper.insertWithFields(user);
return user;
}
public List<User> findByName(String name) {return userMapper.findByName(name);
}
public int delete(int id) {return userMapper.delete(id);
}
}
- address 表的 service 类:
package com.bolingcavalry.druidtwosource.service;
import com.bolingcavalry.druidtwosource.entity.Address;
import com.bolingcavalry.druidtwosource.entity.User;
import com.bolingcavalry.druidtwosource.mapper.first.UserMapper;
import com.bolingcavalry.druidtwosource.mapper.second.AddressMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class AddressService {
@Autowired
AddressMapper addressMapper;
public Address insertWithFields(Address address) {addressMapper.insertWithFields(address);
return address;
}
public List<Address> findByCityName(String cityName) {return addressMapper.findByCityName(cityName);
}
public int delete(int id) {return addressMapper.delete(id);
}
}
- user 表的 controller:
package com.bolingcavalry.druidtwosource.controller;
import com.bolingcavalry.druidtwosource.entity.User;
import com.bolingcavalry.druidtwosource.service.UserService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/user")
@Api(tags = {"UserController"})
public class UserController {
@Autowired
private UserService userService;
@ApiOperation(value = "新增 user 记录", notes="新增 user 记录")
@RequestMapping(value = "/insertwithfields",method = RequestMethod.PUT)
public User create(@RequestBody User user) {return userService.insertWithFields(user);
}
@ApiOperation(value = "删除指定 ID 的 user 记录", notes="删除指定 ID 的 user 记录")
@ApiImplicitParam(name = "id", value = "用户 ID", paramType = "path", required = true, dataType = "Integer")
@RequestMapping(value = "/{id}", method = RequestMethod.DELETE)
public int delete(@PathVariable int id){return userService.delete(id);
}
@ApiOperation(value = "依据名称含糊查找所有 user 记录", notes="依据名称含糊查找所有 user 记录")
@ApiImplicitParam(name = "name", value = "用户名", paramType = "path", required = true, dataType = "String")
@RequestMapping(value = "/findbyname/{name}", method = RequestMethod.GET)
public List<User> findByName(@PathVariable("name") String name){return userService.findByName(name);
}
}
- address 表的 controller:
package com.bolingcavalry.druidtwosource.controller;
import com.bolingcavalry.druidtwosource.entity.Address;
import com.bolingcavalry.druidtwosource.service.AddressService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* @Description: user 表操作的 web 接口
* @author: willzhao E-mail: zq2599@gmail.com
* @date: 2020/8/4 8:31
*/
@RestController
@RequestMapping("/address")
@Api(tags = {"AddressController"})
public class AddressController {
@Autowired
private AddressService addressService;
@ApiOperation(value = "新增 address 记录", notes="新增 address 记录")
@RequestMapping(value = "/insertwithfields",method = RequestMethod.PUT)
public Address create(@RequestBody Address address) {return addressService.insertWithFields(address);
}
@ApiOperation(value = "删除指定 ID 的 address 记录", notes="删除指定 ID 的 address 记录")
@ApiImplicitParam(name = "id", value = "地址 ID", paramType = "path", required = true, dataType = "Integer")
@RequestMapping(value = "/{id}", method = RequestMethod.DELETE)
public int delete(@PathVariable int id){return addressService.delete(id);
}
@ApiOperation(value = "依据城市名含糊查找所 address 记录", notes="依据城市名含糊查找所 address 记录")
@ApiImplicitParam(name = "name", value = "城市名", paramType = "path", required = true, dataType = "String")
@RequestMapping(value = "/findbycityname/{cityname}", method = RequestMethod.GET)
public List<Address> findByName(@PathVariable("cityname") String cityName){return addressService.findByCityName(cityName);
}
}
- 至此,编码实现,接下来编写单元测试代码;
单元测试
- 新增配置文件 application-test.yml,其内容仅有下图红框地位与 application.yml 不同,其余的全副统一:
- user 表的测试用例如下:
package com.bolingcavalry.druidtwosource.controller;
import com.bolingcavalry.druidtwosource.entity.User;
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonParser;
import org.junit.jupiter.api.*;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import java.util.UUID;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.core.IsEqual.equalTo;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
/**
* @Description: 单元测试类
* @author: willzhao E-mail: zq2599@gmail.com
* @date: 2020/8/9 23:55
*/
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
@ActiveProfiles("test")
class UserControllerTest {
@Autowired
private MockMvc mvc;
// user 表的 name 字段,这里为了保障测试时新增和删除的记录是同一条,用 UUID 作为用户名
static String testName;
@BeforeAll
static void init() {testName = UUID.randomUUID().toString().replaceAll("-","");
}
@Test
@Order(1)
void insertWithFields() throws Exception {String jsonStr = "{\"name\": \"" + testName + "\", \"age\": 10}";
mvc.perform(MockMvcRequestBuilders.put("/user/insertwithfields")
.contentType(MediaType.APPLICATION_JSON)
.content(jsonStr)
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$.name", is(testName)))
.andDo(print())
.andReturn()
.getResponse()
.getContentAsString();}
@Test
@Order(2)
void findByName() throws Exception {mvc.perform(MockMvcRequestBuilders.get("/user/findbyname/"+ testName).accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$", hasSize(1)))
.andDo(print());
}
@Test
@Order(3)
void delete() throws Exception {
// 先依据名称查出记录
String responseString = mvc.perform(MockMvcRequestBuilders.get("/user/findbyname/"+ testName).accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$", hasSize(1)))
.andDo(print())
.andReturn()
.getResponse()
.getContentAsString();
// 反序列化失去数组
JsonArray jsonArray = JsonParser.parseString(responseString).getAsJsonArray();
// 反序列化失去 user 实例
User user = new Gson().fromJson(jsonArray.get(0), User.class);
// 执行删除
mvc.perform(MockMvcRequestBuilders.delete("/user/"+ user.getId()).accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(content().string(equalTo("1")))
.andDo(print());
}
}
- address 表的单元测试如下:
package com.bolingcavalry.druidtwosource.controller;
import com.bolingcavalry.druidtwosource.entity.Address;
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonParser;
import org.junit.jupiter.api.*;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import java.util.UUID;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.core.IsEqual.equalTo;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
@ActiveProfiles("test")
class AddrestControllerTest {
@Autowired
private MockMvc mvc;
// address 表的 cityName 字段,这里为了保障测试时新增和删除的记录是同一条,用 UUID 作为用户名
static String testCityName;
@BeforeAll
static void init() {testCityName = UUID.randomUUID().toString().replaceAll("-","");
}
@Test
@Order(1)
void insertWithFields() throws Exception {String jsonStr = "{\"city\": \"" + testCityName + "\", \"street\": \"streetName\"}";
mvc.perform(MockMvcRequestBuilders.put("/address/insertwithfields")
.contentType(MediaType.APPLICATION_JSON)
.content(jsonStr)
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$.city", is(testCityName)))
.andDo(print())
.andReturn()
.getResponse()
.getContentAsString();}
@Test
@Order(2)
void findByName() throws Exception {mvc.perform(MockMvcRequestBuilders.get("/address/findbycityname/"+ testCityName).accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$", hasSize(1)))
.andDo(print());
}
@Test
@Order(3)
void delete() throws Exception {
// 先依据名称查出记录
String responseString = mvc.perform(MockMvcRequestBuilders.get("/address/findbycityname/"+ testCityName).accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$", hasSize(1)))
.andDo(print())
.andReturn()
.getResponse()
.getContentAsString();
// 反序列化失去数组
JsonArray jsonArray = JsonParser.parseString(responseString).getAsJsonArray();
// 反序列化失去 user 实例
Address address = new Gson().fromJson(jsonArray.get(0), Address.class);
// 执行删除
mvc.perform(MockMvcRequestBuilders.delete("/address/"+ address.getId()).accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(content().string(equalTo("1")))
.andDo(print());
}
}
- 至此,编码实现,而能够开始验证了;
验证,单元测试
- user 表对应的单元测试操作如下图,三个测试方法先后新增记录,查问记录,而后删除掉:
- AddrestControllerTest 也依照上图做同样的操作;
验证,swagger
- 浏览器拜访:http://localhost:8080/swagger-ui.html,会展现 swagger 页面如下:
- 先来试试新增操作:
- 返回数据如下图:
- 以下是用 MySQL 数据库客户端工具查看到的 mybatis.user 表的数据,可见服务性能失常:
- 其余接口请自行操作验证;
进入 druid 监控页面
- druid 监控页面地址是:http://localhost:8080/druid,账号密码都是 admin:
- 登录后可见数据库操作:
- 在数据源页面能够见到两个数据源,如下图:
- 以上就是残缺的 springboot+mybatis+druid 多数据源开发和验证过程,心愿能给您一些参考;
你不孤独,欣宸原创一路相伴
- Java 系列
- Spring 系列
- Docker 系列
- kubernetes 系列
- 数据库 + 中间件系列
- DevOps 系列
欢送关注公众号:程序员欣宸
微信搜寻「程序员欣宸」,我是欣宸,期待与您一起畅游 Java 世界 …
https://github.com/zq2599/blog_demos