Skip to content

一、概述

MyBatis-Plus(简称 MP)是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高 效率而生。

官网:https://mybatis.plus/https://mp.baomidou.com/

文档地址:https://mybatis.plus/guide/

源码地址:https://github.com/baomidou/mybatis-plus

二、使用示例

2.1 与springboot整合

依赖

groovy
runtimeOnly 'mysql:mysql-connector-java:8.0.15'
compile 'com.baomidou:mybatis-plus-boot-starter:3.4.3'
compile 'com.alibaba:druid-spring-boot-starter:1.2.6'

配置

yml
# 数据源配置


<NolebasePageProperties />




spring:
  autoconfigure:
    exclude: com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driverClassName: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/ruoyi_vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true
    username: root
    password: 1234
    druid:
      # 初始连接数
      initialSize: 5
      # 最小连接池数量
      minIdle: 10
      # 最大连接池数量
      maxActive: 20
      # 配置获取连接等待超时的时间
      maxWait: 60000
      # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
      timeBetweenEvictionRunsMillis: 60000
      # 配置一个连接在池中最小生存的时间,单位是毫秒
      minEvictableIdleTimeMillis: 300000
      # 配置一个连接在池中最大生存的时间,单位是毫秒
      maxEvictableIdleTimeMillis: 900000
      # 配置检测连接是否有效
      validationQuery: SELECT 1 FROM DUAL
      testWhileIdle: true
      testOnBorrow: false
      testOnReturn: false
      # 注意这个值和druid原生不一致,默认启动了stat
      filters: stat
      webStatFilter:
        enabled: true
      statViewServlet:
        enabled: true
        # 设置白名单,不填则允许所有访问
        allow:
        url-pattern: /druid/*
        # 控制台管理用户名和密码
        login-username: ruoyi
        login-password: 123456
      filter:
        stat:
          enabled: true
          # 慢SQL记录
          log-slow-sql: true
          slow-sql-millis: 1000
          merge-sql: true
        wall:
          config:
            multi-statement-allow: true
            
# MyBatisPlus配置
# https://baomidou.com/config/
mybatis-plus:
  # 不支持多包, 如有需要可在注解配置 或 提升扫包等级
  # 例如 com.**.**.mapper
#  mapperPackage: com.ruoyi.**.mapper
  # 对应的 XML 文件位置
  mapperLocations: classpath*:**/*Mapper.xml
  # 实体扫描,多个package用逗号或者分号分隔
  typeAliasesPackage: com.lly.**.model
  # 针对 typeAliasesPackage,如果配置了该属性,则仅仅会扫描路径下以该类作为父类的域对象
  #typeAliasesSuperType: Class<?>
  # 如果配置了该属性,SqlSessionFactoryBean 会把该包下面的类注册为对应的 TypeHandler
  #typeHandlersPackage: null
  # 如果配置了该属性,会将路径下的枚举类进行注入,让实体类字段能够简单快捷的使用枚举属性
  #typeEnumsPackage: null
  # 启动时是否检查 MyBatis XML 文件的存在,默认不检查
  checkConfigLocation: false
  # 通过该属性可指定 MyBatis 的执行器,MyBatis 的执行器总共有三种:
  # SIMPLE:该执行器类型不做特殊的事情,为每个语句的执行创建一个新的预处理语句(PreparedStatement)
  # REUSE:该执行器类型会复用预处理语句(PreparedStatement)
  # BATCH:该执行器类型会批量执行所有的更新语句
  executorType: SIMPLE
  # 指定外部化 MyBatis Properties 配置,通过该配置可以抽离配置,实现不同环境的配置部署
  configurationProperties: null
  configuration:
    # 自动驼峰命名规则(camel case)映射
    # 如果您的数据库命名符合规则无需使用 @TableField 注解指定数据库字段名
    mapUnderscoreToCamelCase: true
    # 默认枚举处理类,如果配置了该属性,枚举将统一使用指定处理器进行处理
    # org.apache.ibatis.type.EnumTypeHandler : 存储枚举的名称
    # org.apache.ibatis.type.EnumOrdinalTypeHandler : 存储枚举的索引
    # com.baomidou.mybatisplus.extension.handlers.MybatisEnumTypeHandler : 枚举类需要实现IEnum接口或字段标记@EnumValue注解.
    defaultEnumTypeHandler: org.apache.ibatis.type.EnumTypeHandler
    # 当设置为 true 的时候,懒加载的对象可能被任何懒属性全部加载,否则,每个属性都按需加载。需要和 lazyLoadingEnabled 一起使用。
    aggressiveLazyLoading: true
    # MyBatis 自动映射策略
    # NONE:不启用自动映射
    # PARTIAL:只对非嵌套的 resultMap 进行自动映射
    # FULL:对所有的 resultMap 都进行自动映射
    autoMappingBehavior: PARTIAL
    # MyBatis 自动映射时未知列或未知属性处理策
    # NONE:不做任何处理 (默认值)
    # WARNING:以日志的形式打印相关警告信息
    # FAILING:当作映射失败处理,并抛出异常和详细信息
    autoMappingUnknownColumnBehavior: NONE
    # Mybatis一级缓存,默认为 SESSION
    # SESSION session级别缓存,同一个session相同查询语句不会再次查询数据库
    # STATEMENT 关闭一级缓存
    localCacheScope: SESSION
    # 开启Mybatis二级缓存,默认为 true
    cacheEnabled: false
    # 更详细的日志输出 会有性能损耗
    # logImpl: org.apache.ibatis.logging.stdout.StdOutImpl
  global-config:
    # 是否打印 Logo banner
    banner: true
    # 是否初始化 SqlRunner
    enableSqlRunner: false
    dbConfig:
      # 主键类型
      # AUTO 数据库ID自增
      # NONE 空
      # INPUT 用户输入ID
      # ASSIGN_ID 全局唯一ID
      # ASSIGN_UUID 全局唯一ID UUID
      idType: AUTO
      # 表名前缀
      tablePrefix: null
      # 字段 format,例: %s,(对主键无效)
      columnFormat: null
      # 表名是否使用驼峰转下划线命名,只对表名生效
      tableUnderline: true
      # 大写命名,对表名和字段名均生效
      capitalMode: false
      # 全局的entity的逻辑删除字段属性名
      logicDeleteField: null
      # 逻辑已删除值
      logicDeleteValue: 2
      # 逻辑未删除值
      logicNotDeleteValue: 0
      # 字段验证策略之 insert,在 insert 的时候的字段验证策略
      # IGNORED 忽略判断
      # NOT_NULL 非NULL判断
      # NOT_EMPTY 非空判断(只对字符串类型字段,其他类型字段依然为非NULL判断)
      # DEFAULT 默认的,一般只用于注解里
      # NEVER 不加入 SQL
      insertStrategy: NOT_EMPTY
      # 字段验证策略之 update,在 update 的时候的字段验证策略
      updateStrategy: NOT_EMPTY
      # 字段验证策略之 select,在 select 的时候的字段验证策略既 wrapper 根据内部 entity 生成的 where 条件
      selectStrategy: NOT_EMPTY

配置类

java
package com.lly.db.mybatisplus.config;

