一、SpringBoot+MybatisPlus+P6spy环境搭建

练习代码已上传到CSDN资源中

SpringBoot 整合Mybatis-plus

一、环境搭建

一、SpringBoot+MybatisPlus+P6spy环境搭建_第1张图片

1.pom.xml依赖


<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0modelVersion>
    <parent>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-parentartifactId>
        <version>2.3.0.RELEASEversion>
        <relativePath/> 
    parent>
    <groupId>com.mybatisplusgroupId>
    <artifactId>mybatisplugsdemoartifactId>
    <version>0.0.1-SNAPSHOTversion>
    <name>mybatisplugsdemoname>
    <description>Demo project for Spring Bootdescription>

    <properties>
        <java.version>1.8java.version>
    properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-thymeleafartifactId>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webartifactId>
        dependency>

        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-devtoolsartifactId>
            <scope>runtimescope>
            <optional>trueoptional>
        dependency>
        <dependency>
            <groupId>mysqlgroupId>
            <artifactId>mysql-connector-javaartifactId>
            <scope>runtimescope>
        dependency>
        <dependency>
            <groupId>org.projectlombokgroupId>
            <artifactId>lombokartifactId>
            <optional>trueoptional>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-testartifactId>
            <scope>testscope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintagegroupId>
                    <artifactId>junit-vintage-engineartifactId>
                exclusion>
            exclusions>
        dependency>
        <dependency>
            <groupId>com.baomidougroupId>
            <artifactId>mybatis-plus-boot-starterartifactId>
            <version>3.2.0version>druid
        dependency>
        <dependency>
            <groupId>p6spygroupId>
            <artifactId>p6spyartifactId>
            <version>3.8.5version>
        dependency>
        <dependency>
            <groupId>junitgroupId>
            <artifactId>junitartifactId>
            <scope>testscope>
        dependency>

    dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-maven-pluginartifactId>
            plugin>
        plugins>
    build>

project>

2.application.yml

spring:
  datasource:
    driver-class-name: com.p6spy.engine.spy.P6SpyDriver
    url: jdbc:p6spy:mysql://192.168.1.2:3306/mp?serverTimezone=CTT&useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&useSSL=true
    username: root
    password: root
  application:
    name: mybatisplus

3.spy.properties配置文件

module.log=com.p6spy.engine.logging.P6LogFactory,com.p6spy.engine.outage.P6OutageFactory
# 自定义日志打印
logMessageFormat=com.p6spy.engine.spy.appender.MultiLineFormat
# 使用日志系统记录sql
#appender=com.p6spy.engine.logging.appender.StdoutLogger
appender=com.p6spy.engine.spy.appender.StdoutLogger
#appender=com.p6spy.engine.logging.appender.FileLogger
#logfile  = d:/spy.log
## 配置记录Log例外
#excludecategories=info,debug,result,batc,resultset
# 设置使用p6spy driver来做代理
deregisterdrivers=true
# 日期格式
dateformat=yyyy-MM-dd HH:mm:ss
# 实际驱动
driverlist=com.mysql.cj.jdbc.Driver
# 是否开启慢SQL记录
outagedetection=true
# 慢SQL记录标准 秒
outagedetectioninterval=2

4.创建实体类

//TODO 实体类需要注意
/*
1. @TableName
2.TableId
* */
@Data
@TableName("tbl_employee")
public class Employee implements Serializable {
    @TableId(type = IdType.AUTO)
    private Integer id;
    private String lastName;
    private String email;
    private Integer gender;
    private Integer age;

}

sql语句

-- 创建库
CREATE DATABASE mp;
-- 使用库
USE mp;
-- 创建表
CREATE TABLE tbl_employee(
 id INT(11) PRIMARY KEY AUTO_INCREMENT,
 last_name VARCHAR(50),
 email VARCHAR(50),
 gender CHAR(1),
 age INT
);
INSERT INTO tbl_employee(last_name,email,gender,age) VALUES('Tom','[email protected]',1,22);
INSERT INTO tbl_employee(last_name,email,gender,age) VALUES('Jerry','[email protected]',0,25);
INSERT INTO tbl_employee(last_name,email,gender,age) VALUES('Black','[email protected]',1,30);
INSERT INTO tbl_employee(last_name,email,gender,age) VALUES('White','[email protected]',0,35);

5.创建Mapper

// TODO mapper的写法: mapper 继承 BaseMapper  T 实体类

public interface EmployeeMapper extends BaseMapper<Employee> {
}

6.启动类

@SpringBootApplication
@MapperScan("com.mybatisplus.mybatisplugsdemo.mapper")
public class MybatisplugsdemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(MybatisplugsdemoApplication.class, args);
    }

}
// 注意:Springboot启动类放的位置,扫描的是当前包及子包。一般建议放到com.xxx
/*
	比如:
		com.mybatisplus        存放启动类
		com.mybatisplus.controller
		com.mybatisplus.mapper
		
*/

7.测试类

@RunWith(SpringRunner.class)
@SpringBootTest
class MybatisplugsdemoApplicationTests {

    @Test
    void contextLoads() {
    }

}
public class Test01 extends MybatisplugsdemoApplicationTests {

    @Resource
   private EmployeeMapper employeeMapper;

    @Test
    public void fn(){
        /**
         * 查询所有数据
         */
        employeeMapper.selectList(null).forEach(System.out::println);
    }
}

只需要创建 EmployeeMapper 接口, 并继承 BaseMapper 接口.这就是使用 MP需要完成的所有操作,甚至不需要创建 SQL 映射文件。

二、BaseMapper 、Wrapper源码

1.BaseMapper.java

/**
 * Mapper 继承该接口后,无需编写 mapper.xml 文件,即可获得CRUD功能
 * 

这个 Mapper 支持 id 泛型

* * @author hubin * @since 2016-01-23 */
public interface BaseMapper<T> extends Mapper<T> { /** * 插入一条记录 * * @param entity 实体对象 */ int insert(T entity); /** * 根据 ID 删除 * * @param id 主键ID */ int deleteById(Serializable id); /** * 根据 columnMap 条件,删除记录 * * @param columnMap 表字段 map 对象 */ int deleteByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap); /** * 根据 entity 条件,删除记录 * * @param wrapper 实体对象封装操作类(可以为 null) */ int delete(@Param(Constants.WRAPPER) Wrapper<T> wrapper); /** * 删除(根据ID 批量删除) * * @param idList 主键ID列表(不能为 null 以及 empty) */ int deleteBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList); /** * 根据 ID 修改 * * @param entity 实体对象 */ int updateById(@Param(Constants.ENTITY) T entity); /** * 根据 whereEntity 条件,更新记录 * * @param entity 实体对象 (set 条件值,可以为 null) * @param updateWrapper 实体对象封装操作类(可以为 null,里面的 entity 用于生成 where 语句) */ int update(@Param(Constants.ENTITY) T entity, @Param(Constants.WRAPPER) Wrapper<T> updateWrapper); /** * 根据 ID 查询 * * @param id 主键ID */ T selectById(Serializable id); /** * 查询(根据ID 批量查询) * * @param idList 主键ID列表(不能为 null 以及 empty) */ List<T> selectBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList); /** * 查询(根据 columnMap 条件) * * @param columnMap 表字段 map 对象 */ List<T> selectByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap); /** * 根据 entity 条件,查询一条记录 * * @param queryWrapper 实体对象封装操作类(可以为 null) */ T selectOne(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper); /** * 根据 Wrapper 条件,查询总记录数 * * @param queryWrapper 实体对象封装操作类(可以为 null) */ Integer selectCount(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper); /** * 根据 entity 条件,查询全部记录 * * @param queryWrapper 实体对象封装操作类(可以为 null) */ List<T> selectList(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper); /** * 根据 Wrapper 条件,查询全部记录 * * @param queryWrapper 实体对象封装操作类(可以为 null) */ List<Map<String, Object>> selectMaps(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper); /** * 根据 Wrapper 条件,查询全部记录 *

注意: 只返回第一个字段的值

* * @param queryWrapper 实体对象封装操作类(可以为 null) */
List<Object> selectObjs(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper); /** * 根据 entity 条件,查询全部记录(并翻页) * * @param page 分页查询条件(可以为 RowBounds.DEFAULT) * @param queryWrapper 实体对象封装操作类(可以为 null) */ IPage<T> selectPage(IPage<T> page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper); /** * 根据 Wrapper 条件,查询全部记录(并翻页) * * @param page 分页查询条件 * @param queryWrapper 实体对象封装操作类 */ IPage<Map<String, Object>> selectMapsPage(IPage<T> page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper); }

