Mybatis Plus

学习新的技术栈时,一定要多敲,多回复,记笔记

1.概述:mybatis 增强工具 原始不变,只做增强

官网官网:MyBatis-PlusRedirect

对数据库的crud操作封装起来,不用我们书写

只需让接口映射继承BaseMapper即可,<>泛型中就是要操作的数据库表对应的实体类如user表

入门

使用时引入mp依赖,定义配置文件即可

1.1关于主键的生成策略

@TableId注解作用:

  1. 映射表中主键字段与实体类属性的关系(尤其表中主键字段名称与实体类属性名称不一致时);
  2. 定义主键生成策略;

//指定主键自增的生成策略

type不指定值时,默认生成的主键iD是基于雪花算法生成的。

@TableId(value = "user_id",type = IdType.AUTO)

生成策略

应用场景

特点

IdType.AUTO

数据库主键自增(确保数据库设置了 主键自增 否则无效)

1.使用数据库自带的主键自增值;2.数据库自增的主键值会回填到实体类中;3.数据库服务端生成的;

IdType.ASSIGN_ID

主键类型为number类型或数字类型String

1.MP客户端生成的主键值;2.生成的主键值是数字形式的字符串3.主键对应的类型可以是数字类型或者数字类型的字符串4.底层基于雪花算法,让数据库的唯一标识也参与id的生成运算,保证id在分布式环境下,全局唯一(避免id的主键冲突问题);

IdType.ASSIGN_UUID

主键类型为 string(包含数字和字母组成)

1.生成的主键值包含数字和字母组成的字符串;2.注意事项:如果数据库中主键值是number类型的,可不可用

注解@TableField作用:

  • 指定表中普通字段与实体类属性之间的映射关系;
  • 忽略实体类中多余属性与表中字段的映射关系(@TableField(exist = false));

以下情况可以省略:

  • 名称一样
  • 数据库字段使用_分割,实体类属性名使用驼峰名称(自动开启驼峰映射)

1.2MP实现常规的增删改查

(1)分页查询

需要定义相关配置:类似于拦截器 用于批量注册

interceptor.addInnerInterceptor(paginationInterceptor);//批量注册要用的bean

1.批量注册mp插件的对象

new MybatisPlusInterceptor();

2.构建分页插件

new PaginationInnerInter

@Configuration
public class MybatisPlusConfig {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        PaginationInnerInterceptor paginationInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);
        // 设置请求的页面大于最大页后操作, true调回到首页,false 继续请求  默认false
        // paginationInterceptor.setOverflow(false);
        // 设置最大单页限制数量,-1不受限制
        paginationInterceptor.setMaxLimit(-1L);
        interceptor.addInnerInterceptor(paginationInterceptor);
        return interceptor;
    } 
}
/**
  * 分页查询:
  *  1. 当前页码:currentPage
  *  2. 每页显示条数:size
  *
  *  注意:使用mp的分页要设置一个拦截器!!!
*/
@Test
public void testSelectPage() {
  int current = 1;//当前页码
  int size = 2;//每页显示条数
  IPage<User> page = new Page(current,size);
  userMapper.selectPage(page,null);
  List<User> records = page.getRecords();//当前页的数据
  long pages = page.getPages();//总页数 2
  long total = page.getTotal();//总记录数 4
  System.out.println(records);
  System.out.println(pages);
  System.out.println(total);
}

(2)QueryWrapper实现基础查询

wrapper对象封装了条件。

/**
 * 基础比较查询
 * Wrapper接口:
 *  1.QueryWrapper
 *    LambdaQueryWrapper //查询
 *  2.UpdateWrapper
 *    LambdaUpdateWrapper //更新
 */
eq( ) :  等于 =
ne( ) :  不等于 <> 或者 !=
gt( ) :  大于 >
ge( ) :  大于等于  >=
lt( ) :  小于 <
le( ) :  小于等于 <=
between ( ) :  BETWEEN 值1 AND 值2 
notBetween ( ) :  NOT BETWEEN 值1 AND 值2 
in( ) :  in
notIn( ) :not in
like():like模糊查询
orderby()
sql中反向查询eg:not like != 等等,查询时是不会走索引的;
查询实现

QueryWrapper逻辑查询or

  1. 通过QueryWrapper多条件查询时,默认使用and关键字拼接SQL;
  2. 通过QueryWrapper调用or()方法时,底层会使用or关键字拼接方法左右的查询条件;