import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator;
import com.baomidou.mybatisplus.core.injector.AbstractMethod;
import com.baomidou.mybatisplus.core.injector.DefaultSqlInjector;
import com.baomidou.mybatisplus.core.injector.ISqlInjector;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.BlockAttackInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.IllegalSQLInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import com.lly.db.mybatisplus.config.handler.CreateAndUpdateMetaObjectHandler;
import com.lly.db.mybatisplus.core.methods.InsertAll;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import java.util.List;

/**
 * @Classname MybatisPlusConfig
 * @Description mybatisPlus配置类
 * @Date 2021/07/18 13:18
 * @Created by wxp
 */
@Component
@EnableTransactionManagement(proxyTargetClass = true)
public class MybatisPlusConfig {

    /**
     * 一缓和二缓遵循mybatis的规则,需要设置 MybatisConfiguration#useDeprecatedExecutor = false 避免缓存出现问题(该属性会在旧插件移除后一同移除)
     */
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        //向Mybatis过滤器链中添加分页拦截器
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        // 乐观锁插件
        interceptor.addInnerInterceptor(optimisticLockerInnerInterceptor());
        //还可以添加i他的拦截器
        return interceptor;
    }

    /**
     * 乐观锁插件
     * https://baomidou.com/guide/interceptor-optimistic-locker.html
     */
    public OptimisticLockerInnerInterceptor optimisticLockerInnerInterceptor() {
        return new OptimisticLockerInnerInterceptor();
    }

    /**
     * 如果是对全表的删除或更新操作,就会终止该操作
     * https://baomidou.com/guide/interceptor-block-attack.html
     */
//	public BlockAttackInnerInterceptor blockAttackInnerInterceptor() {
//        return new BlockAttackInnerInterceptor();
//    }

    /**
     * sql性能规范插件(垃圾SQL拦截)
     * 如有需要可以启用
     */
//	public IllegalSQLInnerInterceptor illegalSQLInnerInterceptor() {
//		return new IllegalSQLInnerInterceptor();
//	}


    /**
     * 自定义主键策略
     * https://baomidou.com/guide/id-generator.html
     */
//	@Bean
//	public IdentifierGenerator idGenerator() {
//		return new CustomIdGenerator();
//	}

    /**
     * 元对象字段填充控制器
     * 可对执行的sql进行扩展,如:添加更新时间等操作
     * https://baomidou.com/guide/auto-fill-metainfo.html
     */
    @Bean
    public MetaObjectHandler metaObjectHandler() {
        return new CreateAndUpdateMetaObjectHandler();
    }

    /**
     * sql注入器配置
     * 可对BaseMapper中的方法进行拓展,如批量插入
     * https://baomidou.com/guide/sql-injector.html
     */
    @Bean
    public ISqlInjector sqlInjector() {
        return new DefaultSqlInjector() {
            @Override
            public List<AbstractMethod> getMethodList(Class<?> mapperClass) {
                List<AbstractMethod> methodList = super.getMethodList(mapperClass);
                methodList.add(new InsertAll());
                return methodList;
            }
        };
    }

    /**
     * TenantLineInnerInterceptor 多租户插件
     * https://baomidou.com/guide/interceptor-tenant-line.html
     * DynamicTableNameInnerInterceptor 动态表名插件
     * https://baomidou.com/guide/interceptor-dynamic-table-name.html
     */


}

自动填充更新时间

java
package com.lly.db.mybatisplus.config.handler;

import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import com.lly.datacover.exception.MicroRuntimeException;
import com.lly.db.mybatisplus.DBError;
import org.apache.http.HttpStatus;
import org.apache.ibatis.reflection.MetaObject;

import java.util.Date;

/**
 * MP注入处理器
 *
 * @author Lion Li
 * @date 2021/4/25
 */
public class CreateAndUpdateMetaObjectHandler implements MetaObjectHandler {

	@Override
	public void insertFill(MetaObject metaObject) {
		try {
			//根据属性名字设置要填充的值
			if (metaObject.hasGetter("createTime")) {
				if (metaObject.getValue("createTime") == null) {
					this.setFieldValByName("createTime", new Date(), metaObject);
				}
			}
//			if (metaObject.hasGetter("createBy")) {
//				if (metaObject.getValue("createBy") == null) {
//					this.setFieldValByName("createBy", SecurityUtils.getUsername(), metaObject);
//				}
//			}
		} catch (Exception e) {
//			throw new MicroRuntimeException("自动注入异常 => " + e.getMessage(), HttpStatus.SC_UNAUTHORIZED);
			throw new MicroRuntimeException(DBError.auto_fail,"自动注入异常 => " + e.getMessage());
		}
	}

	@Override
	public void updateFill(MetaObject metaObject) {
		try {
//			if (metaObject.hasGetter("updateBy")) {
//				if (metaObject.getValue("updateBy") == null) {
//					this.setFieldValByName("updateBy", SecurityUtils.getUsername(), metaObject);
//				}
//			}
			if (metaObject.hasGetter("updateTime")) {
				if (metaObject.getValue("updateTime") == null) {
					this.setFieldValByName("updateTime", new Date(), metaObject);
				}
			}
		} catch (Exception e) {
			throw new MicroRuntimeException(DBError.auto_fail,"自动注入异常 => " + e.getMessage());
		}
	}

}

批量插入

java
package com.lly.db.mybatisplus.core.methods;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.core.enums.SqlMethod;
import com.baomidou.mybatisplus.core.injector.AbstractMethod;
import com.baomidou.mybatisplus.core.metadata.TableInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import com.lly.common.utils.StringUtils;
import org.apache.ibatis.executor.keygen.Jdbc3KeyGenerator;
import org.apache.ibatis.executor.keygen.KeyGenerator;
import org.apache.ibatis.executor.keygen.NoKeyGenerator;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlSource;

/**
 * @Classname InsertAll
 * @Description TODO
 * @Date 2021/07/18 14:42
 * @Created by wxp
 */
public class InsertAll extends AbstractMethod {

    @Override
    public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
        final String sql = "<script>insert into %s %s values %s</script>";
        final String fieldSql = prepareFieldSql(tableInfo);
        final String valueSql = prepareValuesSqlForMysqlBatch(tableInfo);
        KeyGenerator keyGenerator = new NoKeyGenerator();
        SqlMethod sqlMethod = SqlMethod.INSERT_ONE;
        String keyProperty = null;
        String keyColumn = null;
        // 表包含主键处理逻辑,如果不包含主键当普通字段处理
        if (StringUtils.isNotBlank(tableInfo.getKeyProperty())) {
            if (tableInfo.getIdType() == IdType.AUTO) {
                /** 自增主键 */
                keyGenerator = new Jdbc3KeyGenerator();
                keyProperty = tableInfo.getKeyProperty();
                keyColumn = tableInfo.getKeyColumn();
            } else {
                if (null != tableInfo.getKeySequence()) {
                    keyGenerator = TableInfoHelper.genKeyGenerator(getMethod(sqlMethod), tableInfo, builderAssistant);
                    keyProperty = tableInfo.getKeyProperty();
                    keyColumn = tableInfo.getKeyColumn();
                }
            }
        }
        final String sqlResult = String.format(sql, tableInfo.getTableName(), fieldSql, valueSql);
        SqlSource sqlSource = languageDriver.createSqlSource(configuration, sqlResult, modelClass);
        return this.addInsertMappedStatement(mapperClass, modelClass, "insertAll", sqlSource, keyGenerator, keyProperty, keyColumn);
    }

    private String prepareFieldSql(TableInfo tableInfo) {
        StringBuilder fieldSql = new StringBuilder();
        if (StringUtils.isNotBlank(tableInfo.getKeyColumn())) {
            fieldSql.append(tableInfo.getKeyColumn()).append(",");
        }
        tableInfo.getFieldList().forEach(x -> fieldSql.append(x.getColumn()).append(","));
        fieldSql.delete(fieldSql.length() - 1, fieldSql.length());
        fieldSql.insert(0, "(");
        fieldSql.append(")");
        return fieldSql.toString();
    }

    private String prepareValuesSqlForMysqlBatch(TableInfo tableInfo) {
        final StringBuilder valueSql = new StringBuilder();
        valueSql.append("<foreach collection=\"list\" item=\"item\" index=\"index\" open=\"(\" separator=\"),(\" close=\")\">");
        if (StringUtils.isNotBlank(tableInfo.getKeyColumn())) {
            valueSql.append("#{item.").append(tableInfo.getKeyProperty()).append("},");
        }
        tableInfo.getFieldList().forEach(x -> valueSql.append("#{item.").append(x.getProperty()).append("},"));
        valueSql.delete(valueSql.length() - 1, valueSql.length());
        valueSql.append("</foreach>");
        return valueSql.toString();
    }
}

