Mybatis-Plus
一、简介
官方文档:https://baomidou.com/pages/24112f/
二、入门案例
1、开发环境
IDE:idea
JDK:JDK8+
构建工具:maven
数据库:MySQL
SpringBoot
MyBatis-Plus
2、创建数据库以及表
创建数据库表
3、创建Spring Boot工程
a>初始化工程
添加依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
配置数据源
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/mybatis_plus?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=UTC
username: root
password:
创建实体类
package com.kj.mybatisplus.pojo;
import lombok.*;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private Long id;
private String name;
private Integer age;
private String email;
}
创建接口
package com.kj.mybatisplus.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.kj.mybatisplus.pojo.User;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface UserMapper extends BaseMapper<User> {
}
测试
package com.kj.mybatisplus;
import com.kj.mybatisplus.mapper.UserMapper;
import com.kj.mybatisplus.pojo.User;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
@SpringBootTest
public class MybatisplusTest {
@Autowired UserMapper userMapper;
@Test
public void test(){
//通过条件构造器查询一个list集合,若没有条件,则可以设置为null
List<User> list = userMapper.selectList(null);
list.forEach(System.out::println);
}
}
添加日志
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/mybatis_plus?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=UTC
username: root
password:
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
==> Preparing: SELECT id,name,age,email FROM user
==> Parameters:
<== Columns: id, name, age, email
<== Row: 1, Jone, 18, test1@baomidou.com
<== Row: 2, Jack, 20, trst2@baomidou.com
<== Row: 3, Tom, 28, test3@baomidou.com
<== Row: 4, Sandy, 21, test4@baomidou.com
<== Row: 5, Billie, 24, test5@baomidou.com
<== Total: 5
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@c2e3264]
User(id=1, name=Jone, age=18, email=test1@baomidou.com)
User(id=2, name=Jack, age=20, email=trst2@baomidou.com)
User(id=3, name=Tom, age=28, email=test3@baomidou.com)
User(id=4, name=Sandy, age=21, email=test4@baomidou.com)
User(id=5, name=Billie, age=24, email=test5@baomidou.com)
三、功能
新增
@Test
public void testInsert(){
User user = new User(null,"张三",23,"zhangsan@baomidou.com");
int result = userMapper.insert(user);
System.out.println("result:"+result);
System.out.println("userid:"+user.getId());
}
/*
==> Preparing: INSERT INTO user ( id, name, age, email ) VALUES ( ?, ?, ?, ? )
==> Parameters: 1643803350057201666(Long), 张三(String), 23(Integer), zhangsan@baomidou.com(String)
<== Updates: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@642505c7]
result:1
userid:1643803350057201666
*/
删除
@Test
public void testDelete(){
//通过id删除
int result = userMapper.deleteById(1643803350057201666L);
System.out.println("result:"+result);
}
/*
==> Preparing: DELETE FROM user WHERE id=?
==> Parameters: 1643803350057201666(Long)
<== Updates: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@2d9f64c9]
result:1
*/
@Test
public void testDelete(){
//通过map来删除
Map<String,Object> map = new HashMap<>();
map.put("name","张三");
map.put("age",23);
int result = userMapper.deleteByMap(map);
System.out.println("result"+result);
}
/*
==> Preparing: DELETE FROM user WHERE name = ? AND age = ?
==> Parameters: 张三(String), 23(Integer)
<== Updates: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@4339e0de]
result1
*/
@Test
public void testDelete(){
//通过多个id实现批量删除
List<Long> list = Arrays.asList(4L, 5L);
int result = userMapper.deleteBatchIds(list);
System.out.println("result"+result);
}
/*
==> Preparing: DELETE FROM user WHERE id IN ( ? , ? )
==> Parameters: 4(Long), 5(Long)
<== Updates: 2
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@21ac5eb4]
result2
*/
修改
@Test
public void testUpdate(){
//根据id修改用户信息
User user = new User();
user.setId(1L);
user.setName("Lili");
user.setAge(15);
int result = userMapper.updateById(user);
System.out.println("result"+result);
}
/*
==> Preparing: UPDATE user SET name=?, age=? WHERE id=?
==> Parameters: Lili(String), 15(Integer), 1(Long)
<== Updates: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@61d84e08]
result1
*/
查询
@Test
public void testSelect(){
//根据id查询
User result = userMapper.selectById(1L);
System.out.println("result"+result);
}
/*
==> Preparing: SELECT id,name,age,email FROM user WHERE id=?
==> Parameters: 1(Long)
<== Columns: id, name, age, email
<== Row: 1, Lili, 15, test1@baomidou.com
<== Total: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@52d6cd34]
resultUser(id=1, name=Lili, age=15, email=test1@baomidou.com)
*/
@Test
public void testSelect(){
//根据多个id查询多个用户信息
List<Long> list = Arrays.asList(1L, 2L, 3L, 4L);
List<User> result = userMapper.selectBatchIds(list);
result.forEach(System.out::println);
}
/*
==> Preparing: SELECT id,name,age,email FROM user WHERE id IN ( ? , ? , ? , ? )
==> Parameters: 1(Long), 2(Long), 3(Long), 4(Long)
<== Columns: id, name, age, email
<== Row: 1, Lili, 15, test1@baomidou.com
<== Row: 2, Jack, 20, trst2@baomidou.com
<== Row: 3, Tom, 28, test3@baomidou.com
<== Row: 4, 张三, 23, zhangsan@baomidou.com
<== Total: 4
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6428591a]
User(id=1, name=Lili, age=15, email=test1@baomidou.com)
User(id=2, name=Jack, age=20, email=trst2@baomidou.com)
User(id=3, name=Tom, age=28, email=test3@baomidou.com)
User(id=4, name=张三, age=23, email=zhangsan@baomidou.com)
*/
@Test
public void testSelect(){
//根据map查询
Map<String,Object> map = new HashMap<>();
map.put("name","张三");
map.put("age",23);
List<User> result = userMapper.selectByMap(map);
result.forEach(System.out::println);
}
/*
==> Preparing: SELECT id,name,age,email FROM user WHERE name = ? AND age = ?
==> Parameters: 张三(String), 23(Integer)
<== Columns: id, name, age, email
<== Row: 4, 张三, 23, zhangsan@baomidou.com
<== Total: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@c2e3264]
User(id=4, name=张三, age=23, email=zhangsan@baomidou.com)
*/
@Test
public void testSelect(){
//根据id查询
// User result = userMapper.selectById(1L);
// System.out.println("result"+result);
//根据多个id查询多个用户信息
// List<Long> list = Arrays.asList(1L, 2L, 3L, 4L);
// List<User> result = userMapper.selectBatchIds(list);
// result.forEach(System.out::println);
//根据map查询
// Map<String,Object> map = new HashMap<>();
// map.put("name","张三");
// map.put("age",23);
// List<User> result = userMapper.selectByMap(map);
// result.forEach(System.out::println);
//查询所有数据
List<User> users = userMapper.selectList(null);
users.forEach(System.out::println);
}
/*
==> Preparing: SELECT id,name,age,email FROM user
==> Parameters:
<== Columns: id, name, age, email
<== Row: 1, Lili, 15, test1@baomidou.com
<== Row: 2, Jack, 20, trst2@baomidou.com
<== Row: 3, Tom, 28, test3@baomidou.com
<== Row: 4, 张三, 23, zhangsan@baomidou.com
<== Row: 5, 李四, 24, lisi@baomidou.com
<== Total: 5
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@57c47a9e]
User(id=1, name=Lili, age=15, email=test1@baomidou.com)
User(id=2, name=Jack, age=20, email=trst2@baomidou.com)
User(id=3, name=Tom, age=28, email=test3@baomidou.com)
User(id=4, name=张三, age=23, email=zhangsan@baomidou.com)
User(id=5, name=李四, age=24, email=lisi@baomidou.com)
*/
自定义功能
@Mapper
public interface UserMapper extends BaseMapper<User> {
/**
* 根据id查询用户信息,返回map
* @param id
* @return
*/
Map<String,Object> selectMapById(Long id);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace=绑定一个对应的Dao/Mapper接口-->
<mapper namespace="com.kj.mybatisplus.mapper.UserMapper">
<select id="selectMapById" resultType="map">
select name,age,email from mybatis_plus.user where id = #{id}
</select>
</mapper>
@Test
public void testSelect(){
//自定义查询
Map<String, Object> map = userMapper.selectMapById(1L);
System.out.println(map);
}
/*
==> Preparing: select name,age,email from mybatis_plus.user where id = ?
==> Parameters: 1(Long)
<== Columns: name, age, email
<== Row: Lili, 15, test1@baomidou.com
<== Total: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@2a685eba]
{name=Lili, age=15, email=test1@baomidou.com}
*/
通用Service
说明:
- 通用Service CRUD封装Service接口,进一步封装CRUD采用
get
查询单行,remove
删除,list
查询集合,page
分页,前缀命名方式区分mapper
层避免混淆- 泛型
T
为任意实体对象- 建议如果存在自定义通用Service方法的可能,请创建自己的
IBaseService
继承Mybatis_Plus
的基类
IService
Mybatis_Plus中有一个接口IService和其实现类ServiceImpl,封装了常见的业务逻辑
详情查看源码 IService 和 ServiceImpl
创建Service接口和实现类
接口
package com.kj.mybatisplus.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.kj.mybatisplus.pojo.User;
public interface UserService extends IService<User> {
}
实现类
package com.kj.mybatisplus.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.kj.mybatisplus.mapper.UserMapper;
import com.kj.mybatisplus.pojo.User;
import com.kj.mybatisplus.service.UserService;
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
}
测试
package com.kj.mybatisplus;
import com.kj.mybatisplus.service.UserService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
public class MybatisplusServiceTest {
@Autowired
private UserService userService;
@Test
public void testGetCount(){
//查询总记录数
long count = userService.count();
System.out.println(count);
}
}
/*
==> Preparing: SELECT COUNT( * ) FROM user
==> Parameters:
<== Columns: COUNT( * )
<== Row: 5
<== Total: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@3419e23b]
5
*/
批量添加
@Test
public void testInsertMore(){
//批量添加
List<User> list = new ArrayList<>();
for(int i=1;i<=10;i++){
User user = new User();
user.setId(5L+i);
user.setName("abc"+i);
user.setAge(20+i);
list.add(user);
}
boolean b = userService.saveBatch(list);
System.out.println(b);
}
/*
==> Preparing: INSERT INTO user ( id, name, age ) VALUES ( ?, ?, ? )
==> Parameters: 6(Long), abc1(String), 21(Integer)
==> Parameters: 7(Long), abc2(String), 22(Integer)
==> Parameters: 8(Long), abc3(String), 23(Integer)
==> Parameters: 9(Long), abc4(String), 24(Integer)
==> Parameters: 10(Long), abc5(String), 25(Integer)
==> Parameters: 11(Long), abc6(String), 26(Integer)
==> Parameters: 12(Long), abc7(String), 27(Integer)
==> Parameters: 13(Long), abc8(String), 28(Integer)
==> Parameters: 14(Long), abc9(String), 29(Integer)
==> Parameters: 15(Long), abc10(String), 30(Integer)
true
*/
@TableName
操控的表由UserMapper中BaseMapper<泛型(实体类)>设置的泛型(实体类)决定
package com.kj.mybatisplus.pojo;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.*;
@Data
@AllArgsConstructor
@NoArgsConstructor
//设置实体类所对应的表名
@TableName("user")
public class User {
private Long id;
private String name;
private Integer age;
private String email;
}
如果所有表名由统一的前缀,在yml文件中设置
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/mybatis_plus?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=UTC
username: root
password:
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
#设置MyBatis-Plus的全局配置
global-config:
db-config:
table-prefix: t_
@TableId
将属性所对应的字段指定为主键
@TableId
private Long uid;
value属性
@TableId注解的value属性用于指定主键的字段
例:表中属性名为uid,实体类中名称为id
@TableId(value = "uid")
private Long id;
只有value属性时可写为:
@TableId("uid")
private Long uid;
type属性
@TableId注解的type属性设置主键生成策略
当数据库中属性设置为自增,而Mybatis-Plus默认为雪花算法时
@TableId(value = "uid",type = IdType.AUTO)
private Long id;
通过全局配置主键生成策略
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/mybatis_plus?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=UTC
username: root
password:
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
#设置MyBatis-Plus的全局配置
global-config:
db-config:
table-prefix: t_
#设置统一的主键生成策略
id-type: auto
雪花算法
需要选择合适的方案去应对数据规模的增长,以应对逐渐增长的访问压力和数据量。
数据库的扩展方式主要包括:业务分库、主从复制,数据库分表。
将不同的业务数据分散存储到不同的数据库服务器,能够支撑百万甚至千万用户规模的业务,但如果业务继续发展,同一业务的单表数据也会达到单台数据库服务器的处理瓶颈。例如:淘宝的几亿用户数据,如果全部存放在一台数据库服务器的一张表中,肯定是无法满足性能要求的,此时就需要对单表数据进行拆分。
单表数据拆分有两种方式:垂直分表和水平分表
垂直分表适合将表中某些不常用且占据了大量空间的列拆分出去。
例如:假设有一个婚恋网站,用户在筛选其它用户时,主要使用的是age和sex两个字段进行查询,而其它字段主要用于展示,一般不会在业务查询中用到,因此我们可以将这两个字段独立到另外一张表中,这样在查询age和sex时,就能带来一定的性能提升。
水平分表适合表行数特别大的表,有的公司要求单表行数超过5000万就必须进行分表,这个数字可以作为参考,但并不是绝对标准,关键还是要看表的访问性能。对于一些比较复杂的表,可能超过1000万就要分表了;而对于一些简单的表,即使存储数据超过1亿行,也可以不分表。
但不管怎样,当看到表的数据量达到千万级别时,作为架构师就要警觉起来,因为这很可能是架构的性能瓶颈或者隐患。
水平分表相比垂直分表,会引入更多的复杂性,例如要求全局唯一的数据id该如何处理。
主键自增
- 以最常见的用户ID为例,可以按照1000000的范围大小进行分段,1999999放到表1中,10000001999999放到表2中,以此类推。
- 复杂点:分段大小的选取。分段太小会导致切分后子表数量过多,增加维护复杂度;分段太大可能会导致单表依然存在性能问题,一般建议分段大小在100万至2000万之间,具体需要根据业务选取合适的分段大小。
- 优点:可以随着数据的增加平滑地扩充新的表。例如,现在的用户是100万,如果增加到1000万,只需要增加新的表就可以了,原有的数据不需要动。
- 缺点:分布不均匀。假如按照1000万来进行分表,有可能某个分段实际存储的数据量只有一条,而另外一个分段实际存储的数据量有1000万条。
取模
- 同样以用户ID为例,假如我们一开始就规划了10个数据库表,可以简单的用user_id % 10的值来表示数据所属的数据库表编号,ID为985的用户放到编号为5的子表中,ID为10086的用户放到标号为6的子表中。
- 复杂点:初始表数量的确定。表数量太多维护比较麻烦,表数量太少又可能导致单表性能存在问题。
- 优点:表分布比较均匀。
- 扩充新的表很麻烦,所有的表都要重分布。
雪花算法
雪花算法是由Twitter公布的分布式主键生成算法,它能够保证不同表的主键的不重复性,以及相同表的主键的有序性。
- 核心思想:
- 长度共64bit(一个Long型)
- 首先是一个符号位,1bit标识,由于Long型在java中是带符号的,最高位是符号位,正数是0,负数是1,所以id一般是正数,最高位是0
- 41bit时间戳(毫秒级),存储的是时间戳的差值(当前时间戳-开始时间戳),结果约等于69.73年
- 10bit作为机器的ID(5bit是数据中心,5bit是机器ID,可以部署在1024个节点)
- 12bit作为毫秒的流水号(意味着每个节点在每毫秒可以产生4096个ID)
- 优点:整体上按照时间自增排序,并且整个分布式系统不会产生ID碰撞,并且效率较高
@TableField
指定属性所对应的字段名
例:数据库中字段名为:user_name,实体类中属性名为:name
@TableField("user_name")
private String name;
@TableLogic
逻辑删除
- 物理删除:真实删除,将对应数据从数据库中删除,之后查询不到此条被删除的数据
- 逻辑删除:假删除,将对应数据中代表是否被删除字段的状态修改为“被删除状态”,之后在数据库中仍旧能看到此条数据记录
- 使用场景:可以进行数据恢复
实现逻辑删除
step1:数据库中创建逻辑删除状态列,设置默认值为0
step2:在实体类中添加isDelete属性,并添加@TableLogic注解
@TableLogic
private Integer isDeleted;
step3:运行删除功能
@Test
public void testDelete(){
//通过id删除
// int result = userMapper.deleteById(1643803350057201666L);
// System.out.println("result:"+result);
//通过map来删除
// Map<String,Object> map = new HashMap<>();
// map.put("name","张三");
// map.put("age",23);
// int result = userMapper.deleteByMap(map);
// System.out.println("result"+result);
//通过多个id实现批量删除
List<Long> list = Arrays.asList(14L,15L);
int result = userMapper.deleteBatchIds(list);
System.out.println("result"+result);
}
/*
==> Preparing: UPDATE user SET is_deleted=1 WHERE id IN ( ? , ? ) AND is_deleted=0
==> Parameters: 14(Long), 15(Long)
<== Updates: 2
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@7487b142]
result2
*/
step4:运行查询功能
@Test
public void testSelect(){
//根据id查询
// User result = userMapper.selectById(1L);
// System.out.println("result"+result);
//根据多个id查询多个用户信息
// List<Long> list = Arrays.asList(1L, 2L, 3L, 4L);
// List<User> result = userMapper.selectBatchIds(list);
// result.forEach(System.out::println);
//根据map查询
// Map<String,Object> map = new HashMap<>();
// map.put("name","张三");
// map.put("age",23);
// List<User> result = userMapper.selectByMap(map);
// result.forEach(System.out::println);
// 查询所有数据
List<User> users = userMapper.selectList(null);
users.forEach(System.out::println);
//自定义查询
// Map<String, Object> map = userMapper.selectMapById(1L);
// System.out.println(map);
}
/*
==> Preparing: SELECT id,name,age,email,is_deleted FROM user WHERE is_deleted=0
==> Parameters:
<== Columns: id, name, age, email, is_deleted
<== Row: 1, Lili, 15, test1@baomidou.com, 0
<== Row: 2, Jack, 20, trst2@baomidou.com, 0
<== Row: 3, Tom, 28, test3@baomidou.com, 0
<== Row: 4, 张三, 23, zhangsan@baomidou.com, 0
<== Row: 5, 李四, 24, lisi@baomidou.com, 0
<== Row: 6, abc1, 21, null, 0
<== Row: 7, abc2, 22, null, 0
<== Row: 8, abc3, 23, null, 0
<== Row: 9, abc4, 24, null, 0
<== Row: 10, abc5, 25, null, 0
<== Row: 11, abc6, 26, null, 0
<== Row: 12, abc7, 27, null, 0
<== Row: 13, abc8, 28, null, 0
<== Row: 16, 李四, 24, lisi@baomidou.com, 0
<== Total: 14
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@790a251b]
User(id=1, name=Lili, age=15, email=test1@baomidou.com, isDeleted=0)
User(id=2, name=Jack, age=20, email=trst2@baomidou.com, isDeleted=0)
User(id=3, name=Tom, age=28, email=test3@baomidou.com, isDeleted=0)
User(id=4, name=张三, age=23, email=zhangsan@baomidou.com, isDeleted=0)
User(id=5, name=李四, age=24, email=lisi@baomidou.com, isDeleted=0)
User(id=6, name=abc1, age=21, email=null, isDeleted=0)
User(id=7, name=abc2, age=22, email=null, isDeleted=0)
User(id=8, name=abc3, age=23, email=null, isDeleted=0)
User(id=9, name=abc4, age=24, email=null, isDeleted=0)
User(id=10, name=abc5, age=25, email=null, isDeleted=0)
User(id=11, name=abc6, age=26, email=null, isDeleted=0)
User(id=12, name=abc7, age=27, email=null, isDeleted=0)
User(id=13, name=abc8, age=28, email=null, isDeleted=0)
User(id=16, name=李四, age=24, email=lisi@baomidou.com, isDeleted=0)
*/
四、条件构造器和常用接口
1、wrapper介绍
- wrapper:条件构造器,最顶端父类
- AbstractWrapper:用于查询条件封装,生成sql的where条件
- QueryWrapper:查询条件封装
- UpdateWrapper:Update条件封装
- AbstractLambdaWrapper:使用Lambda语法
- LambdaQueryWrapper:用于Lambda语法使用的查询Wrapper
- LambdaUpdateWrapper:Lambda更新封装Wrapper
- AbstractWrapper:用于查询条件封装,生成sql的where条件
2、QueryWrapper
组装查询条件
package com.kj.mybatisplus;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.kj.mybatisplus.mapper.UserMapper;
import com.kj.mybatisplus.pojo.User;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
@SpringBootTest
public class MybatisplusWrapperTest {
@Autowired
private UserMapper userMapper;
@Test
public void queryWrapperTest(){
//查询用户名中包含a,年龄在20到30之间,邮箱信息不为空的用户信息
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.like("name","a")
.between("age",20,30)
.isNotNull("email");
List<User> list = userMapper.selectList(queryWrapper);
list.forEach(System.out::println);
}
}
/*
==> Preparing: SELECT id,name,age,email,is_deleted FROM user WHERE is_deleted=0 AND (name LIKE ? AND age BETWEEN ? AND ? AND email IS NOT NULL)
==> Parameters: %a%(String), 20(Integer), 30(Integer)
<== Columns: id, name, age, email, is_deleted
<== Row: 2, Jack, 20, trst2@baomidou.com, 0
<== Total: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@56913163]
User(id=2, name=Jack, age=20, email=trst2@baomidou.com, isDeleted=0)
*/
组装排序条件
@Test
public void queryWrapperTest02(){
//查询用户信息,按照年龄降序排序,若年龄相同,按照id升序排序
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.orderByDesc("age")
.orderByAsc("id");
List<User> list = userMapper.selectList(queryWrapper);
list.forEach(System.out::println);
}
/*
==> Preparing: SELECT id,name,age,email,is_deleted FROM user WHERE is_deleted=0 ORDER BY age DESC,id ASC
==> Parameters:
<== Columns: id, name, age, email, is_deleted
<== Row: 3, Tom, 28, test3@baomidou.com, 0
<== Row: 13, abc8, 28, null, 0
<== Row: 12, abc7, 27, null, 0
<== Row: 11, abc6, 26, null, 0
<== Row: 10, abc5, 25, null, 0
<== Row: 5, 李四, 24, lisi@baomidou.com, 0
<== Row: 9, abc4, 24, null, 0
<== Row: 16, 李四, 24, lisi@baomidou.com, 0
<== Row: 4, 张三, 23, zhangsan@baomidou.com, 0
<== Row: 8, abc3, 23, null, 0
<== Row: 7, abc2, 22, null, 0
<== Row: 6, abc1, 21, null, 0
<== Row: 2, Jack, 20, trst2@baomidou.com, 0
<== Row: 1, Lili, 15, test1@baomidou.com, 0
<== Total: 14
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@702b06fb]
User(id=3, name=Tom, age=28, email=test3@baomidou.com, isDeleted=0)
User(id=13, name=abc8, age=28, email=null, isDeleted=0)
User(id=12, name=abc7, age=27, email=null, isDeleted=0)
User(id=11, name=abc6, age=26, email=null, isDeleted=0)
User(id=10, name=abc5, age=25, email=null, isDeleted=0)
User(id=5, name=李四, age=24, email=lisi@baomidou.com, isDeleted=0)
User(id=9, name=abc4, age=24, email=null, isDeleted=0)
User(id=16, name=李四, age=24, email=lisi@baomidou.com, isDeleted=0)
User(id=4, name=张三, age=23, email=zhangsan@baomidou.com, isDeleted=0)
User(id=8, name=abc3, age=23, email=null, isDeleted=0)
User(id=7, name=abc2, age=22, email=null, isDeleted=0)
User(id=6, name=abc1, age=21, email=null, isDeleted=0)
User(id=2, name=Jack, age=20, email=trst2@baomidou.com, isDeleted=0)
User(id=1, name=Lili, age=15, email=test1@baomidou.com, isDeleted=0)
*/
组装删除条件
@Test
public void test03(){
//删除邮箱地址为NULL的信息
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.isNull("email");
int result = userMapper.delete(queryWrapper);
System.out.println("result:"+result);
}
/*
==> Preparing: UPDATE user SET is_deleted=1 WHERE is_deleted=0 AND (email IS NULL)
==> Parameters:
<== Updates: 8
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1b9776f5]
result:8
*/
==> Preparing: SELECT id,name,age,email,is_deleted FROM user WHERE is_deleted=0
==> Parameters:
<== Columns: id, name, age, email, is_deleted
<== Row: 1, Lili, 15, test1@baomidou.com, 0
<== Row: 2, Jack, 20, trst2@baomidou.com, 0
<== Row: 3, Tom, 28, test3@baomidou.com, 0
<== Row: 4, 张三, 23, zhangsan@baomidou.com, 0
<== Row: 5, 李四, 24, lisi@baomidou.com, 0
<== Row: 16, 李四, 24, lisi@baomidou.com, 0
<== Total: 6
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@3e15bb06]
User(id=1, name=Lili, age=15, email=test1@baomidou.com, isDeleted=0)
User(id=2, name=Jack, age=20, email=trst2@baomidou.com, isDeleted=0)
User(id=3, name=Tom, age=28, email=test3@baomidou.com, isDeleted=0)
User(id=4, name=张三, age=23, email=zhangsan@baomidou.com, isDeleted=0)
User(id=5, name=李四, age=24, email=lisi@baomidou.com, isDeleted=0)
User(id=16, name=李四, age=24, email=lisi@baomidou.com, isDeleted=0)
3、使用QueryWrapper实现修改功能
@Test
public void test04(){
//修改年龄大于20且用户名包含o或者邮箱地址为空的用户信息
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.gt("age",20)
.like("name","o")
.or()
.isNull("email");
User user = new User();
user.setAge(26);
user.setName("lisa");
int result = userMapper.update(user, queryWrapper);
System.out.println("result:"+result);
}
/*
==> Preparing: UPDATE user SET name=?, age=? WHERE is_deleted=0 AND (age > ? AND name LIKE ? OR email IS NULL)
==> Parameters: lisa(String), 26(Integer), 20(Integer), %o%(String)
<== Updates: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@a18649a]
result:1
*/
条件的优先级
@Test
public void test05(){
//修改用户名包含s且(年龄大于20或邮箱地址为空)的用户信息
QueryWrapper<User> queryWrapper = new QueryWrapper();
queryWrapper.like("name","s")
.and(i->i.gt("age",20).or().isNull("email"));
User user = new User();
user.setName("haha");
user.setAge(24);
int result = userMapper.update(user, queryWrapper);
System.out.println("result:"+result);
}
/*
==> Preparing: UPDATE user SET name=?, age=? WHERE is_deleted=0 AND (name LIKE ? AND (age > ? OR email IS NULL))
==> Parameters: haha(String), 24(Integer), %s%(String), 20(Integer)
<== Updates: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@3d5790ea]
result:1
*/
组装select字句
@Test
public void test06(){
//查询用户的用户名,年龄,邮箱信息
QueryWrapper<User> queryWrapper = new QueryWrapper();
queryWrapper.select("name","age","email");
List<Map<String, Object>> maps = userMapper.selectMaps(queryWrapper);
maps.forEach(System.out::println);
}
/*
==> Preparing: SELECT name,age,email FROM user WHERE is_deleted=0
==> Parameters:
<== Columns: name, age, email
<== Row: Lili, 15, test1@baomidou.com
<== Row: Jack, 20, trst2@baomidou.com
<== Row: haha, 24, test3@baomidou.com
<== Row: 张三, 23, zhangsan@baomidou.com
<== Row: 李四, 24, lisi@baomidou.com
<== Row: 李四, 24, lisi@baomidou.com
<== Total: 6
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@7a7cc52c]
{name=Lili, age=15, email=test1@baomidou.com}
{name=Jack, age=20, email=trst2@baomidou.com}
{name=haha, age=24, email=test3@baomidou.com}
{name=张三, age=23, email=zhangsan@baomidou.com}
{name=李四, age=24, email=lisi@baomidou.com}
{name=李四, age=24, email=lisi@baomidou.com}
*/
组装子查询
@Test
public void test07(){
//查询id小于等于5的用户信息
QueryWrapper<User> queryWrapper = new QueryWrapper();
queryWrapper.inSql("id","select id from user where id <=5");
List<User> list = userMapper.selectList(queryWrapper);
list.forEach(System.out::println);
}
/*
==> Preparing: SELECT id,name,age,email,is_deleted FROM user WHERE is_deleted=0 AND (id IN (select id from user where id <=5))
==> Parameters:
<== Columns: id, name, age, email, is_deleted
<== Row: 1, Lili, 15, test1@baomidou.com, 0
<== Row: 2, Jack, 20, trst2@baomidou.com, 0
<== Row: 3, haha, 24, test3@baomidou.com, 0
<== Row: 4, 张三, 23, zhangsan@baomidou.com, 0
<== Row: 5, 李四, 24, lisi@baomidou.com, 0
<== Total: 5
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@4fc142ec]
User(id=1, name=Lili, age=15, email=test1@baomidou.com, isDeleted=0)
User(id=2, name=Jack, age=20, email=trst2@baomidou.com, isDeleted=0)
User(id=3, name=haha, age=24, email=test3@baomidou.com, isDeleted=0)
User(id=4, name=张三, age=23, email=zhangsan@baomidou.com, isDeleted=0)
User(id=5, name=李四, age=24, email=lisi@baomidou.com, isDeleted=0)
*/
4、使用UpdateWrapper实现修改功能
@Test
public void test08(){
//修改用户名包含h且(年龄大于20或邮箱地址为空)的用户信息
UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
updateWrapper.like("name","h")
.and(i -> i.gt("age",20).or().isNull("email"));
updateWrapper.set("name","nini").set("email","nini@baomidou.com");
int result = userMapper.update(null, updateWrapper);
System.out.println("result:"+result);
}
/*
==> Preparing: UPDATE user SET name=?,email=? WHERE is_deleted=0 AND (name LIKE ? AND (age > ? OR email IS NULL))
==> Parameters: nini(String), nini@baomidou.com(String), %h%(String), 20(Integer)
<== Updates: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@5c534b5b]
result:1
*/
5、模拟开发中组装条件的情况
@Test
public void test09(){
String name = "";
Integer ageBegin = 10;
Integer ageEnd = 25;
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
if (StringUtils.isNotBlank(name)){
//isNotBlank:判断某个字符串是否不为空字符串,不为null,不为空白符
queryWrapper.like("name",name);
}
if (ageBegin != null){
queryWrapper.gt("age",ageBegin);
}
if (ageEnd != null){
queryWrapper.le("age",ageEnd);
}
List<User> list = userMapper.selectList(queryWrapper);
list.forEach(System.out::println);
}
/*
==> Preparing: SELECT id,name,age,email,is_deleted FROM user WHERE is_deleted=0 AND (age > ? AND age <= ?)
==> Parameters: 10(Integer), 25(Integer)
<== Columns: id, name, age, email, is_deleted
<== Row: 1, Lili, 15, test1@baomidou.com, 0
<== Row: 2, Jack, 20, trst2@baomidou.com, 0
<== Row: 3, nini, 24, nini@baomidou.com, 0
<== Row: 4, 张三, 23, zhangsan@baomidou.com, 0
<== Row: 5, 李四, 24, lisi@baomidou.com, 0
<== Row: 16, 李四, 24, lisi@baomidou.com, 0
<== Total: 6
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@5c534b5b]
User(id=1, name=Lili, age=15, email=test1@baomidou.com, isDeleted=0)
User(id=2, name=Jack, age=20, email=trst2@baomidou.com, isDeleted=0)
User(id=3, name=nini, age=24, email=nini@baomidou.com, isDeleted=0)
User(id=4, name=张三, age=23, email=zhangsan@baomidou.com, isDeleted=0)
User(id=5, name=李四, age=24, email=lisi@baomidou.com, isDeleted=0)
User(id=16, name=李四, age=24, email=lisi@baomidou.com, isDeleted=0)
*/
使用condition组装条件
@Test
public void test10(){
String name = "";
Integer ageBegin = 10;
Integer ageEnd = 25;
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.like(StringUtils.isNotBlank(name),"name",name)
.gt(ageBegin!=null,"age",ageBegin)
.le(ageEnd!=null,"age",ageEnd);
List<User> list = userMapper.selectList(queryWrapper);
list.forEach(System.out::println);
}
/*
==> Preparing: SELECT id,name,age,email,is_deleted FROM user WHERE is_deleted=0 AND (age > ? AND age <= ?)
==> Parameters: 10(Integer), 25(Integer)
<== Columns: id, name, age, email, is_deleted
<== Row: 1, Lili, 15, test1@baomidou.com, 0
<== Row: 2, Jack, 20, trst2@baomidou.com, 0
<== Row: 3, nini, 24, nini@baomidou.com, 0
<== Row: 4, 张三, 23, zhangsan@baomidou.com, 0
<== Row: 5, 李四, 24, lisi@baomidou.com, 0
<== Row: 16, 李四, 24, lisi@baomidou.com, 0
<== Total: 6
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@2418ba04]
User(id=1, name=Lili, age=15, email=test1@baomidou.com, isDeleted=0)
User(id=2, name=Jack, age=20, email=trst2@baomidou.com, isDeleted=0)
User(id=3, name=nini, age=24, email=nini@baomidou.com, isDeleted=0)
User(id=4, name=张三, age=23, email=zhangsan@baomidou.com, isDeleted=0)
User(id=5, name=李四, age=24, email=lisi@baomidou.com, isDeleted=0)
User(id=16, name=李四, age=24, email=lisi@baomidou.com, isDeleted=0)
*/
6、Lambda
LambdaQueryWrapper
@Test
public void test11(){
String name = "";
Integer ageBegin = 10;
Integer ageEnd = 25;
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.like(StringUtils.isNotBlank(name),User::getName,name)
.gt(ageBegin!=null,User::getAge,ageBegin)
.le(ageEnd!=null,User::getAge,ageEnd);
List<User> list = userMapper.selectList(queryWrapper);
list.forEach(System.out::println);
}
/*
==> Preparing: SELECT id,name,age,email,is_deleted FROM user WHERE is_deleted=0 AND (age > ? AND age <= ?)
==> Parameters: 10(Integer), 25(Integer)
<== Columns: id, name, age, email, is_deleted
<== Row: 1, Lili, 15, test1@baomidou.com, 0
<== Row: 2, Jack, 20, trst2@baomidou.com, 0
<== Row: 3, nini, 24, nini@baomidou.com, 0
<== Row: 4, 张三, 23, zhangsan@baomidou.com, 0
<== Row: 5, 李四, 24, lisi@baomidou.com, 0
<== Row: 16, 李四, 24, lisi@baomidou.com, 0
<== Total: 6
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@5c534b5b]
User(id=1, name=Lili, age=15, email=test1@baomidou.com, isDeleted=0)
User(id=2, name=Jack, age=20, email=trst2@baomidou.com, isDeleted=0)
User(id=3, name=nini, age=24, email=nini@baomidou.com, isDeleted=0)
User(id=4, name=张三, age=23, email=zhangsan@baomidou.com, isDeleted=0)
User(id=5, name=李四, age=24, email=lisi@baomidou.com, isDeleted=0)
User(id=16, name=李四, age=24, email=lisi@baomidou.com, isDeleted=0)
*/
LambdaUpdateWrapper
@Test
public void test12(){
//修改id为16且(年龄大于20或邮箱地址为空)的用户信息
LambdaUpdateWrapper<User> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.eq(User::getId,16)
.and(i -> i.gt(User::getAge,20).or().isNull(User::getEmail));
updateWrapper.set(User::getName,"sasha").set(User::getEmail,"sasha@baomidou.com");
int result = userMapper.update(null, updateWrapper);
System.out.println("result:"+result);
}
/*
==> Preparing: UPDATE user SET name=?,email=? WHERE is_deleted=0 AND (id = ? AND (age > ? OR email IS NULL))
==> Parameters: sasha(String), sasha@baomidou.com(String), 16(Integer), 20(Integer)
<== Updates: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@3ee69ad8]
result:1
*/
五、插件
1、分页插件
MyBatisPlus 自带分页插件,只要简单的配置即可实现分页功能
添加配置类
package com.kj.mybatisplus.config;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@MapperScan("com.kj.mybatisplus.mapper")
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
测试
package com.kj.mybatisplus;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.kj.mybatisplus.mapper.UserMapper;
import com.kj.mybatisplus.pojo.User;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
public class MybatisplusPluginTest {
@Autowired
private UserMapper userMapper;
@Test
public void pageTest(){
Page<User> page = new Page<>(1,3);
userMapper.selectPage(page, null);
System.out.println(page);
}
}
/*
==> Preparing: SELECT COUNT(*) AS total FROM user WHERE is_deleted = 0
==> Parameters:
<== Columns: total
<== Row: 6
<== Total: 1
==> Preparing: SELECT id,name,age,email,is_deleted FROM user WHERE is_deleted=0 LIMIT ?
==> Parameters: 3(Long)
<== Columns: id, name, age, email, is_deleted
<== Row: 1, Lili, 15, test1@baomidou.com, 0
<== Row: 2, Jack, 20, trst2@baomidou.com, 0
<== Row: 3, nini, 24, nini@baomidou.com, 0
<== Total: 3
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@3a543f31]
com.baomidou.mybatisplus.extension.plugins.pagination.Page@4601203a
*/
分页相关数据获取
@Test
public void pageTest(){
Page<User> page = new Page<>(1,3);
userMapper.selectPage(page, null);
System.out.println(page.getPages());
System.out.println(page.getRecords());
System.out.println(page.getCurrent());
System.out.println(page.getSize());
System.out.println(page.hasNext()+","+page.hasPrevious());
}
/*
==> Preparing: SELECT COUNT(*) AS total FROM user WHERE is_deleted = 0
==> Parameters:
<== Columns: total
<== Row: 6
<== Total: 1
==> Preparing: SELECT id,name,age,email,is_deleted FROM user WHERE is_deleted=0 LIMIT ?
==> Parameters: 3(Long)
<== Columns: id, name, age, email, is_deleted
<== Row: 1, Lili, 15, test1@baomidou.com, 0
<== Row: 2, Jack, 20, trst2@baomidou.com, 0
<== Row: 3, nini, 24, nini@baomidou.com, 0
<== Total: 3
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@7187bac9]
2
[User(id=1, name=Lili, age=15, email=test1@baomidou.com, isDeleted=0), User(id=2, name=Jack, age=20, email=trst2@baomidou.com, isDeleted=0), User(id=3, name=nini, age=24, email=nini@baomidou.com, isDeleted=0)]
1
3
true,false
*/
自定义分页功能
package com.kj.mybatisplus.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.kj.mybatisplus.pojo.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.Map;
@Mapper
public interface UserMapper extends BaseMapper<User> {
/**
* 根据id查询用户信息,返回map
* @param id
* @return
*/
Map<String,Object> selectMapById(Long id);
/**
* 根据年龄分页查询用户信息
* @param page
* @param age
* @return
*/
Page<User> selectPageVo(@Param("page") Page<User> page,@Param("age") Integer age);
}
配置别名
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/mybatis_plus?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=UTC
username: root
password:
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
type-aliases-package: com.kj.mybatisplus.pojo
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace=绑定一个对应的Dao/Mapper接口-->
<mapper namespace="com.kj.mybatisplus.mapper.UserMapper">
<select id="selectMapById" resultType="map">
select name,age,email from mybatis_plus.user where id = #{id}
</select>
<select id="selectPageVo" resultType="User">
select id,name,age,email from mybatis_plus.user where age > #{age}
</select>
</mapper>
@Test
public void teatPageVo(){
Page<User> page = new Page<>(1,3);
userMapper.selectPageVo(page, 20);
System.out.println(page);
}
/*
==> Preparing: SELECT COUNT(*) AS total FROM mybatis_plus.user WHERE age > ?
==> Parameters: 20(Integer)
<== Columns: total
<== Row: 14
<== Total: 1
==> Preparing: select id,name,age,email from mybatis_plus.user where age > ? LIMIT ?
==> Parameters: 20(Integer), 3(Long)
<== Columns: id, name, age, email
<== Row: 3, nini, 24, nini@baomidou.com
<== Row: 4, 张三, 23, zhangsan@baomidou.com
<== Row: 5, 李四, 24, lisi@baomidou.com
<== Total: 3
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@7efe7b87]
com.baomidou.mybatisplus.extension.plugins.pagination.Page@1937eaff
*/
2、乐观锁
场景
一件商品,成本价是80元,售价是100元。老板先是通知小李,说你去把商品价格增加50元。小李正在玩游戏,耽搁了一个小时。正好一个小时后,老板觉得商品价格增加到150元,价格太高,可能会影响销量。又通知小王,你把价格降低30元。
此时,小李和小王同时操作商品后台系统。小李操作的时候,系统先取出商品价格100元;小王也在操作,取出的商品价格也是100元。小李价格加了50元,并将100+50=150元存入了数据库;小王将商品减了30元,并将100-30=70元存入了数据库。是的,如果没有锁,小李的操作就完全被小王覆盖了。
现在商品价格是70元,比成本低10元。几分钟后,这个商品很快出售了1千多件,老板亏1万多。
乐观锁与悲观锁
上面的故事,如果是乐观锁,小王保存价格前,会检查下价格是否被修改了。如果被修改过了,则重新取出被修改后的价格,150元,这样他会将120元存入数据库。
如果是悲观锁,小李取出数据后,小王只能等小李操作完之后,才能对价格进行操作,也会保证最终的价格是120元。
模拟修改冲突
数据库中增加商品表
create table product
(
`id` BIGINT(20) NOT NULL COMMENT'主键ID',
`name` VARCHAR(30) NULL DEFAULT NULL COMMENT'商品名称',
`price` INT(11) DEFAULT 0 COMMENT'价格',
`VERSION` INT(11) DEFAULT 0 COMMENT'乐观锁版本号',
PRIMARY KEY(id)
);
INSERT INTO product(id,name,price) VALUES(1,'外星人笔记本',100);
添加实体类
package com.kj.mybatisplus.pojo;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("product")
public class Product {
private Long id;
private String name;
private Integer price;
private Integer version;
}
添加Mapper
package com.kj.mybatisplus.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.kj.mybatisplus.pojo.Product;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface ProductMapper extends BaseMapper<Product> {
}
模拟冲突
@Test
public void testProduct01(){
//小李查询价格
Product productL = productMapper.selectById(1);
System.out.println("小李查询到的价格为:"+productL.getPrice());
//小王查询价格
Product productW = productMapper.selectById(1);
System.out.println("小王查询到的价格为:"+productW.getPrice());
//小李修改价格
productL.setPrice(productL.getPrice()+50);
productMapper.updateById(productL);
//小王修改价格
productW.setPrice(productW.getPrice()-30);
productMapper.updateById(productW);
//老板查询价格
Product productB = productMapper.selectById(1);
System.out.println("老板查询到的价格为:"+productB.getPrice());
}
/*
==> Preparing: SELECT id,name,price,version FROM product WHERE id=?
==> Parameters: 1(Integer)
<== Columns: id, name, price, version
<== Row: 1, 外星人笔记本, 100, 0
<== Total: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@15dc339f]
小李查询到的价格为:100
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@7544ac86] was not registered for synchronization because synchronization is not active
JDBC Connection [HikariProxyConnection@992457879 wrapping com.mysql.cj.jdbc.ConnectionImpl@2630dbc4] will not be managed by Spring
==> Preparing: SELECT id,name,price,version FROM product WHERE id=?
==> Parameters: 1(Integer)
<== Columns: id, name, price, version
<== Row: 1, 外星人笔记本, 100, 0
<== Total: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@7544ac86]
小王查询到的价格为:100
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@512d6e60] was not registered for synchronization because synchronization is not active
JDBC Connection [HikariProxyConnection@859985937 wrapping com.mysql.cj.jdbc.ConnectionImpl@2630dbc4] will not be managed by Spring
==> Preparing: UPDATE product SET name=?, price=?, version=? WHERE id=?
==> Parameters: 外星人笔记本(String), 150(Integer), 0(Integer), 1(Long)
<== Updates: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@512d6e60]
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1caa9eb6] was not registered for synchronization because synchronization is not active
JDBC Connection [HikariProxyConnection@525551643 wrapping com.mysql.cj.jdbc.ConnectionImpl@2630dbc4] will not be managed by Spring
==> Preparing: UPDATE product SET name=?, price=?, version=? WHERE id=?
==> Parameters: 外星人笔记本(String), 70(Integer), 0(Integer), 1(Long)
<== Updates: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1caa9eb6]
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@27e7c77f] was not registered for synchronization because synchronization is not active
JDBC Connection [HikariProxyConnection@1869652507 wrapping com.mysql.cj.jdbc.ConnectionImpl@2630dbc4] will not be managed by Spring
==> Preparing: SELECT id,name,price,version FROM product WHERE id=?
==> Parameters: 1(Integer)
<== Columns: id, name, price, version
<== Row: 1, 外星人笔记本, 70, 0
<== Total: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@27e7c77f]
老板查询到的价格为:70
*/
乐观锁实现流程
数据库中添加version字段
取出记录时,获取当前version
SELECT `id`,`name`,`price`,`version` FROM product WHERE id=1
更新时,version+1,如果WHERE语句中的version版本不对,则更新失败
UPDATE prodect SET price=price+50,`version`=`version`+1 WHERE id=1 AND `version`=1
MyBatisPlus实现乐观锁
修改实体类
package com.kj.mybatisplus.pojo;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.Version;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("product")
public class Product {
private Long id;
private String name;
private Integer price;
@Version //添加乐观锁注解
private Integer version;
}
添加乐观锁插件
package com.kj.mybatisplus.config;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@MapperScan("com.kj.mybatisplus.mapper")
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return interceptor;
}
}
测试结果
/*
==> Preparing: SELECT id,name,price,version FROM product WHERE id=?
==> Parameters: 1(Integer)
<== Columns: id, name, price, version
<== Row: 1, 外星人笔记本, 70, 0
<== Total: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@42b28ff1]
小李查询到的价格为:70
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@3c74aa0d] was not registered for synchronization because synchronization is not active
JDBC Connection [HikariProxyConnection@1820594585 wrapping com.mysql.cj.jdbc.ConnectionImpl@76ad6715] will not be managed by Spring
==> Preparing: SELECT id,name,price,version FROM product WHERE id=?
==> Parameters: 1(Integer)
<== Columns: id, name, price, version
<== Row: 1, 外星人笔记本, 70, 0
<== Total: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@3c74aa0d]
小王查询到的价格为:70
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@3743539f] was not registered for synchronization because synchronization is not active
JDBC Connection [HikariProxyConnection@1927452108 wrapping com.mysql.cj.jdbc.ConnectionImpl@76ad6715] will not be managed by Spring
==> Preparing: UPDATE product SET name=?, price=?, version=? WHERE id=? AND version=?
==> Parameters: 外星人笔记本(String), 120(Integer), 1(Integer), 1(Long), 0(Integer)
<== Updates: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@3743539f]
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6ae62c7e] was not registered for synchronization because synchronization is not active
JDBC Connection [HikariProxyConnection@780570776 wrapping com.mysql.cj.jdbc.ConnectionImpl@76ad6715] will not be managed by Spring
==> Preparing: UPDATE product SET name=?, price=?, version=? WHERE id=? AND version=?
==> Parameters: 外星人笔记本(String), 40(Integer), 1(Integer), 1(Long), 0(Integer)
<== Updates: 0
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6ae62c7e]
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@5d497a91] was not registered for synchronization because synchronization is not active
JDBC Connection [HikariProxyConnection@102185114 wrapping com.mysql.cj.jdbc.ConnectionImpl@76ad6715] will not be managed by Spring
==> Preparing: SELECT id,name,price,version FROM product WHERE id=?
==> Parameters: 1(Integer)
<== Columns: id, name, price, version
<== Row: 1, 外星人笔记本, 120, 1
<== Total: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@5d497a91]
老板查询到的价格为:120
*/
优化修改流程
@Test
public void testProduct01(){
//小李查询价格
Product productL = productMapper.selectById(1);
System.out.println("小李查询到的价格为:"+productL.getPrice());
//小王查询价格
Product productW = productMapper.selectById(1);
System.out.println("小王查询到的价格为:"+productW.getPrice());
//小李修改价格
productL.setPrice(productL.getPrice()+50);
productMapper.updateById(productL);
//小王修改价格
productW.setPrice(productW.getPrice()-30);
int result = productMapper.updateById(productW);
if (result == 0){
//修改失败
Product productNew = productMapper.selectById(1);
productNew.setPrice(productNew.getPrice()-30);
productMapper.updateById(productNew);
}
//老板查询价格
Product productB = productMapper.selectById(1);
System.out.println("老板查询到的价格为:"+productB.getPrice());
}
/*
==> Preparing: SELECT id,name,price,version FROM product WHERE id=?
==> Parameters: 1(Integer)
<== Columns: id, name, price, version
<== Row: 1, 外星人笔记本, 100, 0
<== Total: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@282ffbf5]
小李查询到的价格为:100
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@378f002a] was not registered for synchronization because synchronization is not active
JDBC Connection [HikariProxyConnection@452817647 wrapping com.mysql.cj.jdbc.ConnectionImpl@f1d0004] will not be managed by Spring
==> Preparing: SELECT id,name,price,version FROM product WHERE id=?
==> Parameters: 1(Integer)
<== Columns: id, name, price, version
<== Row: 1, 外星人笔记本, 100, 0
<== Total: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@378f002a]
小王查询到的价格为:100
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@75181b50] was not registered for synchronization because synchronization is not active
JDBC Connection [HikariProxyConnection@1869652507 wrapping com.mysql.cj.jdbc.ConnectionImpl@f1d0004] will not be managed by Spring
==> Preparing: UPDATE product SET name=?, price=?, version=? WHERE id=? AND version=?
==> Parameters: 外星人笔记本(String), 150(Integer), 1(Integer), 1(Long), 0(Integer)
<== Updates: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@75181b50]
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@4b0f2299] was not registered for synchronization because synchronization is not active
JDBC Connection [HikariProxyConnection@856047451 wrapping com.mysql.cj.jdbc.ConnectionImpl@f1d0004] will not be managed by Spring
==> Preparing: UPDATE product SET name=?, price=?, version=? WHERE id=? AND version=?
==> Parameters: 外星人笔记本(String), 70(Integer), 1(Integer), 1(Long), 0(Integer)
<== Updates: 0
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@4b0f2299]
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@352ed70d] was not registered for synchronization because synchronization is not active
JDBC Connection [HikariProxyConnection@117911771 wrapping com.mysql.cj.jdbc.ConnectionImpl@f1d0004] will not be managed by Spring
==> Preparing: SELECT id,name,price,version FROM product WHERE id=?
==> Parameters: 1(Integer)
<== Columns: id, name, price, version
<== Row: 1, 外星人笔记本, 150, 1
<== Total: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@352ed70d]
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@12704e15] was not registered for synchronization because synchronization is not active
JDBC Connection [HikariProxyConnection@1361409513 wrapping com.mysql.cj.jdbc.ConnectionImpl@f1d0004] will not be managed by Spring
==> Preparing: UPDATE product SET name=?, price=?, version=? WHERE id=? AND version=?
==> Parameters: 外星人笔记本(String), 120(Integer), 2(Integer), 1(Long), 1(Integer)
<== Updates: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@12704e15]
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@2373ad99] was not registered for synchronization because synchronization is not active
JDBC Connection [HikariProxyConnection@862146308 wrapping com.mysql.cj.jdbc.ConnectionImpl@f1d0004] will not be managed by Spring
==> Preparing: SELECT id,name,price,version FROM product WHERE id=?
==> Parameters: 1(Integer)
<== Columns: id, name, price, version
<== Row: 1, 外星人笔记本, 120, 2
<== Total: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@2373ad99]
老板查询到的价格为:120
*/
六、通用枚举
表中的有些字段值是固定的,例如性别(男或女),此时我们可以使用MyBatisPlus的通用枚举来实现
添加sex字段
创建sex枚举类
package com.kj.mybatisplus.enums;
import com.baomidou.mybatisplus.annotation.EnumValue;
import lombok.Getter;
@Getter
public enum SexEnum {
MALE(1,"男"),
FEMALE(2,"女");
@EnumValue //将注解所标识的属性的值存储到数据库中
private Integer sex;
private String sexName;
SexEnum(Integer sex, String sexName) {
this.sex = sex;
this.sexName = sexName;
}
}
在实体类中添加sex属性
private SexEnum sex;
测试
package com.kj.mybatisplus;
import com.kj.mybatisplus.enums.SexEnum;
import com.kj.mybatisplus.mapper.UserMapper;
import com.kj.mybatisplus.pojo.User;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
public class MybatisplusEnumTest {
@Autowired
private UserMapper userMapper;
@Test
public void test(){
User user = new User();
user.setName("kufufu");
user.setAge(18);
user.setSex(SexEnum.MALE);
int result = userMapper.insert(user);
System.out.println("result:"+result);
}
}
/*
==> Preparing: INSERT INTO user ( name, age, sex ) VALUES ( ?, ?, ? )
==> Parameters: kufufu(String), 18(Integer), 1(Integer)
<== Updates: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@55259aa7]
result:1
*/
七、代码生成器
1、引入依赖
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.5.2</version>
</dependency>
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
</dependency>
2、快速生成
FastAutoGenerator.create("url", "username", "password")
.globalConfig(builder -> {
builder.author("baomidou") // 设置作者
.enableSwagger() // 开启 swagger 模式
.fileOverride() // 覆盖已生成文件
.outputDir("D://"); // 指定输出目录
})
.dataSourceConfig(builder -> builder.typeConvertHandler((globalConfig, typeRegistry, metaInfo) -> {
int typeCode = metaInfo.getJdbcType().TYPE_CODE;
if (typeCode == Types.SMALLINT) {
// 自定义类型转换
return DbColumnType.INTEGER;
}
return typeRegistry.getColumnType(metaInfo);
}))
.packageConfig(builder -> {
builder.parent("com.baomidou.mybatisplus.samples.generator") // 设置父包名
.moduleName("system") // 设置父包模块名
.pathInfo(Collections.singletonMap(OutputFile.xml, "D://")); // 设置mapperXml生成路径
})
.strategyConfig(builder -> {
builder.addInclude("t_simple") // 设置需要生成的表名
.addTablePrefix("t_", "c_"); // 设置过滤表前缀
})
.templateEngine(new FreemarkerTemplateEngine()) // 使用Freemarker引擎模板,默认的是Velocity引擎模板
.execute();
八、多数据源
适用于多种场景:纯粹多库、读写分离、一主多从、混合模式等
目前我们就来模拟一个纯粹多库的一个场景,其他场景类似
场景说明:
我们创建两个库,分别为mybatis_plus(以前的库不动),与mybatis_plus_1(新增),将mybatis_plus库中的product表移动到mybatis_plus_1库,这样每个库一张表,通过一个测试用例分别获取用户数据与商品数据,如果获取到说明多库模拟成功
1、创建数据库以及表
创建数据库mybatis_plus_1和表product
CREATE DATABASE `mybatis_plus_1` /*!40100 DEFAULT CHARACTER SET utf8mb4*/;
use `mybatis_plus_1`;
CREATE TABLE product(
`id` BIGINT(20) NOT NULL COMMENT '主键ID',
`name` VARCHAR(30) NULL DEFAULT NULL COMMENT '商品名称',
`price` INT(11) DEFAULT 0 COMMENT '价格',
`version` INT(11) DEFAULT 0 COMMENT '乐观锁版本号',
PRIMARY KEY (id)
);
添加测试数据
INSERT INTO product(id,name,price) VALUES(1,'外星人笔记本',100);
删除mybatis_plus中的product表
use mybatis_plus;
DROP TABLE IF EXISTS product;
2、引入依赖
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<version>3.6.1</version>
</dependency>
3、配置多数据源
说明:注释掉之前的数据库连接,添加新配置
spring:
#配置数据源信息
datasource:
dynamic:
#设置默认的数据源或者数据源组,默认即为master
primary: master
#严格匹配数据源,默认false,true未匹配到指定数据源时抛出异常,false使用默认数据源
strict: false
datasource:
master:
url: jdbc:mysql://localhost:3306/mybatis_plus?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=UTC
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password:
slave_1:
url: jdbc:mysql://localhost:3306/mybatis_plus_1?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=UTC
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: