MyBatis-Plus快速

之前我在《通用Mapper》文章中呼吁大家去看MyBatis-Plus官方文档,不知道大家去看了没。我倒是认认真真看了一遍,也就花了3、40分钟而已。我的评价是,有通用Mapper使用经验的,会觉得写得还马马虎虎,但完全没有类似经验的人看来,没什么卵用。

如果大家觉得看文档有难度,可以参考以下视频:

黑马程序员MyBatis-Plus(简单明了,但对于Wrapper的介绍不够细致)

慕课网MyBatis-Plus入门教程(官网推荐)

慕课网MyBatis-Plus进阶(官网推荐)

有了通用Mapper的铺垫,跟着本文直接操作是最佳实践,所以我不打算写太多描述性的东西,都在代码里。

学习的切入点就是3个测试类,我花了很长时间写的:

  • BaseMapperTest
  • ServiceTest
  • MapperXMLTest

按顺序学习,好好看注释。最后给了思维导图,画了很久的。

通用Mapper的问题

上一篇我们测试了通用Mapper,结果发现隐藏了很多坑:

  • 原生接口批量操作支持不佳
  • 非Selective方法会导致数据库默认值不生效(会传入null)
  • 对于条件查询、条件更新、条件删除,当条件为null时可能导致全表操作

image.png

image.png

那MyBatis-Plus能解决这些问题吗?

MyBatis-Plus环境搭建

MyBatis-Plus由baomidou组织开源,和通用Mapper不是同一个作者。但不论是通用Mapper还是Mybatis-Plus,都是我们国人开发的。按官网的说法,MyBatis-Plus是在Spring容器启动时完成SQL的解析和注入,之后的调用几乎没什么性能损耗。

让我们开始吧。

最终结构:

image.png

 

准备一个空的SpringBoot项目。

SQL

