MyBatisPlus
一、基本使用
1.使用步骤:
①引入MybatisPlus依赖,代替Mybatis依赖
MyBatisPlus官方提供了starter,其中集成了Mybatis和MybatisPlus的所有功能,并且实现了自动装配效果。
xml
<!--MybatisPlus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3.1</version>
</dependency>
②引入MybatisPlus依赖,代替Mybatis依赖
java
public interface UserMapper extends BaseMapper<User> {
}
2.常见注解:
MyBatisPlus通过扫描实体类,并基于反射获取实体类信息作为数据库表信息
- 类名驼峰转下划线作为表名
- 名为id的字段作为主键
- 变量名驼峰转下划线作为表的字段名
常见注解
注解 | 作用 |
---|---|
@TableName | 用来指定表名 |
@TableId | 用来指定表中的主键字段信息 |
@TableField | 用来指定表中的普通字段信息 |
使用@TableField的常见场景:
-
成员变量名
与数据库字段名不一致
-
成员变量名
以is开头
,且是布尔值
-
成员变量名
与数据库关键字冲突
-
成员变量
不是数据库字段
IDType:
- AUTO:数据库自增长
- INPUT:通过set方法自行输入
- ASSIGN_ID:分配 ID,接口IdentifierGenerator的方法nextId来生成id,
默认实现类为DefaultIdentifierGenerator雪花算法
3.常见配置
MyBatisPlus的配置项继承了MyBatis原生配置和一些自己特有的配置
yaml
mybatis-plus:
type-aliases-package: com.itheima.mp.domain.po # 别名扫描包
mapper-locations: "classpath*:/mapper/**/*.xml" # Mapper.xml文件地址,默认值
configuration:
map-underscore-to-camel-case: true # 是否开启下划线和驼峰的映射
cache-enabled: false # 是否开启二级缓存
global-config:
db-config:
id-type: assign_id # id为雪花算法生成
update-strategy: not_null # 更新策略:只更新非空字段
二、核心功能
1.条件构造器
QueryWrapper
UpdateWrapper
LambadaQueryWrapper
java
@Test
void testQueryWarpper(){
// 1.构建查询条件
QueryWrapper<User> wrapper = new QueryWrapper<User>()
.select("id","username","info","balance")
.like("username","o")
.ge("balance",1000);
// 2.查询
List<User> users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}
@Test
void testUpdateQueryWarpper(){
// 1.更新后的数据
User user = new User();
user.setBalance(2000);
// 2.更新的条件
QueryWrapper<User> wrapper = new QueryWrapper<User>()
.eq("username","jack");
// 3.执行更新
userMapper.update(user,wrapper);
}
@Test
void testUpdateWarpper(){
// 1.更新后的数据
List<Long> ids = List.of(1L,2L,4L);
// 2.更新的条件
UpdateWrapper<User> wrapper = new UpdateWrapper<User>()
.setSql("balance = balance - 20")
.in("id",ids);
// 3.执行更新
userMapper.update(null,wrapper);
}
@Test
void testLambadaQueryWrapper(){
// 1.构造查询条件
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<User>()
.select(User::getId,User::getUsername,User::getInfo,User::getBalance)
.like(User::getUsername,"o")
.ge(User::getBalance,10000);
// 2.查询
List<User> users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}
条件构造器的用法:
QueryWrapper和LambdaQueryWrapper通常用来构建select、delete、update的where条件部分
UpdateWrapper和LambdaUpdateWrapper通常只有在set语句比较特殊才使用
尽量使用LambdaQueryWrapper和LambdaUpdateWrapper,避免硬编码
2.自定义 SQL
我们可以利用MyBatisPlus的Wrapper来构建复杂的Where条件,然后自己定义SQL语句中剩下的部分。
①基于Wrapper构建where条件
java
@Test
void testCustomSqlUpdate(){
// 1.更新的条件
List<Long> ids = List.of(1L,2L,4L);
int amount = 200;
// 2.定义条件
QueryWrapper<User> wrapper = new QueryWrapper<User>()
.in("id",ids);
// 3.调用自定义 SQL 方法
userMapper.updateBalanceByIds(wrapper,amount);
}
②在mapper方法参数中用Param注解声明wrapper变量名称,必须是ew
java
public interface UserMapper extends BaseMapper<User> {
void updateBalanceByIds
(@Param(Constants.WRAPPER) QueryWrapper<User> wrapper, @Param("amount") int amount);
}
③自定义SQL,并使用Wrapper条件
xml
<update id="updateBalanceByIds">
UPDATE user SET balance = balance - #{amount} ${ew.customSqlSegment}
</update>
3. Service接口
save
:新增remove
:删除update
:更新get
:查询单个结果list
:查询集合结果count
:计数page
:分页查询
使用步骤
- 自定义Service接口继承IService接口
java
public interface IUserService extends IService<User> {
}
- 自定义Service实现类,实现自定义接口并继承Servicelmpl类
java
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
}
IService 的 Lambda 查询、修改
java
@Override
@Transactional
public void deductBalance(Long id, Integer money) {
// 1.查询用户
User user = getById(id);
// 2.校验用户状态
if (user == null || user.getStatus() == 2){
throw new RuntimeException("用户状态异常");
}
// 3.校验余额是否充足
if(user.getBalance() < money){
throw new RuntimeException("用户余额不足");
}
// 4.扣减余额
//baseMapper.deductBalance(id,money);
int remainBalance = user.getBalance() - money;
lambdaUpdate()
.set(User::getBalance,remainBalance)
.set(remainBalance==0,User::getStatus,2)
.eq(User::getId,id)
.eq(User::getBalance,User.getBalance())//加锁,乐观锁
.update();
}
@Override
public List<User> queryUsers(String name, Integer status, Integer minBalance, Integer maxBalance) {
return lambdaQuery()
.like(name!=null,User::getUsername,name)
.eq(status!=null,User::getStatus,status)
.ge(minBalance!=null,User::getBalance,minBalance)
.le(maxBalance!=null,User::getBalance,maxBalance)
.list();
}
IService 批量新增
批处理方案:
-
普通for循环逐条插入速度极差,不推荐
-
MP的批量新增,基于预编译的批处理,性能不错
-
配置jdbc参数,开rewriteBatchedStatements,性能最好
&rewriteBatchedStatements=true
三、扩展功能
MybatisPlus插件
- 安装插件MybatisPlus插件
- 在 tools 下的Config Database配置 Database
- 在 tools 下的Code Generator选择需要配置的项
静态工具Db
避免Service互相注入循环依赖
java
@Override
public UserVO queryUserAndAddressById(Long id) {
// 1.查询用户
User user = getById(id);
if (user == null || user.getStatus() ==2){
throw new RuntimeException("用户状态异常");
}
// 2.查询地址
List<Address> addresses = Db.lambdaQuery(Address.class)
.eq(Address::getUserId, id).list();
// 3.封装VO
// TODO 3.1 将 User的PO转换为VO
UserVO userVO = BeanUtil.copyProperties(user, UserVO.class);
// TODO 3.2 转地址VO
if (CollUtil.isNotEmpty(addresses)){
userVO.setAddresses(BeanUtil.copyToList(addresses,AddressVO.class));
}
return userVO;
}
逻辑删除
逻辑删除就是基于代码逻辑模拟删除效果,但并不会真正删除数据。思路如下:
-
在表中添加一个字段标记数据是否被删除
-
当删除数据时把标记置为1
-
查询时只查询标记为0的数据
删除操作:
UPDATE user SET deleted = 1 WHERE id = 1 AND deleted = 0
查询操作:
SELECT * FROM user WHERE deleted = 0
MybatisPlus提供了逻辑删除功能,无需改变方法调用的方式,而是在底层帮我们自动修改CRUD的语句。我们要做的就是在application.yaml文件中配置逻辑删除的字段名称和值即可:
yaml
mybatis-plus:
global-config:
db-config:
logic-delete-field: flag # 全局逻辑删除的实体字段名,字段类型可以是boolean、integer
logic-delete-value: 1 # 逻辑已删除值(默认为 1)
logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
逻辑删除本身也有自己的问题,比如:
- 会导致数据库表垃圾数据越来越多,影响查询效率
- SQL中全都需要对逻辑删除字段做判断,影响查询效率
因此,我不太推荐采用逻辑删除功能,如果数据不能删除,可以采用把数据迁移到其它表的办法。
枚举处理器
在application.yml中配置全局枚举处理器:
mybatis-plus:
configuration:
default-enum-type-handler: com.baomidou.mybatisplus.core.handlers.MybatisEnumTypeHandler
如何实现PO类中的枚举类型变量与数据库字段的转换?
①给枚举中的与数据库对应value值添加@EnumValue注解
②在配置文件中配置统一的枚举处理器,实现类型转换
java
public enum UserStatus {
NORMAL(1,"正常"),
FROZEN(2,"冻结"),
;
@EnumValue //该注解告知MyBatis枚举类型的值
private final int value;
@JsonValue //该注解指明返回的数据值为什么
private final String desc;
UserStatus(int value,String desc){
this.value = value;
this.desc = desc;
}
}
四、插件功能
序号 | 拦截器 | 描述 |
---|---|---|
1 | TenantLineInnerInterceptor | 多租户插件 |
2 | DynamicTableNameInnerInterceptor | 动态表名插件 |
3 | PaginationInnerInterceptor | 分页插件 |
4 | OptimisticLockerInnerInterceptor | 乐观锁插件 |
5 | IllegalSQLInnerInterceptor | SQL性能规范插件,检测并拦截垃圾SQL |
6 | BlockAttackInnerInterceptor | 防止全表更新和删除的插件 |
分页插件
- 配置类中注册MyBatisPlus的核心插件,同时添加分页插件
java
@Configuration
public class MybatisConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
// 1.初始化核心插件
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 2.添加分页插件
PaginationInnerInterceptor pageInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);
pageInterceptor.setMaxLimit(1000L); // 设置分页上限
interceptor.addInnerInterceptor(pageInterceptor);
return interceptor;
}
}
- Page Api的使用
java
@Test
void testPageQuery() {
// 1.查询
int pageNo = 1, pageSize = 5;
// 1.1.分页参数
Page<User> page = Page.of(pageNo, pageSize);
// 1.2.排序参数, 通过OrderItem来指定
page.addOrder(new OrderItem("balance", false));
// 1.3.分页查询
Page<User> p = userService.page(page);
// 2.总条数
System.out.println("total = " + p.getTotal());
// 3.总页数
system.out.println("pages = " + p.getPages());
// 4.分页数据
List<User> records = p.getRecords();
records.forEach(System.out::println);
}