QueryWrapper模糊查询like

  • like("表列名","条件值"); 作用:查询包含关键字的信息,底层会自动添加匹配关键字,比如:%条件值%
  • likeLeft("表列名","条件值"); 作用:左侧模糊搜索,也就是查询以指定条件值结尾的数据,比如:%条件值
  • likeRight("表列名","条件值");作用:右侧模糊搜索,也就是查询以指定条件值开头的数据,比如:条件值%

QueryWrapper排序查询

  • orderByAsc 升序排序,方法内可传入多个字段
  • orderByDesc 降序排序,方法内可传入多个字段

QueryWrapper限定字段查询

select方法说明

MP查询时,默认将表中所有字段数据映射查询,但是有时我们仅仅需要查询部分字段信息,这是可以使用select()方法限定返回的字段信息,避免I/O资源的浪费;

QueryWrapper实现分页条件查询

//参数1:分页对象
//参数2:查询条件
mapper.selectPage(page,wrapper);

(3)LambdaQueryWrapper查询

  1. 使用QueryWrapper查询数据时需要手写对应表的列名信息,及其容易写错,开发体验不好;
  2. 使用QueryWrapper查询数据时,表的列名硬编码书写,后期一旦表结构更改,则会带来很大的修改工作量,维护性较差;

LambdaQueryWrapper可以解决上述出现问题,开发推荐使用;

如:wrapper.like(User::getPassword, "123456") 条件方法中用实体类::get方法当第一个参数

@Test
public void testWrapper4() throws Exception{
  // LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
  LambdaQueryWrapper<User> wrapper = Wrappers.<User>lambdaQuery();

  //        wrapper.like("user_name", "%伤%")
  //                .eq("password","123456")
  //                .ge("age", 28)
  //                .between("age",29 , 39);  // 包含边界值
  wrapper.like(User::getUserName, "%伤%")
    .eq(User::getPassword, "123456")
    .ge(User::getAge, 28)
    .between(User::getAge, 29, 39)
    .orderByDesc(User::getAge)
    .select(User::getId, User::getUserName);
  List<User> users = userMapper.selectList(wrapper);
  System.out.println(users);
}

条件删除

条件更新

(4)自定义查询接口实现分页查询

目前我们使用MP自带的分页插件可以很友好的实现分页查询操作,但是如果一些查询需要我们自定义SQL,那该如何实现分页查询操作呢?

  1. 1自定义接口中直接传入Page分页对象即可;
//@Mapper
public interface UserMapper extends BaseMapper<User> {
    /**
     * 查询大于指定id的用户信息,并分页查询实现
     * @param page
     * @param id
     * @return
     */
    IPage<User> findGtIdByPage(IPage<User> page, @Param("id") Long id);
}
  1. 2定义xml映射文件
//@Mapper
public interface UserMapper extends BaseMapper<User> {
    /**
     * 查询大于指定id的用户信息,并分页查询实现
     * @param page
     * @param id
     * @return
     */
    IPage<User> findGtIdByPage(IPage<User> page, @Param("id") Long id);
}

测试2:

/**
     * @Description 自定义sql分页查询实现
     */
@Test
public void test19(){
    IPage<User> page=new Page<>(2,3);
    IPage<User> users = userMapper.findGtIdByPage(page, 3l);
    System.out.println(users.getRecords());
    System.out.println(user.getPages());
    System.out.println(user.getTotal());
}

5.mp实现Service封装

5.1为了快捷的开发,对业务层Service也进行了封装,直接提供了相关的接口和实现类,开发时,可继承它提供的接口和实现类,使得编码更加高效。

步骤

  1. 定义一个xxxService接口,该接口继承公共接口IService
  2. 定义一个xxxServiceImpl服务实现类,该类继承ServiceImpl<Mapper,Entity>,并实现自定义的扩展接口。

注意:1.ServiceImpl父类已经注入了UserMapper对象,名称叫做baseMapper,所以当前实现类直接可以使用baseMapper完成操作2.因为ServiceImpl已经实现了IService下的方法,所以当前服务类没有必要再次实现

思想:共性的业务代码交给框架封装维护,非共性的业务,在接口xxxService定义,然后在当前的服务类下实现

核心API介绍

5.2 快速入门

1.定义服务扩展接口

2.定义服务实现

3.测试

结果:

5.3 MP封装Service实现crud操作

(1)1.条件查询且仅返回一个

