说明
没接触过MyBatisPlus,昨天去B站找视频看,根据“狂神说Java”这个up主(此处穿插广告!这个up主的所有视频都很适合新手,通俗易懂,并且也有很多扩展,可以关注一波~~)发布的“MyBatisPlus最新完整教程通俗易懂”视频进行了学习,今天发现全都忘得差不多了(蛤蛤蛤)!所以再来刷一遍视频,自己跟着写一下代码,边复习边感受MyBatisPlus的魅力!此处只是用来记录学习过程!不说废话了,我开始了!
背景
首先呢,是去官网了解一下MyBatisPlus(此处抛个链接 MyBatisPlus官网指南)),之前一直听说这个可以省去很多的CRUD语句,简化开发,然后昨天看了看,果然名不虚传,而且我超级喜欢那个“代码生成器”,让我大开眼界!!
过程
(一)快速入门
- 主要是进行数据库的创建,这里就是根据官方的快速入门里面的数据库脚本创建好了数据库和一个user表,并且在配置文件中application.properties进行数据库的配置:
# 配置数据库 MySQL5
spring.datasource.url=jdbc:mysql://localhost:3306/mybatisplus?useSSL-false&useUnicode=true&characterEncoding=utf-8
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.username=root
spring.datasource.password=root
- 在IDEA里通过初始化器Spring Initializer来快速创建一个Springboot工程,并删除无关文件。
- 导入数据库、MyBatisPlus以及相关依赖(注意使用MyBatisPlus就不要导入MyBatis的依赖了,且视频教程中用的是3.0.5版本,所以我也是用这个版本来学习,很多功能后面的版本有了别的方式实现或者优化):
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.34</version>
</dependency>
<dependency>
<groupId>org.projectlomok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.0.5</version>
</dependency>
- 生成对应的实体类(第一次用lombok,感觉有很多实用的注解,注意lombok除了需要导入依赖还有别的配置,此处指路 link)
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private Long id;
private String name;
private Integer age;
private String email;
}
- 编写实体类对应的UserMapper接口(相当于MyBatis中的DAO层),并让这个接口继承BaseMapper(注意这里面BaseMapper后面要加实体类的泛型,我就是忘记加然后运行了很多次都没有成功,这是一个内置通用Mapper),这时候所有关于这个实体类的CRUD基本操作都已经编写好啦!:
//在对应的接口上继承BaseMapper类
@Repository
public interface UserMapper extends BaseMapper<User>{
//所有的CRUD都已经编写完成!
}
}
- 在SpringBoot的启动类上扫描这个mapper包,保证将mapper包下的所有XXXMapper接口都被扫描:
@MapperScan("com.wmr.mapper")
@SpringBootApplication
public class MybatisplusApplication {
public static void main(String[] args) {
SpringApplication.run(MybatisplusApplication.class, args);
}
}
- 进行一个小测试,在测试类里面注入UserMapper接口,直接调用里面关于CRUD的类,这里是查询全部的selectList()方法,这个方法需要一个Wrapper的参数(与条件查询有关),目前直接置为空即可:
@SpringBootTest
class MybatisplusApplicationTests {
@Autowired
private UserMapper userMapper;
@Test
void contextLoads() {
List<User> users = userMapper.selectList(null);
users.forEach(System.out::println);
}
在控制台可以看见输出结果:
- 如果想在控制台看到执行的SQL语句是什么,还需要配置日志,在配置文件中application.properties中配置,选择自带的默认日志即可:
# 配置日志,可以看到控制台输出的SQL执行语句
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
- 再次启动测试,看一下控制台输出,已经可以看到SQL和参数的信息了:
(二)CRUD的一些测试和扩展:
- 插入测试及主键生成策略
我也是按照视频中的测试方法进行测试,在不指定id的情况下,会自动生成一长串id,这个id是全局唯一id,也是默认生成id。
@Test
public void insertTest(){
User user = new User();
user.setName("zhangsan");
user.setAge(18);
user.setEmail("[email protected]");
int insert = userMapper.insert(user);
System.out.println(insert);
}
在视频中介绍了一些主键生成策略以及雪花算法。(这里指路分布式唯一id生成方案)
主键生成策略中介绍了一个注解@TableId(type = IdType.AUTO)
这个注解的参数中idType是个枚举类型,用来设置数据库中的主键生成方式。需要将注解加到实体类里面:
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
@TableId(type = IdType.AUTO)
private Long id;
private String name;
private Integer age;
private String email;
}
枚举类型的各个参数介绍:
AUTO(0),//数据库自增
NONE(1),//未设置主键
INPUT(2),//手动输入
ID_WORKER(3),//默认的全局唯一id
UUID(4),//全局唯一id uuid
ID_WORKER_STR(5);//ID_WORKER字符串表示法
- 更新测试与自动填充处理
对表中的记录进行更新,注意updateById()这个方法需要一个User对象而不是一个数字,经测试修改成功:
@Test
public void updateTest() {
User user = new User();
user.setName("lisi");
user.setAge(19);
user.setId(3l);
int i = userMapper.updateById(user);
System.out.println(i);
}
关于自动填充处理,视频中主要扩展了对于数据库的设计需要“创建时间”和“更新时间”,这两个字段一般是表的必备,并且需要自动化。
方式一数据库级别(有些项目或者公司不允许动数据库,这种方式就不可以了)
修改数据库中的表,添加两个字段,并且“更新时间”需要设定“根据当前时间戳更新”,然后对实体类进行更新,之后即可测试。
(失败了!原因是我用的MySQL5.5,不支持同一个表中两个字段的默认值都为CURRENT_TIMESTAMP~~哭了,所以我还是来尝试方式二吧!)
方式二代码级别
给数据库添加两个字段之后,不需要设置任何默认值:
之后对实体类进行更新,并且加上关于字段的注解,表示在插入的时候填写内容和插入更新都填写:
//字段添加填充内容
@TableField(fill = FieldFill.INSERT)
private Date createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
然后编写一个处理器来处理这个注解,这个处理器类需要实现MetaObjectHandler接口,然后把未完成的方法完成,具体看注释:
@Component//不要忘记把处理器加入到Ioc容器中
public class MyMetaObjectHandler implements MetaObjectHandler {
//插入时的填充策略
@Override
public void insertFill(MetaObject metaObject) {
//在插入时,两个字段都填写
//这个方法有三个参数,分别是要操作的字段,字段值,还有一个metaObject
this.setFieldValByName("createTime", new Date(), metaObject);
this.setFieldValByName("updateTime", new Date(), metaObject);
}
//修改时的填充策略
@Override
public void updateFill(MetaObject metaObject) {
//更新时,只更新更新时间的字段
this.setFieldValByName("updateTime", new Date(), metaObject);
}
}
最后测试一下插入和更新操作,测试成功!
- 乐观锁
我是第一次知道这个,这里就把老师讲的一模一样写过来,方便自己复习:
(乐观锁:认为不会出现问题,无论干什么都不上锁,出了问题,再次更新值测试
悲观锁:认为总是会出现问题,无论干什么都会上锁,再去操作)
乐观锁实现方式
取出记录时,获取当前version
更新时,带上这个version
执行更新时,set version = newVersion where version = oldVersion
如果version不对,就更新失败
乐观锁:1、先查询,获得版本号version=1
--A线程
update user set name = "ran",version = version+1
where id = 2 and version = 1
--B线程抢先完成,这个时候version = 2,会导致A修改失败
update user set name = "ran",version = version+1
where id = 2 and version = 1
下面将进行乐观锁的测试,首先给表加一个version字段并设置默认值为1:
然后对实体类进行更新并加@Version注解:
@Version //MP的Version注解,代表这是一个乐观锁
private Integer version;
在一个配置类中注册乐观锁插件(官网有XML和SpringBoot的方式介绍):
@MapperScan("com.wmr.mapper")
//代表自动管理事务的注解
@EnableTransactionManagement
//代表这是一个配置类
@Configuration
public class MPConfig {
//注册乐观锁插件
@Bean
public OptimisticLockerInterceptor optimisticLockerInterceptor() {
return new OptimisticLockerInterceptor();
}
}
之后即可进行测试
乐观锁成功测试
//测试乐观锁成功
@Test
public void optimisticLockerTest(){
User user = userMapper.selectById(2l);
user.setName("aaa");
int i = userMapper.updateById(user);
System.out.println(i);
}
可以看看控制台,发现在执行时已经进行了version的判断
数据库已经成功更新,并将version的值置为2:
乐观锁失败测试(多线程)
//测试乐观锁失败,模拟多线程
@Test
public void optimisticLockerTest2(){
//线程一
User user = userMapper.selectById(1l);
user.setName("bbb");
//模拟另一个线程执行插队操作
User user2 = userMapper.selectById(1l);
user2.setName("ccc");
userMapper.updateById(user2);
userMapper.updateById(user);//如果没有乐观锁就会覆盖插队线程的值
}
执行结果:
会发现最终结果是插队线程设定的值而不是线程一,如没有设置乐观锁,那么插队线程的值就会被覆盖。
- 查询操作
这里省略map查询和批量查询等,只需要调用相关方法,传入参数即可。
MP的另一个优点就是内置了分页插件:
首先同样在配置类里导入分页插件(官网可查):
//注册分页插件
@Bean
public PaginationInterceptor paginationInterceptor() {
return new PaginationInterceptor();
}
然后就可以通过Page对象直接使用了(注意是MP的Page对象)!
//测试分页查询
public void pageTest(){
//参数一:当前页
//参数二:页面大小
Page<User> page = new Page<>(1,3);
userMapper.selectPage(page,null);
page.getRecords().forEach(System.out::println);
}
可以看到查询结果成功:
- 删除操作
省略基本删除操作、批量删除、map删除,可自行查看方法并进行测试
下面介绍的是逻辑删除(挺有意思,让我不敢随便在网上冲浪了~~)
物理删除:从数据库中删除
逻辑删除:在数据库中没有被移除,而是通过一个变量来让他失效
deleted = 0 ==> deleted = 1
首先在表中增加一个deleted字段,默认值为0.
然后更新实体类,并增加逻辑删除的注解:
@TableLogic //逻辑删除注解
private Integer deleted;
注册逻辑删除组件:
//注册逻辑删除组件
@Bean
public ISqlInjector SqlInjector() {
return new LogicSqlInjector();
}
在application.properties文件中进行逻辑删除的相关配置(现在的版本与现在的步骤不同,详见官网文档):
# 配置逻辑删除
mybatis-plus.global-config.db-config.logic-delete-value=1
mybatis-plus.global-config.db-config.logic-not-delete-value=0
之后进行测试,可以看到即使使用的是删除方法,执行的却是更新操作:
但是之后再对同一条记录进行查询,已经查不到了,但是数据库中还是有记录的,只不过deleted变成了1,这个可以用来实现管理员功能,管理员依旧可以查看用户删除的信息,类似于一个“垃圾桶”:
- Wrapper与SQL性能测试课自行配置和测试:
//Wrapper测试
@Test
void contextLoads3() {
QueryWrapper queryWrapper = new QueryWrapper();
queryWrapper.eq("name","aaa");
User user = userMapper.selectOne(queryWrapper);
System.out.println(user);
}
wrapper可以进行各种各样的条件查询,MP真的很强大,一些简单的单表SQL已经不需要我们编写和配置xml文件,直接调用方法就可以实现。
(三)代码自动生成器
这是我觉得让我这个菜鸟发现新大陆的东西!
试用成功,我好爱这个功能嗷!吹爆!
这篇太长了,另外写一篇把代码自动生成器的配置总结一下~