Mybatis-Plus- CRUD接口-主键策略-自动填充和乐观锁-分页-逻辑删除-条件构造器和常用接口

简介: Mybatis-Plus- CRUD接口-主键策略-自动填充和乐观锁-分页-逻辑删除-条件构造器和常用接口

一、插入操作

添加测试类,进行功能测试:

@SpringBootTest
public class CRUDTests {  

    @Autowired
    private UserMapper userMapper;

    @Test
    public void testInsert(){

        User user = new User();
        user.setName("Helen");
        user.setAge(18);
        user.setEmail("1941310246@qq.com");

        int result = userMapper.insert(user);
        System.out.println("影响的行数:" + result); //影响的行数
        System.out.println("id:" + user); //id自动回填
    }

}

运行 添加成功

注意:数据库插入id值默认为:全局唯一id

二、数据库分库分表策略

背景

随着业务规模的不断扩大,需要选择合适的方案去应对数据规模的增长,以应对逐渐增长的访问压力和数据量。
数据库的扩展方式主要包括:

1. 业务分库
2. 主从复制
3. 数据库分表。

1、业务分库

业务分库指的是按照业务模块将数据分散到不同的数据库服务器。

例如,一个简单的电商网站,包括用户、商品、订单三个业务模块,我们可以将用户数据、商品数据、订单数据分开放到三台不同的数据库服务器上,而不是将所有数据都放在一台数据库服务器上。这样的就变成了3个数据库同时承担压力,系统的吞吐量自然就提高了。

虽然业务分库能够分散存储和访问压力,但同时也带来了新的问题,接下来我进行详细分析。

  • join 操作问题

业务分库后,原本在同一个数据库中的表分散到不同数据库中,导致无法使用 SQL 的 join 查询。

  • 事务问题

原本在同一个数据库中不同的表可以在同一个事务中修改,业务分库后,表分散到不同的数据库中,无法通过事务统一修改。

  • 成本问题

业务分库同时也带来了成本的代价,本来 1 台服务器搞定的事情,现在要 3 台,如果考虑备份,那就是 2 台变成了 6 台。

2、主从复制和读写分离

读写分离的基本原理是将数据库读写操作分散到不同的节点上。读写分离的基本实现是:

  • 数据库服务器搭建主从集群,一主一从、一主多从都可以。
  • 数据库主机负责读写操作,从机只负责读操作。
  • 数据库主机通过复制将数据同步到从机,每台数据库服务器都存储了所有的业务数据。
  • 业务服务器将写操作发给数据库主机,将读操作发给数据库从机。

3、数据库分表

将不同业务数据分散存储到不同的数据库服务器,能够支撑百万甚至千万用户规模的业务,但如果业务继续发展,同一业务的单表数据也会达到单台数据库服务器的处理瓶颈。

例如,淘宝的几亿用户数据,如果全部存放在一台数据库服务器的一张表中,肯定是无法满足性能要求的,此时就需要对单表数据进行拆分。

单表数据拆分有两种方式:垂直分表和水平分表。示意图如下:

  • 垂直分表:将表中某些不常用占用空间的列拆分出去
  • 水平分表:水平分表适合表行数特别大的表

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

雪花算法:

  • 主键生成算法,它能够保证不同表的主键的不重复性,以及相同表的主键的有序性。
  • 整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞,并且效率较高

三、MP的主键策略

1、ASSIGN_ID

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

在实体字段中配置

2、AUTO 自增策略

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

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

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

properties文件中添加

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

四、更新操作

注意:update时生成的sql自动是动态sql:

UPDATE user SET age=? WHERE id=?

在CRUDTests测试类中 添加testUpdateById方法

@Test
public void testUpdateById(){  

    User user = new User();
    user.setId(1427258609506459650L);//要更改的id
        user.setAge(28);
        user.setName("Annie");
    int result = userMapper.updateById(user);
    System.out.println("影响的行数:" + result);
}