使用示例

java
package com.note.technology.mybatisplus.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.note.technology.mybatisplus.model.DemoModel;

/**
 * @Classname TestMapper
 * @Description TODO
 * 需使用@MapperScan("com.note.technology.mybatisplus.mapper") 配置扫描的mapper路径
 */
public interface TestMapper extends BaseMapper<DemoModel> {
}
java
package com.note.technology.mybatisplus.model;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

/**
 * @Classname DemoModel
 * @Description TODO
 */
@TableName("demo")
@Data
public class DemoModel {

    // 自增长id
    @TableId(value = "ID", type = IdType.AUTO)
    private Integer id;

    private String name;

    private String desc;
}

2.2 抽取Base接口和服务类

BaseDao

java
package com.lly.db.mybatisplus.core.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Param;

import java.util.Collection;

/**
 * @Classname IMapper 
 * @Description 继承baseMapper 并添加 批量插入的方法(需注入批量添加的sql注入器)
 * @Date 2021/07/18 14:43
 * @Created by wxp
 */
public interface IBaseDao<T> extends BaseMapper<T> {
    int insertAll(@Param("list") Collection<T> batchList);

}

Iservice接口

添加转换为vo的方法及分页对象转换的方法

java
package com.lly.db.mybatisplus.core.service;

import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.metadata.OrderItem;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.lly.common.utils.BeanUtils;
import com.lly.datacover.page.Order;
import com.lly.datacover.page.PageQuery;
import com.lly.datacover.page.Pager;

import java.io.Serializable;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * @Classname IService
 * @Description TODO
 * @Date 2021/07/18 14:46
 * @Created by wxp
 */
public interface IService<T> extends com.baomidou.mybatisplus.extension.service.IService<T> {

    boolean saveAll(Collection<T> entityList);

    /**
     * 根据 ID 查询
     *
     * @param kClass vo类型
     * @param id     主键ID
     */
    default <K> K getVoById(Serializable id, Class<K> kClass) {
        T t = getBaseMapper().selectById(id);
        return BeanUtils.toBean(t, kClass);
    }

    /**
     * 根据 ID 查询
     *
     * @param id        主键ID
     * @param convertor 转换函数
     * @param <K>       vo类型
     */
    default <K> K getVoById(Serializable id, Function<T, K> convertor) {
        T t = getBaseMapper().selectById(id);
        return convertor.apply(t);
    }

    /**
     * 查询(根据ID 批量查询)
     *
     * @param kClass vo类型
     * @param idList 主键ID列表
     */
    default <K> List<K> listVoByIds(Collection<? extends Serializable> idList, Class<K> kClass) {
        List<T> list = getBaseMapper().selectBatchIds(idList);
        if (list == null) {
            return null;
        }
        return list.stream()
                .map(any -> BeanUtils.toBean(any, kClass))
                .collect(Collectors.toList());
    }

    /**
     * 查询(根据ID 批量查询)
     *
     * @param convertor 转换函数
     * @param idList    主键ID列表
     */
    default <K> List<K> listVoByIds(Collection<? extends Serializable> idList,
                                    Function<Collection<T>, List<K>> convertor) {
        List<T> list = getBaseMapper().selectBatchIds(idList);
        if (list == null) {
            return null;
        }
        return convertor.apply(list);
    }

    /**
     * 查询(根据 columnMap 条件)
     *
     * @param kClass    vo类型
     * @param columnMap 表字段 map 对象
     */
    default <K> List<K> listVoByMap(Map<String, Object> columnMap, Class<K> kClass) {
        List<T> list = getBaseMapper().selectByMap(columnMap);
        if (list == null) {
            return null;
        }
        return list.stream()
                .map(any -> BeanUtils.toBean(any, kClass))
                .collect(Collectors.toList());
    }

    /**
     * 查询(根据 columnMap 条件)
     *
     * @param convertor 转换函数
     * @param columnMap 表字段 map 对象
     */
    default <K> List<K> listVoByMap(Map<String, Object> columnMap,
                                    Function<Collection<T>, List<K>> convertor) {
        List<T> list = getBaseMapper().selectByMap(columnMap);
        if (list == null) {
            return null;
        }
        return convertor.apply(list);
    }

    /**
     * 根据 Wrapper,查询一条记录 <br/>
     * <p>结果集,如果是多个会抛出异常,随机取一条加上限制条件 wrapper.last("LIMIT 1")</p>
     *
     * @param kClass       vo类型
     * @param queryWrapper 实体对象封装操作类 {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper}
     */
    default <K> K getVoOne(Wrapper<T> queryWrapper, Class<K> kClass) {
        return BeanUtils.toBean(getOne(queryWrapper, true), kClass);
    }

    /**
     * 根据 Wrapper,查询一条记录 <br/>
     * <p>结果集,如果是多个会抛出异常,随机取一条加上限制条件 wrapper.last("LIMIT 1")</p>
     *
     * @param convertor    转换函数
     * @param queryWrapper 实体对象封装操作类 {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper}
     */
    default <K> K getVoOne(Wrapper<T> queryWrapper, Function<T, K> convertor) {
        return convertor.apply(getOne(queryWrapper, true));
    }

    /**
     * 查询列表
     *
     * @param kClass       vo类型
     * @param queryWrapper 实体对象封装操作类 {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper}
     */
    default <K> List<K> listVo(Wrapper<T> queryWrapper, Class<K> kClass) {
        List<T> list = getBaseMapper().selectList(queryWrapper);
        if (list == null) {
            return null;
        }
        return list.stream()
                .map(any -> BeanUtils.toBean(any, kClass))
                .collect(Collectors.toList());
    }

    /**
     * 查询列表
     *
     * @param convertor    转换函数
     * @param queryWrapper 实体对象封装操作类 {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper}
     */
    default <K> List<K> listVo(Wrapper<T> queryWrapper, Function<Collection<T>, List<K>> convertor) {
        List<T> list = getBaseMapper().selectList(queryWrapper);
        if (list == null) {
            return null;
        }
        return convertor.apply(list);
    }

