一、MyBatisPlus

  • MyBatisPlus 能大量的节省我们些CRUD的时间,所有的CRUD都能通过MyBatisPlus来自动完成
  • MyBatis Plus 简称 MP 是一个MyBatis的增强工具包,制作增强不做改变,为简化开发,提高生产率而生
  • MyBatis 官网: https://baomidou.com/
  • MyBatis文档: https://baomidou.com/guide/

    二、快速入门

  1. 官网: https://baomidou.com/guide/quick-start.html#%E5%88%9D%E5%A7%8B%E5%8C%96%E5%B7%A5%E7%A8%8B
  2. 创建一个springboot项目,选上lombok,spring web
  3. 导入mybatis plus 依赖

    1. <dependency>
    2. <groupId>com.baomidou</groupId>
    3. <artifactId>mybatis-plus-boot-starter</artifactId>
    4. <version>3.0.5</version>
    5. </dependency>
  4. 创建一个表,随便创建一个用户表

  5. 编写 application.properties 配置文件

    # MySQL 5 配置
    spring.datasource.username=root
    spring.datasource.password=0000
    spring.datasource.url=jdbc:mysql://localhost:3306/mp
    spring.datasource.driver-class-name=com.mysql.jdbc.Driver
    

    1.png

  6. 使用MyBatis之后

    • 创建实体类 User
    • 创建UserMapper接口 继承 BaseMapper 【这个Mapper就是Dao】
    • 在UserMapper接口上添加@Repository 注解
    • 在Application主方法类 上添加
      • //扫描Mapper文件
      • @MapperScan(“com.yixuexi._02_spring_boot_mybatisplus.dao”)
    • 使用

2.png

1. 细节

  • MyBatis和MyBatis-spring 依赖就不要加入到项目中了。MyBatisPlus自动维护
  • 在真实开发中,表的字段一般都会有 version(乐观锁) deleted(逻辑删除) gmt_create(创建时间),gmt_modified(修改时间)

    2. 坑:

  • springboot的MySQL start 的mysql依赖版本是 8.0 版本,和电脑上的不匹配的话会报错,所以最好别选mysqlStart,自己添加mysql5.0的依赖

  • 需要在主启动类上去扫描我们的mapper包下所有的接口
    • @MapperScan(“com.yixuexi._02_spring_boot_mybatisplus.dao”)
  • 如果是MySQL8.0的话,那么spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

    3. 配置日志

    因为现在所有的SQL是不可见的,我们希望通过日志的方式将SQL输出出来
    # 配置日志
    mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
    3.png

三、insert update

1. insert 插入

@Test
void insertPlus(){
    User user = new User();
    user.setId(7);
    user.setAge(18);
    user.setName("仝子瑜");
    user.setEmail("2298320493@qq.com");
    // 如果不设置id的话,会帮我们自动生成id值 这个id值很大很大
    int insert = userMapper.insert(user);
    // 输出受影响的行数
    System.out.println(insert);
}

为什么不设置id,自动插入的值会是很大很大的呢? 123561315643213216541

  1. 在实体类主键字段上添加 @TableId(type = IdType.AUTO)
  2. 数据库字段一定要是自增的 auto_increment
  3. 上面设置了后,插入的User对象中,就算id属性赋值了,也会按照它的自增
  4. 其余的IdType源码解释

未命名图片.png
一旦手动输入[ IdType.INPUT ]之后 就需要自己写ID,如果不写就是NULL【其实这个也可以用来自增,因为实体类是null,到数据库里面会根据数据库的值进行自增】

3.update更新

@Test
void updatePlus(){
    User user = new User();
    user.setId(1);
    user.setEmail("2298320493@qq.com");
    user.setName("zhangsan");
    user.setAge(15);
    // 注意!这里传入的值是一个User对象,通过这个user对象的id进行更新
    userMapper.updateById(user);  
}

update的SQL语句都是MyBatisPlus自己的动态SQL ,通过对属性的ifnull 进行判断。

4. 自动填充

  • 创建时间,修改时间!这些操作一般都是自动化完成的,我们不希望手动更新!
  • 阿里巴巴开发手册:所有的数据库表:gmt_create【create_time】 ,gmt_modified 【update_time】几乎所有的表都要配置上,而且需要自动化!

    方式一:数据库级别 (工作中不建议使用)

  1. 在创建表时添加两个字段 create_time update_time
  2. 字段类型 datetime
  3. 给字段加上默认约束,获得当前时间,和更新时更新

    方式二:代码级别

  4. 删除数据库字段的默认值

  5. 实体类的字段属性上需要增加注解 ```java /**
    • 字段插入时,自动填充 / @TableField(fill = FieldFill.INSERT) private Date createTime; /*
    • 字段更新时,自动填充 */ @TableField(fill = FieldFill.UPDATE) private Date updateTime;

3. 编写处理器 处理注解【人家已经写好了,我们稍微改改】
- 官网地址: [https://baomidou.com/guide/auto-fill-metainfo.html](https://baomidou.com/guide/auto-fill-metainfo.html)
- 一定要记得在处理器上面添加@Component注解,将组件添加到容器中
```java
/**
* @date: 2021/1/5   0:02
* @author: 易学习
* @Component: 一定不要忘记把组件添加到容器中
*/
@Component
public class MyDateObjectHandler implements MetaObjectHandler {

