MyBatis Plus

news2024/9/24 5:24:26

概述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
在这里插入图片描述
优点:

  • 毫秒数在高位,自增序列在低位,整个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);

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/171201.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

python-MySQL数据库基础(二)操作数据库、数据表进行增删改查、分组、排序、连接

操作数据库(DDL) Cmder是一个软件包&#xff0c;它被创建出来用于Windows上替代模拟器的&#xff0c;cmder官网&#xff1a;https://cmder.net/&#xff0c;安装完成后把安装目录下的bin文件夹添加到环境变量里才能正常使用&#xff08;跟安装python类似&#xff09;。 连接数…

85.机器翻译与数据集

语言模型是自然语言处理的关键&#xff0c; 而机器翻译是语言模型最成功的基准测试。 因为机器翻译正是将输入序列转换成输出序列的 序列转换模型&#xff08;sequence transduction&#xff09;的核心问题。 序列转换模型在各类现代人工智能应用中发挥着至关重要的作用&#x…

Linux常用命令——traceroute命令

在线Linux命令查询工具(http://www.lzltool.com/LinuxCommand) traceroute 显示数据包到主机间的路径 补充说明 traceroute命令用于追踪数据包在网络上的传输时的全部路径&#xff0c;它默认发送的数据包大小是40字节。 通过traceroute我们可以知道信息从你的计算机到互联网…

若依项目启动前后端分离版本

若依项目的启动 1、下载源码 git clone https://gitee.com/y_project/RuoYi-Vue.git拉取代码、解压解压后 项目结构&#xff1a; 2、项目依赖 1、前端的依赖 2、后端的依赖 模块化&#xff1a; 依赖&#xff1a; 3、项目配置 1、前端配置 查看package.json 文件、能看到…

【Kubernetes】 从基础认识 k8s核心pod相关概念

基础 提示:此篇帮助朋友们,养成从0到1不断延伸知识的一种方法 最简单的创建pod入手 访问官方文档,直接使用案例,进行修改即可! 官网地址:https://kubernetes.io/zh-cn/docs/tasks/configure-pod-container/assign-pods-nodes/删除不需要的部分,根据规则添加需要的参数…

[oeasy]python0053_ 续行符_line_continuation_python行尾续行

续行符与三引号 回忆上次内容 上次还是转义序列 类型英文符号\abell响铃\bbackspace退格\ttab水平制表符\vvertical tab垂直制表符换行不回车\\backslash反斜杠\"double quote双引号\’single quote单引号\xhh具体字符输出(hh)16 进制对应的ascii 字符\ooo具体字符输出…

TDengine 时序数据特色查询语法详解,助力时序场景下的应用开发

小 T 导读&#xff1a;TDengine 是专为时序数据而研发的大数据平台&#xff0c;存储和计算都针对时序数据的特点量身定制&#xff0c;在支持标准 SQL 的基础之上&#xff0c;还提供了一系列贴合时序业务场景的特色查询语法&#xff0c;极大地方便了时序场景的应用开发。TDengin…

Java 里面 @InitBinder注解的使用原理

目录 1 @InitBinder注解作用1.1绑定同属性多对象1.2 类型转换2 总结1 @InitBinder注解作用 1.1绑定同属性多对象 第一个作用: 绑定同属性多对象 什么意思,就是有一个接口,参数是两个实体类,这两个实体类里面的属性名称还是一样,那么前端给这个接口传值的时候,哪个参数…

JavaEE day3 初识web与HTML 2

HTML HTML&#xff1a; 1.由标签&#xff08;tag&#xff09;组成的一棵树形结构&#xff0c;由于标签处于一棵树上&#xff0c;有时候也用元素&#xff08;element&#xff09;或者结点&#xff08;node&#xff09;表示 2.基本结构&#xff1a;html包括head与body两部分&a…

HummerRisk V0.9.0:增加RBAC 拓扑图,云检测、漏洞、主机等模块增加规则

HummerRisk V0.9.0发布&#xff1a;增加RBAC 资源拓扑图&#xff0c;首页新增检查的统计数据&#xff0c;云检测、漏洞、主机等模块增加规则&#xff0c;对象存储增加京东云&#xff0c;操作审计增加金山云&#xff0c;镜像仓库新增设置别名。 感谢社区中小伙伴们的反馈&#…

40 个定时任务,带你理解 RocketMQ 设计精髓!

大家好&#xff0c;我是君哥。今天来分享 RocketMQ 的定时任务。通过这些定时任务&#xff0c;能让我们更加理解 RocketMQ 的消息处理机制和设计理念。从 RocketMQ 4.9.4 的源代码上看&#xff0c;RocketMQ 的定时任务有很多&#xff0c;今天主要讲解一些核心的定时任务。1 架构…

智云通CRM:如何做好销售复盘,提升业绩?

我们在销售拜访中经常听见客户这样说&#xff1a;“不好意思啊&#xff0c;洛经理&#xff0c;我最近没有时间&#xff0c;等过一段时间不忙了&#xff0c;我们再约。”“洛经理&#xff0c;谢谢你大老远跑一趟&#xff0c;给我介绍这个产品。我很满意&#xff0c;不过我需要和…

RPC框架泛调用原理及转转的实践

RPC框架泛化调用功能在网关、接口测试等场景下有着广泛的需求&#xff0c;本文给各位读者介绍一下主流的泛化调用实现方式及原理&#xff0c;比较各种实现方案的优缺点&#xff0c;并分享泛化调用在转转的实践。一方面有助于RPC框架使用方理解泛化调用&#xff0c;更好地使用泛…

论文投稿指南——中文核心期刊推荐(原子能技术)

【前言】 &#x1f680; 想发论文怎么办&#xff1f;手把手教你论文如何投稿&#xff01;那么&#xff0c;首先要搞懂投稿目标——论文期刊 &#x1f384; 在期刊论文的分布中&#xff0c;存在一种普遍现象&#xff1a;即对于某一特定的学科或专业来说&#xff0c;少数期刊所含…

spring mvc配置类简介及放静态资源释放

配置文件ApplicationContext.xml 基于spring的项目资源都是通过DispatcherServlet作为拦截器&#xff0c;DispatcherServlet是前置控制器&#xff0c;配置在web.xml文件中的。拦截匹配的请求&#xff0c;Servlet拦截匹配规则要自己定义&#xff0c;把拦截下来的请求&#xff0…

JS中Math.random()方法的使用总结

&#x1f525; 前言 Math.random() 这个方法相信大家都知道&#xff0c;是用来生成随机数的。不过一般的参考手册时却没有说明如何用这个方法来生成指定范围内的随机数。下面就来详细的介绍一下Math.random()&#xff0c;以及如何用它来生成制定范围内的随机数。 &#x1f525…

位运算__

异或运算相同为0&#xff0c;不同为1&#xff0c;相当于无进位相加0 ^ N NN ^ N 0异或运算满足交换律和结合律一、打印一个数的二进制题目打印一个数的二进制代码package bitoperation;public class PrintBinary {public static void printBinary(int num) {for (int i 32; …

代码重构之路 --我的2022年总结

2022年是我正式参加工作的第10个年头&#xff0c;也是我在CSDN上写博客的第11个年头。在这10余年的时间里&#xff0c;虽然在工作上遇到了各种情况&#xff0c;但我一直坚持输出、坚持分享&#xff0c;一共在CSDN上发表了530多篇原创博文。在这些文章中&#xff0c;大部分都是与…

论文投稿指南——中文核心期刊推荐(能源与动力工程)

【前言】 &#x1f680; 想发论文怎么办&#xff1f;手把手教你论文如何投稿&#xff01;那么&#xff0c;首先要搞懂投稿目标——论文期刊 &#x1f384; 在期刊论文的分布中&#xff0c;存在一种普遍现象&#xff1a;即对于某一特定的学科或专业来说&#xff0c;少数期刊所含…

一篇五分生信临床模型预测文章代码复现——Figure 4-6 临床模型构建(八)

之前讲过临床模型预测的专栏,但那只是基础版本,下面我们以自噬相关基因为例子,模仿一篇五分文章,将图和代码复现出来,学会本专栏课程,可以具备发一篇五分左右文章的水平: 本专栏目录如下: Figure 1:差异表达基因及预后基因筛选(图片仅供参考) Figure 2. 生存分析,…