入门
1.导入依赖
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.3.1</version>
</dependency>
2.接口继承BaseMapper,注意声明对应的实体类作为泛型
3.注入
@Autowired
private UserMapper userMapper;
4.基础操作
首先,查看数据库结构和实体类
package com.itheima.mp.domain.po;
import lombok.Data;
import java.time.LocalDateTime;
@Data
public class User {
/**
* 用户id
*/
private Long id;
/**
* 用户名
*/
private String username;
/**
* 密码
*/
private String password;
/**
* 注册手机号
*/
private String phone;
/**
* 详细信息
*/
private String info;
/**
* 使用状态(1正常 2冻结)
*/
private Integer status;
/**
* 账户余额
*/
private Integer balance;
/**
* 创建时间
*/
private LocalDateTime createTime;
/**
* 更新时间
*/
private LocalDateTime updateTime;
}
- 增
默认为分配id,实现类为DefaultldentifierGenerators雪花算法
可以自己配置,注解优先级大于全局配置(见下方讲解)
如果实体类设置id则按照设置的id来
void testInsert() {
User user = new User();
user.setUsername("Lucy");
user.setPassword("123");
user.setPhone("18688990011");
user.setBalance(200);
user.setInfo("{\"age\": 24, \"intro\": \"英文老师\", \"gender\": \"female\"}");
user.setCreateTime(LocalDateTime.now());
user.setUpdateTime(LocalDateTime.now());
userMapper.insert(user);
}
- 查
单个查询
void testSelectById() {
User user = userMapper.selectById(4L);
System.out.println("user = " + user);
}
数组查询
@Test
void testQueryByIds() {
List<User> users = userMapper.selectBatchIds(List.of(1L, 2L, 3L, 4L));
users.forEach(System.out::println);
}
- 改
@Test
void testUpdateById() {
User user = new User();
user.setId(4L);
user.setBalance(20000);
userMapper.updateById(user);
}
- 删
@Test
void testDeleteUser() {
userMapper.deleteById(4L);
}
常见注解
常用配置
条件构造器
MyBatisPlus支持各种复杂的where条件,可以满足日常开发的所有需求。
基于QueryWrapper的查询
需求:
①查询出名字中带o的,存款大于等于1000元的人的id、username、info、 balance字段
@Test
void testQueryWrapper() {
//1.创建查询条件
QueryWrapper<User> wrapper = new QueryWrapper<User>()
.select("id", "username", "balance")
.like("username", "%o%")
.ge("balance", 1000);
//2.查询
List<User> users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}
②更新用户名为jack的用户的余额
@Test
void testUpdateByQueryWrapper() {
User user = new User();
user.setBalance(666666);
QueryWrapper<User> wrapper = new QueryWrapper<User>()
.eq("id", 1L);
userMapper.update(user, wrapper);
}
基于UpdateWrapper的更新
需求:更新id为1,2 4的用户的余额,扣200
@Test
void testUpdateWrapper() {
UpdateWrapper<User> wrapper = new UpdateWrapper<User>()
.setSql("balance = balance - 200")
.in("id", List.of(1L, 2L, 3L));
userMapper.update(null, wrapper);
}
Lambda的区别避免硬编码,和没有Lambda的区别就是把字段名换成get
举个栗子:
@Test
void testQueryWrapper() {
//1.创建查询条件
// QueryWrapper<User> wrapper = new QueryWrapper<User>()
// .select("id", "username", "balance")
// .like("username", "%o%")
// .ge("balance", 1000);
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<User>()
.select(User::getId, User::getUsername, User::getBalance)
.like(User::getUsername, "%o%")
.ge(User::getBalance, 1000);
//2.查询
List<User> users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}
条件构造器的用法:
●QueryWrapper和LambdaQueryWrapper通常用来构建select
、delete、 update的where条件部分
●UpdateWrapper和LambdaUpdateWrapper通常只有在set语
句比较特殊才使用
●尽量使用LambdaQueryWrapper和LambdaUpdateWrapper
避免硬编码
自定义SQL
MyBatisPlus为什么需要自定义SQL?
举个栗子:对比xml和mp(mp是MyBatisPlus的简称)
需求:将id在指定范围的用户(例如1、2、4 )的余额扣减指定值
java实现
@Test
void testUpdateWrapper() {
UpdateWrapper<User> wrapper = new UpdateWrapper<User>()
.setSql("balance = balance - 200")
.in("id", List.of(1L, 2L, 3L));
userMapper.update(null, wrapper);
}
xml形式
<update id="updateUserBalance">
UPDATE user
SET balance = balance - 200
WHERE id IN
<foreach collection="idList" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</update>
对比可得:mp擅长where条件但sql语句不得不写在业务代码中不利于维护。
步骤:
- 基于Wapper构建where条件
@Test
void testCustomSqlUpdate() {
List<Long> longs = List.of(1L, 2L, 3L);
int amount=200;
QueryWrapper<User> userQueryWrapper = new QueryWrapper<User>().in("id", longs);
userMapper.customSqlUpdate(userQueryWrapper, amount);
}
- 在mapper方法参数中使用Param注解声明wrapper变量名称必须是ew
void customSqlUpdate(@Param(("ew")) QueryWrapper<User> userQueryWrapper, @Param(("amount")) int amount);
- 自定义SQL,并使用Wrapper条件
<update id="customSqlUpdate">
UPDATE user SET balance=balance-#{amount} ${ew.customSqlSegment}
</update>
Service接口
IService 接口在软件开发中,尤其是基于服务架构(如微服务架构)的系统中,扮演着非常重要的角色。
可以减少简单的service层代码,替代简单的增删改查
作用:
- 定义服务契约:明确服务层应该提供哪些功能,这对于服务提供者和消费者之间的通信至关重要。
- 解耦:通过接口定义服务,可以将服务的实现与服务的调用者解耦。这样,服务的调用者就无需知道服务的具体实现细节,只需要知道接口定义即可。
使用步骤:
ServiceImpl
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper,User> implements IUserService {
}
Service
public interface IUserService extends IService<User> {
}
控制层
@Autowired
private IUserService iUserService;
@Test
void testSaveUser() {
User user = new User();
user.setUsername("Lili");
user.setPassword("123");
user.setPhone("18688990011");
user.setBalance(200);
user.setInfo("{\"age\": 24, \"intro\": \"英文老师\", \"gender\": \"female\"}");
user.setCreateTime(LocalDateTime.now());
user.setUpdateTime(LocalDateTime.now());
System.out.println(iUserService);
iUserService.save(user);
}
解释IUserService接口实现IService接口(声明泛型),UserServiceImpl实现IUserService接口便可以重写IService的方法,但是由于方法太多显然无法全部实现,可以继承提供的ServiceImpl类(声明自定义的Mapper和DAO)
ServiceImpl中根据提供的Mapper实现对应的代码,实际上和手动实现MyBatisPlus一样。
IService基础业务接口示例
package com.itheima.mp.controller;
import cn.hutool.core.bean.BeanUtil;
import com.itheima.mp.domain.dto.UserFormDTO;
import com.itheima.mp.domain.po.User;
import com.itheima.mp.domain.vo.UserVO;
import com.itheima.mp.service.IUserService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@Api(tags = "用户模块")
@RequestMapping("/users")
@RequiredArgsConstructor
public class UserController {
private final IUserService userService;
@PostMapping
@ApiOperation(value = "新增用户信息")
public void saveUser(@RequestBody UserFormDTO userFormDTO) {
User user = BeanUtil.copyProperties(userFormDTO, User.class);
userService.save(user);
}
@DeleteMapping("/{id}")
@ApiOperation(value = "删除用户信息")
public void deleteUser(@PathVariable("id") Long id) {
userService.removeById(id);
}
@ApiOperation(value = "根据id查询用户信息")
@GetMapping("/{id}")
public UserVO getUser(@ApiParam("用户id") @PathVariable("id") Long id) {
User user = userService.getById(id);
return BeanUtil.copyProperties(user, UserVO.class);
}
@ApiOperation(value = "根据id查询批量用户信息")
@GetMapping
public List<UserVO> getUser(@ApiParam("用户id集合") @RequestParam("ids") List<Long> ids) {
List<User> users = userService.listByIds(ids);
return BeanUtil.copyToList(users, UserVO.class);
}
@ApiOperation(value = "扣减用户余额")
@PutMapping("/{id}/deduct/{money}")
public void getUser(@ApiParam("用户id") @PathVariable("id") Long id,
@ApiParam("扣减金额") @PathVariable("money") Integer money) {
userService.deductBalance(id, money);
}
}
IService的Lambda查询
lambdaQuery()
需求:实现一个根据复杂条件查询用户的接口,查询条件如下:
●name:用户名关键字,可以为空
●status:用户状态,可以为空
●minBalance:最小余额,可以为空
●maxBalance:最大余额,可以为空
查询
多个使用list()
一个使用one()
数量使用 count()
是否存在exist()
分页使用page()
替换了下方代码
lambdaUpdate()
扣除指定id的余额
@Override
@Transactional
public void deductBalance(Long id, Integer money) {
User byId = getById(id);
if(byId==null||byId.getStatus()==2){
throw new RuntimeException("用户不存在或已被冻结");
}
if(byId.getBalance()<money){
throw new RuntimeException("余额不足");
}
lambdaUpdate()
.set(User::getBalance,byId.getBalance()-money)
.set(byId.getBalance()-money==0,User::getStatus,2)
.eq(User::getId,id)
.eq(User::getStatus,byId.getStatus())//乐观锁
.update();
}
IService的批量新增
对比性能
1.使用for循环依次新增用户(和数据库交互了100000次)
private User createUser(Long num) {
User user = new User();
user.setUsername("user_"+num);
user.setPassword("123");
user.setPhone("18688990011");
user.setBalance(200);
user.setInfo("{\"age\": 24, \"intro\": \"英文老师\", \"gender\": \"female\"}");
user.setCreateTime(LocalDateTime.now());
user.setUpdateTime(LocalDateTime.now());
return user;
}
@Test
void testSaveUser() {
long l = System.currentTimeMillis();
for (int i = 0; i < 100000; i++) {
iUserService.save(createUser(l+i));
}
long end = System.currentTimeMillis();
System.out.println("耗时:"+(end-l));
}
2.使用for循环批量插入数据(依次和数据库交互插入一千条数据)
一次new十万条数据过大,并且网络请求数据包有上线
@Test
void testBathSaveUser() {
long l = System.currentTimeMillis();
ArrayList<User> objects = new ArrayList<>(1000);
for (int i = 0; i <= 100000; i++) {
objects.add(createUser(l+i));
if(i%1000==0){
iUserService.saveBatch(objects);
objects.clear();
}
}
long end = System.currentTimeMillis();
System.out.println("耗时:"+(end-l));
System.out.println("总数:"+iUserService.count());
}
开启批处理
开启rewriteBatchedStatements=true参数
配置jdbc参数,开rewriteBatchedStatements,性能最好
例如:
url: jdbc:mysql://127.0.0.1:3306/mp?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true
注意rewriteBatchedStatements是mysql的配置,由引入的驱动做的,不是mp做到
实际上是把数据整理成了一条sql,只和数据库进行了一次网络请求
静态工具
为了避免循环依赖例如user中注入address,address注入user
使用:
在UserServiceImpl中只要为声明字节码就可以使用Db的API
List<Address> list = Db.lambdaQuery(Address.class)
.eq(Address::getUserId, id)
.list();
注意:Db的Api和Isevice类似
优秀栗子:
@Override
public List<UserVO> getUserVOByIds(List<Long> ids) {
//根据ids查询用户
List<User> users = listByIds(ids);
if(CollUtil.isEmpty(users)){
return Collections.emptyList();
}
//根据查询的用户获得准确的ids
List<Long> collectid = users.stream().map(User::getId).collect(Collectors.toList());
//使用Db根据ids查询地址
List<Address> addresses = Db.lambdaQuery(Address.class)
.in(Address::getUserId, collectid)
.list();
List<AddressVO> addressVOS = BeanUtil.copyToList(addresses, AddressVO.class);
//根据地址的userId分组
Map<Long, List<AddressVO>> collect = addressVOS.stream().collect(Collectors.groupingBy(AddressVO::getUserId));
List<UserVO> userVOS = new ArrayList<>(users.size());
/**
* 先 add 再 set 可以避免这些风险,原因如下:
*
* 确保对象存在:首先将对象添加到集合中,确保对象已经存在于集合中。这样可以避免在后续操作中出现对象未添加到集合中的情况,从而减少潜在的错误。
*
* 简化异常处理:如果在 set 属性时发生异常,由于对象已经被添加到集合中,可以更容易地进行异常处理和调试,确保对象不会丢失。
*
* 逻辑清晰:先 add 再 set 的顺序使得代码的逻辑流程更加清晰,便于理解和维护。这种顺序符合一般的编程习惯,减少潜在的逻辑错误。
*
* 先 set 再 add 可能会导致在设置属性时出现异常或逻辑错误,从而导致对象没有被正确添加到集合中的原因如下:
*
* 异常处理:在设置属性时,可能会涉及到一些复杂的逻辑或外部资源的访问(例如数据库查询、网络请求等)。这些操作可能会抛出异常,如果在 set 属性时发生异常,对象可能还没有被添加到集合中,从而导致对象丢失。
*
* 逻辑错误:在设置属性时,可能会依赖于某些条件或状态。如果在这些条件或状态不满足的
*/
for (User user : users) {
UserVO userVO = BeanUtil.copyProperties(user, UserVO.class);
userVOS.add(userVO);
userVO.setAddresses(collect.get(user.getId()));
}
return userVOS;
}
逻辑删除
JSON处理器
场景:数据库中user表中有一-个json类型的字段:
步骤:
1.
注解 @TableField(typeHandler = JacksonTypeHandler.class):
这个注解告诉 MyBatis Plus,在处理 info 字段时,使用 JacksonTypeHandler 进行类型转换。
JacksonTypeHandler 会将 UserInfo 对象序列化为 JSON 字符串,存储在数据库中;或者将数据库中的 JSON 字符串反序列化为 UserInfo 对象。
2.
开启自动映射
插件功能
MyBatisPlus提供的内置拦截器有下面这些:
分页插件
@Configuration
public class MyBatisConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor mybaPlusInterceptor = new MybatisPlusInterceptor();
//添加分页配置
PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);
paginationInnerInterceptor.setMaxLimit(1000L);
mybaPlusInterceptor.addInnerInterceptor(paginationInnerInterceptor);
return mybaPlusInterceptor;
}
}
@Test
void testQuery(){
Page<User> userPage = Page.of(1, 2);
//添加排序条件true表示正序
userPage.addOrder(new OrderItem("id", true));
userPage.addOrder(new OrderItem("balance", true));
Page<User> page = iUserService.page(userPage);
List<User> records = page.getRecords();
System.out.println(records);
System.out.println("******************");
System.out.println(page);
}