十、MybatisPlus
总结:
① mapper接口:mapper接口中无需写增删改查方法。只需要继承BaseMapper接口;(里面自动帮忙写好了基本的增删改查方法,需要时可以直接调用 “xxxMapper.方法名” 直接调用即可)
② mapper.xml配置文件:mapper.xml配置文件无需创建。使用条件构造器Wrapper帮忙拼接复杂的sql语句,无需创建mapper接口对应的mapper.xml配置文件编写sql语句了;
③ 实体类上添加注解与数据库红表和字段绑定:表名:@TableName(“ACC_POS”)、主键:@TableId(“ID”)、字段@TableField(“CRT_DT”)
MyBatis-Plus(简称 MP)是一个MyBatis的增强工具,在MyBatis的基础上只做增强不做改变,为简化开发 提高效率而生.
一、配置:application.properties配置文件中配置mysql的连接、添加日志的配置;
二、创建实体类:
@TableId(type = IdType.AUTO)//id自增
//@TableId(type = IdType.INPUT)//一旦手动输入id之后,就需要自己设置id了(即测试类MybatisPlusApplicationTests中testInsert()方法new User时需要setId)
private Long id;
@Version//MP的乐观锁的version注解
private Integer version;
@TableLogic//逻辑删除注解
private Integer deleted;
//字段添加填充内容
@TableField(fill = FieldFill.INSERT)//在插入时填充
private Date createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)//在插入和更新时填充
private Date updateTime;
三、新建mapper接口:继承BaseMapper基类、添加@Repository注解;(无需创建UserMapper接口对应的UserMapper.xml文件);
四、启动类上加注解:MybatisPlusApplication启动类上添加@MapperScan(“com.asd.mapper”)注解扫描mapper包;
五、测试类进行测试:注入userMapper,调用其extends的BaseMapper中已有的一些CRUD方法。
wrapper.isNotNull("name"):name不为null的
wrapper.eq("name", "Jack"):name为Jack的
wrapper.ge("age", 12):age大于等于12的
wrapper.in("age", 20,30,40):age在"20,30,40"几个数字当中的
wrapper.between("age", 20, 30):age在20-30岁之间的
wrapper.like("name", "p"):name中包含p的
wrapper.notLike("name", "e"):name中不包含e的
wrapper.orderByDesc("id"):orderByDesc降序,根据id进行降序
wrapper.likeRight("email", "t"):(%在右边)表示 t%,表示以t开头
wrapper.inSql("id", "select id from user where id<3"):inSql表示id是在子查询中查询出来的 where-in查询
wrapper.orderByAsc("code"):升序
wrapper.orderByDesc("code"):降序
User user = userMapper.selectOne(wrapper);
Integer count = userMapper.selectCount(wrapper);
userMapper.selectList(wrapper).forEach(System.out::println);
userMapper.selectMaps(wrapper).forEach(System.out::println);
userMapper.selectObjs(wrapper).forEach(System.out::println);
List<User> users=userMapper.selectList(wrapper);
users.forEach(System.out::println);
List<Map<String, Object>> maps = userMapper.selectMaps(wrapper);
maps.forEach(System.out::println);//对其进行foreach
List<Object> objects = userMapper.selectObjs(wrapper);//selectObjs查询的是一些对象
objects.forEach(System.out::println);
详细参考如下:
一、配置
①application.properties配置文件中配置mysql的连接、添加日志的配置;(②idea中连接数据库:右方Database——+Data Source—MySql——输入User、Password(Database不用输)——Test Connection;)
# 端口号
server.port=9000
# 环境设置
spring.profiles.active=dev
# 数据库连接配置
#mysql 5 驱动不同:com.mysql.jdbc.Driver
#mysql 8 驱动不同:com.mysql.cj.jdbc.Driver、需要增加时区的设置
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.url=jdbc:mysql://localhost:3306/mybatis_plus?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# 配置日志(此处添加默认日志;若其他日志如log4j则需要添加相应的依赖)
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
二、创建实体类
1、有关主键生成策略:
分布式系统唯一id生成方案:uuid、自增id、雪花算法、redis生成、Zookeeper生成;
【1】雪花算法:此处(即MybatisPlusApplicationTests类中testInsert()方法没有setId,实体类User的id上未添加@TableId(type= IdType.AUTO))会自动生成id使用雪花算法:雪花算法snowflake是Twitter开源的分布式ID生成算法,结果是一个long型的ID。其核心思想是:使用41bit作为毫秒数,10bit作为机器的ID(5个bit是数据中心,5个bit的机器ID),12bit作为毫秒内的流水号(意味着每个节点在每毫秒可以产生 4096 个 ID),最后还有一个符号位,永远是0。可以保证几乎全球唯一。
【2】主键自增:
第一步:实体类User的id字段上添加@TableId(type=IdType.AUTO)注解(其type可设不同值,当值为IdType.AUTO表示主键自增);
第二步:数据库需要自增的字段上勾选自增;(实体类上加上面注解,数据库中字段不勾选自增会报错);
IdType的值:
public enum IdType {
AUTO(0), //数据库id自增
NONE(1), //未设置主键
INPUT(2), //手动输入
ID_WORKER(3),//默认的全局id(数字)
UUID(4), // 全局唯一id
ID_WORKER_STR(5);//截取字符串 ID_WORKER的字符串表示(字符串)
}
【3】手动输入:
第一步:将实体类User的id字段上注解@TableId(type=IdType.INPUT)中改为INPUT;
第二步:测试类MybatisPlusApplicationTests中testInsert()方法new User时需要setId值(user.setId(6L)😉(一旦手动输入id之后,就需要自己设置id了,
测试类MybatisPlusApplicationTests中testInsert()方法若没有setId,User(id=null, name=lala, age=3, email=123456@qq.com))
2、(时间日期等字段的)自动填充:
创建时间、修改时间,gmt_create、gmt_modified几乎所有的表都要配置上,而且需要自动化,不希望手动更新;
【方式一】:数据库级别(因为修改数据库了,工作中不建议使用)
1.在表中新增字段create_time,update_time,类型为datetime;勾选下方 根据当前时间戳更新,输入设置的默认值为 CURRENT_TIMESTAMP;
2.实体类直接同步,添加字段,类型为Date;
3.测试查看更新结果;
【方式二】:代码级别
1.删除数据库的默认值、更新操作((根据当前时间戳更新)自动更新取消勾选,设置的默认值删除);
2.实体类的字段createTime、updateTime上添加 @TableField(fill= FieldFill.INSERT)注解:(fill= FieldFill.INSERT)在插入时填充,(fill = FieldFill.INSERT_UPDATE)在插入和更新时填充
public enum FieldFill {
DEFAULT, //默认不填充
INSERT, //在插入时填充
UPDATE, //在更新时填充
INSERT_UPDATE;//在插入和更新时填充
}
3.编写处理器来处理注解
新建handler包,编写MyMetaObjectHandler处理器类;
①此处理器要加入到IOC容器中被识别,需要在类上加@Component注解; 用到日志需要在类上加@Slf4j注解;
②此类implements MetaObjectHandler 实现MetaObjectHandler,实现里面的insertFill、updateFill方法;
方法中调用this.setFieldValByName(String fieldName, Object fieldVal, MetaObject metaObject) 参数:(想修改的字段名、想插入的字段值、想给哪个参数处理)
4.实体类User的id字段上改为@TableId(type= IdType.AUTO)自增再在test类中进行插入、更新测试,观察时间的填充和变化;
3、乐观锁:
乐观锁:十分乐观,总是认为不会出现问题,无论干什么都不去上锁,如果出现问题,在此更新测试;
悲观锁:十分悲观,认为总是出现问题,无论干什么都会上锁,在去操作;
乐观锁的实现方式:
1.取出记录时,获取当前版本;
2.更新时,带上这个version;
3.执行更新时,set version=newVersion where version=oldversion
4.如果version不对,就更新失败;
eg:
(1.先查询获取版本号为version=1;2.更新时,id条件后带上这个version;3.执行更新时把version由原来的版本改为最新的版本,一般会自动+1;4.如果version不对,就更新失败)
-----A(线程)
update user set name='lala',version=version+1
where id=2 and version=1
-----B(线程)抢先于A线程完成,此时version=2了,会导致A线程修改失败!
update user set name='lala',version=version+1
where id=2 and version=1
测试MP(mybatis plus)的乐观锁插件
1.表中新增version字段,并设置默认值都为1;
2.实体类加对应的字段version,并在字段上添加@version注解(mybatis plus的乐观锁的version注解);
3.注册组件:新建config包,创建MyBatisPlusConfig配置类:
①在类上加@Confguration注解表示这是一个配置类;(有事务的控制还可以加上一个@EnableTransactionManagement注解自动管理事务);
若在此配置类上管理mybatis plus,则可以将启动类上的包扫描(是交给mybatis做的),不放在启动类上了,放在此配置类上;
②类中注册乐观锁插件;
此时乐观锁插件配置完毕;
4.测试类中进行测试;
4、删除操作:
Test测试类中删除测试:deleteById(id)根据id删除、deleteBatchIds(idLists)根据批量id删除、deleteByMap(map)根据map封装和删除条件删除;
【逻辑删除】:(本质是更新操作,不是删除!)
物理删除:从数据库中直接移除;
逻辑删除:在数据库中没有被移除,而是通过一个变量让他失效;(如:isDel=0 =>isDel=1,运用例子:管理员可以查看被删除的记录)防止数据的丢失
1.在数据表中添加一个deleted字段,设置一个默认值 为0;
2.实体类中增加deleted字段并添加注解@TableLogic(逻辑删除注解);
3.配置:MyBatisPlusConfig配置类中添加@Bean逻辑删除组件、application.properties属性文件中添加逻辑删除配置;
4.测试删除(deleteById(id)此时记录仍在,只是deleted字段值由0变为1,即由未删除状态变为已删除状态);
(逻辑删除之后,再进行selectById(id)查询操作,会查询不到此数据,因为加上上方一系列逻辑删除操作之后,查询时会自动拼接 AND deleted=0查询条件
SELECT id,name,age,email,version,deleted,create_time,update_time FROM user WHERE id=? AND deleted=0;
查询时会自动过滤掉被逻辑删除的字段)
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
//id对应数据库中的主键(uuid、自增id、雪花算法、redis生成、Zookpeer生成)
@TableId(type = IdType.AUTO)//id自增
//@TableId(type = IdType.INPUT)//一旦手动输入id之后,就需要自己设置id了(即测试类MybatisPlusApplicationTests中testInsert()方法new User时需要setId)
private Long id;
private String name;
private Integer age;
private String email;
@Version//MP的乐观锁的version注解
private Integer version;
@TableLogic//逻辑删除注解
private Integer deleted;
//字段添加填充内容
@TableField(fill = FieldFill.INSERT)//在插入时填充
private Date createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)//在插入和更新时填充
private Date updateTime;
}
三、新建mapper接口
//①在对应的Mapper上继承基本的接口BaseMapper (无须再写对应的UserMapper.xml配置文件了)——②后面再在测试类上加@MapperScan注解扫描mapper文件夹
@Repository
public interface UserMapper extends BaseMapper<User> {
//所有的CRUD都编写完成了(BaseMapper已经帮忙写好了)
}
四、启动类上加注解:MybatisPlusApplication启动类上添加@MapperScan(“com.asd.mapper”)注解扫描mapper包;
//加此注解扫描mapper文件夹
/*@MapperScan("com.asd.mapper")*/
@SpringBootApplication
public class MybatisPlusApplication {
public static void main(String[] args) {
SpringApplication.run(MybatisPlusApplication.class, args);
}
}
五、测试类进行测试:注入userMapper,调用其extends的BaseMapper中已有的一些CRUD方法。
@SpringBootTest
public class WapperTest {
@Autowired
private UserMapper userMapper;
//查询name不为null的用户,并且邮箱不为空的用户,年龄大于等于12岁的
@Test
void contextLoads() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper
.isNotNull("name")
.isNotNull("email")
.ge("age", 12);//g大于 e等于
userMapper.selectList(wrapper).forEach(System.out::println);
//List<User> users=userMapper.selectList(wrapper);//和map对比
//users.forEach(System.out::println);
/*和map对比:
map直接put条件值;
wrapper是一个对象,对象不能直接put,他有自己的很多的方法,可以直接使用其方法赋条件。*/
}
//查询年龄在"20,30,40"几个数字当中的,名字为Jack的
@Test
public void test2() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.in("age", 20,30,40);
wrapper.eq("name", "Jack");
User user = userMapper.selectOne(wrapper);//selectOne查询一个数据,出现多个结果使用list或者map
System.out.println(user);
}
//查询年龄在20-30岁之间的用户
@Test
public void test3() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.between("age", 20, 30);
Integer count = userMapper.selectCount(wrapper);//selectCount查询结果数,返回一个Integer类型 数量
System.out.println(count);
}
//模糊查询:查询name中不包含e,并且email以t开头的
@Test
public void test4() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
//likeLeft、likeRight, 左和右如何区分,代表%在左边还是右边 %e或者e%,若两边都要匹配%e%
wrapper
.notLike("name", "e")//notLike表示name中不包含e
.like("name","p")//名字中包含p的
.likeRight("email", "t");//表示 t%,表示以t开头
wrapper.between("age", 20, 30);
List<Map<String, Object>> maps = userMapper.selectMaps(wrapper);
maps.forEach(System.out::println);//对其进行foreach
}
//sql中嵌套sql
@Test
public void test5() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
//inSql表示id是在子查询中查询出来的 where-in查询
wrapper.inSql("id", "select id from user where id<3");
List<Object> objects = userMapper.selectObjs(wrapper);//selectObjs查询的是一些对象
objects.forEach(System.out::println);
}
//排序
@Test
public void test6() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
//根据id进行排序
wrapper.orderByDesc("id");//orderByDesc降序
List<User> users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}
}
六、分页查询:
(参考:https://blog.csdn.net/womenyiqilalala/article/details/95073892
MybatisPlus其实也内置了分页插件;如何使用?:
1.配置文件中配置 PaginationInterceptor拦截器组件;
2.再直接使用Page(或IPage)对象即可;
1.MyBatisPlusConfig 配置文件中:
@MapperScan("com.asd.mapper")//mapper文件夹扫描
@EnableTransactionManagement//自动管理事务的注解 默认开启
@Configuration//配置类
public class MyBatisPlusConfig {
//分页插件(配置拦截器)
@Bean
public PaginationInterceptor paginationInterceptor() {
return new PaginationInterceptor();
}
}
2.使用:
@Resource
private UserMapper userMapper;
//测试分页查询1
@Test
public void testPage() {
//参数一:当前页码、参数二:页面大小(显示的条数)
Page<User> page = new Page<>(2, 5);//查询第1页,每页5个 /查询第2页,每页5个
IPage<User> userIPage = userMapper.selectPage(page, null);
System.out.println("总页数: "+userIPage.getPages());
System.out.println("总记录数: "+userIPage.getTotal());
userIPage.getRecords().forEach(System.out::println);
List<User> list=userIPage.getRecords();
for(User user : list){
System.out.println(user);//输出对象
}
}
//测试分页查询2
@Test
public void testPage2(){
//方法参数一:page
Page<User> page = new Page<>(1 , 2);
//方法参数二:wrapper
QueryWrapper<User> wrapper =new QueryWrapper<>();
wrapper.like("age", 9);
//调用
IPage<User> userIPage = userMapper.selectPage(page , wrapper);
System.out.println("总页数: "+userIPage.getPages());
System.out.println("总记录数: "+userIPage.getTotal());
userIPage.getRecords().forEach(System.out::println);
}
七、其他:
6.2 条件构造器Wrapper
写一些复杂的sql就可以使用条件构造器来替代;
和map对比(都是new出来作为查询条件传入):
map直接put条件值;
wrapper是一个对象,对象不能直接put,他有自己的很多的方法,可以直接使用其方法赋条件。
6.3 代码自动生成器
AutoGenerator 是 MyBatis-Plus 的代码生成器,通过 AutoGenerator 可以快速生成 Entity、Mapper、Mapper XML、
Service、Controller 等各个模块的代码,极大的提升了开发效率。
dao、pojo、service、controller都自己生成。
1.pom.xml中引入mybatis-plus的依赖;
2.application.properties进行配置:服务端口、开发环境、连接数据库的配置等;
3.编写一个类让其帮忙自动生成pojo…等其他类;
test包下创建一个GenerateCode.java类(即代码自动生成器)
注:自动生成代码时报错:引入 “模板引擎” 依赖!
测试自动生成代码在项目:“E:\IdeaWorkSpace\springboot_mp_jdbc\easyexcel_work_test”————
"MybatisPlusGenerator.java"中!
6.4 性能分析插件
在平时开发中会遇到一些慢sql(慢查询),可通过测试、压测、druid…等工具进行操作;
MP也提供了一款性能分析插件,如果超过这个时间就停止运行;
作用:分析每条sql执行的时间. 性能分析拦截器,用于输出每条sql语句及其执行时间;
1.导入插件;
即配置:MyBatisPlusConfig配置类中添加@Bean性能分析插件(即sql执行效率插件)、application.properties属性文件中设置开发环境;
(配置完后此插件即可生效,但直接return new PerformanceInterceptor();是无法看到功能的;
可以在new出一个对象之后setMaxTime(1)设置sql执行的最大时间为1ms(默认单位ms),如果超过了不执行、以及开启sql格式化的格式化支持)
2.测试使用;
Test中测试查询全部的用户contextLoads;
(结果:运行报错
Cause: com.asd.mybatis_plus.core.exceptions.MybatisPlusException:The SQL execution time is too large, please optimize !
因为sql执行时间设置为1ms太短,sql实际的执行时间超过规定的时间所以抛异常,需要将时间设置变长.)