/**
     * 实现crud
     * 1.条件查询且仅返回一个
     * getOne:  sql查询的结果必须为1个或者没有,否则报错 即符合条件的超过1个就会报错
     */
@Test
public void c(){
    LambdaQueryWrapper<User> wrapper = Wrappers.<User>lambdaQuery();
    wrapper.le(User::getAge,12);
    User one = userService.getOne(wrapper);
    System.out.println(one);
}
}

(2)根据条件批量查询

/**
* @Description  根据条件批量查询并分页 
*/
@Test  
public void test4(){
LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(User.class);
wrapper.gt(User::getAge,20);
//构建分页对象
IPage page=new Page<>(2,3);
userService.page(page,wrapper);
System.out.println(page.getRecords());
System.out.println(page.getPages());
System.out.println(page.getTotal());
}

/**
 * @Description 测试服务层save保存单条操作
 */
@Test
public void test5(){
    User user1 = User.builder().name("wangwu").userName("laowang4").
    email("444@163.com").age(20).password("333").build();
    boolean isSuccess = userService.save(user1);
    System.out.println(isSuccess?"保存成功":"保存失败");
}

/**
 * @Description 测试服务层批量保存
 */
@Test
public void test6(){
    User user2 = User.builder().name("wangwu2").userName("laowang2").
    email("444@163.com").age(20).password("333").build();
    User user3 = User.builder().name("wangwu3").userName("laowang3").
    email("444@163.com").age(20).password("333").build();
    boolean isSuccess = userService.saveBatch(Arrays.asList(user2, user3));
    System.out.println(isSuccess?"保存成功":"保存失败");
}

/**
 * @Description 根据id删除操作
 */
@Test
public void test7(){
    boolean isSuccess = userService.removeById(17l);
    System.out.println(isSuccess?"保存成功":"保存失败");
}

/**
 * @Description 根据条件批量删除
 */
@Test
public void test8(){
    LambdaQueryWrapper<User> wrapper = Wrappers.lambdaQuery(User.class);
    wrapper.gt(User::getId,12)
    .gt(User::getAge,20);
    boolean remove = userService.remove(wrapper);
    System.out.println(remove);
}

/**
 * @Description 测试根据id更新数据
 */
@Test
public void test9(){
    //UPDATE tb_user SET password=?, t_name=? WHERE id=?
    User user2 = User.builder().name("wangwu2").password("333").id(3l).build();
    boolean success = userService.updateById(user2);
    System.out.println(success);
}

/**
 * @Description 测试根据条件批量更新
 */
@Test
public void test10(){
    LambdaUpdateWrapper<User> wrapper = Wrappers.lambdaUpdate(User.class);
    //UPDATE tb_user SET age=? WHERE (id IN (?,?,?))
    wrapper.in(User::getId,Arrays.asList(1l,3l,5l)).set(User::getAge,40);
    boolean update = userService.update(wrapper);
    System.out.println(userService);
}

6.MP代码生成器

前景:新的业务实现时,构建过程重复简单的步骤,效率低。

于是又了MP代码生成器,通过MP代码生成器可以生成模板性的代码,减少手工操作的繁琐,使开发人员聚焦于业务开发之上,提升开发效率;

CodeGenerator 类是MyBatis-Plus 的核心代码生成器类,通过 CodeGenerator 可以

快速生成 Mapper接口、Entity实体类、Mapper XML、Service、Controller 等各个

模块的代码,极大的提升了开发效率。

不推荐使用,稍微麻烦点

推荐使用插件MybatisX

7.逻辑删除

物理删除是deletefrom ,而逻辑删除只是在删除的数据上打标记,并没有实际删除

实际在删除数据时,为了数据留痕,一般选择逻辑删除,也就是为删除表添加逻辑删除字段,通过修改字段状态值来表示数据是否被删除;mp配置:

# 设置mp运行时行为
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 控制台输出sql
  global-config:
    db-config:
      logic-delete-field: deleted # 约定全局删除字段
      logic-delete-value: 1 #删除则为1
      logic-not-delete-value: 0

调整实体类:

@Data
@NoArgsConstructor//主要用于mybatis底层反射构建user实体类对象
@AllArgsConstructor//主要是lombok基于构建者模式构建对象
@Builder
/**
 * 如果变的名称与实体类名称一致,该注解可省略
 */
@TableName("tb_user")
public class User {
	//......
    @TableLogic//指定逻辑删除字段
    private Integer deleted;
}

