MybatisPlus入门学习
基于狂神说Java-MybatisPlus
MybatisPlus快速入门
学习MybatisPlus,前提需要熟悉以下内容
Spring、SpringMVC、Mybatis
为什么要学习它?它可以实现简化开发,CRUD代码实现全自动,解放双手辣~~~
MybatisPlus,只做增强,不做改变,只需要简单的配置就可以实现功能,如丝般顺滑
MybatisPlus是什么
我们知道Mybatis是一个封装了JDBC的半自动CRUD框架,而MybatisPlus在Mybatis的基础上又做了一个加强,实现半自动到全自动的转变
能干嘛?
在哪下?
MybatisPlus官网:https://baomidou.com
项目地址(源码)
GitHub:https://github.com/baomidou/mybatis-plus
Gitee:https://gitee.com/baomidou/mybatis-plus
怎么玩?
MybatisPlus的快速开始:https://baomidou.com/guide/quick-start.html#%E5%88%9D%E5%A7%8B%E5%8C%96%E5%B7%A5%E7%A8%8B
使用第三方组件:
1、导入依赖
2、依赖如何配置
3、代码怎么写
4、提高技术扩展能力
首先创建数据库,名称为mybatis_plus
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');
项目构建,使用SpringBoot的初始化向导进行快速构建
在pom文件中引入依赖
<!--数据库驱动-->
<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-->
<!--MybatisPlus启动器是属于baomidou公司本身自己开发,而不是SpringBoot家族-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.0.5</version>
</dependency>
数据库配置
# MySQL 8 数据库需要在URL下增加一些规则
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/mybatis_plus?useSSL=false&useUnicode=true\
&CharacterEncoding=UTF-8&serverTimezone=UTC
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
原生MVC架构:pojo-dao(连接Mybatis、mapper.xml配置文件)-Service-Controller
使用了MybatisPlus之后
pojo
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private Long id;
private String name;
private Integer age;
private String email;
}
mapper
// 在对应的mapper上面实现基本的接口
@Repository // 声明当前接口是一个持久层接口
public interface UserMapper extends BaseMapper<User> {
// 和JPA一样,只要实现或者继承父类(接口),那么基本的单表的CRUD已经编写完成
}
SpringBoot主配置类
// 扫描mapper包下的持久层接口
@MapperScan("com.hrc.mapper")
测试
// 继承BaseMapper,所有的方法都来自父类,也可以编写属于自己的API
@Autowired
private UserMapper userMapper;
@Test
void contextLoads() {
// API:selectList(@Param Wrapper wrapper);括号传递一个参数构造器
// 如果传递为null,那么就表示没有条件,查询全部数据
List<User> list = userMapper.selectList(null);
list.forEach(System.out::println);
}
MybatisPlus配置日志
对于现在的sql来说,是不可见的,我们希望知道它是怎么执行的,而它执行的过程都会在日志中进行详细的记录,所以MybatisPlus的日志配置也成为了必需
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
CRUD扩展
数据添加
@Test
public void testIns() {
User user = new User(null, "YouTuBe", 22, "test6@baomidou.com");
int res = userMapper.insert(user);
System.out.println(res);
System.out.println(user);
}
请注意,上面的代码,id传值为空,但是在控制台输出时,MybatisPlus自动将id进行回填了
这边就需要多聊一嘴东西,这边数据库插入id的默认值是,全局的唯一id
这边就需要聊一聊主键生成策略
id主键生成策略
MybatisPlus的默认主键生成策略
@TableId(type = IdType.ID_WORKER)
这边主要要聊的是一个东西叫做雪花算法
雪花算法是Twitter使用zookeeper实现的一种全局唯一id的服务,英文名叫snowflake,它生成的id的结构如下
0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000
- 第一位,0,表示未使用
- 接下来的41位,它表示毫秒级时间戳,它的使用时间可以长达69年
- 在接下来的5位,数据中心id
- 还有5位,机器id
- 最后12位,毫秒内的计数,可以产生4096个id
这些数位加起来刚好64位,正好对应一个64位的Long类型
既然它使用的是MybatisPlus默认的,现在需要使用MySQL数据库支持的主键自增,那么,就需要将数据库中的表的主键id设置为主键自增
接着将User类中的id字段设置为主键自增策略
@TableId(type = IdType.AUTO)
当然,不止这两个主键生成策略,其余的在源码中也解释到了,而且还是中文注释(题外话,MybatisPlus的作者是中国人[doge])
@Getter
public enum IdType {
/**
* 数据库ID自增
*/
AUTO(0),
/**
* 该类型为未设置主键类型
*/
NONE(1),
/**
* 用户输入ID
* 该类型可以通过自己注册自动填充插件进行填充
*/
INPUT(2),
/* 以下3种类型、只有当插入对象ID 为空,才自动填充。 */
/**
* 全局唯一ID (idWorker)
*/
ID_WORKER(3),
/**
* 全局唯一ID (UUID)
*/
UUID(4),
/**
* 字符串全局唯一ID (idWorker 的字符串表示)
*/
ID_WORKER_STR(5);
private int key;
IdType(int key) {
this.key = key;
}
}
数据更新
@Test
public void testUpd() {
User user = new User();
// 根据条件实现动态拼接
user.setId(1330100039132925955L);
user.setAge(19);
// updateById,虽然是根据id进行修改,但是参数是一个对象
int res = userMapper.updateById(user);
System.out.println(res);
}
并且MybatisPlus会根据你设置了多少属性来实现动态SQL
自动填充
在数据库中,数据的创建时间,数据的修改时间,一般在语句执行之后显示,但是我们不能手动添加这些东西,这需要时间
在阿里巴巴开发手册中表示,所有的数据库表创建时间和修改时间一定要有,也就是gmt_create和gmt_modified,这两个字段,需要实现自动化
数据库增加字段
按照上面的进行同步设置就可以辣~~
字段增加完毕就可以在代码当中进行同步
private Date createTime;
private Date updateTime;
Java代码自动填充
在对应的创建时间和修改时间添加注解
// 字段的自动填充内容
@TableField(fill = FieldFill.INSERT)
private Date createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
并且还需要编写对应的处理器
// 在进行数据插入时的填充策略
@Component // 需要将组件加载到IOC容器中
@Slf4j
public class MyMetaObjectMapperHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
log.info("正在读取当前插入时间...");
// 这边的API,setFieldValByName(String fieldName, Object fieldVal, MetaObject metaObject);
this.setFieldValByName("createTime", new Date(), metaObject);
this.setFieldValByName("updateTime", new Date(), metaObject);
}
@Override
public void updateFill(MetaObject metaObject) {
log.info("正在读取当前修改时间...");
this.setFieldValByName("updateTime", new Date(), metaObject);
}
}
乐观锁
先聊一聊乐观锁和悲观锁的概念吧
乐观锁,很乐观,觉得任何操作都不会出现问题,所以不会加锁,不过在数据库设计中要使用到乐观锁的话,一般都会添加一个字段叫做version,版本号,在进行数据更新的时候,需要去返回比对一下版本号,版本号不相同,它就不让你修改数据,需要重新设置值进行测试
悲观锁,很悲观,觉得不管是什么操作它都会出现问题,只要有一个线程进来,立刻加锁,不允许其他线程访问,在高并发使用悲观锁将是一个非常致命的选择
这边主要是聊聊乐观锁在MybatisPlus中的使用
乐观锁实现方式:
- 取出记录时,获取当前version
- 更新时,带上这个version
- 执行更新时, set version = newVersion where version = oldVersion
- 如果version不对,就更新失败
-- 对于乐观锁来说,首先需要查询获得版本号
-- 在更新时,比对版本号
update user set name = "xxx" , version = version + 1
where id = 3 and version = 1
-- 如果这个时候有一个线程在上一个线程抢先完成,那么,这个时候,version = 2,就无法使用
update user set name = "abc", version = version + 1
where id = 1 and version = 1
在数据库中添加一个version字段
在实体类中添加一个属性
@Version // 添加一个版本号,实现乐观锁
private Integer version;
添加一个配置类
@MapperScan("com.hrc.mybatisplus.mapper") // 本来这个东西是可以放在SpringBoot的主启动类上来使用,不过现在有了配置类,可以交给MybatisPlus自己监管
@Configuration // 声明此类为配置类,加载到容器中使用
@EnableTransactionManagement // 此注解声明交给MybatisPlus自动管理事务
public class MybatisPlusConfig {
// 注册乐观锁插件
@Bean
public OptimisticLockerInterceptor optimisticLockerInterceptor() {
return new OptimisticLockerInterceptor();
}
}
测试
// 测试一个成功的乐观锁
@Test
public void testOpt() {
User user = userMapper.selectById(1L);
user.setName("Jackie");
user.setEmail("Mylove@baomidou.com");
System.out.println(userMapper.updateById(user));
}
// 测试乐观锁失败
@Test
public void testOptFail() {
// 在这边模拟两个线程进行插队操作
// 线程1正常执行
User user = userMapper.selectById(2L);
user.setAge(22);
user.setName("Jose");
// 线程2进行插队操作,在线程1之前抢先更新
User user2 = userMapper.selectById(2L);
user2.setAge(21);
user2.setName("Marting");
userMapper.updateById(user2);
userMapper.updateById(user); // 在它的上方,第二个线程最先执行完毕,所以这一段代码不会执行
// 如果这个地方执行失败,就是使用自旋锁来进行重复提交,直到成功为止
}
数据查询
@Test
public void testSel() {
// User user = userMapper.selectById(5L);
List<User> userList = userMapper.selectBatchIds(Arrays.asList(1, 2, 3, 4, 5));// 批量查询
userList.forEach(System.out::println);
}
@Test // Map条件查询
public void testSel2() {
HashMap<String, Object> map = new HashMap<>();
map.put("name", "Tom");
List<User> userList = userMapper.selectByMap(map);
userList.forEach(System.out::println);
}
分页查询
分页对于一些网站来说,使用的还是比较多,比较常见的有几种
- limit分页
- pageHelper第三方插件
- MybatisPlus内置分页插件
如何配置MybatisPlus分页插件?
配置类
// MybatisPlus分页插件
@Bean
public PaginationInterceptor paginationInterceptor() {
// PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
// 设置请求的页面大于最大页后操作, true调回到首页,false 继续请求 默认false
// paginationInterceptor.setOverflow(false);
// 设置最大单页限制数量,默认 500 条,-1 不受限制
// paginationInterceptor.setLimit(500);
// 开启 count 的 join 优化,只针对部分 left join
// paginationInterceptor.setCountSqlParser(new JsqlParserCountOptimize(true));
return new PaginationInterceptor();
}
测试
@Test
public void testSelPage() {
Page<User> page = new Page<>(1, 5);
IPage<User> userIPage = userMapper.selectPage(page, null);
userIPage.getRecords().forEach(System.out::println);
}
数据删除
@Test // 删除单个
public void testDel() {
System.out.println(userMapper.deleteById(1330817100494761987L));
}
@Test // 删除多个
public void testBenchDel() {
System.out.println(userMapper.deleteBatchIds(Arrays.asList(1330817100494761986L, 1330817100494761985L)));
}
@Test // Map删除
public void testDelMap() {
Map<String, Object> map = new HashMap<>();
map.put("name", "957");
System.out.println(userMapper.deleteByMap(map));
}
逻辑删除
在讲物理删除的时候,需要了解两个东西
物理删除:从数据库中直接删除
逻辑删除:在数据库中没有被移除,而是使用一个字段来让他失效(举一个不是特别恰当的例子:可以理解为在未删除和删除之间,中间加了一个“回收站”)
可以使用一个字段叫做deleted,int类型,指定一个数值,表示这个数据会被删除
使用逻辑删除的好处,如果删除了,就可以使用管理员的身份直接查看被删除的记录
首先在数据库中添加一个字段
实体类增加字段
@TableLogic // 实现逻辑删除
private Integer deleted;
在配置类中增加组件
// 逻辑删除
@Bean
public ISqlInjector iSqlInjector() {
return new LogicSqlInjector();
}
配置文件中添加配置
# 配置逻辑删除
# 当字段值为1时,默认删除当前数据
mybatis-plus.global-config.db-config.logic-delete-value=1
# 当字段值为0时,不删除
mybatis-plus.global-config.db-config.logic-not-delete-value=0
用来删除的API进行测试,但是结果可以看到
逻辑删除本质上就是一个Update语句
它指定要删除的记录在数据库中还是存在的
但是在查询这一条数据时,它依旧不会被查出来
在进行查询的时候,会自动过滤掉被逻辑删除的字段;
性能分析插件
在开发中,会遇到一种情况叫做慢查询(也叫慢SQL),慢查询会导致CPU负载太高(主要是查询语句中会有比较复杂的处理算法,又或者是在进行IO时负载过高),MybatisPlus针对此种情况推出了一个东西叫做性能分析插件,一旦查询的时间超过插件指定时间,程序就会自动停止
首先需要定义一个开发环境,因为在下面导入插件的时候会使用到
spring.profiles.active=dev
1、导入插件
@Bean
// 一定要设置当前项目的环境,切记!切记!
@Profile({"dev"}) // 指定环境为dev生效
public PerformanceInterceptor performanceInterceptor() {
PerformanceInterceptor interceptor = new PerformanceInterceptor();
// 是否格式化代码
interceptor.setFormat(true);
// 设置SQL超时时间
interceptor.setMaxTime(5000L);
return interceptor;
}
测试,在测试时,控制台输出的东西会有一些不同
控制台打印输出的东西,会出现一个SQL的执行时间,如果当前SQL的执行时间超过了你所设定的这个最大超时时间,那么程序会抛出异常
条件构造器
其实之前就已经在查询全部数据的时候就已经埋下了一个伏笔,测试SQL是否连接成功的时候,当时的selectList方法的传递的值为null(不知道的可以看博客开头的快速入门),这个null表示的就是Wrapper,这才是本小节的重点
复杂的条件查询,MybatisPlus中提供了一个类,AbstractWrapper,这是个抽象类,源码中也明确的表示了,这是一个条件查询的封装,也就是说,所有的类都可以通过继承它来实现复杂查询的API调用
并且在官网中也罗列出了相关的API,这是一一对应的
来吧,测试一下叭~~~
多条件查询
@Test
void contextLoads() {
// 查询name不为空,email不为空的用户,年龄>=21岁
// QueryWrapper是AbstractWrapper的子类,内部属性是为了生成查询条件
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.isNotNull("name").isNotNull("email").ge("age", 21);
List<User> users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}
查看一下测试的结果
多条件查询这边控制台打印的信息和代码当中的Wrapper条件是一一对饮的
单表单个数据
@Test // 查询单个
void test01() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("name", "957");
System.out.println(userMapper.selectOne(wrapper));
}
查询 范围
@Test // 查询区间
void test0203() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.between("age", 20, 24);
// 查询在此区间之内的数据
// userMapper.selectMaps(wrapper).forEach(System.out::println);
// 查询在此区间的数据有几条
System.out.println(userMapper.selectCount(wrapper));
}
左右两边定义查询范围
@Test // 左右模糊匹配
void test04() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
/*
这边的API,likeLeft和likeRight
likeLeft:从指定字段的属性值的从右往左进行模糊查询,即 column like "%xx"
likeRight:从指定字段的属性值的从左往右进行模糊查询,即 column like "xx%"
*/
wrapper.likeRight("email", "t").likeLeft("name", "e");
userMapper.selectMaps(wrapper).forEach(System.out::println);
}
套娃
@Test // 子查询
void test05() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.inSql("id", "select id from user where id < 3");
userMapper.selectObjs(wrapper).forEach(System.out::println);
}
排序
@Test // 查询结果排序
void test06() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.orderByDesc("id");
userMapper.selectObjs(wrapper).forEach(System.out::println);
}
代码自动生成器
让MybatisPlus自己写代码,dao,service,pojo,controller全部都是自动生成
public class HrcCode {
public static void main(String[] args) {
// 代码生成器
AutoGenerator mpg = new AutoGenerator();
// 配置策略
// 1. 全局配置
GlobalConfig gc = new GlobalConfig();
String strProPath = System.getProperty("user.dir");
gc.setOutputDir(strProPath + "/src/main/java");
gc.setAuthor("小黄要成为一线程序员吖"); // 作者
gc.setOpen(false); // 是否打开资源管理器
gc.setFileOverride(false); // 是否覆盖
mpg.setGlobalConfig(gc);
// 2. 数据源配置
DataSourceConfig dsc = new DataSourceConfig();
dsc.setDbType(DbType.MYSQL);
dsc.setTypeConvert(new MySqlTypeConvert());
dsc.setUrl("jdbc:mysql://localhost:3306/mybatis_plus?characterEncoding=utf-8&serverTimezone=GMT%2B8&useSSL=false");
dsc.setDriverName("com.mysql.jdbc.Driver");
dsc.setUsername("root");
dsc.setPassword("123456");
mpg.setDataSource(dsc);
// 3. 包配置
PackageConfig pc = new PackageConfig();
pc.setEntity("entity");
pc.setController("controller");
pc.setMapper("mapper");
pc.setModuleName("user");
pc.setService("service");
pc.setParent("com.hrc");
mpg.setPackageInfo(pc);
// 4. 策略配置
StrategyConfig sc = new StrategyConfig();
sc.setNaming(NamingStrategy.underline_to_camel);
sc.setColumnNaming(NamingStrategy.underline_to_camel);
sc.setEntityLombokModel(true); // 是否使用lombok
sc.setLogicDeleteFieldName("deleted"); // 逻辑删除
// 字段自动填充
TableFill gmtCreate = new TableFill("gmt_create", FieldFill.INSERT);
TableFill gmtUpdate = new TableFill("gmt_update", FieldFill.INSERT_UPDATE);
List<TableFill> list = new ArrayList<>();
list.add(gmtCreate);
list.add(gmtUpdate);
sc.setTableFillList(list);
sc.setVersionFieldName("version"); // 乐观锁
sc.setRestControllerStyle(true);
sc.setControllerMappingHyphenStyle(true); // 是否为驼峰转连字符
mpg.setStrategy(sc);
mpg.execute(); // 执行
}
}
underline_to_camel);
sc.setColumnNaming(NamingStrategy.underline_to_camel);
sc.setEntityLombokModel(true); // 是否使用lombok
sc.setLogicDeleteFieldName("deleted"); // 逻辑删除
// 字段自动填充
TableFill gmtCreate = new TableFill("gmt_create", FieldFill.INSERT);
TableFill gmtUpdate = new TableFill("gmt_update", FieldFill.INSERT_UPDATE);
List<TableFill> list = new ArrayList<>();
list.add(gmtCreate);
list.add(gmtUpdate);
sc.setTableFillList(list);
sc.setVersionFieldName("version"); // 乐观锁
sc.setRestControllerStyle(true);
sc.setControllerMappingHyphenStyle(true); // 是否为驼峰转连字符
mpg.setStrategy(sc);
mpg.execute(); // 执行
}
}
本文地址:https://blog.csdn.net/weixin_46468474/article/details/110210608