    /**
     * 查询所有
     *
     * @param kClass vo类型
     * @see Wrappers#emptyWrapper()
     */
    default <K> List<K> listVo(Class<K> kClass) {
        return listVo(Wrappers.emptyWrapper(), kClass);
    }

    /**
     * 查询所有
     *
     * @param convertor 转换函数
     * @see Wrappers#emptyWrapper()
     */
    default <K> List<K> listVo(Function<Collection<T>, List<K>> convertor) {
        return listVo(Wrappers.emptyWrapper(), convertor);
    }

    /**
     * 无条件翻页查询(通过原生Page) 转换为VO
     *
     * @param page 翻页对象
     */
    default <K> Page<K> pageVo(Page<T> page, Class<K> kClass) {
        return pageVo(page, Wrappers.emptyWrapper(), kClass);
    }

    /**
     * 无条件翻页查询(通过原生Page) 转换为VO
     *
     * @param page      翻页对象
     * @param convertor 转换函数
     */
    default <K> Page<K> pageVo(Page<T> page, Function<Collection<T>, List<K>> convertor) {
        return pageVo(page, Wrappers.emptyWrapper(), convertor);
    }

    /**
     * 翻页查询(通过原生Page) 转换为VO
     *
     * @param page         翻页对象
     * @param queryWrapper 实体对象封装操作类
     * @param convertor    转换函数
     */
    default <K> Page<K> pageVo(Page<T> page, Wrapper<T> queryWrapper,
                               Function<Collection<T>, List<K>> convertor) {
        Page<T> result = getBaseMapper().selectPage(page, queryWrapper);
        Page<K> kResult = (Page<K>) result;
        kResult.setRecords(convertor.apply(result.getRecords()));
        return kResult;
    }

    /**
     * 翻页查询(通过原生Page) 转换为VO
     *
     * @param page         翻页对象
     * @param queryWrapper 实体对象封装操作类
     */
    default <K> Page<K> pageVo(Page<T> page, Wrapper<T> queryWrapper, Class<K> kClass) {
        Page<T> result = getBaseMapper().selectPage(page, queryWrapper);
        return coverPage(result, kClass);
    }


    default Page<T> pageVo(PageQuery query) {
        return pageVo(query, Wrappers.emptyWrapper());
    }

    default Page<T> pageVo(PageQuery query, Wrapper<T> queryWrapper) {
        return getBaseMapper().selectPage(convertPage(query), queryWrapper);
    }

    /**
     * @param query
     * @param kClass
     * @Description: 无条件翻页查询(通过PageQuery转换) 转换为VO
     * @return: com.baomidou.mybatisplus.extension.plugins.pagination.Page<K>
     * @date: 2021/08/08
     */
    default <K> Page<K> pageVo(PageQuery query, Class<K> kClass) {
        return pageVo(convertPage(query), kClass);
    }

    /**
     * @param query
     * @param convertor
     * @Description: 无条件翻页查询(通过PageQuery转换) 转换为VO
     * @return: com.baomidou.mybatisplus.extension.plugins.pagination.Page<K>
     * @date: 2021/08/08
     */
    default <K> Page<K> pageVo(PageQuery query, Function<Collection<T>, List<K>> convertor) {
        return pageVo(convertPage(query), convertor);
    }

    /**
     * @param query
     * @param queryWrapper
     * @param convertor
     * @Description: 翻页查询(通过PageQuery转换) 转换为VO
     * @return: com.baomidou.mybatisplus.extension.plugins.pagination.Page<K>
     * @date: 2021/08/08
     */
    default <K> Page<K> pageVo(PageQuery query, Wrapper<T> queryWrapper,
                               Function<Collection<T>, List<K>> convertor) {
        return pageVo(convertPage(query), queryWrapper, convertor);
    }

    /**
     * 翻页查询(通过PageQuery转换) 转换为VO
     *
     * @param query        翻页对象
     * @param queryWrapper 实体对象封装操作类
     */
    default <K> Page<K> pageVo(PageQuery query, Wrapper<T> queryWrapper, Class<K> kClass) {
        return pageVo(convertPage(query), queryWrapper, kClass);
    }

    /**
     * @param page
     * @Description: 查询PagerVo分页对象(通过原生Page)
     * @return: com.lly.datacover.page.Pager<T>
     * @date: 2021/08/08
     */
    default Pager<T> pager(Page<T> page) {
        return coverPager(page(page));
    }

    /**
     * @param page
     * @Description: 查询PagerVo分页对象(通过原生Page)
     * @return: com.lly.datacover.page.Pager<T>
     * @date: 2021/08/08
     */
    default Pager<T> pager(Page<T> page, Wrapper<T> queryWrapper) {
        return coverPager(page(page, queryWrapper));
    }


    /**
     * @param page
     * @param convertor
     * @Description: 查询PagerVo分页对象(通过原生Page) 转换为VO
     * @return: com.lly.web.model.dto.Pager<T>
     * @date: 2021/07/30
     */
    default <K> Pager<K> pagerVo(Page<T> page, Function<Collection<T>, List<K>> convertor) {
        return coverPager(pageVo(page, convertor));
    }

    /**
     * @param page
     * @param queryWrapper
     * @param convertor
     * @Description: 查询PagerVo分页对象(通过原生Page) 转换为VO
     * @return: com.lly.web.model.dto.Pager<T>
     * @date: 2021/07/30
     */
    default <K> Pager<K> pagerVo(Page<T> page, Wrapper<T> queryWrapper,
                                 Function<Collection<T>, List<K>> convertor) {
        return coverPager(pageVo(page, queryWrapper, convertor));
    }

    /**
     * @param page
     * @param kClass
     * @Description: 查询PagerVo分页对象(通过原生Page) 转换为VO
     * @return: com.lly.web.model.dto.Pager<T>
     * @date: 2021/07/30
     */
    default <K> Pager<K> pagerVo(Page<T> page, Class<K> kClass) {
        return coverPager(pageVo(page, kClass));
    }

    /**
     * @param page
     * @param queryWrapper
     * @param kClass
     * @Description: 查询PagerVo分页对象(通过原生Page) 转换为VO
     * @return: com.lly.web.model.dto.Pager<T>
     * @date: 2021/07/30
     */
    default <K> Pager<K> pagerVo(Page<T> page, Wrapper<T> queryWrapper, Class<K> kClass) {
        return coverPager(pageVo(page,queryWrapper,kClass));
    }

    /**
     * @param query
     * @Description: 查询PagerVo分页对象(通过PageQuery转换)
     * @return: com.lly.datacover.page.Pager<T>
     * @date: 2021/08/08
     */
    default Pager<T> pager(PageQuery query) {
        return coverPager(pageVo(query));
    }

    /**
     * @param query
     * @param queryWrapper
     * @Description: 查询PagerVo分页对象(通过PageQuery转换)
     * @return: com.lly.datacover.page.Pager<T>
     * @date: 2021/08/08
     */
    default Pager<T> pager(PageQuery query, Wrapper<T> queryWrapper) {
        return coverPager(pageVo(query,queryWrapper));
    }