    /**
     * 插入时的填充策略
     * @param metaObject
     */
    @Override
    public void insertFill(MetaObject metaObject) {
        /**
         * setFieldValByName:填充你要指定的值
         *  第一个参数:要填充的字段名
         *  第二个参数:要填充的值
         *  第三个参数:metaObject
         *  这里要写两个 把更新的字段也写上
         */
        this.setFieldValByName("createTime",new Date(),metaObject);
        this.setFieldValByName("updateTime",new Date(),metaObject);
    }
    /**
     * 更新时的填充策略
     * @param metaObject
     */
    @Override
    public void updateFill(MetaObject metaObject) {
        // 和上面一样,只是不需要设置 createTime了
        this.setFieldValByName("updateTime",new Date(),metaObject);
    }
}

四、乐观锁 & 悲观锁

乐观锁:顾名思义十分乐观,他总是认为不会出现问题,无论干什么都不会去上锁!如果出现了问题就再次更新值测试。
悲观锁:顾名思义十分悲观,他总是认为总是出现问题,无论干什么都会去上锁!再去上锁。

乐观锁实现方式:

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

乐观锁:现查询,获得版本号 version = 1

— A 线程 update user set name = ‘仝子瑜’,version = version + 1 where id = 22 and version = 1 —- B 线程,抢先修改,这个时候 version = 2 导致_A 修改失败_ update user set name = ‘张三’,version = version + 1 where id = 22 and version = 1

1. 测试MP中的乐观锁

  1. 给数据库中增加version 字段 int 类型 默认值为1
  2. 实体类添加对应的字段,并且加上@version注解,表明这是乐观锁
  3. 注册组件(最新版也需要注册,3.0.5需要注册组件) ```java /**
  • @date: 2021/1/5 19:01
  • @author: 易学习
  • @Configuration: 表明这个是一个配置类,这样的话,之前注册的处理器的扫描 @MapperScan()也可以放到这里
  • @MapperScan: 扫描处理器,这里扫描的是之前那个 createTime 和 updateTime
  • @EnableTransactionManagement: 事务控制 */

@MapperScan(“com.yixuexi.mybatisplus.mapper”) @EnableTransactionManagement @Configuration public class MyBatisPlusConfig {

/**
 * 注册乐观锁插件
 */
@Bean
public OptimisticLockerInterceptor optimisticLockerInterceptor(){
    return new OptimisticLockerInterceptor();
}

}


4. 测试
```java
    /**
    * 测试乐观锁,成功案例
    */
    @Test
    void happyTest(){
        // 1.查询用户信息
        User user1 = userMapper.selectById(1);
        // 2.修改用户信息
        user1.setName("马保国");
        user1.setAge(69);
        user1.setEmail("mabaoguo@163.com");
        // 3.执行更新操作
        userMapper.updateById(user1);
    }
/**
* 测试乐观锁,失败案例
*/
@Test
void happyTest2(){
    // 线程1
    User user1 = userMapper.selectById(1);
    user1.setName("马保国");
    user1.setAge(69);
    user1.setEmail("mabaoguo@163.com");
    // 线程2 模拟另外一个线程执行了插队操作
    User user2 = userMapper.selectById(1);
    user2.setName("旭旭宝宝");
    user2.setAge(35);
    // 线程2 抢先更新
    userMapper.updateById(user2);
    // 线程1执行更新操作
    // 如果没有乐观锁,就会覆盖上面的旭旭宝宝,成为马保国
    // 因为设置了乐观锁 没有覆盖 马保国没有被进行更新,因为where的version=? 不成立
    userMapper.updateById(user1); 
}

五、查询

1. 查询

