在前三篇文章中,我们分别介绍了需求、设计、以及测试管理的实现功能,本篇我们一起来实现多数据源和业务持久层开发。
├─logs
│ └─spring-boot-logback # 日志文件
│ all_api-test-logback.log # 所有日志
│ err_api-test-logback.log # 错误日志
├─src
│ ├─main
│ │ ├─java
│ │ │ └─com
│ │ │ └─zuozewei
│ │ │ └─springbootdatadrivendemo
│ │ │ │ SpringbootDataDrivenDemoApplication.java # 启动类
│ │ │ │
│ │ │ ├─db
│ │ │ │ ├─auto # 存放MyBatis Generator生成器生成的数据层代码,可以随时删除再生成
│ │ │ │ │ ├─mapper # DAO 接口
│ │ │ │ │ └─model # Entity 实体
│ │ │ │ └─manual # 存放自定义的数据层代码,包括对MyBatis Generator自动生成代码的扩展
│ │ │ │ ├─mapper # DAO 接口
│ │ │ │ └─model # Entity 实体
│ │ │ ├─handler # 数据转换
│ │ │ └─service # 业务逻辑
│ │ │ └─impl # 实现类
│ │ │
│ │ └─resources
│ │ │ application.yml # 全局配置文件
│ │ │ generatorConfig.xml # Mybatis Generator 配置文件
│ │ │ logback-spring.xml # logback 配置文件
│ │ │ spy.properties # P6Spy 配置文件
│ │ │
│ │ ├─db
│ │ ├─mapper
│ │ │ └─com
│ │ │ └─zuozewei
│ │ │ └─springbootdatadrivendemo
│ │ │ └─db
│ │ │ ├─auto # 存放MyBatis Generator生成器生成的数据层代码,可以随时删除再生成
│ │ │ │ └─mapper # 数据库 Mapping 文件
│ │ │ │
│ │ │ └─manual # 存放自定义的数据层代码,包括对MyBatis Generator自动生成代码的扩展
│ │ │ └─mapper # 数据库 Mapping 文件
│ │ └─testng
│ │ │ APICollection-TestSuite.xml # 所用测试用例集
│ │ └─jdbcbapi
│ │ jdbcAPI-TestSuite.xml # 某API测试用例集
│ │
│ └─test
│ └─java
│ └─com
│ └─zuozewei
│ └─springbootdatadrivendemo
│ └─demo # 接口测试用例
├─pom.xml
上面介绍了我们的用例管理使用的是 MySQL 数据库,而本文我们演示的是业务数据库使用的是 H2,那么必然的我们需要面对处理多数据源的问题。而在需求篇我们分析过了传统方式,确实可用,不足在于需要根据不同数据源建立不同的 package,一旦数据源发生变更,需要更改所在的 package。也看过了动态数据源,不能满足我们多数据源的需求。
而经过查找,我们发现一个开源的项目,即 dynamic-datasource-spring-boot-starter
可以满足我们的需求。
dynamic-datasource-spring-boot-starter 是一个基于 springboot 的快速集成多数据源的启动器。其支持 Jdk 1.7+, SpringBoot 1.4.x 1.5.x 2.0.x。 特性:
约定:
常见示例:
# 多主多从 纯粹多库(记得设置primary) 混合配置
spring: spring: spring:
datasource: datasource: datasource:
dynamic: dynamic: dynamic:
datasource: datasource: datasource:
master_1: mysql: master:
master_2: oracle: slave_1:
slave_1: sqlserver: slave_2:
slave_2: postgresql: oracle_1:
slave_3: h2: oracle_2:
我们这里使用需要注意点有:
引包:
<!--快速集成多数据源的启动器-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<version>2.5.6</version>
</dependency>
配置 application.yml
文件:
spring:
datasource:
dynamic:
primary: mysql # 设置默认的数据源或者数据源组,默认值即为 master
strict: false # 设置严格模式,默认 false 不启动. 启动后在未匹配到指定数据源时候回抛出异常,不启动会使用默认数据源.
datasource:
mysql_1: # 测试用例库
username: zuozewei
password: zuozewei
url: jdbc:mysql://172.16.106.188:3306/autotest?useUnicode=true&characterEncoding=UTF-8&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC&useSSL=false
driver-class-name: com.mysql.cj.jdbc.Driver
continue-on-error: true # 默认true,初始化失败是否继续
separator: ";" # sql 默认分号分隔符
mysql_2: # 业务数据库
username: zuozewei
password: zuozewei
url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC&useSSL=false
driver-class-name: com.mysql.cj.jdbc.Driver
continue-on-error: true # 默认true,初始化失败是否继续
separator: ";" # sql 默认分号分隔符
h2: # 业务数据库
username: sa
password: ""
url: jdbc:h2:mem:test
driver-class-name: org.h2.Driver
schema: db/schema.sql # 配置则生效,自动初始化表结构
data: db/data.sql # 配置则生效,自动初始化数据
continue-on-error: true # 默认true,初始化失败是否继续
separator: ";" # sql 默认分号分隔符
druid: # 这里可以重写默认值
initial-size: 5
public-key: MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKUVA/IL/iON8f63bv2i/pIAK+1sXY228slLkTKrI9axwBMIoPV7+PqdRTv6uqMl3j6nei0EDBWEu/Wp/qOQ/ScCAwEAAQ==
使用 @DS
切换数据源。建议只注解在 service 实现类上。比如我们指定取用例 service 方法的数据源:
**
* 描述: 参数化自定义查询实现类
*
* @author zuozewei
* @create 2019-11-21 16:04
*/
@Service
public class TestDataServiceImpl implements TestDataService {
@Resource
private TestDataMapper testDataMapper;
@DS("mysql_1")
@Override
public List<LinkedHashMap<String, Object>> selectBysql(String sql) {
return testDataMapper.selectBysql(sql);
}
}
启动的时候,我们可以看到各数据源已初始化成功。
业务持久层的框架,我们依旧选择统一使用 Mybatis。在使用 Mybatis 的时候,Dao 接口,Entity 实体类,还有每个实体类对应的 xml 都得自己写,这其实也是工作量很大的事情,维护起来也很费劲,使用我们这里选用的是mybatis-generator
或 mybatis-generator-gui
来快速生成 Mybatis 的Java POJO文件及数据库 Mapping 文件。
这里我们演示的业务数据库是 H2,数据库的表结构脚本 schema.sql:
drop table t_coffee if exists;
create table t_coffee (
id bigint not null auto_increment,
name varchar(255),
price bigint not null,
create_time timestamp,
update_time timestamp,
primary key (id)
);
初始化数据脚本 data.sql:
insert into t_coffee (name, price, create_time, update_time) values ('espresso', 2000, now(), now());
insert into t_coffee (name, price, create_time, update_time) values ('latte', 2500, now(), now());
insert into t_coffee (name, price, create_time, update_time) values ('capuccino', 2500, now(), now());
insert into t_coffee (name, price, create_time, update_time) values ('mocha', 3000, now(), now());
insert into t_coffee (name, price, create_time, update_time) values ('macchiato', 3000, now(), now());
这两个SQL文件都放到 src\main\resources\db
下。
这里的 price 我们扩展了自定义类型,所以我们需要使用 TypeHandler 解决自定义类型预处理。因为 price 是joda-money
类型,数据库中却是 bigint 类型。MyBatis 为我们提供的方法即是 TypeHandler 来应对 Java 和 jdbc 字段类型不匹配的情况。MyBatis 中内置了不少的TypeHandler,如果我们想要自己自定义一个 TypeHandler 可以实现 TypeHandler 接口,也可以继承 BaseTypeHandler 类。下面我们实现一个将 Java 中的 joda-money
类型利用我们自定义的 MoneyTypeHandler 来转换为 JDBC 的 bigint 类型。
引包:
!--money类型-->
<dependency>
<groupId>org.joda</groupId>
<artifactId>joda-money</artifactId>
<version>LATEST</version>
</dependency>
新建一个 handler package,编写 MoneyTypeHandler.java
:
/**
* 在 Money 与 Long 之间转换的 TypeHandler,处理 CNY 人民币
*/
public class MoneyTypeHandler extends BaseTypeHandler<Money> {
/**
* 设置非空参数
* @param ps
* @param i
* @param parameter
* @param jdbcType
* @throws SQLException
*/
@Override
public void setNonNullParameter(PreparedStatement ps, int i, Money parameter, JdbcType jdbcType) throws SQLException {
ps.setLong(i, parameter.getAmountMinorLong());
}
/**
* 根据列名,获取可以为空的结果
* @param rs
* @param columnName
* @return
* @throws SQLException
*/
@Override
public Money getNullableResult(ResultSet rs, String columnName) throws SQLException {
return parseMoney(rs.getLong(columnName));
}
/**
* 根据列索引,获取可以为空的结果
* @param rs
* @param columnIndex
* @return
* @throws SQLException
*/
@Override
public Money getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
return parseMoney(rs.getLong(columnIndex));
}
/**
*
* @param cs
* @param columnIndex
* @return
* @throws SQLException
*/
@Override
public Money getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
return parseMoney(cs.getLong(columnIndex));
}
/**
* 处理 CNY 人民币
* @param value
* @return
*/
private Money parseMoney(Long value) {
return Money.of(CurrencyUnit.of("CNY"), value / 100.0);
}
}
MyBatis Generator是 MyBatis 的代码生成器,支持为 MyBatis 的所有版本生成代码。非常容易及快速生成 Mybatis 的Java POJO文件及数据库 Mapping 文件。
引包:
<!--mybatis-generator生成器-->
<dependency>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-core</artifactId>
<version>1.3.7</version>
</dependency>
配置 generatorConfig.xml
:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
<context id="H2Tables" targetRuntime="MyBatis3">
<!--支持流式 fluent 方法-->
<plugin type="org.mybatis.generator.plugins.FluentBuilderMethodsPlugin" />
<!-- 自动生成toString方法 -->
<plugin type="org.mybatis.generator.plugins.ToStringPlugin" />
<!-- 自动生成hashcode方法 -->
<plugin type="org.mybatis.generator.plugins.SerializablePlugin" />
<!-- 分页插件 -->
<plugin type="org.mybatis.generator.plugins.RowBoundsPlugin" />
<!--数据库连接信息-->
<jdbcConnection driverClass="org.h2.Driver"
connectionURL="jdbc:h2:mem:test"
userId="sa"
password="">
</jdbcConnection>
<!--模型生成器、Mapper生成器-->
<javaModelGenerator targetPackage="com.zuozewei.springbootdatadrivendemo.db.auto.model"
targetProject="./src/main/java">
<property name="enableSubPackages" value="true" />
<property name="trimStrings" value="true" />
</javaModelGenerator>
<sqlMapGenerator targetPackage="com.zuozewei.springbootdatadrivendemo.db.auto.mapper"
targetProject="./src/main/resources/mapper">
<property name="enableSubPackages" value="true" />
</sqlMapGenerator>
<javaClientGenerator type="MIXEDMAPPER"
targetPackage="com.zuozewei.springbootdatadrivendemo.db.auto.mapper"
targetProject="./src/main/java">
<property name="enableSubPackages" value="true" />
</javaClientGenerator>
<!--表映射-->
<table tableName="t_coffee" domainObjectName="Coffee" >
<generatedKey column="id" sqlStatement="CALL IDENTITY()" identity="true" />
<columnOverride column="price" javaType="org.joda.money.Money" jdbcType="BIGINT"
typeHandler="com.zuozewei.springbootdatadrivendemo.handler.MoneyTypeHandler"/>
</table>
</context>
</generatorConfiguration>
注意:
在工程启动类编写一个调用方法:
@SpringBootApplication(exclude = DruidDataSourceAutoConfigure.class)
@Slf4j
@MapperScan("com.zuozewei.springbootdatadrivendemo.db")
public class SpringbootDataDrivenDemoApplication implements ApplicationRunner {
public static void main(String[] args) {
SpringApplication.run(SpringbootDataDrivenDemoApplication.class, args);
log.info("程序启动!");
}
@Override
public void run(ApplicationArguments args) throws Exception {
generateArtifacts();
log.info("启动generateArtifacts");
}
/**
* 执行MyBatisGenerator
* @throws Exception
*/
private void generateArtifacts() throws Exception {
List<String> warnings = new ArrayList<>();
ConfigurationParser cp = new ConfigurationParser(warnings);
Configuration config = cp.parseConfiguration(
this.getClass().getResourceAsStream("/generatorConfig.xml"));
DefaultShellCallback callback = new DefaultShellCallback(true);
MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config, callback, warnings);
myBatisGenerator.generate(null);
}
}
启动工程:
检查配置文件指定路径是否生成文件:
还有一种简易的方法生成 Mybatis 的 Java POJO 文件及数据库 Mapping 文件,即使用 mybatis-generator-gui
工具。
mybatis-generator-gui 是基于 mybatis generator 开发一款界面工具, 本工具可以使你非常容易及快速生成 Mybatis 的 Java POJO 文件及数据库 Mapping 文件。 核心特性:
要求:由于使用了 Java 8 的众多特性,所以要求 JDK 1.8.0.60 以上版本,另外 JDK 1.9 暂时还不支持。
git clone https://github.com/zouzg/mybatis-generator-gui
cd mybatis-generator-gui
mvn jfx:jar
cd target/jfx/app/
java -jar mybatis-generator-gui.jar
由于目前不支持 H2 数据库,故使用示意图代替:
在 service package 下新建 Service 接口 CoffeeService.java
:
/**
* 描述: coffee Service
*
* @author zuozewei
* @create 2019-11-21 18:00
*/
public interface CoffeeService {
// 插入
int addCoffee(Coffee coffee);
// 查询
List selectCoffeeFromDs(CoffeeExample coffeeExample) throws InterruptedException;
}
实现 CoffeeService 接口,并使用注解指定 h2 数据源,新建 CoffeeServiceImpl.java
:
/**
* 描述: CoffeeService 实现类
*
* @author zuozewei
* @create 2019-11-21 18:00
*/
@Service
@DS("h2")
public class CoffeeServiceImpl implements CoffeeService {
@Resource
private CoffeeMapper coffeeMapper;
@Override
public int addCoffee(Coffee coffee) {
return coffeeMapper.insert(coffee);
}
@Override
public List selectCoffeeFromDs(CoffeeExample coffeeExample) throws InterruptedException {
return coffeeMapper.selectByExample(coffeeExample);
}
}
在 application.yml
中配置 mybatis
mybatis:
type-aliases-package: com.zuozewei.db # 自动扫描实体类所在的包
type-handlers-package: com.zuozewei.handler # 指定 TypeHandler 所在的包
configuration:
map-underscore-to-camel-case: true # 开启驼峰功能
call-setters-on-nulls: true # 调用setter null,返回空也必须设置到bean中 (直接执行sql专用)
mapper-locations: classpath*:/mapper/**/*.xml # 扫描类路径下所有以xml文件结尾的文件
最后,多数据源和业务持久层的工程结构大概是以下的样子:
在今天这篇文章中,主要和大家分享了实现注解式多数据源和业务持久层开发的过程。在实现过程中,你最需要关注的几部分内容是:
MybatisGenerator
或者 MybatisGeneratorGUI
自动化生成持久层代码,节省大量重复开发工作;希望对你能有所启发。