    /**
     * @param query
     * @param convertor
     * @Description: 查询PagerVo分页对象(通过PageQuery转换) 转换为VO
     * @return: com.lly.datacover.page.Pager<T>
     * @date: 2021/08/08
     */
    default <K>Pager<K> pagerVo(PageQuery query, Function<Collection<T>, List<K>> convertor) {
        return coverPager(pageVo(query, convertor));
    }

    /**
     * @param query
     * @param queryWrapper
     * @param convertor
     * @Description: 查询PagerVo分页对象(通过PageQuery转换) 转换为VO
     * @return: com.lly.datacover.page.Pager<T>
     * @date: 2021/08/08
     */
    default <K>Pager<K> pagerVo(PageQuery query, Wrapper<T> queryWrapper,
                             Function<Collection<T>, List<K>> convertor) {
        return coverPager(pageVo(query,queryWrapper,convertor));
    }

    /**
     * @param query
     * @param kClass
     * @Description: 查询PagerVo分页对象(通过PageQuery转换) 转换为VO
     * @return: com.lly.datacover.page.Pager<T>
     * @date: 2021/08/08
     */
    default <K>Pager<K> pagerVo(PageQuery query, Class<K> kClass) {
        return coverPager(pageVo(query,kClass));
    }

    /**
     * @param query
     * @param queryWrapper
     * @param kClass
     * @Description: 查询PagerVo分页对象(通过PageQuery转换) 转换为VO
     * @return: com.lly.datacover.page.Pager<T>
     * @date: 2021/08/08
     */
    default <K> Pager<K> pagerVo(PageQuery query, Wrapper<T> queryWrapper, Class<K> kClass) {
        return coverPager(pageVo(query,queryWrapper,kClass));
    }


    @Override
    default boolean saveBatch(Collection<T> entityList) {
        return saveBatch(entityList, DEFAULT_BATCH_SIZE);
    }

    @Override
    default boolean saveOrUpdateBatch(Collection<T> entityList) {
        return saveOrUpdateBatch(entityList, DEFAULT_BATCH_SIZE);
    }

    @Override
    default boolean updateBatchById(Collection<T> entityList) {
        return updateBatchById(entityList, DEFAULT_BATCH_SIZE);
    }


    /**
     * @Description:  将page<K> 转换为pager<K> K为泛型
     * @param page
     * @return: com.lly.datacover.page.Pager<K>
     * @date: 2021/08/09
     */
    default <K> Pager<K> coverPager(Page<K> page) {
        Pager<K> pager = new Pager<>();
        pager.setPagenum((int) page.getCurrent());
        pager.setPagesize((int) page.getSize());
        pager.setData(page.getRecords());
        return pager;
    }

    /**
     * @Description: 将page<po>转换为pager<vo>
     * @param page
     * @param kClass
     * @return: com.lly.datacover.page.Pager<K>
     * @date: 2021/08/09
     */
    default <K> Pager<K> coverPager(Page<T> page, Class<K> kClass) {
        Pager<K> pager = new Pager<>();
        pager.setPagenum((int) page.getCurrent());
        pager.setPagesize((int) page.getSize());
        List<K> volist = page.getRecords().stream()
                .map(any -> BeanUtils.toBean(any, kClass))
                .collect(Collectors.toList());
        pager.setData(volist);
        return pager;
    }

    /**
     * @param query
     * @Description: 将pageQuery对象转换为page对象
     * @return: com.baomidou.mybatisplus.extension.plugins.pagination.Page<T>
     * @date: 2021/08/08
     */
    default Page<T> convertPage(PageQuery query) {
        Page<T> page = null;
        if (null != query){
            page = new Page<>(query.getPagenum(),query.getPagenum());
            Order[] orders = query.getOrders();
            if (orders.length > 0){
                List<OrderItem> collect = Arrays.stream(orders).map(order ->
                        order.getOrdertype().equals(Order.Type.ASC) ?
                                OrderItem.asc(order.getOrderby()) : OrderItem.desc(order.getOrderby()))
                        .collect(Collectors.toList());
                page.setOrders(collect);
            }
        }
        return page;
    }

    /**
     * @Description: 将page中的实体转换为Vo
     * @param page
     * @param kClass
     * @return: com.baomidou.mybatisplus.extension.plugins.pagination.Page<K>
     * @date: 2021/08/09
     */
    default <K> Page<K> coverPage(Page<T> page, Class<K> kClass) {
        List<K> volist = page.getRecords().stream()
                .map(any -> BeanUtils.toBean(any, kClass))
                .collect(Collectors.toList());
        Page<K> Page = (Page<K>) page;
        Page.setRecords(volist);
        return Page;
    }
}

BaseService

java
package com.lly.db.mybatisplus.core.service;

import com.baomidou.mybatisplus.core.toolkit.ClassUtils;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.lly.db.mybatisplus.core.mapper.IBaseDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.ResolvableType;

import java.util.Collection;

/**
 * @Classname BaseService
 * @Description service基类
 * @Date 2021/07/18 15:27
 * @Created by wxp
 */
public class BaseService<M extends IBaseDao<T>, T> extends ServiceImpl<M, T> implements IService<T> {

    @Autowired
    protected M baseMapper;

    @Override
    public M getBaseMapper() {
        return baseMapper;
    }


    protected Class<T> entityClass = currentModelClass();

    @Override
    public Class<T> getEntityClass() {
        return entityClass;
    }

    protected Class<T> mapperClass = currentMapperClass();

    @Override
    protected Class<T> currentMapperClass() {
        return (Class<T>) this.getResolvableType().as(BaseService.class).getGeneric(0).getType();
    }

    @Override
    protected Class<T> currentModelClass() {
        return (Class<T>) this.getResolvableType().as(BaseService.class).getGeneric(1).getType();
    }

    @Override
    protected ResolvableType getResolvableType() {
        return ResolvableType.forClass(ClassUtils.getUserClass(getClass()));
    }

    /**
     * 单条执行性能差
     * <p>
     * {@link #saveAll(Collection)}
     */
    @Deprecated
    @Override
    public boolean saveBatch(Collection<T> entityList, int batchSize) {
        return super.saveBatch(entityList, batchSize);
    }

    @Override
    public boolean saveOrUpdate(T entity) {
        return super.saveOrUpdate(entity);
    }

    /**
     * 单条执行性能差
     * <p>
     * {@link #saveAll(Collection)}
     */
    @Deprecated
    @Override
    public boolean saveOrUpdateBatch(Collection<T> entityList, int batchSize) {
        return super.saveOrUpdateBatch(entityList, batchSize);
    }

    @Override
    public boolean updateBatchById(Collection<T> entityList, int batchSize) {
        return super.updateBatchById(entityList, batchSize);
    }

    @Override
    public boolean saveAll(Collection<T> entityList) {
        return baseMapper.insertAll(entityList) == entityList.size();
    }

}

2.3 多数据源

依赖

groovy
compile 'com.baomidou:dynamic-datasource-spring-boot-starter:3.4.0'

配置

yml
spring:
	datasource:
		# 动态数据源文档 https://www.kancloud.cn/tracy5546/dynamic-datasource/content
    dynamic:
      #设置默认的数据源或者数据源组,默认值即为 master
      primary: master
      datasource:
        # 主库数据源
        master:
          driverClassName: com.mysql.cj.jdbc.Driver
          url: jdbc:mysql://127.0.0.1:3306/ruoyi_vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true
          username: root
          password: 1234
        # 从库数据源
        slave:
          driverClassName: com.mysql.cj.jdbc.Driver
          url:
          username:
          password:

