MyBatis Plus

时间:2023-01-19 22:53:40

概述MyBatis-Plus

MyBatis-Plus简称 MP是一个 MyBatis的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开
发、提高效率而生
官方文档:https://baomidou.com/

Hello MP

添加依赖

<!-- 如果没有整合SpringBoot,则需要引入这个依赖,通过注解进行开发,并手工添加所需要的配置
-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus</artifactId>
<version>3.5.3.1</version>
</dependency>

使用MP可以有对应的映射文件,也可以没有映射文件

  • 如果需要对sql语句进行优化,则可以添加映射元文件;如果没有需要sql语句优化,则省略
    mapper.xml文件
    • 在 MyBatis 的基础上只做增强不做改变。过去MyBatis的用法在MP中仍旧有效
  • 具体执行的sql语句可以由MP生成
    定义实体类
  • @TableName添加在实体类上,用于标识实体类所对应的表名注解,标识实体类对应的表
  • @TableId是主键注解,用于在属性上标识对应的注解
    • value用于指定对应的主键字段名,如果不指定,则和属性名称一致
    • type是IdType枚举类型,用于指定主键生成策略
      • AUTO数据库ID自增
      • NONE意思是无状态,该类型为未设置主键类型。注解里等于跟随全局,全局里约等于
        INPUT
      • INPUT是在执行insert操作之前自行 set 主键值
      • ASSIGN_ID采用雪花算法生成主键值,应该是string类型
      • ASSIGN_UUID采用UUID生成字符串值充当主键
    • 不建议使用的三种算法:ID_WORKER分布式全局唯一 ID 长整型类型、UUID是32位UUID字
      符串、ID_WORKER_STR是分布式全局唯一 ID 字符串类型
  • @TableField属性上的针对字段的注解,用于非主键类型的属性
    • value用于定义当前属性对应的数据库字段名
    • exist标识该属性是否为数据库表字段,因为如果不加配置则默认属性都是有同名的对应字段
    • jdbcType用于声明对应的JDBC 类型,该默认值不代表会按照该值生效
    • numericScale用于指定小数点后保留的位数
@Data
@TableName("tb_users")
public class User implements Serializable {
@TableId(type = IdType.AUTO)
private Long id;
private String username;
private String password;
@TableField(exist = false)
private String repassword;
private String email;
}

添加mapper接口,注意使用MP时mapper.xml可有可无

  • MP提供了一个父接口BaseMapper,其中包含了常见的CRUD的方法
public interface UserMapper extends BaseMapper<User> {
}

使用自动扫描进行mapper接口的注册,在主类上添加自动扫描注解即可

@MapperScan("com.yan.dao")
@SpringBootApplication
public class DemoApplication

MP针对业务层提供了IService接口,和对应的实现类ServiceImpl,在具体开发中可以通过继承IService
接口来定义业务接口

public interface IUserServ extends IService<User> {
}

定义业务实现类

@Transactional(readOnly = true,propagation = Propagation.SUPPORTS)
@Service
public class UserServImpl extends ServiceImpl<UserMapper, User> implements
IUserServ {
}

在控制台上打印输入所执行的sql语句

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

IService业务接口

一般使用步骤:
定义特定接口,这个接口用于定义特殊的方法,一般通用方法从MP提供的IService接口种继承

public interface UserService extends IService<User> {
}

定义具体的业务实现类,通用方法的定义是MP提供的父类ServiceImpl,泛型1是当前业务类需要使用的
Mapper接口,泛型2是对应的实体类

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

接口定义

