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
注解。通过第三种正好可以实现我需要的扩展功能。
话不多说,下面我们用代码实现。
- 首先我们定义一个场景以及相关表,就以上方学生和书为场景
## 创建学生物表
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;
- 然后我们通过
idea
创建一个springboot2.x
工程,包路径为com.example.mp_ext
。具体创建过程这里就不详细介绍,我之前的博客或者网上都有对应
图文教程。 - 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>
- 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
- 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;
}
- 定义一个ext包存放通用mapper扩展接口和实现
package com.example.mp_ext.ext;
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.apache.ibatis.annotations.DeleteProvider;
import org.apache.ibatis.annotations.SelectProvider;
import org.apache.ibatis.annotations.UpdateProvider;
import java.util.List;
/**
* 扩展mapper接口
*/
public interface ExtMapper<T> {
/**
* 通过一个属性获取总数
* @param column 属性Getter方法
* @param val 属性值
* @return 实体列表
*/
@SelectProvider(type = ExtSqlProvider.class,method = "countByField")
int countByField(SFunction<T,?> column, Object val);
/**
* 通过一个属性获取实体列表
* @param column 属性Getter方法
* @param val 属性值
* @return 实体列表
*/
@SelectProvider(type = ExtSqlProvider.class,method = "listByField")
List<T> listByField(SFunction<T,?> column, Object val);
/**
* 通过一个属性获取实体分页列表
* @param page 分页实体 new Page(currentPage,pageSize)
* @param column 属性Getter方法
* @param val 属性值
* @return 实体列表
*/
@SelectProvider(type = ExtSqlProvider.class,method = "pageByField")
Page<T> pageByField(Page page,SFunction<T,?> column, Object val);
/**
* 通过一个属性获取实体
* @param column 属性Getter方法
* @param val 属性值
* @return 实体
*/
@SelectProvider(type = ExtSqlProvider.class,method = "getByField")
T getByField(SFunction<T,?> column, Object val);
/**
* 通过一个属性删除记录
* @param column 属性Getter方法
* @param val 属性值
* @return 删除记录数
*/
@DeleteProvider(type = ExtSqlProvider.class,method = "deleteByField")
int deleteByField(SFunction<T,?> column, Object val);
/**
* 通过两个属性 and 关系获取总数
* @param column1 第一个属性Getter方法
* @param val1 第一个属性值
* @param column2 第二个属性Getter方法
* @param val2 第二个属性值
* @return 实体列表
*/
@SelectProvider(type = ExtSqlProvider.class,method = "countBy2Fields")
int countBy2Fields(SFunction<T,?> column1, Object val1, SFunction<T,?> column2, Object val2);
/**
* 通过两个属性 and 关系获取实体列表
* @param column1 第一个属性Getter方法
* @param val1 第一个属性值
* @param column2 第二个属性Getter方法
* @param val2 第二个属性值
* @return 实体列表
*/
@SelectProvider(type = ExtSqlProvider.class,method = "listBy2Fields")
List<T> listBy2Fields(SFunction<T,?> column1, Object val1, SFunction<T,?> 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 = "pageBy2Fields")
Page<T> pageBy2Fields(Page page, SFunction<T,?> column1, Object val1, SFunction<T,?> column2, Object val2);
/**
* 通过两个属性 and 关系获取实体
* @param column1 第一个属性Getter方法
* @param val1 第一个属性值
* @param column2 第二个属性Getter方法
* @param val2 第二个属性值
* @return 实体
*/
@SelectProvider(type = ExtSqlProvider.class,method = "getBy2Fields")
T getBy2Fields(SFunction<T,?> column1, Object val1, SFunction<T,?> column2, Object val2);
/**
* 通过两个属性 and 关系删除记录
* @param column1 第一个属性Getter方法
* @param val1 第一个属性值
* @param column2 第二个属性Getter方法
* @param val2 第二个属性值
* @return 删除记录数
*/
@DeleteProvider(type = ExtSqlProvider.class,method = "deleteBy2Fields")
int deleteBy2Fields(SFunction<T,?> column1, Object val1, SFunction<T,?> column2, Object val2);
/**
* 通过两个属性 and 关系删除记录
* @param conditionColumn 条件列
* @param conditionVal 条件值
* @param updatedColumn 要更新的列
* @param updatedVal 要更新的值
* @return 更新记录数
*/
@UpdateProvider(type = ExtSqlProvider.class,method = "updateByField")
int updateByField(SFunction<T,?> conditionColumn, Object conditionVal, SFunction<T,?> updatedColumn, Object updatedVal);
}
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.ColumnCache;
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
import com.baomidou.mybatisplus.core.toolkit.support.SerializedLambda;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.reflection.property.PropertyNamer;
import java.util.Map;
/**
* 通用扩展mapper的实现
*/
public class ExtSqlProvider {
public String countByField(@Param("param1") SFunction<?, ?> column) {
String format = "SELECT COUNT(*) FROM %s WHERE %s = #{param2}";
return oneField(column,format);
}
public String listByField(@Param("param1") SFunction<?, ?> column) {
String format = "SELECT * FROM %s WHERE %s = #{param2}";
return oneField(column,format);
}
public String pageByField(@Param("param2") SFunction<?, ?> column) {
String format = "SELECT * FROM %s WHERE %s = #{param3}";
return oneField(column,format);
}
public String getByField(@Param("param1") SFunction<?, ?> column) {
String format = "SELECT * FROM %s WHERE %s = #{param2} LIMIT 1";
return oneField(column,format);
}
public String deleteByField(@Param("param1") SFunction<?, ?> column) {
String format = "DELETE FROM %s WHERE %s = #{param2}";
return oneField(column,format);
}
public String countBy2Fields(@Param("param1") SFunction<?,?> column1 ,@Param("param3")SFunction<?,?> column2) {
String format = "SELECT COUNT(*) FROM %s WHERE %s = #{param2} AND %s = #{param4}";
return twoField(column1,column2,format);
}
public String listBy2Fields(@Param("param1") SFunction<?,?> column1 ,@Param("param3")SFunction<?,?> column2) {
String format = "SELECT * FROM %s WHERE %s = #{param2} AND %s = #{param4}";
return twoField(column1,column2,format);
}
public String pageBy2Fields(@Param("param2") SFunction<?,?> column1 ,@Param("param4")SFunction<?,?> column2) {
String format = "SELECT * FROM %s WHERE %s = #{param3} AND %s = #{param5}";
return twoField(column1,column2,format);
}
public String getBy2Fields(@Param("param1") SFunction<?,?> column1 ,@Param("param3")SFunction<?,?> column2) {
String format = "SELECT * FROM %s WHERE %s = #{param2} AND %s = #{param4} LIMIT 1";
return twoField(column1,column2,format);
}
public String deleteBy2Fields(@Param("param1") SFunction<?,?> column1 ,@Param("param3")SFunction<?,?> column2) {
String format = "DELETE FROM %s WHERE %s = #{param2} AND %s = #{param4}";
return twoField(column1,column2,format);
}
public String updateByField(@Param("param3") SFunction<?,?> updatedColumn ,@Param("param1")SFunction<?,?> conditionColumn) {
String format = "UPDATE %s SET %s = #{param4} WHERE %s = #{param2}";
return twoField(updatedColumn,conditionColumn,format);
}
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());
String columnName = LambdaUtils.getColumnMap(aClass).get(LambdaUtils.formatKey(fieldName)).getColumn();
return String.format(format,tableName,columnName);
}
private String twoField(SFunction<?, ?> column1,SFunction<?, ?> column2,String format) {
SerializedLambda lambda1 = LambdaUtils.resolve(column1);
Class<?> aClass = lambda1.getInstantiatedType();
String tableName = TableInfoHelper.getTableInfo(aClass).getTableName();
Map<String, ColumnCache> columnCacheMap = LambdaUtils.getColumnMap(aClass);
String fieldName1 = PropertyNamer.methodToProperty(lambda1.getImplMethodName());
String columnName1 = columnCacheMap.get(LambdaUtils.formatKey(fieldName1)).getColumn();
SerializedLambda lambda2 = LambdaUtils.resolve(column2);
String fieldName2 = PropertyNamer.methodToProperty(lambda2.getImplMethodName());
String columnName2 = columnCacheMap.get(LambdaUtils.formatKey(fieldName2)).getColumn();
return String.format(format,tableName,columnName1,columnName2);
}
}
package com.example.mp_ext.ext;
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import java.util.List;
/**
* 通用拓展service接口及默认实现
*/
/**
* 通用拓展service接口及默认实现
*/
public interface ExtService<M extends ExtMapper<T>,T> {
M getBaseMapper();
/**
* 通过一个属性获取总数
* @param column 属性Getter方法
* @param val 属性值
* @return 实体列表
*/
default int countByField(SFunction<T,?> column, Object val) {
return getBaseMapper().countByField(column,val);
}
/**
* 通过一个属性获取实体列表
* @param column 属性Getter方法
* @param val 属性值
* @return 实体列表
*/
default List<T> listByField(SFunction<T,?> column, Object val) {
return getBaseMapper().listByField(column,val);
}
/**
* 通过一个属性获取实体分页列表
* @param page 分页实体 new Page(currentPage,pageSize)
* @param column 属性Getter方法
* @param val 属性值
* @return 实体列表
*/
default Page<T> pageByField(Page page, SFunction<T,?> column, Object val) {
return getBaseMapper().pageByField(page,column,val);
}
/**
* 通过一个属性获取实体
* @param column 属性Getter方法
* @param val 属性值
* @return 实体
*/
default T getByField(SFunction<T,?> column, Object val) {
return getBaseMapper().getByField(column,val);
}
/**
* 通过一个属性删除记录
* @param column 属性Getter方法
* @param val 属性值
* @return 删除记录数
*/
default int deleteByField(SFunction<T,?> column, Object val) {
return getBaseMapper().countByField(column,val);
}
/**
* 通过两个属性 and 关系获取总数
* @param column1 第一个属性Getter方法
* @param val1 第一个属性值
* @param column2 第二个属性Getter方法
* @param val2 第二个属性值
* @return 实体列表
*/
default int countBy2Fields(SFunction<T,?> column1, Object val1, SFunction<T,?> column2, Object val2) {
return getBaseMapper().countBy2Fields(column1,val1,column2,val2);
}
/**
* 通过两个属性 and 关系获取实体列表
* @param column1 第一个属性Getter方法
* @param val1 第一个属性值
* @param column2 第二个属性Getter方法
* @param val2 第二个属性值
* @return 实体列表
*/
default List<T> listBy2Fields(SFunction<T,?> column1, Object val1, SFunction<T,?> column2, Object val2) {
return getBaseMapper().listBy2Fields(column1,val1,column2,val2);
}
/**
* 通过两个属性 and 关系获取实体列表
* @param page 分页实体 new Page(currentPage,pageSize)
* @param column1 第一个属性Getter方法
* @param val1 第一个属性值
* @param column2 第二个属性Getter方法
* @param val2 第二个属性值
* @return 实体列表
*/
default Page<T> pageBy2Fields(Page page, SFunction<T,?> column1, Object val1, SFunction<T,?> column2, Object val2) {
return getBaseMapper().pageBy2Fields(page,column1,val1,column2,val2);
}
/**
* 通过两个属性 and 关系获取实体
* @param column1 第一个属性Getter方法
* @param val1 第一个属性值
* @param column2 第二个属性Getter方法
* @param val2 第二个属性值
* @return 实体
*/
default T getBy2Fields(SFunction<T,?> column1, Object val1, SFunction<T,?> column2, Object val2) {
return getBaseMapper().getBy2Fields(column1,val1,column2,val2);
}
/**
* 通过两个属性 and 关系删除记录
* @param column1 第一个属性Getter方法
* @param val1 第一个属性值
* @param column2 第二个属性Getter方法
* @param val2 第二个属性值
* @return 删除记录数
*/
default int deleteBy2Fields(SFunction<T,?> column1, Object val1, SFunction<T,?> column2, Object val2) {
return getBaseMapper().deleteBy2Fields(column1,val1,column2,val2);
}
/**
* 通过两个属性 and 关系删除记录
* @param conditionColumn 条件列
* @param conditionVal 条件值
* @param updatedColumn 要更新的列
* @param updatedVal 要更新的值
* @return 更新记录数
*/
default int updateByField(SFunction<T,?> conditionColumn, Object conditionVal, SFunction<T,?> updatedColumn, Object updatedVal) {
return getBaseMapper().updateByField(conditionColumn,conditionVal,updatedColumn,updatedVal);
}
}
- 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> {
}
/**
* 学生service接口
*/
public interface StudentService extends IService<Student>, ExtService<StudentMapper,Student> {
}
/**
* 学生service接口实现
*/
@Service
public class StudentServiceImpl extends ServiceImpl<StudentMapper, Student>
implements StudentService{
}
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> {
}
//service同上
- 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.listByField(Book::getStudentId,1);
books.forEach(System.out::println);
}
@Test
void test2() {
//获取计算机科学与技术专业叫张三的所有学生
List<Student> students = studentMapper.listBy2Fields(Student::getName,"张三",Student::getMajor,"计算机科学与技术");
students.forEach(System.out::println);
}
@Test
void test3() {
//获取id为1的学生的所有书籍
Page<Book> books = bookMapper.pageByField(new Page(1,10),Book::getStudentId,1);
//总页数
System.out.println(books.getPages());
//总数
System.out.println(books.getTotal());
//实体分页列表
books.getRecords().forEach(System.out::println);
}
}
不积跬步无以至千里