2.4 插件自动生成代码

依赖

xml
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-generator</artifactId>
    <version>3.1.1</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
groovy
implementation 'org.freemarker:freemarker:2.3.28'

工具类

java
package com.note.technology.mybatisplus.antogenerator;

import com.baomidou.mybatisplus.core.exceptions.MybatisPlusException;
import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.InjectionConfig;
import com.baomidou.mybatisplus.generator.config.*;
import com.baomidou.mybatisplus.generator.config.po.TableInfo;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
public class CodeGenerator {
    /**
    * <p>
    * 读取控制台内容
    * </p>
    */
    public static String scanner(String tip) {
        Scanner scanner = new Scanner(System.in);
        StringBuilder help = new StringBuilder();
        help.append("请输入" + tip + ":");
        System.out.println(help.toString());
        if (scanner.hasNext()) {
        	String ipt = scanner.next();
            if (StringUtils.isNotEmpty(ipt)) {
                return ipt;
            }
        }
        throw new MybatisPlusException("请输入正确的" + tip + "!");
    }
    public static void main(String[] args) {
        // 代码生成器
        AutoGenerator mpg = new AutoGenerator();
        // 全局配置
        GlobalConfig gc = new GlobalConfig();
        String projectPath = System.getProperty("user.dir");
        gc.setOutputDir(projectPath + "/src/main/java");
        gc.setAuthor("作者");
        gc.setOpen(false);
        mpg.setGlobalConfig(gc);
        // 数据源配置
        DataSourceConfig dsc = new DataSourceConfig();
        dsc.setUrl("jdbc:mysql://loclhost:3306/demo?useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true");
        // dsc.setSchemaName("public");
        dsc.setDriverName("com.mysql.jdbc.Driver");
        dsc.setUsername("root");
        dsc.setPassword("123456");
        mpg.setDataSource(dsc);
        // 包配置
         PackageConfig pc = new PackageConfig();
        pc.setModuleName(scanner("模块名"));
        pc.setParent("com.demo");
        mpg.setPackageInfo(pc);
        // 自定义配置
        InjectionConfig cfg = new InjectionConfig() {
            @Override
            public void initMap() {
                // to do nothing
            }
        };
        List<FileOutConfig> focList = new ArrayList<>();
        focList.add(new FileOutConfig("/templates/mapper.xml.ftl") {
            @Override
            public String outputFile(TableInfo tableInfo) {
                // 自定义输入文件名称
                return projectPath + "/src/main/resources/mapper/" +pc.getModuleName()+ "/" + tableInfo.getEntityName() + "Mapper" +StringPool.DOT_XML;
            }
        });
        cfg.setFileOutConfigList(focList);
        mpg.setCfg(cfg);
        mpg.setTemplate(new TemplateConfig().setXml(null));
        // 策略配置
        StrategyConfig strategy = new StrategyConfig();
        strategy.setNaming(NamingStrategy.underline_to_camel);
        strategy.setColumnNaming(NamingStrategy.underline_to_camel);
//        strategy.setSuperEntityClass("继承父类");
        strategy.setEntityLombokModel(true);
        strategy.setRestControllerStyle(true);
        //strategy.setSuperControllerClass("com.baomidou.ant.common.BaseController");
        strategy.setInclude(scanner("表名"));
        strategy.setSuperEntityColumns("id");
        strategy.setControllerMappingHyphenStyle(true);
        strategy.setTablePrefix(pc.getModuleName() + "_");
        mpg.setStrategy(strategy);
        mpg.setTemplateEngine(new FreemarkerTemplateEngine());
        mpg.execute();
	}
}

2.5 与spring整合

依赖

xml
<!-- mybatis-plus插件依赖 -->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus</artifactId>
    <version>3.1.1</version>
</dependency>
<!-- MySql -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.47</version>
</dependency>
<!-- 连接池 -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.0.11</version>
</dependency>
<!--简化bean代码的工具包-->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
    <version>1.18.4</version>
</dependency>

配置

properties
# jdbc.properties
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/mp?
useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true&useSSL
=false
jdbc.username=root
jdbc.password=root

spring配置

xml
<context:property-placeholder location="classpath:*.properties"/>
    <!-- 定义数据源 -->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"destroy-method="close">
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
        <property name="driverClassName" value="${jdbc.driver}"/>
        <property name="maxActive" value="10"/>
        <property name="minIdle" value="5"/>
    </bean>
    <!--这里使用MP提供的sqlSessionFactory,完成了Spring与MP的整合-->
    <bean id="sqlSessionFactory" class="com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean">
    	<property name="dataSource" ref="dataSource"/>
    </bean>
    <!--扫描mapper接口,使用的依然是Mybatis原生的扫描器-->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    	<property name="basePackage" value="cn.itcast.mp.simple.mapper"/>
    </bean>
</beans>

三、基础知识

3.1 通用Mapper

mybatis-plus提供了一个基础的mapper类com.baomidou.mybatisplus.core.mapper.BaseMappermapper接口继承该接口即可获取基础的crud的操作方法。

基础方法有:增删改查(通过id、wrapper对象和map数据)、查询map、boject、分页Map数据、查询数量、查询单条数据、批量处理等方法。

3.2 常用注解

@TableField

在MP中通过@TableField注解可以指定字段的一些属性,常常解决的问题有2个:

1、对象中的属性名和字段名不一致的问题(非驼峰)

2、对象中的属性字段在表中不存在的问题

@TableName

指定数据库表名

3.3 id生成策略

java
package com.baomidou.mybatisplus.annotation;
import lombok.Getter;
/**
* 生成ID类型枚举类
*
* @author hubin
* @since 2015-11-10
*/
@Getter
public enum IdType {
    /**
    * 数据库ID自增
    */
    AUTO(0),
    
    /**
    * 该类型为未设置主键类型
    */
    NONE(1),
    
    /**
    * 用户输入ID
    * <p>该类型可以通过自己注册自动填充插件进行填充</p>
    */
    INPUT(2),
    
    /* 以下3种类型、只有当插入对象ID 为空,才自动填充。 */
    /**
    * 全局唯一ID (idWorker)
    */
    ID_WORKER(3),
    
    /**
    * 全局唯一ID (UUID)
    */
    UUID(4),
    
    /**
    * 字符串全局唯一ID (idWorker 的字符串表示)
    */
    ID_WORKER_STR(5);
    
    private final int key;
    
    IdType(int key) {
    	this.key = key;
    }
}

使用方式,在model代表的表的主键字段上添加注解@TableId(type = IdType.AUTO) //指定id类型为自增长

3.4 条件查询

分页查询

分页查询需在配置类上添加对应的分页插件

java
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@MapperScan("cn.itcast.mp.mapper") //设置mapper接口的扫描包
public class MybatisPlusConfig {
    /**
    * 分页插件
    */
    @Bean
    public PaginationInterceptor paginationInterceptor() {
        return new PaginationInterceptor();
	}
}

查询对象

