springboot集成mybatis详细教程同时实现自定义拦截器分页功能

springboot-Mybatis基础查询

1.创建数据库

使用mysql-8.0.11版本的数据库,运行CREATE DATABASE 数据库名创建数据库用于测试,这里我取名为TEST_MS,然后创建数据表,这里假设我们要对用户进行分页查询。

USE TEST_MS;
CREATE TABLE USER(
  ID INT PRIMARY KEY AUTO_INCREMENT,
  USERNAME VARCHAR(30) NOT NULL UNIQUE,
  PASSWORD VARCHAR(100) NOT NULL,
  U_NAME VARCHAR(30) NOT NULL,
  PHONE VARCHAR(11),
  EMAIL VARCHAR(50),
  CITY_ID INT,
  CITY_NAME VARCHAR(15),
  PROVIENCE_ID INT,
  PROVIENCE_NAME VARCHAR(10),
  ADDRESS VARCHAR(30),
  PHOTO VARCHAR(30),
  BIRTHDAY DATE,
  ORGANIZATION_ID INT,
  ROLE_ID INT
);

2.springboot引入mybatis

创建一个maven项目,引入springbootspringboot-mybatis依赖。
pom.xml

	<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.2.RELEASE</version>
    </parent>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.0.1</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.11</version>
        </dependency>
    </dependencies>

添加springboot配置文件,这里为了简单,不使用连接池。
application.yml

server:
    port: 8001
spring:
    application:
      name: user-service
    datasource:
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://localhost:3306/TEST_MS?serverTimezone=UTC
        username: 用户名
        password: 密码
mybatis:
  typeAliasesPackage: com.feng.entity
  mapperLocations: classpath:mybatis-mapper/*.xml

其中typeAliasesPackage指定用于映射数据的pojo位置,mapperLocations用于指定映射dao层的mapper文件,如果使用全注解方式开发的话可以省略。

3.编写查询映射

先创建一个用于映射数据的pojo:User.java

package com.feng.entity;

import javax.validation.constraints.NotBlank;

/**
 * Created by Feng
 */
public class User {
    private Integer id;
    
	@NotBlank(message = "用户名不能为空")
    private String userName;
    
    @NotBlank(message = "密码不能为空")
    private String passWord;

    private String uName;

    private String phone;

    private String email;

    private String cityID;

    private String cityName;

    private String provienceID;

    private String provienceName;

    private String address;

    private String  organizationID;

    private String roleID;

    private String photo;

    private String birthday;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getPassWord() {
        return passWord;
    }

    public void setPassWord(String passWord) {
        this.passWord = passWord;
    }

    public String getuName() {
        return uName;
    }

    public void setuName(String uName) {
        this.uName = uName;
    }

    public String getPhone() {
        return phone;
    }