运行测试成功

一、自动填充

需求描述:

项目中经常会遇到一些数据,每次都使用相同的方式填充,例如记录的

创建时间,更新时间等。

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

1、数据库修改

在User表中添加datetime类型的新的字段 create_time、update_time

2、实体类修改

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

@Data
public class User {
    ......
        
    @TableField(fill = FieldFill.INSERT)
    private Date createTime;

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

3、实现元对象处理器接口

注意:不要忘记添加 @Component 注解

package com.atguigu.mybatisplus.handler;

@Slf4j
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {

    @Override
    public void insertFill(MetaObject metaObject) {
        log.info("start insert fill ....");
        this.setFieldValByName("createTime", new Date(), metaObject);
        this.setFieldValByName("updateTime", new Date(), metaObject);
    }

    @Override
    public void updateFill(MetaObject metaObject) {
        log.info("start update fill ....");
        this.setFieldValByName("updateTime", new Date(), metaObject);
    }
}


4、测试

运行之前的testInserttestUpdateById添加和修改方法

结果:

  • 创建时间与修改时间自动填充

五、乐观锁

1.场景

并发:当前用户和其他用户一起操作

没有做好并发控制,就可能导致脏读、幻读和不可重复读等问题。

实现并发控制的主要手段分为乐观并发控制和悲观并发控制两种。

2.乐观锁与悲观锁

  • 乐观锁:
    -在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果冲突,则返回给用户异常信息,让用户决 定如何去做。乐观锁适用于读多写少的场景,这样可以提高程序的吞吐量。
  • 悲观锁:
  • -对该数据进行加锁以防止并发
    -改数据之前先锁定,再修改的方式被称之为悲观并发控制

3、模拟修改冲突

(1)数据库中增加商品表

CREATE TABLE product
(
  id BIGINT(20) NOT NULL COMMENT '主键ID',
  name VARCHAR(30) NULL DEFAULT NULL COMMENT '商品名称',
  price INT(11) DEFAULT 0 COMMENT '价格',
  version INT(11) DEFAULT 0 COMMENT '乐观锁版本号',
    PRIMARY KEY (id)
);


(2)添加数据

INSERT INTO product (id, NAME, price) VALUES (1, '冰箱', 100);

(3)创建实体类

package com.atguigu.mybatis_plus.entity;
@Data
public class Product {
    private Long id;
    private String name;
    private Integer price;
    private Integer version;
}

(4)Mapper

package com.atguigu.mybatis_plus.mapper;
@Repository
public interface ProductMapper extends BaseMapper<Product> {
    
}


(5)测试

@Autowired
private ProductMapper productMapper;

@Test
public void testConcurrentUpdate() {

    //1、小李
    Product p1 = productMapper.selectById(1L);
    System.out.println("小李取出的价格:" + p1.getPrice());

    //2、小王
    Product p2 = productMapper.selectById(1L);
    System.out.println("小王取出的价格:" + p2.getPrice());

    //3、小李将价格加了50元,存入了数据库
    p1.setPrice(p1.getPrice() + 50);
    productMapper.updateById(p1);

    //4、小王将商品减了30元,存入了数据库
    p2.setPrice(p2.getPrice() - 30);
    int result = productMapper.updateById(p2);
    if(result == 0){//更新失败,重试
        //重新获取数据
        p2 = productMapper.selectById(1L);
        //更新
        p2.setPrice(p2.getPrice() - 30);
        productMapper.updateById(p2);
    }

    //最后的结果
    Product p3 = productMapper.selectById(1L);
    System.out.println("最后的结果:" + p3.getPrice());
}

原价100 同时取到值100元 小李加入50元 这时数据库数据为150 小王减去30元失败

重新获取数据为150元 这时在减去30元为120元

两人同时对数据操作 导致数据出现错误

4、解决方案

