MybatisPlus
Demo GitHub - SoftLeaderGy/StartMybatisPlus
快速入门:
使用第三方组件(步骤):
1、导入对应的依赖
2、研究依赖如何配置
3、代码如何编写
4、提高扩展技术能力
步骤:
1、创建数据库'mybatisplus'
2、创建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');3、编写项目,初始化项目:使用SpringBoot初始化项目
4、导入依赖
<!--导入数据库驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<!--导入lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--导入mybatisPlus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.0.5</version>
</dependency>
说明:我们使用mybatisPlus可以节省大量的代码,尽量不要同时导入mybatis和mybatisPlus版本的差异application配置连接数据库
#mysql 5 驱动不同
spring.datasource.username=root
spring.datasource.password=gunveda^123456
spring.datasource.url=jdbc:mysql://t.gunveda.top:33060/mybatisPlus?useSSL=false&useUnicode=true&characterEncoding=utf-8
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
#mysql 8 驱动不同,com.mysql.cj.jdbc.Driver需要增加时区的配置serverTimezone=GMT%2B86、传统方式 pojo—dao(链接mybatis,配置mapper.xml文件)-service-controller
6、使用mybatisPlus之后
- pojo
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private Long id;
private String name;
private Integer age;
private String email;
}- mapper接口
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.guo.pojo.User;
import org.springframework.stereotype.Repository;
//在对应的Mapper上面继承基本的类 BaseMapper
@Repository //表示这个类是持久层的(表示他是个mapper或者是dao层的)
public interface UserMapper extends BaseMapper<User> {
//所有的crud操作已经完成了
//不需要像以前一样配置一堆文件
}- 使用
- 注意点,我们需要在启动类上 去扫描我们的mapper包下的所有接口 @MapperScan("com.guo.mapper")
- 测试类中的例子
@SpringBootTest
class MybatisPlusApplicationTests {
//继承了BaseMapper,所有的方法都来自于自己的父类,我们也可以编写自己的扩展方法!
@Autowired
private UserMapper userMapper;
@Test
void contextLoads() {
// 查询全部用户
//参数是一个Wapper, 条件构造器
List<User> users = userMapper.selectList(null);
users.forEach(System.out::println);
}
}结果查询出来~
思考问题
- SQl谁帮我们写的?MybatisPlus
- 方法哪里来的? MybatisPlus都写好了
配置日志
我们所有的sql现在是不可见的,我们希望知道他是怎么运行的,所以我们必须要看日志!
#配置日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl配置完毕日志之后,后面的学习就需要注意这个自动生成的SQL。就会喜欢上mybatisPLus
CRUD扩展
插入操作
插入测试
//测试插入功能
@Test
public void testInsert(){
User user = new User();
user.setName("郭洋");
user.setAge(3);
user.setEmail("826044698@qq.com");
int result = userMapper.insert(user);// 帮我们自动生成了 id
System.out.println(result);// 受影响的行数
System.out.println(user); // 发现id会自动回填
}
日志:
==> Preparing: INSERT INTO user ( id, name, age, email ) VALUES ( ?, ?, ?, ? )
==> Parameters: 1310754142725124098(Long), 郭洋(String), 3(Integer), 826044698@qq.com(String)插入成功~
数据库插入的id的默认值为:全局唯一的id
主键生成策略
//IdType 源码中的几种类型
public enum IdType {
AUTO, NONE, INPUT, ID_WORKER, UUID, ID_WORKER_STR;
private int key;
private IdType(int key) { /* compiled code */ }
public int getKey() { /* compiled code */ }
}默认 @TableId(type = IdType.ID_WORKER) 全局唯一id (ID_WORKER)分布式系统唯一id生成:https://www.cnblogs.com/haoxinyue/p/5208136.html
雪花算法
- snowflake是Twitter开源的分布式ID生成算法,结果是一个long型的ID。其核心思想是:使用41bit作为毫秒数,10bit作为机器的ID(5个bit是数据中心,5个bit的机器ID),12bit作为毫秒内的流水号(意味着每个节点在每毫秒可以产生 4096 个 ID),最后还有一个符号位,永远是0。几乎可以保证全球唯一了!
主键自增 [@TableId(type ](/TableId(type ) = IdType.AUTO) 自增(AUTO)
- 实体类字段上 [@TableId(type ](/TableId(type ) = IdType.AUTO)
- 数据库字段一定要自增
- 再次测试 id自增~
其余的源码解释
public enum IdType {
AUTO, //数据库id自增
NONE, // 未设置主键
INPUT, // 手动输入
ID_WORKER, // 默认的全局id
UUID, // 全局唯一id
ID_WORKER_STR; // ID_WORKER的字符串表示法
private int key;
private IdType(int key) { /* compiled code */ }
public int getKey() { /* compiled code */ }
}更新操作
测试更新
//更新操作测试
@Test
public void testUpdata(){
User user = new User();
// 通过条件自动拼接动态sql
user.setName("郭洋");
user.setId(1L);
//注意:虽然方法名字叫 updateById,但是 参数 是一个对象!
userMapper.updateById(user);
}所有的sql都是自动帮你动态配置的!
自动填充
创建时间、修改时间! 这些操作都是自动化完成的,我们不希望手动更新
方式一: 数据库级别
- 在表中新增字段 create_time、update_time
- 在插入字段 之后 ,我们需要先把实体类同步(添加字段)
private Date createTime;
private Date updateTime;- 再次测试 插入方法, 时间自动更新完成~
方式二: 代码级别
- 删除数据库中的默认值、更新操作(初始化数据库 让数据库变为一个干净的数据库)
- 实体类字段属性上需要增加注解
// 字段添加填充内容
@TableField(fill = FieldFill.INSERT)
private Date createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;- 编写处理器 处理这个注解即可
package com.guo.handler;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;
import java.util.Date;
@Slf4j //日志
@Component // 将类放在springboot容器中
public class MyMetaObjectHandler implements MetaObjectHandler {
// 插入时的填充策略
@Override
public void insertFill(MetaObject metaObject) {
log.info("statr insert fill...");
// 里边的三个参数:
// (java.lang.String fieldName, 需要填充的字段名
// java.lang.Object fieldVal, 填充的字段值
// org.apache.ibatis.reflection.MetaObject metaObject) 给那个数据处理
this.setFieldValByName("createTime",new Date(),metaObject);
this.setFieldValByName("updateTime",new Date(),metaObject);
}
// 更新时的填充策略
@Override
public void updateFill(MetaObject metaObject) {
log.info("start update fill...");
this.setFieldValByName("updateTime",new Date(),metaObject);
}
}- 测试插入操作~
- 测试更新操作,观察更新时间~
乐观锁
在面试过程中,我们经常会被问到乐观锁、悲观锁
乐观锁 : 顾名思义 十分乐观,他总是认为不会出现问题,无论干什么都不去上锁,如果出现了问题,再次更新值测试
悲观锁: 顾名思义 十分悲观,他总是认为出现问题,无论干什么 都会上锁。再去操作
我们这里主要讲解乐观锁插件 乐观锁实现方式:
- 取出记录时,获取当前version
- 更新时,带上这个version
- 执行更新时, set version = newVersion where version = oldVersion
- 如果version不对,就更新失败
乐观所: 1、先查询,获得版本号 version = 1
--A线程
update user set name = "guoyang" , version = version + 1
where id = 2 and version = 1
--B线程 抢先执行完成,这个时候 version = 2, 会导致A线程修改失败!
update user set name = "guoyang" , version = version + 1
where id = 2 and version = 1测试一下MybatisPlus 的乐观锁插件
- 给数据库中增加version字段
- 实体类加上对应的字段
//乐观锁测试(版本号)
@Version // 乐观锁Version注解
private Integer version;- 注册组件 (新建config包 创建配置类 config.class )
package com.guo.config;
import com.baomidou.mybatisplus.extension.plugins.OptimisticLockerInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
//扫描我们的 mapper 文件夹
@MapperScan("com.guo.mapper")
@Configuration //表示他是个配置类
public class MyBatisPlusConfig {
// 注册乐观锁插件
@Bean
public OptimisticLockerInterceptor optimisticLockerInterceptor() {
return new OptimisticLockerInterceptor();
}
}- 测试
//测试乐观锁 成功
@Test
public void OptimisticLocker(){
// 1、查询用户信息
User user = userMapper.selectById(1L);
// 2、修改用户信息
user.setName("guoyang");
user.setEmail("1765567867@qq.com");
// 3、执行更新操作
userMapper.updateById(user);
}
//测试乐观锁 失败 多线程下
@Test
public void OptimisticLocker2(){
//线程1
User user = userMapper.selectById(1L);
user.setName("guoyang111");
user.setEmail("1765567867@qq.com");
//模拟另外一个线程执行了插队操作
User user2 = userMapper.selectById(1L);
user2.setName("guoyang222");
user2.setEmail("1765567867@qq.com");
userMapper.updateById(user2);
//
userMapper.updateById(user);// 如果没有乐观锁 就会覆盖插队线程的值
}
}查询操作
//测试查询
@Test
public void testSelectById(){
User user = userMapper.selectById(1L);
System.out.println(user);
}
// 测试批量查询
@Test
public void testSelectByBatchId(){
List<User> users = userMapper.selectBatchIds(Arrays.asList(1, 2, 3));
users.forEach(System.out::println);
}
@Test
//按条件查询 之一 使用 map (where 条件查询)
public void testSelectByBatchIds(){
HashMap<String, Object> map = new HashMap<>();
//自定义查询
map.put("name", "guoyang222");
map.put("age", 3);
List<User> users = userMapper.selectByMap(map);
users.forEach(System.out::println);
}分页查询
- 原始的limit 进行分页
- 第三方插件pageHelper
- MybatisPlus也内置了分页插件
如何使用呢?
- 配置拦截器组件(在MyBatisPlusConfig配置类中配置)
// 分页插件
@Bean
public PaginationInterceptor paginationInterceptor() {
return new PaginationInterceptor();
}- 直接使用page对象即可
//测试分页查询
@Test
public void testPage(){
//参数一: 当前第几页
//参数二: 页面显示几条数据
Page<User> page = new Page<>(2,5);
userMapper.selectPage(page,null);
page.getRecords().forEach(System.out::println);
System.out.println(page.getTotal());
}删除操作
- 基本的删除操作
//测试删除
@Test
public void testDeleteById(){
userMapper.deleteById(1310778514273808386L);
}
// 通过id批量删除
@Test
public void testDeleteBatchById(){
userMapper.deleteBatchIds(Arrays.asList(1310564048345018370L,1310754142725124098L));
}
//通过 map 条件删除
@Test
public void testDeleteMapById(){
HashMap<String, Object> map = new HashMap<>();
map.put("name", "洋");
userMapper.deleteByMap(map);
}工作中会遇到一些问题: 逻辑删除
逻辑删除
物理删除 : 从数据库中移除
逻辑删除 : 在数据库中没有移除,而是通过一个变量来让他失效! deleted=0 变为 delete= 1 将deleted = 0 作为条件 去做一些操作 就实现了逻辑删除(启用、停用)
实例: 管理员可以查看被删除的记录 防止数据库的删除、类似于回收站
- 在数据库中增加一个deleted字段
- 实体类中增加属性
@TableLogic//逻辑删除注解
private Integer deleted;- 在配置文件中进行配置
#配置逻辑删除
mybatis-plus.global-config.db-config.logic-not-delete-value=0
mybatis-plus.global-config.db-config.logic-delete-value=1- 测试
执行的是删除操作,但是实际走的是修改操作 将deleted将修改为1 查询的时候会默认将deleted = 0 拼接称查询条件
性能分析插件
我们在开发中,会遇到慢sql。 mybatisPlus也提供了性能分析插件,如果超过这个时间就停止运行!(新版MytaisPlus版本已经移除了)
- 导入插件
- 测试使用
条件构造器
Wrapper 我们写一些复杂的sql就可以使用他来借代
- 测试一
@Test
void contextLoads() {
// 查询name不为空的用户,并且邮箱不为空的用户,年龄大于等于12
QueryWrapper<User> Wrapper = new QueryWrapper<>();
Wrapper
.isNotNull("name")
.isNotNull("email")
.ge("age",12);//g:表示大于 e:表示等于
userMapper.selectList(Wrapper).forEach(System.out::println);// 和刚学过的map对比一下
}- 测试二
@Test
void test2() {
//查询名字等于Tom的
QueryWrapper<User> Wrapper = new QueryWrapper<>();
Wrapper.eq("name","Tom");
User user = userMapper.selectOne(Wrapper);//selectOne 查询一个数据
System.out.println(user);
}- 测试三(between 相当于 sql里between and )
@Test
void test3() {
//查询年龄在20~30之间的用户
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.between("age", 20 ,30);// 区间
Integer count = userMapper.selectCount(wrapper);//查询结果数
System.out.println(count);
}- 测试四(模糊查询)
//模糊查询
@Test
void test4() {
//查询年龄在20~30之间的用户
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.notLike("name", "e")// name 字段中 不含e的
.likeRight("email" , "t");//likeRight(有通配)相当于 t%
List<Map<String, Object>> maps = userMapper.selectMaps(wrapper);
maps.forEach(System.out::println);
}- 测试五(子查询)
@Test
void test5() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
// 假设id在子查询里
wrapper.inSql("id","select id from user where id < 3 ");
List<Object> objects = userMapper.selectObjs(wrapper);
objects.forEach(System.out::println);
}- 测试六(orderByDesc)倒叙排序
@Test
void test6() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
// 通过id进行排序
wrapper.orderByDesc("id");
List<User> users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}