    public void setPhone(String phone) {
        this.phone = phone;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String getCityID() {
        return cityID;
    }

    public void setCityID(String cityID) {
        this.cityID = cityID;
    }

    public String getCityName() {
        return cityName;
    }

    public void setCityName(String cityName) {
        this.cityName = cityName;
    }

    public String getProvienceID() {
        return provienceID;
    }

    public void setProvienceID(String provienceID) {
        this.provienceID = provienceID;
    }

    public String getProvienceName() {
        return provienceName;
    }

    public void setProvienceName(String provienceName) {
        this.provienceName = provienceName;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public String getOrganizationID() {
        return organizationID;
    }

    public void setOrganizationID(String organizationID) {
        this.organizationID = organizationID;
    }

    public String getRoleID() {
        return roleID;
    }

    public void setRoleID(String roleID) {
        this.roleID = roleID;
    }

    public String getPhoto() {
        return photo;
    }

    public void setPhoto(String photo) {
        this.photo = photo;
    }

    public String getBirthday() {
        return birthday;
    }

    public void setBirthday(String birthday) {
        this.birthday = birthday;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", userName='" + userName + '\'' +
                ", passWord='" + passWord + '\'' +
                ", uName='" + uName + '\'' +
                ", phone='" + phone + '\'' +
                ", email='" + email + '\'' +
                ", cityID='" + cityID + '\'' +
                ", cityName='" + cityName + '\'' +
                ", provienceID='" + provienceID + '\'' +
                ", provienceName='" + provienceName + '\'' +
                ", address='" + address + '\'' +
                ", organizationID='" + organizationID + '\'' +
                ", roleID='" + roleID + '\'' +
                ", photo='" + photo + '\'' +
                ", birthday='" + birthday + '\'' +
                '}';
    }
}

创建用于查询的UserDao和用于分页的Pager

package com.feng.dao;

import com.feng.entity.User;
import database.support.Pager;
import org.apache.ibatis.annotations.Param;

import java.util.List;

/**
 * Created by Feng
 */
public interface UserDao {
    List<User> getUser(@Param("user") User user);

    List<User> getUser(@Param("pager") Pager pager, @Param("user") User user);

    User loadUserByUserName(String userName);

    int addUser(User user);

    int addUsers(List<User> userList);
}

package database.support;

import javax.validation.constraints.NotNull;

/**
 * Created by Feng
 */
public class Pager {
    @NotNull
    private int pageNo;

    private int pageSize = 10;

    private int total;

    private String sortBy;

    private String rank = "DESC";

    public int getPageNo() {
        return pageNo;
    }

    public int getPageSize() {
        return pageSize;
    }

    public int getTotal() {
        return total;
    }

    public String getSortBy() {
        return sortBy;
    }

    public String getRank() {
        return rank;
    }

    public Pager setPageNo(int pageNo) throws IllegalAccessException {
        if(pageNo <= 0){
            throw new IllegalAccessException("分页页码必须大于0");
        }
        this.pageNo = pageNo;
        return this;
    }

    public Pager setPageSize(int pageSize) throws IllegalAccessException {
        if(pageSize <= 0){
            throw new IllegalAccessException("单页数据量必须大于0");
        }
        this.pageSize = pageSize;
        return this;
    }

    public Pager setTotal(int total) {
        this.total = total;
        return this;
    }

    public Pager setSortBy(String sortBy) {
        this.sortBy = sortBy;
        return this;
    }

    public Pager setRank(String rank) {
        this.rank = rank;
        return this;
    }
}

我们会用到UserDao中重载过的两个getUser方法,其中一个只接受用于查询的User参数,另一个额外接受一个Pager对象用于分页。
然后在spring配置文件中指定的包内编写用于映射UserDao的mybatis映射配置文件UserMapper.xml

<?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.feng.dao.UserDao">
    <resultMap id="UserMap" type="com.feng.entity.User">
        <id column="ID" property="id" jdbcType="INTEGER"></id>
        <result column="USERNAME" property="userName" jdbcType="VARCHAR"></result>
        <result column="U_NAME" property="uName" jdbcType="VARCHAR"></result>
        <result column="PHONE" property="phone" jdbcType="VARCHAR"></result>
        <result column="EMAIL" property="email" jdbcType="VARCHAR"></result>
        <result column="CITY_ID" property="cityID" jdbcType="INTEGER"></result>
        <result column="CITY_NAME" property="cityName" jdbcType="INTEGER"></result>
        <result column="PROVIENCE_ID" property="provienceID" jdbcType="INTEGER"></result>
        <result column="PROVIENCE_NAME" property="provienceName" jdbcType="INTEGER"></result>
        <result column="ADDRESS" property="address" jdbcType="INTEGER"></result>
        <result column="PHOTO" property="photo" jdbcType="VARCHAR"></result>
        <result column="BIRTHDAY" property="birthday" jdbcType="DATE"></result>
        <result column="ORGANIZATION_ID" property="organizationID" jdbcType="INTEGER"></result>
        <result column="ROLE_ID" property="roleID" jdbcType="INTEGER"></result>
    </resultMap>
    <sql id="userInfo">
        ID,USERNAME,U_NAME,PHONE,EMAIL,CITY_ID,CITY_NAME,PROVIENCE_ID,PROVIENCE_NAME,ADDRESS,PHOTO,BIRTHDAY,ORGANIZATION_ID,ROLE_ID
    </sql>
    <sql id="cUserInfo">
        <include refid="userInfo"/>,PASSWORD
    </sql>
    <select id="loadUserByUserName" resultMap="UserMap" parameterType="java.lang.String">
        SELECT
        <include refid="cUserInfo"/>
        FROM USER
        WHERE USERNAME = #{userName}
    </select>
    <select id="getUser" resultMap="UserMap" parameterType="com.feng.entity.User">
        SELECT
        <include refid="userInfo"/>
        FROM USER
        <where>
            <if test="user.id != null">
                ID = #{id}
            </if>
            <if test="user.userName != null">
                AND USERNAME = #{userName}
            </if>
            <if test="user.uName != null">
                AND U_NAME LIKE #{uName}
            </if>
            <if test="user.phone != null">
                AND PHONE = #{phone}
            </if>
            <if test="user.email != null">
                AND EMAIL = #{email}
            </if>
            <if test="user.cityID != null">
                AND CITY_ID = #{cityID}
            </if>
            <if test="user.provienceID != null">
                AND PROVIENCE_ID = #{provienceID}
            </if>
            <if test="user.address != null">
                AND ADDRESS LIKE #{address}
            </if>
            <if test="user.organizationID != null">
                AND ORGANIZATION_ID = #{organizationID}
            </if>
            <if test="user.roleID != null">
                AND ROLE_ID = #{roleID}
            </if>
        </where>
    </select>
    <insert id="addUser" parameterType="com.feng.entity.User" >
        insert into USER (ID,USERNAME,PASSWORD,U_NAME,PHONE,EMAIL,CITY_ID,CITY_NAME,PROVIENCE_ID,PROVIENCE_NAME,ADDRESS,PHOTO,BIRTHDAY,ORGANIZATION_ID,ROLE_ID)
        values (#{id},
                #{userName},
                #{passWord},
                #{uName},
                #{phone},
                #{email},
                #{cityID},
                #{cityName},
                #{provienceID},
                #{provienceName},
                #{address},
                #{photo},
                #{birthday},
                #{organizationID},
                #{roleID})
    </insert>
</mapper>

这里需要说明的是我们在UserDao中有两个getUser方法,但在配置文件中只映射了一个select标签,这是因为mybatis和dao层之间的映射只与方法名称有关,而与方法参数无关。所以在sql语句不变,只是参数增加的情况下我们可以用一个select标签映射多个方法。唯一要注意的是传入多个对象参数时要在dao层中使@Param注解为参数指定名称。
然后添加一个mybatis配置类MybatisConfig

package com.feng.config;

import database.mybatis.plug.PageInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.mybatis.spring.boot.autoconfigure.ConfigurationCustomizer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * Created by Feng
 */
@MapperScan("com.feng.dao")
@Configuration
public class MybatisConfig {

}

使用MapperScan用于指定dao层位置。
这个时候我们已经可以通过注入的方式使用UserDao来查询用户数据了,但为了规范,我们在dao层之上增加一个service层,编写UserService接口和实现类

package com.feng.service;

import com.feng.entity.User;
import database.support.Pager;

import java.util.List;

/**
 * Created by Feng
 */
public interface UserService {
    public User getUserByID(int id);

    public int removeUserByID(int id);

    public User loadUserByUserName(String userName);

    public List<User> getUser(Pager pager,User user);

    public List<User> getUser(User user);

}

package com.feng.service;

import com.feng.dao.UserDao;
import com.feng.entity.User;
import database.support.Pager;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.List;

/**
 * Created by Feng
 */
@Service
public class UserServiceImpl implements UserService {

    @Resource
    private UserDao userDao;

    @Override
    public User getUserByID(int id) {
        User queryUser = new User();
        queryUser.setId(id);
        List<User> users = userDao.getUser(queryUser);
        if(users != null && users.size() > 0){
            return users.get(0);
        }
        return null;
    }

    @Override
    public int removeUserByID(int id) {
        return 0;
    }

    @Override
    public User loadUserByUserName(String userName) {
        return userDao.loadUserByUserName(userName);
    }

    @Override
    public List<User> getUser(Pager pager, User user) {
        return userDao.getUser(pager,user);
    }

    @Override
    public List<User> getUser(User user) {
        return userDao.getUser(user);
    }
}

创建启动类和junit测试类

package com.feng;

import org.springframework.boot.SpringApplication;

@SpringBootApplication
public class SpringBootWebApplication {
    public static void main(String[] args){
        SpringApplication.run(SpringBootWebApplication.class, args);
    }
}

import com.feng.SpringBootWebApplication;
import com.feng.entity.User;
import com.feng.service.UserService;
import database.support.Pager;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import java.util.List;

/**
 * Created by Feng
 */
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = SpringBootWebApplication.class)
public class JDBCTest {
    @Autowired
    private UserService userService;

    @Test
    public void testQuery() throws IllegalAccessException {
        List<User> users = userService.getUser(new User());
        System.out.println(users);
    }
}

测试普通查询成功
在这里插入图片描述

4.使用自定义拦截器分页

mybatis分页的三种方法就不废话了,基本上很少有用内存或者sql语句来分页的,在实际生产中,大多数还是使用拦截器分页。我在网上找相关中文资料,百分之九十都是直接用pageHelper插件来做。很费解的是一个中文文档这么全面的,由国人编写的插件为啥还要写一大堆教程,直接看文档不好吗。。。
言归正传,这里我们自己编写拦截器(插件)用于自动拦截包含Pager参数的查询方法
先看官方文档里关于拦截器的描述

插件(plugins)
MyBatis 允许你在已映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:
Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
ParameterHandler (getParameterObject, setParameters)
ResultSetHandler (handleResultSets, handleOutputParameters)
StatementHandler (prepare, parameterize, batch, update, query)
这些类中方法的细节可以通过查看每个方法的签名来发现,或者直接查看 MyBatis 发行包中的源代码。 如果你想做的不仅仅是监控方法的调用,那么你最好相当了解要重写的方法的行为。 因为如果在试图修改或重写已有方法的行为的时候,你很可能在破坏 MyBatis 的核心模块。 这些都是更低层的类和方法,所以使用插件的时候要特别当心。
通过 MyBatis 提供的强大机制,使用插件是非常简单的,只需实现 Interceptor 接口,并指定想要拦截的方法签名即可。

至于这几个对象和其内部方法的作用,大家可以在网上找相关资料,我们要拦截的是StatementHandler.prepare方法,这个方法用于生成statement,参数包含一个Connection对象,我们可以借助它在查询发起前先先执行计数操作并替换原有的sql语句
先编写一个工具类用于构建分页和计数sql语句

package database.support;

/**
 * Created by Feng
 */
public interface JDBCSupport {
    public String generatePageSql(Pager pager,String sql);

    public String generateCountSql(String sql);
}

package database.support;

import org.springframework.util.StringUtils;

/**
 * Created by Feng
 */
public class MySqlSupport implements JDBCSupport {
    /**
     * 拼接分页sql
     * @param pager
     * @param sql
     * @return
     */
    @Override
    public String generatePageSql(Pager pager, String sql) {
        long startIndex = (pager.getPageNo() - 1) * pager.getPageSize()+1;
        long endIndex = startIndex + pager.getPageSize()-1;
        StringBuilder sqlBuilder = new StringBuilder(sql);

        if(!StringUtils.isEmpty(pager.getSortBy())){
            sqlBuilder.append(" ORDER BY ");
            sqlBuilder.append(pager.getSortBy());
            sqlBuilder.append(pager.getRank());
        }
        sqlBuilder.append(" limit ");
        sqlBuilder.append(startIndex);
        sqlBuilder.append(",");
        sqlBuilder.append(endIndex);
        return sqlBuilder.toString().toUpperCase();
    }

    /**
     * 拼接计数sql
     * @param sql
     * @return
     */
    @Override
    public String generateCountSql(String sql) {
        return ("SELECT COUNT(*) FROM (" + sql + ") A").toUpperCase();
    }

}

接下来就是最重要的拦截器了

package database.mybatis.plug;

import database.support.JDBCSupport;
import database.support.MySqlSupport;
import database.support.Pager;
import org.apache.ibatis.executor.resultset.ResultSetHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.Map;
import java.util.Properties;

/**
 * Created by Feng
 */
@Intercepts({
        @Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class,Integer.class}),
})
public class PageInterceptor implements Interceptor {
    private JDBCSupport jdbcSupport = new MySqlSupport();

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        StatementHandler statementHandler = (StatementHandler)invocation.getTarget();
        Map<String,Object> paramsMap = (Map<String, Object>) statementHandler.getParameterHandler().getParameterObject();