public interface IService<T> {
//插入一条记录(选择字段,策略插入),非空属性参与生成sql语句,否则不出现。default才
能生效
default boolean save(T entity) {
return SqlHelper.retBool(getBaseMapper().insert(entity));
}
//插入批量,一次性提供1000条sql语句,使用注解设置所有的异常都执行回滚
@Transactional(rollbackFor = Exception.class)
default boolean saveBatch(Collection<T> entityList) {
return saveBatch(entityList, DEFAULT_BATCH_SIZE);
}
//根据ID列执行删除 DELETE FROM tb_users WHERE id=?
default boolean removeById(Serializable id) {
return SqlHelper.retBool(getBaseMapper().deleteById(id));
}
//根据实体的ID删除,其他的属性无效 DELETE FROM tb_users WHERE id=?
default boolean removeById(T entity) {
return SqlHelper.retBool(getBaseMapper().deleteById(entity));
}
//根据columnMap条件执行删除记录,其中的key为列名称,value标识条件为列名称=value
值,所有的key/value对是and连接 DELETE FROM tb_users WHERE role_id = ? AND sex
= ? AND id = ?
default boolean removeByMap(Map<String, Object> columnMap) {
Assert.notEmpty(columnMap, "error: columnMap must not be empty");
return SqlHelper.retBool(getBaseMapper().deleteByMap(columnMap));
}
//根据实体对象的查询条件执行删除记录,删除条件封装在QueryWrapper对象中
default boolean remove(Wrapper<T> queryWrapper) {
return SqlHelper.retBool(getBaseMapper().delete(queryWrapper));
}
基本用法:
QueryWrapper<User> wrapper=new QueryWrapper<>();
wrapper.eq("sex",true).between("id",10,30).or().like("username","zhang");
boolean removed = userService.remove(wrapper);
System.out.println(removed);
所执行的sql语句:
==> Preparing: DELETE FROM tb_users WHERE (sex = ? AND id BETWEEN ? AND ?
OR username LIKE ?)
==> Parameters: true(Boolean), 10(Integer), 30(Integer), %zhang%(String)
<== Updates: 0
//批量删除,参数是要删除数据的id所构成的集合
default boolean removeByIds(Collection<?> list) {
if (CollectionUtils.isEmpty(list)) {
return false;
}
return SqlHelper.retBool(getBaseMapper().deleteBatchIds(list));
}
//根据ID选择修改实体类中的所有非空属性,如果属性为空则不参与修改,值仍旧为原来的数据
default boolean updateById(T entity) {
return SqlHelper.retBool(getBaseMapper().updateById(entity));
}
基础用法:
User tmp=new User();
tmp.setId(2L);
tmp.setUsername("王胡子");
boolean res = userService.updateById(tmp);
所执行的sql语句:
==> Preparing: UPDATE tb_users SET username=? WHERE id=?
==> Parameters: 王胡子(String), 2(Long)
<== Updates: 1
//具体的修改是通过UpdateWrapper对象进行封装,更新记录,需要设置sqlset
*
* @param updateWrapper 实体对象封装操作类 {@link
com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper}
*/
default boolean update(Wrapper<T> updateWrapper) {
return update(null, updateWrapper);
}
基础用法:
User tmp=new User();
tmp.setId(2L);
tmp.setUsername("王胡子");
boolean res = userService.updateById(tmp);
所执行的sql语句:
==> Preparing: UPDATE tb_users SET username=? WHERE id=?
==> Parameters: 王胡子(String), 2(Long)
<== Updates: 1
基础用法:
UpdateWrapper<User> wrapper=new UpdateWrapper<>();
//set用于定义需要修改的列和对应的新值
wrapper.set("username","贺老总");
wrapper.set("password","666666");
//和QueryWrapper一样定义对应的修改条件
wrapper.between("id",10,20);
boolean res = userService.update(wrapper);
所执行的sql语句:
==> Preparing: UPDATE tb_users SET username=?,password=? WHERE (id BETWEEN
? AND ?)
==> Parameters: 贺老总(String), 666666(String), 10(Integer), 20(Integer)
<== Updates: 0
//一组实体对象的集合根据每个对象的ID批量更新
@Transactional(rollbackFor = Exception.class)
default boolean updateBatchById(Collection<T> entityList) {
return updateBatchById(entityList, DEFAULT_BATCH_SIZE);
}
//根据参数entity的id属性值判断执行save或者update操作。TableId 注解存在更新记录,
否插入一条记录
boolean saveOrUpdate(T entity);
调用语句1User tmp=new User();
tmp.setUsername("爱新觉罗");
tmp.setPassword("666666");
tmp.setRoleId(2L);
boolean res = userService.saveOrUpdate(tmp);
多执行的sql语句:
==> Preparing: INSERT INTO tb_users ( username, password, role_id ) VALUES
( ?, ?, ? )
==> Parameters: 爱新觉罗(String), 666666(String), 2(Long)
<== Updates: 1
调用语句2:这里不同于语句1的是id有值
User tmp=new User();
tmp.setId(3L);
tmp.setUsername("爱新觉罗");
tmp.setPassword("666666");
tmp.setRoleId(2L);
boolean res = userService.saveOrUpdate(tmp);
所执行的sql语句:
首先按照id执行查询,如果有返回数据则执行修改操作
==> Preparing: SELECT id,username,password,birth,sex,role_id FROM tb_users
WHERE id=?
==> Parameters: 3(Long)
<== Columns: id, username, password, birth, sex, role_id
<== Row: 3, yan222, 888888, 2021-08-25, 1, 4
<== Total: 1
修改操作
==> Preparing: UPDATE tb_users SET username=?, password=?, role_id=? WHERE
id=?
==> Parameters: 爱新觉罗(String), 666666(String), 2(Long), 3(Long)
<== Updates: 1
如果查询不到数据,则执行插入操作
==> Preparing: INSERT INTO tb_users ( id, username, password, role_id )
VALUES ( ?, ?, ?, ? )
==> Parameters: 300(Long), 爱新觉罗(String), 666666(String), 2(Long)
<== Updates: 1
//根据 ID 查询
default T getById(Serializable id) {
return getBaseMapper().selectById(id);
}
//按照id的集合进行查询(根据ID 批量查询),类似于id in (集合)
default List<T> listByIds(Collection<? extends Serializable> idList) {
return getBaseMapper().selectBatchIds(idList);
//查询(根据 columnMap 条件),Map中key为列名称,value是对应的值,多个key/value
之间使用and连接
default List<T> listByMap(Map<String, Object> columnMap) {
return getBaseMapper().selectByMap(columnMap);
}
//根据Wrapper查询一条记录。结果集,如果是多个会抛出异常,随机取一条加上限制条件
wrapper.last("LIMIT 1")
default T getOne(Wrapper<T> queryWrapper) {
return getOne(queryWrapper, true);
}
//查询总记录数,类似于select count(*) from... where 1=1
default long count() {
return count(Wrappers.emptyWrapper());
}
//根据Wrapper查询条件,查询总记录数 类似于select count(*) from ... where
...
default long count(Wrapper<T> queryWrapper) {
return
SqlHelper.retCount(getBaseMapper().selectCount(queryWrapper));
}
//按照查询条件queryWrapper执行查询,获取多行数据,每行数据转换为一个值bean,返回查
询列表
default List<T> list(Wrapper<T> queryWrapper) {
return getBaseMapper().selectList(queryWrapper);
}
//查询所有
default List<T> list() {
return list(Wrappers.emptyWrapper());
}
//按照查询条件queryWrapper执行物理的分页查询
default <E extends IPage<T>> E page(E page, Wrapper<T> queryWrapper) {
return getBaseMapper().selectPage(page, queryWrapper);
}
调用语句
//查询条件
QueryWrapper<User> wrapper=new QueryWrapper<>();
wrapper.like("username","zhangsan");//模糊查询
wrapper.gt("id",10);//id大于10,多个条件之间没有使用or()则为and连接
wrapper.between("birth",new java.sql.Date(1900-1900,1-1,1),new
java.sql.Date(2050-1900,2-1,15));
wrapper.or().in("sex",true,false);
//构建分页条件,参数1是页码值,参数2是每页行数
IPage<User> page= Page.of(50,2);
page=userService.page(page,wrapper);
System.out.println("查询结果集List:"+page.getRecords());
System.out.println("总页数:"+page.getPages());
System.out.println("当前页码值:"+page.getCurrent());
System.out.println("每页行数:"+page.getSize());
System.out.println("总行数:"+page.getTotal());
对应的sql语句
==> Preparing: SELECT COUNT(*) AS total FROM tb_users WHERE (username LIKE
? AND id > ? AND birth BETWEEN ? AND ? OR sex IN (?, ?))
==> Parameters: %zhangsan%(String), 10(Integer), 1900-01-01(Date), 2050-02-
15(Date), true(Boolean), false(Boolean)
<== Columns: total
<== Row: 3
<== Total: 1
==> Preparing: SELECT id,username,password,birth,sex,role_id FROM tb_users
WHERE (username LIKE ? AND id > ? AND birth BETWEEN ? AND ? OR sex IN
(?,?)) LIMIT ?
==> Parameters: %zhangsan%(String), 10(Integer), 1900-01-01(Date), 2050-02-
15(Date), true(Boolean), false(Boolean), 2(Long)
<== Columns: id, username, password, birth, sex, role_id
<== Row: 1, yanjun, 123456, 1989-02-03, 1, 1
<== Row: 2, 王胡子, 666666, 2000-12-13, 1, 2
<== Total: 2
//无条件翻页查询
default <E extends IPage<T>> E page(E page) {
return page(page, Wrappers.emptyWrapper());
}
调用语句:
IPage<User> page= Page.of(1,2); 构建分页条件,参数1是页码值,参数2是每页行数。
如果参数1小于1则默认值为1;如果页码值大于最大页码值则返回空集合,不会进行合理化处理
page=userService.page(page);
System.out.println("查询结果集List:"+page.getRecords());
System.out.println("总页数:"+page.getPages());
System.out.println("当前页码值:"+page.getCurrent());
System.out.println("每页行数:"+page.getSize());
System.out.println("总行数:"+page.getTotal());
所执行的sql语句:
==> Preparing: SELECT COUNT(*) AS total FROM tb_users 获取总行数
==> Parameters:
<== Columns: total
<== Row: 3
<== Total: 1
==> Preparing: SELECT id,username,password,birth,sex,role_id FROM tb_users
LIMIT ?查询第一页的数据limit 2或者limit 0,2
==> Parameters: 2(Long)
<== Columns: id, username, password, birth, sex, role_id
<== Row: 1, yanjun, 123456, 1989-02-03, 1, 1
<== Row: 2, 王胡子, 666666, 2000-12-13, 1, 2
<== Total: 2
分页查询需要一个配置
@MapperScan("com.yan.mapper")
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
//添加分页插件
interceptor.addInnerInterceptor(new
PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
//获取对应entity的BaseMapper。在具体实现类中如果需要使用声明的Mapper对象,可以通
过这个方法直接获取
BaseMapper<T> getBaseMapper();

BaseMapper接口

Mapper 继承该接口后,无需编写 mapper.xml 文件,即可获得CRUD功能,这个 Mapper 支持 id 泛型

public interface BaseMapper<T> extends Mapper<T>

其中的泛型T用于指定当前mapper接口相关的实体类类型 public interface UserMapper extends
BaseMapper
相关的方法

public interface BaseMapper<T> extends Mapper<T> {
//插入一条记录,返回的整型数用于表示受影响行数。采用的是动态生成sql语句执行插入[非空
属性],如果id没有设置,则插入执行后会有一个值【tableid注解中的主键生成策略】,并返回
int insert(T entity);
使用样例代码
User user=new User();
user.setUsername("yanjun1");
user.setPassword("123456");
user.setRoleId(2L);
int inserted = userMapper.insert(user);
System.out.println(inserted);
===========================================================================
======
//根据ID删除,返回int用于表示受影响行数,可以用于进行删除是否成功的判断
int deleteById(Serializable id);
//根据实体ID删除。注意其它非id属性即使非空也不生效
int deleteById(T entity);
//根据columnMap条件,删除记录。删除操作的where部分使用map进行定义,map中key为列
名称,value为值,条件为key=value,所有多个key/value之间的关系为and
int deleteByMap(@Param(Constants.COLUMN_MAP) Map<String, Object>
columnMap);
使用样例代码
Map<String,Object> map=new HashMap<>();
map.put("id",123);
map.put("username","zhangsan");
int deleted = userMapper.deleteByMap(map);
System.out.println(deleted);
所执行的sql语句
==> Preparing: DELETE FROM tb_users WHERE id = ? AND username = ?
==> Parameters: 123(Integer), zhangsan(String)
<== Updates: 0
//根据queryWrapper定义复杂条件执行删除记录
int delete(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
使用样例代码
QueryWrapper<User> wrapper=new QueryWrapper<>();
wrapper.eq("id",1).or().like("username","zhang").between("id",100,200);
int deleted = userMapper.delete(wrapper);
System.out.println(deleted);
所执行的SQL语句
==> Preparing: DELETE FROM tb_users WHERE (id = ? OR username LIKE ? AND
id BETWEEN ? AND ?)
==> Parameters: 1(Integer), %zhang%(String), 100(Integer), 200(Integer)
<== Updates: 1
//批量删除,参数就是id的集合
int deleteBatchIds(@Param(Constants.COLL) Collection<?> idList);
使用样例代码
Long[] arr=new Long[]{11L,22L,33L};
Collection<Long> cols= Arrays.asList(arr);
int deleted = userMapper.deleteBatchIds(cols);
所执行的SQL语句
==> Preparing: DELETE FROM tb_users WHERE id IN ( ? , ? , ? )
==> Parameters: 11(Long), 22(Long), 33(Long)
<== Updates: 0
===========================================================================
========
/根据ID修改,按照id作为修改条件,非空属性作为修改内容进行update操作,返回受影响行数
int updateById(@Param(Constants.ENTITY) T entity);
使用样例代码
User user=new User();
user.setId(999L);
user.setUsername("zhangsanfeng");
int updated = userMapper.updateById(user);
所执行的SQL语句
如果没有设置id值则,=null永不成功
==> Preparing: UPDATE tb_users SET username=? WHERE id=?
==> Parameters: zhangsanfeng(String), null
<== Updates: 0
如果有id值则
==> Preparing: UPDATE tb_users SET username=? WHERE id=?
==> Parameters: zhangsanfeng(String), 999(Long)
<== Updates: 0
//根据updateWrapper作为复杂条件条件执行update操作,需要修改的内容在entity中定
义,非空属性就是修改内容
int update(@Param(Constants.ENTITY) T entity, @Param(Constants.WRAPPER)
Wrapper<T> updateWrapper);
使用样例代码
User user=new User();
user.setId(999L); //实体类对象中的id属性无效
user.setUsername("zhangsanfeng");
QueryWrapper<User> wrapper=new QueryWrapper<>();
wrapper.between("id",100,300).or().like("username","zhang");
int updated = userMapper.update(user,wrapper);
所执行的SQL语句
==> Preparing: UPDATE tb_users SET username=? WHERE (id BETWEEN ? AND ? OR
username LIKE ?)
==> Parameters: zhangsanfeng(String), 100(Integer), 300(Integer), %zhang%
(String)
<== Updates: 0
===========================================================================
========
//根据ID执行查询
T selectById(Serializable id);
//查询(根据ID 批量查询),类似于deleteBatchIds
List<T> selectBatchIds(@Param(Constants.COLL) Collection<? extends
Serializable> idList);
//根据columnMap作为条件执行查询,其中columnMap格式为key是表字段名,value是对应的
数据,所有key/value之间使用and连接
List<T> selectByMap(@Param(Constants.COLUMN_MAP) Map<String, Object>
columnMap);
//根据queryWrapper作为复杂条件查询一条记录,如果有多条数据会报异常,建议在查询条件
中添加一个limit 1
default T selectOne(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper)
{
List<T> list = this.selectList(queryWrapper);
if (list.size() == 1) {
return list.get(0);
} else if (list.size() > 1) {
throw new TooManyResultsException("Expected one result (or
null) to be returned by selectOne(), but found: " + list.size());
} else {
return null;
}
}
//根据QueryWrapper条件判断是否存在记录,实际上就是执行一个count(*)统计操作,如果
count(*)>0则表示存在,否则不存在
default boolean exists(Wrapper<T> queryWrapper) {
Long count = this.selectCount(queryWrapper);
return null != count && count > 0;
}
//根据QueryWrapper封装的复杂条件,查询总记录数
Long selectCount(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
//根据QueryWrapper负责查询条件查询满足条件的全部记录
List<T> selectList(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
//根据QueryWrapper封装的复杂条件查询全部记录,并支持分页查询功能,参数page就是分页
查询相关参数,例如页码值、每页行数等,参数queryWrapper就是查询条件,允许为null
<P extends IPage<T>> P selectPage(P page, @Param(Constants.WRAPPER)
Wrapper<T> queryWrapper);
样例代码
//每页2行数据,获取第1页数据
Page<User> pages=Page.of(1,2);
QueryWrapper<User> wrapper=new QueryWrapper<>();
wrapper.eq("sex",true).in("id",11,22,33,44);
Page<User> userPage = userMapper.selectPage(pages, wrapper);
System.out.println("返回的集合:"+userPage.getRecords());
System.out.println("当前页码值:"+userPage.getCurrent());
System.out.println("总行数:"+userPage.getTotal());
System.out.println("最大页码值:"+userPage.getPages());
System.out.println("每页行数:"+userPage.getSize());
对应的执行SQL语句
==> Preparing: SELECT COUNT(*) AS total FROM tb_users WHERE (sex = ? OR id
IN (?, ?, ?, ?))
==> Parameters: true(Boolean), 11(Integer), 22(Integer), 33(Integer),
44(Integer)
<== Columns: total
<== Row: 3
<== Total: 1
==> Preparing: SELECT id,username,password,birth,sex,role_id FROM tb_users
WHERE (sex = ? OR id IN (?,?,?,?)) LIMIT ?
==> Parameters: true(Boolean), 11(Integer), 22(Integer), 33(Integer),
44(Integer), 2(Long)
<== Columns: id, username, password, birth, sex, role_id
<== Row: 2, 王胡子, 666666, 2000-12-13, 1, 2
<== Row: 3, 爱新觉罗, 666666, 2021-08-25, 1, 2
<== Total: 2
注意:分页需要配置对应的分页插件,否则调用方法没有分页效果。具体的分页实现实际上是依赖于拦
截器PaginationInnerInterceptor执行分页操作的拦截处理
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
//添加分页插件
interceptor.addInnerInterceptor(new
PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
//根据QueryWrapper负责条件查询全部记录,只是返回结果为Map,一行数据对应一个Map对<P extends IPage<Map<String, Object>>> P selectMapsPage(P page,
@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
}

在控制器中可以通过集合或者数组接收客户端提交的一组数据

@Controller
public class RoleController {
@GetMapping("/test")
public String test(Integer[] id){
for(int kk:id )
System.out.println("--->"+kk);
return "success";
}
}

页面请求localhost:8080/test?id=11&id=22&id=33

物理分页的实现

分页处理实际上有物理分页和逻辑分页两种,如果使用逻辑分页实际上是依赖于内存进行分页,引入逻
辑分页实际上没有什么意义;具体开发种一般使用物理分页。MyBatisPlus提供了分页插件和对应的分
页方法,使用插件+xxxPage方法则可以实现物理 分页。
1、配置分页拦截器

@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
//添加分页插件
interceptor.addInnerInterceptor(new
PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}

2、在业务层中处理分页。注意这个分页插件能处理页码值小的情形,但不能处理页码值大于最大页码
值。如果当页码值大于最大页码值时返回为空集合

@Test
void testPage1(){
//每页2行数据,获取第1页数据
Page<User> pages=Page.of(1,2);
QueryWrapper<User> wrapper=new QueryWrapper<>();
wrapper.eq("sex",true).or().in("id",11,22,33,44);
Page<User> userPage = userMapper.selectPage(pages, wrapper);
System.out.println("返回的集合:"+userPage.getRecords());
System.out.println("当前页码值:"+userPage.getCurrent());
System.out.println("总行数:"+userPage.getTotal());
System.out.println("最大页码值:"+userPage.getPages());
System.out.println("每页行数:"+userPage.getSize());
}

基础使用总结

集成使用MP可以很方便的实现单表的CRUD功能,甚至连XML文件都不用编写。如果复杂应用也是可以
使用MyBatis的所有功能,例如xml映射元文件和注解等。
只需要引入starter工程,并配置mapper扫描路径即可。

<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3.1</version>
</dependency>

核心配置application.properties

mybatis-plus.configuration.logimpl=org.apache.ibatis.logging.stdout.StdOutImpl
mybatis-plus.mapper-locations=classpath:mapper/*.xml

方法都是MyBatis-Plus写好的,直接引用即可

public interface UserMapper extends BaseMapper<User>{}

主类或者配置上添加自动扫描配置

@MapperScan("com.yan.mapper")

实体类上的注解

MyBatisPlus提供了一些注解供在实体类和表信息出现不对应的时候使用。通过使用注解完成逻辑上匹

  • @TableName用于实体类的类名和数据库表名不一致
@TableName(value ="tb_users")
public class User implements Serializable {
  • @TableId用于实体类的主键名称和表中主键名称不一致,并设置对应的主键生成策略
@TableId(type = IdType.AUTO)
private Long id;
  • @TableField用于实体类中的成员名称和表中字段名称不一致
//事实上MP支持驼峰计数,默认情况下列名称rold_id对应的属性名称就是roleId
@TableField("role_id")
private Long roleId;
@TableField(exist = false) //这个属性没有对应的列名称
private static final long serialVersionUID = 1L;

主键生成策略

@TableId重要的属性type用于设置对应的注解生成策略

  • 数据库ID自增,例如MySQL中的auto_increment,请确保数据库设置了 ID自增否则无效
    IdType.Auto
  • 未设置主键类型,注解里等于跟随全局,全局里约等于 INPUT,例如IdType.NONE
  • 用户输入ID,类型可以通过自己注册自动填充插件进行填充,例如IdType.INPUT
  • 分配ID值,默认实现为雪花算法ASSIGN_ID
  • 分配UUID值,去掉其中的-值,ASSIGN_UUID

UUID

UUID通用唯一识别码的标准型式包含32个16进制数字,以连字号分为五段,形式为8-4-4-4-12的36个字
符,到目前为止业界一共有5种方式生成UUID
JDK中提供了UUID的生成工具类

public class Test1 {
public static void main(String[] args) {
for(int i=0;i<10;i++) {
String str = UUID.randomUUID().toString();
System.out.println(str);
}
}
}

优点:性能非常高:本地生成,没有网络消耗。
缺点:

  • 没有排序,无法保证趋势递增
  • UUID往往使用字符串存储,查询的效率比较低
    • uuid生成的是36个字符,剔除其中的连字符-,也会有32个字符
  • 不易于存储:UUID太长,16字节128位,通常以36长度的字符串表示,很多场景不适用
  • 信息不安全:基于MAC地址生成UUID的算法可能会造成MAC地址泄露,这个漏洞曾被用于寻找梅
    丽莎病毒的制作者位置
  • ID作为主键时在特定的环境会存在一些问题,比如做DB主键的场景下,UUID就非常不适用

雪花算法

SnowFlake雪花算法是一种以划分命名空间来生成ID的一种算法,这种方案把64-bit分别划分成多段,
分开来标示机器、时间等
核心思想:使用41bit作为毫秒数,10bit作为机器的ID(5个bit是数据中心,5个bit的机器ID),12bit作为
毫秒内的流水号(意味着每个节点在每毫秒可以产生4096个ID),最后还有一个符号位,永远是0
MyBatis Plus
优点:

  • 毫秒数在高位,自增序列在低位,整个ID都是趋势递增的
  • 不依赖数据库等第三方系统,以服务的方式部署,稳定性更高,生成ID的性能也是非常高的
  • 可以根据自身业务特性分配bit位,非常灵活
    缺点:
    强依赖机器时钟,如果机器上时钟回拨,会导致发号重复或者服务会处于不可用状态

自动填充

在常用业务中有些属性需要配置一些默认值,MyBatis-Plus提供了实现此功能的插件,也就是自动填充
功能。比如创建时间、修改时间这些操作一般都是自动化完成的,是不用去手动更新的
方式一:数据库级别,不建议使用

create table tb_users(
id bigint primary key auto_increment, … …
create_time timestamp default current_timestamp ON UPDATE CURRENT_TIMESTAMP
)engine=innodb default charset utf8;

方式二:代码级别填充字段 @TableField(fill = FieldFill.INSERT)和@TableField(fill =
FieldFill.INSERT_UPDATE),最后自定义实现类处理这个注解
修改实体类,在对应属性上添加注解 fill属性是一个FieldFill枚举类型值,用于执行在
insert/update/insert和update时执行自动填充功能

@TableField(value="create_time",fill = FieldFill.INSERT_UPDATE)
private Date ctime;

添加字段自动填充策略

@Configuration
public class FieldFillConfig implements MetaObjectHandler {
public void insertFill(MetaObject metaObject) { //执行插入操作的处理
setFieldValByName("ctime", new Date(), metaObject);
}
public void updateFill(MetaObject metaObject) { //执行update操作的处理
setFieldValByName("ctime", new Date(), metaObject);
}
}

数据的填充实际上是依赖于MetaObjectHandler实现的,提供方法 setFieldValByName用于针对特定的
字段填充数据,getFieldValByName获取特定字段的数据
编码调用,这里并没有针对ctime属性设置数据,但是可以从sql语句或者数据库中查看到会有数据

void testCreate1(){
User user=new User();
user.setUsername("yanjun1");
user.setPassword("123456");
user.setRoleId(2L);
int inserted = userMapper.insert(user);

排除实体类中非表字段

  • 使用transient关键字修饰非表字段,但是被transient修饰后,无法进行序列化
  • 使用static关键字,因为使用的是lombok框架生成的get/set方法,所以对于静态变量需要手动生成
    get/set方法
  • 使用@TableField(exist = false)注解

乐观锁机制的实现

事务是指作为单个逻辑工作单元执行的一系列操作,这些操作要么全做,要么全不做,是一个不可分割
的工作单元。

  • 数据一致性:是一个综合性的规定,或者说是一个把握全局的规定。因为它是由原子性、持久性、
    隔离性共同保证的结果,而不是单单依赖于某一种技术
  • 原子性:MySQL 是通过靠 Redo 的 WAL(Write Ahead Log)技术来实现这种效果的
  • 持久性:就是指一个事务一旦提交,它对数据库中数据的改变就应该是永久性的,接下来的操作或
    故障不应该对其有任何影响
  • 隔离性:依靠锁和多版本控制MVCC实现
    在 MySQL 事务中,锁的实现与隔离级别有关系,在RR隔离级别下,MySQL 为了解决幻读的问题,以牺
    牲并行度为代价,通过 Gap 锁来防止数据的写入

锁的出现主要解决的是进程同步的问题

  • 悲观锁:对共享数据添加悲观锁,则有人持有锁则其他人不能修改数据。其实,给公共资源加上悲
    观锁的话,就破外了进程了之间的同步性。牺牲了效率,但是保证了数据的安全性
  • 乐观锁:就是对共享数据加上一个版本号或者时间戳,多线程可以去修改,但是在修改之前都会去
    查询这个版本号,如果版本号没有被修改过,则都可以去修改,但是修改完成后,就会对版本号进
    行修改,若又想更改成本价,此时会先去去查询版本号,若版本号已经被修改了,那么就不能去修

    MP中乐观锁的实现:
    1、在表中添加一个额外列 alter user tb_users add version bigint default 0
    2、修改实体类添加对应的属性,同时通过注解添加配置
@Version
private Long version; //声明对应的属性就是乐观锁

3、添加额外的乐观锁插件

@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
//添加乐观锁插件
interceptor.addInnerInterceptor(new
OptimisticLockerInnerInterceptor());
return interceptor;
}
}

插入操作

@Test
void testCreate1(){
User user=new User();
user.setUsername("yanjun1");
user.setPassword("123456");
user.setRoleId(2L);
int inserted = userMapper.insert(user);
System.out.println(inserted);
System.out.println(user);
}

修改操作

@Test
void testUpdate1(){
User user= userMapper.selectById(1614879563333713931L);
user.setUsername("zhangsanfeng");
int updated = userMapper.updateById(user);
System.out.println(updated);
}

分析修改操作的SQL语句

==> Preparing: SELECT id,username,password,birth,sex,role_id,create_time AS
ctime,version FROM tb_users WHERE id=? 按照id执行查询操作,获取需要修改的原始数据
==> Parameters: 1614879563333713932(Long)
<== Columns: id, username, password, birth, sex, role_id, ctime, version
<== Row: 1614879563333713932, yanjun1, 123456, null, 1, 2, 2023-01-17
06:37:26, 0
<== Total: 1
==> Preparing: UPDATE tb_users SET username=?, password=?, sex=?, role_id=?,
create_time=?, version=? WHERE id=? AND version=? 修改操作,具体编码中并没有修改
version的值,系统会自动执行+1处理;修改的条件原来只有id=?,但是引入乐观锁后则and
version=读取时的id值
==> Parameters: zhangsanfeng(String), 123456(String), 1(Integer), 2(Long),
2023-01-17 14:37:26.0(Timestamp), 1(Long), 99(Long), 0(Long)
<== Updates: 0

乐观锁就是新增一个额外列用于存储当前行数据的版本号。当修改当前行数据时,版本号会自动+1,而
执行的修改操作条件时id=? and version=?,如果版本号和原来的版本号不一致,则没有满足条件的数
据,修改失败。

逻辑删除

互联网应用中最大的财富实际上就是数据,所以具体开发中一般不建议进行数据的物理删除。

  • 物理删除:真实删除,将对应数据从数据库中删除,之后查询不到此条被删除的数据
  • 逻辑删除:假删除,将对应数据中代表是否被删除字段的状态修改为“被删除状态”,之后在数据库
    中仍旧能看到此条数据记录
    @TableLogic注解表示逻辑删除,属性value=“未删除的值,默认值为0”,属性delval=“删除后的值,默
    认值为1”,如果不设置,就使用默认值
    使用场景:可以进行数据恢复
    1、在表中添加一个额外列deleted用于表示当前行的数据是否已经被删除,如果值为0表示没有删除,1
    表示已经删除。后续查询中系统会自动添加上deleted=0表示只查询没有删除的数据
alter table tb_users add deleted boolean default 0;

2、修改实体类添加上对应的用于表示是否删除列对应的属性,同时使用注解进行配置

@TableLogic(value = "0",delval = "1")
private Boolean deleted;

其中value是用于表示数据没有删除时deleted列对应的值,delval用于设置表示当前行数据已经被删除
的列对应的值
3、编码使用

Long id=5L;
int deleted = userMapper.deleteById(id);

在控制台上可以看到真正执行的sql语句不是delete操作,而是修改deleted列的值

==> Preparing: UPDATE tb_users SET deleted=1 WHERE id=? AND deleted=0
==> Parameters: 5(Long)
<== Updates: 1

如果需要撤销删除【回收站】仅仅就是将deleted列的值从1修改为0即可

MP工作原理

1、通过注解实现对象与表一一映射
2、通过属性注解实现对象的属性与表中的字段一一映射
3、将公共的方法进行抽取,抽取到BaseMapper接口中
4、将用户操作的方法对象,转化为数据库能够识别的Sql语句
例如调用方法userMapper.insert(user对象),需要转换出的SQL语句为insert into 表名(字段名…) value
(属性值…)
拼接过程:

  • 通过userMapper查找父级接口BaseMapper
  • 根据BaseMapper 查找泛型对象 User对象
  • 根据user对象查找指定的注解@TableName获取表名
  • 根据user对象的属性动态获取表中的字段@TableField
  • 在获取字段的同时获取属性的值,最后进行sql拼接
  • MP将拼接好的Sql交给Mybatis框架处理执行

Wrapper条件构造

  • Wrapper条件构造抽象类,最顶端父类
    • AbstractWrapper用于查询条件封装生成sql的where条件
      • QueryWrapper查询条件封装
      • UpdateWrapper用于Update 条件封装
    • AbstractLambdaWrapper使用Lambda 语法
      • LambdaQueryWrapper用于Lambda语法使用的查询Wrapper
      • LambdaUpdateWrapper是Lambda 更新封装Wrapper
        基本使用
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.like("name","yan").lt("age",40);
List<User> userList = userMapper.selectList(queryWrapper);