mybatis-Plus 实践篇之CRUD操作

mybatis-Plus 实践篇之CRUD操作

上篇我们说了mybaits-plus的逆向工程的操作,这篇我们来说下CRUD操作吧,本来打算写一篇的,但是篇幅实在有点长;可读性不好,还是拆一下;

快速开始

这里就不重新建项目引入依赖了,我们直接在上篇的项目中开始
开始之前,我们需要开启打印下mybatis-plus在控制台打印的sql,只需要在yaml文件中加上如下配置即可

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

测试查询查看控制台输出:

我们先来看下BaseMapper中有哪些方法,如下:

查询操作

添加分页拦截器,新建MpHandler如下:

package com.demo.study.config;

import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.context.annotation.Configuration;
import java.util.Date;

@Configuration
public class MpHandler {
     // 分页插件
    @Bean
    public PaginationInterceptor paginationInterceptor(){
        return new PaginationInterceptor();
    }
}

测试查询

// 查询(使用的最多,单个,多个,数量,分页查询!)
@Test
void testSelectById(){
    User user = userMapper.selectById(1L);
    System.out.println(user);
}

@Test
void testSelectByIds(){
    // 批量查询
    List<User> users = userMapper.selectBatchIds(Arrays.asList(1, 2, 3));
    users.forEach(System.out::println);
}

@Test
void testSelectByCount(){
    // 查询数据量 SELECT COUNT(1) FROM user
    Integer integer = userMapper.selectCount(null);
    System.out.println(integer);
}

@Test
void testSelectByMap(){
    // 简单的条件查询!
    HashMap<String, Object> map = new HashMap<>();
    map.put("name","张三");
    map.put("age",21);
    List<User> users = userMapper.selectByMap(map);
    users.forEach(System.out::println);
}
// 分页实现 limit(sql) PageHelper Mp内置分页插件(导入即可!)
@Test
void testPage(){ // 当前页、总页数
    //1、先查询总数
    //2、本质还是 LIMIT 0,10 (默认的)
    // 参数 (当前页,每个页面的大小!)
    // 以后做分页就使用Page对象即可!
    Page<User> page = new Page<>(2,5);
    userMapper.selectPage(page,null);
    System.out.println(page.getTotal());
    System.out.println(page.hasNext());
    System.out.println(page.hasPrevious());
    page.getRecords().forEach(System.out::println);
    System.out.println(page.getSize());
}

新增操作

user实体对应字段

// 配置主键自增策略 当然还有其他策略可以自行选择(还有雪花算法,手动设置,uuid等)
@ApiModelProperty(value = "主键")
@TableId(value = "id", type = IdType.AUTO)
private Integer id;

//因为上篇文章中我们配置了这两个时间字段的自动填充策略,
//那么我们在执行插入和更新操作的时候就会自动更新为当前时间
@ApiModelProperty(value = "创建时间")
@TableField(fill = FieldFill.INSERT)
private Date createdDate;

@ApiModelProperty(value = "更新时间")
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updatedDate;

开始之前,我们还需要配置下注解处理器,处理时间的自动填充
MpHandler implements MetaObjectHandler

// 插入的策略
@Override
public void insertFill(MetaObject metaObject) {
    // this.setFieldValByName()设置当前字段的值!
    // String fieldName, Object fieldVal, MetaObject metaObjec
    // 以后只要是插入操作就会自动控制
    // createTime updateTime 使用 new Date() 进行填充
    this.setFieldValByName("createdDate",new Date(),metaObject);
    this.setFieldValByName("updatedDate",new Date(),metaObject);
}
    // 更新策略
@Override
public void updateFill(MetaObject metaObject) {
    this.setFieldValByName("updatedDate",new Date(),metaObject);
}

测试类中测试新增

// 因为这里我们主键配置的自增 时间配置了自动填充所以无需我们再处理
@Test
void testInsert(){
    User user = new User();
    user.setAge(18).setName("如花").setCreatedBy("echo");
    userMapper.insert(user);
    // mp 插入成功后主键会自动回显
    System.out.println(userMapper.selectById(user.getId()));
}

