MybatisPlus.xmind
一、MybatisPlus快速入门
1.基本介绍
官网:
简介 | MyBatis-Plus
MyBatis Plus是一个基于MyBatis的增强工具,它简化了MyBatis的使用,提供了一系列的增强功能,使开发更加方便快捷。
MyBatis Plus的主要特点包括:
- 无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
- 损耗小:启动即会自动注入基本 CURD,性能基本无损耗,直接面向对象操作
- 强大的 CRUD 操作:内置通用 Mapper、通用 Service,仅仅通过少量配置即可实现单表大部分 CRUD 操作,更有强大的条件构造器,满足各类使用需求
- 支持 Lambda 形式调用:通过 Lambda 表达式,方便的编写各类查询条件,无需再担心字段写错
- 支持主键自动生成:支持多达 4 种主键策略(内含分布式唯一 ID 生成器 - Sequence),可自由配置,完美解决主键问题
- 支持 ActiveRecord 模式:支持 ActiveRecord 形式调用,实体类只需继承 Model 类即可进行强大的 CRUD 操作
- 支持自定义全局通用操作:支持全局通用方法注入( Write once, use anywhere )
- 内置代码生成器:采用代码或者 Maven 插件可快速生成 Mapper 、 Model 、 Service 、 Controller 层代码,支持模板引擎,更有超多自定义配置等您来使用
- 内置分页插件:基于 MyBatis 物理分页,开发者无需关心具体操作,配置好插件之后,写分页等同于普通 List 查询
- 分页插件支持多种数据库:支持 MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、Postgre、SQLServer 等多种数据库
- 内置性能分析插件:可输出 Sql 语句以及其执行时间,建议开发测试时启用该功能,能快速揪出慢查询
- 内置全局拦截插件:提供全表 delete 、 update 操作智能分析阻断,也可自定义拦截规则,预防误操作
2.快速入门
准备数据库和表
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user`
(
id BIGINT NOT NULL COMMENT '主键ID',
name VARCHAR(30) NULL DEFAULT NULL COMMENT '姓名',
age INT NULL DEFAULT NULL COMMENT '年龄',
email VARCHAR(50) NULL DEFAULT NULL COMMENT '邮箱',
PRIMARY KEY (id)
);
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');
创建springboot项目
引入依赖
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.2</version>
</dependency>
配置文件
# 应用服务 WEB 访问端口
server.port=8082
#下面这些内容是为了让MyBatis映射
#指定Mybatis的Mapper文件
mybatis.mapper-locations=classpath:mapper/*xml
#指定Mybatis的实体目录
#mybatis.type-aliases-package=com.pkq.springboot.mybatis.entity
# MySQL数据库驱动
#这个驱动也可以省略,可以根据使用的MySQL自动加载相应的驱动
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# 数据源名称
spring.datasource.name=defaultDataSource
# 数据库连接地址
spring.datasource.url=jdbc:mysql://localhost:3306/mybatisPlus?serverTimezone=UTC&zeroDateTimeBehavior=convertToNull
# 数据库用户名和密码
spring.datasource.username=root
spring.datasource.password=123456
MybatisPlus默认开启驼峰映射
实体类
package com.pkq.demo.pojo;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* ClassName: User
* Package: com.pkq.demo.pojo
* Description:
*
* @Author pkq
* @Create 2024/3/22 15:42
* @Version 1.0
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("user")//指定表名
@Builder
public class User {
private Integer id;
private String name;
private Integer age;
private String email;
}
如果表的名字和实体类名字一样,就不要进行二者之间的映射
mapper接口
package com.pkq.demo.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.pkq.demo.pojo.User;
/**
* ClassName: UserMapper
* Package: com.pkq.demo.mapper
* Description:
*
* @Author pkq
* @Create 2024/3/22 15:48
* @Version 1.0
*/
public interface UserMapper extends BaseMapper<User> {
}
扫描mapper
在boot启动类添加@MapperScan进行扫描Mapper文件夹
package com.pkq.demo;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@MapperScan("com.pkq.demo.mapper")
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
测试
package com.pkq.demo;
import com.pkq.demo.mapper.UserMapper;
import com.pkq.demo.pojo.User;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import javax.annotation.Resource;
import java.util.List;
/**
* ClassName: TestUser
* Package: com.pkq.demo
* Description:
*
* @Author pkq
* @Create 2024/3/22 15:52
* @Version 1.0
*/
@SpringBootTest
public class TestUser {
@Resource
private UserMapper userMapper;
@Test
public void testUser(){
//通过条件查询来查询一个list,如果没有条件,则写null
List<User> users = userMapper.selectList(null);
for (User user:users){
System.out.println(user);
}
}
}
User(id=1, name=Jone, age=18, email=test1@baomidou.com)
User(id=2, name=Jack, age=20, email=test2@baomidou.com)
User(id=3, name=Tom, age=28, email=test3@baomidou.com)
User(id=4, name=Sandy, age=21, email=test4@baomidou.com)
User(id=5, name=Billie, age=24, email=test5@baomidou.com)
配置日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
二、增删改查
1.BaseMapper源码
package com.baomidou.mybatisplus.core.mapper;
public interface BaseMapper<T> extends Mapper<T> {
/**
* 插入一条记录
* @param entity 实体对象
*/
int insert(T entity);
/**
* 根据 ID 删除
* @param id 主键ID
*/
int deleteById(Serializable id);
/**
* 根据实体(ID)删除
* @param entity 实体对象
* @since 3.4.4
*/
更多Java –大数据 – 前端 – UI/UE - Android - 人工智能资料下载,可访问百度:尚硅谷官网(www.atguigu.com)
int deleteById(T entity);
/**
* 根据 columnMap 条件,删除记录
* @param columnMap 表字段 map 对象
*/
int deleteByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);
/**
* 根据 entity 条件,删除记录
* @param queryWrapper 实体对象封装操作类(可以为 null,里面的 entity 用于生成 where
语句)
*/
int delete(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
/**
* 删除(根据ID 批量删除)
* @param idList 主键ID列表(不能为 null 以及 empty)
*/
int deleteBatchIds(@Param(Constants.COLLECTION) Collection<? extends
Serializable> idList);
/**
* 根据 ID 修改
* @param entity 实体对象
*/
int updateById(@Param(Constants.ENTITY) T entity);
/**
* 根据 whereEntity 条件,更新记录
* @param entity 实体对象 (set 条件值,可以为 null)
* @param updateWrapper 实体对象封装操作类(可以为 null,里面的 entity 用于生成
where 语句)
*/
int update(@Param(Constants.ENTITY) T entity, @Param(Constants.WRAPPER)
Wrapper<T> updateWrapper);
/**
* 根据 ID 查询
* @param id 主键ID
*/
T selectById(Serializable id);
/**
* 查询(根据ID 批量查询)
* @param idList 主键ID列表(不能为 null 以及 empty)
*/
List<T> selectBatchIds(@Param(Constants.COLLECTION) Collection<? extends
Serializable> idList);
/**
* 查询(根据 columnMap 条件)
* @param columnMap 表字段 map 对象
*/
List<T> selectByMap(@Param(Constants.COLUMN_MAP) Map<String, Object>
columnMap);
/**
更多Java –大数据 – 前端 – UI/UE - Android - 人工智能资料下载,可访问百度:尚硅谷官网(www.atguigu.com)
* 根据 entity 条件,查询一条记录
* <p>查询一条记录,例如 qw.last("limit 1") 限制取一条记录, 注意:多条数据会报异常
</p>
* @param queryWrapper 实体对象封装操作类(可以为 null)
*/
default T selectOne(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper) {
List<T> ts = this.selectList(queryWrapper);
if (CollectionUtils.isNotEmpty(ts)) {
if (ts.size() != 1) {
throw ExceptionUtils.mpe("One record is expected, but the query
result is multiple records");
}
return ts.get(0);
}
return null;
}
/**
* 根据 Wrapper 条件,查询总记录数
* @param queryWrapper 实体对象封装操作类(可以为 null)
*/
Long selectCount(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
/**
* 根据 entity 条件,查询全部记录
* @param queryWrapper 实体对象封装操作类(可以为 null)
*/
List<T> selectList(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
/**
* 根据 Wrapper 条件,查询全部记录
* @param queryWrapper 实体对象封装操作类(可以为 null)
*/
List<Map<String, Object>> selectMaps(@Param(Constants.WRAPPER) Wrapper<T>
queryWrapper);
/**
* 根据 Wrapper 条件,查询全部记录
* <p>注意: 只返回第一个字段的值</p>
* @param queryWrapper 实体对象封装操作类(可以为 null)
*/
List<Object> selectObjs(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
/**
* 根据 entity 条件,查询全部记录(并翻页)
* @param page 分页查询条件(可以为 RowBounds.DEFAULT)
* @param queryWrapper 实体对象封装操作类(可以为 null)
*/
<P extends IPage<T>> P selectPage(P page, @Param(Constants.WRAPPER)
Wrapper<T> queryWrapper);
/**
* 根据 Wrapper 条件,查询全部记录(并翻页)
* @param page 分页查询条件
* @param queryWrapper 实体对象封装操作类
*/
<P extends IPage<Map<String, Object>>> P selectMapsPage(P page,
@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
2.insert
@Test
public void testInsertUser(){
//新增用户信息
User user=new User();
user.setName("皮卡丘");
user.setAge(20);
user.setEmail("pkq@pkq.com");
int row = userMapper.insert(user);
System.out.println("受影响的行数:"+row);
System.out.println(user);
}
最终执行插入操作以后,id的值不是自增的,因为MyBatis-Plus在实现插入数据时,会默认基于雪花算法的策略生成id
:
3.delete
3.1通过id删除
@Test
public void testDeleteById(){
//通过id删除用户信息
//DELETE FROM user WHERE id=?
int result = userMapper.deleteById(1475754982694199298L);
System.out.println("受影响行数:"+result);
}
3.2通过id批量删除
@Test
public void testDeleteBatchIds(){
//通过多个id批量删除
//DELETE FROM user WHERE id IN ( ? , ? , ? )
List<Long> idList = Arrays.asList(1L, 2L, 3L);
int result = userMapper.deleteBatchIds(idList);
System.out.println("受影响行数:"+result);
}
3.3通过map来删除
@Test
public void testDeleteByMap(){
//根据map集合中所设置的条件删除记录
//DELETE FROM user WHERE name = ? AND age = ?
Map<String, Object> map = new HashMap<>();
map.put("age", 23);
map.put("name", "张三");
int result = userMapper.deleteByMap(map);
System.out.println("受影响行数:"+result);
}
4.update
@Test
public void testUpdateById(){
User user = new User(4L, "admin", 22, null);
//UPDATE user SET name=?, age=? WHERE id=?
int result = userMapper.updateById(user);
System.out.println("受影响行数:"+result);
}
5.select
5.1根据id查询信息
@Test
public void testSelectById(){
//根据id查询用户信息
//SELECT id,name,age,email FROM user WHERE id=?
User user = userMapper.selectById(4L);
System.out.println(user);
}
5.2根据多个id查询
@Test
public void testSelectBatchIds(){
//根据多个id查询多个用户信息
//SELECT id,name,age,email FROM user WHERE id IN ( ? , ? )
List<Long> idList = Arrays.asList(4L, 5L);
List<User> list = userMapper.selectBatchIds(idList);
list.forEach(System.out::println);
}
5.3通过map条件进行查询
@Test
public void testSelectByMap(){
//通过map条件查询用户信息
//SELECT id,name,age,email FROM user WHERE name = ? AND age = ?
Map<String, Object> map = new HashMap<>();
map.put("age", 22);
map.put("name", "admin");
List<User> list = userMapper.selectByMap(map);
list.forEach(System.out::println);
}
5.4查询所有数据
@Test
public void testUser(){
//通过条件查询来查询一个list,如果没有条件,则写null
//SELECT id,name,age,email FROM user
List<User> users = userMapper.selectList(null);
for (User user:users){
System.out.println(user);
}
}
**通过观察BaseMapper中的方法,大多方法中都有Wrapper类型的形参,此为条件构造器,可针 **
**对于SQL语句设置不同的条件,若没有条件,则可以为该形参赋值null,即查询(删除/修改)所 **
有数据
6.通用service
MyBatis-Plus中有一个接口 IService和其实现类 ServiceImpl,封装了常见的业务层逻辑
说明:
通用 Service CRUD 封装IService接口,进一步封装 CRUD 采用 get 查询单行 remove 删
除 list 查询集合 page 分页 前缀命名方式区分 Mapper 层避免混淆,
泛型 T 为任意实体对象
建议如果存在自定义通用 Service 方法的可能,请创建自己的 IBaseService 继承
Mybatis-Plus 提供的基类
官网地址:https://baomidou.com/pages/49cc81/#service-crud-%E6%8E%A5%E5%8F%
A3
:::
MybatisPlus为了开发更加快捷,对业务层也进行了封装,直接提供了相关的接口和实现类;我们在进行业务层开发时,可以继承它提供的接口和实现类,使得编码更加高效;
实现流程:
- 定义一个服务扩展接口,该接口继承公共接口IService;
- 定义一个服务实现类,该类继承ServiceImpl<Mapper,Entity>,并实现自定义的扩展接口;
注意事项:
1.ServiceImpl父类已经注入了UserMapper对象,名称叫做baseMapper,所以当前实现类直接可以使用baseMapper完成操作2.因为ServiceImpl已经实现了IService下的方法,所以当前服务类没有必要再次实现
思想:共性的业务代码交给框架封装维护,非共性的业务,在接口UserService定义,然后在当前的服务类下实现;
6.1创建service接口及其实现类
package com.pkq.demo.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.pkq.demo.pojo.User;
/**
* ClassName: UserService
* Package: com.pkq.demo.service
* Description:
*
* @Author pkq
* @Create 2024/3/23 14:44
* @Version 1.0
*/
public interface UserService extends IService<User> {
}
package com.pkq.demo.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.pkq.demo.mapper.UserMapper;
import com.pkq.demo.pojo.User;
import com.pkq.demo.service.UserService;
import org.springframework.stereotype.Service;
/**
* ClassName: UserServiceImpl
* Package: com.pkq.demo.service.impl
* Description:
*
* @Author pkq
* @Create 2024/3/23 14:44
* @Version 1.0
*/
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
}
6.2测试查询的记录总数
package com.pkq.demo;
import com.pkq.demo.service.UserService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
/**
* ClassName: MybatisPlusTest
* Package: com.pkq.demo
* Description:
*
* @Author pkq
* @Create 2024/3/23 14:47
* @Version 1.0
*/
@SpringBootTest
public class MybatisPlusTest {
@Autowired
private UserService userService;
@Test
public void testIserVice(){
long count = userService.count();
System.out.println(count);
}
}
6.3测试批量插入
@Test
public void testSaveBatch(){
ArrayList<User> userArrayList=new ArrayList<>();
for(int i=0;i<5;i++){
User user = new User();
user.setName("pkq"+i);
user.setAge(20+i);
userArrayList.add(user);
}
userService.saveBatch(userArrayList);
List<User> list = userService.list();
for (User user:list){
System.out.println(user);
}
}
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@d13baac] was not registered for synchronization because synchronization is not active
JDBC Connection [HikariProxyConnection@1398428044 wrapping com.mysql.cj.jdbc.ConnectionImpl@71179b6f] will not be managed by Spring
==> Preparing: SELECT id,name,age,email FROM user
==> Parameters:
<== Columns: id, name, age, email
<== Row: -1954500607, 皮卡丘, 20, pkq@pkq.com
<== Row: 1, Jone, 18, test1@baomidou.com
<== Row: 2, Jack, 20, test2@baomidou.com
<== Row: 3, Tom, 28, test3@baomidou.com
<== Row: 4, Sandy, 21, test4@baomidou.com
<== Row: 5, Billie, 24, test5@baomidou.com
<== Row: 562069505, pkq0, 20, null
<== Row: 704675842, pkq1, 21, null
<== Row: 704675843, pkq2, 22, null
<== Row: 767590402, pkq3, 23, null
<== Row: 767590403, pkq4, 24, null
<== Total: 11
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@d13baac]
User(id=-1954500607, name=皮卡丘, age=20, email=pkq@pkq.com)
User(id=1, name=Jone, age=18, email=test1@baomidou.com)
User(id=2, name=Jack, age=20, email=test2@baomidou.com)
User(id=3, name=Tom, age=28, email=test3@baomidou.com)
User(id=4, name=Sandy, age=21, email=test4@baomidou.com)
User(id=5, name=Billie, age=24, email=test5@baomidou.com)
User(id=562069505, name=pkq0, age=20, email=null)
User(id=704675842, name=pkq1, age=21, email=null)
User(id=704675843, name=pkq2, age=22, email=null)
User(id=767590402, name=pkq3, age=23, email=null)
User(id=767590403, name=pkq4, age=24, email=null)
/**
* @Description 测试条件查询,且仅返回一个
* getOne:sql查询的结果必须为1条或者没有,否则报错 !!!!
*/
@Test
public void test2(){
LambdaQueryWrapper<User> wrapper = Wrappers.lambdaQuery(User.class);
wrapper.gt(User::getAge,20);
User one = userService.getOne(wrapper);
System.out.println(one);
}
/**
* @Description 根据条件批量查询
*/
@Test
public void test3(){
LambdaQueryWrapper<User> wrapper = Wrappers.lambdaQuery(User.class);
wrapper.gt(User::getAge,20);
List<User> list = userService.list(wrapper);
System.out.println(list);
}
/**
* @Description 根据条件批量查询并分页
*/
@Test
public void test4(){
LambdaQueryWrapper<User> wrapper = Wrappers.lambdaQuery(User.class);
wrapper.gt(User::getAge,20);
//构建分页对象
IPage<User> page=new Page<>(2,3);
userService.page(page,wrapper);
System.out.println(page.getRecords());
System.out.println(page.getPages());
System.out.println(page.getTotal());
}
/**
* @Description 测试服务层save保存单条操作
*/
@Test
public void test5(){
User user1 = User.builder().name("wangwu").userName("laowang4").
email("444@163.com").age(20).password("333").build();
boolean isSuccess = userService.save(user1);
System.out.println(isSuccess?"保存成功":"保存失败");
}
/**
* @Description 测试服务层批量保存
*/
@Test
public void test6(){
User user2 = User.builder().name("wangwu2").userName("laowang2").
email("444@163.com").age(20).password("333").build();
User user3 = User.builder().name("wangwu3").userName("laowang3").
email("444@163.com").age(20).password("333").build();
boolean isSuccess = userService.saveBatch(Arrays.asList(user2, user3));
System.out.println(isSuccess?"保存成功":"保存失败");
}
/**
* @Description 根据id删除操作
*/
@Test
public void test7(){
boolean isSuccess = userService.removeById(17l);
System.out.println(isSuccess?"保存成功":"保存失败");
}
/**
* @Description 根据条件批量删除
*/
@Test
public void test8(){
LambdaQueryWrapper<User> wrapper = Wrappers.lambdaQuery(User.class);
wrapper.gt(User::getId,12)
.gt(User::getAge,20);
boolean remove = userService.remove(wrapper);
System.out.println(remove);
}
/**
* @Description 测试根据id更新数据
*/
@Test
public void test9(){
//UPDATE tb_user SET password=?, t_name=? WHERE id=?
User user2 = User.builder().name("wangwu2").password("333").id(3l).build();
boolean success = userService.updateById(user2);
System.out.println(success);
}
/**
* @Description 测试根据条件批量更新
*/
@Test
public void test10(){
LambdaUpdateWrapper<User> wrapper = Wrappers.lambdaUpdate(User.class);
//UPDATE tb_user SET age=? WHERE (id IN (?,?,?))
wrapper.in(User::getId,Arrays.asList(1l,3l,5l)).set(User::getAge,40);
boolean update = userService.update(wrapper);
System.out.println(userService);
}
三、常用注解
经过以上的测试,在使用MyBatis-Plus实现基本的CRUD时,我们并没有指定要操作的表,只是在 Mapper接口继承BaseMapper时,设置了泛型User,而操作的表为user表
由此得出结论,MyBatis-Plus在确定操作的表时,由BaseMapper的泛型决定,即实体类型决
定,且默认操作的表名和实体类型的类名一致
如果实体类的名字和表的名字不一致,我们就需要使用@TableName注解
@TableName
描述:表名注解,标识实体类所对应的表
使用的位置:实体类
@TableName("t_user")
public class User {
private Long id;
private String name;
private Integer age;
private String email;
}
**在开发的过程中,我们经常遇到以上的问题,即实体类所对应的表都有固定的前缀,例如t_或tbl_ **
**此时,可以使用MyBatis-Plus提供的全局配置,为实体类所对应的表名设置默认的前缀,那么就 **
不需要在每个实体类上通过@TableName标识实体类对应的表
mybatis-plus:
configuration:
# 配置MyBatis日志
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
# 配置MyBatis-Plus操作表的默认前缀
table-prefix: t_
@TableId
- 描述:主键注解
- 使用位置:实体类主键字段
@TableName("t_user")
public class User {
@TableId
private Long uid;
private String name;
private Integer age;
private String email;
}
- MyBatis-Plus在实现CRUD时,会默认将id作为主键列,并在插入数据时,默认基于雪花算法的策略生成id
- 如果实体类和表中的主键不是id,而且其他字段,比如uid,那么就会报错,我们需要在实体类的uid属性上面添加@TableId注解标识为主键
@TableId的value属性
如果实体类主键对应属性为id,表中主键字段是uid,则需要通过@TableId注解的value属性,指定表中的主键字段
@TableName("t_user")
public class User {
@TableId(value="uid")
private Long id;
private String name;
private Integer age;
private String email;
}
@TableId的type属性
type属性用来定义主键策略
常用主键策略
值 | 描述 |
---|---|
IdType.ASSIGN_ID(默认) | 基于雪花算法的策略生成数据id,和数据库id是否设置自增无关 |
IdType.AUTO | 使用数据库的自增策略,注意:该类型要确保数据库设置了id自增,否则无效 |
生成策略 | 应用场景 | 特点 |
---|---|---|
IdType.AUTO | 数据库主键自增(确保数据库设置了 主键自增 否则无效) | 1.使用数据库自带的主键自增值; |
2.数据库自增的主键值会回填到实体类中; | ||
3.数据库服务端生成的; | ||
IdType.ASSIGN_ID | 主键类型为number类型或数字类型String | 1.MP客户端生成的主键值; |
2.生成的主键值是数字形式的字符串 | ||
3.主键对应的类型可以是数字类型或者数字类型的字符串 | ||
4.底层基于雪花算法,让数据库的唯一标识也参与id的生成运算,保证id在分布式环境下,全局唯一(避免id的主键冲突问题); | ||
IdType.ASSIGN_UUID | 主键类型为 string(包含数字和字母组成) | 1.生成的主键值包含数字和字母组成的字符串; |
2.注意事项:如果数据库中主键值是number类型的,可不可用 |
全局配置主键生成策略
mybatis-plus:
configuration:
# 配置MyBatis日志
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
# 配置MyBatis-Plus操作表的默认前缀
table-prefix: t_
# 配置MyBatis-Plus的主键策略
id-type: auto
雪花算法
背景
- 要选择一个合数发方案来面对数据规模的增长,面对逐渐增长的访问压力和数据量
- 数据库的扩展方式主要有:业务分库,主从复制,数据库分表
数据库分表
将不同业务数据分散存储到不同的数据库服务器,能够支撑百万甚至千万用户规模的业务,但如果业务继续发展,同一业务的单表数据也会达到单台数据库服务器的处理瓶颈。例如,淘宝的几亿用户数据,如果全部存放在一台数据库服务器的一张表中,肯定是无法满足性能要求的,此时就需要对单表数据进行拆分。
单表数据拆分有两种方式:垂直分表和水平分表。示意图如下
垂直分表
把表中不经常使用而且占很大空间的列拆分出去
水平分表
当表中的数据记录很多的时候,考虑水平分表,但是也会导致更加复杂,需要考虑全局唯一性id
主键自增:可以按照范围来分段,比如1999999放到表1,10000001999999放到表2
如果分段过大,导致单表依然可能存在性能问题,如果分段过小,会导致切分后子表数量过多。
优点:可以随着数据的增加,平滑的扩充新表,比如现在用户100万,若增加到500万,只需要增加新的表即可,原有数据不需要变化。
缺点:分布不均匀,可能有的分段存储的数据量只有很少几条,而其他分段存储数据量很多
**取模 **
①同样以用户 ID 为例,假如我们一开始就规划了 10 个数据库表,可以简单地用 user_id % 10 的值来
表示数据所属的数据库表编号,ID 为 985 的用户放到编号为 5 的子表中,ID 为 10086 的用户放到编号
为 6 的子表中。
②复杂点:初始表数量的确定。表数量太多维护比较麻烦,表数量太少又可能导致单表性能存在问题。
③优点:表分布比较均匀。
④缺点:扩充新的表很麻烦,所有数据都要重分布
雪花算法
雪花算法是由Twitter公布的分布式主键生成算法,它能够保证不同表的主键的不重复性,以及相同表的主键的有序性。
①核心思想:
长度共64bit(一个long型)。
首先是一个符号位,1bit标识,由于long基本类型在Java中是带符号的,最高位是符号位,正数是0,负数是1,所以id一般是正数,最高位是0。
41bit时间截(毫秒级),存储的是时间截的差值(当前时间截 - 开始时间截),结果约等于69.73年。
10bit作为机器的ID(5个bit是数据中心,5个bit的机器ID,可以部署在1024个节点)。
12bit作为毫秒内的流水号(意味着每个节点在每毫秒可以产生 4096 个 ID)。
@TableField
MyBatis-Plus在执行SQL语句时,要保证实体类中的属性名和
表中的字段名一致 。如果实体类中的属性名和字段名不一致的情况,会出现什么问题呢?
-
如果实体类属性用的是驼峰命名,数据库表的字段使用下划线风格,MybatisPlus会自动把下划线命名风格转化为驼峰命名
-
如果实体类的属性和表中的字段命名不一样,需要在实体类属性上面添加@TableField注解来设置属性对应的字段名
:::tips
注解@TableField作用: -
指定表中普通字段与实体类属性之间的映射关系;
-
忽略实体类中多余属性与表中字段的映射关系(@TableField(exist = false));
以下情况可以省略:
- 名称一样
- 数据库字段使用_分割,实体类属性名使用驼峰名称(自动开启驼峰映射)
:::
@TableName("t_user")
public class User {
@TableId(type=IdType.ASSIGN_ID)
private Long id;
@TableField("userName")
private String name;
private Integer age;
private String email;
//增删改查操作时,忽略该属性
@TableField(exist = false)
private String address;
}
@TableLogic(逻辑删除)
- 物理删除:真实删除,将对应数据从数据库中删除,之后查询不到此条被删除的数据
- 逻辑删除:假删除,将对应数据中代表是否被删除字段的状态修改为“被删除状态”,之后在数据库中仍旧能看到此条数据记录
- 使用场景:可以进行数据恢复
- 本质:修改操作
实际在删除数据时,为了数据留痕,一般选择逻辑删除,也就是为删除表添加逻辑删除字段,通过修改字段状态值来表示数据是否被删除;
修改表的结构
0表示未删除,1表示删除
# 设置mp运行时行为
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 控制台输出sql
global-config:
db-config:
logic-delete-field: is_delete # 约定全局删除字段
logic-delete-value: 1
logic-not-delete-value: 0
实体类
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("user")//指定表名
public class User {
private Integer id;
private String name;
private Integer age;
private String email;
@TableLogic
private Integer isDelete;
}
删除前数据库信息
@Test
public void testDeletetUser(){
List<Long> list= Arrays.asList(1L,2L,3L);
int result = userMapper.deleteBatchIds(list);
System.out.println(result);
}
测试查询功能,看看能否查询到逻辑删除的数据
被逻辑删除的数据是查询不到的。
四、条件构造器和常用接口
1.wrapper
- Wrapper : 条件构造抽象类,最顶端父类
- AbstractWrapper : 用于查询条件封装,生成 sql 的 where 条件
- QueryWrapper : 查询条件封装
- UpdateWrapper : Update 条件封装
- AbstractLambdaWrapper : 使用Lambda 语法
- LambdaQueryWrapper :用于Lambda语法使用的查询Wrapper
- LambdaUpdateWrapper : Lambda 更新封装Wrapper
- AbstractWrapper : 用于查询条件封装,生成 sql 的 where 条件
2.QueryWrapper
eq( ) : 等于 =
ne( ) : 不等于 <> 或者 !=
gt( ) : 大于 >
ge( ) : 大于等于 >=
lt( ) : 小于 <
le( ) : 小于等于 <=
between ( ) : BETWEEN 值1 AND 值2
notBetween ( ) : NOT BETWEEN 值1 AND 值2
in( ) : in
notIn( ) :not in
sql中反向查询eg:not like != 等等,查询时是不会走索引的;
2.1数据库准备
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for tb_user 没有给自增
-- ----------------------------
DROP TABLE IF EXISTS `tb_user`;
CREATE TABLE `tb_user` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`user_name` varchar(255) DEFAULT NULL,
`password` varchar(255) DEFAULT NULL,
`name` varchar(255) DEFAULT NULL,
`age` int(11) DEFAULT NULL,
`email` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- ----------------------------
-- Records of tb_user
-- ----------------------------
BEGIN;
INSERT INTO `tb_user` VALUES (1, '赵一伤', '123456', 'zys', 19, 'zys@itcast.cn');
INSERT INTO `tb_user` VALUES (2, '钱二败', '123456', 'qes', 18, 'qes@itcast.cn');
INSERT INTO `tb_user` VALUES (3, '孙三毁', '123456', 'ssh', 20, 'ssh@itcast.cn');
INSERT INTO `tb_user` VALUES (4, '李四摧', '123456', 'lsc', 20, 'lsc@itcast.cn');
INSERT INTO `tb_user` VALUES (5, '周五输', '123456', 'zws', 20, 'zws@itcast.cn');
INSERT INTO `tb_user` VALUES (6, '吴六破', '123456', 'wlp', 21, 'wlp@itcast.cn');
INSERT INTO `tb_user` VALUES (7, '郑七灭', '123456', 'zqm', 22, 'zqm@itcast.cn');
INSERT INTO `tb_user` VALUES (8, '王八衰', '123456', 'wbs', 22, 'wbs@itcast.cn');
INSERT INTO `tb_user` VALUES (9, '张无忌', '123456', 'zwj', 25, 'zwj@itcast.cn');
INSERT INTO `tb_user` VALUES (10, '赵敏', '123456', 'zm', 26, 'zm@itcast.cn');
INSERT INTO `tb_user` VALUES (11, '赵二伤', '123456', 'zes', 25, 'zes@itcast.cn');
INSERT INTO `tb_user` VALUES (12, '赵三伤', '123456', 'zss1', 28, 'zss1@itcast.cn');
INSERT INTO `tb_user` VALUES (13, '赵四伤', '123456', 'zss2', 29, 'zss2@itcast.cn');
INSERT INTO `tb_user` VALUES (14, '赵五伤', '123456', 'zws', 39, 'zws@itcast.cn');
INSERT INTO `tb_user` VALUES (15, '赵六伤', '123456', 'zls', 29, 'zls@itcast.cn');
INSERT INTO `tb_user` VALUES (16, '赵七伤', '123456', 'zqs', 39, 'zqs@itcast.cn');
COMMIT;
SET FOREIGN_KEY_CHECKS = 1;
2.2基本查询
要求:查询用户中姓名包含"伤",密码为"123456",且年龄为19或者25或者29,查询结果按照年龄降序排序;
package com.pkq.demo;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.pkq.demo.mapper.UserMapper;
import com.pkq.demo.pojo.User;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import javax.annotation.Resource;
import java.util.List;
/**
* ClassName: QueryWrapperTest
* Package: com.pkq.demo
* Description:
*
* @Author pkq
* @Create 2024/3/28 10:40
* @Version 1.0
*/
@SpringBootTest
public class QueryWrapperTest {
@Resource
private UserMapper userMapper;
/**
* @Description 测试条件查询
* 要求:查询用户中姓名包含"伤",密码为"123456",且年龄为19或者25或者29,查询结果按照年龄降序排序;
* 如果查询的条件过于复杂,mp还适合么?
* 简单的操作,直接使用mp
* 但是非常复杂的操作,比如多表关联查询 复杂条件查询等,建议使用xml下sql实现;
*/
@Test
public void test1(){
/**
* SELECT id,user_name,password,name,age,email FROM tb_user
* WHERE (user_name LIKE ? AND password = ? AND age IN (?,?,?)) ORDER BY age DESC
*
* Parameters: %伤%(String), 123456(String), 19(Integer), 25(Integer), 29(Integer)
*/
QueryWrapper<User> queryWrapper=new QueryWrapper<>();
queryWrapper.like("user_name","伤")
.eq("password","123456")
.in("age",19,25,29)
.orderByDesc("age");
List<User> userList = userMapper.selectList(queryWrapper);
for (User user:userList){
System.out.println(user);
}
}
}
2.3QueryWrapper逻辑查询or
2.3.1 OR查询说明
- 通过QueryWrapper多条件查询时,默认使用and关键字拼接SQL;
- 通过QueryWrapper调用or()方法时,底层会使用or关键字拼接方法左右的查询条件;
2.3.2代码
业务要求:查询用户名为"lisi"或者年龄大于23的用户信息;
/**
* 查询用户名为"lisi"或者年龄大于23的用户信息;
*/
@Test
public void test2(){
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("name","lisi")
.or()
.gt("age",23);
List<User> userList = userMapper.selectList(queryWrapper);
for (User user:userList){
System.out.println(user);
}
}
2.4QueryWrapper实现模糊查询like
2.4.1模糊查询常用方法
- like(“表列名”,“条件值”); 作用:查询包含关键字的信息,底层会自动添加匹配关键字,比如:%条件值%
- likeLeft(“表列名”,“条件值”); 作用:左侧模糊搜索,也就是查询以指定条件值结尾的数据,比如:%条件值
- likeRight(“表列名”,“条件值”);作用:右侧模糊搜索,也就是查询以指定条件值开头的数据,比如:条件值%
2.4.2代码
/**
* 模糊查询
*/
@Test
public void testWrapper3(){
//1.创建查询条件构建器
QueryWrapper<User> wrapper = new QueryWrapper<>();
//2.设置条件
wrapper.likeLeft("user_name","zhang");
/*
SELECT id,user_name,password,name,age,email
from tb_user
where user_name like ?
%zhang
*/
List<User> users = userMapper.selectList(wrapper);
System.out.println(users);
}
2.5QueryWrapper排序查询
2.5.1 核心方法
- orderByAsc 升序排序,方法内可传入多个字段
- orderByDesc 降序排序,方法内可传入多个字段
Wrapper 条件构造器中 order by 排序相关的方法如下:
orderBy(boolean condition, boolean isAsc, R... columns)
// 按 id, age 字段进行升序
wrapper.orderBy(true, true, "id", "age");
参数说明:
- condition : 是否组装条件;
- isAsc : 是否升序,true 为升序,false 为降序;
2.5.2 代码实现
需求:先根据age升序排序,如果年龄相同则按照id降序排序;
/**
* 需求:先根据age升序排序,如果年龄相同则按照id降序排序;
*/
@Test
public void testWrapper4(){
//1.创建查询条件构建器
QueryWrapper<User> wrapper = new QueryWrapper<>();
//2.设置条件
wrapper.orderByAsc("age").orderByDesc("id");
/*
select * from tb_user where user_name = ? or age < ? and name in (?,?) order by age asc
*/
List<User> users = userMapper.selectList(wrapper);
System.out.println(users);
}
2.6QueryWrapper限定字段查询
2.6.1方法说明
MP查询时,默认将表中所有字段数据映射查询,但是有时我们仅仅需要查询部分字段信息,这是可以使用select()方法限定返回的字段信息,避免I/O资源的浪费;
示例:
wrapper.select(“字段1”,“字段2”,…)
2.6.2 代码示例
@Test
public void testWrapper5(){
//1.创建查询条件构建器
QueryWrapper<User> wrapper = new QueryWrapper<>();
//2.设置条件
wrapper.eq("user_name","lisi")
.or()
.lt("age",23)
.in("name","李四","王五")
//.orderBy(true,true,"age")
.orderByDesc("age")
.select("id","user_name");
/*
select id,user_name from tb_user where user_name = ? or age < ? and name in (?,?) order by age asc
*/
List<User> users = userMapper.selectList(wrapper);
System.out.println(users);
}
2.7QueryWrapper实现分页条件查询
4.7.1 方法说明
//参数1:分页对象
//参数2:查询条件
mapper.selectPage(page,wrapper);
4.7.2 代码实现
需求:查询年龄大于等于23的用户信息,并显示第2页,每页大小为4;
@Test
public void testWrapper6(){
int currentPage=2;//当前第几页
int currentPageSize=4;//每页有多少条
//创建分页对象
IPage<User> pageInfo=new Page<>(currentPage,currentPageSize);
QueryWrapper<User> wrapper=new QueryWrapper<>();
wrapper.gt("age",23);
//分页查询 pageInfo==userIPage -->true
IPage<User> userIPage = userMapper.selectPage(pageInfo,wrapper);
//获取分页参数
long pages = pageInfo.getPages();//总页数
long current = userIPage.getCurrent();//当前页
List<User> records = userIPage.getRecords();//当前页的数据
long total = userIPage.getTotal();//总记录数
long size = userIPage.getSize();//每页显示条数
System.out.println("总页数:"+pages);
System.out.println("当前页:"+current);
System.out.println("总记录数:"+total);
System.out.println("每页显示条数:"+size);
System.out.println("当前页的数据:");
for (User user:records){
System.out.println(user);
}
}
JDBC Connection [HikariProxyConnection@1202874820 wrapping com.mysql.cj.jdbc.ConnectionImpl@71e7adbb] will not be managed by Spring
==> Preparing: SELECT COUNT(*) AS total FROM tb_user WHERE (age > ?)
==> Parameters: 23(Integer)
<== Columns: total
<== Row: 8
<== Total: 1
==> Preparing: SELECT id,user_name,password,name,age,email FROM tb_user WHERE (age > ?) LIMIT ?,?
==> Parameters: 23(Integer), 4(Long), 4(Long)
<== Columns: id, user_name, password, name, age, email
<== Row: 13, 赵四伤, 123456, zss2, 29, zss2@itcast.cn
<== Row: 14, 赵五伤, 123456, zws, 39, zws@itcast.cn
<== Row: 15, 赵六伤, 123456, zls, 29, zls@itcast.cn
<== Row: 16, 赵七伤, 123456, zqs, 39, zqs@itcast.cn
<== Total: 4
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6ba060f3]
总页数:2
当前页:2
总记录数:8
每页显示条数:4
当前页的数据:
User(id=13, userName=赵四伤, password=123456, name=zss2, age=29, email=zss2@itcast.cn)
User(id=14, userName=赵五伤, password=123456, name=zws, age=39, email=zws@itcast.cn)
User(id=15, userName=赵六伤, password=123456, name=zls, age=29, email=zls@itcast.cn)
User(id=16, userName=赵七伤, password=123456, name=zqs, age=39, email=zqs@itcast.cn)
3.LambdaQueryWrapper查询
3.1 使用QueryWrapper开发存在的问题
- 使用QueryWrapper查询数据时需要手写对应表的列名信息,及其容易写错,开发体验不好;
- 使用QueryWrapper查询数据时,表的列名硬编码书写,后期一旦表结构更改,则会带来很大的修改工作量,维护性较差;
LambdaQueryWrapper可以解决上述出现问题,开发推荐使用;
3.2 代码实现
@Test
public void testLambdaQueryWrapper1() throws Exception{
// LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
LambdaQueryWrapper<User> wrapper = Wrappers.<User>lambdaQuery();
// wrapper.like("user_name", "%伤%")
// .eq("password","123456")
// .ge("age", 28)
// .between("age",29 , 39); // 包含边界值
wrapper.like(User::getUserName, "伤")
.eq(User::getPassword, "123456")
.in(User::getAge, Arrays.asList(19,25,29))
.orderByDesc(User::getAge)
.select(User::getId, User::getUserName);
List<User> users = userMapper.selectList(wrapper);
System.out.println(users);
}
4.LambdaQueryWrapper删除和更新操作
4.1 条件删除
/**
* 条件删除
* @throws Exception
*/
@Test
public void testWrapper5() throws Exception{
LambdaQueryWrapper<User> wrapper = Wrappers.<User>lambdaQuery().eq(User::getUserName, "武大郎");
int i = userMapper.delete(wrapper);
System.out.println(i);
}
4.2条件更新
/**
* 条件更新
* @throws Exception
*/
@Test
public void testWrapper6() throws Exception{
/**
* UPDATE tb_user SET t_name=? WHERE (id = ?)
*/
// 参数1: 最新的值
User user = new User();
user.setUserName("张三丰");
// 参数2:更新时条件
LambdaQueryWrapper<User> wrapper = Wrappers.<User>lambdaQuery();
wrapper.eq(User::getId, 15);
int update = userMapper.update(user, wrapper);
System.out.println(update);
}
/**
* 条件更新
* @throws Exception
*/
@Test
public void testWrapper7() throws Exception{
/**
* UPDATE tb_user SET t_name=?, user_name=? WHERE (id = ?)
*/
// 参数1: 最新的值
// 参数2:更新时条件
LambdaUpdateWrapper<User> wrapper = Wrappers.<User>lambdaUpdate();
wrapper.eq(User::getId, 15)
.set(User::getUserName, "张三丰666")
.set(User::getName,"zsf666");
int update = userMapper.update(null, wrapper);
System.out.println(update);
}
5.自定义查询接口实现分页查询
目前我们使用MP自带的分页插件可以很友好的实现分页查询操作,但是如果一些查询需要我们自定义SQL,那该如何实现分页查询操作呢?
5.1 自定义接口中直接传入Page分页对象即可
public interface UserMapper extends BaseMapper<User> {
/**
* 查询大于指定id的用户信息,并分页查询实现
* @param page
* @param id
* @return
*/
IPage<User> findGtIdByPage(IPage<User> page, @Param("id") Long id);
}
5.2定义xml映射文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.pkq.demo.mapper.UserMapper">
<select id="findGtIdByPage" resultType="com.pkq.demo.pojo.User">
select id,user_name,password,name,age,email from tb_user where id > #{id}
</select>
</mapper>
5.3测试
@Test
public void test2(){
IPage<User> userIPage=new Page<>();
userMapper.findGtIdByPage(userIPage,5L);
//获取分页参数
long pages = userIPage.getPages();//总页数
long current = userIPage.getCurrent();//当前页
List<User> records = userIPage.getRecords();//当前页的数据
long total = userIPage.getTotal();//总记录数
long size = userIPage.getSize();//每页显示条数
System.out.println("总页数:"+pages);
System.out.println("当前页:"+current);
System.out.println("总记录数:"+total);
System.out.println("每页显示条数:"+size);
System.out.println("当前页的数据:");
for (User user:records){
System.out.println(user);
}
}
==> Preparing: SELECT COUNT(*) AS total FROM tb_user WHERE id > ?
==> Parameters: 5(Long)
<== Columns: total
<== Row: 11
<== Total: 1
==> Preparing: select id,user_name,password,name,age,email from tb_user where id > ? LIMIT ?
==> Parameters: 5(Long), 10(Long)
<== Columns: id, user_name, password, name, age, email
<== Row: 6, 吴六破, 123456, wlp, 21, wlp@itcast.cn
<== Row: 7, 郑七灭, 123456, zqm, 22, zqm@itcast.cn
<== Row: 8, 王八衰, 123456, wbs, 22, wbs@itcast.cn
<== Row: 9, 张无忌, 123456, zwj, 25, zwj@itcast.cn
<== Row: 10, 赵敏, 123456, zm, 26, zm@itcast.cn
<== Row: 11, 赵二伤, 123456, zes, 25, zes@itcast.cn
<== Row: 12, 赵三伤, 123456, zss1, 28, zss1@itcast.cn
<== Row: 13, 赵四伤, 123456, zss2, 29, zss2@itcast.cn
<== Row: 14, 赵五伤, 123456, zws, 39, zws@itcast.cn
<== Row: 15, 张三丰666, 123456, zsf666, 29, zls@itcast.cn
<== Total: 10
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1db87583]
总页数:2
当前页:1
总记录数:11
每页显示条数:10
当前页的数据:
User(id=6, userName=吴六破, password=123456, name=wlp, age=21, email=wlp@itcast.cn)
User(id=7, userName=郑七灭, password=123456, name=zqm, age=22, email=zqm@itcast.cn)
User(id=8, userName=王八衰, password=123456, name=wbs, age=22, email=wbs@itcast.cn)
User(id=9, userName=张无忌, password=123456, name=zwj, age=25, email=zwj@itcast.cn)
User(id=10, userName=赵敏, password=123456, name=zm, age=26, email=zm@itcast.cn)
User(id=11, userName=赵二伤, password=123456, name=zes, age=25, email=zes@itcast.cn)
User(id=12, userName=赵三伤, password=123456, name=zss1, age=28, email=zss1@itcast.cn)
User(id=13, userName=赵四伤, password=123456, name=zss2, age=29, email=zss2@itcast.cn)
User(id=14, userName=赵五伤, password=123456, name=zws, age=39, email=zws@itcast.cn)
User(id=15, userName=张三丰666, password=123456, name=zsf666, age=29, email=zls@itcast.cn)
五、MybatisPlus分页插件
创建配置类MyBatisPlusConfig
package com.pkq.demo.config;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* ClassName: MybatisPlusConfig
* Package: com.pkq.demo.config
* Description:
*
* @Author pkq
* @Create 2024/3/28 10:11
* @Version 1.0
*/
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
PaginationInnerInterceptor paginationInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);
// 设置请求的页面大于最大页后操作, true调回到首页,false 继续请求 默认false
// paginationInterceptor.setOverflow(false);
// 设置最大单页限制数量,-1不受限制
paginationInterceptor.setMaxLimit(-1L);
//如果配置多个插件,切记分页最后添加
interceptor.addInnerInterceptor(paginationInterceptor);
return interceptor;
}
}
测试
@Test
public void testPage(){
int currentPage=2;//当前第几页
int currentPageSize=4;//每页有多少条
//创建分页对象
IPage<User> pageInfo=new Page<>(currentPage,currentPageSize);
//分页查询 pageInfo==userIPage -->true
IPage<User> userIPage = userMapper.selectPage(pageInfo,null);
//获取分页参数
long pages = userIPage.getPages();//总页数
long current = userIPage.getCurrent();//当前页
List<User> records = userIPage.getRecords();//当前页的数据
long total = userIPage.getTotal();//总记录数
long size = userIPage.getSize();//每页显示条数
System.out.println("总页数:"+pages);
System.out.println("当前页:"+current);
System.out.println("总记录数:"+total);
System.out.println("每页显示条数:"+size);
System.out.println("当前页的数据:");
for (User user:records){
System.out.println(user);
}
}
六、 MP代码生成器
6.1 开发现状
开发中当有一个新的业务要实现时,通常我们需要构建一下信息:
- 定义PO类数据库表和实体类的映射 Java Bean,打各种mp的注解。
- 定义DAO层需要编写接口 Mapper ,接口 Mapper 需要去继承 MP 中的 BaseMapper 接口。
- 定义Service层编写 Service 层接口和实现类。业务接口需要去继承 MP 中的 IService,业务实现类需要继承 MP 中的 ServiceImpl 和 实现业务接口。
- 定义Controller层编写 Controller 并标注 Spring MVC 中的相关注解。 显然上述存在固定的流程,且存在大量重复操作,you now 代码价值低且没效率!
6.2 MP逆向工程介绍
针对目前开发的现状,MP的代码生成器就可以一展身手了;
通过MP代码生成器可以生成模板性的代码,减少手工操作的繁琐,使开发人员聚焦于业务开发之上,提升开发效率;
AutoGenerator 类是MyBatis-Plus 的核心代码生成器类,通过 AutoGenerator 可以快速生成 Mapper接口、Entity实体类、Mapper XML、Service、Controller 等各个模块的代码,极大的提升了开发效率。
6.3 代码生成
参考代码:资料\mp_generator
或者是gitee开源链接:https://gitee.com/jitheima/mp_generator.git
完整代码:
说明:以后在项目中使用时,先在本工程生成,然后就可以把代码拷贝到对应的项目目录中使用了;
6.4代码生成说明
代码生成说明
当有一个新的业务实现时,对接口的功能实现上,我们通常来说需要构建下面的信息:
- PO类数据库表和实体类的映射 Java Bean。
- DAO层需要编写接口 Mapper ,接口 Mapper 需要去继承 MP 中的 BaseMapper 接口。
- Service层编写 Service 层接口和实现类。业务接口需要去继承 MP 中的 IService,业务实现类需要继承 MP 中的 ServiceImpl 和 实现业务接口。
- Controller层编写 Controller 并标注 Spring MVC 中的相关注解。
从上面的各类代码中可以放下,代码都是模板性的,如果用手工copy、修改的方式来实现,太烦人也没效率,而这时就是代码生成器小展身手的时候,使用代码生成器生成模板性的代码,减少手工操作的繁琐,集中精力在业务开发上,提升开发效率。
AutoGenerator 是 MyBatis-Plus 的代码生成器,通过 AutoGenerator 可以快速生成 Mapper接口、Entity实体类、Mapper XML、Service、Controller 等各个模块的代码,极大的提升了开发效率。
具体使用步骤
- 打开 CodeGenerator 类
- 修改对应的参数即可相关参数介绍:
DB_TYPE: 数据库类型, 默认是 MySQL
DB_NAME: 数据库名,用户修改为自己的数据库
HOST_NAME: 数据库IP, 默认 localhost
JDBC_USERNAME: 数据库用户名, 默认:root
JDBC_PASSWORD: 数据库密码,默认:root
TABLES: 需要生成代码的表, 数组
PACKAGE_PARENT_NAME: 代码生成的包结构
IS_DTO: 是否生成DTO, 默认:false
AUTHOR: 作者名称, 默认:itheima
- 运行main方法执行生成代码
- 拷贝到自己的项目中即可
以后在项目中使用,在这里生成后,可以把代码拷贝到对应的目录里使用,在整个黑马头条项目开发阶段,使用了当前生成的mapper和实体类。
七、MybatisX插件[扩展]
7.1 MybatisX插件介绍
MybatisX 是一款基于 IDEA 的快速开发插件,为效率而生。
安装方法:打开 IDEA,进入 File -> Settings -> Plugins -> Browse Repositories,输入 mybatisx
搜索并安装。
功能:
- Java 与 XML 调回跳转
- Mapper 方法自动生成 XML
mybatisx-001.gif
7.2 基于MybatisX实现逆向工程
八.乐观锁[扩展]
当要更新一条记录的时候,希望这条记录没有被别人更新
乐观锁实现方式:
- 取出记录时,获取当前 version
- 更新时,带上这个 version
- 执行更新时, set version = newVersion where version = oldVersion
- 如果 version 不对,就更新失败
乐观锁就是当前操作者认为在自己操作资源的过程中,其他人操作相同资源的可能性极低,所以无需加锁,而是通过设置一个版本号来加以约束;
悲观锁:排它锁,比如synchronized关键字就是悲观锁,当前线程做操作时,不允许其它线程做操作;
乐观锁:当前线程做操作时,允许其它线程做操作,但是如果其它线程做了操作,则当前操作失败;
乐观锁在数据库中有什么优势?
避免长事务场景锁定数据资源,导致其它线程操作该资源时阻塞,如果阻塞过多,那么导致数据库连接资源耗尽,进而数据库宕机了;(事务阻塞,不会释放连接资源)
本质上就是在操作前,先获取操作行的version版本号,然后再做前天操作,然后最后再更新这一行,更新时,给sql条件一个判断版本号的sql片段: select version,xxx from user where id=100; version=30 --> 做其他操作(20s);—> update user set xxx,version=version+1 where xxxx and version=30;
使用场景:
1.业务操作周期长,如果业务整个加入事务,导致数据库资源锁定周期过长,性能降低;
2.如果资源争抢过于激烈,会导致失败重试次数过多,导致性能降低;
示例:
@Data
@NoArgsConstructor//主要用于mybatis底层反射构建user实体类对象
@AllArgsConstructor//主要是lombok基于构建者模式构建对象
@Builder
/**
* 如果变的名称与实体类名称一致,该注解可省略
*/
@TableName("tb_user")//指定表名
public class User {
private Integer id;
private String userName;
private String password;
private String name;
private Integer age;
private String email;
@TableLogic
private Integer isDeleted;
@Version
private Integer version;
}
配置乐观锁拦截器:
/**
* 注册插件
* @return
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
//构建mp的插件注册器bean,通过该bean可批量注册多个插件
MybatisPlusInterceptor plusInterceptor = new MybatisPlusInterceptor();
//配置乐观锁拦截器
OptimisticLockerInnerInterceptor optimisticLockerInnerInterceptor = new OptimisticLockerInnerInterceptor();
//注册
plusInterceptor.addInnerInterceptor(optimisticLockerInnerInterceptor);
return plusInterceptor;
}
测试:
/**
* @Description 测试乐观锁
*/
@Test
public void testOp(){
User user = userMapper.selectById(5);
System.out.println(user);
user.setName("zws777");
userMapper.updateById(user);
}
使用mp的乐观锁,需要先自己根据主键id查询用户信息,信息中包含了此时的version数据,然后再更新,更新时会将查询的version值作为更新条件取更新;
效果:
九、MP字段自动填充
1.MP字段自动填充
1.1 背景说明
说明:
实际开发中有些字段存在大量的重复操作,比如create_time update_time等,需要经常在实体类中设置new Date(),比较繁琐,可以借助MP的自动填充功能
1.2 定义实体类
@TableName("tb_user")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class User {
//指定插入时自动填充的字段
@TableField(value = "create_time",fill = FieldFill.INSERT)
private Date createTime;
//自定插入或者更新时自动填充的字段
@TableField(value = "update_time",fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
}
1.3 配置填充规则
package com.itheima.handler;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;
import java.util.Date;
@Component
public class FillDataHandler implements MetaObjectHandler {
/**
* 定义自动填充的方法
* @param metaObject
*/
@Override
public void insertFill(MetaObject metaObject) {
//设置insert操作时的时间点
metaObject.setValue("createTime",new Date());
//设置update操作时的时间点
metaObject.setValue("updateTime",new Date());
}
/**
* 定义更新时填充的方法
* @param metaObject
*/
@Override
public void updateFill(MetaObject metaObject) {
//设置update操作时的时间点
metaObject.setValue("updateTime",new Date());
}
}
1.4 测试
更新测试:
/**
* @Description 根据主键id更新用户信息(开发最常用)
*/
@Test
public void test04(){
long id=12l;
//把主键信息和要更新的信息封装到实体类中,设置什么值,就更新什么值,为null的属性,不做处理
User user = User.builder().id(id).userName("张三丰2").age(120).build();
//UPDATE tb_user SET real_name=?, age=? WHERE id=?
//如何获取real_name字段的? user--->User.class--->通过反射获取userName Field字段对象---》
// 获取字段上的注解对象----》value值就可获取--》real_name
int count = userMapper.updateById(user);
System.out.println(count);
}
插入测试:
/**
* @Description 测试插入
*/
@Test
public void test02(){
User user =
User.builder()
.userName("itheima4")
.name("itcast4")
.age(14)
.email("itcast@itcast4.cn")
.password("44444")
.build();
//插入数据
int count = userMapper.insert(user);
System.out.println("受影响行数:"+count);
}