        String sql = statementHandler.getBoundSql().getSql();
        for(String key:paramsMap.keySet()){
            if(paramsMap.get(key) instanceof Pager){
                Pager pager = (Pager) paramsMap.get(key);
                Connection connection = (Connection)invocation.getArgs()[0];
                PreparedStatement countStatement = connection.prepareStatement(jdbcSupport.generateCountSql(sql));
                ResultSet rs = countStatement.executeQuery();
                if(rs.next()) {
                    pager.setTotal(rs.getInt(1));
                }
                MetaObject metaObject = SystemMetaObject.forObject(statementHandler);
                String pageSql = jdbcSupport.generatePageSql(pager,sql);
                metaObject.setValue("delegate.boundSql.sql", pageSql);
                break;
            }
        }
        return invocation.proceed();
    }

    @Override
    public Object plugin(Object o) {
        if (o instanceof StatementHandler) {
            return Plugin.wrap(o, this);
        } else {
            return o;
        }
    }

    @Override
    public void setProperties(Properties properties) {

    }
}

Intercepts注解用于标识该拦截器作用的目标对象和目标方法。在intercept方法中我们先获取查询方法的所有参数并进行遍历,当发现包含Pager参数后我们先构建计数语句执行查询并将结果写入Pager中,然后借助mybatis自带的反射工具将sql替换成构建好的分页查询语句。
在之前的mybatis配置类中添加这个拦截器

package com.feng.config;

import database.mybatis.plug.PageInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.mybatis.spring.boot.autoconfigure.ConfigurationCustomizer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * Created by Feng
 */
@MapperScan("com.feng.dao")
@Configuration
public class MybatisConfig {
    /**
     * 添加mybatis分页拦截器
     * @return
     */
    @Bean
    public PageInterceptor pageInterceptor(){
        return new PageInterceptor();
    }

}

新增测试方法

@Test
    public void testPagerQuery() throws IllegalAccessException {
        Pager pager = new Pager().setPageSize(2).setPageNo(1);
        List<User> users = userService.getUser(pager,new User());
        System.out.println(users);
        System.out.println(pager);
    }

测试结果
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_35488769/article/details/90671948