这一篇从一个入门的根本体验介绍,再到对于 CRUD 的一个具体介绍,在介绍过程中将波及到的一些问题,例如逐步策略,主动填充,乐观锁等内容说了一下,只选了一些重要的内容,还有一些没提及到,具体能够参考官网,简略的看完,其实会发现,如果遇到单表的 CRUD ,间接用 MP 必定难受,如果写多表,还是用 Mybatis 多点,毕竟间接写 SQL 会直观一点,MP 给我的感觉,就是办法封装了很多,还有一些算比拟是用的插件,然而可读性会略微差一点,不过集体有集体的认识哇,祝大家国庆高兴 ~

一 引言

最后的 JDBC,咱们须要写大量的代码来实现与根本的 CRUD ,或者会在肯定水平上应用 Spring 的 JdbcTemplate 或者 Apache 的 DBUtils ,这样一些对 JDBC 的简略封装的工具类。

再到后再应用 Mybatis 等一些优良的长久层框架,大大的简化了开发,咱们只须要应用肯定的 XML 或者注解就能够实现原来的工作

JDBC --> Mybatis 无疑简化了开发者的工作,而明天咱们所讲额 MyBatis-Plus 就是在 MyBatis 的根底上,更加的简化开发,来一起看看吧!

二 初识 MyBatis-Plus

下列介绍来自官网:

(一) 概述

MyBatis-Plus(简称 MP)是一个 MyBatis 的加强工具,在 MyBatis 的根底上只做加强不做扭转,为简化开发、提高效率而生。

咱们的愿景是成为 MyBatis 最好的搭档,就像魂斗罗中的 1P、2P,基友搭配,效率翻倍。

总之一句话:MyBatis-Plus —— 为简化开发而生

(二) 个性

  • 无侵入:只做加强不做扭转,引入它 不会 对现有工程产生影响,如丝般顺滑
  • 损耗小:启动即会主动注入根本 CRUD,性能根本无损耗,间接面向对象操作
  • 弱小的 CRUD 操作:内置通用 Mapper、通用 Service,仅仅通过大量配置即可实现单表大部分 CRUD 操作,更有弱小的条件结构器,满足各类应用需要
  • 反对 Lambda 模式调用:通过 Lambda 表达式,不便的编写各类查问条件,无需再放心字段写错
  • 反对主键主动生成:反对多达 4 种主键策略(内含分布式惟一 ID 生成器 - Sequence),可自在配置,完满解决主键问题
  • 反对 ActiveRecord 模式:反对 ActiveRecord 模式调用,实体类只需继承 Model 类即可进行弱小的 CRUD 操作
  • 反对自定义全局通用操作:反对全局通用办法注入( Write once, use anywhere )
  • 内置代码生成器:采纳代码或者 Maven 插件可疾速生成 Mapper 、 Model 、 Service 、 Controller 层代码,反对模板引擎,更有超多自定义配置等您来应用
  • 内置分页插件:基于 MyBatis 物理分页,开发者无需关怀具体操作,配置好插件之后,写分页等同于一般 List 查问
  • 分页插件反对多种数据库:反对 MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、Postgre、SQLServer 等多种数据库
  • 内置性能剖析插件:可输入 Sql 语句以及其执行工夫,倡议开发测试时启用该性能,能疾速揪出慢查问
  • 内置全局拦挡插件:提供全表 delete 、 update 操作智能剖析阻断,也可自定义拦挡规定,预防误操作

(三) 反对数据库

  • mysql 、mariadb 、oracle 、db2 、h2 、hsql 、sqlite 、postgresql 、sqlserver 、presto 、Gauss 、Firebird
  • Phoenix 、clickhouse 、Sybase ASE 、 OceanBase 、达梦数据库 、虚谷数据库 、人大金仓数据库 、南大通用数据库

三 入门初体验

依照官网的案例简略试一下 ,注:官网是基于 Springboot 的示例

@Repositorypublic interface UserMapper extends BaseMapper<User> {}

(一) 创立入门案例表

@Repositorypublic interface UserMapper extends BaseMapper<User> {}

自行创立一个数据库即可,而后导入官网给出的案例表,而后插入如下数据

