SpringBoot 整合 tk_Mybatis
1 简介
上一节进行 SpringBoot 整合 Mybatis,若想更加快速开发,则使用 tk_Mybatis 或 Mybatis-Plus 等 Mybatis 的工具。以下介绍 tk_Mybatis。为了方便学习,将上一节中的实体类、 mapper 接口和 mapper.xml 先删除。
tk.mybatis 是在 MyBatis 框架的基础上提供了很多工具,让开发更加高效。
2 引入依赖
在 pom.xml 文件中引入 mapper-spring-boot-starter 依赖,该依赖会自动引入 MyBaits 相关依赖。因此可以将上一节中的 Mybatis 依赖和 application.yml 中的 mybatis 配置去掉。
添加 tk_mybatis 依赖,如下:
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper-spring-boot-starter</artifactId>
<version>2.1.4</version>
</dependency>
3 创建通用父级接口
在 java 文件下创建 tk.mybatis.mapper 包,然后创建 MyMapper 接口。主要作用是让 mapper 层的接口继承该接口,以达到使用 tk.mybatis 的目的。
特别注意的是,该接口不可被扫描到,否则会出错。而之前在 HelloSpringBootApplication 启动类中配置的扫描路径是 com.pky.hello.springboot 包下的所有文件。
因此只需将该接口所在的包和 com.pky.hello.springboot 同级即可,如图 3.1 所示:
图 3.1
3.1 MyMapper 接口
package tk.mybatis.mapper;
import tk.mybatis.mapper.common.Mapper;
import tk.mybatis.mapper.common.MySqlMapper;
/**
* 自己的 Mapper
* 特别注意,该接口不能被扫描到,否则会出错
*/
public interface MyMapper<T> extends Mapper<T>, MySqlMapper<T> {
}
3.2 HelloSpringbootApplication 启动类
package com.pky.hello.springboot;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import tk.mybatis.spring.annotation.MapperScan;
@SpringBootApplication(scanBasePackages = "com.pky.hello.springboot") //将所有基于该包名进行扫描
@MapperScan(basePackages = "com.pky.hello.springboot.commons.mapper") //指定扫描 mapper,避免扫描到 tk.mybatis.mapper 包的接口
public class HelloSpringbootApplication {
public static void main(String[] args) {
SpringApplication.run(HelloSpringbootApplication.class, args);
}
}
4 自动代码生成
4.1 介绍
在开发过程中,我们需要些很多实体类,其实我们并不需花费时间在这上面。
因此我们使用 MyBatis 的 Maven 插件生成代码,这样我们无需手动编写实体类、Mapper、Mapper.xml 配置文件,只需要使用 MyBatis 提供的一个 Maven 插件就可以自动生成所需的各种文件便能够满足基本的业务需求,如果业务比较复杂只需要修改相关文件即可。
4.2 插件
在 pom.xml 中添加插件
<plugin>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.3.5</version>
<configuration>
<configurationFile>${basedir}/src/main/resources/generator/generatorConfig.xml</configurationFile>
<overwrite>true</overwrite>
<verbose>true</verbose>
</configuration>
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.35</version>
</dependency>
<!-- 这里采用了 tk_mybatis,若不想要则去掉 -->
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper</artifactId>
<version>4.1.4</version>
</dependency>
</dependencies>
</plugin>
configurationFile:自动生成代码的配置文件路径
4.3 generatorConfig.xml 配置文件
创建自动生成代码的配置文件,在 resources 中创建 generator,并在 generator 下创建配置类 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>
<!-- 引入数据库连接配置 -->
<properties resource="jdbc.properties"/>
<context id="Mysql" targetRuntime="MyBatis3Simple" defaultModelType="flat">
<property name="beginningDelimiter" value="`"/>
<property name="endingDelimiter" value="`"/>
<!-- 配置 tk.mybatis 插件 -->
<plugin type="tk.mybatis.mapper.generator.MapperPlugin">
<property name="mappers" value="tk.mybatis.mapper.MyMapper"/>
</plugin>
<!-- 配置数据库连接 -->
<jdbcConnection
driverClass="${jdbc.driverClass}"
connectionURL="${jdbc.connectionURL}"
userId="${jdbc.username}"
password="${jdbc.password}">
</jdbcConnection>
<!-- 配置实体类存放路径 -->
<javaModelGenerator targetPackage="com.pky.springbootdemo.commons.domain" targetProject="src/main/java"/>
<!-- 配置 XML 存放路径 -->
<sqlMapGenerator targetPackage="mapper" targetProject="src/main/resources"/>
<!-- 配置 DAO 存放路径 -->
<javaClientGenerator
targetPackage="com.pky.springbootdemo.commons.mapper"
targetProject="src/main/java"
type="XMLMAPPER"/>
<!-- 配置需要指定生成的数据库和表,tb_user 是表名,可用 % 代替,表示全部表 -->
<table catalog="myshop" tableName="tb_user">
<!-- mysql 配置 -->
<generatedKey column="id" sqlStatement="Mysql" identity="true"/>
</table>
</context>
</generatorConfiguration>
注意:若该配置头中的“http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd” 报错,则点击左边红色灯泡里选项的第一项即可。
4.4 jdbc.properties 数据库连接配置
在 resources 中创建 jdbc.properties,用于 generatorConfig.xml 数据库连接的配置信息。
jdbc.driverClass=com.mysql.jdbc.Driver
jdbc.connectionURL=jdbc:mysql://localhost:3306/myshop?useUnicode=true&characterEncoding=utf-8&useSSL=false
jdbc.username=root
jdbc.password=root
4.5 自动代码生成
-
点即 IDEA 右边的 Maven Project,如图 4.1 操作双击选项
图 4.1 -
代码生成成功则会在控制台显示如图 4.2 所示信息
图 4.2 -
此时,会在相应目录下看到生成的实体类 TbUsr、Mapper 接口 TbUserMapper 和 TbUserMapper.xml,如下图 4.3 所示
图 4.3
提示:查看实体类中的@Table(name = “myshop.tb_user”)是否正确
5 tk_mybatis 的使用
既然用上了 tk_mybatis,自然要知道如何使用,如何提高开发效率。
tk_mybatis 有好几种方式:
- 普通Example方式(从and方法开始可以实现动态sql拼接)
- Criteria方式(可使用criteria完成动态sql拼接)
- Example.builder 方式(其中where从句中内容可以拿出来进行动态sql拼接)
- Example.builder + Weekend方式
下面介绍 Exxample 方式,如下
5.1 例子
Example example = new Example(CandidateBrandEntity.class);
example
//.selectProperties("cabId","cabName")
.and().andEqualTo("cabDeleted",0)
.andLike("cabName","%d%");
// 排序
example.orderBy("cabCreatedTime")
/*.desc()*/
.orderBy("cabId").desc();
// 获得结果
List<CandidateBrandEntity> brands = brandEntityMapper.selectByExample(example);
5.2 LoginServiceImpl
根据 5.1 的例子,我们完善登录的业务处理。
package com.pky.hello.springboot.commons.service;
import com.pky.hello.springboot.commons.domain.TbUser;
import com.pky.hello.springboot.commons.mapper.TbUserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;
import tk.mybatis.mapper.entity.Example;
/**
* 登录业务逻辑
*/
@Service
public class LoginServiceImpl implements LoginService {
@Autowired
TbUserMapper tbUserMapper;
/**
* 通过用户名获取登录用户信息
* @param tbUser
* @return
*/
@Override
public TbUser getByLoginId(TbUser tbUser) {
Example example = new Example(TbUser.class);
// "username" 与实体类属性对应
example.createCriteria().andEqualTo("username", tbUser.getUsername());
TbUser user = tbUserMapper.selectOneByExample(example);
boolean flag = false;
// 查询成功
if(user != null) {
// 判断密码
flag = checkPassword(user, tbUser.getPassword());
}
// 登录成功
if(flag) {
return user;
}
// 登录失败
return null;
}
/**
* 判断密码
* @param tbUser
* @param loginPwd 登录密码
* @return
*/
private Boolean checkPassword(TbUser tbUser, String loginPwd) {
// 因数据库中的密码是 md5 加密,因此需要将登录密码进行加密后比较
String password = DigestUtils.md5DigestAsHex(loginPwd.getBytes());
// 密码正确
if(tbUser.getPassword().equals(password)) {
return true;
}
return false;
}
}
5.3 LoginController
目前我们写的是 JSON API 接口,因此需要添加 JSON 依赖。
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.9.7</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.28</version>
</dependency>
在实体类上添加 @JsonInclude(JsonInclude.Include.NON_NULL) ,表示属性为 NULL 不序列化
并且还要做参数的数据校验,因此添加数据校验的依赖以及工具类。
- 依赖
<!-- 数据校验 -->
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
</dependency>
在实体类属性上添加校验规则,如下:
/**
* 用户名
*/
@NotNull(message = "用户名不可为空")
private String username;
/**
* 密码,加密存储
*/
@NotNull(message = "密码不可为空")
@JsonIgnore // 密码不进行序列化,即不返回修饰的属性
private String password;
- 数据校验工具类,在 commons 包下创建 utils 包,再在 utils 包下创建数据校验工具类 BeanValidator
package com.pky.hello.springboot.commons.utils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.Validator;
import java.util.*;
/**
* JSR303 Validator(Hibernate Validator)工具类.
* <p>
* ConstraintViolation 中包含 propertyPath, message 和 invalidValue 等信息.
* 提供了各种 convert 方法,适合不同的 i18n 需求:
* 1. List<String>, String 内容为 message
* 2. List<String>, String 内容为 propertyPath + separator + message
* 3. Map<propertyPath, message>
* <p>
* 详情见wiki: https://github.com/springside/springside4/wiki/HibernateValidator
*
* <p>Title: BeanValidator</p>
* <p>Description: </p>
*
* @author PKY
* @version 1.0.0
* @date 2018/5/26 17:21
*/
@Component
public class BeanValidator {
@Autowired
private Validator validatorInstance;
private static Validator validator;
@PostConstruct //Spring 启动时,直接执行修饰的方法
public void init() {
BeanValidator.validator = validatorInstance;
}
/**
* 调用 JSR303 的 validate 方法, 验证失败时抛出 ConstraintViolationException.
*/
private static void validateWithException(Validator validator, Object object, Class<?>... groups) throws ConstraintViolationException {
Set constraintViolations = validator.validate(object, groups);
if (!constraintViolations.isEmpty()) {
throw new ConstraintViolationException(constraintViolations);
}
}
/**
* 辅助方法, 转换 ConstraintViolationException 中的 Set<ConstraintViolations> 中为 List<message>.
*/
private static List<String> extractMessage(ConstraintViolationException e) {
return extractMessage(e.getConstraintViolations());
}
/**
* 辅助方法, 转换 Set<ConstraintViolation> 为 List<message>
*/
private static List<String> extractMessage(Set<? extends ConstraintViolation> constraintViolations) {
List<String> errorMessages = new ArrayList<>();
for (ConstraintViolation violation : constraintViolations) {
errorMessages.add(violation.getMessage());
}
return errorMessages;
}
/**
* 辅助方法, 转换 ConstraintViolationException 中的 Set<ConstraintViolations> 为 Map<property, message>.
*/
private static Map<String, String> extractPropertyAndMessage(ConstraintViolationException e) {
return extractPropertyAndMessage(e.getConstraintViolations());
}
/**
* 辅助方法, 转换 Set<ConstraintViolation> 为 Map<property, message>.
*/
private static Map<String, String> extractPropertyAndMessage(Set<? extends ConstraintViolation> constraintViolations) {
Map<String, String> errorMessages = new HashMap<>();
for (ConstraintViolation violation : constraintViolations) {
errorMessages.put(violation.getPropertyPath().toString(), violation.getMessage());
}
return errorMessages;
}
/**
* 辅助方法, 转换 ConstraintViolationException 中的 Set<ConstraintViolations> 为 List<propertyPath message>.
*/
private static List<String> extractPropertyAndMessageAsList(ConstraintViolationException e) {
return extractPropertyAndMessageAsList(e.getConstraintViolations(), " ");
}
/**
* 辅助方法, 转换 Set<ConstraintViolations> 为 List<propertyPath message>.
*/
private static List<String> extractPropertyAndMessageAsList(Set<? extends ConstraintViolation> constraintViolations) {
return extractPropertyAndMessageAsList(constraintViolations, " ");
}
/**
* 辅助方法, 转换 ConstraintViolationException 中的 Set<ConstraintViolations> 为 List<propertyPath + separator + message>.
*/
private static List<String> extractPropertyAndMessageAsList(ConstraintViolationException e, String separator) {
return extractPropertyAndMessageAsList(e.getConstraintViolations(), separator);
}
/**
* 辅助方法, 转换 Set<ConstraintViolation> 为 List<propertyPath + separator + message>.
*/
private static List<String> extractPropertyAndMessageAsList(Set<? extends ConstraintViolation> constraintViolations, String separator) {
List<String> errorMessages = new ArrayList<>();
for (ConstraintViolation violation : constraintViolations) {
errorMessages.add(violation.getPropertyPath() + separator + violation.getMessage());
}
return errorMessages;
}
/**
* 服务端参数有效性验证
*
* @param object 验证的实体对象
* @param groups 验证组
* @return 验证成功:返回 null;验证失败:返回错误信息
*/
public static String validator(Object object, Class<?>... groups) {
try {
validateWithException(validator, object, groups);
} catch (ConstraintViolationException ex) {
List<String> list = extractMessage(ex);
list.add(0, "数据验证失败:");
// 封装错误消息为字符串
StringBuilder sb = new StringBuilder();
for (int i = 0; i < list.size(); i++) {
String exMsg = list.get(i);
if (i != 0) {
sb.append(String.format("%s. %s", i, exMsg)).append(list.size() > 1 ? "<br/>" : "");
} else {
sb.append(exMsg).append(list.size() > 1 ? "<br/>" : "");
}
}
return sb.toString();
}
return null;
}
}
- Controller
package com.pky.hello.springboot.controller;
import com.alibaba.fastjson.JSONObject;
import com.pky.hello.springboot.commons.domain.TbUser;
import com.pky.hello.springboot.commons.service.LoginService;
import com.pky.hello.springboot.commons.utils.BeanValidator;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping(value = "users")
public class LoginController {
@Autowired
LoginService loginService;
@GetMapping(value = "login")
public JSONObject login(TbUser tbUser) {
JSONObject jsonObject = new JSONObject();
// 数据校验
String message = BeanValidator.validator(tbUser);
if(StringUtils.isNotBlank(message)) {
jsonObject.put("msg", message);
return jsonObject;
}
// 登录校验
TbUser user = loginService.getByLoginId(tbUser);
// 登录成功
if(user != null) {
jsonObject.put("msg", "登录成功!");
jsonObject.put("tb_user", user);
}
// 登录失败
else {
jsonObject.put("msg", "用户名或密码错误!");
}
return jsonObject;
}
}
6 结果
-
成功结果,浏览器输入: http://localhost:8082/v1/hello_springboot/users/login?username=zhangsan&password=123456 ,如图 6.1 所示
图 6.1 -
数据校验结果,浏览器输入: http://localhost:8082/v1/hello_springboot/users/login ,如图 6.2 所示
图 6.2 -
账号密码错误,浏览器输入: http://localhost:8082/v1/hello_springboot/users/login?username=ceshi&password=123
图 6.3
以上可以发现,我们并没有在 mapper 接口和 mapper.xml 中写相应的代码就可从数据库中获取数据。因为tk_mybatis 已经将 sql 封装好了,我们只需调用其相应 的方法即可。
7 附 tk_mybatis 的几种用法
7.1 Criteria方式(可使用criteria完成动态sql拼接)
- 从代码语义来理解,先创建需要查询的实例
Example example = new Example(TbUser.class);
example.createCriteria().andEqualTo("username","zhangsan");
TbUser tbUser = tbUserMapper.selectOneByExample(example);
System.out.println(tbUser.getId());
- 模糊查询及排序
Example example = new Example(TbUser.class);
example.createCriteria().andLike("username","%zhang%");
example.orderBy("created").desc();
List<TbUser> tbUsers = tbUserMapper.selectByExample(example);
tbUsers.forEach(tbUser -> System.out.println(tbUser.getUsername()));
- 自定义 mapper,如多表联查,和之前写 MyBatis 时一样,在xxxMapper.xml 中
<select id="selectById" resultType="com.pky.spring.boot.login.domain.TbItemDesc">
SELECT
b.item_desc AS 'itemDesc'
FROM
tb_item AS a
LEFT JOIN tb_item_desc AS b
ON a.id = b.item_id
WHERE a.id = #{id}
</select>
注意:自定义的话还需要在 mapper 对应的接口上写对应的注解代码,如下:
package com.pky.springbootdemo.commons.mapper;
import com.pky.springbootdemo.commons.domain.TbUser;
import org.apache.ibatis.annotations.Select;
import org.springframework.stereotype.Repository;
import tk.mybatis.mapper.MyMapper;
@Repository
public interface TbUserMapper extends MyMapper<TbUser> {
/**
* 自定义的需要写相应的注解,并且括号内容和 XML 的一致
* @param username
* @return
*/
@Select("Select * from tb_user where username = #{username}")
TbUser getByLoginId(String username);
}
7.2 Example.builder 方式(其中where从句中内容可以拿出来进行动态sql拼接)
Example example = Example.builder(MybatisDemo.class)
.select("cabId","cabName")
.where(Sqls.custom().andEqualTo("count", 0)
.andLike("name", "%d%"))
.orderByDesc("count","name")
.build();
List<MybatisDemo> demos = mybatisDemoMapper.selectByExample(example);
7.3 Example.builder + Weekend方式
- 优势:不用输入属性名,避免数据库有变动或输入错误就会出错
//获得seekendsql
WeekendSqls<MybatisDemo> sqls = WeekendSqls.<MybatisDemo>custom();
//可进行动态sql拼接
sqls = sqls.andEqualTo(MybatisDemo::getCount,0).andLike(MybatisDemo::getName,"%d%");
//获得结果
List<MybatisDemo> demos = mybatisDemoMapper.selectByExample(Example.builder(MybatisDemo.class).where(sqls).orderByDesc("count","name").build());
7.4 参考地址