测试

    @Test
    public void testDelete(){
        //根据id删除
        int count = userMapper.deleteById(12l);
        System.out.println(count);
    }

输出的效果:

但是对应的查询如果不加添加,则删除的无法查询到:

    @Test
    public void testGetById(){
        User user = userMapper.selectById(12l);//查询已经删除的iD12的User
        System.out.println(user);
    }
//注意观察它的sql语句  追加的条件deleted=0

效果:

逻辑删除本质就是拦截sql,动态追加sql片段

查询  deleted=0

       删除: 将sql转化成update操作;

总结:一定要在yaml配置文件中添加全局逻辑删除的字段,在实体类中指定逻辑删除字段

8.乐观锁

说到乐观锁,我们需要先了解事务

事务的操作本是数据库进行控制,为了方便用户进行业务逻辑的操作,spring对事务进行了扩展实现,在spring框架中有两种事务的实现方式,①编程式事务,用户通过自己的代码来控制事务的处理逻辑。②通过注解@Transactional实现,就是声明式事务。一般开发中我们多用通过添加@Transactional注解来进行实现,当添加此注解之后事务的自动功能就会关闭,由spring框架来进行控制。

说白了,事务操作就是AOP思想的体现,当一个方法添加@Transactional注解后,spring会基于此类生成一个代理对象,会将这个代理对象作为bean,当使用这个代理对象的方法时,如果有事务处理,就会先把事务的自动提交给关系,然后去执行具体的业务逻辑,如果执行逻辑没有出现异常,那么代理逻辑就会直接提交,如果出现任何异常情况,那么直接进行回滚操作,当户也可控制对哪些异常进行回滚操作。

乐观锁就是当前操作者认为在自己操作资源的过程中,其他人操作相同资源的可能性极低,所以无需加锁,而是通过设置一个版本号来加以约束;

悲观锁:排它锁,比如synchronized关键字就是悲观锁,当前线程做操作时,不允许其它线程做操作;

乐观锁:当前线程做操作时,允许其它线程做操作,但是如果其它线程做了操作,则当前操作失败;

乐观锁在数据库中有什么优势?

避免长事务场景锁定数据资源,导致其它线程操作该资源时阻塞,如果阻塞过多,那么导致数据库连接资源耗尽,进而数据库宕机了;

本质上就是在操作前,先获取操作行的version版本号,然后再做前天操作,然后最后再更新这一行,更新时,给sql条件一个判断版本号的sql片段: select version,xxx from user where id=100; version=30 --> 做其他操作(20s);---> update user set xxx,version=version+1 where xxxx and version=30;

使用场景:

1.业务操作周期长,如果业务整个加入事务,导致数据库资源锁定周期过长,性能降低;

2.如果资源争抢过于激烈,会导致失败重试次数过多,导致性能降低;

  @Version
    private Integer version;
  /**
     * 注册插件
     * @return
     */
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor(){
        //构建mp的插件注册器bean,通过该bean可批量注册多个插件
        MybatisPlusInterceptor plusInterceptor = new MybatisPlusInterceptor();
        //配置乐观锁拦截器
        OptimisticLockerInnerInterceptor optimisticLockerInnerInterceptor = new OptimisticLockerInnerInterceptor();
        //注册
        plusInterceptor.addInnerInterceptor(optimisticLockerInnerInterceptor);
        return plusInterceptor;
    }

使用mp的乐观锁,需要先自己根据主键id查询用户信息,信息中包含了此时的version数据,然后再更新,更新时会将查询的version值作为更新条件取更新;

9.mp字段自动填充

自动填充是先填充到实体对象中在插入到数据库中,本质上是实体对象对应的属性

引入:create_time和updateTime字段的自动填充

配置类似拦截器等

@Component
public class FillDataHandler implements MetaObjectHandler {
    /**
     * 插入数据时相关的数据封装
     */
    @Override
    public void insertFill(MetaObject metaObject) {
        metaObject.setValue("createTime",new Date());
        metaObject.setValue("updateTime",new Date());

    }
    @Override
    public void updateFill(MetaObject metaObject) {
        metaObject.setValue("updateTime",new Date());
    }
}

测试:

//自动填充测试
@Test
public void tinsert(){
    User user = User.builder().name("阿树")
    .age(18)
    .email("123123")
    .password("12345")
    .build();
    int i = userMapper.insert(user);
    System.out.println(i);
}

  • 29
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值