Wrapper 表示包装

2.Wrapper.java

/**
 * 条件构造抽象类
 *
 * @author hubin
 * @since 2018-05-25
 */
@SuppressWarnings("all")
public abstract class Wrapper<T> implements ISqlSegment {

    /**
     * 实体对象(子类实现)
     *
     * @return 泛型 T
     */
    public abstract T getEntity();

    public String getSqlSelect() {
        return null;
    }

    public String getSqlSet() {
        return null;
    }

    public String getSqlComment() {
        return null;
    }

    /**
     * 获取 MergeSegments
     */
    public abstract MergeSegments getExpression();

    /**
     * 获取自定义SQL 简化自定义XML复杂情况
     * 

使用方法

*

`自定义sql` + ${ew.customSqlSegment}

*

1.逻辑删除需要自己拼接条件 (之前自定义也同样)

*

2.不支持wrapper中附带实体的情况 (wrapper自带实体会更麻烦)

*

3.用法 ${ew.customSqlSegment} (不需要where标签包裹,切记!)

*

4.ew是wrapper定义别名,可自行替换

*/
public String getCustomSqlSegment() { MergeSegments expression = getExpression(); if (Objects.nonNull(expression)) { NormalSegmentList normal = expression.getNormal(); String sqlSegment = getSqlSegment(); if (StringUtils.isNotEmpty(sqlSegment)) { if (normal.isEmpty()) { return sqlSegment; } else { return Constants.WHERE + StringPool.SPACE + sqlSegment; } } } return StringPool.EMPTY; } /** * 查询条件为空(包含entity) */ public boolean isEmptyOfWhere() { return isEmptyOfNormal() && isEmptyOfEntity(); } /** * 查询条件不为空(包含entity) */ public boolean nonEmptyOfWhere() { return !isEmptyOfWhere(); } /** * 查询条件为空(不包含entity) */ public boolean isEmptyOfNormal() { return CollectionUtils.isEmpty(getExpression().getNormal()); } /** * 查询条件为空(不包含entity) */ public boolean nonEmptyOfNormal() { return !isEmptyOfNormal(); } /** * 深层实体判断属性 * * @return true 不为空 */ public boolean nonEmptyOfEntity() { T entity = getEntity(); if (entity == null) { return false; } TableInfo tableInfo = TableInfoHelper.getTableInfo(entity.getClass()); if (tableInfo == null) { return false; } if (tableInfo.getFieldList().stream().anyMatch(e -> fieldStrategyMatch(entity, e))) { return true; } return StringUtils.isNotEmpty(tableInfo.getKeyProperty()) ? Objects.nonNull(ReflectionKit.getMethodValue(entity, tableInfo.getKeyProperty())) : false; } /** * 根据实体FieldStrategy属性来决定判断逻辑 */ private boolean fieldStrategyMatch(T entity, TableFieldInfo e) { switch (e.getWhereStrategy()) { case NOT_NULL: return Objects.nonNull(ReflectionKit.getMethodValue(entity, e.getProperty())); case IGNORED: return true; case NOT_EMPTY: return StringUtils.checkValNotNull(ReflectionKit.getMethodValue(entity, e.getProperty())); case NEVER: return false; default: return Objects.nonNull(ReflectionKit.getMethodValue(entity, e.getProperty())); } } /** * 深层实体判断属性 * * @return true 为空 */ public boolean isEmptyOfEntity() { return !nonEmptyOfEntity(); } }

三、CRUD操作

四、条件查询器

3.x版本以前 条件查询器EntityWrapper.java ,3.x版本改为QueryWrapper.java

QueryChainWrapper extends AbstractChainWrapper

一、SpringBoot+MybatisPlus+P6spy环境搭建_第2张图片

//查询年龄在18-50 之间且性别为男姓名为tom的所有用户
employeerMapper.selectPage(new Page<Employeer>(1,4),new QueryWrapper<Employee>().between("age",18,50).eq("gender",1).eq("last_name","tom")).forEach(System.out::println);

AbstractWrapper.java