  • 数据库中添加version字段
  • 取出记录时,获取当前version
SELECT id,`name`,price,`version` FROM product WHERE id=1

更新时,version + 1,如果where语句中的version版本不对,则更新失败

UPDATE product SET price=price+50, `version`=`version` + 1 WHERE id=1 AND `version`=1

接下来介绍如何在Mybatis-Plus项目中,使用乐观锁:

5、乐观锁实现流程

(1)修改实体类
在实体类添加 @Version 注解
     @Version
     private Integer version;
(2)创建配置文件

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

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

  
@EnableTransactionManagement//事务处理
@Configuration
@MapperScan("com.atguigu.mybatis_plus.mapper")
public class MybatisPlusConfig {
    
}
(3)注册乐观锁插件

在 MybatisPlusConfig 中注册 Bean

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

(4)测试

更改数据两次 version+2

六、查询

1、通过多个id批量查询

完成了动态sql的foreach的功能

@Test
public void testSelectBatchIds(){

    List<User> users = userMapper.selectBatchIds(Arrays.asList(1, 2, 3));
    users.forEach(System.out::println);
}

查询结果:

2、简单的条件查询

通过map封装查询条件

注意:

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

@Test
public void testSelectByMap(){

    HashMap<String, Object> map = new HashMap<>();
    map.put("name", "Helen");
    map.put("age", 18);
    List<User> users = userMapper.selectByMap(map);

    users.forEach(System.out::println);
}

运行结果:

七、分页

1、分页插件

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

(1)添加分页插件

之前写的MyBatisPlusConfig配置类中添加@Bean配置

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

(2)测试selectPage分页

测试:最终通过page对象获取相关数据

@Test
public void testSelectPage() {

    Page<User> page = new Page<>(1,5);
    Page<User> pageParam = userMapper.selectPage(page, null);

    pageParam.getRecords().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());
}

结果:这里只输出了第一页的数据

2、返回指定的列

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

测试selectMapsPage分页:结果集是Map

@Test
public void testSelectMapsPage() {

    //返回很多null列
    //Page<User> page = new Page<>(1, 5);
    //QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    //queryWrapper.select("name", "age");
    //Page<User> pageParam = userMapper.selectPage(page, queryWrapper);
    //
    //pageParam.getRecords().forEach(System.out::println);


    //Page不需要泛型
    Page<Map<String, Object>> page = new Page<>(1, 5);
    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    queryWrapper.select("name", "age");
    Page<Map<String, Object>> pageParam = userMapper.selectMapsPage(page, queryWrapper);

    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());
}

运行结果:输出特定查询列

七、删除

1、根据id删除记录

@Test
public void testDeleteById(){

    int result = userMapper.deleteById(1427270191766597638L);
    System.out.println(result);
}

2、批量删除

@Test
public void testDeleteBatchIds() {

    int result = userMapper.deleteBatchIds(Arrays.asList(8, 9, 10));
    System.out.println(result);
}

3、简单条件删除

@Test
public void testDeleteByMap() {

    HashMap<String, Object> map = new HashMap<>();
    map.put("name", "Annie");
    map.put("age", 28);

    int result = userMapper.deleteByMap(map);
    System.out.println(result);
}


八、逻辑删除

1、物理删除和逻辑删除

  • 物理删除:真实删除,将对应数据从数据库中删除,之后查询不到此条被删除数据
  • 逻辑删除:假删除,将对应数据中代表是否被删除字段状态修改为“被删除状态”,之后在数据库中仍旧能看到此条数据记录

逻辑删除的使用场景:

  • 可以进行数据恢复
  • 有关联数据,不便删除

2、逻辑删除实现流程

(1)数据库修改

添加 deleted字段

ALTER TABLE `user` ADD COLUMN `deleted` boolean DEFAULT false

(2)实体类修改

添加deleted 字段,并加上 @TableLogic 注解

@TableLogic
private Integer deleted;

