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 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);
    }
}
  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> {
    
}

/**
 * 學生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同上
  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.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);
    }
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM