5 SpringBoot 整合 tk_Mybatis

SpringBoot 整合 tk_Mybatis

1 简介

  上一节进行 SpringBoot 整合 Mybatis,若想更加快速开发,则使用 tk_MybatisMybatis-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 插件生成代码,这样我们无需手动编写实体类MapperMapper.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 结果

  以上可以发现,我们并没有在 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 参考地址

https://github.com/abel533/Mapper/wiki/6.example

SpringBoot 整合 Mybatis <<                 >> 通用 JSON API

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值