注意:mp 没有提供批量插入,我们需要自己写xml sql来实现!
这里来回顾下,批量插入的写法

<insert id="batchInsert" useGeneratedKeys="true" keyProperty="id" parameterType="java.util.List">
        insert into user
        (name, age) values
        <foreach collection="list" item="item" index="index" separator=",">
            <trim prefix="(" suffix=")" suffixOverrides=",">
                #{item.name,jdbcType=VARCHAR},
                #{item.age,jdbcType=INTEGER}
            </trim>
        </foreach>
</insert>

删除操作

// 删除(单个,多个)
@Test
void testDeleteById(){
    userMapper.deleteById(1);
}
@Test
void testDeleteByIds(){
    userMapper.deleteBatchIds(Arrays.asList(2,3,4,5));
}

一般,我们删除都采用逻辑删除,即将is_deleted改为1即可!

修改操作

开始之前我们先来了解下乐观锁、悲观锁!
乐观锁 : 非常乐观,无论什么操作都不加锁!(分布式环境怎么处理冲突问题呢?版本号)
悲观锁 : 非常悲观,无论什么操作都加锁!(性能问题)
当要更新一条记录的时候,希望这条记录没有被别人更新过,通常的方式就增加一个乐观锁字段即可 (version)

MP 对乐观锁也进行了支持!

  • 1、添加version注解到字段上面
  • 2、添加 乐观锁插件即可!
  • 3、测试就自动带上了版本号!
    数据库新增version字段我们设置初始值为1,那么它每次都会去上一次值做比较,如果是原值就证明没被改动过就更新,如果非原值就更新失败!
    对应的user实体及数据库中我们也新增此version字段加上@version注解
@Version
private Integer version;

这里我们在MpHandler中加入 乐观锁拦截器

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

接下来我们测试更新操作:

@Test
void testLock(){
    User user = userMapper.selectById(2);
    user.setName("老铁");
    userMapper.updateById(user);

}


可以看到更新的时候它帮我们自动加上了这个version 字段

接下来我们来模仿下并发的情况下,因为version加锁更新失败的情况:

 @Test
void testLockFail(){
    // 老铁-> 狂铁
    User initUser = userMapper.selectById(2);
    initUser.setName("狂铁");

    // x 先修改为 打铁
    User xUser = userMapper.selectById(2);
    xUser.setName("打铁");
    userMapper.updateById(xUser);

    // 改成 狂铁
    userMapper.updateById(initUser);
}

执行查看控制台输出

可以看到并发的情况下若是版本号被修改类,initUser则会更新失败!

删除操作

