让自己变得稀缺的最好的办法就是让自己拥有多维度的能力,这种把多种能力结合在一起,并且为同一个目标服务,就会发挥巨大的价值。? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? --------《认知红利》
mybatisplus官网?(MP)对MyBatis进一步简化
MyBatis-Plus
1、MybatisPlus入门案例
创建springboot工程mybatis-plus
1、核心依赖如下
<!--mybatis-plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.3.1</version>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
2、在application.properties配置文件中添加 MySQL 数据库的相关配置
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/mybatis_plus?serverTimezone=GMT%2B8
spring.datasource.username=root
spring.datasource.password=123456
3、启动类
在 Spring Boot 启动类中添加?@MapperScan ?注解,扫描 Mapper 文件夹
在mapper文件夹下编写Mapper接口,因为我们已经在启动类上加了@MapperScan注解,所以这里的mapper接口什么都不写也没啥问题,至于@Repository的作用,下面会说
单元测试
测试类的包名称要和启动类的包名称一致
IDEA在 userMapper 处报错,因为找不到注入的对象,因为类是动态创建的,但是程序可以正确的执行。
为了避免报错,可以在 dao 层 的接口上添加?@Repository?注解
import org.junit.jupiter.api.Test;
@SpringBootTest
public class MybatisPlusApplicationTests {
@Autowired
private UserMapper userMapper;
@Test
public void test1(){
System.out.println(("----- selectAll method test ------"));
//UserMapper 中的 selectList() 方法的参数为 MP 内置的条件封装
//所以不填写就是无任何条件
List<User> users = userMapper.selectList(null);
users.forEach(System.out::println);
}
}
测试结果:
为了观察sql,我们加入以下配置到application.properties
#mybatis日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
此时再运行,控制台就可打印sql了
2、MybatisPlus的CRUD
目前,我们的user表中的字段如下,值得注意的是表的id并不是自增的?
1.插入操作-主键生成策略
执行如下代码,会发现很有意思的
现象:我们在添加user的时候并没有设置id,且数据库的id也不是自增的,但是最后一行的user对象中id却有输出
@Test
public void testInsert(){
User user = new User();
user.setName("谷小姐");
user.setAge(18);
user.setEmail("6665559999@qq.com");
int result = userMapper.insert(user);
System.out.println(result); //影响的行数
System.out.println(user); //id自动回填
}
控制台打印如下:
是因为mybatisplus默认实现自动回填id,这个id的生成策略又是什么呢
主键生成策略:
可以通过@TableId注解的 type属性来设置主键id的生成策略
默认使用如下:
@TableId(type = IdType.ASSIGN_ID)
private Long id;
(1)ASSIGN_ID(雪花算法)
? ??? ??如果不设置类型值,默认则使用IdType.ASSIGN_ID策略(自3.3.0起)。该策略会使用雪花算法自动生成主键ID,主键类型为长整型或字符串(分别对应的MySQL的表字段为BIGINT和VARCHAR),确保分布式系统的一个唯一id值
(2)ASSIGN_UUID(排除中划线的UUID)
? ??? ??如果使用IdType.ASSIGN_UUID策略,并重新自动生成排除中划线?的UUID作为主键。主键类型为String,对应MySQL字段为VARCHAR(32),包含(-)的话UUID是36长度的,(-)占了4个。
(3)AUTO(数据库ID自增)
? ??? ??对于像MySQL这样的支持主键自动递增的数据库,我们可以使用IdType.AUTO策略。 但是要求数据库字段是整形的
(4)INPUT(插入前自行设置主键值)
(5)NONE(无状态)
? ??? ??如果使用IdType.NONE策略,表示未设置主键类型,使用默认的规则
------------------------
补充:
雪花算法(SnowFlake)是 Twitter 开源的分布式 id 生成算法,在分布式环境下产生不重复的id。使用一个 64 bit 的 long 型的数字作为全局唯一 id。 公式: 0 (最高位预留 1位) + 时间戳 (41 位) + 机器 ID (10 位) + 随机序列 (12 位) = 64 bit (long类型的值,最长19个数,例如1545580694141997057)
2.?更新操作-自动填充-版本号修改
现象:根据id修改名字的时候,只传了id和名字,拼的是动态sql,只修改名字,并不是完全的覆盖。
@Test
public void testUpdateById(){
User user = new User();
user.setId(1553211962987388930L);
user.setAge(30);
//UPDATE user SET age=? WHERE id=?
int result = userMapper.updateById(user);
System.out.println(result);
}
自动填充
项目中经常会遇到一些数据,每次都使用相同的方式填充,例如记录的创建时间,更新时间等。
我们可以使用MyBatis Plus的自动填充功能,完成这些字段的赋值工作:
1.在数据库表user表添加datetime类型的新字段create_time、update_time,在实体类也添加对应的属性,命名格式:驼峰命名
import java.util.Date;
@Data
public class User {
......
//添加
@TableField(fill = FieldFill.INSERT)
private Date createTime;
//@TableField(fill = FieldFill.UPDATE)
//添加修改
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
}
2.在实体类上对应的属性上添加注解 配置自动填充
3.实现元对象处理器接口
MyMetaObjectHandler是随便起的名字,要注意加@Component注解
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import java.util.Date;
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(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);
}
}
测试添加功能时自动填充:
@Test
public void testInsert(){
User user = new User();
user.setName("悠米");
user.setAge(19);
user.setEmail("ym_888@qq.com");
int result = userMapper.insert(user);
}
打印的sql日志
数据库变化
测试修改功能的自动填充
@Test
public void testUpdateById(){
User user = new User();
user.setId(1553547529604321282L);
user.setAge(24);
int result = userMapper.updateById(user);
}
打印sql日志
数据库变化?????
乐观锁-版本号修改
当要更新一条记录的时候,希望这条记录没有被别人更新,也就是说实现线程安全的数据更新。
乐观锁:在高并发的情况下保证数据的安全
1.数据库表user添加字段version,【加默认值0或者使用@TableField,这里使用默认值】
?2.实体类加字段,字段上加注解@Version
@Version
private Integer version;
3.配置乐观锁的插件
在 MybatisPlusConfig(自定义配置类) 中注册 OptimisticLockerInterceptor 锁的拦截器,?也可以直接在启动类配置乐观锁的插件。我们选择在配置类中加入乐观锁的插件
@SpringBootConfiguration
public class MybatisPlusConfig {
/**
* 乐观锁插件
*/
@Bean
public OptimisticLockerInterceptor optimisticLockerInterceptor() {
return new OptimisticLockerInterceptor();
}
}
在测试类得这么测试,不要像上面那样直接new一个user,然后修改。一定要先去查询
@Test
public void testUpdateById(){
//查询
User user = userMapper.selectById(1553554443532869633L);
//修改数据
user.setName("Helen Yao");
user.setEmail("helen@qq.com");
//执行更新
userMapper.updateById(user);
}
?实现方式
- 取出记录时,获取当前version
- 更新时,带上这个version
- 执行更新时, set version = newVersion where version = oldVersion
- 如果version不对,就更新失败
看源码可知: Long或者int,版本号则加1? ; 时间类型则返回当前时间
3.查询(id查询、批量查询、条件查询、分页查询)
3.1 id查询
@Test
public void testSelectById(){
//根据id查询记录
User user = userMapper.selectById(1L);
System.out.println(user);
}
?3.2 多个id批量查询
@Test
public void testSelectBatchIds(){
//通过多个id批量查询
List<User> users = userMapper.selectBatchIds(Arrays.asList(1, 2, 3));
//java的写法要会
users.forEach(System.out::println);
}
?3.3 map条件查询
通过map封装查询条件
注意:map中的key对应的是数据库中的列名。例如数据库user_id,实体类是userId,这时map的key需要填写user_id
@Test
public void testSelectByMap(){
HashMap<String, Object> map = new HashMap<>();
// 列名
map.put("name", "悠米");
map.put("age", 28);
List<User> users = userMapper.selectByMap(map);
users.forEach(System.out::println);
}
3.4 Page?分页查询
MyBatis Plus自带分页插件,只要简单的配置即可实现分页功能
在MybatisPlusConfig配置类中加入分页插件配置,也可以写在启动类
/**
* 分页插件
*/
@Bean
public PaginationInterceptor paginationInterceptor() {
return new PaginationInterceptor();
}
测试:最终通过page对象获取相关数据
查询第一页,每页5条数据
@Test
public void testSelectPage() {
Page<User> page = new Page<>(1,5);
//第二个参数表示查询条件,null表示查询所有
userMapper.selectPage(page, null);
page.getRecords().forEach(System.out::println);
}
limit m,n? ? ? ? ? m:从第几条开始? ?n:查询多少条
page对象?里还有很多关于分页的信息,列如:
System.out.println(page.getCurrent());//当前第几页
System.out.println(page.getPages());//总页数
System.out.println(page.getSize());//每页显示数量
System.out.println(page.getTotal());//总记录数
System.out.println(page.hasNext());//是否有下一页
System.out.println(page.hasPrevious());//是否有上一页
4.删除(id删除、批量删除、条件删除、逻辑删除)
4.1 id删除
@Test
public void testDeleteById(){
//根据id删除 【物理删除】
int result = userMapper.deleteById(1L);
System.out.println(result);
}
?4.2 多个id批量删除
@Test
public void testDeleteBatchIds() {
//多个id批量删除
int result = userMapper.deleteBatchIds(Arrays.asList(3, 4, 5));
System.out.println(result);
}
?4.3 条件查询删除
@Test
public void testDeleteByMap() {
HashMap<String, Object> map = new HashMap<>();
map.put("name", "Helen");
map.put("age", 18);
//简单的条件查询删除
int result = userMapper.deleteByMap(map);
System.out.println(result);
}
4.4 逻辑删除
- 物理删除:真实删除,将对应数据从数据库中删除,之后查询不到此条被删除数据
- 逻辑删除:假删除,将对应数据中代表是否被删除字段状态修改为“被删除状态”,之后在数据库中仍旧能看到此条数据记录
1、数据库表user中添加字段deleted(或者is_deleted)
2、实体类添加deleted?字段和@TableLogic 注解
@TableLogic
private Integer deleted;
3、application.properties?配置是否被删除的逻辑值
此为默认值,如果你的默认值和mp默认的一样,该配置可无。
mybatis-plus.global-config.db-config.logic-delete-value=1
mybatis-plus.global-config.db-config.logic-not-delete-value=0
测试逻辑删除
/**
* 测试 逻辑删除
*/
@Test
public void testLogicDelete() {
int result = userMapper.deleteById(2L);
System.out.println(result);
}
注意:被删除数据的deleted 字段的值必须是 0,才能被选取出来执行逻辑删除的操作
此时再去查询该条数据
@Test
public void testSelectById(){
User user = userMapper.selectById(2L);
System.out.println(user);
}
3、MybatisPlus的条件构造器
拼接各种条件(查询条件、删除条件、修改条件)
QueryWrapper
注意:以下条件构造器的方法入参中的?column? 均表示数据库字段。
1、ge、gt、le、lt、isNull、isNotNull
@Test
public void testSelect() {
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
//年龄大于等于25岁的
queryWrapper.ge("age", 25);
List<User> users = userMapper.selectList(queryWrapper);
//int delete = userMapper.delete(queryWrapper);
System.out.println(users);
}
2、eq、ne
ne 不等于
@Test
public void testSelectOne() {
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("name", "飘絮");
//seletOne返回的是一条实体记录,当出现多条时会报错
User user = userMapper.selectOne(queryWrapper);
System.out.println(user);
}
3、between、notBetween
@Test
public void testSelectCount() {
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
//between 包含大小边界
queryWrapper.between("age", 20, 30);
Integer count = userMapper.selectCount(queryWrapper);
System.out.println(count);
}
4、like、notLike、likeLeft、likeRight
@Test
public void testLike(){
QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
// userQueryWrapper.like("name","d");// %d%
// userQueryWrapper.likeLeft("name","d"); //%d
userQueryWrapper.likeRight("name","米"); // d%
List<User> users = userMapper.selectList(userQueryWrapper);
System.out.println(users);
}
5、orderBy、orderByDesc、orderByAsc
@Test
public void testSelectListOrderBy() {
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.orderByDesc("age");
List<User> users = userMapper.selectList(queryWrapper);
users.forEach(System.out::println);
}
6、last
@Test
public void testSelectListLast() {
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
//直接拼接到 sql 的最后,只能调用一次,多次调用以最后一次为准
queryWrapper.last("limit 3");
//sql注入的风险:永远为真的条件
//queryWrapper.last(" or 1=1");
List<User> users = userMapper.selectList(queryWrapper);
users.forEach(System.out::println);
}
???????
7、指定要查询的列
@Test
public void testSelectListColumn() {
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
// select 限制返回的字段
queryWrapper.select("id", "name", "age");
List<User> users = userMapper.selectList(queryWrapper);
users.forEach(System.out::println);
}
|