-- ------------------------------ Table structure for user-- ----------------------------DROP TABLE IF EXISTS `user`;CREATE TABLE `user`  (  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',  `name` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '姓名',  `age` int(11) NULL DEFAULT NULL COMMENT '年龄',  `email` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '邮箱',  PRIMARY KEY (`id`) USING BTREE);-- ------------------------------ Records of user-- ----------------------------INSERT INTO `user` VALUES (1, 'Jone', 18, 'test1@baomidou.com');INSERT INTO `user` VALUES (2, 'Jack', 20, 'test2@baomidou.com');INSERT INTO `user` VALUES (3, 'Tom', 28, 'test3@baomidou.com');INSERT INTO `user` VALUES (4, 'Sandy', 21, 'test4@baomidou.com');INSERT INTO `user` VALUES (5, 'Billie', 24, 'test5@baomidou.com');

(二) 初始化 SpringBoot 我的项目

如果没有接触过 SpringBoot,应用惯例的 SSM 也是能够的,为了演示不便,这里还是应用了SpringBoot,如果想在 SSM 中应用,一个留神依赖的批改,还一个就须要批改 xml 中的一些配置

(1) 引入依赖

引入 MyBatis-Plus-boot-starter 必定是没什么疑难的,同样咱们还须要引入,数据库连贯的驱动依赖,还能够看须要引入 lombok,这里为了简便所以应用了它,如果不想应用,手动生成构造方法和 get set 即可

<!-- 数据库驱动--><dependency>    <groupId>mysql</groupId>    <artifactId>mysql-connector-java</artifactId></dependency><!-- MyBatis-Plus --><dependency>    <groupId>com.baomidou</groupId>    <artifactId>mybatis-plus-boot-starter</artifactId>    <version>3.4.0</version></dependency><!-- lombok --><dependency>    <groupId>org.projectlombok</groupId>    <artifactId>lombok</artifactId></dependency>

(2) 配置数据库信息

如何进行数据库相干的信息在以前的SpringBoot文章曾经说过了,这里强调一下:

mysql 5 驱动:com.mysql.jdbc.Driver

mysql 8 驱动:com.mysql.cj.jdbc.Driver 、还须要减少时区的配置

serverTimezone=GMT%2B8spring.datasource.username=rootspring.datasource.password=root99spring.datasource.url=jdbc:mysql://localhost:3306/mybatis_plus?useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

(3) 创立实体类

依据数据库字段创立出对应实体属性就行了,还是提一下:上方三个注解,次要是应用了 lombok 主动的生成那些 get set 等办法,不想用的同学间接本人按原来的办法显式的写进去就能够了~

@Data@AllArgsConstructor@NoArgsConstructorpublic class User {    @TableId(type = IdType.AUTO)    private Long id;    private String name;    private Integer age;    private String email;}

(4) 创立Mapper接口

代码如下,能够看到,咱们额定的继承了 BaseMapper,同时指定了泛型为 User

@Repositorypublic interface UserMapper extends BaseMapper<User> {}

其实点进去 BaseMapper 看一下,你会发现,在其中曾经定义了对于 CRUD 一些根本办法还有一些波及到配合条件实现更简单的操作,同时泛型中指定的实体,会在增删改查的办法中被调用

照这样说的话,如同啥货色都被写好了,如果当初想要进行一个简略的增删改查,是不是间接应用就行了

(5) 测试

首先在测试类中注入 UserMapper,这里演示一个查问所有的办法,所以应用了 selectList ,其参数是一个条件,这里先置为空。

如果有哪些办法的应用不明确,咱们能够先点到 BaseMapper 中去看一下,down 下源码当前,会有一些正文阐明

/** * 依据 entity 条件,查问全副记录 * * @param queryWrapper 实体对象封装操作类(能够为 null) */List<T> selectList(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

上面是测试查问所有的全代码

@SpringBootTestclass MybatisPlusApplicationTests {    @Autowired    private UserMapper userMapper;    @Test    void contextLoads() {        List<User> userList = userMapper.selectList(null);        for (User user : userList) {            System.out.println(user);        }    }}

(5) 后果

控制台输入如下

User(id=1, name=Jone, age=18, email=test1@baomidou.com)User(id=2, name=Jack, age=20, email=test2@baomidou.com)User(id=3, name=Tom, age=28, email=test3@baomidou.com)User(id=4, name=Sandy, age=21, email=test4@baomidou.com)User(id=5, name=Billie, age=24, email=test5@baomidou.com)

通过一个简略的测试,感觉还是很香的,而以前在 Mybatis 中咱们执行sql语句时,是能够看到控制台打印的日志的,而这里显然没有,其实通过一行简略的配置就能够了

(三) 开启日志

其实只须要在配置文件中退出短短的一行就能够了

MyBatis-Plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

打印如下:

当然你还能够通过一些 MyBatis Log 的插件,来疾速的查看本人所执行的 sql

四 CRUD

(一) 插入操作

(1) 可用办法

首先,先试试插入一个实体的操作,咱们抉择应用了 insert 这个办法,上面是其定义:

/** * 插入一条记录 * * @param entity 实体对象 */int insert(T entity);

(2) 演示

@Testpublic void testInsert() {    // 拟一个对象    User user = new User();    user.setName("现实二旬不止");    user.setAge(30);    user.setEmail("ideal_bwh@163.com");    // 插入操作    int count = userMapper.insert(user);     System.out.println(count);    System.out.println(user);}

后果:

依据后果看到,插入的确胜利了,然而一个发蒙的问题呈现了,为啥 id 变成了一个 long 类型的值

(3) 主键生成策略

对于主键的生成,官网有如下的一句话:

自3.3.0开始,默认应用雪花算法+UUID(不含中划线)

也就是说,因为下面咱们没有做任何的解决,所以它应用了默认的算法来当做主键 id

A:雪花算法(snowflake)
snowflake是Twitter开源的分布式ID生成算法,后果是一个long型的ID。其核心思想是:应用41bit作为毫秒数,10bit作为机器的ID(5个bit是数据中心,5个bit的机器ID),12bit作为毫秒内的流水号(意味着每个节点在每毫秒能够产生 4096 个 ID),最初还有一个符号位,永远是0。

雪花算法 + UUID 所以根本是能够保障惟一的

当然除了雪花算法为,咱们还有一些别的主键生成的策略,例如 Redis、数据库自增

对于咱们之前罕用的一种主键生成形式,个别都会用到数据库id自增

(4) 设置主键自增

  • 数据库主键字段设置自增!!!
  • 主键实体类字段注解 @TableId(type = IdType.AUTO)

再次插入,发现 id 曾经实现了自增

(5) 字段注解解释

@TableId 注解中的属性 Type 的值来自于 IdType 这个枚举类,其中我把每一项简略解释一下

  • AUTO(0) :数据库 ID 自增(MySQL 失常,Oracle 未测试)

    • 如果你想要全局都应用 AUTO 这样的数据库自增形式,能够间接在 application.properties 中增加
    • MyBatis-Plus.global-config.db-config.id-type=auto
  • NONE(1) :该类型为未设置主键类型(注解里等于追随全局,全局里约等于 INPUT)
  • INPUT(2) :用户输出 ID,也能够自定义输出策略,内置策略如下

    • DB2KeyGenerator
    • H2KeyGenerator
    • KingbaseKeyGenerator
    • OracleKeyGenerator
    • PostgreKeyGenerator

应用时:

先增加 @Bean,而后实体类配置主键 Sequence,指定主键策略为 IdType.INPUT 即可,重点不说这个,有须要能够间接扒官网

@Beanpublic IKeyGenerator keyGenerator() {    return new H2KeyGenerator();}
  • ASSIGN_ID(3) :雪花算法
  • ASSIGN_UUID(4):不含中划线的UUID

3.3.0 后,ID_WORKER(3)、ID_WORKER_STR(3)、UUID(4) 就曾经被弃用了,前两个能够应用 ASSIGN_ID(3)代替,最初一个应用 ASSIGN_UUID(4)代替

(二) 更新操作

(1) 可用办法

// 依据 whereEntity 条件,更新记录int update(@Param(Constants.ENTITY) T entity, @Param(Constants.WRAPPER) Wrapper<T> updateWrapper);// 依据 ID 批改int updateById(@Param(Constants.ENTITY) T entity);

(2) 演示

MyBatis-Plus 中的更新操作也是十分不便

举一种比拟常见的一种状况,通过 id 值批改某些字段

传统做法会传一个批改后的对象,而后通过 #{} 设置具体更新的值和 id

<update id="updateById">    UPDATE user SET name=#{name}, portrait=#{portrait}, gender=#{gender}, telephone=#{telephone}, email=#{email} WHERE id=#{id}</update>

MyBatis-Plus 形式:

@Testpublic void testUpdate() {    // 拟一个对象    User user = new User();    user.setId(1L);    user.setName("现实二旬不止");    user.setAge(20);    int i = userMapper.updateById(user);    System.out.println(i);}

首先咱们给定了 id 值,同时又批改了姓名和年龄这两个字段,然而并不是全副字段,来看一下执行成果

神奇的发现,咱们不须要在 sql 中进行设置了,所有的配置都被主动做好了,更新的内容和 id 都被主动填充好了

(3) 主动填充

主动填充是填充什么内容呢?首先咱们须要晓得,一般来说表中的创立工夫批改工夫,咱们总是心愿可能给依据插入或者批改的工夫主动填充,而不须要咱们手动的去更新

可能以前的我的项目不是特地综合或须要等起因,有时候也不会去设置创立工夫等字段,写这部分是因为,在阿里巴巴的Java开发手册(第5章 MySQL 数据库 - 5.1 建表规约 - 第 9 条 )有明确指出:

【强制】表必备三字段:id, create_time, update_time。

阐明:其中 id 必为主键,类型为 bigint unsigned、单表时自增、步长为 1。create_time, update_time

的类型均为 datetime 类型,前者当初时示意主动式创立,后者过去分词示意被动式更新

A:数据库级别(不罕用)

咱们能够通过间接批改数据库中对应字段的默认值,来实现数据库级别的主动增加语句

例如上图中我首先增加了 create_time, update_time 两个字段,而后将类型抉择为 datetime,又设置其默认值为 CURRENT_TIMESTAMP

注:更新工夫字段中要勾选 On Update Current_Timestamp ,插入不必,应用 SQLYog 没问题,在 Navicat 某个版本下间接通过可视化操作可能会报错,没有此默认值,这种状况就把表先导进去,而后批改SQL,在SQL 中批改语句

create_time` datetime(0)  DEFAULT CURRENT_TIMESTAMP(0) COMMENT '创立工夫',update_time` datetime(0)  DEFAULT CURRENT_TIMESTAMP(0) COMMENT '批改工夫',
B:代码级别

依据官网的主动填充性能的阐明,其实咱们须要做的只有两点:

  • 为 create_time, update_time 两个字段配置注解
  • 自定义实现类 MyMetaObjectHandler

注:开始前,别忘了删除方才数据库级别测试时的字段默认值等喔

首先填充字段注解:

@TableField(fill = FieldFill.INSERT)

@TableField(fill = FieldFill.INSERT_UPDATE)

@TableField(fill = FieldFill.INSERT)private Date createTime;@TableField(fill = FieldFill.INSERT_UPDATE)private Date updateTime;

FieldFill 阐明:

public enum FieldFill {    /**     * 默认不解决     */    DEFAULT,    /**     * 插入时填充字段     */    INSERT,    /**     * 更新时填充字段     */    UPDATE,    /**     * 插入和更新时填充字段     */    INSERT_UPDATE}

接着创立自定义实现类 MyMetaObjectHandler,让其实现MetaObjectHandler,重写其 insertFill 和 updateFill 办法,打印日志就不说了,通过 setFieldValByName 就能够对字段进行赋值,源码中这个办法有三个参数

/** * 通用填充 * * @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) {...}
  • fieldName:字段名
  • fieldVal:该字段赋予的值
  • metaObject:操作哪个数据
@Slf4j@Componentpublic class MyMetaObjectHandler implements MetaObjectHandler {    @Override    public void insertFill(MetaObject metaObject) {        log.info("start insert fill ....");        setFieldValByName("createTime", new Date(), metaObject);        setFieldValByName("updateTime", new Date(), metaObject);    }    @Override    public void updateFill(MetaObject metaObject) {        log.info("start update fill ....");        setFieldValByName("updateTime", new Date(), metaObject);    }}

查看一下成果:

上面还有一些注意事项:

注意事项:

  • 填充原理是间接给entity的属性设置值!!!
  • 注解则是指定该属性在对应状况下必有值,如果无值则入库会是null
  • MetaObjectHandler提供的默认办法的策略均为:如果属性有值则不笼罩,如果填充值为null则不填充
  • 字段必须申明TableField注解,属性fill抉择对应策略,该申明告知MyBatis-Plus须要预留注入SQL字段
  • 填充处理器MyMetaObjectHandler在 Spring Boot 中须要申明@Component@Bean注入
  • 要想依据注解FieldFill.xxx字段名以及字段类型来辨别必须应用父类的strictInsertFill或者strictUpdateFill办法
  • 不须要依据任何来辨别能够应用父类的fillStrategy办法

(4) 乐观锁插件

演示乐观锁插件前,首先补充一些根底概念:

A:没有锁会怎么样

打个比方,一张电影票价格为 30,老板通知员工 A ,把价格上调到 50,员工 A 因为有事耽误了两个小时,然而老板想了一会感觉提价太高了,就想着定价 40 好了,正好碰到员工 B,就让员工 B 将价格升高 10 块

当正好两个员工都在操作后盾零碎时,两人同时取出以后价格,即 30 元,员工A 先操作后 价格变成了 50元,然而员工 B 又将30 - 10 ,即 变成20块,执行了更新操作,此时员工 B 的更新操作就会把后面的 50 元笼罩掉,即最终成为了 20元,尽管我心田毫无波澜,但老板却亏的一匹

B:乐观锁和乐观锁
  • 乐观锁:故名思意非常乐观,它总是认为不会呈现问题,无论干什么不去上锁!如果呈现了问题,
    再次更新值测试

    • 乐观锁下,员工 B 更新时会查看是否这个价格曾经被他人批改过了,如果是就会取出新的值,再操作
  • 乐观锁:故名思意非常乐观,它总是认为总是呈现问题,无论干什么都会上锁!再去操作!

    • 乐观锁下,员工 B 只能在 员工 A 操作完当前能力操作,这样能保证数据只有一个人在操作
C:MP 中的乐观锁插件

用意:

当要更新一条记录的时候,心愿这条记录没有被他人更新

乐观锁实现形式:

  • 取出记录时,获取以后version
  • 更新时,带上这个version
  • 执行更新时, set version = newVersion where version = oldVersion
  • 如果version不对,就更新失败

实现这个性能,只须要两步:

  • 增加数据库version字段和实体字段以及注解
@Version // 乐观锁的Version注解private Integer version;
  • 创立配置类,引入乐观锁插件
// 扫描 mapper 文件夹@MapperScan("cn.ideal.mapper")@EnableTransactionManagement// 代表配置类@Configuration public class MyBatisPlusConfig {    // 乐观锁插件    @Bean    public OptimisticLockerInterceptor optimisticLockerInterceptor() {        return new OptimisticLockerInterceptor();    }}

阐明:刚开始例如扫描 mapper 这样的注解就放在了启动类中,当初有了配置类,所以把它也移过来了

测试一下:

首先1号和2号获取到的数据是一样的,然而在1号还没有执行到更新的时候,2号领先提交了更新操作,也就是说,以后实在数据曾经是被2号批改过的了,与1号后面获取到的不统一了

如果没有乐观锁,那么2号提交的更新会被1号的更新数据笼罩

// 测试更新@Testpublic void testUpdate() {    // 1号获得了数据    User user1 = userMapper.selectById(1L);    user1.setName("乐观锁1号");    user1.setAge(20);    user1.setEmail("ideal_bwh@xxx.com");    // 2号获得了数据    User user2 = userMapper.selectById(1L);    user2.setName("乐观锁2号");    user2.setAge(30);    user2.setEmail("ideal@xxx.com");    // 2号提交更新     userMapper.updateById(user2);    // 1号提交更新    userMapper.updateById(user1);}

能够看到,在2号领先执行后,1号就没有胜利执行了

同样数据库中表的其 version 也从1变成了1

(三) 查问操作

(1) 可用办法

// 依据 ID 查问T selectById(Serializable id);// 依据 entity 条件,查问一条记录T selectOne(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);// 查问(依据ID 批量查问)List<T> selectBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);// 依据 entity 条件,查问全副记录List<T> selectList(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);// 查问(依据 columnMap 条件)List<T> selectByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);// 依据 Wrapper 条件,查问全副记录List<Map<String, Object>> selectMaps(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);// 依据 Wrapper 条件,查问全副记录。留神: 只返回第一个字段的值List<Object> selectObjs(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);// 依据 entity 条件,查问全副记录(并翻页)IPage<T> selectPage(IPage<T> page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);// 依据 Wrapper 条件,查问全副记录(并翻页)IPage<Map<String, Object>> selectMapsPage(IPage<T> page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);// 依据 Wrapper 条件,查问总记录数Integer selectCount(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

(2) 简略查问

A:依据 id 查问
@Testpublic void testSelectById(){    User user = userMapper.selectById(1L);    System.out.println(user);}
B:依据 id 汇合查问

阐明:我这里应用的还是最根本的写法,例如 List 能够用工具类创立 如:Arrays.asList(1, 2, 3)

遍历也齐全能够这样 users.forEach(System.out::println);

@Testpublic void testSelectByBatchId(){    List list = new ArrayList();    list.add(1);    list.add(2);    list.add(3);    List<User> users = userMapper.selectBatchIds(list);    for (User user : users){        System.out.println(user);    }}
C:依据 map 查问
@Testpublic void testSelectByMap(){    HashMap<String, Object> map = new HashMap<>();    // 自定义要查问的字段和值    map.put("name","现实二旬不止");    map.put("age",30);    List<User> users = userMapper.selectByMap(map);    for (User user : users){        System.out.println(user);    }}

通过日志的打印能够看到,它依据咱们的抉择主动拼出了 SQL 的条件

==>  Preparing: SELECT id,name,age,email,version,create_time,update_time FROM user WHERE name = ? AND age = ?==> Parameters: 现实二旬不止(String), 30(Integer)

(3) 分页查问

JavaWeb 阶段,大家都应该有手写过分页,配合 SQL 的 limit 进行分页,前面在 Mybatis 就会用一些例如 pageHelper 的插件,而 MyBatis-Plus 中也有一个内置的分页插件

应用前只须要进行一个小小的配置,在方才配置类中,退出分页插件的配置代码

// 扫描咱们的mapper 文件夹@MapperScan("cn.ideal.mapper")@EnableTransactionManagement@Configuration // 配置类public class MyBatisPlusConfig {    // 乐观锁插件    @Bean    public OptimisticLockerInterceptor optimisticLockerInterceptor() {        return new OptimisticLockerInterceptor();    }    // 分页插件    @Bean    public PaginationInterceptor paginationInterceptor() {        return new PaginationInterceptor();    }}

接着就能够测试分页了

@Testpublic void testPage(){    // Page 参数: 参数1:当前页 ,参数1:页面大小    Page<User> page = new Page<>(2,3);    userMapper.selectPage(page,null);    List<User> users = page.getRecords();    for (User user : users){        System.out.println(user);    }    System.out.println(page.getTotal());}

执行后果日志:

==>  Preparing: SELECT id,name,age,email,version,create_time,update_time FROM user LIMIT ?,?==> Parameters: 3(Long), 3(Long)<==    Columns: id, name, age, email, version, create_time, update_time<==        Row: 4, Sandy, 21, test4@baomidou.com, 1, null, null<==        Row: 5, Billie, 24, test5@baomidou.com, 1, null, null<==        Row: 1308952901602811906, 现实二旬不止, 30, ideal_bwh@163.com, 1, null, null<==      Total: 3

(4) 条件查问 ※

如何实现一些条件绝对简单的查问呢?MyBatis-Plus 也给咱们提供了一些用法,帮忙咱们不便的结构各种条件

其实后面大家应该就留神到了,在查问操作的可用办法中,参数中往往带有一个名叫 Wrapper<T> queryWrapper 的内容,这就是咱们要结构条件的重点

查问中最罕用的就是 QueryWrapper

阐明:

继承自 AbstractWrapper ,本身的外部属性 entity 也用于生成 where 条件
及 LambdaQueryWrapper, 能够通过 new QueryWrapper().lambda() 办法获取

实例化一个 QueryWrapper 后,通过调用一些内置的办法,就能够实现条件结构

A:如何应用

例如咱们想要结构这样一个条件:查问邮箱不为空,且年龄小于 25 岁的用户

@Testvoid contextLoads() {    // 实例化一个 QueryWrapper 对象    QueryWrapper<User> wrapper = new QueryWrapper<>();    // 进行具体条件结构    wrapper            .isNotNull("email")            .lt("age", 25);    // 执行具体的查询方法,同时将 wrapper 条件作为参数传入    List<User> users = userMapper.selectList(wrapper);    for (User user : users){        System.out.println(user);    }}

看一下打印的日志:

==>  Preparing: SELECT id,name,age,email,version,deleted,create_time,update_time FROM user WHERE deleted=0 AND (email IS NOT NULL AND age < ?)==> Parameters: 25(Integer)<==    Columns: id, name, age, email, version, deleted, create_time, update_time<==        Row: 2, 现实, 22, ideal_bwh@xxx.com, 1, 0, 2020-09-26 15:06:09, 2020-09-26 21:21:52<==        Row: 4, Sandy, 21, test4@baomidou.com, 1, 0, null, null<==        Row: 5, Billie, 24, test5@baomidou.com, 1, 0, null, null<==      Total: 3

能够看到条件都被主动在 SQL 中结构进去了

应用的形式就这么简略,通过各种奇妙的结构就好了

B:结构形式

上面是从官网摘取的各种结构形式:


allEq
allEq(Map<R, V> params)allEq(Map<R, V> params, boolean null2IsNull)allEq(boolean condition, Map<R, V> params, boolean null2IsNull)
  • 全副eq(或个别isNull)

个别参数阐明:

params : key为数据库字段名,value为字段值
null2IsNull : 为true则在mapvaluenull时调用 isNull 办法,为false时则疏忽valuenull

  • 例1: allEq({id:1,name:"老王",age:null})--->id = 1 and name = '老王' and age is null
  • 例2: allEq({id:1,name:"老王",age:null}, false)--->id = 1 and name = '老王'
allEq(BiPredicate<R, V> filter, Map<R, V> params)allEq(BiPredicate<R, V> filter, Map<R, V> params, boolean null2IsNull)allEq(boolean condition, BiPredicate<R, V> filter, Map<R, V> params, boolean null2IsNull) 

个别参数阐明:

filter : 过滤函数,是否容许字段传入比对条件中
paramsnull2IsNull : 同上

  • 例1: allEq((k,v) -> k.indexOf("a") >= 0, {id:1,name:"老王",age:null})--->name = '老王' and age is null
  • 例2: allEq((k,v) -> k.indexOf("a") >= 0, {id:1,name:"老王",age:null}, false)--->name = '老王'
eq
eq(R column, Object val)eq(boolean condition, R column, Object val)
  • 等于 =
  • 例: eq("name", "老王")--->name = '老王'
ne
ne(R column, Object val)ne(boolean condition, R column, Object val)
  • 不等于 <>
  • 例: ne("name", "老王")--->name <> '老王'
gt
gt(R column, Object val)gt(boolean condition, R column, Object val)
  • 大于 >
  • 例: gt("age", 18)--->age > 18
ge
ge(R column, Object val)ge(boolean condition, R column, Object val)
  • 大于等于 >=
  • 例: ge("age", 18)--->age >= 18
lt
lt(R column, Object val)lt(boolean condition, R column, Object val)
  • 小于 <
  • 例: lt("age", 18)--->age < 18
le
le(R column, Object val)le(boolean condition, R column, Object val)
  • 小于等于 <=
  • 例: le("age", 18)--->age <= 18
between
between(R column, Object val1, Object val2)between(boolean condition, R column, Object val1, Object val2)
  • BETWEEN 值1 AND 值2
  • 例: between("age", 18, 30)--->age between 18 and 30
notBetween
notBetween(R column, Object val1, Object val2)notBetween(boolean condition, R column, Object val1, Object val2)
  • NOT BETWEEN 值1 AND 值2
  • 例: notBetween("age", 18, 30)--->age not between 18 and 30
like
like(R column, Object val)like(boolean condition, R column, Object val)
  • LIKE '%值%'
  • 例: like("name", "王")--->name like '%王%'
notLike
notLike(R column, Object val)notLike(boolean condition, R column, Object val)
  • NOT LIKE '%值%'
  • 例: notLike("name", "王")--->name not like '%王%'
likeLeft
likeLeft(R column, Object val)likeLeft(boolean condition, R column, Object val)
  • LIKE '%值'
  • 例: likeLeft("name", "王")--->name like '%王'
likeRight
likeRight(R column, Object val)likeRight(boolean condition, R column, Object val)
  • LIKE '值%'
  • 例: likeRight("name", "王")--->name like '王%'
isNull
isNull(R column)isNull(boolean condition, R column)
  • 字段 IS NULL
  • 例: isNull("name")--->name is null
isNotNull
isNotNull(R column)isNotNull(boolean condition, R column)
  • 字段 IS NOT NULL
  • 例: isNotNull("name")--->name is not null
in
in(R column, Collection<?> value)in(boolean condition, R column, Collection<?> value)
  • 字段 IN (value.get(0), value.get(1), ...)
  • 例: in("age",{1,2,3})--->age in (1,2,3)
in(R column, Object... values)in(boolean condition, R column, Object... values)
  • 字段 IN (v0, v1, ...)
  • 例: in("age", 1, 2, 3)--->age in (1,2,3)
notIn
notIn(R column, Collection<?> value)notIn(boolean condition, R column, Collection<?> value)
  • 字段 NOT IN (value.get(0), value.get(1), ...)
  • 例: notIn("age",{1,2,3})--->age not in (1,2,3)
notIn(R column, Object... values)notIn(boolean condition, R column, Object... values)
  • 字段 NOT IN (v0, v1, ...)
  • 例: notIn("age", 1, 2, 3)--->age not in (1,2,3)
inSql
inSql(R column, String inValue)inSql(boolean condition, R column, String inValue)
  • 字段 IN ( sql语句 )
  • 例: inSql("age", "1,2,3,4,5,6")--->age in (1,2,3,4,5,6)
  • 例: inSql("id", "select id from table where id < 3")--->id in (select id from table where id < 3)
notInSql
notInSql(R column, String inValue)notInSql(boolean condition, R column, String inValue)
  • 字段 NOT IN ( sql语句 )
  • 例: notInSql("age", "1,2,3,4,5,6")--->age not in (1,2,3,4,5,6)
  • 例: notInSql("id", "select id from table where id < 3")--->id not in (select id from table where id < 3)
groupBy
groupBy(R... columns)groupBy(boolean condition, R... columns)
  • 分组:GROUP BY 字段, ...
  • 例: groupBy("id", "name")--->group by id,name
orderByAsc
orderByAsc(R... columns)orderByAsc(boolean condition, R... columns)
  • 排序:ORDER BY 字段, ... ASC
  • 例: orderByAsc("id", "name")--->order by id ASC,name ASC
orderByDesc
orderByDesc(R... columns)orderByDesc(boolean condition, R... columns)
  • 排序:ORDER BY 字段, ... DESC
  • 例: orderByDesc("id", "name")--->order by id DESC,name DESC
orderBy
orderBy(boolean condition, boolean isAsc, R... columns)
  • 排序:ORDER BY 字段, ...
  • 例: orderBy(true, true, "id", "name")--->order by id ASC,name ASC
having
having(String sqlHaving, Object... params)having(boolean condition, String sqlHaving, Object... params)
  • HAVING ( sql语句 )
  • 例: having("sum(age) > 10")--->having sum(age) > 10
  • 例: having("sum(age) > {0}", 11)--->having sum(age) > 11

func

func(Consumer<Children> consumer)func(boolean condition, Consumer<Children> consumer)
  • func 办法(次要不便在呈现if...else下调用不同办法能一直链)
  • 例: func(i -> if(true) {i.eq("id", 1)} else {i.ne("id", 1)})
or
or()or(boolean condition)
  • 拼接 OR

注意事项:

被动调用or示意紧接着下一个办法不是用and连贯!(不调用or则默认为应用and连贯)

  • 例: eq("id",1).or().eq("name","老王")--->id = 1 or name = '老王'
or(Consumer<Param> consumer)or(boolean condition, Consumer<Param> consumer)
  • OR 嵌套
  • 例: or(i -> i.eq("name", "李白").ne("status", "活着"))--->or (name = '李白' and status <> '活着')
and
and(Consumer<Param> consumer)and(boolean condition, Consumer<Param> consumer)
  • AND 嵌套
  • 例: and(i -> i.eq("name", "李白").ne("status", "活着"))--->and (name = '李白' and status <> '活着')
nested
nested(Consumer<Param> consumer)nested(boolean condition, Consumer<Param> consumer)
  • 失常嵌套 不带 AND 或者 OR
  • 例: nested(i -> i.eq("name", "李白").ne("status", "活着"))--->(name = '李白' and status <> '活着')
apply
apply(String applySql, Object... params)apply(boolean condition, String applySql, Object... params)
  • 拼接 sql

注意事项:

该办法可用于数据库函数 动静入参的params对应后面applySql外部的{index}局部.这样是不会有sql注入危险的,反之会有!

  • 例: apply("id = 1")--->id = 1
  • 例: apply("date_format(dateColumn,'%Y-%m-%d') = '2008-08-08'")--->date_format(dateColumn,'%Y-%m-%d') = '2008-08-08'")
  • 例: apply("date_format(dateColumn,'%Y-%m-%d') = {0}", "2008-08-08")--->date_format(dateColumn,'%Y-%m-%d') = '2008-08-08'")
last
last(String lastSql)last(boolean condition, String lastSql)
  • 忽视优化规定间接拼接到 sql 的最初

注意事项:

只能调用一次,屡次调用以最初一次为准 有sql注入的危险,请审慎应用

  • 例: last("limit 1")
exists
exists(String existsSql)exists(boolean condition, String existsSql)
  • 拼接 EXISTS ( sql语句 )
  • 例: exists("select id from table where age = 1")--->exists (select id from table where age = 1)
notExists
notExists(String notExistsSql)notExists(boolean condition, String notExistsSql)
  • 拼接 NOT EXISTS ( sql语句 )
  • 例: notExists("select id from table where age = 1")--->not exists (select id from table where age = 1)

(四) 删除操作

(1) 可用办法

// 依据 entity 条件,删除记录int delete(@Param(Constants.WRAPPER) Wrapper<T> wrapper);// 删除(依据ID 批量删除)int deleteBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);// 依据 ID 删除int deleteById(Serializable id);// 依据 columnMap 条件,删除记录int deleteByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);

(2) 简略删除

A:依据 id 删除
@Testpublic void testDeleteById(){    userMapper.deleteById(1L);}
B:依据 id 汇合删除
@Testpublic void testDeleteBatchIds(){    List list = new ArrayList();    list.add(1308952901602811906L);    list.add(1308952901602811907L);    list.add(1308952901602811908L);    list.add(1308952901602811909L);    userMapper.deleteBatchIds(list);}
C:依据 map 删除
 @Testpublic void testDeleteMap(){    HashMap<String, Object> map = new HashMap<>();    map.put("name","现实二旬不止");    userMapper.deleteByMap(map);}

(3) 逻辑删除

删除这块再补充一下逻辑删除的概念,物理删除很好了解,就是实实在在的在数据库中删没了,然而逻辑删除,顾名思义只是逻辑上被删除了,实际上并没有,只是通过减少一个字段让其生效而已,例如 deleted = 0 => deleted = 1

能够

利用的场景就是管理员想查看删除记录,在谬误删除下,能够有逆转的机会等等

首先数据库减少 deleted 字段,同时创立其实体和注解

@TableLogic // 逻辑删除private Integer deleted;

接着只须要在全局配置中配置即可

application.properties

# 配置逻辑删除MyBatis-Plus.global-config.db-config.logic-delete-value=1MyBatis-Plus.global-config.db-config.logic-not-delete-value=0

application.yml

MyBatis-Plus:  global-config:    db-config:      logic-delete-field: flag  # 全局逻辑删除的实体字段名(since 3.3.0,配置后能够疏忽不配置步骤2)      logic-delete-value: 1 # 逻辑已删除值(默认为 1)      logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)

成果如下:

你会发现,逻辑删除会走一个更新操作,通过批改指定字段 deleted 的值为 0 实现咱们想要的成果

五 代码主动生成器

MyBatis-Plus 提供了一个十分便捷,有意思的内容,那就是代码的主动生成,咱们通过一些配置,就能够主动的生成 controller、service、mapper、pojo 的内容,并且接口或者注解等内容都会依照配置指定的格局生成。(提前准备好数据库和表)

首先除了 MyBatis-Plus 的依赖以外,还须要引入 swagger 和 velocity 的依赖,然而这两者其实是可选的,能够抉择不配置就不必引入了,默认应用 velocity 这个模板引擎,大家还能够换成别的

<dependency>    <groupId>org.apache.velocity</groupId>    <artifactId>velocity-engine-core</artifactId>    <version>2.0</version></dependency><dependency>    <groupId>io.springfox</groupId>    <artifactId>springfox-swagger2</artifactId>    <version>2.9.2</version></dependency><dependency>    <groupId>io.springfox</groupId>    <artifactId>springfox-swagger-ui</artifactId>    <version>2.9.2</version></dependency>

例如:

Velocity(默认):

<dependency>    <groupId>org.apache.velocity</groupId>    <artifactId>velocity-engine-core</artifactId>    <version>latest-velocity-version</version></dependency>

Freemarker:

<dependency>    <groupId>org.freemarker</groupId>    <artifactId>freemarker</artifactId>    <version>latest-freemarker-version</version></dependency>

Beetl:

<dependency>    <groupId>com.ibeetl</groupId>    <artifactId>beetl</artifactId>    <version>latest-beetl-version</version></dependency>
  • 留神!如果您抉择了非默认引擎,须要在 AutoGenerator 中 设置模板引擎。

    AutoGenerator generator = new AutoGenerator();// set freemarker enginegenerator.setTemplateEngine(new FreemarkerTemplateEngine());// set beetl enginegenerator.setTemplateEngine(new BeetlTemplateEngine());// set custom engine (reference class is your custom engine class)generator.setTemplateEngine(new CustomTemplateEngine());// other config...

上面就是一个主配置了,批改其中的数据库连贯等信息,以及包的名称等等等执行就能够了

package cn.ideal;import com.baomidou.mybatisplus.annotation.DbType;import com.baomidou.mybatisplus.annotation.FieldFill;import com.baomidou.mybatisplus.annotation.IdType;import com.baomidou.mybatisplus.annotation.TableField;import com.baomidou.mybatisplus.generator.AutoGenerator;import com.baomidou.mybatisplus.generator.config.DataSourceConfig;import com.baomidou.mybatisplus.generator.config.GlobalConfig;import com.baomidou.mybatisplus.generator.config.PackageConfig;import com.baomidou.mybatisplus.generator.config.StrategyConfig;import com.baomidou.mybatisplus.generator.config.po.TableFill;import com.baomidou.mybatisplus.generator.config.rules.DateType;import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;import java.util.ArrayList;/** * @ClassName: AutomaticCodeGenerate * @Description: TODO * @Author: BWH_Steven * @Date: 2020/10/2 21:29 * @Version: 1.0 */public class AutomaticCodeGenerate {    public static void main(String[] args) {        // 须要构建一个代码主动生成器对象        AutoGenerator mpg = new AutoGenerator();        // 配置策略        // 全局配置        GlobalConfig gc = new GlobalConfig();        String projectPath = System.getProperty("user.dir");        gc.setAuthor("BWH_Steven");        gc.setOutputDir(projectPath + "/src/main/java");        gc.setOpen(false);        gc.setFileOverride(false); // 是否笼罩        gc.setServiceName("%sService"); // 去Service的I前缀        gc.setIdType(IdType.ID_WORKER);        gc.setDateType(DateType.ONLY_DATE);        gc.setSwagger2(true);        mpg.setGlobalConfig(gc);        // 设置数据源        DataSourceConfig dsc = new DataSourceConfig();        dsc.setUrl("jdbc:mysql://localhost:3306/mybatis_plus?useSSL=false&useUnicode=true&characterEncoding" +                "=utf-8&serverTimezone=GMT%2B8");        dsc.setDriverName("com.mysql.cj.jdbc.Driver");        dsc.setUsername("root");        dsc.setPassword("root99");        dsc.setDbType(DbType.MYSQL);        mpg.setDataSource(dsc);        // 包的配置        PackageConfig pc = new PackageConfig();        pc.setModuleName("test");        pc.setParent("cn.ideal");        pc.setEntity("entity");        pc.setMapper("mapper");        pc.setService("service");        pc.setController("controller");        mpg.setPackageInfo(pc);        // 策略配置        StrategyConfig strategy = new StrategyConfig();        strategy.setInclude("user"); // 设置要映射的表名        strategy.setNaming(NamingStrategy.underline_to_camel);        strategy.setColumnNaming(NamingStrategy.underline_to_camel);        strategy.setEntityLombokModel(true); // 主动lombok;        strategy.setLogicDeleteFieldName("deleted");        // 主动填充配置        TableFill gmtCreate = new TableFill("create_time", FieldFill.INSERT);        TableFill gmtModified = new TableFill("update_time", FieldFill.INSERT_UPDATE);        ArrayList<TableFill> tableFills = new ArrayList<>();        tableFills.add(gmtCreate);        tableFills.add(gmtModified);        strategy.setTableFillList(tableFills); // 乐观锁        strategy.setVersionFieldName("version");        strategy.setRestControllerStyle(true);        strategy.setControllerMappingHyphenStyle(true);        mpg.setStrategy(strategy);        mpg.execute(); //执行    }}

生成构造成果如下:

我简略贴两段生成的内容:

controller

package cn.ideal.test.controller;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;/** * <p> *  前端控制器 * </p> * * @author BWH_Steven * @since 2020-10-02 */@RestController@RequestMapping("/test/user")public class UserController {}

entity

package cn.ideal.test.entity;import com.baomidou.mybatisplus.annotation.IdType;import java.util.Date;import com.baomidou.mybatisplus.annotation.Version;import com.baomidou.mybatisplus.annotation.TableId;import com.baomidou.mybatisplus.annotation.FieldFill;import com.baomidou.mybatisplus.annotation.TableLogic;import com.baomidou.mybatisplus.annotation.TableField;import java.io.Serializable;import io.swagger.annotations.ApiModel;import io.swagger.annotations.ApiModelProperty;import lombok.Data;import lombok.EqualsAndHashCode;/** * <p> *  * </p> * * @author BWH_Steven * @since 2020-10-02 */@Data@EqualsAndHashCode(callSuper = false)@ApiModel(value="User对象", description="")public class User implements Serializable {    private static final long serialVersionUID = 1L;    @ApiModelProperty(value = "主键ID")      @TableId(value = "id", type = IdType.AUTO)    private Long id;    @ApiModelProperty(value = "姓名")    private String name;    @ApiModelProperty(value = "年龄")    private Integer age;    @ApiModelProperty(value = "邮箱")    private String email;    @ApiModelProperty(value = "版本")    @Version    private Integer version;    @TableLogic    private Integer deleted;    @ApiModelProperty(value = "创立工夫")      @TableField(fill = FieldFill.INSERT)    private Date createTime;    @ApiModelProperty(value = "批改工夫")      @TableField(fill = FieldFill.INSERT_UPDATE)    private Date updateTime;}

service

package cn.ideal.test.service;import cn.ideal.test.entity.User;import com.baomidou.mybatisplus.extension.service.IService;/** * <p> *  服务类 * </p> * * @author BWH_Steven * @since 2020-10-02 */public interface UserService extends IService<User> {}

service 实现类

package cn.ideal.test.service.impl;import cn.ideal.test.entity.User;import cn.ideal.test.mapper.UserMapper;import cn.ideal.test.service.UserService;import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;import org.springframework.stereotype.Service;/** * <p> *  服务实现类 * </p> * * @author BWH_Steven * @since 2020-10-02 */@Servicepublic class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {}

mapper

package cn.ideal.test.mapper;import cn.ideal.test.entity.User;import com.baomidou.mybatisplus.core.mapper.BaseMapper;/** * <p> *  Mapper 接口 * </p> * * @author BWH_Steven * @since 2020-10-02 */public interface UserMapper extends BaseMapper<User> {}

mapper XML

<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="cn.ideal.test.mapper.UserMapper"></mapper>