DROP TABLE IF EXISTS `mp_user`;
CREATE TABLE `mp_user` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id',
  `name` varchar(50) DEFAULT '' COMMENT '姓名',
  `age` tinyint(3) unsigned DEFAULT NULL COMMENT '年龄',
  `user_type` tinyint(1) unsigned DEFAULT '1' COMMENT '用户类型 1-大帅哥 2-普通帅哥 3-小帅哥',
  `create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
  `deleted` tinyint(1) unsigned DEFAULT '0' COMMENT '是否删除',
  `version` int(11) unsigned DEFAULT '0' COMMENT '乐观锁版本号',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<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.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.3.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.bravo</groupId>
    <artifactId>mybatis-plus-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>mybatis-plus-demo</name>
    <description>Demo project for Spring Boot</description>

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

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.0</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

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

</project>

启动类

/**
 * 注意,Mybatis-Plus使用的是原生的@MapperScan,是org包下的,而通用Mapper使用的是tk包下的
 * @author qiyu
 */
@MapperScan("com.bravo.demo.dao")
@SpringBootApplication
public class MybatisPlusDemoApplication {

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

}

application.yml

server:
  port: 7890

spring:
  datasource:
    url: jdbc:mysql:///test?useUnicode=true&characterEncoding=utf-8&useSSL=false&allowMultiQueries=true
    username: root
    password: root
    driver-class-name: com.mysql.jdbc.Driver

logging:
  level:
    com.bravo.demo.dao: debug

POJO

/**
 * 通用Mapper中叫 @Table(name = "tk_user")
 *
 * @author qiyu
 */
@Data
@TableName("mp_user")
public class MpUserPojo {

    /**
     * MyBatis-Plus默认名为'id'的字段是主键
     * 如果主键名不叫'id',而是'userId'之类的,必须通过 @TableId 标识
     * 主键生成策略默认是无意义的long数值,可以指定@TableId的IdType属性为AUTO,随数据库自增
     * 加上 @TableId(type = IdType.AUTO)
     */
    private Long id;

    /**
     * 姓名
     */
    private String name;

    /**
     * 年龄
     */
    private Integer age;

    /**
     * 用户类型
     */
    private Integer userType;

    /**
     * 创建时间
     */
    private Date createTime;

    /**
     * 修改时间
     */
    private Date updateTime;

    /**
     * 是否删除,逻辑删除请用 @TableLogic
     */
    private Boolean deleted;

    /**
     * 乐观锁版本号,需要乐观锁请用 @Version
     * 支持的字段类型:
     * long,
     * Long,
     * int,
     * Integer,
     * java.util.Date,
     * java.sql.Timestamp,
     * java.time.LocalDateTime
     */
    private Integer version;

}

Mapper接口

/**
 * 继承MyBatis-Plus提供的BaseMapper,提供了增删改查及分页方法,基本已经完全满足日常开发需求
 *
 * @author qiyu
 */
public interface MpUserMapper extends BaseMapper<MpUserPojo> {
}

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="com.bravo.demo.dao.MpUserMapper">
    <resultMap id="BaseResultMap" type="com.bravo.demo.pojo.MpUserPojo">
        <!--
          WARNING - @mbg.generated
        -->
        <id column="id" jdbcType="BIGINT" property="id"/>
        <result column="name" jdbcType="VARCHAR" property="name"/>
        <result column="age" jdbcType="TINYINT" property="age"/>
        <result column="create_time" jdbcType="TIMESTAMP" property="createTime"/>
        <result column="update_time" jdbcType="TIMESTAMP" property="updateTime"/>
        <result column="deleted" jdbcType="BIT" property="deleted"/>
    </resultMap>

    <select id="myGetById" resultType="com.bravo.demo.pojo.MpUserPojo">
        SELECT name, age, user_type FROM mp_user WHERE id=#{id}
    </select>

</mapper>

Service接口

/**
 * 继承MyBatis-Plus提供的IService接口
 *
 * @author qiyu
 * @date 2020-09-13 16:38
 */
public interface MpUserService extends IService<MpUserPojo> {
}

Service实现类

/**
 * 继承MyBatis-Plus提供的ServiceImpl,定好Mapper和DO
 * 实现自己的Service接口
 * <p>
 * 但不得不说,这种写法很恶心,语义上讲不通
 *
 * @author qiyu
 * @date 2020-09-13 16:39
 */
@Service
public class MpUserServiceImpl extends ServiceImpl<MpUserMapper, MpUserPojo> implements MpUserService {
}

MybaitsPlusConfig

/**
 * @author qiyu
 * @date 2020-09-13 14:00
 */
@Configuration
public class MybatisPlusConfig {
    /**
     * MyBatis-Plus插件配置
     */
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        // 新的分页插件,一缓和二缓遵循mybatis的规则,需要设置 MybatisConfiguration#useDeprecatedExecutor = false 避免缓存出现问题
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        // 乐观锁插件
        interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
        return interceptor;
    }

}

测试案例

拷贝后在自己本地调试每个方法

如果数据脏了需要清表,请重新执行一遍最开始的SQL。

通过BaseMapper学习基本操作

/**
 * 执行的时候注意观察控制台打印的SQL
 *
 * 困惑一:
 * QueryWrapper和UpdateWrapper啥区别?
 *
 * 你可以把QueryWrapper等同于通用Mapper的Example,是用来构造条件的。
 * 而UpdateWrapper几乎和QueryWrapper是一样的,只不过多了set()方法,我们可以通过set把要更新的值也一并设置进去。
 * 比如你用UpdateWrapper.eq()等方法构造了一堆条件,如果不想额外构造entity,则可以直接set("name", "bravo")
 * 也就是说 UpdateWrapper ≈ QueryWrapper + 要设置的值
 *
 * 困惑二:
 * LambdaQuery这种Lambda开头的Wrapper又是啥?
 *  
 * QueryWrapper/UpdateWrapper默认是这样的wrapper.eq("user_type", 1),条件字段是数据库Column
 * 这样的坏处是容易写错,而且编译器没法校验。
 * LambdaWrapper则支持POJO字段作为条件,比如wrapper.eq(User::getUserType, 1),可以编译期发现错误。
 *
 * @author qiyu
 */
@SpringBootTest
class BaseMapperTest {

    @Autowired
    private MpUserMapper userMapper;

    @Test
    void contextLoads() {
    }

    /**
     * 插入,默认类似于insertSelective()效果。相比通用Mapper,默认名字为id的字段为主键,不需要主键注解
     * INSERT INTO mp_user ( id, name, age, user_type ) VALUES ( 1301805567879794689, '测试1', 18, 1 );
     * 你会发现插入时已经有主键了,MyBatis-Plus默认会自动生成long数值,如需改变请修改MpUserPojo的id
     */
    @Test
    public void testInsert() {
        MpUserPojo mpUserPojo = new MpUserPojo();
        mpUserPojo.setName("测试1");
        mpUserPojo.setAge(18);
        mpUserPojo.setUserType(1);
        // 插入
        userMapper.insert(mpUserPojo);
    }

    @Test
    public void testSelect() {
        /**
         * 根据主键查询
         * SELECT id,name,age,user_type,create_time,update_time,deleted,version
         * FROM mp_user
         * WHERE id=1;
         */
        MpUserPojo mpUserPojo = userMapper.selectById(null);

        /**
         * 条件查询,如果查出多个则抛异常,个人觉得很鸡肋,不推荐使用
         * SELECT id,name,age,user_type,create_time,update_time,deleted,version
         * FROM mp_user
         * WHERE (age = 18);
         */
//        QueryWrapper<MpUserPojo> oneQuery = new QueryWrapper<>();
//        oneQuery.eq("age", 18);
//        MpUserPojo one = userMapper.selectOne(oneQuery);

        /**
         * 条件查询,本质上和selectOne()一样,推荐
         * SELECT id,name,age,user_type,create_time,update_time,deleted,version
         * FROM mp_user
         * WHERE (age = 18);
         */
        QueryWrapper<MpUserPojo> listQuery = new QueryWrapper<>();
        listQuery.eq("age", 18);
        List<MpUserPojo> list = userMapper.selectList(listQuery);

        /**
         * 条件查询 likeRight ==>  like '测试%',还可以指定查询的字段,避免全列返回
         * SELECT name,age
         * FROM mp_user
         * WHERE (name LIKE 'null%');
         * 即使条件为null,也不会像通用Mapper一样忽略条件导致查询全部
         */
        QueryWrapper<MpUserPojo> queryWrapper = new QueryWrapper<>();
        queryWrapper.select("name", "age").likeRight("name", null);
        List<MpUserPojo> mpUserPojoList2 = userMapper.selectList(queryWrapper);

        /**
         * 批量查询,如果ids==null 或 ids.size()==0,会报错,而不是像通用Mapper一样查询全部
         * SELECT id,name,age,user_type,create_time,update_time,deleted,version
         * FROM mp_user
         * WHERE id IN ( );
         * 其实你点进源码,会发现人家已经提醒了ids不能为空
         */
//        List<MpUserPojo> mpUserList = userMapper.selectBatchIds(new ArrayList<>());

        /**
         * 分页查询,需要额外配置MyBatis-Plus提供的分页插件,{@link com.bravo.demo.config.MybatisPlusConfig}
         *
         * 会发送两条SQL:
         *
         * 1.查询总数量
         * SELECT COUNT(1)
         * FROM mp_user
         * WHERE (name LIKE '测试1%');
         *
         * 2.查询分页数据
         * SELECT name,age
         * FROM mp_user
         * WHERE (name LIKE '测试1%')
         * LIMIT 1;
         *
         * list打印:[MpUserPojo(id=null, name=测试1, age=18, userType=null, createTime=null, updateTime=null, deleted=null, version=null)]
         */
        // 分页
        Page<MpUserPojo> page = new Page<>();
        page.setPages(1);
        page.setSize(1);
        // 条件
        QueryWrapper<MpUserPojo> pageQuery = new QueryWrapper<>();
        pageQuery.select("name", "age").likeRight("name", "测试1");
        Page<MpUserPojo> pageList = userMapper.selectPage(page, pageQuery);
        System.out.println(pageList.getRecords());

        /**
         * 如果你仔细观察上面SQL,会发现虽然指定查询name、age,但实际上是用整个MpUserPojo对象接收的,其他都为null
         * 如果你觉得这样太难看,可以使用以下方法,让返回值是map。
         * 然而个人觉得,没太大卵用,因为实际开发还是建议返回对象,而不是map
         *
         * SELECT name,age
         * FROM mp_user
         * WHERE (name LIKE '测试1%');
         *
         * list打印:[{name=测试1, age=18}, {name=测试1, age=18}]
         */
        List<Map<String, Object>> maps = userMapper.selectMaps(pageQuery);
        System.out.println(maps);

        /**
         * 和上面的一样,只不过这次是分页。我不是很喜欢这种。
         *
         * 1.查询总数量
         * SELECT COUNT(1)
         * FROM mp_user
         * WHERE (name LIKE '测试1%');
         *
         * 2.查询分页数据
         * SELECT name,age
         * FROM mp_user
         * WHERE (name LIKE '测试1%')
         * LIMIT 1;
         *
         * list打印:[{name=测试1, age=18}]
         */
        Page<Map<String, Object>> mapPage = new Page<>();
        mapPage.setPages(1);
        mapPage.setSize(1);
        Page<Map<String, Object>> mapPageList = userMapper.selectMapsPage(mapPage, pageQuery);
        System.out.println(mapPageList.getRecords());

        /**
         * COUNT方法
         *
         * SELECT COUNT( 1 )
         * FROM mp_user
         * WHERE (name = '测试1');
         */
        QueryWrapper<MpUserPojo> countQuery = new QueryWrapper<>();
        countQuery.eq("name", "测试1");
        Integer count = userMapper.selectCount(countQuery);
        System.out.println(count);

        /**
         * 查询条件除了用QueryWrapper构造以外,还可以用Map,不过Map构造的条件都是等值条件,而queryWrapper可以使用like等
         * 就用QueryWrapper即可,太多选择反而显得凌乱,不推荐
         */
        Map<String, Object> queryMap = new HashMap<>();
        queryMap.put("name", "测试1");
        List<MpUserPojo> result = userMapper.selectByMap(queryMap);
        System.out.println(result);
    }

    /**
     * 只更新设置的字段,类似于updateByPrimaryKeySelective()
     * INSERT INTO mp_user ( id, name, age, user_type ) VALUES ( 1, '测试1', 18, 1 );
     * UPDATE mp_user SET name='修改后的值' WHERE id=1;
     */
    @Test
    public void testUpdateById() {
        // 先插入
        MpUserPojo mpUserPojo = new MpUserPojo();
        mpUserPojo.setName("测试1");
        mpUserPojo.setAge(18);
        mpUserPojo.setUserType(1);
        userMapper.insert(mpUserPojo);

        // 修改
        MpUserPojo updatePojo = new MpUserPojo();
        updatePojo.setId(mpUserPojo.getId());
        updatePojo.setName("修改后的值");
        userMapper.updateById(updatePojo);
    }

    /**
     * 即使传的条件为null也不会导致条件消失,能更有效防止条件失效导致的全表修改
     * INSERT INTO mp_user ( id, name, age, user_type ) VALUES ( 1, '测试1', 18, 1 );
     * UPDATE mp_user SET name='修改后的值' WHERE (user_type = null);
     */
    @Test
    public void testUpdate() {
        // 先插入
        MpUserPojo mpUserPojo = new MpUserPojo();
        mpUserPojo.setName("测试2");
        mpUserPojo.setAge(18);
        mpUserPojo.setUserType(1);
        userMapper.insert(mpUserPojo);

        // 修改
        MpUserPojo updatePojo = new MpUserPojo();
        updatePojo.setId(mpUserPojo.getId());
        updatePojo.setName("修改后的值");
        UpdateWrapper<MpUserPojo> updateWrapper = new UpdateWrapper<>();
        // 即使条件为null也不会更新全表。但是!!!如果Wrapper没有设置任何条件,仍然会更新全表!
//        updateWrapper.eq("user_type", null);
        userMapper.update(updatePojo, updateWrapper);
    }

    /**
     * 默认都是物理删除,如果需要逻辑删除,请在deleted上加@TableLogic
     */
    @Test
    public void testDelete() {
        /**
         * 根据主键删除(物理删除)
         * DELETE
         * FROM mp_user
         * WHERE id=1;
         */
        userMapper.deleteById(1L);

        /**
         * 批量删除(物理删除),如果ids.size()==0,会报错,而不是像通用Mapper一样删除全部
         * DELETE
         * FROM mp_user
         * WHERE id IN ( );
         */
        userMapper.deleteBatchIds(Arrays.asList(1L, 2L));

        /**
         * 条件删除(物理删除)
         * DELETE
         * FROM mp_user
         * WHERE (age = null);
         * 即使条件为null,也不会像通用Mapper一样忽略条件导致删除全部(age=null是无效的,age is NULL才会生效)
         * 注意,如果你不给queryWrapper设置任何条件,仍然会删除全表!!!!
         */
        QueryWrapper<MpUserPojo> queryWrapper = new QueryWrapper<>();
//        queryWrapper.eq("age", null);
        userMapper.delete(queryWrapper);

        /**
         * map构造条件,不说了
         */
//        userMapper.deleteByMap()
    }

    /**
     * 乐观锁
     *
     * 支持乐观锁分两步:
     * 1.配置乐观锁插件 {@link com.bravo.demo.config.MybatisPlusConfig}
     * 2.DO字段上加@Version
     *
     * UPDATE mp_user SET name='测试乐观锁', version=1
     * WHERE id=1 AND version=0 AND deleted=0;
     */
    @Test
    public void testOptimisticLock() {
        MpUserPojo mpUserPojo = new MpUserPojo();
        mpUserPojo.setId(1L);
        mpUserPojo.setName("测试乐观锁");
        // 假设这条记录是刚从数据库查出来的,version=0
        mpUserPojo.setVersion(0);
        // 如果更新成功,version=version+1
        userMapper.updateById(mpUserPojo);
    }

}

Service对Mapper的封装

/**
 * 1.Service层更加易用,对Mapper做了一层封装
 *
 * 2.Service层的命名方式和Mapper不同
 * select --> get
 * insert --> save
 * delete --> remove
 * update --> update
 *
 * 3.支持了批量操作,而且可以指定一次操作多少个,比如 saveBatch(Collection<T> entityList, int batchSize);
 *
 * 4.对于增删改,返回值统一为boolean,而不是int(修改行数)
 *
 * 5.getOne(Wrapper, boolean)与BaseMapper的selectOne(Wrapper)不同,如果传false不会抛异常,有多个值则list.get(0)
 *   T getOne(Wrapper<T> queryWrapper, boolean throwEx);
 *
 * 6.Service层链式调用更顺手
 *
 * @author qiyu
 * @date 2020-09-13 16:41
 */
@SpringBootTest
public class ServiceTest {

    @Autowired
    private MpUserService userService;

    @Test
    public void testServiceSave() {
        // ---------- 增 -----------
        // 插入
        MpUserPojo userPojo = new MpUserPojo();
        userPojo.setName("test Service");
        userPojo.setAge(18);
        userService.save(userPojo);

        userPojo.setId(null);

        // 批量插入,即使不传入batchSize,默认也是1000条。比如实际由1w条,内部会按每次1000条批量插入
        boolean save = userService.saveBatch(Collections.singletonList(userPojo), 1000);
    }

    /**
     * 特别注意,虽然Wrapper的条件设置为null不影响,但Wrapper本身不设置任何条件还是会触发全表更新
     */
    @Test
    public void testServiceUpdate() {
        // ---------- 改 LambdaWrapper和普通Wrapper的唯一区别是 一个用POJO的字段,一个用数据库的字段表示条件-----------
        // 根据id更新
        MpUserPojo updateUser = new MpUserPojo();
        updateUser.setId(1L);
        updateUser.setName("bravo2020");
        userService.updateById(updateUser);

        // 条件更新 方式1 lambdaUpdate,用DO的字段
        boolean update1 = userService.lambdaUpdate().eq(MpUserPojo::getName, "bravo").set(MpUserPojo::getAge, 23).update();

        // 条件更新 方式2 普通update,用数据库字段
        boolean update2 = userService.update().eq("name", "bravo").set("age", 18).update();

        // 条件更新 方式3 传入LambdaUpdateWrapper,用DO的字段
        boolean update3 = userService.update(new LambdaUpdateWrapper<MpUserPojo>().eq(MpUserPojo::getName, "bravo").set(MpUserPojo::getAge, 18));

        // 条件更新 方式4 传入UpdateWrapper,用数据库字段
        boolean update4 = userService.update(new UpdateWrapper<MpUserPojo>().eq("name", "bravo").set("age", 18));

        // 条件更新 方式5 传入Entity表示更新的字段,QueryWrapper表示条件
        boolean bravo5 = userService.update(updateUser, new QueryWrapper<MpUserPojo>().lambda().eq(MpUserPojo::getName, "bravo"));

        // 批量更新。如果要根据条件批量更新还是自己写吧,注意SQL。
        boolean update = userService.updateBatchById(Collections.singletonList(updateUser), 1000);
    }

    @Test
    public void testServiceGet() {
        // ---------- 查 -----------
        // 条件查询 方式1 lambdaQuery,用DO的字段
        List<MpUserPojo> queryList1 = userService.lambdaQuery().eq(MpUserPojo::getName, "bravo")
                .select(MpUserPojo::getName, MpUserPojo::getAge)
                .list();

        // 条件查询 方式2 普通Query,用数据库字段
        List<MpUserPojo> queryList2 = userService.query().ge("age", 19).select("name", "age").list();

        // 条件查询 方式3 传入LambdaQueryWrapper,用DO的字段
        List<MpUserPojo> queryList3 = userService.list(new LambdaQueryWrapper<MpUserPojo>().eq(MpUserPojo::getName, "bravo"));

        // 条件查询 方式4 传入QueryWrapper,用数据库字段
        List<MpUserPojo> queryList4 = userService.list(new QueryWrapper<MpUserPojo>().eq("name", "bravo"));

        // getOne条件查询,有个重载方法 getOne(Wrapper<T> queryWrapper, boolean throwEx);
        MpUserPojo getOne = userService.getOne(Wrappers.<MpUserPojo>lambdaQuery().eq(MpUserPojo::getName, "bravo"));

        // 批量查询
        List<MpUserPojo> listBatch = userService.listByIds(Arrays.asList(1L, 2L, 3L));

        // 分页
        Page<MpUserPojo> page = new Page<>();
        page.setPages(1);
        page.setSize(2);
        Page<MpUserPojo> pageList = userService.page(page, new QueryWrapper<MpUserPojo>().lambda().eq(MpUserPojo::getName, "bravo"));

        // count
        int count = userService.count(new LambdaQueryWrapper<MpUserPojo>().eq(MpUserPojo::getName, "bravo"));
    }

    /**
     * 特别注意,虽然Wrapper的条件设置为null不影响,但Wrapper本身不设置任何条件还是会触发全表删除
     */
    @Test
    public void testServiceRemove() {
        // ---------- 删 和通用Mapper不同的是,@TableLogic对批量删除也是起作用的 -----------
        // 根据id删除
        userService.removeById(1L);
        // 条件删除,特别注意,Wrapper不设置任何条件还是会触发全表删除
        userService.remove(Wrappers.<MpUserPojo>lambdaQuery());
        // 根据ids批量删除 UPDATE mp_user SET deleted=1 WHERE id IN ( 1 , 2 , 3 ) AND deleted=0;
        boolean remove = userService.removeByIds(Arrays.asList(1L, 2L, 3L));
    }

}

手写SQL与驼峰映射

/**
 * 测试手写SQL与接口方法是否会自动映射驼峰
 * <p>
 * 在通用Mapper中,接口的驼峰和手写SQL的驼峰要分别开启,MyBatis-Plus是一起的
 *
 * @author qiyu
 * @date 2020-09-13 18:16
 */
@SpringBootTest
public class MapperXMLTest {

    @Autowired
    private MpUserMapper userMapper;

    @Test
    public void create() {
        MpUserPojo mpUserPojo = new MpUserPojo();
        mpUserPojo.setName("bravo");
        mpUserPojo.setUserType(1);
        mpUserPojo.setAge(18);
        userMapper.insert(mpUserPojo);
    }

    @Test
    public void test() {
        // 手写SQL
        MpUserPojo userPojo = userMapper.myGetById(1305079897975795715L);
        System.out.println(userPojo);
        // 接口方法
        MpUserPojo mpUserPojo = userMapper.selectById(1305079897975795715L);
        System.out.println(mpUserPojo);
    }

}

全局设置

#关于MyBatis-Plus的设置
mybatis-plus:
  # 别名扫描路径。这样Mapper.xml中的每个语句的resultType就不用写全路径了(但我还是习惯写全路径,尤其类很多的时候,不看路径不知道谁是谁)
  type-aliases-package: com.bravo.demo.pojo

  # Mapper.xml扫描路径。然后在Mapper接口写上自定义方法并关联XML语句,即可实现手写SQL
  mapper-locations: classpath*:mapper/**/*.xml

  # MyBatis-Plus驼峰转换,配置后不论手写SQL还是接口方法,都能自动映射(默认on)
  configuration:
    map-underscore-to-camel-case: on

  # 全局设置:主键生成策略、逻辑删除字段
  global-config:
    db-config:
      id-type: auto
      logic-delete-field: deleted

MyBatis-Plus VS 通用Mapper

  • MyBatis-Plus默认是Selective的,避免插入null导致数据库默认值失效,实在想改也行,但不建议
  • MyBatis-Plus默认会生成id,如果不需要可以自行配置,且默认"id"为主键,但可以通过@TableId修改
  • MyBatis-Plus提供了Wrapper接口,使用更安全,能避免一部分条件空值导致的全表修改,但Wrapper为空还是会导致全表操作
  • MyBatis-Plus和通用Mapper一样,默认是物理删除,但可配置为逻辑删除
  • MyBatis-Plus条件构造可以使用数据库字段也可以用POJO属性(Lambda)通用Mapper使用的是POJO属性
  • MyBatis-Plus自带分页功能,而通用Mapper需要额外引入PageHelper
  • MyBatis-Plus做乐观锁很简单
  • MyBatis-Plus还有一些插件能防止全表更新、删除
  • MyBatis-Plus可以帮我们分批执行批量SQL,比如1次1000条(MyBatis默认批量操作一次最多发送1w+)

可以说,MyBatis-Plus几乎是完胜通用Mapper的...硬要说缺点,MyBatis-Plus比通用Mapper灵活意味着它更难掌握,前者上手难度大概是后者的2倍,尤其Wrapper那一块,api太多了。

还有其他插件以及枚举处理、自动填充,这里就不介绍了,大家看官方文档即可。

要相信自己,官方文档而已,还是中国人写的,稳得一批~

MyBatis-Plus官方文档

create_time/update_time默认值处理

大家应该注意到了,虽然MyBatis-Plus有自动填充的功能(去查文档),但我都习惯让数据库默认生成create_time、update_time、deleted。在我看来,这样的好处有:

  • 防止忘了设置
  • 简化代码

那么,对于代码中set和数据库默认设置,两者性能对比如何呢?

我做了个实验:循环插入10w条数据并测试耗时。

/**
 * 关于create_time,update_time默认值的生成性能
 * <p>
 * 数据库自动生成 VS 代码里生成
 * 
 * 大家把Date类型改为LocalDateTime
 *
 * @author qiyu
 * @date 2020-09-13 20:48
 */
@SpringBootTest
public class DBGenerateDefaultValueTest {

    @Autowired
    private MpUserMapper userMapper;

    /**
     * 代码中生成,耗时:821630
     */
    @Test
    public void testCodeGenerate() {
        long start = System.currentTimeMillis();

        for (int i = 0; i < 100000; i++) {
            MpUserPojo userPojo = new MpUserPojo();
            userPojo.setName("bravo");
            userPojo.setAge(18);
            userPojo.setUserType(1);
            userPojo.setCreateTime(LocalDateTime.now());
            userPojo.setUpdateTime(LocalDateTime.now());
            userPojo.setDeleted(false);
            userPojo.setVersion(0);
            userMapper.insert(userPojo);
        }

        System.out.println("耗时:" + (System.currentTimeMillis() - start));
    }

    // ========== 为了公平性,记得清表 ===========

    /**
     * 数据库生成 耗时:812613
     */
    @Test
    public void testDbGenerate() {
        long start = System.currentTimeMillis();

        for (int i = 0; i < 100000; i++) {
            MpUserPojo userPojo = new MpUserPojo();
            userPojo.setName("bravo");
            userPojo.setAge(18);
            userPojo.setUserType(1);
            userMapper.insert(userPojo);
        }

        System.out.println("耗时:" + (System.currentTimeMillis() - start));
    }

}

代码生成耗时:821秒

数据库生成耗时:812秒

两种方式相差不大。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值