Mybatis Plus 自定义通用扩展 Mapper

Mybatis Plus 自定义通用扩展 Mapper

环境:IDEA,SpringBoot2.x,Mybatis Plus

前景需求

我们在使用Mybatis Plus时,查询都需要使用到QueryWrapper
复杂的SQL使用QueryWrapper就不多说,但是一些简单的SQL也需要
QueryWrapper就不很人性化,比如我们经常通过一个外键去查询相关数据

例:在学生和书的关系中,学生和书是一对多的关系,通常我们会在书籍表中加一列学生 id 作为外键
(可能是逻辑外键,也可能是物理外键)用以表示一对多的关系。当我们知道一个学生的 id 时,需要
查找这个学生所拥有的书籍,就需要使用该学生 id 去书籍表中查询,
如:select * from book where student_id = 1

这时候,我们就需要一个通用的、简单易用,最好是无侵入或者低侵入的方式去扩展我们的dao接口。
如果能和Lambda QueryWrapper一样,使用Lambda引用实体属性Getter方法代替列名就更好了。

解决方案

在进一步了解mybatis后,了解到Mybatis SQL可以通过三种方式书写,除了我们常用的mapper.xml
@Select注解,第三种就是@SelectProvider注解。通过第三种正好可以实现我需要的扩展功能。
话不多说,下面我们用代码实现。

  1. 首先我们定义一个场景以及相关表,就以上方学生和书为场景
