application.properties 配置文件中添加 MySQL 数据库的相关配置:

spring boot 2.0(内置jdbc5驱动)

#mysql数据库连接
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/mybatis_plus?characterEncoding=utf-8&useSSL=false
spring.datasource.username=root
spring.datasource.password=root

spring boot 2.1及以上(内置jdbc8驱动)

注意:driverurl的变化

spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/mybatis_plus?serverTimezone=GMT%2B8
spring.datasource.username=root
spring.datasource.password=root

否则运行测试用例报告如下错误:

java.sql.SQLException: The server time zone value 'Öйú±ê׼ʱ¼ä' is unrecognized or represents more

这里的 driver-class-name 使用了  com.mysql.cj.jdbc.Driver ,在 jdbc 8 建议使用这个驱动,否则运行测试用例的时候会有 WARN 信息

MP

1.继承BaseMapper

public interface UserMapper extends BaseMapper<User> {
}

提供了基本的增删改查

查看sql输出日志

#mybatis日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

MP主键策略

MyBatis-Plus默认的主键策略是:ASSIGN_ID (使用了雪花算法)

@TableId(type = IdType.ASSIGN_ID)
private String id;

雪花算法:分布式ID生成器

雪花算法是由Twitter公布的分布式主键生成算法,它能够保证不同表的主键的不重复性,以及相同表的主键的有序性。

核心思想:

长度共64bit(一个long型)。

首先是一个符号位,1bit标识,由于long基本类型在Java中是带符号的,最高位是符号位,正数是0,负数是1,所以id一般是正数,最高位是0

41bit时间截(毫秒级),存储的是时间截的差值(当前时间截 - 开始时间截),结果约等于69.73年。

10bit作为机器的ID5bit是数据中心,5bit的机器ID,可以部署在1024个节点)。