(3)配置(可选)

application.properties 加入以下配置,此为默认值,如果你的默认值和mp默认的一样,该配置可无

mybatis-plus.global-config.db-config.logic-delete-value=1
mybatis-plus.global-config.db-config.logic-not-delete-value=0

application.yml

mybatis-plus:
  global-config:
    db-config:
      logic-delete-field: flag  # 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2)
      logic-delete-value: 1 # 逻辑已删除值(默认为 1)
      logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)

(4)测试

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

    int result = userMapper.deleteById(1L);
    System.out.println(result);
}

(5)测试逻辑删除后的查询

MyBatis Plus中查询操作也会自动添加逻辑删除字段的判断

@Test
public void testLogicDeleteSelect() {
    
    List<User> users = userMapper.selectList(null);
    users.forEach(System.out::println);
}

sql:SELECT id,name,age,email,create_time,update_time,deleted FROM user WHERE deleted=0

这里查询deleted为0的也就是未删除的数据 为1的数据为逻辑删除的数据不查询

Wapper

Wrapper即条件构造器,用来写一些复杂的sql语句。

1.eq 等于 = 、 ne不等于<> 、gt大于 > 、 ge大于等于 >= 、 lt 小于 < 、le小于等于 <=

eq 等于 =

  • 例: eq(“name”, “老王”)—>name = ‘老王’

ne 不等于 <>

  • 例: ne(“name”, “老王”)—>name <> ‘老王’

gt大于等于 >=

  • 例: gt(“age”, 18)—>age > 18

ge 大于等于 >=

  • 例: ge(“age”, 18)—>age >= 18

lt 小于 <

  • 例: lt(“age”, 18)—>age < 18

le小于等于 <=

  • 例: le(“age”, 18)—>age <= 18
@Test
public void testSelectOne() {

    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    queryWrapper.eq("name", "Tom");

    User user = userMapper.selectOne(queryWrapper);//只能返回一条记录,多余一条则抛出异常
    System.out.println(user);
}

2. between 、 notBetween、 like、 notLike、 likeLeft 、likeRight

BETWEEN 值1 AND 值2

  • 例: between(“age”, 18, 30)—>age between 18 and 30

NOT BETWEEN 值1 AND 值2

  • 例: notBetween(“age”, 18, 30)—>age not between 18 and 30

LIKE ‘%值%’

  • 例: like(“name”, “王”)—>name like ‘%王%’

NOT LIKE ‘%值%’

  • 例: notLike(“name”, “王”)—>name not like ‘%王%’

likeLeft LIKE ‘%值’

  • 例: likeLeft(“name”, “王”)—>name like ‘%王’

likeRight LIKE ‘值%’

  • 例: likeRight(“name”, “王”)—>name like ‘王%’

3.isNull isNotNull in notIn inSql notInSql groupBy orderByAsc

字段 IS NULL

  • 例: isNull(“name”)—>name is null

字段 IS NOT NULL

  • 例: isNotNull(“name”)—>name is not null

字段 IN (value.get(0), value.get(1), …)

例: in(“age”,{1,2,3})—>age in (1,2,3)

字段 NOT IN (value.get(0), value.get(1), …)

  • 例: notIn(“age”,{1,2,3})—>age not in (1,2,3)

字段 NOT IN (v0, v1, …)

  • 例: notIn(“age”, 1, 2, 3)—>age not in (1,2,3)

字段 IN ( sql语句 )

例: inSql(“age”, “1,2,3,4,5,6”)—>age in (1,2,3,4,5,6) 例: inSql(“id”,

“select id from table where id < 3”)—>id in (select id from table

where id < 3)

字段 NOT IN ( sql语句 )

例: notInSql(“age”, “1,2,3,4,5,6”)—>age not in (1,2,3,4,5,6) 例:

notInSql(“id”, “select id from table where id < 3”)—>id not in