/**
*      * selectById(id)测试根据id查询一个
*/
@Test
void testSelectById(){
    User user = userMapper.selectById(1);
    System.out.println(user);
}
/**
* selectBatchIds(集合) 测试查询批量
*/
@Test
void testSelectBatchIds(){
    List<User> users = userMapper.selectBatchIds(Arrays.asList(1, 2, 3));
    System.out.println(users);
}
/**
* 条件查询:
*/
@Test
void testSelectByMap(){
    HashMap<String,Object> userHashMap = new HashMap<>();
    //自定义要查询的条件
    userHashMap.put("name","张五");
    List<User> users = userMapper.selectByMap(userHashMap);
    System.out.println(users);
}

2. 分页查询

mp中也集成了分页插件
官网: https://baomidou.com/guide/interceptor.html#%E4%BD%BF%E7%94%A8%E6%96%B9%E5%BC%8F-%E4%BB%A5%E5%88%86%E9%A1%B5%E6%8F%92%E4%BB%B6%E4%B8%BE%E4%BE%8B

1. 加入组件

导入分页插件,在自己创建的MyBatisPlus配置类里面 (3.4.0版本)

   /**
     * 新的分页插件,一缓和二缓遵循mybatis的规则,需要设置 MybatisConfiguration#useDeprecatedExecutor = false 避免缓存出现问题(该属性会在旧插件移除后一同移除)
     */
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.H2));
        return interceptor;
    }

    @Bean
    public ConfigurationCustomizer configurationCustomizer() {
        return configuration -> configuration.setUseDeprecatedExecutor(false);
    }

(3.0.5版本) 分页插件组件

@Bean
public PaginationInterceptor paginationInterceptor(){
    return new PaginationInterceptor();
}

2. 测试分页查询

/**
 * 测试分页插件
 */
@Test
void testPage(){
    //创建一个Page对象
    // 有参构造参数:第一个:当前页。第二个:一次查几个
    Page<User> page = new Page(1,3);
    //通过page的对象getxxx()方法可以获得其余属性
    System.out.println(page.getTotal());
    // 调用selectPage()方法 第一个是page 对象,第二个是wrapper,没有就写null
    IPage<User> userIPage = userMapper.selectPage(page,null);
    System.out.println(userIPage);
}

六、删除

未命名图片.png

@Test
void testDeleteByMap(){
    Map<String,Object> map = new HashMap();
    // 根据条件删除,条件是姓名为张三的记录
    map.put("name","张三");
    userMapper.deleteByMap(map);
}

1. 逻辑删除

  1. 在数据表中增加deleted字段【默认值等于 0】
  2. 实体类中增加deleted属性 添加@TableLogic注解
  3. 添加组件【3.0.5需要 3.1.1以上版本不需要】 ```java /**
  • 逻辑删除组件 */ @Bean public ISqlInjector sqlInjector(){ return new LogicSqlInjector(); } ```
  1. 配置逻辑删除在application.properties中

    # 配置逻辑删除
    # 没有删除的值为0
    mybatis-plus.global-config.db-config.logic-delete-value=1
    # 删除的后的 属性为1
    mybatis-plus.global-config.db-config.logic-not-delete-value=0
    
  2. 测试删除

  • 此时查询也查不到 deleted为1 的用户了

    SELECT id,name,age,email,deleted,version,create_time,update_time FROM user WHERE id=? AND deleted=0 查询会自动在后面拼接一个 and deleted = 0.

未命名图片.png

七、条件构造器

用来写复杂的SQL
Wapper 是一个接口,底下有很多的实现类,查询用QueryWrapper类

具体方法看官网: https://baomidou.com/guide/wrapper.html#abstractwrapper
ge(“字段名”,值) 大于等于

/**
 * @date: 2021/1/5   23:56
 * @author: 易学习
 */
@SpringBootTest
public class WrapperTest {
    @Autowired
    private UserMapper userMapper;

    /**
     * 1. 查询name不为空,并且邮箱不为空的用户,并且年龄>=12
     * isNotNull("字段")   该字段不为空
     * ge("字段",值) 大于等于
     */
    @Test
    void test1(){
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.isNotNull("name")
                .isNotNull("email")
                .ge("age",12);
        List<User> users = userMapper.selectList(wrapper);
        System.out.println(users);
    }

    /**
     * 2. 查询名字为 张五的记录
     */
    @Test
    void test2(){
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.eq("name","张五");
        List user = userMapper.selectList(wrapper);
        System.out.println(user);
    }

    /**
     * 3. 查询年龄在18-20之间的用户数量
     */
    @Test
    void test3(){
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.between("age",18,20);
        Integer integer = userMapper.selectCount(wrapper);
        System.out.println(integer);
    }

    /**
     * 4. 模糊查询,名字里面没有五的
     */
    @Test
    void test4(){
        QueryWrapper wrapper = new QueryWrapper();
        wrapper.notLike("name","五");
        List list = userMapper.selectMaps(wrapper);
        System.out.println(list);
    }
}