欢送拜访我的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: 8080spring: #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@EnableSwagger2public 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 */@Configurationpublic 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;@Repositorypublic 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 */@Repositorypublic 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;@Servicepublic 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