zoukankan      html  css  js  c++  java
  • 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);
        }
    }
    
    不积跬步无以至千里
  • 相关阅读:
    koa 放置 前台打包dist 目录
    tomcat startup.bat 包含springboot的输出 里面乱码的解决方案
    base64 转文件上传
    4时4态 加被动 例句:I will have been being done
    软件推荐 Notable / 现改用 Vnote 了
    [win10] 开始-设置 / 右键-显示设置 / 右键个性化 等都不好使了。。 ms-settings:display
    viewui tree 自定义化(源码copy出来改动)#添加 获取selected 解决方案
    idea 暂存 Stash Changes Git/Repository/Stash Changes 恢复暂存 UnStash Changes
    vm 虚拟机总是蓝屏 移除打印机和声卡 移除这俩硬件 (大文件用飞秋传输)
    docker中mysql 汉字乱码,显示问号
  • 原文地址:https://www.cnblogs.com/xiaogblog/p/14857900.html
Copyright © 2011-2022 走看看