目录
二.自定义一个类继承ServiceImpl类和实现自定义的接口
[编辑 配置MP让其走逻辑删除](#编辑 配置MP让其走逻辑删除)
三.在实体类中添加乐观锁字段对应的成员变量(使用@Version注解)
MybatisPlus概述
MyBatis-Plus(简称 MP)是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生(提供了快速使用mybatis的方式)。
官网:MyBatis-Plus 或 Redirect
MybatisPlus特性
- 无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
- 损耗小:启动即会自动注入基本 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 操作智能分析阻断,也可自定义拦截规则,预防误操作
MP快速入门
第一步:数据库准备
sql
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;
第二步:导入依赖
html
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--mybatisplus起步依赖-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
</dependencies>
第三步:在application配置文件中编写数据源配置,连接数据库,并让sql语句打印在终端
html
#配置数据源
spring:
datasource:
username: root
password: 1234
url: jdbc:mysql://192.168.230.130:3306/mp_db?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC&useSSL=false
driver-class-name: com.mysql.cj.jdbc.Driver
# 设置mp运行时行为
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
第三步:编写实体类
java
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@TableName("tb_user")//配置该实体类对于数据中的数据表,如果类名和表名一样,可以不用配置
public class User {
private Long id;
private String userName;
private String password;
private String name;
private Integer age;
private String email;
}
第四步:编写mapper接口
java
/**
* 编写接口继承BaseMapper接口,
* 其中泛型是是与数据表对应的entity类,会根据泛型类的@TableName注解值获取要操作的数据表
*/
@Mapper
public interface UserMapper extends BaseMapper<User> {
}
第五步:测试
java
@SpringBootTest
public class TestUserMapper {
@Autowired
private UserMapper userMapper;
@Test
public void test01(){
User user = userMapper.selectById(1L);
System.out.println(user);
}
}
可以发现usrName成员变量已经自动驼峰映射成use_name,因为mp默认开启驼峰映射
MP实现增删查改功能
插入功能
注意事项:
1.如果主键对应的实体类属性中没有设置主键的生成策略,那么MP自动使用雪花算法为主键生成值,且回填到实体对象下;
2.如果未指定主键生成策略,即使表的主键是主键自增,也不会使用主键自增,因为默认的生成策略是使用算法算法生成一个主键id;
测试
java
@Test
public void test02(){
User user=User.builder()
.userName("hhh")
.email("153@qq.com")
.age(18)
.name("a")
.password("123")
.build();
int insert = userMapper.insert(user);
System.out.println(insert);
}
可以看到主键id是使用雪花算法生成的一个Long型id,即使我们已经为id主键设置了主键自增
并且这个生成的主键id已经回填到了实体类中
主键属性使用的注解@TableId
使用注解@TableId指定id生成策略,让其使用主键自增,而不是使用雪花算法生成一个不重复的Long型id
java
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@TableName("tb_user")//配置该实体类对于数据中的数据表,如果类名和表名一样,可以不用配置
public class User {
/**
* value属性值是如果这个类的成员变量名字与数据库字段名不一致,且不符合驼峰映射规则,就可以使用value属性值进行赋值
* 让这个主键id使用主键自增策略,默认是使用雪花算法生成一个不重复Long型id
*/
@TableId(value = "id",type = IdType.AUTO)
private Long id;
private String userName;
private String password;
private String name;
private Integer age;
private String email;
}
主键生成策略
MP提供的常用主键生成策略如下:
生成策略 | 应用场景 | 特点 |
---|---|---|
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类型的,可不可用 |
普通列注解-@TableField
注解@TableField作用:
- 指定表中普通字段与实体类属性之间的映射关系;
- 忽略实体类中多余属性与表中字段的映射关系(
@TableField(exist = false)
);
以下情况可以省略:
- 名称一样
- 数据库字段使用_分割,实体类属性名使用驼峰名称(自动开启驼峰映射)
1.成员变量名和数据库字段名名字不一样,且不符合驼峰映射时
java
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@TableName("tb_user")//配置该实体类对于数据中的数据表,如果类名和表名一样,可以不用配置
public class User {
/**
* value属性值是如果这个类的成员变量名字与数据库字段名不一致,且不符合驼峰映射规则,就可以使用value属性值进行赋值
* 让这个主键id使用主键自增策略,默认是使用雪花算法生成一个不重复Long型id
*/
@TableId(value = "id",type = IdType.AUTO)
private Long id;
/**
* 使用@TableField注解让这个成员变量与字段进行映射
*/
@TableField(value = "user_name")
private String realName;
private String password;
private String name;
private Integer age;
private String email;
}
可以看到自动生成的sql语句使用了AS关键字
2.类的成员变量不需要进行映射,即没有匹配的数据库字段名
java
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@TableName("tb_user")//配置该实体类对于数据中的数据表,如果类名和表名一样,可以不用配置
public class User {
/**
* value属性值是如果这个类的成员变量名字与数据库字段名不一致,且不符合驼峰映射规则,就可以使用value属性值进行赋值
* 让这个主键id使用主键自增策略,默认是使用雪花算法生成一个不重复Long型id
*/
@TableId(value = "id",type = IdType.AUTO)
private Long id;
/**
* 使用@TableField注解让这个成员变量与字段进行映射
*/
@TableField(value = "user_name")
private String realName;
private String password;
private String name;
private Integer age;
private String email;
/**
* 让这个成员变量不进行sql语句的赋值
*/
@TableField(exist = false)
private String address;
}
可以看到address成员变量并没有在自动生成的sql语句里显示
删除功能
1.根据id删除
java
int i = userMapper.deleteById(8L);
2.根据id批量删除
java
int i = userMapper.deleteBatchIds(Arrays.asList(10L, 11));
3.根据map构造条件,删除
java
Map<String, Object> map = new HashMap<>();
//delete from tb_user where user_name = ? and age = ?
map.put("user_name","itcast");
map.put("age","18");
userMapper.deleteByMap(map);
修改功能
java
@Test
public void test04(){
User user=User.builder()
.id(2L)
.password("1277")
.build();
//根据主键id来修改数据库的信息,如果这个对象的成员变量值为null,就不修改数据库的字段名
int i = userMapper.updateById(user);
System.out.println(i);
}
查询功能
MP实现分页查询
MP的分页拦截器并没有自动装配,所以需要我们在配置类自己维护一个分页拦截器的bean
java
@Configuration
public class MybatisPlusConfig {
/**
* 用于批量注册mp的插件bean
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
//构建批量注册插件的对象
MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
//构建分页插件对象,并指定mysql数据库
PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);
//配置每页的大小,-1为无上限
paginationInnerInterceptor.setMaxLimit(-1L);
//将分页插件注册
mybatisPlusInterceptor.addInnerInterceptor(paginationInnerInterceptor);
return mybatisPlusInterceptor;
}
}
测试
java
@Test
public void test05(){
int page=2;
int pageSize=4;
//构建分页对象
IPage<User> pageInfo = new Page<>(page, pageSize);
//pageInfo==userIPage-->true,这两个对象是同一个,地址相同
//null为无条件查询
IPage<User> userIPage = userMapper.selectPage(pageInfo, null);
System.out.println(pageInfo==userIPage);
//获取分页参数
long total = userIPage.getTotal();//获取总记录数
long pages = userIPage.getPages();//获取总页数
long size = userIPage.getSize();//获取当前页大小
long current = userIPage.getCurrent();//获取当前的页数
List<User> users = userIPage.getRecords();//获取当前页的对象集合
}
改进:只使用一个分页对象
java
@Test
public void test05(){
int page=2;
int pageSize=4;
//构建分页对象
IPage<User> pageInfo = new Page<>(page, pageSize);
//pageInfo==userIPage-->true,这两个对象是同一个,地址相同
//null为无条件查询
// IPage<User> userIPage = userMapper.selectPage(pageInfo, null);
//TODO:因为这两是同一个对象,所以可以直接不定义一个新对象
userMapper.selectPage(pageInfo,null);
//获取分页参数
long total =pageInfo.getTotal();//获取总记录数
long pages =pageInfo.getPages();//获取总页数
long size = pageInfo.getSize();//获取当前页大小
long current= pageInfo.getCurrent();//获取当前的页数
List<User> users =pageInfo.getRecords();//获取当前页的对象集合
}
QueryWrapper实现基础查询
QueryWrapper常用API
javaeq( ) : 等于 = ne( ) : 不等于 <> 或者 != gt( ) : 大于 > ge( ) : 大于等于 >= lt( ) : 小于 < le( ) : 小于等于 <= between ( ) : BETWEEN 值1 AND 值2 notBetween ( ) : NOT BETWEEN 值1 AND 值2 in( ) : in notIn( ) :not in like() : like 模糊查询,左右都加% RightLike() :只有右边加% LeftLike():只有左边加%
sql中反向查询eg:not like != 等等,查询时是不会走索引的;
所以尽量不使用not
基础查询实现
要求:查询用户中姓名包含"伤",密码为"123456",且年龄为19或者25或者29,查询结果按照年龄降序排序;
java
@Test
public void test06(){
//要求:查询用户中姓名包含"伤",密码为"123456",且年龄为19或者25或者29,查询结果按照年龄降序排序;
QueryWrapper<User> wrapper = new QueryWrapper<>();
//设置查询条件
//TODO:条件之间默认使用and关键字
//第一个参数是数据库字段名
wrapper.like("user_name","伤")
.eq("password","123456")
.in("age",Arrays.asList(19,25,29))
.orderByDesc("age");
List<User> users = userMapper.selectList(wrapper);
}
OR查询
查询用户名为"lisi"或者年龄大于23的用户信息;
java
@Test
public void test07(){
//"lisi"或者年龄大于23的用户信息;
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("user_name","lisi")
.or()//使用or关键字关联条件,默认失and
.gt("age",23);
List<User> users = userMapper.selectList(wrapper);
}
QueryWrapper模糊查询like
模糊查询常用方法
- like("表列名","条件值"); 作用:查询包含关键字的信息,底层会自动添加匹配关键字,比如:%条件值%
- likeLeft("表列名","条件值"); 作用:左侧模糊搜索,也就是查询以指定条件值结尾的数据,比如:%条件值
- likeRight("表列名","条件值");作用:右侧模糊搜索,也就是查询以指定条件值开头的数据,比如:条件值%
左边模糊
java
@Test
public void test08(){
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.likeLeft("user_name","二");
List<User> users = userMapper.selectList(wrapper);
}
QueryWrapper排序查询
核心方法
- orderByAsc 升序排序,方法内可传入多个字段
- orderByDesc 降序排序,方法内可传入多个字段
QueryWrapper限定字段查询
MP查询时,默认将表中所有字段数据映射查询,但是有时我们仅仅需要查询部分字段信息,这是可以使用select()方法限定返回的字段信息,避免I/O资源的浪费;
如果不写as realName就无法映射到realName成员变量
mp是直接使用select方法的值进行拼接,这样一来就不会识别到realName成员变量的@TableFileld注解
java
@Test
public void test09(){
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper
.select("user_name as realName","email")
.orderByDesc("age");
List<User> users = userMapper.selectList(wrapper);
System.out.println(users);
}
QueryWrapper实现分页条件查询
java
@Test
public void test10(){
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.ge("age",20)
.orderByDesc("age");
//构建分页对象
IPage<User> page = new Page<>(2,2);
userMapper.selectPage(page, wrapper);
//获取当前页的对象
List<User> users = page.getRecords();
System.out.println(users);
long pages = page.getPages();
}
LambdaQueryWrapper查询
使用QueryWrapper开发存在的问题
- 使用QueryWrapper查询数据时需要手写对应表的列名信息,及其容易写错,开发体验不好;
- 使用QueryWrapper查询数据时,表的列名硬编码书写,后期一旦表结构更改,则会带来很大的修改工作量,维护性较差;
LambdaQueryWrapper可以解决上述出现问题,开发推荐使用;
java
@Test
public void test11(){
//直接new一个对象
//LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
//使用工具类生成一个对象
LambdaQueryWrapper<User> wrapper = Wrappers.<User>lambdaQuery();
//TODO:数据中的字段名通过类的成员变量名以及成员变量上的注解获取,解耦合
wrapper.select(User::getRealName,User::getEmail)
.like(User::getRealName,"伤")
.eq(User::getPassword,"123456");
List<User> users = userMapper.selectList(wrapper);
System.out.println(users);
}
LambdaQueryWrapper实现条件删除和更新操作
java
@Test
public void test12(){
//使用wrapper实现条件删除
LambdaQueryWrapper<User> wrapper = Wrappers.<User>lambdaQuery().eq(User::getRealName, "hhh");
int delete = userMapper.delete(wrapper);
System.out.println(delete);
}
java
@Test
public void test13(){
User user=User.builder()
.realName("hhh")
.build();
LambdaQueryWrapper<User> wrapper = Wrappers.<User>lambdaQuery().eq(User::getId, 1L);
int update = userMapper.update(user, wrapper);
}
或者直接使用lambdaUpdate的set方法直接进行赋值
java
@Test
public void test14(){
//直接使用lambdaUpdate
LambdaUpdateWrapper<User> wrapper = Wrappers.<User>lambdaUpdate()
.eq(User::getId, 1L)//更新条件
.set(User::getRealName, "aaa")
.set(User::getPassword, "1243");
userMapper.update(null,wrapper);
}
自定义接口
java
/**
* 编写接口继承BaseMapper接口,
* 其中泛型是是与数据表对应的entity类,会根据泛型类的@TableName注解值获取要操作的数据表
*/
@Mapper
public interface UserMapper extends BaseMapper<User> {
/**
* 分页查询id大于id的用户集合
* @param page 分页查询对象,分页拦截器会根据先根据这个分页查询对象来拦截sql语句进行分页查询
* 然后将查询到的用户集合会封装到这个分页对象中
* @param id id
*/
IPage<User> findUserGtId(IPage<User>page, @Param("id") Long id);
}
xml文件写在哪呢?
直接在resources目录下创建mapper目录即可
因为mp默认的扫描xml文件是mapper目录下的所有文件
xml文件
html
<?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.itheima.mapper.UserMapper">
<select id="findUserGtId" resultType="com.itheima.pojo.User">
select id,user_name AS realName,password,name,age,email from tb_user where id>#{id}
</select>
</mapper>
测试
java
@Test
public void test15(){
IPage<User> page = new Page<>(2, 5);
userMapper.findUserGtId(page,3L);
//获取当前页的对象集合
List<User> users = page.getRecords();
System.out.println(users);
}
可以看到分页查询拦截器会根据分页对象自动为sql语句添加limit关键字进行分页查询
MP实现Service封装
MybatisPlus为了开发更加快捷,对业务层也进行了封装,直接提供了相关的接口和实现类; 我们在进行业务层开发时,可以继承它提供的接口和实现类,使得编码更加高效;
实现流程
- 定义一个服务扩展接口,该接口继承公共接口IService;
- 定义一个服务实现类,该类继承ServiceImpl<Mapper,Entity>,并实现自定义的扩展接口;
注意事项:
1.ServiceImpl父类已经注入了UserMapper对象,名称叫做baseMapper,所以当前实现类直接可以使用baseMapper完成操作 2.因为ServiceImpl已经实现了IService下的方法,所以当前服务类没有必要再次实现
思想:共性的业务代码交给框架封装维护,非共性的业务,在接口UserService定义,然后在当前的服务类下实现;
快速使用
一.自定义一个接口继承IService接口
java
/**
* 自定义一个服务层接口继承公共服务接口,泛型是实体类对象(通过@TableName注解知道要操作的数据表)
*/
public interface UserService extends IService<User> {
IPage<User> getUserGtId(IPage<User>page,Long id);
}
二.自定义一个类继承ServiceImpl类和实现自定义的接口
/**
* 自定义一个服务层实现类,实现自定义的服务层接口
* 因为这个服务层接口继承的公共服务层接口,有许多待实现的方法
* 所以我们还有继承一个ServiceImpl类,这个类实现了IService接口的所有方法
* 第一个泛型是要使用的dao层mapper,ServiceImpl会自动注入这个mapper bean,所以自定义的实现类就不用注入mapper了
* 注入的这个mapper成员变量叫baseMapper
*/
java
/**
* 自定义一个服务层实现类,实现自定义的服务层接口
* 因为这个服务层接口继承的公共服务层接口,有许多待实现的方法
* 所以我们还有继承一个ServiceImpl类,这个类实现了IService接口的所有方法
* 第一个泛型是要使用的dao层mapper,ServiceImpl会自动注入这个mapper bean,所以自定义的实现类就不用注入mapper了
* 注入的这个mapper成员变量叫baseMapper
*/
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
/**
* 只需要实现UserService接口的方法
*/
@Override
public IPage<User> getUserGtId(IPage<User> page, Long id) {
//直接使用ServiceImpl注入的mapper bean即可
return baseMapper.findUserGtId(page,3L);
}
}
MP封装Service实现CRUD操作
java
/**
* @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);
}
java
@Test
public void test02(){
User user1 = User.builder().name("wangwu2").realName("laowang2").
email("444@163.com").age(20).password("333").build();
userService.saveOrUpdate(user1);//没有设置主键id就是save(新增操作)
User user2 = User.builder().id(user1.getId()).name("wangwu3").realName("laowang3").
email("444@163.com").age(20).password("333").build();
userService.saveOrUpdate(user2);//设置了主键id就是update(修改操作)
}
使用MybatisX生成代码
逻辑删除
给表中新增加一个字段,0代表未删除,1代表以删除
这样一来删除的行依然存在于表中
添加逻辑删除字段
配置MP让其走逻辑删除
java
#配置数据源
spring:
datasource:
username: root
password: 1234
url: jdbc:mysql://192.168.230.130:3306/mp_db?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC&useSSL=false
driver-class-name: com.mysql.cj.jdbc.Driver
# 设置mp运行时行为
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
logic-delete-field: deleted #指定逻辑删除的表字段
logic-delete-value: 1 #指定成功删除后的值
logic-not-delete-value: 0 #指定未被删除的值
使用@TableLogic注解指定逻辑删除的字段
java
/**
* 逻辑删除的字段对于的成员变量
*/
@TableLogic
private Integer deleted;
测试
java
@Test
public void test01(){
User user = userMapper.selectById(1L);
System.out.println(user);
}
查询时mp给sql语句自动添加 and deleted=0;确保该数据没有没删除
java
@Test
public void test03(){
int i = userMapper.deleteById(1);
System.out.println(i);
}
删除时底层sql是修改deleted字段的值
乐观锁
乐观锁就是当前操作者认为在自己操作资源的过程中,其他人操作相同资源的可能性极低,所以无需加锁,而是通过设置一个版本号来加以约束;
悲观锁:排它锁,比如synchronized关键字就是悲观锁,当前线程做操作时,不允许其它线程做操作;
乐观锁:当前线程做操作时,允许其它线程做操作,但是如果其它线程做了某些操作,然后让版本号改变,如果当前线程发现版本号改变,则当前操作失败;
乐观锁在数据库中有什么优势?
避免长事务场景锁定数据资源,导致其它线程操作该资源时阻塞,如果阻塞过多,那么导致数据库连接资源耗尽,进而数据库宕机了;
本质上就是在操作前,先获取操作行的version版本号,如果当前的版本号与指定的版本号不同则不进行其他操作,相同才进行操作,操作后就让版本号加一。
使用场景:
1.业务操作周期长,如果业务整个加入事务,导致数据库资源锁定周期过长,性能降低;
2.如果资源争抢过于激烈,会导致失败重试次数过多,导致性能降低;
使用乐观锁
一.给数据库表添加一个乐观锁字段
二.在配置类中的插件bean添加乐观锁
java
@Configuration
public class MybatisPlusConfig {
/**
* 用于批量注册mp的插件bean
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
//构建批量注册插件的对象
MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
//构建分页插件对象,并指定mysql数据库
PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);
//配置每页的大小,-1为无上限
paginationInnerInterceptor.setMaxLimit(-1L);
//将分页插件注册
mybatisPlusInterceptor.addInnerInterceptor(paginationInnerInterceptor);
//配置乐观锁插件
OptimisticLockerInnerInterceptor optimisticLockerInnerInterceptor = new OptimisticLockerInnerInterceptor();
mybatisPlusInterceptor.addInnerInterceptor(optimisticLockerInnerInterceptor);
return mybatisPlusInterceptor;
}
}
三.在实体类中添加乐观锁字段对应的成员变量(使用@Version注解)
java
/**
* 乐观锁字段对应的成员变量
*/
@Version
private Integer version;
四.测试
java
@Test
public void test01(){
//先做查询,获取当前对象的version值
User dbUser = userMapper.selectById(2L);
/*
进行其他操作,如果修改了id=2的数据数值,那么version值就不会相同
如果没有进行修改,那么version值还是和dbUser对象的version相同
*/
User user = User.builder()
.id(dbUser.getId())//修改这个id的数据表数据
.realName("abc")
.version(dbUser.getVersion())//会先根据dbUser的version进行判断,如果相等就执行,并让version加一
.build();
userMapper.updateById(user);
}
可以发现dbUser当前查询到的version值为3,
进行更新操作时,根据dbUser的version值给entity对象的version属性进行赋值,如果当前数据表中id=2的version值还是3,则说明表数据没有被修改 ,则entity对象的version值与数据库中的version值相同,就可以进行修改操作,且让version值加一(因为对表数据进行了修改)
如果进行更新的entity对象version属性没有进行赋值,就不会有 and version=? 和set version=?
java
@Test
public void test01(){
//先做查询,获取当前对象的version值
User dbUser = userMapper.selectById(2L);
/*
进行其他操作,如果修改了id=2的数据数值,那么version值就不会相同
如果没有进行修改,那么version值还是和dbUser对象的version相同
*/
User user = User.builder()
.id(dbUser.getId())//修改这个id的行
.realName("abc")
//.version(dbUser.getVersion())//会先根据dbUser的version进行判断,如果相等就执行,并让version加一
.build();
userMapper.updateById(user);
}
实现字段自动填充
先在我新增加了两个字段,给这两个字段填充值时需要频繁的写new Date()进行赋值,我们可以配置让其自动填充
一.添加成员变量
java
/**
* 插入时给这个成员变量赋值
*/
@TableField(fill = FieldFill.INSERT)
private Date createTime;
/**
* 插入或者更新时给这个成员变量赋值
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
二.配置拦截器实现MetaObjectHandler
java
/**
* 一个拦截器,在数据准备插入数据库时进行拦截
*/
@Component
public class FillHandler implements MetaObjectHandler {
/**
* 在进行插入数据时进行拦截
* @param metaObject 封装了准备插入数据库的entity对象
*/
@Override
public void insertFill(MetaObject metaObject) {
//第一个参数时类的成员变量名
metaObject.setValue("createTime",new Date());
metaObject.setValue("updateTime",new Date());
}
/**
* 在进行更新数据时进行拦截
* @param metaObject 封装了准备更新数据库数据的entity对象
*/
@Override
public void updateFill(MetaObject metaObject) {
metaObject.setValue("updateTime",new Date());
}
}
三.测试
java
@Test
public void test02(){
User user=User.builder()
.realName("hah")
.email("153@qq.com")
.age(18)
.name("a")
.password("123")
.build();
int insert = userMapper.insert(user);
System.out.println(insert);
}
拦截到的数据,此时createTime和updateTime成员变量没有值
赋值后,再进行插入