一、MyBatisPlus
- MyBatisPlus 能大量的节省我们些CRUD的时间,所有的CRUD都能通过MyBatisPlus来自动完成
- MyBatis Plus 简称 MP 是一个MyBatis的增强工具包,制作增强不做改变,为简化开发,提高生产率而生
- MyBatis 官网
- MyBatis文档
二、快速入门
- 初始化工程
- 创建一个springboot项目,选上lombok,spring web
导入mybatis plus 依赖
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.0.5</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
创建一个表,随便创建一个用户表
编写 application.properties 配置文件
# MySQL 5 配置
spring.datasource.username=root
spring.datasource.password=0000
spring.datasource.url=jdbc:mysql://localhost:3306/mp
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
使用MyBatis之后
- 创建实体类 User
- 创建UserMapper接口 继承 BaseMapper
【这个Mapper就是Dao】 - 在UserMapper接口上添加@Repository 注解
- 在Application主方法类 上添加
- //扫描Mapper文件
- @MapperScan(“com.yixuexi._02_spring_boot_mybatisplus.dao”)
- 使用
1. 细节
- MyBatis和MyBatis-spring 依赖就不要加入到项目中了。MyBatisPlus自动维护
在真实开发中,表的字段一般都会有 version(乐观锁) deleted(逻辑删除) gmt_create(创建时间),gmt_modified(修改时间)
2. 坑:
springboot的MySQL start 的mysql依赖版本是 8.0 版本,和电脑上的不匹配的话会报错,所以最好别选mysqlStart,自己添加mysql5.0的依赖
- 需要在主启动类上去扫描我们的mapper包下所有的接口
- @MapperScan(“com.yixuexi._02_spring_boot_mybatisplus.dao”)
- 如果是MySQL8.0的话,那么spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
3. 配置日志
因为现在所有的SQL是不可见的,我们希望通过日志的方式将SQL输出出来
# 配置日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
三、insert update
1. insert 插入
@Test
void insertPlus(){
User user = new User();
user.setId(7);
user.setAge(18);
user.setName("仝子瑜");
user.setEmail("2298320493@qq.com");
// 如果不设置id的话,会帮我们自动生成id值 这个id值很大很大
int insert = userMapper.insert(user);
// 输出受影响的行数
System.out.println(insert);
}
为什么不设置id,自动插入的值会是很大很大的呢? 123561315643213216541
- 数据库插入的id的默认值为:全局的唯一id
- 主键生成策略: 雪花算法,uuid,redis 等等等
- 雪花算法: https://www.cnblogs.com/haoxinyue/p/5208136.html
2. 我们需要配置主键自增
- 在实体类主键字段上添加 @TableId(type = IdType.AUTO)
- 数据库字段一定要是自增的 auto_increment
- 上面设置了后,插入的User对象中,就算id属性赋值了,也会按照它的自增
- 其余的IdType源码解释
一旦手动输入[ IdType.INPUT ]之后 就需要自己写ID,如果不写就是NULL【其实这个也可以用来自增,因为实体类是null,到数据库里面会根据数据库的值进行自增】
3.update更新
@Test
void updatePlus(){
User user = new User();
user.setId(1);
user.setEmail("2298320493@qq.com");
user.setName("zhangsan");
user.setAge(15);
// 注意!这里传入的值是一个User对象,通过这个user对象的id进行更新
userMapper.updateById(user);
}
update的SQL语句都是MyBatisPlus自己的动态SQL ,通过对属性的ifnull 进行判断。
4. 自动填充
- 创建时间,修改时间!这些操作一般都是自动化完成的,我们不希望手动更新!
- 阿里巴巴开发手册:所有的数据库表:gmt_create【create_time】 ,gmt_modified 【update_time】几乎所有的表都要配置上,而且需要自动化!
方式一:数据库级别 (工作中不建议使用)
- 在创建表时添加两个字段 create_time update_time
- 字段类型 datetime
-
方式二:代码级别
删除数据库字段的默认值
- 实体类的字段属性上需要增加注解
```java
/**
- 字段插入时,自动填充 / @TableField(fill = FieldFill.INSERT) private Date createTime; /*
- 字段更新时,自动填充 */ @TableField(fill = FieldFill.UPDATE) private Date updateTime;
3. 编写处理器 处理注解【人家已经写好了,我们稍微改改】
- 官网地址: [https://baomidou.com/guide/auto-fill-metainfo.html](https://baomidou.com/guide/auto-fill-metainfo.html)
- 一定要记得在处理器上面添加@Component注解,将组件添加到容器中
```java
/**
* @date: 2021/1/5 0:02
* @author: 易学习
* @Component: 一定不要忘记把组件添加到容器中
*/
@Component
public class MyDateObjectHandler implements MetaObjectHandler {
/**
* 插入时的填充策略
* @param metaObject
*/
@Override
public void insertFill(MetaObject metaObject) {
/**
* setFieldValByName:填充你要指定的值
* 第一个参数:要填充的字段名
* 第二个参数:要填充的值
* 第三个参数:metaObject
* 这里要写两个 把更新的字段也写上
*/
this.setFieldValByName("createTime",new Date(),metaObject);
this.setFieldValByName("updateTime",new Date(),metaObject);
}
/**
* 更新时的填充策略
* @param metaObject
*/
@Override
public void updateFill(MetaObject metaObject) {
// 和上面一样,只是不需要设置 createTime了
this.setFieldValByName("updateTime",new Date(),metaObject);
}
}
四、乐观锁 & 悲观锁
乐观锁:顾名思义十分乐观,他总是认为不会出现问题,无论干什么都不会去上锁!如果出现了问题就再次更新值测试。
悲观锁:顾名思义十分悲观,他总是认为总是出现问题,无论干什么都会去上锁!再去上锁。
乐观锁实现方式:
- 取出记录时,获取当前version
- 更新时,带上这个version
- 执行更新时, SQL: set version = newVersion where version = oldVersion
- 如果version不对,就更新失败
乐观锁:现查询,获得版本号 version = 1
— A 线程
update user set name = ‘仝子瑜’,version = version + 1 where id = 22 and version = 1
—- B 线程,抢先修改,这个时候 version = 2 导致A 修改失败
update user set name = ‘张三’,version = version + 1 where id = 22 and version = 1
1. 测试MP中的乐观锁
- 给数据库中增加version 字段 int 类型 默认值为1
- 实体类添加对应的字段,并且加上@version注解,表明这是乐观锁
- 注册组件(最新版也需要注册,3.0.5需要注册组件) ```java /**
- @date: 2021/1/5 19:01
- @author: 易学习
- @Configuration: 表明这个是一个配置类,这样的话,之前注册的处理器的扫描 @MapperScan()也可以放到这里
- @MapperScan: 扫描处理器,这里扫描的是之前那个 createTime 和 updateTime
- @EnableTransactionManagement: 事务控制 */
@MapperScan(“com.yixuexi.mybatisplus.mapper”) @EnableTransactionManagement @Configuration public class MyBatisPlusConfig {
/**
* 注册乐观锁插件
*/
@Bean
public OptimisticLockerInterceptor optimisticLockerInterceptor(){
return new OptimisticLockerInterceptor();
}
}
4. 测试
```java
/**
* 测试乐观锁,成功案例
*/
@Test
void happyTest(){
// 1.查询用户信息
User user1 = userMapper.selectById(1);
// 2.修改用户信息
user1.setName("马保国");
user1.setAge(69);
user1.setEmail("mabaoguo@163.com");
// 3.执行更新操作
userMapper.updateById(user1);
}
/**
* 测试乐观锁,失败案例
*/
@Test
void happyTest2(){
// 线程1
User user1 = userMapper.selectById(1);
user1.setName("马保国");
user1.setAge(69);
user1.setEmail("mabaoguo@163.com");
// 线程2 模拟另外一个线程执行了插队操作
User user2 = userMapper.selectById(1);
user2.setName("旭旭宝宝");
user2.setAge(35);
// 线程2 抢先更新
userMapper.updateById(user2);
// 线程1执行更新操作
// 如果没有乐观锁,就会覆盖上面的旭旭宝宝,成为马保国
// 因为设置了乐观锁 没有覆盖 马保国没有被进行更新,因为where的version=? 不成立
userMapper.updateById(user1);
}
五、查询
1. 查询
/**
* * selectById(id)测试根据id查询一个
*/
@Test
void testSelectById(){
User user = userMapper.selectById(1);
System.out.println(user);
}
/**
* selectBatchIds(集合) 测试查询批量
*/
@Test
void testSelectBatchIds(){
List<User> users = userMapper.selectBatchIds(Arrays.asList(1, 2, 3));
System.out.println(users);
}
/**
* 条件查询:
*/
@Test
void testSelectByMap(){
HashMap<String,Object> userHashMap = new HashMap<>();
//自定义要查询的条件
userHashMap.put("name","张五");
List<User> users = userMapper.selectByMap(userHashMap);
System.out.println(users);
}
2. 分页查询
mp中也集成了分页插件
官网: https://baomidou.com/guide/interceptor.html#%E4%BD%BF%E7%94%A8%E6%96%B9%E5%BC%8F-%E4%BB%A5%E5%88%86%E9%A1%B5%E6%8F%92%E4%BB%B6%E4%B8%BE%E4%BE%8B
1. 加入组件
导入分页插件,在自己创建的MyBatisPlus配置类里面 (3.4.0版本)
/**
* 新的分页插件,一缓和二缓遵循mybatis的规则,需要设置 MybatisConfiguration#useDeprecatedExecutor = false 避免缓存出现问题(该属性会在旧插件移除后一同移除)
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.H2));
return interceptor;
}
@Bean
public ConfigurationCustomizer configurationCustomizer() {
return configuration -> configuration.setUseDeprecatedExecutor(false);
}
(3.0.5版本) 分页插件组件
@Bean
public PaginationInterceptor paginationInterceptor(){
return new PaginationInterceptor();
}
2. 测试分页查询
/**
* 测试分页插件
*/
@Test
void testPage(){
//创建一个Page对象
// 有参构造参数:第一个:当前页。第二个:一次查几个
Page<User> page = new Page(1,3);
//通过page的对象getxxx()方法可以获得其余属性
System.out.println(page.getTotal());
// 调用selectPage()方法 第一个是page 对象,第二个是wrapper,没有就写null
IPage<User> userIPage = userMapper.selectPage(page,null);
System.out.println(userIPage);
}
六、删除
@Test
void testDeleteByMap(){
Map<String,Object> map = new HashMap();
// 根据条件删除,条件是姓名为张三的记录
map.put("name","张三");
userMapper.deleteByMap(map);
}
1. 逻辑删除
- 物理删除: 直接从数据库中移除
- 逻辑删除: 没有从数据库中删除,而是通过一个字段来让他失效 deleted= 1
- 官网 : https://baomidou.com/guide/logic-delete.html#%E4%BD%BF%E7%94%A8%E6%96%B9%E6%B3%95
- 在数据表中增加deleted字段【默认值等于 0】
- 实体类中增加deleted属性 添加@TableLogic注解
- 添加组件【3.0.5需要 3.1.1以上版本不需要】 ```java /**
- 逻辑删除组件 */ @Bean public ISqlInjector sqlInjector(){ return new LogicSqlInjector(); } ```
配置逻辑删除在application.properties中
# 配置逻辑删除
# 没有删除的值为0
mybatis-plus.global-config.db-config.logic-delete-value=1
# 删除的后的 属性为1
mybatis-plus.global-config.db-config.logic-not-delete-value=0
测试删除
- 此时查询也查不到 deleted为1 的用户了
SELECT id,name,age,email,deleted,version,create_time,update_time FROM user WHERE id=? AND deleted=0 查询会自动在后面拼接一个 and deleted = 0.
七、条件构造器
用来写复杂的SQL
Wapper 是一个接口,底下有很多的实现类,查询用QueryWrapper类
具体方法看官网: https://baomidou.com/guide/wrapper.html#abstractwrapper
ge(“字段名”,值) 大于等于
/**
* @date: 2021/1/5 23:56
* @author: 易学习
*/
@SpringBootTest
public class WrapperTest {
@Autowired
private UserMapper userMapper;
/**
* 1. 查询name不为空,并且邮箱不为空的用户,并且年龄>=12
* isNotNull("字段") 该字段不为空
* ge("字段",值) 大于等于
*/
@Test
void test1(){
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.isNotNull("name")
.isNotNull("email")
.ge("age",12);
List<User> users = userMapper.selectList(wrapper);
System.out.println(users);
}
/**
* 2. 查询名字为 张五的记录
*/
@Test
void test2(){
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("name","张五");
List user = userMapper.selectList(wrapper);
System.out.println(user);
}
/**
* 3. 查询年龄在18-20之间的用户数量
*/
@Test
void test3(){
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.between("age",18,20);
Integer integer = userMapper.selectCount(wrapper);
System.out.println(integer);
}
/**
* 4. 模糊查询,名字里面没有五的
*/
@Test
void test4(){
QueryWrapper wrapper = new QueryWrapper();
wrapper.notLike("name","五");
List list = userMapper.selectMaps(wrapper);
System.out.println(list);
}
}