让自己变得稀缺的最好的办法就是让自己拥有多维度的能力,这种把多种能力结合在一起,并且为同一个目标服务,就会发挥巨大的价值。 --------《认知红利》
mybatisplus官网 (MP)对MyBatis进一步简化
创建springboot工程mybatis-plus
1、核心依赖如下
<dependency> <groupId>com.baomidougroupId> <artifactId>mybatis-plus-boot-starterartifactId> <version>3.3.1version> dependency> <dependency> <groupId>mysqlgroupId> <artifactId>mysql-connector-javaartifactId> 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=1234563、启动类
在 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
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了
目前,我们的user表中的字段如下,值得注意的是表的id并不是自增的
执行如下代码,会发现很有意思的
现象:我们在添加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)
现象:根据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.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
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(){ HashMapmap = new HashMap<>(); // 列名 map.put("name", "悠米"); map.put("age", 28); Listusers = 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() { Pagepage = 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.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
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); }
拼接各种条件(查询条件、删除条件、修改条件)
QueryWrapper
注意:以下条件构造器的方法入参中的 column
均表示数据库字段。
- @Test
- public void testSelect() {
-
- QueryWrapper
queryWrapper = new QueryWrapper<>(); - //年龄大于等于25岁的
- queryWrapper.ge("age", 25);
- List
users = userMapper.selectList(queryWrapper); - //int delete = userMapper.delete(queryWrapper);
- System.out.println(users);
- }
ne 不等于
- @Test
- public void testSelectOne() {
-
- QueryWrapper
queryWrapper = new QueryWrapper<>(); - queryWrapper.eq("name", "飘絮");
- //seletOne返回的是一条实体记录,当出现多条时会报错
- User user = userMapper.selectOne(queryWrapper);
- System.out.println(user);
- }
- @Test
- public void testSelectCount() {
-
- QueryWrapper
queryWrapper = new QueryWrapper<>(); - //between 包含大小边界
- queryWrapper.between("age", 20, 30);
-
- Integer count = userMapper.selectCount(queryWrapper);
- System.out.println(count);
- }
- @Test
- public void testLike(){
-
- QueryWrapper
userQueryWrapper = new QueryWrapper<>(); - // userQueryWrapper.like("name","d");// %d%
- // userQueryWrapper.likeLeft("name","d"); //%d
- userQueryWrapper.likeRight("name","米"); // d%
-
- List
users = userMapper.selectList(userQueryWrapper); - System.out.println(users);
- }
- @Test
- public void testSelectListOrderBy() {
-
- QueryWrapper
queryWrapper = new QueryWrapper<>(); - queryWrapper.orderByDesc("age");
-
- List
users = userMapper.selectList(queryWrapper); - users.forEach(System.out::println);
- }
- @Test
- public void testSelectListLast() {
-
- QueryWrapper
queryWrapper = new QueryWrapper<>(); - //直接拼接到 sql 的最后,只能调用一次,多次调用以最后一次为准
- queryWrapper.last("limit 3");
- //sql注入的风险:永远为真的条件
- //queryWrapper.last(" or 1=1");
-
- List
users = userMapper.selectList(queryWrapper); - users.forEach(System.out::println);
- }
- @Test
- public void testSelectListColumn() {
-
- QueryWrapper
queryWrapper = new QueryWrapper<>(); - // select 限制返回的字段
- queryWrapper.select("id", "name", "age");
- List
users = userMapper.selectList(queryWrapper); - users.forEach(System.out::println);
- }