注意!mybatisPlus只能够进行单表操作,其他的仍需要mybatis
1.快速入门
编写启动类
java">@MapperScan("com.atguigu.mapper")
@SpringBootApplication
public class MainApplication {public static void main(String[] args) {SpringApplication.run(MainApplication.class,args);}}
编写实体类
java">@Data
public class User {private Long id;private String name;private Integer age;private String email;
}
编写mapper接口
在mybatis中我们只需要继承一个baseMapper接口就可以了,我们可以看到在baseMapper接口中已经帮你编写好了对应的增删改查的方法,你直接使用就可以了,注意在对应的泛型类接口中写入你想要增删改查的pojo类
java">public interface UserMapper extends BaseMapper<User> {}
mybatis-plus的核心功能
基于baseMapper的CRUD方法
1.insert方法
通过insert来插入一个用户数据,mybatis只提供了这一个方法
2.Delete方法
在Delete中接口提供了四种删除方法
1.根据id删除
2.根据Map进行删除,其中map删除将你传入的对象数值和条件放入map对应的代码书写如图所示
3.根据条件删除,将条件封装到wrapper对象中
4.根据传入的ids数组进行批量的id读取并删除
3.update方法
update方法
updateById方法
其使用方法如下,如果传入的一个实体类参数为空那么就不进行修改,这就是为什么在实体类设计中我们需要将变量变成包装类而不是基本的数据类型
因为包装类当没有进行初始化时,其值为空
但如果某个变量用的是非包装类,其没有初始化时比如int类型id,其默认值是0
那么此时你实例化一个user类进行update方法,就会将所有值设为0
4.select方法
有如下方法
java">// 根据 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);
service层的CRUD方法
1.首先在service层中的接口实现IService接口,并将你需要的实体类泛型传入service接口中
接着我们在对应service的实现类中继承ServiceImpl接口并在泛型中传入我们的mapper层和对应的实体类
为什么需要传入mapper层呢?因为在底层我们还是使用的Mapper的接口来进行的数据库操作,并且因为IService中有部分方法没有进行实现,所以我们还需要继承ServiceImpl这个父类,因为这个父类实现了IService接口的一些没有实现的抽象方法
1.插入多条数据
使用saveBatch方法,将传入的一个列表插入到数据库中,使用方法如下
2.更新方法saveOrUpdate()
当传入的类没有id值的时候会视作一个插入的方法
当传入的id存在时就会变成一个更新数据的方法
还有一个是直接根据id进行更新的
3.删除操作
4.查询操作
分页查询
1.添加mybatis-plus插件,能够添加分页查询插件等功能
代码如下,要指定你所使用的数据库
2.代码实现
在Mapper层中mybatisplus提供了一个selectPage方法,可以将对应的page传入
page对象会封装返回的结果,我可以通过page对象获取数据
自定义分页查询
以查询年龄大于1的人举例
1.首先在对应的mapper类中写一个分页查询的方法
要点是返回的数据类型和其中一个参数是Ipage<>其中泛型是你想要查询的实体类
2.在xml文件中将sql语句书写,结尾不能加分号,因为会影响字符串拼接
3.service使用代码如下
其中yaml里面还可以通过type-aliases-pakage指定xml文件中的缩写
条件构造器
在所有的mybatis-plus接口中,所有的方法CRUD方法都存在一个Wrapper这样的条件拼接对象
使用方法:
java">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 != 30and email like "%@gmail.com%"
// 根据 entity 条件,删除记录
int delete(@Param(Constants.WRAPPER) Wrapper<T> wrapper);
继承关系以及各个wrapper类的使用场景
QueryWrapper
组装查询条件
其函数和代表的sql语句写在了下方
其中还能写一个链式调用
组装排序条件:
组装删除条件
组装and和or条件
条件间默认的拼接使用and,如果想使用or就需要使用.or()关键字
指定列查询:
默认是查询所有列,如果没有使用select方法的指定列的话
条件语句示例
使用场景:当我们实际开发中会接收前端传入的参数,我们需要在service层进行一些判断,比如传入的参数是否为空,或者满足什么条件
正常我们使用业务代码会如下所示,需要手写if条件的判断语句,非常麻烦
所以mybatis-plus提供了一个更加方便的方法
可以直接在对应的sql方法中在前面添加一些条件,当满足时才会执行sql语句,不满足时就不会执行
updateWrapper
我们先观察QueryWrapper,他想要修改数据库中的数据的时候需要创建一个实体类,并将实体类传入对应的sql方法中
并且实体类中为空的属性不修改,当你想要将某个数据修改为空时不适合使用QueryWrapper
所以我们使用updateWrapper,它可以将数据修改为空,并且不用创建实体类传入sql方法中
updateWrapper还提供了.set()方法,能够直接将你想要修改的数据进行修改而不用创建实体类
LambdaUpdateWrapper
它存在的作用是通过方法名获取对应的列名
利用Java8的特性,使用匿名函数返回值变量的名称
能够防止自己手写字符串手写错误
核心注解使用:
@TableName
我们在baseMapper泛型中制定了我们要查询的实体类类型,他会根据实体类的名称去查询表名,当实体类和表名称一致时才可以找到
假如实体类名和表名称不一致时就需要@TableName注解指定对应的表名(忽略大小写)
但是假如当多个表名称都不同时,比如都存在一个t_前缀,我们一个一个实体类去添加注解会很麻烦
所有我们有另一种方法
在yaml文件中书写如下配置指定表前缀
java">mybatis-plus: # mybatis-plus的配置global-config:db-config:table-prefix: sys_ # 表名前缀字符串
@TableId
1.当主键列名和表名不一致时
2.指定主键的策略
使用方法如下
java">@TableName("sys_user")
public class User {@TableId(value="主键列名",type=主键策略)private Long id;private String name;private Integer age;private String email;
}
type的可选值如下
测试代码如下
在默认没有指定的情况下其id的生成是雪花算法
我们可以通过在yaml文件中指定主键的分配策略,比如下面的代码就是auto
java">mybatis-plus:configuration:# 配置MyBatis日志log-impl: org.apache.ibatis.logging.stdout.StdOutImplglobal-config:db-config:# 配置MyBatis-Plus操作表的默认前缀table-prefix: t_# 配置MyBatis-Plus的主键策略id-type: auto
注意当我们使用自增长策略时,我们的数据库中的表中也要有自增长这一属性,否则会报错
TableId关键点总结
雪花算法应用场景
在一个大型的分布式系统中,当数据量非常大时,我们通常会将一个表拆分成多个表
有两种拆分方式:
1.纵向拆分,根据数据的访问频率分为冷热数据进行访问
2.横向拆分,直接将表拆分为多个表进行访问
这些方式都存在一个问题,假设我们采用自增长主键,当自增长到一点限度的时候,会自增长到别的表中
比如有三个表,其id范围分别是1-9, 10-19, 20-29当表1装满时,再次插入一条数据就会变成10,而第二个表中的数据的第一个又是10,所以会出现主键重复的问题,所以雪花算法就是为了解决这种问题而出现
雪花算法是一种简单但有效的生成唯一ID的算法,广泛应用于分布式系统中,如微服务架构、分布式数据库、分布式锁等场景,以满足全局唯一标识的需求。
**你需要记住的: 雪花算法生成的数字,需要使用Long 或者 String类型主键!!**
@TableField
当实体类变量名和数据库列名不一致时使用该注解指定
java">@TableName("sys_user")
public class User {@TableIdprivate Long id;@TableField("nickname")private String name;private Integer age;private String email;
}
逻辑删除
逻辑删除,可以方便地实现对数据库记录的逻辑删除而不是物理删除。逻辑删除是指通过更改记录的状态或添加标记字段来模拟删除操作,从而保留了删除前的数据,便于后续的数据分析和恢复。
- 物理删除:真实删除,将对应数据从数据库中删除,之后查询不到此条被删除的数据
- 逻辑删除:假删除,将对应数据中代表是否被删除字段的状态修改为“被删除状态”,之后在数据库中仍旧能看到此条数据记录
**逻辑删除实现:**
1. 数据库和实体类添加逻辑删除字段
1. 表添加逻辑删除字段可以是一个布尔类型、整数类型或枚举类型。
```SQL
ALTER TABLE USER ADD deleted INT DEFAULT 0 ; # int 类型 1 逻辑删除 0 未逻辑删除
```
2. 实体类添加逻辑删除属性```SQL
@Data
public class User {// @TableId
private Integer id;
private String name;
private Integer age;
private String email;
@TableLogic
//逻辑删除字段 int mybatis-plus下,默认 逻辑删除值为1 未逻辑删除 1
private Integer deleted;
}```
2. 指定逻辑删除字段和属性值
1. 单一指定```SQL
@Data
public class User {// @TableId
private Integer id;
private String name;
private Integer age;
private String email;
@TableLogic
//逻辑删除字段 int mybatis-plus下,默认 逻辑删除值为1 未逻辑删除 1
private Integer deleted;
}
```
2. 全局指定```YAML
mybatis-plus:
global-config:
db-config:
logic-delete-field: deleted # 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2)
logic-delete-value: 1 # 逻辑已删除值(默认为 1)
logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
```
3. 演示逻辑删除操作> 逻辑删除以后,没有真正的删除语句,删除改为修改语句!
删除代码:
```Java
//逻辑删除
@Test
public void testQuick5(){
//逻辑删除
userMapper.deleteById(5);
}
```执行效果:
JDBC Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@5871a482] will not be managed by Spring
==> Preparing: UPDATE user SET deleted=1 WHERE id=? AND deleted=0
==> Parameters: 5(Integer)
<== Updates: 1
4. 测试查询数据```Java
@Test
public void testQuick6(){
//正常查询.默认查询非逻辑删除数据
userMapper.selectList(null);
}//SELECT id,name,age,email,deleted FROM user WHERE deleted=0
```
并发问题
乐观锁和悲观锁
乐观锁和悲观锁是在并发编程中用于处理并发访问和资源竞争的两种不同的锁机制!!
悲观锁:
悲观锁的基本思想是,在整个数据访问过程中,将共享资源锁定,以确保其他线程或进程不能同时访问和修改该资源。悲观锁的核心思想是"先保护,再修改"。在悲观锁的应用中,线程在访问共享资源之前会获取到锁,并在整个操作过程中保持锁的状态,阻塞其他线程的访问。只有当前线程完成操作后,才会释放锁,让其他线程继续操作资源。这种锁机制可以确保资源独占性和数据的一致性,但是在高并发环境下,悲观锁的效率相对较低。乐观锁:
乐观锁的基本思想是,认为并发冲突的概率较低,因此不需要提前加锁,而是在数据更新阶段进行冲突检测和处理。乐观锁的核心思想是"先修改,后校验"。在乐观锁的应用中,线程在读取共享资源时不会加锁,而是记录特定的版本信息。当线程准备更新资源时,会先检查该资源的版本信息是否与之前读取的版本信息一致,如果一致则执行更新操作,否则说明有其他线程修改了该资源,需要进行相应的冲突处理。乐观锁通过避免加锁操作,提高了系统的并发性能和吞吐量,但是在并发冲突较为频繁的情况下,乐观锁会导致较多的冲突处理和重试操作。
打个比方:以上厕所举例
悲观锁:上厕所时(访问数据),当一个人进去厕所就会锁门,别的人必须要在外面等待你上完然后释放锁才能用你的厕所,上锁和放锁的过程需要时间
乐观锁:上厕所时,我们不给厕所加上锁,当有人想要上厕所时直接开门进去,如果没人则上厕所,如果厕所有人则退出来,然后再开门进行判定是否还有人使用厕所,节省了上锁解锁的时间
在高并发场景下,乐观锁效率会提高特别多
乐观锁实现
1. 乐观锁实现方案和技术:
- 版本号/时间戳:为数据添加一个版本号或时间戳字段,每次更新数据时,比较当前版本号或时间戳与期望值是否一致,若一致则更新成功,否则表示数据已被修改,需要进行冲突处理。
- CAS(Compare-and-Swap):使用原子操作比较当前值与旧值是否一致,若一致则进行更新操作,否则重新尝试。
- 无锁数据结构:采用无锁数据结构,如无锁队列、无锁哈希表等,通过使用原子操作实现并发安全。
版本号的意思是,假设有ab两个进程取了一个数据,每个取出来的数据都携带一个版本号(时间截)
此时ab两个进程版本号都是1,当a对数据进行修改后对版本号进行了更新,此时版本号是2,然后当b进程修改完数据后一比对发现版本号对不上了,此时就会重新拿取数据进行修改
即每次修改都会检查版本号是否是最新的版本号
代码实现
我们首先将乐观锁的插件放在启动器类里面,以让他添加到ioc容器中
代码如下
然后再数据库中添加对应的字段
ALTER TABLE USER ADD VERSION INT DEFAULT 1 ; # int 类型 乐观锁字段
再实体类中加入一个@Version注解表明这个变量是一个版本号就可以了
java">@Version
private Integer version;
代码实现
java">//演示乐观锁生效场景
@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);
}
防全表更新和删除
防止删库跑路(滑稽)
添加对应的mybatis-plus插件,同样在启动类中添加
java">@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());return interceptor;
}
}
示例如下所示