// 删除(单个,多个)
@Test
void testDeleteById(){
    userMapper.deleteById(3);
}
@Test
void testDeleteByIds(){
    userMapper.deleteBatchIds(Arrays.asList(4,5,6);
}
// 简单的条件删除
@Test
void testDeleteByMap(){
    HashMap<String, Object> map = new HashMap<>();
    map.put("name","张三");
    map.put("age",20);
    userMapper.deleteByMap(map);
}

但是我们实际开发中基本不这么干,只是更新is_deleted字段,改成逻辑删除!

然后我们查询的时候只会把没有删除的查询出来,添加@TableLogic

这里因为我们在代码生成器中指定了删除标识字段,所以这个注解自动加上了

@TableLogic 所标注的字段是一个逻辑删除字段

另外我们还需在yaml中增加逻辑删除值的配置

mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  global-config:
    db-config:
      logic-delete-value: 1 #删除
      logic-not-delete-value: 0 #未删除

MpHandler 中新增逻辑删除插件

@Bean
public ISqlInjector sqlInjector(){
    return new LogicSqlInjector();
}

这两步完了之后,我们再测一下

@Test
void testDeleteById(){
    userMapper.deleteById(4);
}
@Test
void testSelectById(){
    userMapper.selectById(4);
}


可以看到删除的数据我们这里已经查不到了,查询的时候自动给我们带上了is_deleted=0的条件

sql 性能分析插件

MpHandler 中加入以下拦截器,就可以拦截慢sql

// SQL执行效率插件
@Bean
@Profile({"dev","test"})// 设置 dev test 环境开启
public PerformanceInterceptor performanceInterceptor() {
    PerformanceInterceptor performanceInterceptor = new PerformanceInterceptor();
    // 允许执行的sql的最长时间 ,默认的单位是ms
    performanceInterceptor.setMaxTime(2000);
    return performanceInterceptor;
}

这里就不演示了,感兴趣的可以自行试下,maxTime改成1 试下,哈哈

根据控制台的报错信息或者说你配置了统一日志打印的话,可以根据里面的报错信息找到对应的慢sql

然后,我们就可以分析对应的问题(分库分表,索引,缓存等)

常用的条件构造器

就是大于,等于,小于,包含,不包含,子查询等

// 条件查询器
@Test
void testLogicDelete(){
    QueryWrapper<User> wrapper = new QueryWrapper();
    wrapper //
    .isNotNull("name") .ge("age",21); // 大于等于
    .eq("age",0);
    userMapper.delete(wrapper);
}

// 边界查询
@Test
void testSelectCount(){
    QueryWrapper<User> wrapper = new QueryWrapper();
    wrapper.between("age",20,25);
    Integer integer = userMapper.selectCount(wrapper);
}

// 精准匹配
@Test
void testSelectList(){
    QueryWrapper<User> wrapper = new QueryWrapper();
    HashMap<String, Object> map = new HashMap<>(16);
    map.put("name","李四");
    wrapper.allEq(map);
    List<User> users = userMapper.selectList(wrapper);
}

// 模糊查询
@Test
void testSelectMaps(){
    QueryWrapper<User> wrapper = new QueryWrapper();
    wrapper.notLike("name","e")
    .likeRight("email","t");
    List<Map<String,Object>> = userMapper.selectMaps(wrapper);
    maps.forEach(System.out::println);
}

// 子查询
@Test
void testSelectObjs(){
    QueryWrapper<User> wrapper = new QueryWrapper();
    // 条件in查询
    wrapper.in("id",1,2,3,4);
    // 子查询!
    wrapper.inSql("id","select id from user where id < 3");
    List<Object> objects = userMapper.selectObjs(wrapper);
    objects.forEach(System.out::println);
}
// and or
@Test
void testUpdate(){
    // 修改值
    User user = new User();
    user.setAge(99).setName("老佛爷");
    UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
    // 在一些新的框架中,链式编程,lambda表达式,函数式接口十分常用!
    updateWrapper.like("name","张三")
    .or(i->i.eq("name","李四")
    .eq("age",0));
    int update = userMapper.update(user, updateWrapper);
}

// 排序
@Test
void selectList(){
    QueryWrapper<User> wrapper = new QueryWrapper();
    wrapper.orderByAsc("id");
    wrapper.orderByDesc("id");
    List<User> users = userMapper.selectList(wrapper);
    users.forEach(System.out::println);
}

@Test
void tSelectList(){
    // 多表查询解决方案 last (不建议使用,相当于直接拼接到sql 后面)
    // 尽量使用单表查询
    // 如果要多表查询,没有简便方法,就只有xml 中自己写了!
    QueryWrapper<User> wrapper = new QueryWrapper();
    wrapper.last("limit 1");
    List<User> users = userMapper.selectList(wrapper);
    users.forEach(System.out::println);
}

多表操作还需添加的配置

如果,单表查询不满足我们的要求的话,就需要自己写sql 查询了;
自己写sql的话,我们需要绑定mapper.xml 和实体的关系
mybatis-plus 需要增加以下配置

mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  global-config:
    db-config:
      logic-delete-value: 1 # 已删除
      logic-not-delete-value: 0 # 未删除
  mapper-locations: classpath*:com/demo/**/*.xml
  type-aliases-package: com.demo.**.entity

pom 文件中增加如下配置:

<build>
    <resources>
       <resource>
         <directory>src/main/java</directory>
            <includes>
                <include>**/*.xml</include>
            </includes>
       </resource>
    </resources>
</build>

好了,至此我们已经完成了mybatis-plus crud语法的学习和常用插件的配置,那么就尽快使用起来吧;