在MP中,Wrapper接口存在多种实现,其中AbstractWrapper和AbstractChainWrapper是重点实现。官网文档地址:https://mybatis.plus/guide/wrapper.html

  • 主要方法

    java
    // 方法可通过wrapper进行链式调用
    
    // allEq
    // 全部eq(或个别isNull)
    // params : key 为数据库字段名, value 为字段值
    // null2IsNull : 为 true 则在 map 的 value 为null 时调用 isNull 方法,为 false 时则忽略 value 为 null 的
    allEq(Map<R, V> params, boolean null2IsNull);
    // filter : 过滤函数,是否允许字段传入比对条件中 
    allEq(BiPredicate<R, V> filter, Map<R, V> params, boolean null2IsNull);
    // eq  等于 =
    // ne 不等于 <>
    // gt 大于 >
    // ge 大于等于 >=
    // lt 小于 <
    // le 小于等于 <=
    // between BETWEEN 值1 AND 值2
    // notBetween NOT BETWEEN 值1 AND 值2
    // in 字段 IN (value.get(0), value.get(1), ...)
    // like LIKE '%值%'  notLike  likeLeft '%值' likeRight '值%'
    // orderBy  默认asc 升序  orderByAsc  orderByDesc (降序)
    // or  拼接 OR  链接判断的eq、ne等逻辑判断
    // and  AND 嵌套
    // select   在MP查询中,默认查询所有的字段,如果有需要也可以通过select方法进行指定字段。
  • 查询对象

    java
    QueryWrapper wrapper = new QueryWrapper();

3.5 ActiveRecord

ActiveRecord(简称AR)一直广受动态语言( PHP 、 Ruby 等)的喜爱,而 Java 作为准静态语言,对于 ActiveRecord 往往只能感叹其优雅

什么是ActiveRecord?

ActiveRecord也属于ORM(对象关系映射)层,由Rails最早提出,遵循标准的ORM模型:表映射到记录,记 录映射到对象,字段映射到对象属性。配合遵循的命名和配置惯例,能够很大程度的快速实现模型的操作,而 且简洁易懂。 ActiveRecord的主要思想是: 每一个数据库表对应创建一个类,类的每一个对象实例对应于数据库中表的一行记录;通常表的每个字段 在类中都有相应的Field; ActiveRecord同时负责把自己持久化,在ActiveRecord中封装了对数据库的访问,即CURD;; ActiveRecord是一种领域模型(Domain Model),封装了部分业务逻辑;

  • 实体类继承Model

    实体类继承com.baomidou.mybatisplus.extension.activerecord.Model;

    java
    import com.baomidou.mybatisplus.extension.activerecord.Model;
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class User extends Model<User> {
        private Long id;
        private String name;
        private Integer age;
    }
  • 增删改查

    java
    // 通过id查询
    User user = new User();
    user.setId(2L);
    User user2 = user.selectById();
    
    // 新增
    User user = new User();
    user.setName("刘备");
    user.setAge(30);
    boolean insert = user.insert();
    
    // 更新
    User user = new User();
    user.setId(8L);
    user.setAge(35);
    boolean update = user.updateById();
    
    // 删除
    User user = new User();
    user.setId(7L);
    boolean delete = user.deleteById();
    
    // 条件查询
    User user = new User();
    QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
    userQueryWrapper.le("age","20");
    List<User> users = user.selectList(userQueryWrapper);

3.6 Oracle主键

oracle不能使用主键自增长,需要使用Sequence 序列生成id值。

  • oracle驱动包

    xml
    <dependency>
        <groupId>com.oracle</groupId>
        <artifactId>ojdbc8</artifactId>
        <version>12.1.0.1</version>
    </dependency>
  • 配置

    properties
    #数据库连接配置
    spring.datasource.driver-class-name=oracle.jdbc.OracleDriver
    spring.datasource.url=jdbc:oracle:thin:@192.168.31.81:1521:xe
    spring.datasource.username=system
    spring.datasource.password=oracle
    #id生成策略
    mybatis-plus.global-config.db-config.id-type=input
  • 配置序列

    java
    /**
    * 序列生成器
    */
    @Bean
    public OracleKeyGenerator oracleKeyGenerator(){
    	return new OracleKeyGenerator();
    }
  • 在实体类中指定序列的名称

    java
    @KeySequence(value = "SEQ_USER", clazz = Long.class)
    public class User{
    	......
    }

    注意,新增数据的主键值会填到实体类的对象中。

3.7 逻辑删除

在需要逻辑删除的表中添加逻辑删除字段,并在逻辑删除对应的字段上添加@TableLogic注解

并添加配置

properties
# 逻辑已删除值(默认为 1)
mybatis-plus.global-config.db-config.logic-delete-value=1
# 逻辑未删除值(默认为 0)
mybatis-plus.global-config.db-config.logic-not-delete-value=0

3.8 枚举属性

mybatisplus可将实体类中的枚举类型获取到它的值拼接到sql的值中。增删改查均有效。

需添加配置

properties
# 枚举包扫描
mybatis-plus.type-enums-package=cn.itcast.mp.enums

四、拓展

4.1 配置

配置文件

MyBatis 配置文件位置,如果您有单独的 MyBatis 配置,请将其路径配置到 configLocation 中。 MyBatis Configuration 的具体内容请参考MyBatis 官方文档

properties
# springboot
mybatis-plus.config-location = classpath:mybatis-config.xml
xml
<!--springMVC-->
<bean id="sqlSessionFactory" class="com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean">
	<property name="configLocation" value="classpath:mybatis-config.xml"/>
</bean>

mapperLocations

如果Mapper中存在自定义方法,需要告知mapperxml的位置