(select id from table where id < 3)

分组:GROUP BY 字段, …

  • 例: groupBy(“id”, “name”)—>group by id,name

排序:ORDER BY 字段, … ASC

  • 例: orderByAsc(“id”, “name”)—>order by id ASC,name ASC

排序:ORDER BY 字段, … DESC

  • 例: orderByDesc(“id”, “name”)—>order by id DESC,name DESC

排序:ORDER BY 字段, …

  • 例: orderBy(true, true, “id”, “name”)—>order by id ASC,name ASC

HAVING ( sql语句 )

  • 例: having(“sum(age) > 10”)—>having sum(age) > 10 例:
    having(“sum(age) > {0}”, 11)—>having sum(age) > 11

拼接 OR

注意事项:

主动调用or表示紧接着下一个方法不是用and连接!(不调用or则默认为使用and连接)

  • 例: eq(“id”,1).or().eq(“name”,“老王”)—>id = 1 or name = ‘老王’

AND 嵌套

  • 例: and(i -> i.eq(“name”, “李白”).ne(“status”, “活着”))—>and (name =
    ‘李白’ and status <> ‘活着’)

链式调用 lambda 式

// 区分:
// 链式调用 普通
UpdateChainWrapper<T> update();
// 链式调用 lambda 式。注意:不支持 Kotlin 
LambdaUpdateChainWrapper<T> lambdaUpdate();

// 等价示例:
query().eq("id", value).one();
lambdaQuery().eq(Entity::getId, value).one();

// 等价示例:
update().eq("id", value).remove();
lambdaUpdate().eq(Entity::getId, value).remove();


总结

之前看的尚硅谷的在线教育项目学习视频 记录顺便复习

相关文章
|
2天前
|
XML 前端开发 Java
Mybatis-Plus乐观锁配置
Mybatis-Plus乐观锁配置
9 1
|
3天前
|
数据库
MyBatis-Plus如何自动填充数据表的创建时间和更新时间
MyBatis-Plus如何自动填充数据表的创建时间和更新时间
10 1
|
3天前
MyBatis-Plus分页插件基于3.3.2
MyBatis-Plus分页插件基于3.3.2
8 0
MyBatis-Plus分页插件基于3.3.2
|
3天前
|
存储 Java 数据库连接
SSMP整合案例第三步 业务层service开发及基于Mybatis的接口功能拓展
SSMP整合案例第三步 业务层service开发及基于Mybatis的接口功能拓展
6 0
|
1月前
|
SQL
【MybatisPlus】条件构造器、自定义SQL、Service接口
【MybatisPlus】条件构造器、自定义SQL、Service接口
40 0
【MybatisPlus】条件构造器、自定义SQL、Service接口
|
1月前
|
算法 BI 数据库
MyBatisPlus查询条件设置、映射匹配兼容性、id生成策略、多数据操作
MyBatisPlus查询条件设置、映射匹配兼容性、id生成策略、多数据操作
45 3
|
1月前
|
SQL 数据库
MyBatisPlus之逻辑删除、MyBatisPlus解决并发问题的乐观锁机制
MyBatisPlus之逻辑删除、MyBatisPlus解决并发问题的乐观锁机制
32 2
|
1月前
|
SQL Java 关系型数据库
基于SpringBoot使用MyBatisPlus,MyBatisPlus标准数据层开发(CRUD)、MyBatisPlus分页功能的使用
基于SpringBoot使用MyBatisPlus,MyBatisPlus标准数据层开发(CRUD)、MyBatisPlus分页功能的使用
37 2
|
1月前
|
算法 Java 数据库连接
Spring+MySQL+数据结构+集合,Alibaba珍藏版mybatis手写文档
Spring+MySQL+数据结构+集合,Alibaba珍藏版mybatis手写文档
|
1月前
|
Java 数据库连接 Spring
Spring 整合mybatis
Spring 整合mybatis
29 2