## 创建学生物表
CREATE TABLE 'student' (
  'id' int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '学生id',
  'name' varchar(255) NOT NULL COMMENT '学生姓名',
  'grade' int(10) unsigned NOT NULL COMMENT '年级',
  'major' varchar(255) NOT NULL COMMENT '专业',
  PRIMARY KEY ('id')
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

## 创建书籍表
CREATE TABLE 'book' (
  'id' int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '书籍id',
  'book_name' varchar(255) NOT NULL COMMENT '书名',
  'author' varchar(255) NOT NULL COMMENT '作者',
  'student_id' int(10) unsigned NOT NULL COMMENT '所属学生id',
  PRIMARY KEY ('id')
  ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
  1. 然后我们通过idea创建一个springboot2.x工程,包路径为com.example.mp_ext。具体创建过程这里就不详细介绍,我之前的博客或者网上都有对应
    图文教程。
  2. pom.xml文件中引入mybatis plus 和 lombok
<!-- mybatis plus -->
<dependency>
  <groupId>com.baomidou</groupId>
  <artifactId>mybatis-plus-boot-starter</artifactId>
  <version>3.4.0</version>
</dependency>
<!-- lombok -->
<dependency>
  <groupId>org.projectlombok</groupId>
  <artifactId>lombok</artifactId>
  <optional>true</optional>
</dependency>
  1. yml配置文件中定义数据源
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/test_db?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&allowMultiQueries=true&useSSL=false
    username: root
    password: 123456
    tomcat:
      init-s-q-l: SET NAMES utf8mb4
  1. entity层定义学生和书的实体
package com.example.mp_ext.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.Data;
/**
 * 学生实体
 */
@Data
public class Student {
    @TableId(type = IdType.AUTO)
    private Integer id;
    private String name;
    private Integer grade;
    private String major;
}
package com.example.mp_ext.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.Data;
/**
 * 书籍实体
 */
@Data
public class Book {
    @TableId(type = IdType.AUTO)
    private Integer id;
    private String bookName;
    private String author;
    private Integer studentId;
}
  1. 定义一个ext包存放通用mapper扩展接口和实现
package com.example.mp_ext.ext;
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
import org.apache.ibatis.annotations.SelectProvider;
import java.util.List;
/**
 * 扩展mapper接口
 */
public interface ExtMapper<E> {
    /**
     * 通过一个属性获取实体列表
     * @param column 属性Getter方法
     * @param val 属性值
     * @return 实体列表
     */
    @SelectProvider(type = ExtSqlProvider.class,method = "listByOneField")
    List<E> listByOneField(SFunction<E,?> column, Object val);

    /**
     * 通过一个属性获取实体分页列表
     * @param page 分页实体 new Page(currentPage,pageSize)
     * @param column 属性Getter方法
     * @param val 属性值
     * @return 实体列表
     */
    @SelectProvider(type = ExtSqlProvider.class,method = "listByOneField")
    Page<E> pageByOneField(Page page, SFunction<E,?> column, Object val);
    
    /**
     * 通过一个属性获取实体
     * @param column 属性Getter方法
     * @param val 属性值
     * @return 实体
     */
    @SelectProvider(type = ExtSqlProvider.class,method = "getByOneField")
    E getByOneField(SFunction<E,?> column, Object val);

    /**
     * 通过一个属性删除记录
     * @param column 属性Getter方法
     * @param val 属性值
     * @return 删除记录数
     */
    @DeleteProvider(type = ExtSqlProvider.class,method = "deleteByOneField")
    int deleteByOneField(SFunction<E,?> column, Object val);
    
    /**
     * 通过两个属性 and 关系获取实体列表
     * @param column1 第一个属性Getter方法
     * @param val1 第一个属性值
     * @param column2 第二个属性Getter方法
     * @param val2 第二个属性值
     * @return 实体列表
     */
    @SelectProvider(type = ExtSqlProvider.class,method = "listByTwoField")
    List<E> listByTwoField(SFunction<E,?> column1, Object val1,SFunction<E,?> column2, Object val2);

    /**
     * 通过两个属性 and 关系获取实体列表
     * @param page 分页实体 new Page(currentPage,pageSize)
     * @param column1 第一个属性Getter方法
     * @param val1 第一个属性值
     * @param column2 第二个属性Getter方法
     * @param val2 第二个属性值
     * @return 实体列表
     */
    @SelectProvider(type = ExtSqlProvider.class,method = "listByTwoField")
    Page<E> pageByTwoField(Page page, SFunction<E,?> column1, Object val1,SFunction<E,?> column2, Object val2);
    
    /**
     * 通过两个属性 and 关系获取实体
     * @param column1 第一个属性Getter方法
     * @param val1 第一个属性值
     * @param column2 第二个属性Getter方法
     * @param val2 第二个属性值
     * @return 实体
     */
    @SelectProvider(type = ExtSqlProvider.class,method = "getByTwoField")
    E getByTwoField(SFunction<E,?> column1, Object val1,SFunction<E,?> column2, Object val2);

    /**
     * 通过两个属性 and 关系删除记录
     * @param column1 第一个属性Getter方法
     * @param val1 第一个属性值
     * @param column2 第二个属性Getter方法
     * @param val2 第二个属性值
     * @return 删除记录数
     */
    @DeleteProvider(type = ExtSqlProvider.class,method = "deleteByTwoField")
    int deleteByTwoField(SFunction<E,?> column1, Object val1,SFunction<E,?> column2, Object val2);
}
package com.example.mp_ext.ext;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import com.baomidou.mybatisplus.core.toolkit.LambdaUtils;
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
import com.baomidou.mybatisplus.core.toolkit.support.SerializedLambda;
import org.apache.ibatis.reflection.property.PropertyNamer;
/**
 * 通用扩展mapper的实现
 */
public class ExtSqlProvider {

    private String oneField(SFunction<?, ?> column,String format) {
        SerializedLambda lambda = LambdaUtils.resolve(column);
        Class<?> aClass = lambda.getInstantiatedType();
        String tableName = TableInfoHelper.getTableInfo(aClass).getTableName();
        String fieldName = PropertyNamer.methodToProperty(lambda.getImplMethodName());
        return String.format(format,tableName,fieldName);
    }

    public String listByOneField(SFunction<?, ?> column) {
        String format = "SELECT * FROM %s WHERE %s = #{val}";
        return oneField(column,format);
    }

    public String getByOneField(SFunction<?, ?> column) {
        String format = "SELECT * FROM %s WHERE %s = #{val} LIMIT 1";
        return oneField(column,format);
    }

    public String deleteByOneField(SFunction<?, ?> column) {
        String format = "DELETE FROM %s WHERE %s = #{val}";
        return oneField(column,format);
    }

    private String twoField(SFunction<?, ?> column1,SFunction<?, ?> column2,String format) {
        SerializedLambda lambda1 = LambdaUtils.resolve(column1);
        Class<?> aClass = lambda1.getInstantiatedType();
        String tableName = TableInfoHelper.getTableInfo(aClass).getTableName();
        String fieldName1 = PropertyNamer.methodToProperty(lambda1.getImplMethodName());
        SerializedLambda lambda2 = LambdaUtils.resolve(column2);
        String fieldName2 = PropertyNamer.methodToProperty(lambda2.getImplMethodName());
        return String.format(format,tableName,fieldName1,fieldName2);
    }

    public String listByTwoField(SFunction<?, ?> column1,SFunction<?, ?> column2) {
        String format = "SELECT * FROM %s WHERE %s = #{val1} AND %s = #{val2}";
        return twoField(column1,column2,format);
    }

    public String getByTwoField(SFunction<?, ?> column1,SFunction<?, ?> column2) {
        String format = "SELECT * FROM %s WHERE %s = #{val1} AND %s = #{val2} LIMIT 1";
        return twoField(column1,column2,format);
    }

    public String deleteByTwoField(SFunction<?, ?> column1,SFunction<?, ?> column2) {
        String format = "DELETE FROM %s WHERE %s = #{val1} AND %s = #{val2}";
        return twoField(column1,column2,format);
    }

}
package com.example.mp_ext.ext;
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import java.util.List;
/**
 * 通用拓展service接口及默认实现
 */
public interface BaseService<<E,M> extends IService<E> {

    default M getMapper() {
        return (M)getBaseMapper();
    }

    default List<E> listByOneField(SFunction<E,?> column, Object val) {
        ExtMapper<E> extMapper = (ExtMapper) getBaseMapper();
        return extMapper.listByOneField(column,val);
    }

    default Page<E> pageByOneField(Page page, SFunction<E, ?> column, Object val) {
        ExtMapper<E> extMapper = (ExtMapper) getBaseMapper();
        return extMapper.pageByOneField(page,column,val);
    }

    default E getByOneField(SFunction<E, ?> column, Object val) {
        ExtMapper<E> extMapper = (ExtMapper) getBaseMapper();
        return extMapper.getByOneField(column,val);
    }
    
    default int deleteByOneField(SFunction<E, ?> column, Object val) {
        ExtMapper<E> extMapper = (ExtMapper) getBaseMapper();
        return extMapper.deleteByOneField(column,val);
    }
    
    default List<E> listByTwoField(SFunction<E, ?> column1, Object val1, SFunction<E, ?> column2, Object val2) {
        ExtMapper<E> extMapper = (ExtMapper) getBaseMapper();
        return extMapper.listByTwoField(column1,val1,column2,val2);
    }

    default Page<E> pageByTwoField(Page page, SFunction<E, ?> column1, Object val1, SFunction<E, ?> column2, Object val2) {
        ExtMapper<E> extMapper = (ExtMapper) getBaseMapper();
        return extMapper.pageByTwoField(page,column1,val1,column2,val2);
    }

    default E getByTwoField(SFunction<E, ?> column1, Object val1, SFunction<E, ?> column2, Object val2) {
        ExtMapper<E> extMapper = (ExtMapper) getBaseMapper();
        return extMapper.getByTwoField(column1,val1,column2,val2);
    }

    default int deleteByTwoField(SFunction<E, ?> column1, Object val1, SFunction<E, ?> column2, Object val2) {
        ExtMapper<E> extMapper = (ExtMapper) getBaseMapper();
        return extMapper.deleteByTwoField(column1,val1,column2,val2);
    }
}
  1. dao层创建学生和书籍的mapper接口,同时继承mybatis plus基础mapper接口以及自定义扩展mapper接口
package com.example.mp_ext.dao;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.mp_ext.entity.Student;
import com.example.mp_ext.ext.ExtMapper;
import org.apache.ibatis.annotations.Mapper;
/**
 * 学生mapper接口
 */
@Mapper
public interface StudentMapper extends BaseMapper<Student>, ExtMapper<Student> {
    
}
package com.example.mp_ext.dao;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.mp_ext.entity.Book;
import com.example.mp_ext.ext.ExtMapper;
import org.apache.ibatis.annotations.Mapper;
/**
 * 书籍mapper接口
 */
@Mapper
public interface BookMapper extends BaseMapper<Book>, ExtMapper<Book> {
    
}
  1. config层添加mybatis plus分页插件
package com.example.mp_ext.config;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
 * 分页插件
 */
@Configuration
public class MybatisPlusConfig {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        return interceptor;
    }
}

测试及结果

package com.example.mp_ext;
import com.example.mp_ext.dao.StudentMapper;
import com.example.mp_ext.dao.BookMapper;
import com.example.mp_ext.entity.Student;
import com.example.mp_ext.entity.Book;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.List;

@SpringBootTest
class ApplicationTests {
  
    @Autowired
    private StudentMapper studentMapper;
  
    @Test
    void test1() {
      //获取id为1的学生的所有书籍
      List<Book> books = bookMapper.listByOneField(Book::getStudentId,1);
      books.forEach(System.out::println);
    }
    
    @Test
    void test2() {
      //获取计算机科学与技术专业叫张三的所有学生
      List<Student> students = studentMapper.listByTwoField(Student::getName,"张三",Student::getMajor,"计算机科学与技术");
      students.forEach(System.out::println);
    }

    @Test
    void test3() {
        //获取id为1的学生的所有书籍
        Page<Book> books = bookMapper.pageByOneField(new Page(1,10),Book::getStudentId,1);
        //总页数
        System.out.println(books.getPages());
        //总数
        System.out.println(books.getTotal());
        //实体分页列表
        books.getRecords().forEach(System.out::println);
    }
}
不积跬步无以至千里
原文地址:https://www.cnblogs.com/xiaogblog/p/14857900.html