properties
# springboot
mybatis-plus.mapper-locations = classpath*:mybatis/*.xml
xml
<!-- springmvc -->
<bean id="sqlSessionFactory" class="com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean">
	<property name="mapperLocations" value="classpath*:mybatis/*.xml"/>
</bean>

typeAliasesPackage

MyBaits 别名包扫描路径,通过该属性可以给包中的类注册别名,注册后在 Mapper 对应的 XML 文件中可以直接使 用类名,而不用使用全限定的类名(即 XML 中调用的时候不用包含包名)。

properties
# springboot
mybatis-plus.type-aliases-package = cn.itcast.mp.pojo
xml
<!--springmvc-->
<bean id="sqlSessionFactory" class="com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean">
	<property name="typeAliasesPackage" value="com.baomidou.mybatisplus.samples.quickstart.entity"/>
</bean>

mapUnderscoreToCamelCase

是否开启自动驼峰命名规则(camel case)映射,即从经典数据库列名 A_COLUMN(下划线命名) 到经典 Java 属 性名 aColumn(驼峰命名) 的类似映射。

注意:

此属性在 MyBatis 中原默认值为 false,在 MyBatis-Plus 中,此属性也将用于生成最终的 SQL 的 select body

如果您的数据库命名符合规则无需使用 @TableField 注解指定数据库字段名

properties
# springboot
#关闭自动驼峰映射,该参数不能和mybatis-plus.config-location同时存在
mybatis-plus.configuration.map-underscore-to-camel-case=false

cacheEnabled

全局地开启或关闭配置文件中的所有映射器已经配置的任何缓存,默认为 true。

properties
mybatis-plus.configuration.cache-enabled=false

idType

全局默认主键类型,设置后,即可省略实体对象中的@TableId(type = IdType.AUTO)配置。

properties
# springboot
mybatis-plus.global-config.db-config.id-type=auto
xml
<bean id="sqlSessionFactory" class="com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean">
	<property name="dataSource" ref="dataSource"/>
	<property name="globalConfig">
        <bean class="com.baomidou.mybatisplus.core.config.GlobalConfig">
            <property name="dbConfig">
                <bean class="com.baomidou.mybatisplus.core.config.GlobalConfig$DbConfig">
                    <property name="idType" value="AUTO"/>
                </bean>
            </property>
        </bean>
	</property>
</bean>

tablePrefix

表名前缀,全局配置后可省略@TableName()配置。

properties
# springboot
 mybatis-plus.global-config.db-config.table-prefix=tb_
xml
<!--springmvc-->
<bean id="sqlSessionFactory" class="com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean">
	<property name="dataSource" ref="dataSource"/>
	<property name="globalConfig">
        <bean class="com.baomidou.mybatisplus.core.config.GlobalConfig">
            <property name="dbConfig">
                <bean class="com.baomidou.mybatisplus.core.config.GlobalConfig$DbConfig">
                    <property name="idType" value="AUTO"/>
                    <property name="tablePrefix" value="tb_"/>
                </bean>
            </property>
        </bean>
	</property>
</bean>

log-impl

查阅执行期SQL语句

properties
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

4.2 插件

mybatis允许在已映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法 调用包括:

  1. Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
  2. ParameterHandler (getParameterObject, setParameters)
  3. ResultSetHandler (handleResultSets, handleOutputParameters)
  4. StatementHandler (prepare, parameterize, batch, update, query)

可以拦截Executor接口的部分方法,比如update,query,commit,rollback等方法,还有其他接口的 一些方法等。 总体概括为:

  1. 拦截执行器的方法
  2. 拦截参数的处理
  3. 拦截结果集的处理
  4. 拦截Sql语法构建的处理

拦截器定义示例

java
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import java.util.Properties;

@Intercepts({@Signature(
    type= Executor.class,
    method = "update",
    args = {MappedStatement.class,Object.class})})
public class MyInterceptor implements Interceptor {
    
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        //拦截方法,具体业务逻辑编写的位置
        return invocation.proceed();
    }
    @Override
    public Object plugin(Object target) {
        //创建target对象的代理对象,目的是将当前拦截器加入到该对象中
        return Plugin.wrap(target, this);
    }
    @Override
    public void setProperties(Properties properties) {
        //属性设置
    }
}
java
// 注入到spring容器
/**
* 自定义拦截器
*/
@Bean
public MyInterceptor myInterceptor(){
	return new MyInterceptor();
}
xml
<!--配置方式-->
<configuration>
    <plugins>
    	<plugin interceptor="cn.itcast.mp.plugins.MyInterceptor"></plugin>
    </plugins>
</configuration>

执行分析插件

在MP中提供了对SQL执行的分析的插件,可用作阻断全表更新、删除的操作

java
@Bean
public SqlExplainInterceptor sqlExplainInterceptor(){
    SqlExplainInterceptor sqlExplainInterceptor = new SqlExplainInterceptor();
    List<ISqlParser> sqlParserList = new ArrayList<>();
    // 攻击 SQL 阻断解析器、加入解析链
    sqlParserList.add(new BlockAttackSqlParser());
    sqlExplainInterceptor.setSqlParserList(sqlParserList);
    return sqlExplainInterceptor;
}

性能分析插件

性能分析拦截器,用于输出每条 SQL 语句及其执行时间,可以设置最大执行时间,超过时间会抛出异常。不建议生产使用

xml
<configuration>
    <plugins>
        <!-- SQL 执行性能分析,开发环境使用,线上不推荐。 maxTime 指的是 sql 最大执行时长 -->
        <plugin interceptor="com.baomidou.mybatisplus.extension.plugins.PerformanceInterceptor">
            <property name="maxTime" value="100" />
            <!--SQL是否格式化 默认false-->
            <property name="format" value="true" />
        </plugin>
    </plugins>
</configuration>

乐观锁插件

当要更新一条记录的时候,希望这条记录没有被别人更新

乐观锁实现方式:

取出记录时,获取当前version

更新时,带上这个version

执行更新时, set version = newVersion where version = oldVersion 如果version不对,就更新失败

xml
<!--spring-->
<bean class="com.baomidou.mybatisplus.extension.plugins.OptimisticLockerInterceptor"/>
java
// springboot
@Bean
public OptimisticLockerInterceptor optimisticLockerInterceptor() {
	return new OptimisticLockerInterceptor();
}

使用方式

java
// 1. 在表中添加version字段
// 2. 在实体类对象添加version对应的字段和标注@Version注解

支持的数据类型只有:int,Integer,long,Long,Date,Timestamp,LocalDateTime

整数类型下 newVersion = oldVersion + 1 newVersion 会回写到 entity 中

仅支持 updateById(id) 与 update(entity, wrapper) 方法

在 update(entity, wrapper) 方法下, wrapper 不能复用!!!

4.3 sql注入器(实现拓展BaseMapper方法)

编写BaseMapper类

java
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import java.util.List;

public interface MyBaseMapper<T> extends BaseMapper<T> {
	List<T> findAll();
}

拓展后,其他mapper需要继承该mapper

编写sqlInjector

直接继承AbstractSqlInjector的话,原有的BaseMapper中的方法将失效,所以我们选择继承DefaultSqlInjector 进行扩展。

java
import com.baomidou.mybatisplus.core.injector.AbstractMethod;
import com.baomidou.mybatisplus.core.injector.DefaultSqlInjector;
import java.util.List;

public class MySqlInjector extends DefaultSqlInjector {
    
    @Override
    public List<AbstractMethod> getMethodList() {
        List<AbstractMethod> methodList = super.getMethodList();
        methodList.add(new FindAll());
        // 再扩充自定义的方法
        list.add(new FindAll());
        return methodList;
    }
}

编写findall逻辑

java
import com.baomidou.mybatisplus.core.enums.SqlMethod;
import com.baomidou.mybatisplus.core.injector.AbstractMethod;
import com.baomidou.mybatisplus.core.metadata.TableInfo;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlSource;

public class FindAll extends AbstractMethod {
    @Override
    public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
        String sqlMethod = "findAll";
        String sql = "select * from " + tableInfo.getTableName();
        SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass);
        return this.addSelectMappedStatement(mapperClass, sqlMethod, sqlSource, modelClass, tableInfo);
    }
}

将sql注入器注入spring容器

java
/**
* 自定义SQL注入器
*/
@Bean
public MySqlInjector mySqlInjector(){
	return new MySqlInjector();
}

4.4 自动填充功能(插入或更新时自动填充某些字段数据)

添加@TableField注解

java
//插入数据时进行填充  DEFAULT 默认不处理  INSERT 插入时填充   UPDATE 更新时填充  INSERT_UPDATE 插入和更新时填充字段
@TableField(fill = FieldFill.INSERT) 
private String password;

编写MetaObjectHandler

java
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;

@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
    @Override
    public void insertFill(MetaObject metaObject) {
        Object password = getFieldValByName("password", metaObject);
        if(null == password){
            //字段为空,可以进行填充
            setFieldValByName("password", "123456", metaObject);
        }
    }
    @Override
    public void updateFill(MetaObject metaObject) {
    }
}