MyBatis-Plus for Beginners 2

Wed, Jul 15, 2020 5-minute read

常用注解

MyBatis-plus 启动步骤

1.1 创建 User 表

CREATE TABLE USER
(
id BIGINT(20)NOT NULL COMMENT '主键ID',
NAME VARCHAR(30)NULL DEFAULT NULL COMMENT '姓名',
age INT(11)NULL DEFAULT NULL COMMENT '年龄',
email VARCHAR(50)NULL DEFAULT NULL COMMENT '邮箱',
PRIMARY KEY (id)
);

使用 mysqlplus workbench 时候,点击 table-user

SELECT * FROM mybatis_plus.user;
INSERT INTO user (id, name, age, email) VALUES
(1, 'Jone', 18, 'test1@baomidou.com'),
(2, 'Jack', 20, 'test2@baomidou.com'),
(3, 'Tom', 28, 'test3@baomidou.com'),
(4, 'Sandy', 21, 'test4@baomidou.com'),
(5, 'Billie', 24, 'test5@baomidou.com');`

1.2 创建 SpringBoot 项目

new Project -> Spring Initializer

引入依赖

mybatis-plus-boot-starter

lombok

mysql-connector-java

application yml

spring:
  datasource:
    type: com.zaxxer.hikari.HikariDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql//192.168.56.10:3306/mybatis_plus?serverTimezone=GMT%2B8&characterEncoding=utf-8&useSSL=false

1.3 创建实体

User

1.4 创建 mapper 接口并扫描

  • BaseMapper 是 mabatis plus 提供的

public interface UserMapper extends BaseMapper {}

  • 扫描 mapper 接口所在的包

@MapperScan(“com.demo.mybatisplus.mapper”)

public class MybatisplusApplication

1.5 创建测试

image

报红

userMapper 里面都不用写方法,这里测试就有 mybatis_plus 提供的各种方法

image

selectList(Wrapper queryWrapper) -> Wrapper queryWrapper 就是有条件用 wrapper,没有就用 null

不影响运行

image

但是要解决的话,有两个办法

方法一

IDEA 在 userMapper 处报错,因为找不到注入的对象,因为类是动态创建的,但是程序可以正确的执行 为了避免报错,可以在 dao 层 的接口上添加 @Repository

通过以上几个简单的步骤,我们就实现了 User 表的 CRUD 功能,甚至连 XML 文件都不用编写


方法二

test@Autowired这边改为 @Resource

package com.atguigu.mybatisplus;

@SpringBootTest
class MybatisPlusApplicationTests {

	//@Autowired //默认按类型装配。是spring的注解
	@Resource //默认按名称装配,找不到与名称匹配的bean,则按照类型装配。是J2EE的注解
	private UserMapper userMapper;

	@Test
	void testSelectList() {
		//selectList()方法的参数:封装了查询条件
		//null:无任何查询条件
		List<User> users = userMapper.selectList(null);
		users.forEach(System.out::println); // soutc
	}
}

1.6 查看 sql 输出日志

application.yml:

mybatis-plus 和 spring 对齐

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

基本 CRUD

BaseMapper

基本 CRUD 在内置的 BaseMapper 中都已得到了实现

  • 插入操作

@Test
public void testAdd() {
User user = new User();
user.setName("lucy");
user.setAge(20);
user.setEmail("1243@qq.com");
int insert = userMapper.insert(user); //影响的行数
System.out.println(insert); // 
}

最终执行的结果,所获取的 id 为 1507208836087844865

这是因为 MyBatis-Plus 在实现插入数据时,会默认基于雪花算法的策略生成 id

  • 删除

@Test
public void testSelect(){

    //按id删除
   int i = userMapper.deleteById(1507208836087844865L); // 1507208836087844865已经超过int范围,会报错,所以后面加 L
    System.out.println(i);

    //按map删除
    Map<String, Object> map = new HashMap<>();
    map.put("name", "张三");
    map.put("age", "20");
    int i = userMapper.deleteByMap(map);
    System.out.println(i);

    //批量删除
    List<Long> list = Arrays.asList(1369843034785845250L, 1369860653496717314L, 1369890832562638849L);
    userMapper.deleteBatchIds(list);
    System.out.println(list);
}
  • 修改

@Test
public void testUpdate(){

    User user = new User();
    user.setId(1L);
    user.setAge(28);

    //注意:update时生成的sql自动是动态sql
    int result = userMapper.updateById(user);
    System.out.println("影响的行数:" + result);
}
  • 查询

@Test
public void testDelete(){
    // 通过id查询
    // 通过多个id查询
    List<Long> list = Arrays.asList(1L, 2L, 3L);
    userMapper.selectBatchIds(list);
    // 通过条件查询
    // 通过所有数据
    List<User> user = userMapper.selectList(null);
}
  • 自定义功能

image

默认的地址在 resources 下面的 mapper,所以这个配置可以省略,只需要创建 mapper 文件包就可以

做法和 mybatis 一样

通用 Service

通用 Service CRUD 封装 IService 接口,进一步封装 CRUD 采用 get 查询单行 remove 删 除 list 查询集合 page 分页 前缀命名方式区分 Mapper 层避免混淆,

泛型 T 为任意实体对象

建议如果存在自定义通用 Service 方法的可能,请创建自己的 IBaseService 继承 Mybatis-Plus 提供的基类

Ctrl + N, 查看 ServiceImpl 类

创建 Service 接口

创建 service 包,创建 UserService,继承 IService

package com.atguigu.mybatisplus.service;

public interface UserService extends IService<User> {
    
}

创建 Service 实现类

创建 impl 包,创建 UserServiceImpl,继承 ServiceImpl,实现 UserService, 添加 @Service

package com.atguigu.mybatisplus.service.impl;

@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {

}

创建测试类

创建 ServiceTests

package com.atguigu.mybatisplus;

@SpringBootTest
public class ServiceTests {

    @Resource
    private UserService userService;

}

测试记录数

@Test
public void testCount(){

    int count = userService.count();
    System.out.println("总记录数:" + count);
}

测试批量插入

@Test
public void testSaveBatch(){

    // SQL长度有限制,海量数据插入单条SQL无法实行,
    // 因此MP将批量插入放在了通用Service中实现,而不是通用Mapper
    ArrayList<User> users = new ArrayList<>();
    for (int i = 0; i < 5; i++) {
        User user = new User();
        user.setName("Helen" + i);
        user.setAge(10 + i);
        users.add(user);
    }
    userService.saveBatch(users);
}

MyBatis-plus Annotation

@TableName

实体类的名字是 User,数据库表名是 t_user

@TableName(value = "t_user")
public class User {

如果数据库下面得表明都有 t, 比如 t_dept, t_student

可以设置全局

mybatis-plus:
    global-config:
        db-config:
            # 设置实体类所对应得表得统一前缀
            table-prefix: t_

@TableId value

经过以上的测试,MyBatis-Plus 在实现 CRUD 时,会默认将 id 作为主键列,并在插入数据时,默认

基于雪花算法的策略生成 id

实体类和表中表示主键的不是 id

  • 实体类中的属性 id 改为 uid,将表中的字段 id 也改为 uid,程序抛出异常,Field ‘uid’ doesn’t have a default value,说明 MyBatis-Plus 没有将 uid 作为主键赋值
public class User {
    @TableId
    private Long uid;
}
  • 实体类中的属性 id 不变,将表中的字段 id 也改为 uid,程序抛出异常,unknown cloumn ‘id’ in ‘field’ list
public class User {
    @TableId(value = "uid")
    private Long uid;
}

@TableId IdType

  • IdType.ASSIGN_ID(默认), 基于雪花算法的策略生成数据 id,与数据库 id 是否设置自增无

  • IdType.AUTO, 使用数据库的自增策略,注意,该类型请确保数据库设置了 id 自增,否则无效

public class User {
    @TableId(value = "uid", type = IdType.AUTO)
    private Long uid;
}

可以设置全局

mybatis-plus:
    global-config:
        db-config:
            # 设置实体类所对应得表得统一前缀
            table-prefix: t_
            # 配置主键策略
            id-type: auto

我的数据库表格是之前设置的,用这个实际产生的效果是新的 id 为雪花算法,然后第二第三个是雪花算法的自增。 网络用了 数据库的截断,重新插入数据,然后新的数据才是 1- 6 这样的数据排列

雪花算法

业务规模的不断扩大,需要选择合适的方案去应对数据规模的增长,以应对逐渐增长的访问压力和数据量。

单表数据拆分有两种方式:垂直分表水平分表.

水平分表用主键自增的方法

+ 1.0 主键自增

以最常见的用户 ID 为例,可以按照 1000000 的范围大小进行分段,1 ~ 999999 放到表 1 中,1000000 ~ 1999999 放到表 2 中,以此类推。

复杂点:分段大小的选取。分段太小会导致切分后子表数量过多,增加维护复杂度;分段太大可能会导致单表依然存在性能问题,一般建议分段大小在 100 万至 2000 万之间,具体需要根据业务选取合适的分段大小。

优点:可以随着数据的增加平滑地扩充新的表。例如,现在的用户是 100 万,如果增加到 1000 万,只需要增加新的表就可以了,原有的数据不需要动。

缺点:分布不均匀。假如按照 1000 万来进行分表,有可能某个分段实际存储的数据量只有 1 条,而另外一个分段实际存储的数据量有 1000 万条。

+ 2. 0 Hash

同样以用户 ID 为例,假如我们一开始就规划了 10 个数据库表,可以简单地用 user_id % 10 的值来表示数据所属的数据库表编号,ID 为 985 的用户放到编号为 5 的子表中,ID 为 10086 的用户放到编号为 6 的子表中。

复杂点:初始表数量的确定。表数量太多维护比较麻烦,表数量太少又可能导致单表性能存在问题。

优点:表分布比较均匀。

缺点:扩充新的表很麻烦,所有数据都要重分布。

+ 3. 0 雪花算法

能够保证不同表的主键的不重复性,以及相同表的主键的有序性。

@TableField

  • 驼峰命名

MP 会自动将数据库中的下划线命名风格转化为实体类中的驼峰命名风格

  • 实体类中的属性和表中的字段不满足驼峰命名

例如实体类属性 name,表中字段 username

此时需要在实体类属性上使用@TableField(“username”)设置属性所对应的字段名

    @TableField("username")
    private String name;

@TableLogic

1. 逻辑删除

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

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

使用场景:可以进行数据恢复

2.0 实现逻辑删除

  • step1:数据库中创建逻辑删除状态列

deleted (默认 0, 要写)

  • step2:实体类中添加逻辑删除属性
@TableLogic
private Integer deleted;

公司规范数据库 is_deleted, 业务层 Boolean 后写 deleted

需求描述

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

1. 只在数据库操作,不在业务层操作

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

在 mybatis-plus 表 create_time, update_time 增加 CURRENT_TIMESTAMP, 只勾选 update_time 这里的

根据当前的时间戳更新

CURRENT_TIMESTAMP

image

2. 不在数据库操作,在业务层操作,因为数据库类型很多的时候

step 2.1:添加 fill 属性

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

  • @TableField(fill = FieldFill.INSERT)

  • @TableField(fill = FieldFill.INSERT_UPDATE)

@Data
public class User {
    @TableId(type = IdType.ASSIGN_ID)
    private Long id;
    private String name;

    @TableField(fill = FieldFill.INSERT)
    private Integer age;
    private String email;

    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;; //create_time
    
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime; 

}

3.3 实现元对象处理器接口-> 创建 handler 包,创建 MyMetaObjectHandler 类

@Slf4j
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {

    //mp执行添加操作,这个方法执行
    @Override
    public void insertFill(MetaObject metaObject) {
        log.info("start insert fill ....");
        this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
        this.strictInsertFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());

        // 可以看有没有author属性,有true
        boolean hasAuthor = metaObject.hasSetter("author");
        if (hasAuthor) {
            log.info("insert author ....");
            this.strictInsertFill(metaObject, "author", String.class, "stone");
        }  
    }

    // 给用户填充年龄,第一步是填上注解,如果业务层没有赋值,就用自动填充,如果有,就不去执行自动填充(其实有,自动填充也是不起作用的)
    Object age = this.getFieldValByName("age", metaObject);
    if (age == null) {
        log.info("insert age atribute")
        this.strictInsertFill(metaObject, "age", Integer.class, 3);
    }
    

    //mp执行修改操作,这个方法执行
    @Override
    public void updateFill(MetaObject metaObject) {
        log.info("start update fill ....");
        this.strictInsertFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
    }
}
  • 注意:不要忘记添加 @Component 注解 是告诉 spring 交给它来处理_

  • 要做一个自动填充,比如 author 属性,先判断有没有,没有就不加

  • age 属性也要在业务层做一个自动填充,结论要先判断当前对象自动属性是否已经进行了赋值

  • 所以自动填充不是针对个性化的属性,个性化年龄等直接在业务层解决了,set

业务层现在不需要加 setCreatime 方法:

image
  • 建议每个数据库表必须要有 create_time 和 update_time 字段,我们可以使用自动填充功能维护这两个字段

mybatis1

mybatis3