1.简介
- **无侵入**:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
- **损耗小**:启动即会自动注入基本 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 操作智能分析阻断,也可自定义拦截规则,预防误操作
mybatis-plus总结:
自动生成单表的CRUD功能(也就说单表的增删改查不用我们自己写了)
提供丰富的条件拼接方式
全自动ORM类型持久层框架
注意需要下载idea插件Mybatis X插件
mybatisplus 目前只能简化对单表的增删改成,无法多表的操作
2.快速使用
1.创建数据库表并添加数据
DROP TABLE IF EXISTS user;
CREATE TABLE user
(
id BIGINT(20) NOT NULL COMMENT '主键ID',
name VARCHAR(30) NULL DEFAULT NULL COMMENT '姓名',
age INT(11) NULL DEFAULT NULL COMMENT '年龄',
email VARCHAR(50) NULL DEFAULT NULL COMMENT '邮箱',
PRIMARY KEY (id)
);
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');
2.创建boot工程,并导入依赖
<!--springboot父工程-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.0.5</version>
</parent>
<groupId>org.example</groupId>
<artifactId>Mybtis-Plus-demo01</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<!--springboot快速启动器-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!--mybatis-plus
有这个就不许导入 mybatis启动器了,依赖传递进来mybatis
-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.6</version>
</dependency>
<!--druid-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-3-starter</artifactId>
<version>1.2.20</version>
</dependency>
<!-- 测试环境 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<!-- mybatis-plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3.1</version>
</dependency>
<!-- 数据库相关配置启动器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!-- 驱动类-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.28</version>
</dependency>
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.28</version>
</dependency>
</dependencies>
<!--打包-->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
3.编写配置文件启动类
#数据源
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
druid:
username: root
password: 123456
url: jdbc:mysql://localhost:3306/schedule_system
driver-class-name: com.mysql.cj.jdbc.Driver
#mybatis
mybatis-plus:
configuration:
map-underscore-to-camel-case: true #开启驼峰命名 mybatis已经自动开启了,以后不需要在这设置
auto-mapping-unknown-column-behavior: failing #自动映射
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #控制台输出
mapper-locations: classpath:mappers/*.xml #映射文件位置
type-aliases-package: org.example.pojo #别名
启动类
package org.example;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@MapperScan("org.example.mapper") // 扫描mapper接口
public class Main {
public static void main(String[] args) {
System.out.println("Hello world!");
}
}
4.创建实体类
@Data
public class User {
private Long id;
private String name;
private Integer age;
private String email;
}
5.创建接口
public interface UserMapper extends BaseMapper<User> {
}
继承mybatis-plus提供的基础Mapper接口,自带crud方法!
BaseMapper<User> User是你要对哪个对应的实体类增删改查
BaseMapper内部已经实现了增删改查,无需我们自己写增删改查
public interface BaseMapper<T> extends Mapper<T> { int insert(T entity); int deleteById(Serializable id); int deleteById(T entity); int deleteByMap(@Param("cm") Map<String, Object> columnMap); int delete(@Param("ew") Wrapper<T> queryWrapper); int deleteBatchIds(@Param("coll") Collection<?> idList); int updateById(@Param("et") T entity); int update(@Param("et") T entity, @Param("ew") Wrapper<T> updateWrapper); T selectById(Serializable id); List<T> selectBatchIds(@Param("coll") Collection<? extends Serializable> idList); List<T> selectByMap(@Param("cm") Map<String, Object> columnMap); default T selectOne(@Param("ew") 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; } } default boolean exists(Wrapper<T> queryWrapper) { Long count = this.selectCount(queryWrapper); return null != count && count > 0L; } Long selectCount(@Param("ew") Wrapper<T> queryWrapper); List<T> selectList(@Param("ew") Wrapper<T> queryWrapper); List<Map<String, Object>> selectMaps(@Param("ew") Wrapper<T> queryWrapper); List<Object> selectObjs(@Param("ew") Wrapper<T> queryWrapper); <P extends IPage<T>> P selectPage(P page, @Param("ew") Wrapper<T> queryWrapper); <P extends IPage<Map<String, Object>>> P selectMapsPage(P page, @Param("ew") Wrapper<T> queryWrapper); }
6.编写测试类并运行
@SpringBootTest
public class MybatisPluseTest {
@Autowired
private UserMapper userMapper;
@Test
public void testSelect(){
List<User> users = userMapper.selectList(null);
System.out.println(users);
}
}
运行结果
3.核心功能
3.1 基于Mapper接口的curd
通用 CRUD 封装BaseMapper (opens new window)接口, Mybatis-Plus 启动时自动解析实体表关系映射转换为 Mybatis 内部对象注入容器! 内部包含常见的单表操作!
3.1.1 Insert
// 插入一条记录
// T 就是要插入的实体对象
// 默认主键生成策略为雪花算法(后面讲解)
int insert(T entity);
类型 | 参数名 | 描述 |
---|---|---|
T | entity | 实体对象 |
3.1.2 delete
// 根据 entity 条件,删除记录
int delete(@Param(Constants.WRAPPER) Wrapper<T> wrapper);
// 删除(根据ID 批量删除)
int deleteBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);
// 根据 ID 删除
int deleteById(Serializable id);
// 根据 columnMap 条件,删除记录
int deleteByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);
类型 | 参数名 | 描述 |
---|---|---|
Wrapper<T> | wrapper | 实体对象封装操作类(可以为 null) |
Collection<? extends Serializable> | idList | 主键 ID 列表(不能为 null 以及 empty) |
Serializable | id | 主键 ID |
Map<String, Object> | columnMap | 表字段 map 对象 |
3.1.3 update
// 根据 whereWrapper 条件,更新记录
int update(@Param(Constants.ENTITY) T updateEntity,
@Param(Constants.WRAPPER) Wrapper<T> whereWrapper);
// 根据 ID 修改 主键属性必须值
int updateById(@Param(Constants.ENTITY) T entity);
类型 | 参数名 | 描述 |
---|---|---|
T | entity | 实体对象 (set 条件值,可为 null) |
Wrapper<T> | updateWrapper | 实体对象封装操作类(可以为 null,里面的 entity 用于生成 where 语句) |
3.1.4. select
// 根据 ID 查询
T selectById(Serializable id);
// 根据 entity 条件,查询一条记录
T selectOne(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
// 查询(根据ID 批量查询)
List<T> selectBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);
// 根据 entity 条件,查询全部记录
List<T> selectList(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
// 查询(根据 columnMap 条件)
List<T> selectByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);
// 根据 Wrapper 条件,查询全部记录
List<Map<String, Object>> selectMaps(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
// 根据 Wrapper 条件,查询全部记录。注意: 只返回第一个字段的值
List<Object> selectObjs(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
// 根据 entity 条件,查询全部记录(并翻页)
IPage<T> selectPage(IPage<T> page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
// 根据 Wrapper 条件,查询全部记录(并翻页)
IPage<Map<String, Object>> selectMapsPage(IPage<T> page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
// 根据 Wrapper 条件,查询总记录数
Integer selectCount(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
类型 | 参数名 | 描述 |
---|---|---|
Serializable | id | 主键 ID |
Wrapper<T> | queryWrapper | 实体对象封装操作类(可以为 null) |
Collection<? extends Serializable> | idList | 主键 ID 列表(不能为 null 以及 empty) |
Map<String, Object> | columnMap | 表字段 map 对象 |
IPage<T> | page | 分页查询条件(可以为 RowBounds.DEFAULT) |
3.1.5测试
@SpringBootTest
public class MybatisPluseTest {
@Autowired
private UserMapper userMapper;
//增加
@Test
public void insert() {
// List<User> users = userMapper.selectList(null);
// System.out.println(users);
User user = new User();
user.setId(6l);
user.setAge(18);
user.setName("lz");
user.setEmail("123@qq.com");
int insert = userMapper.insert(user);
System.out.println(insert);
}
//这里演示map的删除
@Test
public void delete() {
Map<String, Object> map = new HashMap<>();
map.put("name", "lz");
int i = userMapper.deleteByMap(map);
}
//修改
@Test
public void update() {
//将id等于6的修改
//将姓名改为lxxxx
// User user = new User();
// user.setId(6l);
// user.setName("lxxxx");
// userMapper.updateById(user); //这里是根据修改
//将所有人的年龄就该为18
User user1 = new User();
user1.setAge(18);
userMapper.update(user1, null);//这里的null是没有条件,也就是修改user1中的所有的年龄 ,如果这里不是null,那么就是不修改
//因为null是不做修改,所有我们实体类全是包装类,不是基本类型
}
@Test
public void select() {
//根据id查询
User user = userMapper.selectById(1);
System.out.println(user);
//根据集合查询
List<Long> list = new ArrayList<>();
list.add(1l);
list.add(2l);
List<User> users = userMapper.selectBatchIds(list);
System.out.println(users);
//查询所有
List<User> users1 = userMapper.selectList(null);
System.out.println(users1+"----------------");
}
3.2基于sevice接口的curd
3.2.1创建service接口
接口继承IService接口
public interface UserService extends IService<User> {
}
3.2.2service的实现类
接口继承IService接口
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper,User> implements UserService{
}
3.2.3常用方法
保存:
// 插入一条记录(选择字段,策略插入)
boolean save(T entity);
// 插入(批量)
boolean saveBatch(Collection<T> entityList);
// 插入(批量)
boolean saveBatch(Collection<T> entityList, int batchSize);
修改或者保存:
// TableId 注解存在更新记录,否插入一条记录
boolean saveOrUpdate(T entity);
// 根据updateWrapper尝试更新,否继续执行saveOrUpdate(T)方法
boolean saveOrUpdate(T entity, Wrapper<T> updateWrapper);
// 批量修改插入
boolean saveOrUpdateBatch(Collection<T> entityList);
// 批量修改插入
boolean saveOrUpdateBatch(Collection<T> entityList, int batchSize);
移除:
// 根据 queryWrapper 设置的条件,删除记录
boolean remove(Wrapper<T> queryWrapper);
// 根据 ID 删除
boolean removeById(Serializable id);
// 根据 columnMap 条件,删除记录
boolean removeByMap(Map<String, Object> columnMap);
// 删除(根据ID 批量删除)
boolean removeByIds(Collection<? extends Serializable> idList);
更新:
// 根据 UpdateWrapper 条件,更新记录 需要设置sqlset
boolean update(Wrapper<T> updateWrapper);
// 根据 whereWrapper 条件,更新记录
boolean update(T updateEntity, Wrapper<T> whereWrapper);
// 根据 ID 选择修改
boolean updateById(T entity);
// 根据ID 批量更新
boolean updateBatchById(Collection<T> entityList);
// 根据ID 批量更新
boolean updateBatchById(Collection<T> entityList, int batchSize);
数量:
// 查询总记录数
int count();
// 根据 Wrapper 条件,查询总记录数
int count(Wrapper<T> queryWrapper);
查询:
// 根据 ID 查询
T getById(Serializable id);
// 根据 Wrapper,查询一条记录。结果集,如果是多个会抛出异常,随机取一条加上限制条件 wrapper.last("LIMIT 1")
T getOne(Wrapper<T> queryWrapper);
// 根据 Wrapper,查询一条记录
T getOne(Wrapper<T> queryWrapper, boolean throwEx);
// 根据 Wrapper,查询一条记录
Map<String, Object> getMap(Wrapper<T> queryWrapper);
// 根据 Wrapper,查询一条记录
<V> V getObj(Wrapper<T> queryWrapper, Function<? super Object, V> mapper);
集合:
// 查询所有
List<T> list();
// 查询列表
List<T> list(Wrapper<T> queryWrapper);
// 查询(根据ID 批量查询)
Collection<T> listByIds(Collection<? extends Serializable> idList);
// 查询(根据 columnMap 条件)
Collection<T> listByMap(Map<String, Object> columnMap);
// 查询所有列表
List<Map<String, Object>> listMaps();
// 查询列表
List<Map<String, Object>> listMaps(Wrapper<T> queryWrapper);
// 查询全部记录
List<Object> listObjs();
// 查询全部记录
<V> List<V> listObjs(Function<? super Object, V> mapper);
// 根据 Wrapper 条件,查询全部记录
List<Object> listObjs(Wrapper<T> queryWrapper);
// 根据 Wrapper 条件,查询全部记录
<V> List<V> listObjs(Wrapper<T> queryWrapper, Function<? super Object, V> mapper);
3.2.4使用
@SpringBootTest
public class ServiceTest {
@Autowired
private UserService service;
@Test
public void save_select() {
User user = new User();
user.setId(9L);
user.setName("sad"); //只修改有值的,没有设置的不做修改
boolean b = service.saveOrUpdate(user);
System.out.println(b);
}
//批量加入
@Test
public void save_insert() {
ArrayList<User> list = new ArrayList<>();
User user = new User();
user.setId(9L);
user.setName("张三");
user.setAge(18);
user.setEmail("123456789@qq.com");
User user1 = new User();
user1.setId(10L);
user1.setName("李四");
user1.setAge(18);
user1.setEmail("123456789@qq.com");
list.add(user);
list.add(user1);
boolean b = service.saveBatch(list);
System.out.println(b);
}
//批量删除
@Test
public void save_delete() {
ArrayList<Long> longs = new ArrayList<>();
longs.add(9L);
longs.add(10L);
boolean b = service.removeByIds(longs);
}
@Test
public void save_selectById() {
User byId = service.getById(1); //单个
List<User> list = service.list();//所有
System.out.println(byId);
System.out.println(list);
}
}
使用基于service的批量插入(注意电脑卡顿)
一个是一条一条插,一个是1000条1000条的插
这种方式最佳性能
INSERT INTO user ( username, password, phone, info, balance, create_time, update_time )
VALUES
(user_1, 123, 18688190001, "", 2000, 2023-07-01, 2023-07-01),
(user_2, 123, 18688190002, "", 2000, 2023-07-01, 2023-07-01),
(user_3, 123, 18688190003, "", 2000, 2023-07-01, 2023-07-01),
(user_4, 123, 18688190004, "", 2000, 2023-07-01, 2023-07-01);
@Test
void testSaveBatch() { //优先这种,速度快
// 准备10万条数据
List<User> list = new ArrayList<>(1000);
long b = System.currentTimeMillis();
for (int i = 1; i <= 100000; i++) {
list.add(buildUser(i));
// 每1000条批量插入一次
if (i % 1000 == 0) {
userService.saveBatch(list);
list.clear();
}
}
long e = System.currentTimeMillis();
System.out.println("耗时:" + (e - b));
}
@Test
void testSaveOneByOne() { //速度慢,极其不推荐
long b = System.currentTimeMillis();
for (int i = 1; i <= 100000; i++) {
userService.save(buildUser(i));
}
long e = System.currentTimeMillis();
System.out.println("耗时:" + (e - b));
}
private User buildUser(int i) { //生成要插入的数值
User user = new User();
user.setUsername("user_" + i);
user.setPassword("123");
user.setPhone("" + (18688190000L + i));
user.setBalance(2000);
user.setInfo("{\"age\": 24, \"intro\": \"英文老师\", \"gender\": \"female\"}");
user.setCreateTime(new Date());
user.setUpdateTime(new Date());
return user;
}
4.分页查询
1.导入分页插件
@MapperScan("org.example.mapper") // 扫描mapper接口
public class Main {
public static void main(String[] args) {
System.out.println("Hello world!");
}
@Bean
public MybatisPlusInterceptor pageHelper(){
MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor(); //mybatis的所有插件都在这里里面
mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); //分页插件
return mybatisPlusInterceptor;
}
}
2.测试
@SpringBootTest
public class PageTest {
//分页测试
@Autowired
private UserMapper mapper;
@Test
public void testPage(){
Page<User> userPage = new Page<User>(1,3); //(页码,页容量)
Page<User> userPage1 = mapper.selectPage(userPage, null);
System.out.println(userPage1.getRecords()); //当前页数据
System.out.println(userPage1.getTotal()); //总记录数
System.out.println(userPage1.getCurrent());//当前页码
System.out.println(userPage1.getPages()); //当前页容量
System.out.println(userPage1.hasNext()); //是否有下一页
}
}
2.自定义分页
有时候我们想根据年龄分页查询,但方法中没有,这个时候我们可以自己定义
接口层
public interface UserMapper extends BaseMapper<User> {
IPage<User> selectPageOne(IPage<User> page,@Param("age") Integer age);
}
xml层
<mapper namespace="org.example.mappers.UserMapper">
<select id="selectPageOne" resultType="org.example.pojo.User">
select * from user where age =#{age}
</select>
</mapper>
测试
@Test
public void testPage2(){
Page<User> userPage = new Page<>(2, 3);
//这里userpage也获得了查询的数据
IPage<User> userIPage = mapper.selectPageOne(userPage, 18);
System.out.println(userIPage.getCurrent());
}
结果
5条件构造器
1.介绍
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("name", "John"); // 添加等于条件
queryWrapper.ne("age", 30); // 添加不等于条件
queryWrapper.like("email", "@gmail.com"); // 添加模糊匹配条件
等同于:
delete from user where name = "John" and age != 30
and email like "%@gmail.com%"
// 根据 entity 条件,删除记录
int delete(@Param(Constants.WRAPPER) Wrapper<T> wrapper);
使用MyBatis-Plus的条件构造器,你可以构建灵活、高效的查询条件,而不需要手动编写复杂的 SQL 语句。它提供了许多方法来支持各种条件操作符,并且可以通过链式调用来组合多个条件。这样可以简化查询的编写过程,并提高开发效率。
继承体系
Wrapper : 条件构造抽象类,最顶端父类
- AbstractWrapper : 用于查询条件封装,生成 sql 的 where 条件
- QueryWrapper : 查询/删除条件封装
- UpdateWrapper : 修改条件封装
- AbstractLambdaWrapper : 使用Lambda 语法
- LambdaQueryWrapper :用于Lambda语法使用的查询Wrapper
- LambdaUpdateWrapper : Lambda 更新封装Wrapper
2 组装条件
3.QueryWrapper代码练习
@SpringBootTest
public class QueryWrapperTest {
@Autowired
private UserMapper userMapper;
@Test
public void testQueryWrapper(){
//查询用户名包含a,年龄在20到30之间,并且邮箱不为null的用户信息
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.like("name", "a").between("age", 20, 30).isNotNull("email");
List<User> users = userMapper.selectList(wrapper);
System.out.println(users);
}
@Test
public void testQueryWrapper2(){
//按年龄降序查询用户,如果年龄相同则按id升序排列
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.orderByDesc("age").orderByAsc("id");
List<User> users = userMapper.selectList(wrapper);
System.out.println(users);
}
@Test
public void testQueryWrapper3(){
//删除email为空的用户
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.isNull("email");
int result = userMapper.delete(wrapper);
System.out.println(result);
}
@Test
public void testQueryWrapper4(){
//将年龄大于20并且用户名中包含有a或邮箱为null的用户信息修改
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.gt("age", 20).like("name", "a").or().isNull("email");
User user = new User();
user.setAge(18);
int result = userMapper.update(user, wrapper);
System.out.println(result);
}
@Test
public void testQueryWrapper5(){
//查询用户信息的name和age字段
QueryWrapper<User> wrapper = new QueryWrapper<>();
//selectMaps()返回Map集合列表,通常配合select()使用,避免User对象中没有被查询到的列值为null
wrapper.select("name", "age");
List<User> users = userMapper.selectList(wrapper);
System.out.println(users);
}
@Test
public void testQueryWrapper6(){
String name = "root";
int age = 18;
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
//判断条件拼接
//当name不为null拼接等于, age > 1 拼接等于判断
//方案1: 手动判断
// if (!StringUtils.isEmpty(name)){
// queryWrapper.eq("name",name);
// }
// if (age > 1){
// queryWrapper.eq("age",age);
// }
//方案2: 拼接condition判断
//每个条件拼接方法都condition参数,这是一个比较运算,为true追加当前条件!
//eq(condition,列名,值)
queryWrapper.eq(!StringUtils.isEmpty(name),"name",name)
.gt(age>1,"age",age); //如果为true则追加当前条件,这里就是 如果名字等于root并且年龄也等于root,年龄要大于18
List<User> users = userMapper.selectList(queryWrapper);
System.out.println(users);
}
}
4.UpdateWrapper代码练习
@SpringBootTest
public class UpdateWrapperTest {
@Autowired
private UserMapper userMapper;
@Test
public void testUpdate(){
//将年龄大于20并且用户名中包含有a或邮箱为null的用户信息修改
UpdateWrapper<User> wrapper = new UpdateWrapper<>();
wrapper.like("name","a").gt("age",20).or().isNull("email");
User user = new User();
user.setAge(22);
userMapper.update(user,wrapper);
}
@Test
public void testUpdate2(){
//将id = 3 的email设置为null, age = 18
UpdateWrapper<User> wrapper = new UpdateWrapper<>();
wrapper.eq(
"id",3).set("email",null).set("age",18);
int update = userMapper.update(null, wrapper);
System.out.println(update);
}
@Test
public void test4(){
// 将id为1,2,4的余额减去200
UpdateWrapper<User> wrapper = new UpdateWrapper<>();
wrapper.setSql("balance=balance-200")// 等于 set balance=balance-200
.in("id",1,2,4); //等于 where (id IN (?,?,?))
userMapper.update(null,wrapper);
}
}
如果要在mapper接口中自定义方法,而且方法中用到了wrapper,那名字必须设置为ew
void deductBalanceByIds(@Param("money") int money, @Param("ew") QueryWrapper<User> wrapper);
xml中这样用warpper
5.总结
使用queryWrapper + 实体类形式可以实现修改,但是无法将列值修改为null值!
使用updateWrapper可以随意设置列的值!!
queryWrapper主要负责sql中where后面的部分
updateWrapper主要负责sql中修改 的部分
两个搭配,完美结合
6.LambdaQueryWrapper(推荐)
6.1Lambda表达式
Lambda 表达式的语法可以分为以下几个部分:
1. 参数列表: 参数列表用小括号 `()` 括起来,可以指定零个或多个参数。如果没有参数,可以省略小括号;如果只有一个参数,可以省略小括号。
示例:`(a, b)`, `x ->`, `() ->`
2. 箭头符号:箭头符号 `->` 分割参数列表和 Lambda 表达式的主体部分。示例:`->`
3. Lambda 表达式的主体: Lambda 表达式的主体部分可以是一个表达式或一个代码块。如果是一个表达式,可以省略 return 关键字;如果是多条语句的代码块,需要使用大括号 `{}` 括起来,并且需要明确指定 return 关键字。示例:
- 单个表达式:`x -> x * x`
- 代码块:`(x, y) -> { int sum = x + y; return sum; }`
具体如下
// 使用 Lambda 表达式实现一个接口的方法
interface Greeting {
void sayHello();
}
public class LambdaExample {
public static void main(String[] args) {
//原始匿名内部类方式
Greeting greeting = new Greeting() {
@Override
public void sayHello(int a) {
System.out.println("Hello, world!");
}
};
a->System.out.println("Hello, world!")
// 使用 Lambda 表达式实现接口的方法
greeting = () -> System.out.println("Hello, world!");
System.out::println;
() -> 类.XXX(); -> 类::方法名
// 调用接口的方法
greeting.sayHello();
}
}
方法
方法引用是 Java 8 中引入的一种语法特性,它提供了一种简洁的方式来直接引用已有的方法或构造函数。方法引用可以替代 Lambda 表达式,使代码更简洁、更易读。
Java 8 支持以下几种方法引用的形式:
1. **静态方法引用:** 引用静态方法,语法为 `类名::静态方法名`。
2. **实例方法引用:** 引用实例方法,语法为 `实例对象::实例方法名`。
3. **对象方法引用:** 引用特定对象的实例方法,语法为 `类名::实例方法名`。
4. **构造函数引用:** 引用构造函数,语法为 `类名::new`。
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
public class MethodReferenceExample {
public static void main(String[] args) {
List<String> names = Arrays.asList("John", "Tom", "Alice");
// 使用 Lambda 表达式
names.forEach(name -> System.out.println(name));
// 使用方法引用
names.forEach(System.out::println);
}
}
6.2lambdaQueryWrapper使用案例:
@Test
public void testQuick4(){
String name = "root";
int age = 18;
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
//每个条件拼接方法都condition参数,这是一个比较运算,为true追加当前条件!
//eq(condition,列名,值)
queryWrapper.eq(!StringUtils.isEmpty(name),"name",name)
.eq(age>1,"age",age);
//TODO: 使用lambdaQueryWrapper
LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
//注意: 需要使用方法引用
//技巧: 类名::方法名
lambdaQueryWrapper.eq(!StringUtils.isEmpty(name), User::getName,name);
List<User> users= userMapper.selectList(lambdaQueryWrapper);
System.out.println(users);
}
6.3lambdaUpdateWrapper使用
@Test
public void testQuick2(){
UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
//将id = 3 的email设置为null, age = 18
updateWrapper.eq("id",3)
.set("email",null) // set 指定列和结果
.set("age",18);
//使用lambdaUpdateWrapper
LambdaUpdateWrapper<User> updateWrapper1 = new LambdaUpdateWrapper<>();
updateWrapper1.eq(User::getId,3)
.set(User::getEmail,null)
.set(User::getAge,18);
//如果使用updateWrapper 实体对象写null即可!
int result = userMapper.update(null, updateWrapper);
System.out.println("result = " + result);
}
插入10000条数据,看哪个速度快
7.注解使用
@TableName注解
表名注解,标识实体类对应的表
使用位置:实体类
@TableName("sys_user") //对应数据库表名 public class User { private Long id; private String name; private Integer age; private String email; }
特殊情况:如果表名和实体类名相同(忽略大小写)可以省略该注解!
其他解决方案:全局设置前缀
mybatis-plus: # mybatis-plus的配置 global-config: db-config: table-prefix: sys_ # 表名前缀字符串
@TableId 注解
主键注解
使用位置:实体类主键字段
@TableName("sys_user") public class User { @TableId(value="主键列名",type=主键策略) private Long id; private String name; private Integer age; private String email; }
属性 类型 必须指定 默认值 描述 value String 否 "" 主键字段名 type Enum 否 IdType.NONE 指定主键类型
IdType属性可选值:
值 描述 AUTO 数据库 ID 自增 (mysql配置主键自增长) ASSIGN_ID(默认) 分配 ID(主键类型为 Number(Long )或 String)(since 3.3.0),使用接口 IdentifierGenerator
的方法nextId
(默认实现类为DefaultIdentifierGenerator
雪花算法)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
在以下场景下,添加`@TableId`注解是必要的:
1. 实体类的字段与数据库表的主键字段不同名:如果实体类中的字段与数据库表的主键字段不一致,需要使用`@TableId`注解来指定实体类中表示主键的字段。
2. 主键生成策略不是默认策略:如果需要使用除了默认主键生成策略以外的策略,也需要添加`@TableId`注解,并通过`value`属性指定生成策略。
1. 雪花算法使用场景
雪花算法(Snowflake Algorithm)是一种用于生成唯一ID的算法。它由Twitter公司提出,用于解决分布式系统中生成全局唯一ID的需求。
在传统的自增ID生成方式中,使用单点数据库生成ID会成为系统的瓶颈,而雪花算法通过在分布式系统中生成唯一ID,避免了单点故障和性能瓶颈的问题。
雪花算法生成的ID是一个64位的整数,由以下几个部分组成:
1. 时间戳:41位,精确到毫秒级,可以使用69年。
2. 节点ID:10位,用于标识分布式系统中的不同节点。
3. 序列号:12位,表示在同一毫秒内生成的不同ID的序号。通过将这三个部分组合在一起,雪花算法可以在分布式系统中生成全局唯一的ID,并保证ID的生成顺序性。
雪花算法的工作方式如下:
1. 当前时间戳从某一固定的起始时间开始计算,可以用于计算ID的时间部分。
2. 节点ID是分布式系统中每个节点的唯一标识,可以通过配置或自动分配的方式获得。
3. 序列号用于记录在同一毫秒内生成的不同ID的序号,从0开始自增,最多支持4096个ID生成。需要注意的是,雪花算法依赖于系统的时钟,需要确保系统时钟的准确性和单调性,否则可能会导致生成的ID不唯一或不符合预期的顺序。
雪花算法是一种简单但有效的生成唯一ID的算法,广泛应用于分布式系统中,如微服务架构、分布式数据库、分布式锁等场景,以满足全局唯一标识的需求。
你需要记住的: 雪花算法生成的数字,需要使用Long 或者 String类型主键!!
@TableField
一般情况下我们并不需要给字段添加
@TableField
注解,一些特殊情况除外:
成员变量名与数据库字段名不一致
成员变量是以
isXXX
命名,按照JavaBean
的规范,MybatisPlus
识别字段时会把is
去除,这就导致与数据库不符。成员变量名与数据库一致,但是与数据库的关键字冲突。使用
@TableField
注解给字段名添加转义字符:``
@TableName("sys_user") public class User { @TableId private Long id; @TableField("nickname") private String name; private Integer age; private String email; }
属性 类型 必须指定 默认值 描述
value String 否 "" 数据库字段名
exist boolean 否 true 是否为数据库表字段MyBatis-Plus会自动开启驼峰命名风格映射!!!
8.Mybatis高级扩展
8.1逻辑删除
8.1.0介绍
逻辑删除,可以方便地实现对数据库记录的逻辑删除而不是物理删除。逻辑删除是指通过更改记录的状态或添加标记字段来模拟删除操作,从而保留了删除前的数据,便于后续的数据分析和恢复。
- 物理删除:真实删除,将对应数据从数据库中删除,之后查询不到此条被删除的数据
- 逻辑删除:假删除,将对应数据中代表是否被删除字段的状态修改为“被删除状态”,之后在数据库中仍旧能看到此条数据记录
数据库和实体类添加逻辑删除字段
表添加逻辑删除字段
ALTER TABLE USER ADD deleted INT DEFAULT 0 ; # int 类型 1 逻辑删除 0 未逻辑删除
实体类添加逻辑删除属性(这种也称为单一指定)
@Data
public class User {
// @TableId
private Integer id;
private String name;
private Integer age;
private String email;
@TableLogic
//逻辑删除字段 int mybatis-plus下,默认 逻辑删除值为1 未逻辑删除 0
private Integer deleted;
}
全局指定
mybatis-plus:
global-config:
db-config:
logic-delete-field: deleted # 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2) #deleted是实体类和实体表中字段或属性名称
logic-delete-value: 1 # 逻辑已删除值(默认为 1)
logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
8.1.2逻辑删除代码实现
首先,这是我们的数据库
这是我们的代码
@Test
public void testDelete(){
int result = userMapper.deleteById(7L);
System.out.println(result);
}
开始执行
效果
逻辑删除就是假删除
public interface UserMapper extends BaseMapper<User> {
//逻辑回复
int updatehf(User user);
int updatelljhf(User user);
}
这个时候我们查询一下,看看能不能查询到(这里是查询所有)
代码加运行结果(已经查询不到它了)
8.1.3逻辑删除恢复
mybatis plus自身的修改是无法恢复逻辑删除的
我们需要自定义方法与操作
接口(这里的两个方法都可以实现逻辑删除的恢复)
public interface UserMapper extends BaseMapper<User> {
//逻辑回复
int updatehf(User user);
int updatelljhf(User user);
}
<update id="updatehf">
update user set deleted=#{deleted} where id=#{id}
</update>
<update id="updatelljhf">
update user
<set>
<if test="deleted != null ">
deleted=#{deleted},
</if>
<if test="name != null and name != ''">
name=#{name},
</if>
<if test="age != null and age != ''">
age=#{age},
</if>
<if test="email != null and email != ''">
email=#{email},
</if>
</set>
where id=#{id}
</update>
测试一下
@Test
public void testDeleteByMap(){
User user = new User();
user.setId(7l);
user.setDeleted(0);
user.setEmail("132@1633112.com");
int updatelljhf = userMapper.updatelljhf(user);
System.out.println(updatelljhf);
// int updatehf = userMapper.updatehf(user);
// System.out.println(updatehf);
// userMapper.updateById(user);
}
已经恢复了 ,我们在查询一下,看能不能查到它
乐观锁悲观锁
乐观锁和悲观锁是在并发编程中用于处理并发访问和资源竞争的两种不同的锁机制!!
悲观锁:
悲观锁的基本思想是,在整个数据访问过程中,将共享资源锁定,以确保其他线程或进程不能同时访问和修改该资源。悲观锁的核心思想是"先保护,再修改"。在悲观锁的应用中,线程在访问共享资源之前会获取到锁,并在整个操作过程中保持锁的状态,阻塞其他线程的访问。只有当前线程完成操作后,才会释放锁,让其他线程继续操作资源。这种锁机制可以确保资源独占性和数据的一致性,但是在高并发环境下,悲观锁的效率相对较低。乐观锁:
乐观锁的基本思想是,认为并发冲突的概率较低,因此不需要提前加锁,而是在数据更新阶段进行冲突检测和处理。乐观锁的核心思想是"先修改,后校验"。在乐观锁的应用中,线程在读取共享资源时不会加锁,而是记录特定的版本信息。当线程准备更新资源时,会先检查该资源的版本信息是否与之前读取的版本信息一致,如果一致则执行更新操作,否则说明有其他线程修改了该资源,需要进行相应的冲突处理。乐观锁通过避免加锁操作,提高了系统的并发性能和吞吐量,但是在并发冲突较为频繁的情况下,乐观锁会导致较多的冲突处理和重试操作。理解点: 悲观锁和乐观锁是两种解决并发数据问题的思路,不是具体技术!!!
具体技术和方案:
1. 乐观锁实现方案和技术:
- 版本号/时间戳:为数据添加一个版本号或时间戳字段,每次更新数据时,比较当前版本号或时间戳与期望值是否一致,若一致则更新成功,否则表示数据已被修改,需要进行冲突处理。
- CAS(Compare-and-Swap):使用原子操作比较当前值与旧值是否一致,若一致则进行更新操作,否则重新尝试。
- 无锁数据结构:采用无锁数据结构,如无锁队列、无锁哈希表等,通过使用原子操作实现并发安全。
2. 悲观锁实现方案和技术:
- 锁机制:使用传统的锁机制,如互斥锁(Mutex Lock)或读写锁(Read-Write Lock)来保证对共享资源的独占访问。
- 数据库锁:在数据库层面使用行级锁或表级锁来控制并发访问。
- 信号量(Semaphore):使用信号量来限制对资源的并发访问。
介绍版本号乐观锁技术的实现流程:
- 每条数据添加一个版本号字段version
- 取出记录时,获取当前 version
- 更新时,检查获取版本号是不是数据库当前最新版本号
- 如果是[证明没有人修改数据], 执行更新, set 数据更新 , version = version+ 1
- 如果 version 不对[证明有人已经修改了],我们现在的其他记录就是失效数据!就更新失败
使用mybatis-plus数据使用乐观锁
1.添加版本号更新插件
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); //分页插件
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());//乐观锁插件
return interceptor;
}
2.数据库表中插入乐观锁字段
ALTER TABLE USER ADD VERSION INT DEFAULT 1 ; # int 类型 乐观锁字段
- 支持的数据类型只有:int,Integer,long,Long,Date,Timestamp,LocalDateTime
- 仅支持 `updateById(id)` 与 `update(entity, wrapper)` 方法
3.对应的实体类中也加入
@Version
private Integer version;
4.测试
@Test
public void testQuick7(){
//步骤1: 先查询,在更新 获取version数据
//同时查询两条,但是version唯一,最后更新的失败
User user = userMapper.selectById(5);
User user1 = userMapper.selectById(5);
user.setAge(20);
user1.setAge(30);
userMapper.updateById(user);
//乐观锁生效,失败!
userMapper.updateById(user1);
}
防止全表更新和删除
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); //分页插件
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());//乐观锁插件
interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());//防止全表更新删除插件
return interceptor;
}
逆向工程
1.idea连接数据库
2.使用
可以快速帮你创建service,mapper,实体类等实体类接口
Mybatis X
可以帮助你自动生成一些对数据库的操作
Mybatis X 插件 | MyBatis-Plus (baomidou.com)https://baomidou.com/guides/mybatis-x/
练习中遇到的问题
1.selectById找不到问题
解决方式:这个时候需要在对应的实体类属性主键上加 @TableId注解