/**
 * 所有包装类都继承此抽象类,此抽象类代理了大部分生成 where 条件的方法
 * 
  • 泛型: Children ,表示子类
  • *
  • 泛型: Param ,表示子类所包装的具体 Wrapper 类型
  • * * @author miemie * @since 2018-12-19 */
    @SuppressWarnings({"serial", "unchecked"}) public abstract class AbstractChainWrapper<T, R, Children extends AbstractChainWrapper<T, R, Children, Param>, Param> extends Wrapper<T> implements Compare<Children, R>, Func<Children, R>, Join<Children>, Nested<Param, Children> { protected final Children typedThis = (Children) this; /** * 子类所包装的具体 Wrapper 类型 */ protected Param wrapperChildren; /** * 必须的构造函数 */ public AbstractChainWrapper() { } @SuppressWarnings("rawtypes") public AbstractWrapper getWrapper() { return (AbstractWrapper) wrapperChildren; } @Override public T getEntity() { throw ExceptionUtils.mpe("can not use this method for \"%s\"", "getEntity"); } @Override public MergeSegments getExpression() { throw ExceptionUtils.mpe("can not use this method for \"%s\"", "getExpression"); } @Override public String getCustomSqlSegment() { throw ExceptionUtils.mpe("can not use this method for \"%s\"", "getCustomSqlSegment"); } @Override public <V> Children allEq(boolean condition, Map<R, V> params, boolean null2IsNull) { getWrapper().allEq(condition, params, null2IsNull); return typedThis; } @Override public <V> Children allEq(boolean condition, BiPredicate<R, V> filter, Map<R, V> params, boolean null2IsNull) { getWrapper().allEq(condition, filter, params, null2IsNull); return typedThis; } @Override public Children eq(boolean condition, R column, Object val) { getWrapper().eq(condition, column, val); return typedThis; } @Override public Children ne(boolean condition, R column, Object val) { getWrapper().ne(condition, column, val); return typedThis; } @Override public Children gt(boolean condition, R column, Object val) { getWrapper().gt(condition, column, val); return typedThis; } @Override public Children ge(boolean condition, R column, Object val) { getWrapper().ge(condition, column, val); return typedThis; } @Override public Children lt(boolean condition, R column, Object val) { getWrapper().lt(condition, column, val); return typedThis; } @Override public Children le(boolean condition, R column, Object val) { getWrapper().le(condition, column, val); return typedThis; } @Override public Children between(boolean condition, R column, Object val1, Object val2) { getWrapper().between(condition, column, val1, val2); return typedThis; } @Override public Children notBetween(boolean condition, R column, Object val1, Object val2) { getWrapper().notBetween(condition, column, val1, val2); return typedThis; } @Override public Children like(boolean condition, R column, Object val) { getWrapper().like(condition, column, val); return typedThis; } @Override public Children notLike(boolean condition, R column, Object val) { getWrapper().notLike(condition, column, val); return typedThis; } @Override public Children likeLeft(boolean condition, R column, Object val) { getWrapper().likeLeft(condition, column, val); return typedThis; } @Override public Children likeRight(boolean condition, R column, Object val) { getWrapper().likeRight(condition, column, val); return typedThis; } @Override public Children isNull(boolean condition, R column) { getWrapper().isNull(condition, column); return typedThis; } @Override public Children isNotNull(boolean condition, R column) { getWrapper().isNotNull(condition, column); return typedThis; } @Override public Children in(boolean condition, R column, Collection<?> coll) { getWrapper().in(condition, column, coll); return typedThis; } @Override public Children notIn(boolean condition, R column, Collection<?> coll) { getWrapper().notIn(condition, column, coll); return typedThis; } @Override public Children inSql(boolean condition, R column, String inValue) { getWrapper().inSql(condition, column, inValue); return typedThis; } @Override public Children notInSql(boolean condition, R column, String inValue) { getWrapper().notInSql(condition, column, inValue); return typedThis; } @Override public Children groupBy(boolean condition, R... columns) { getWrapper().groupBy(condition, columns); return typedThis; } @Override public Children orderBy(boolean condition, boolean isAsc, R... columns) { getWrapper().orderBy(condition, isAsc, columns); return typedThis; } @Override public Children having(boolean condition, String sqlHaving, Object... params) { getWrapper().having(condition, sqlHaving, params); return typedThis; } @Override public Children or(boolean condition) { getWrapper().or(condition); return typedThis; } @Override public Children apply(boolean condition, String applySql, Object... value) { getWrapper().apply(condition, applySql, value); return typedThis; } @Override public Children last(boolean condition, String lastSql) { getWrapper().last(condition, lastSql); return typedThis; } @Override public Children comment(boolean condition, String comment) { getWrapper().comment(condition, comment); return typedThis; } @Override public Children exists(boolean condition, String existsSql) { getWrapper().exists(condition, existsSql); return typedThis; } @Override public Children notExists(boolean condition, String notExistsSql) { getWrapper().notExists(condition, notExistsSql); return typedThis; } @Override public Children and(boolean condition, Consumer<Param> consumer) { getWrapper().and(condition, consumer); return typedThis; } @Override public Children or(boolean condition, Consumer<Param> consumer) { getWrapper().or(condition, consumer); return typedThis; } @Override public Children nested(boolean condition, Consumer<Param> consumer) { getWrapper().nested(condition, consumer); return typedThis; } @Override public String getSqlSegment() { throw ExceptionUtils.mpe("can not use this method for \"%s\"", "getSqlSegment"); } }

    QueryWrapper.java

    
    /**
     * Entity 对象封装操作类
     *
     * @author hubin miemie HCL
     * @since 2018-05-25
     */
    @SuppressWarnings("serial")
    public class QueryWrapper<T> extends AbstractWrapper<T, String, QueryWrapper<T>>
        implements Query<QueryWrapper<T>, T, String> {
    
        /**
         * 查询字段
         */
        private SharedString sqlSelect = new SharedString();
    
        public QueryWrapper() {
            this(null);
        }
    
        public QueryWrapper(T entity) {
            super.setEntity(entity);
            super.initNeed();
        }
    
        public QueryWrapper(T entity, String... columns) {
            super.setEntity(entity);
            super.initNeed();
            this.select(columns);
        }
    
        /**
         * 非对外公开的构造方法,只用于生产嵌套 sql
         *
         * @param entityClass 本不应该需要的
         */
        private QueryWrapper(T entity, Class<T> entityClass, AtomicInteger paramNameSeq,
                             Map<String, Object> paramNameValuePairs, MergeSegments mergeSegments,
                             SharedString lastSql, SharedString sqlComment) {
            super.setEntity(entity);
            this.entityClass = entityClass;
            this.paramNameSeq = paramNameSeq;
            this.paramNameValuePairs = paramNameValuePairs;
            this.expression = mergeSegments;
            this.lastSql = lastSql;
            this.sqlComment = sqlComment;
        }
    
        @Override
        public QueryWrapper<T> select(String... columns) {
            if (ArrayUtils.isNotEmpty(columns)) {
                this.sqlSelect.setStringValue(String.join(StringPool.COMMA, columns));
            }
            return typedThis;
        }
    
        @Override
        public QueryWrapper<T> select(Predicate<TableFieldInfo> predicate) {
            return select(entityClass, predicate);
        }
    
        @Override
        public QueryWrapper<T> select(Class<T> entityClass, Predicate<TableFieldInfo> predicate) {
            this.entityClass = entityClass;
            this.sqlSelect.setStringValue(TableInfoHelper.getTableInfo(getCheckEntityClass()).chooseSelect(predicate));
            return typedThis;
        }
    
        @Override
        public String getSqlSelect() {
            return sqlSelect.getStringValue();
        }
    
        /**
         * 返回一个支持 lambda 函数写法的 wrapper
         */
        public LambdaQueryWrapper<T> lambda() {
            return new LambdaQueryWrapper<>(entity, entityClass, sqlSelect, paramNameSeq, paramNameValuePairs, expression,
                lastSql, sqlComment);
        }
    
        /**
         * 用于生成嵌套 sql
         * 

    * 故 sqlSelect 不向下传递 *

    */
    @Override protected QueryWrapper<T> instance() { return new QueryWrapper<>(entity, entityClass, paramNameSeq, paramNameValuePairs, new MergeSegments(), SharedString.emptyString(), SharedString.emptyString()); } }

    查询条件

    或者

    //或者  or()    (gender = ? and last_name like ? or email like ?)
    new QueryWrapper().eq("gender",0).like("last_name","老师").or().like("email","a");
    
    //orNew() (Gender = ? and last_name like ?)or(email liek ?)  3.x版本之后都没有这个方法
    new QueryWrapper().eq("gender",0).like("last_name","老师").orNew().like("email","a");
    

    条件修改

    //修改zx 的年龄为29 
    
    Employee employee = new Employee();
    employee.setAge(29);
    int update = employeeMapper.update(employee, new UpdateWrapper<Employee>().eq("last_name", "zx"));
    /*
    UPDATE tbl_employee  SET age=?  
     
     WHERE (last_name = ?)
    UPDATE tbl_employee  SET age=29  
     
     WHERE (last_name = 'zx');
    */
    

    排序

    //查询性别为 0 ,根据age排序 asc/desc   ,简单分页
     List<Employee> employees = employeeMapper.selectList(new QueryWrapper<Employee>()
                    .eq("gender", 0)
                    .orderByAsc("age")
                    //.orderByDesc("age","gender")
                    //last() 方法,有sql注入的风险
                    .last("limit 1,2")
    );
    /*
    SELECT  id,last_name,gender,age,email  FROM tbl_employee 
     WHERE (gender = ?) ORDER BY age ASC limit 1,2
     
     写法有很多种,根据自己习惯进行选择
    */
    

    五、AR模式【不推荐】

    仅仅需要让实体类继承 Model 类且实现主键指定方法,即可开启 AR 之旅.这个是版本在3.x以前的写法,3.x版本以后的写法就是直接继承Model

    @Data
    @TableName("tb_user")
    public class User extends Model<User> {
        @TableId(type = IdType.AUTO)
        private Integer id;
        private String name;
        private Integer age;
    
    	// mybatisplus版本在3.x以后就不用写
        protected Serializable pkVal() {
            return this.id; }
    }
    
     CREATE TABLE tb_user(
    	id INT PRIMARY KEY AUTO_INCREMENT,
    	`name` VARCHAR(50),
    	age INT 
     );
    

    错误:Not Found TableInfoCache.

    /*
    mybatisplus activerecord之mybatisplus entity XXX Not Found TableInfoCache.
    1.需要重写pkVal()方法  // mybatisplus版本在3.x以后就不用写
    2.编写mapper.比如:public interface UserMapper extends BaseMapper{ }
    */
    

    案例:

    public class TestModel  extends MybatisplugsdemoApplicationTests {
        //添加
        @Test
        public void fn(){
            User user = new User();
            user.setName("marry");
            user.setAge(11);
            user.insert();
        }
        //查询
        @Test
        public void findAll(){
            User user = new User();
            user.selectList(null).forEach(System.out::println);
        }
    }
    

    六、代码生成器

    AutoGenerator 是 MyBatis-Plus 的代码生成器,通过 AutoGenerator 可以快速生成 Entity、Mapper、Mapper XML、Service、Controller 等各个模块的代码,极大的提升了开发效率。

    说明

    自定义模板有哪些可用参数?Github Gitee AbstractTemplateEngine 类中方法 getObjectMap 返回 objectMap 的所有值都可用

    • 表及字段命名策略选择在 MP 中,我们建议数据库表名 和 表字段名采用驼峰命名方式, 如果采用下划线命名方式 请开启全局下划线开关,如果表名字段名命名方式不一致请注解指定,我们建议最好保持一致(2.3版本以后默认是开启的)。这么做的原因是为了避免在对应实体类时产生的性能损耗,这样字段不用做映射就能直接和实体类对应。当然如果项目里不用考虑这点性能损耗,那么你采用下滑线也是没问题的,只需要在生成代码时配置 dbColumnUnderline 属性就可以

    1.MP 的代码生成器默认使用的是 Apache 的 Velocity 模板,当然也可以更换为别的模板技术,例如 freemarker。

    <dependency>
        <groupId>org.apache.velocitygroupId>
        <artifactId>velocity-engine-coreartifactId>
        <version>2.2version>
    dependency>
    

    Strategy 策率

    顶级Service层

    IService.java

    /**
     * 顶级 Service
     *
     * @author hubin
     * @since 2018-06-23
     */
    public interface IService<T> {
    
        /**
         * 插入一条记录(选择字段,策略插入)
         *
         * @param entity 实体对象
         */
        boolean save(T entity);
    
        /**
         * 插入(批量)
         *
         * @param entityList 实体对象集合
         */
        @Transactional(rollbackFor = Exception.class)
        default boolean saveBatch(Collection<T> entityList) {
            return saveBatch(entityList, 1000);
        }
    
        /**
         * 插入(批量)
         *
         * @param entityList 实体对象集合
         * @param batchSize  插入批次数量
         */
        boolean saveBatch(Collection<T> entityList, int batchSize);
    
        /**
         * 批量修改插入
         *
         * @param entityList 实体对象集合
         */
        @Transactional(rollbackFor = Exception.class)
        default boolean saveOrUpdateBatch(Collection<T> entityList) {
            return saveOrUpdateBatch(entityList, 1000);
        }
    
        /**
         * 批量修改插入
         *
         * @param entityList 实体对象集合
         * @param batchSize  每次的数量
         */
        boolean saveOrUpdateBatch(Collection<T> entityList, int batchSize);
    
        /**
         * 根据 ID 删除
         *
         * @param id 主键ID
         */
        boolean removeById(Serializable id);
    
        /**
         * 根据 columnMap 条件,删除记录
         *
         * @param columnMap 表字段 map 对象
         */
        boolean removeByMap(Map<String, Object> columnMap);
    
        /**
         * 根据 entity 条件,删除记录
         *
         * @param queryWrapper 实体包装类 {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper}
         */
        boolean remove(Wrapper<T> queryWrapper);
    
        /**
         * 删除(根据ID 批量删除)
         *
         * @param idList 主键ID列表
         */
        boolean removeByIds(Collection<? extends Serializable> idList);
    
        /**
         * 根据 ID 选择修改
         *
         * @param entity 实体对象
         */
        boolean updateById(T entity);
    
        /**
         * 根据 whereEntity 条件,更新记录
         *
         * @param entity        实体对象
         * @param updateWrapper 实体对象封装操作类 {@link com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper}
         */
        boolean update(T entity, Wrapper<T> updateWrapper);
    
        /**
         * 根据 UpdateWrapper 条件,更新记录 需要设置sqlset
         *
         * @param updateWrapper 实体对象封装操作类 {@link com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper}
         */
        default boolean update(Wrapper<T> updateWrapper) {
            return update(null, updateWrapper);
        }
    
        /**
         * 根据ID 批量更新
         *
         * @param entityList 实体对象集合
         */
        @Transactional(rollbackFor = Exception.class)
        default boolean updateBatchById(Collection<T> entityList) {
            return updateBatchById(entityList, 1000);
        }
    
        /**
         * 根据ID 批量更新
         *
         * @param entityList 实体对象集合
         * @param batchSize  更新批次数量
         */
        boolean updateBatchById(Collection<T> entityList, int batchSize);
    
        /**
         * TableId 注解存在更新记录,否插入一条记录
         *
         * @param entity 实体对象
         */
        boolean saveOrUpdate(T entity);
    
        /**
         * 根据 ID 查询
         *
         * @param id 主键ID
         */
        T getById(Serializable id);
    
        /**
         * 查询(根据ID 批量查询)
         *
         * @param idList 主键ID列表
         */
        Collection<T> listByIds(Collection<? extends Serializable> idList);
    
        /**
         * 查询(根据 columnMap 条件)
         *
         * @param columnMap 表字段 map 对象
         */
        Collection<T> listByMap(Map<String, Object> columnMap);
    
        /**
         * 根据 Wrapper,查询一条记录 
    *

    结果集,如果是多个会抛出异常,随机取一条加上限制条件 wrapper.last("LIMIT 1")

    * * @param queryWrapper 实体对象封装操作类 {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper} */
    default T getOne(Wrapper<T> queryWrapper) { return getOne(queryWrapper, true); } /** * 根据 Wrapper,查询一条记录 * * @param queryWrapper 实体对象封装操作类 {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper} * @param throwEx 有多个 result 是否抛出异常 */ T getOne(Wrapper<T> queryWrapper, boolean throwEx); /** * 根据 Wrapper,查询一条记录 * * @param queryWrapper 实体对象封装操作类 {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper} */ Map<String, Object> getMap(Wrapper<T> queryWrapper); /** * 根据 Wrapper,查询一条记录 * * @param queryWrapper 实体对象封装操作类 {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper} * @param mapper 转换函数 */ <V> V getObj(Wrapper<T> queryWrapper, Function<? super Object, V> mapper); /** * 根据 Wrapper 条件,查询总记录数 * * @param queryWrapper 实体对象封装操作类 {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper} */ int count(Wrapper<T> queryWrapper); /** * 查询总记录数 * * @see Wrappers#emptyWrapper() */ default int count() { return count(Wrappers.emptyWrapper()); } /** * 查询列表 * * @param queryWrapper 实体对象封装操作类 {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper} */ List<T> list(Wrapper<T> queryWrapper); /** * 查询所有 * * @see Wrappers#emptyWrapper() */ default List<T> list() { return list(Wrappers.emptyWrapper()); } /** * 翻页查询 * * @param page 翻页对象 * @param queryWrapper 实体对象封装操作类 {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper} */ IPage<T> page(IPage<T> page, Wrapper<T> queryWrapper); /** * 无条件翻页查询 * * @param page 翻页对象 * @see Wrappers#emptyWrapper() */ default IPage<T> page(IPage<T> page) { return page(page, Wrappers.emptyWrapper()); } /** * 查询列表 * * @param queryWrapper 实体对象封装操作类 {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper} */ List<Map<String, Object>> listMaps(Wrapper<T> queryWrapper); /** * 查询所有列表 * * @see Wrappers#emptyWrapper() */ default List<Map<String, Object>> listMaps() { return listMaps(Wrappers.emptyWrapper()); } /** * 查询全部记录 */ default List<Object> listObjs() { return listObjs(Function.identity()); } /** * 查询全部记录 * * @param mapper 转换函数 */ default <V> List<V> listObjs(Function<? super Object, V> mapper) { return listObjs(Wrappers.emptyWrapper(), mapper); } /** * 根据 Wrapper 条件,查询全部记录 * * @param queryWrapper 实体对象封装操作类 {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper} */ default List<Object> listObjs(Wrapper<T> queryWrapper) { return listObjs(queryWrapper, Function.identity()); } /** * 根据 Wrapper 条件,查询全部记录 * * @param queryWrapper 实体对象封装操作类 {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper} * @param mapper 转换函数 */ <V> List<V> listObjs(Wrapper<T> queryWrapper, Function<? super Object, V> mapper); /** * 翻页查询 * * @param page 翻页对象 * @param queryWrapper 实体对象封装操作类 {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper} */ IPage<Map<String, Object>> pageMaps(IPage<T> page, Wrapper<T> queryWrapper); /** * 无条件翻页查询 * * @param page 翻页对象 * @see Wrappers#emptyWrapper() */ default IPage<Map<String, Object>> pageMaps(IPage<T> page) { return pageMaps(page, Wrappers.emptyWrapper()); } /** * 获取对应 entity 的 BaseMapper * * @return BaseMapper */ BaseMapper<T> getBaseMapper(); /** * 以下的方法使用介绍: * * 一. 名称介绍 * 1. 方法名带有 query 的为对数据的查询操作, 方法名带有 update 的为对数据的修改操作 * 2. 方法名带有 lambda 的为内部方法入参 column 支持函数式的 * * 二. 支持介绍 * 1. 方法名带有 query 的支持以 {@link ChainQuery} 内部的方法名结尾进行数据查询操作 * 2. 方法名带有 update 的支持以 {@link ChainUpdate} 内部的方法名为结尾进行数据修改操作 * * 三. 使用示例,只用不带 lambda 的方法各展示一个例子,其他类推 * 1. 根据条件获取一条数据: `query().eq("column", value).one()` * 2. 根据条件删除一条数据: `update().eq("column", value).remove()` * */ /** * 链式查询 普通 * * @return QueryWrapper 的包装类 */ default QueryChainWrapper<T> query() { return new QueryChainWrapper<>(getBaseMapper()); } /** * 链式查询 lambda 式 *

    注意:不支持 Kotlin

    * * @return LambdaQueryWrapper 的包装类 */
    default LambdaQueryChainWrapper<T> lambdaQuery() { return new LambdaQueryChainWrapper<>(getBaseMapper()); } /** * 链式更改 普通 * * @return UpdateWrapper 的包装类 */ default UpdateChainWrapper<T> update() { return new UpdateChainWrapper<>(getBaseMapper()); } /** * 链式更改 lambda 式 *

    注意:不支持 Kotlin

    * * @return LambdaUpdateWrapper 的包装类 */
    default LambdaUpdateChainWrapper<T> lambdaUpdate() { return new LambdaUpdateChainWrapper<>(getBaseMapper()); } /** *

    * 根据updateWrapper尝试更新,否继续执行saveOrUpdate(T)方法 * 此次修改主要是减少了此项业务代码的代码量(存在性验证之后的saveOrUpdate操作) *

    * * @param entity 实体对象 */
    default boolean saveOrUpdate(T entity, Wrapper<T> updateWrapper) { return update(entity, updateWrapper) || saveOrUpdate(entity); } }

    ServiceImpl.java

    **
     * IService 实现类( 泛型:M 是 mapper 对象,T 是实体 , PK 是主键泛型 )
     *
     * @author hubin
     * @since 2018-06-23
     */
    @SuppressWarnings("unchecked")
    public class ServiceImpl<M extends BaseMapper<T>, T> implements IService<T> {
    
        protected Log log = LogFactory.getLog(getClass());
    
        @Autowired
        protected M baseMapper;
    
        @Override
        public M getBaseMapper() {
            return baseMapper;
        }
    
        /**
         * 判断数据库操作是否成功
         *
         * @param result 数据库操作返回影响条数
         * @return boolean
         */
        protected boolean retBool(Integer result) {
            return SqlHelper.retBool(result);
        }
    
        protected Class<T> currentModelClass() {
            return (Class<T>) ReflectionKit.getSuperClassGenericType(getClass(), 1);
        }
    
        /**
         * 批量操作 SqlSession
         */
        protected SqlSession sqlSessionBatch() {
            return SqlHelper.sqlSessionBatch(currentModelClass());
        }
    
        /**
         * 释放sqlSession
         *
         * @param sqlSession session
         */
        protected void closeSqlSession(SqlSession sqlSession) {
            SqlSessionUtils.closeSqlSession(sqlSession, GlobalConfigUtils.currentSessionFactory(currentModelClass()));
        }
    
        /**
         * 获取 SqlStatement
         *
         * @param sqlMethod ignore
         * @return ignore
         */
        protected String sqlStatement(SqlMethod sqlMethod) {
            return SqlHelper.table(currentModelClass()).getSqlStatement(sqlMethod.getMethod());
        }
    
        @Override
        public boolean save(T entity) {
            return retBool(baseMapper.insert(entity));
        }
    
        /**
         * 批量插入
         *
         * @param entityList ignore
         * @param batchSize ignore
         * @return ignore
         */
        @Transactional(rollbackFor = Exception.class)
        @Override
        public boolean saveBatch(Collection<T> entityList, int batchSize) {
            String sqlStatement = sqlStatement(SqlMethod.INSERT_ONE);
            try (SqlSession batchSqlSession = sqlSessionBatch()) {
                int i = 0;
                for (T anEntityList : entityList) {
                    batchSqlSession.insert(sqlStatement, anEntityList);
                    if (i >= 1 && i % batchSize == 0) {
                        batchSqlSession.flushStatements();
                    }
                    i++;
                }
                batchSqlSession.flushStatements();
            }
            return true;
        }
    
        /**
         * TableId 注解存在更新记录,否插入一条记录
         *
         * @param entity 实体对象
         * @return boolean
         */
        @Transactional(rollbackFor = Exception.class)
        @Override
        public boolean saveOrUpdate(T entity) {
            if (null != entity) {
                Class<?> cls = entity.getClass();
                TableInfo tableInfo = TableInfoHelper.getTableInfo(cls);
                Assert.notNull(tableInfo, "error: can not execute. because can not find cache of TableInfo for entity!");
                String keyProperty = tableInfo.getKeyProperty();
                Assert.notEmpty(keyProperty, "error: can not execute. because can not find column for id from entity!");
                Object idVal = ReflectionKit.getMethodValue(cls, entity, tableInfo.getKeyProperty());
                return StringUtils.checkValNull(idVal) || Objects.isNull(getById((Serializable) idVal)) ? save(entity) : updateById(entity);
            }
            return false;
        }
    
    

    Servcie层写法

    public interface EmployeeService extends IService<Employee>{}
    
    public class EmployeeServiceImpl extends ServiceImpl<EmployeeMapper,Employee> implements EmployeeService{}
    

    七、分页插件

    1.插件机制:
    Mybatis 通过插件(Interceptor) 可以做到拦截四大对象相关方法的执行,根据需求,完成相关数据的动态改变。
    Executor
    StatementHandler
    ParameterHandler
    ResultSetHandler

    2.插件原理
    四大对象的每个对象在创建时,都会执行 interceptorChain.pluginAll(),会经过每个插件对象的 plugin()方法,目的是为当前的四大对象创建代理。代理对象就可以拦截到四大对象相关方法的执行,因为要执行四大对象的方法需要经过代理

    分页插件

    com.baomidou.mybatisplus.plugins.PaginationInterceptor

    @Configuration
    public class MyBatisPlusConfig {
        //注册插件 
        @Bean
        public PaginationInterceptor paginationInterceptor() {
            return new PaginationInterceptor();
        }
    }
    
    Page<Employee> employeeIPage =(Page<Employee>) employeeMapper.selectPage(new Page<>(2, 2), null);
    employeeIPage.getRecords().forEach(System.out::println);
    
    public class Page<T> implements IPage<T> {
    
        private static final long serialVersionUID = 8545996863226528798L;
    
        /**
         * 查询数据列表
         */
        private List<T> records = Collections.emptyList();
    
        /**
         * 总数
         */
        private long total = 0;
        /**
         * 每页显示条数,默认 10
         */
        private long size = 10;
    
        /**
         * 当前页
         */
        private long current = 1;
    
        /**
         * 排序字段信息
         */
        private List<OrderItem> orders = new ArrayList<>();
    
        /**
         * 自动优化 COUNT SQL
         */
        private boolean optimizeCountSql = true;
        /**
         * 是否进行 count 查询
         */
        private boolean isSearchCount = true;
    
        public Page() {
        }
    
        /**
         * 分页构造函数
         *
         * @param current 当前页
         * @param size    每页显示条数
         */
        public Page(long current, long size) {
            this(current, size, 0);
        }
    
        public Page(long current, long size, long total) {
            this(current, size, total, true);
        }
    
        public Page(long current, long size, boolean isSearchCount) {
            this(current, size, 0, isSearchCount);
        }
    
        public Page(long current, long size, long total, boolean isSearchCount) {
            if (current > 1) {
                this.current = current;
            }
            this.size = size;
            this.total = total;
            this.isSearchCount = isSearchCount;
        }
    
        /**
         * 是否存在上一页
         *
         * @return true / false
         */
        public boolean hasPrevious() {
            return this.current > 1;
        }
    
        /**
         * 是否存在下一页
         *
         * @return true / false
         */
        public boolean hasNext() {
            return this.current < this.getPages();
        }
    
        @Override
        public List<T> getRecords() {
            return this.records;
        }
    
        @Override
        public Page<T> setRecords(List<T> records) {
            this.records = records;
            return this;
        }
    
        @Override
        public long getTotal() {
            return this.total;
        }
    
        @Override
        public Page<T> setTotal(long total) {
            this.total = total;
            return this;
        }
    
        @Override
        public long getSize() {
            return this.size;
        }
    
        @Override
        public Page<T> setSize(long size) {
            this.size = size;
            return this;
        }
    
        @Override
        public long getCurrent() {
            return this.current;
        }
    
        @Override
        public Page<T> setCurrent(long current) {
            this.current = current;
            return this;
        }
    
        /**
         * 获取当前正序排列的字段集合
         * 

    * 为了兼容,将在不久后废弃 * * @return 正序排列的字段集合 * @see #getOrders() * @deprecated 3.2.0 */ @Override @Nullable @Deprecated public String[] ascs() { return CollectionUtils.isNotEmpty(orders) ? mapOrderToArray(OrderItem::isAsc) : null; } /** * 查找 order 中正序排序的字段数组 * * @param filter 过滤器 * @return 返回正序排列的字段数组 */ private String[] mapOrderToArray(Predicate<OrderItem> filter) { List<String> columns = new ArrayList<>(orders.size()); orders.forEach(i -> { if (filter.test(i)) { columns.add(i.getColumn()); } }); return columns.toArray(new String[0]); } /** * 移除符合条件的条件 * * @param filter 条件判断 */ private void removeOrder(Predicate<OrderItem> filter) { for (int i = orders.size() - 1; i >= 0; i--) { if (filter.test(orders.get(i))) { orders.remove(i); } } } /** * 添加新的排序条件,构造条件可以使用工厂:{@link OrderItem#build(String, boolean)} * * @param items 条件 * @return 返回分页参数本身 */ public Page<T> addOrder(OrderItem... items) { orders.addAll(Arrays.asList(items)); return this; } /** * 添加新的排序条件,构造条件可以使用工厂:{@link OrderItem#build(String, boolean)} * * @param items 条件 * @return 返回分页参数本身 */ public Page<T> addOrder(List<OrderItem> items) { orders.addAll(items); return this; } /** * 设置需要进行正序排序的字段 *

    * Replaced:{@link #addOrder(OrderItem...)} * * @param ascs 字段 * @return 返回自身 * @deprecated 3.2.0 */ @Deprecated public Page<T> setAscs(List<String> ascs) { return CollectionUtils.isNotEmpty(ascs) ? setAsc(ascs.toArray(new String[0])) : this; } /** * 升序 *

    * Replaced:{@link #addOrder(OrderItem...)} * * @param ascs 多个升序字段 * @deprecated 3.2.0 */ @Deprecated public Page<T> setAsc(String... ascs) { // 保证原来方法 set 的语意 removeOrder(OrderItem::isAsc); for (String s : ascs) { addOrder(OrderItem.asc(s)); } return this; } /** * 获取需简要倒序排列的字段数组 *

    * * @return 倒序排列的字段数组 * @see #getOrders() * @deprecated 3.2.0 */ @Override @Deprecated public String[] descs() { return mapOrderToArray(i -> !i.isAsc()); } /** * Replaced:{@link #addOrder(OrderItem...)} * * @param descs 需要倒序排列的字段 * @return 自身 * @deprecated 3.2.0 */ @Deprecated public Page<T> setDescs(List<String> descs) { // 保证原来方法 set 的语意 if (CollectionUtils.isNotEmpty(descs)) { removeOrder(item -> !item.isAsc()); for (String s : descs) { addOrder(OrderItem.desc(s)); } } return this; } /** * 降序,这方法名不知道是谁起的 *

    * Replaced:{@link #addOrder(OrderItem...)} * * @param descs 多个降序字段 * @deprecated 3.2.0 */ @Deprecated public Page<T> setDesc(String... descs) { setDescs(Arrays.asList(descs)); return this; } @Override public List<OrderItem> orders() { return getOrders(); } public List<OrderItem> getOrders() { return orders; } public void setOrders(List<OrderItem> orders) { this.orders = orders; } @Override public boolean optimizeCountSql() { return optimizeCountSql; } @Override public boolean isSearchCount() { if (total < 0) { return false; } return isSearchCount; } public Page<T> setSearchCount(boolean isSearchCount) { this.isSearchCount = isSearchCount; return this; } public Page<T> setOptimizeCountSql(boolean optimizeCountSql) { this.optimizeCountSql = optimizeCountSql; return this; } }

    执行分析插件

    1. com.baomidou.mybatisplus.plugins.SqlExplainInterceptor
    2. SQL 执行分析拦截器,只支持 MySQL5.6.3 以上版本
    3. 该插件的作用是分析 DELETE UPDATE 语句,防止小白
      或者恶意进行 DELETE UPDATE 全表操作
    4. 只建议在开发环境中使用,不建议在生产环境使用
    5. 在插件的底层 通过 SQL 语句分析命令:Explain 分析当前的 SQL 语句,
      根据结果集中的 Extra 列来断定当前是否全表操作。
    @Configuration
    public class MyBatisPlusConfig {
        //注册插件
        @Bean
        public PaginationInterceptor paginationInterceptor() {
            return new PaginationInterceptor();
        }
    
        //分析插件
        @Bean
        public SqlExplainInterceptor sqlExplainInterceptor(){
            SqlExplainInterceptor sqlExplainInterceptor = new SqlExplainInterceptor();
            List<ISqlParser> sqlParserList = new ArrayList<>();
            sqlParserList.add(new BlockAttackSqlParser());
            sqlExplainInterceptor.setSqlParserList(sqlParserList);
            return sqlExplainInterceptor;
        }
    }
    
    @Test
        public void fn(){
            try {
                employeeMapper.delete(null);
            }catch (Exception e){
                e.printStackTrace();
                System.out.println("不能全表删除");
            }
    
        }
    
    org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.exceptions.PersistenceException: 
    ### Error updating database.  Cause: com.baomidou.mybatisplus.core.exceptions.MybatisPlusException: Prohibition of full table deletion
    ### Cause: com.baomidou.mybatisplus.core.exceptions.MybatisPlusException: Prohibition of full table deletion
    	at org.mybatis.spring.MyBatisExceptionTranslator.translateExceptionIfPossible(MyBatisExceptionTranslator.java:78)
    	at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:440)
    	at com.sun.proxy.$Proxy65.delete(Unknown Source)
    	at org.mybatis.spring.SqlSessionTemplate.delete(SqlSessionTemplate.java:303)
    	at com.baomidou.mybatisplus.core.override.MybatisMapperMethod.execute(MybatisMapperMethod.java:68)
    	at com.baomidou.mybatisplus.core.override.MybatisMapperProxy.invoke(MybatisMapperProxy.java:61)
    	at com.sun.proxy.$Proxy68.delete(Unknown Source)
    	at com.mybatisplus.mybatisplugsdemo.DeleTest.fn(DeleTest.java:15)
    	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    	at java.lang.reflect.Method.invoke(Method.java:498)
    	at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
    	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    	at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
    	at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    	at org.springframework.test.context.junit4.statements.RunBeforeTestExecutionCallbacks.evaluate(RunBeforeTestExecutionCallbacks.java:74)
    	at org.springframework.test.context.junit4.statements.RunAfterTestExecutionCallbacks.evaluate(RunAfterTestExecutionCallbacks.java:84)
    	at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75)
    	at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86)
    	at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84)
    	at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366)
    	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:251)
    	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:97)
    	at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
    	at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
    	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
    	at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
    	at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
    	at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
    	at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
    	at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
    	at org.junit.runners.ParentRunner.run(ParentRunner.java:413)
    	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:190)
    	at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
    	at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
    	at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)
    	at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:230)
    	at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:58)
    Caused by: org.apache.ibatis.exceptions.PersistenceException: 
    ### Error updating database.  Cause: com.baomidou.mybatisplus.core.exceptions.MybatisPlusException: Prohibition of full table deletion
    ### Cause: com.baomidou.mybatisplus.core.exceptions.MybatisPlusException: Prohibition of full table deletion
    	at org.apache.ibatis.exceptions.ExceptionFactory.wrapException(ExceptionFactory.java:30)
    	at org.apache.ibatis.session.defaults.DefaultSqlSession.update(DefaultSqlSession.java:199)
    	at org.apache.ibatis.session.defaults.DefaultSqlSession.delete(DefaultSqlSession.java:212)
    	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    	at java.lang.reflect.Method.invoke(Method.java:498)
    	at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:426)
    	... 37 more
    Caused by: com.baomidou.mybatisplus.core.exceptions.MybatisPlusException: Prohibition of full table deletion
    	at com.baomidou.mybatisplus.core.toolkit.ExceptionUtils.mpe(ExceptionUtils.java:49)
    	at com.baomidou.mybatisplus.core.toolkit.Assert.isTrue(Assert.java:38)
    	at com.baomidou.mybatisplus.core.toolkit.Assert.notNull(Assert.java:72)
    	at com.baomidou.mybatisplus.extension.parsers.BlockAttackSqlParser.processDelete(BlockAttackSqlParser.java:40)
    	at com.baomidou.mybatisplus.core.parser.AbstractJsqlParser.processParser(AbstractJsqlParser.java:94)
    	at com.baomidou.mybatisplus.core.parser.AbstractJsqlParser.parser(AbstractJsqlParser.java:67)
    	at com.baomidou.mybatisplus.extension.handlers.AbstractSqlParserHandler.sqlParser(AbstractSqlParserHandler.java:76)
    	at com.baomidou.mybatisplus.extension.plugins.SqlExplainInterceptor.intercept(SqlExplainInterceptor.java:63)
    	at org.apache.ibatis.plugin.Plugin.invoke(Plugin.java:61)
    	at com.sun.proxy.$Proxy89.update(Unknown Source)
    	at org.apache.ibatis.session.defaults.DefaultSqlSession.update(DefaultSqlSession.java:197)
    	... 43 more
    不能全表删除
    

    性能分析插件

    1. com.baomidou.mybatisplus.plugins.PerformanceInterceptor
    2. 性能分析拦截器,用于输出每条 SQL 语句及其执行时间
    3. SQL 性能执行分析,开发环境使用,超过指定时间,停止运行。有助于发现问题

    性能分析插件是3.2.0 以上版本移除,推荐使用第三方扩展 执行 SQL

    乐观锁插件

    1. com.baomidou.mybatisplus.plugins.OptimisticLockerInterceptor
    2. 如果想实现如下需求: 当要更新一条记录的时候,希望这条记录没有被别人更新
    3. 乐观锁的实现原理:
      取出记录时,获取当前 version 2
      更新时,带上这个 version 2
      执行更新时, set version = yourVersion+1 where version = yourVersion
      如果 version 不对,就更新失败
    4. @Version 用于注解实体字段,必须要有。

    @Bean
    public OptimisticLockerInterceptor optimisticLockerInterceptor(){
        OptimisticLockerInterceptor optimisticLockerInterceptor = new OptimisticLockerInterceptor();
        return optimisticLockerInterceptor;
    }
    

    特别说明:

    • 支持的数据类型只有:int,Integer,long,Long,Date,Timestamp,LocalDateTime
    • 整数类型下 newVersion = oldVersion + 1
    • newVersion 会回写到 entity
    • 仅支持 updateById(id)update(entity, wrapper) 方法
    • update(entity, wrapper) 方法下, wrapper 不能复用!!!
        @Test
        public void fn (){
            //跟新操作
            Employee employee = new Employee();
            employee.setId(10);
            employee.setLastName("tome");
            employee.setEmail("[email protected]");
            employee.setGender(1);
            employee.setAge(22);
            employee.setVersion(3);
    
            employeeMapper.update(employee,null);
        }
    
    UPDATE tbl_employee  SET last_name=?,
    gender=?,
    version=?,
    age=?,
    email=?  
     
     WHERE (version = ?)
    UPDATE tbl_employee  SET last_name='tome',
    gender=1,
    version=4,
    age=22,
    email='[email protected]'  
     
     WHERE (version = 3);
    
    update tbl_user set name = 'update',version = 3 where id = 100 and version = 2
    
    # 条件中version是我们的。  set 中的version是数据库返回来的。
    

    八、自定义全局操作 SQL注入

    SQL 自动注入器接口 ISqlInjector

    @Configuration
    public class MySqlInjector implements ISqlInjector {
        @Override
        public void inspectInject(MapperBuilderAssistant builderAssistant, Class<?> mapperClass) {
            //将EmployeeMapper中定义deleteAll,处理成对已的MapperStatment对象,加入到configuration对线中
    
            
    
    
    
        }
    }
    
    /**
     * SQL 自动注入器
     *
     * @author hubin
     * @since 2018-04-07
     */
    public abstract class AbstractSqlInjector implements ISqlInjector {
    
        private static final Log logger = LogFactory.getLog(AbstractSqlInjector.class);
    
        @Override
        public void inspectInject(MapperBuilderAssistant builderAssistant, Class<?> mapperClass) {
            Class<?> modelClass = extractModelClass(mapperClass);
            if (modelClass != null) {
                String className = mapperClass.toString();
                Set<String> mapperRegistryCache = GlobalConfigUtils.getMapperRegistryCache(builderAssistant.getConfiguration());
                if (!mapperRegistryCache.contains(className)) {
                    List<AbstractMethod> methodList = this.getMethodList(mapperClass);
                    if (CollectionUtils.isNotEmpty(methodList)) {
                        TableInfo tableInfo = TableInfoHelper.initTableInfo(builderAssistant, modelClass);
                        // 循环注入自定义方法
                        methodList.forEach(m -> m.inject(builderAssistant, mapperClass, modelClass, tableInfo));
                    } else {
                        logger.debug(mapperClass.toString() + ", No effective injection method was found.");
                    }
                    mapperRegistryCache.add(className);
                }
            }
        }
    
        /**
         * 

    * 获取 注入的方法 *

    * * @param mapperClass 当前mapper * @return 注入的方法集合 * @since 3.1.2 add mapperClass */
    public abstract List<AbstractMethod> getMethodList(Class<?> mapperClass); /** * 提取泛型模型,多泛型的时候请将泛型T放在第一位 * * @param mapperClass mapper 接口 * @return mapper 泛型 */ protected Class<?> extractModelClass(Class<?> mapperClass) { Type[] types = mapperClass.getGenericInterfaces(); ParameterizedType target = null; for (Type type : types) { if (type instanceof ParameterizedType) { Type[] typeArray = ((ParameterizedType) type).getActualTypeArguments(); if (ArrayUtils.isNotEmpty(typeArray)) { for (Type t : typeArray) { if (t instanceof TypeVariable || t instanceof WildcardType) { break; } else { target = (ParameterizedType) type; break; } } } break; } } return target == null ? null : (Class<?>) target.getActualTypeArguments()[0]; } }

    九、逻辑删除

    • application.yml 加入配置(如果你的默认值和mp默认的一样,该配置可无):
    mybatis-plus:
      global-config:
        db-config:
          logic-delete-field: flag  #全局逻辑删除字段值 3.3.0开始支持,详情看下面。
          logic-delete-value: 1 # 逻辑已删除值(默认为 1)
          logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
    
    • 实体类字段上加上@TableLogic注解
    @Data
    @TableName("tb_user")
    public class User{
        @TableId(type=IdType.AUTO)
        private Integer id; 
        private String name;
        
    	@TableLogic
    	private Integer logicfalg;    //logic_falg
    }
    
    
    

    说明:

    • 字段支持所有数据类型(推荐使用 Integer,Boolean,LocalDateTime)
    • 如果使用LocalDateTime,建议逻辑未删除值设置为字符串null,逻辑删除值只支持数据库函数例如now()
    • 效果: 使用mp自带方法删除和查找都会附带逻辑删除功能
    # 删除 
    update user set deleted=1 where id =1 and deleted=0
    # 查找 
    select * from user where deleted=0
    

    十、公共字段自动填充

    实现元对象处理器接口:com.baomidou.mybatisplus.core.handlers.MetaObjectHandler

    https://mp.baomidou.com/guide/auto-fill-metainfo.html

    insertFill(MetaObject metaObject)

    updateFill(MetaObject metaObject)

    metaobject: 元对象. 是 Mybatis 提供的一个用于更加方便,更加优雅的访问对象的属性,
    给对象的属性设置值 的一个对象. 还会用于包装对象. 支持对 Object 、Map、Collection
    等对象进行包装
    本质上 metaObject 获取对象的属性值或者是给对象的属性设置值,最终是要
    通过 Reflector 获取到属性的对应方法的 Invoker, 最终 invoke.

    /**
     * 元对象字段填充控制器抽象类,实现公共字段自动写入
     *
     * @author hubin
     * @since 2016-08-28
     */
    public interface MetaObjectHandler {
    
        /**
         * 乐观锁常量
         *
         * @deprecated 3.1.1 {@link Constants#MP_OPTLOCK_ET_ORIGINAL}
         */
        String MP_OPTLOCK_ET_ORIGINAL = "MP_OPTLOCK_ET_ORIGINAL";
    
        /**
         * 插入元对象字段填充(用于插入时对公共字段的填充)
         *
         * @param metaObject 元对象
         */
        void insertFill(MetaObject metaObject);
    
        /**
         * 更新元对象字段填充(用于更新时对公共字段的填充)
         *
         * @param metaObject 元对象
         */
        void updateFill(MetaObject metaObject);
    
        /**
         * 通用填充
         *
         * @param fieldName  java bean property name
         * @param fieldVal   java bean property value
         * @param metaObject meta object parameter
         */
        default MetaObjectHandler setFieldValByName(String fieldName, Object fieldVal, MetaObject metaObject) {
            if (Objects.nonNull(fieldVal)) {
                if (metaObject.hasSetter(fieldName) && metaObject.hasGetter(fieldName)) {
                    metaObject.setValue(fieldName, fieldVal);
                } else if (metaObject.hasGetter(Constants.ENTITY)) {
                    Object et = metaObject.getValue(Constants.ENTITY);
                    if (et != null) {
                        MetaObject etMeta = SystemMetaObject.forObject(et);
                        if (etMeta.hasSetter(fieldName)) {
                            etMeta.setValue(fieldName, fieldVal);
                        }
                    }
                }
            }
            return this;
        }
    
        /**
         * insert 时填充,只会填充 fill 被标识为 INSERT 与 INSERT_UPDATE 的字段
         *
         * @param fieldName  java bean property name
         * @param fieldVal   java bean property value
         * @param metaObject meta object parameter
         * @since 3.0.7
         */
        default MetaObjectHandler setInsertFieldValByName(String fieldName, Object fieldVal, MetaObject metaObject) {
            return setFieldValByName(fieldName, fieldVal, metaObject, FieldFill.INSERT);
        }
    
        /**
         * update 时填充,只会填充 fill 被标识为 UPDATE 与 INSERT_UPDATE 的字段
         *
         * @param fieldName  java bean property name
         * @param fieldVal   java bean property value
         * @param metaObject meta object parameter
         * @since 3.0.7
         */
        default MetaObjectHandler setUpdateFieldValByName(String fieldName, Object fieldVal, MetaObject metaObject) {
            return setFieldValByName(fieldName, fieldVal, metaObject, FieldFill.UPDATE);
        }
    
        /**
         * Common method to set value for java bean.
         * 

    如果包含前缀 et 使用该方法,否则可以直接 metaObject.setValue(fieldName, fieldVal);

    * * @param fieldName java bean property name * @param fieldVal java bean property value * @param metaObject meta object parameter * @param fieldFill 填充策略枚举 * @since 3.0.7 */
    default MetaObjectHandler setFieldValByName(String fieldName, Object fieldVal, MetaObject metaObject, FieldFill fieldFill) { if (Objects.nonNull(fieldVal)) { if (metaObject.hasSetter(fieldName) && metaObject.hasGetter(fieldName) && isFill(fieldName, fieldVal, metaObject, fieldFill)) { metaObject.setValue(fieldName, fieldVal); } else if (metaObject.hasGetter(Constants.ENTITY)) { Object et = metaObject.getValue(Constants.ENTITY); if (et != null) { MetaObject etMeta = SystemMetaObject.forObject(et); if (etMeta.hasSetter(fieldName) && isFill(fieldName, fieldVal, etMeta, fieldFill)) { etMeta.setValue(fieldName, fieldVal); } } } } return this; } /** * get value from java bean by propertyName *

    如果包含前缀 et 使用该方法,否则可以直接 metaObject.setValue(fieldName, fieldVal);

    * * @param fieldName java bean property name * @param metaObject parameter wrapper * @return 字段值 */
    default Object getFieldValByName(String fieldName, MetaObject metaObject) { if (metaObject.hasGetter(fieldName)) { return metaObject.getValue(fieldName); } else if (metaObject.hasGetter(Constants.ENTITY_DOT + fieldName)) { return metaObject.getValue(Constants.ENTITY_DOT + fieldName); } return null; } /** * 填充判断 *
  • 如果是主键,不填充
  • *
  • 根据字段名找不到字段,不填充
  • *
  • 字段类型与填充值类型不匹配,不填充
  • *
  • 字段类型需在TableField注解里配置fill: @TableField(value="test_type", fill = FieldFill.INSERT), 没有配置或者不匹配时不填充
  • * v_3.1.0以后的版本(不包括3.1.0),子类的值也可以自动填充,Timestamp的值也可以填入到java.util.Date类型里面 * * @param fieldName java bean property name * @param fieldVal java bean property value * @param metaObject meta object parameter * @param fieldFill 填充策略枚举 * @return 是否进行填充 * @since 3.0.7 */
    default boolean isFill(String fieldName, Object fieldVal, MetaObject metaObject, FieldFill fieldFill) { TableInfo tableInfo = metaObject.hasGetter(Constants.MP_OPTLOCK_ET_ORIGINAL) ? TableInfoHelper.getTableInfo(metaObject.getValue(Constants.MP_OPTLOCK_ET_ORIGINAL).getClass()) : TableInfoHelper.getTableInfo(metaObject.getOriginalObject().getClass()); if (Objects.nonNull(tableInfo)) { Optional<TableFieldInfo> first = tableInfo.getFieldList().stream() //v_3.1.1+ 设置子类的值也可以通过 .filter(e -> e.getProperty().equals(fieldName) && e.getPropertyType().isAssignableFrom(fieldVal.getClass())) .findFirst(); if (first.isPresent()) { FieldFill fill = first.get().getFieldFill(); return fill.equals(fieldFill) || FieldFill.INSERT_UPDATE.equals(fill); } } return false; } /** * 是否开启了插入填充 */ default boolean openInsertFill() { return true; } /** * 是否开启了更新填充 */ default boolean openUpdateFill() { return true; } }

    开发步骤

    1. 注解填充字段 @TableFile(fill = FieldFill.INSERT) 查看 FieldFill
    2. 自定义公共字段填充处理器
    3. MP 全局注入 自定义公共字段填充处理器
    @Data
    @TableName("tb_user")
    public class User extends Model<User> {
        @TableId(value = "id",type = IdType.AUTO)
        private Integer id;
        @TableField(fill = FieldFill.UPDATE)
        private String name;
        private Integer age;
    
       /* protected Serializable pkVal() {
            return id;
        }*/
    
    }
    
    /**
     * 自定义填充处理器
     */
    @Component
    @Slf4j
    public class MyMetaObjectHandler implements MetaObjectHandler {
        @Override
        public void insertFill(MetaObject metaObject) {
        //获取到需要填充字段的值
            Object fieldVale = getFieldValByName("name", metaObject);
            if(fieldVale == null){
                System.out.println("插入操作,满足填充条件");
                setFieldValByName("name","xxx", metaObject);
            }
        }
    
        @Override
        public void updateFill(MetaObject metaObject) {
            /* 获取到需要填充字段的值 */
            Object fieldVale = getFieldValByName("name", metaObject);
            if(fieldVale == null){
                System.out.println("修改操作,满足填充条件");
                setFieldValByName("name","xxx", metaObject);
            }
        }
    }
    
    
        @Test
        public void updateMeta(){
            User user = new User();
            user.setId(3);
            user.setAge(23);
    
            userMapper.update(user,new QueryWrapper<User>().eq("id",1));
        }
    

    十一、Oracle主键Sequence

    MySQL: 支持主键自增。 IdType.Auto
    Oracle: 序列(Sequence)

    https://mp.baomidou.com/guide/sequence.html#spring-boot

    1. 实体类配置主键 Sequence @KeySequence(value=”序列名”,clazz=xxx.class 主键属性类型)
    2. 全局 MP 主键生成策略为 IdType.INPUT
    3. 全局 MP 中配置 Oracle 主键 Sequence
      com.baomidou.mybatisplus.incrementer.OracleKeyGenerator
    4. 可以将@keySequence 定义在父类中,可实现多个子类对应的多个表公用一个 Sequence

    你可能感兴趣的:(Springboot)