12bit作为毫秒内的流水号(意味着每个节点在每毫秒可以产生 4096 ID

优点:整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞,并且效率较高。

AUTO 自增策略

需要在创建数据表的时候设置主键自增

实体字段中配置 @TableId(type = IdType.AUTO)

@TableId(type = IdType.AUTO)
private Long id;

要想影响所有实体的配置,可以设置全局主键配置

#全局设置主键生成策略
mybatis-plus.global-config.db-config.id-type=auto

MP自动填充

项目中经常会遇到一些数据,每次都使用相同的方式填充,例如记录的创建时间,更新时间等。

我们可以使用MyBatis Plus的自动填充功能,完成这些字段的赋值工作

1.实体上增加字段并添加自动填充注解

@TableField(fill = FieldFill.INSERT)
private Date createTime;  //create_time

@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime; //update_time

2.实现元对象处理器接口

@Component
public class MyMetaObjectHandler implements MetaObjectHandler {

    //mp执行添加操作,这个方法执行
    @Override
    public void insertFill(MetaObject metaObject) {
        this.setFieldValByName("createTime",new Date(),metaObject);
        this.setFieldValByName("updateTime",new Date(),metaObject);
    }

    //mp执行修改操作,这个方法执行
    @Override
    public void updateFill(MetaObject metaObject) {
        this.setFieldValByName("updateTime",new Date(),metaObject);
    }
}

乐观锁:添加版本号

适用场景:当要更新一条记录的时候,希望这条记录没有被别人更新,也就是说实现线程安全的数据更新

   这就是更新丢失:最后更新的数据会覆盖之前更新的数据

例如:抢票.1W人抢一张票,通过乐观锁版本号机制实现只有一个人抢到票

乐观锁实现方式:

取出记录时,获取当前version

更新时,带上这个version

执行更新时, set version = newVersion where version = oldVersion

如果version不对,就更新失败

1.修改实体类

添加 @Version 注解

@Version
private Integer version;

2.创建配置文件

创建包config,创建文件MybatisPlusConfig.java

此时可以删除主类中的 @MapperScan 扫描注解

@Configuration
@MapperScan("com.atguigu.demomptest.mapper")
public class MpConfig {
    /**
     * 乐观锁插件
     */
    @Bean
    public OptimisticLockerInterceptor optimisticLockerInterceptor() {
        return new OptimisticLockerInterceptor();
    }
}

3.注册乐观锁插件

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

4.测试

实体类
@TableLogic
    @TableField(fill = FieldFill.INSERT)
    private Integer deleted;
Handler中
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {

    //mp执行添加操作,这个方法执行
    @Override
    public void insertFill(MetaObject metaObject) {
        this.setFieldValByName("createTime",new Date(),metaObject);
        this.setFieldValByName("updateTime",new Date(),metaObject);
        this.setFieldValByName("version",1,metaObject);//添加填充规则
    }
测试类:先执行新增操作
//添加
    @Test
    public void testAdd() {
        User user = new User();
        user.setName("王五");
        user.setAge(20);
        user.setEmail("[email protected]");
        int insert = userMapper.insert(user);
        System.out.println(insert);
    }

在进行修改操作,可以发现,version版本从1变成了2,证明乐观锁生效了
//测试乐观锁
    @Test
    public void testOptimisticLocker() {
        //根据id查询
        User user = userMapper.selectById(1340899349885612034L);
        //修改
        user.setName("张三");
        userMapper.updateById(user);
    }

查询

1.通过多个id批量查询

//多个id批量查询
@Test
public void testSelect1() {
    List<User> users = userMapper.selectBatchIds(Arrays.asList(1, 2, 3));
    System.out.println(users);
}

2.简单的条件查询

通过map封装查询条件

注意:map中的key对应数据库中的列名。如:数据库user_id,实体类是userId,这时mapkey需要填写user_id

//简单条件查询
@Test
public void testSelect2() {
    Map<String, Object> columnMap = new HashMap<>();
    columnMap.put("name","Jack");
    columnMap.put("age",20);
    List<User> users = userMapper.selectByMap(columnMap);

3.分页

MyBatis Plus自带分页插件,只要简单的配置即可实现分页功能

添加分页插件

配置类中添加@Bean配置
/**
 * 分页插件
 */
@Bean
public PaginationInterceptor paginationInterceptor() {
    return new PaginationInterceptor();
}

测试selectPage分页

//分页查询
@Test
public void testSelectPage() {
    Page<User> page = new Page(1,3);
    Page<User> userPage = userMapper.selectPage(page, null);
    //返回对象得到分页所有数据
    long pages = userPage.getPages(); //总页数
    long current = userPage.getCurrent(); //当前页
    List<User> records = userPage.getRecords(); //查询数据集合
    long total = userPage.getTotal(); //总记录数
    boolean hasNext = userPage.hasNext();  //下一页
    boolean hasPrevious = userPage.hasPrevious(); //上一页

    System.out.println(pages);
    System.out.println(current);
    System.out.println(records);
    System.out.println(total);
    System.out.println(hasNext);

当指定了特定的查询列时,希望分页结果列表只返回被查询的列,而不是很多null

测试selectMapsPage分页:结果集是Map

@Test
public void testSelectMapsPage() {
//Page不需要泛型
Page<Map<String, Object>> page = newPage<>(1, 5);
Page<Map<String, Object>> pageParam = userMapper.selectMapsPage(page, null);
List<Map<String, Object>> records = pageParam.getRecords();
records.forEach(System.out::println);
System.out.println(pageParam.getCurrent());
System.out.println(pageParam.getPages());
System.out.println(pageParam.getSize());
System.out.println(pageParam.getTotal());
System.out.println(pageParam.hasNext());
System.out.println(pageParam.hasPrevious());
}

删除

根据id删除记录
@Test
public void testDeleteById(){
    int result = userMapper.deleteById(5L);
system.out.println(result);
}
批量删除
@Test
public void testDeleteBatchIds() {
    int result = userMapper.deleteBatchIds(Arrays.asList(8, 9, 10));
system.out.println(result);
}
简单条件删除
@Test
public void testDeleteByMap() {
HashMap<String, Object> map = new HashMap<>();
map.put("name", "Helen");
map.put("age", 18);
    int result = userMapper.deleteByMap(map);
system.out.println(result);
}

逻辑删除

物理删除:真实删除,将对应数据从数据库中删除,之后查询不到此条被删除数据

逻辑删除:假删除,将对应数据中代表是否被删除字段状态修改为被删除状态,之后在数据库中仍旧能看到此条数据记录

逻辑删除的使用场景:

可以进行数据恢复

有关联数据,不便删除

实体类修改
添加deleted 字段,并加上 @TableLogic 注解
@TableLogic
private Integer deleted;
Handler中
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {

    //mp执行添加操作,这个方法执行
    @Override
    public void insertFill(MetaObject metaObject) {
        this.setFieldValByName("createTime",new Date(),metaObject);
        this.setFieldValByName("updateTime",new Date(),metaObject);
        this.setFieldValByName("version",1,metaObject);//添加填充规则
        this.setFieldValByName("deleted",0,metaObject); //给逻辑删除字段填充默认值
    }
配置(可选)
application.properties 加入以下配置,此为默认值,如果你的默认值和mp默认的一样,该配置可无
mybatis-plus.global-config.db-config.logic-delete-value=1 #逻辑已删除值(默认为 1)
mybatis-plus.global-config.db-config.logic-not-delete-value=0 # 逻辑未删除值(默认 0)

测试
测试后发现,数据并没有被删除,deleted字段的值由0变成了1
测试后分析打印的sql语句,是一条update
注意:被删除前,数据的deleted 字段的值必须是 0,才能被选取出来执行逻辑删除的操作
@Test
public void testLogicDelete() {
    int result = userMapper.deleteById(1L);
system.out.println(result);
}

条件构造器和常用接口

查询方式:注意:like查询不需要添加%%

QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        //ge、gt、le、lt 大于等于,大于,小于等于,小于
        //queryWrapper.ge("age",21);

        //eq、ne
        //queryWrapper.eq("name","Tom");

        //between、notBetween
        //queryWrapper.between("age",24,28);

        //like、notLike、likeLeft、likeRight
        //queryWrapper.like("name","张");

        //orderBy、orderByDesc、orderByAsc
        queryWrapper.orderByDesc("id");

        List<User> users = userMapper.selectList(queryWrapper);

#AbstractWrapper

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表示紧接着下一个方法不是用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 <> '活着')

QueryWrapper

可以通过 new QueryWrapper().lambda() 方法获取

使用 Wrapper 自定义SQL

用注解

@Select("select * from mysql_data ${ew.customSqlSegment}")
List<MysqlData> getAll(@Param(Constants.WRAPPER) Wrapper wrapper);
04-02 09:59