快速入门
配置想要什么去官网添加哪个
rewriteBatchedStatements=true批处理,配置在关于数据库的url中(与Mybatis无关)
第一步Mybatis依赖引入
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3.1</version>
</dependency>
第二步,在Mapper接口上认爸爸,也就是继承
public interface UserMapper extends BaseMapper<User> {
}
继承BaseMapper<T> ,泛型要指定对应的实体类
基础的增删改查对应insert,delete,update,select。
那么MabstisPlus怎么知道我们需要改动哪张表呢?
泛型中的User就是与数据库对应的PO.
MybatisPlus就是根据PO实体的信息来推断出表的信息,从而生成SQL的。默认情况下:
-
MybatisPlus会把PO实体的类名驼峰转下划线作为表名
-
MybatisPlus会把PO实体的所有变量名驼峰转下划线作为表的字段名,并根据变量类型推断字段类型
-
MybatisPlus会把名为id的字段作为主键
但很多情况下,默认的实现与实际场景不符,因此MybatisPlus提供了一些注解便于我们声明表信息。
常见注解
MybatisPlus小规则
MybatisPlus通过扫描对应的实体类经过反射获取信息
规则一:实体类类名驼峰转下划线为表名
规则二:变量名字驼峰转下划线为字段名字
规则三:自动把为id的变量视为主键
常用注解
@TableName:用来指定表名
@TableId:用来指定表中的主键字段信息
有一个type属性,默认的是(type = IdType.NONE)是枚举类型
这里比较常见的有三种:
-
AUTO
:利用数据库的id自增长 -
INPUT
:手动生成id -
ASSIGN_ID
:雪花算法生成Long
类型的全局唯一id,这是默认的ID策略
@TableField:用来指定表中的普通字段信息
没有指定id类型默认雪花算法生成
常见配置
大多数的配置都有默认值,因此我们都无需配置。但还有一些是没有默认值的,例如:
-
实体类的别名扫描包
-
全局id类型
-
mybatis-plus: type-aliases-package: com.itheima.mp.domain.po global-config: db-config: id-type: auto # 全局id类型为自增长
需要注意的是,MyBatisPlus也支持手写SQL的,而mapper文件的读取地址可以自己配置:
mybatis-plus:
mapper-locations: "classpath*:/mapper/**/*.xml" # Mapper.xml文件地址,当前这个是默认值。
可以看到默认值是classpath*:/mapper/**/*.xml
,也就是说我们只要把mapper.xml文件放置这个目录下就一定会被加载。
条件构造器(复杂的业务)
其中参数Wrapper就是负责构造条件的抽象类
基于QueryWrapper
查询名字中带o的用户,且存款大于1000
@Test
void testQueryWrapper(){
//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);
}
更新名字为jack的人余额为2000
@Test
void testUpdateByQueryWrapper(){
//1.要更新的数据
User user = new User();
user.setBalance(2000);
//2.更新的条件
QueryWrapper<User> wrapper = new QueryWrapper<User>().eq("username","jack");
//3.执行更新
userMapper.update(user,wrapper);
}
基于UpdateWrapper
更新id为1,2,4的用户,资金扣两百。
/**
* 更新id为1,2,4的用户,资金扣两百
*/
@Test
void testUpdateWrapper() {
List<Long> ids = List.of(1L, 2L, 4L);
UpdateWrapper<User> wrapper = new UpdateWrapper<User>()
.setSql("balance = balance - 200")
.in("id",ids);
userMapper.update(null,wrapper);
}
基于LambdaQueryWrapper
因为上面我们在代码中写死了字段名,所以我们寻求一个既不写死但是还能写入字段名的方法
其中一种办法是基于变量的gettter
方法结合反射技术。因此我们只要将条件对应的字段的getter
方法传递给MybatisPlus,它就能计算出对应的变量名了。而传递方法可以使用JDK8中的方法引用
和Lambda
表达式。 因此MybatisPlus又提供了一套基于Lambda的Wrapper,包含两个:
-
LambdaQueryWrapper
-
LambdaUpdateWrapper
分别对应QueryWrapper和UpdateWrapper
@Test
void testLambdaQueryWrapper() {
// 1.构建条件 WHERE username LIKE "%o%" AND balance >= 1000
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.lambda()
.select(User::getId, User::getUsername, User::getInfo, User::getBalance)
.like(User::getUsername, "o")
.ge(User::getBalance, 1000);
// 2.查询
List<User> users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}
@Test
void testLambdaQueryWrapper(){
//1.构建查询条件
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<User>()
.select(User::getId,User::getUsername,User::getInfo,User::getBalance)
.like(User::getUsername,"o")
.ge(User::getBalance,1000);
//2.查询
List<User> users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}
其实就是把死的字段替换为了get方法获取
自定义SQL
因为上面写balence = balence - 200是直接写在了服务层,但是要是都写在xml文件里面有in,还需要foreach ,所以有了自定义SQL。
我们使用mp构建条件,但是剩下的我们自己写
测试类
@Test
void testCustomSqlUpdate(){
//1.更新条件
List<Long> ids = List.of(1L,2L,3L);
int amout = 200;
//2.定义条件
QueryWrapper<User> wrapper = new QueryWrapper<User>().in("id",ids);
//3.调用自定义方法
userMapper.updateBalanceByIds(wrapper,amout);
}
UserMapper
void updateBalanceByIds(@Param("ew") QueryWrapper<User> wrapper,@Param("amout") int amout);
对于传进来的wrapper对象必须要使用@Param("ew"),且只有这一个注解指定名称
UserMapper.xml
<update id="updateBalanceByIds">
update user set balance = balance - #{amout} ${ew.customSqlSegment}
</update>
多表关联使用
假设我们查询为出所有收货地址在北京的并且用户id在1、2、4之中的用户 要是自己基于mybatis实现SQL:
<select id="queryUserByIdAndAddr" resultType="com.itheima.mp.domain.po.User">
SELECT *
FROM user u
INNER JOIN address a ON u.id = a.user_id
WHERE u.id
<foreach collection="ids" separator="," item="id" open="IN (" close=")">
#{id}
</foreach>
AND a.city = #{city}
</select>
但是我们使用自定义SQL,我们用MybatisPlus只写限制条件,代码如下
@Test
void testCustomJoinWrapper() {
// 1.准备自定义查询条件
QueryWrapper<User> wrapper = new QueryWrapper<User>()
.in("u.id", List.of(1L, 2L, 4L))
.eq("a.city", "北京");
// 2.调用mapper的自定义方法
List<User> users = userMapper.queryUserByWrapper(wrapper);
users.forEach(System.out::println);
}
Mapper层代码如下
@Select("SELECT u.* FROM user u INNER JOIN address a ON u.id = a.user_id ${ew.customSqlSegment}")
List<User> queryUserByWrapper(@Param("ew")QueryWrapper<User> wrapper);
Service接口
CRUD
通用的Service接口及默认实现,封装了一些常用的service模板方法。 通用接口为IService
,默认实现为ServiceImpl
,其中封装的方法可以分为以下几类:
-
save
:新增 -
remove
:删除 -
update
:更新 -
get
:查询单个结果 -
list
:查询集合结果 -
count
:计数 -
page
:分页查询
IUserService
public interface IUserService extends IService<User> {
}
UserService
泛型传入的两个类型第一个是对应的Mapper接口,第二个是实体类User
ServiceImpl封装的是IService的实现方法。
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper,User> implements IUserService{
}
例子
@Test
void testSaveUser(){
User user = new User();
//user.setId(5L);
user.setUsername("LiLei");
user.setPassword("123");
user.setPhone("18688990011");
user.setBalance(200);
user.setInfo("{\"age\": 24, \"intro\": \"英文老师\", \"gender\": \"female\"}");
user.setCreateTime(LocalDateTime.now());
user.setUpdateTime(LocalDateTime.now());
iUserService.save(user);
}
@Test
void testQuery(){
List<User> users = iUserService.listByIds(List.of(1L,2L,4L));
users.forEach(System.out::println);
}
接口例子
@ApiOperation("新增用户接口")
@PostMapping
public void saveUser(@RequestBody UserFormDTO userDTO){
//1.把DTO拷贝到PO里面
User user = BeanUtil.copyProperties(userDTO,User.class);
//2.新增
userService.save(user);
}
@ApiOperation("删除用户接口")
@DeleteMapping("{id}")
public void deleteUserById( @ApiParam("用户id") @PathVariable("id") Long id){
userService.removeById(id);
}
@ApiOperation("根据id查询用户接口")
@GetMapping("{id}")
public UserVO queryUserById(@ApiParam("用户id") @PathVariable("id") Long id){
//1.查询返回的是PO
User user = userService.getById(id);
//2. 把PO拷贝到VO返回
return BeanUtil.copyProperties(user, UserVO.class);
}
@ApiOperation("批量根据id查询用户接口")
@GetMapping
public List<UserVO> queryUserByIds(@ApiParam("用户id集合") @RequestParam("ids") List<Long> ids){
//1.查询返回的是PO
List<User> users = userService.listByIds(ids);
//2. 把PO拷贝到VO返回
return BeanUtil.copyToList(users, UserVO.class);
}
@ApiOperation("根据复杂条件查询用户")
@GetMapping("/list")
public List<UserVO> queryUsers(UserQuery query){
//1.查询用户PO
List<User> users = userService.queryUsers(query.getName(),query.getStatus(),query.getMinBalance(),query.getMaxBalance());
//2. 把PO拷贝到VO返回,用这个方法直接返回的是一个关于一个UserVO集合
return BeanUtil.copyToList(users, UserVO.class);
}
以上接口直接调用
糊涂包的属性拷贝是有两种类型,第一种是创建好对象拷贝进去,只是起到赋值的作用
第二种就是,BeanUtil.copyToList(users, UserVo.class)直接生成对象
复杂案例
controller
@ApiOperation("扣减用户余接口")
@PutMapping("/{id}/deduction/{money}")
public void deductBalance(@ApiParam("用户id") @PathVariable("id") Long id,
@ApiParam("扣减的金额") @PathVariable("money") Integer money){
userService.deductBalance(id,money);
}
IService
void deductBalance(Long id, Integer money);
UserServiceImpl
@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.扣减余额update user set balance = balance - ?
//第一种写法
//baseMapper.deductBalance(id,money);//baseMapper其实就是UserMapper,因为在开头继承的UserServiceImpl不仅实现了接口方法,也注入了UserMapper,之所以叫baseMapper是因为指定的是泛型
//第二种写法,若余额为0则修改状态
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();
}
复杂更新建议用LambdaUpdate(),先set再添加条件,第二个set就是假如余额为0的话就更新状态。
扩展功能
代码生成
在tool里面先连接数据库config datebase
再Code Generator
静态工具(DB)(Service多表调用)
Controller(跟据id单个查询)
@ApiOperation("根据id查询用户以及地址接口")
@GetMapping("{id}")
public UserVO queryUserById(@ApiParam("用户id") @PathVariable("id") Long id){
return userService.queryUserAndAddressById(id);
}
UserServiceImpl
@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.封装
//3.1转User的po为vo
UserVO userVO = BeanUtil.copyProperties(user,UserVO.class);
//3.2转地址VO
if(CollUtil.isNotEmpty(addresses)){
userVO.setAddress(BeanUtil.copyToList(addresses, AddressVO.class));
}
return userVO;
}
使用了Db调用LambdaQuery(Sddress.class)不用再注入AddressService调用方法,直接用字节码class
使用在Service相互调用的时候
Conllertroller(跟据id列表批量查询)
@ApiOperation("批量根据id查询用户及地址接口")
@GetMapping
public List<UserVO> queryUserByIds(@ApiParam("用户id集合") @RequestParam("ids") List<Long> ids){
return userService.queryUserAndAddressByIds(ids);
}
UserServiceImpl
@Override
public List<UserVO> queryUserAndAddressByIds(List<Long> ids) {
//1.查询用户
List<User> userList = listByIds(ids);
if(CollUtil.isEmpty(userList)){
return Collections.emptyList();
}
//2.查询住址
//2.1跟据用户id查询地址
List<Address> addresses = Db.lambdaQuery(Address.class).in(Address::getUserId,ids).list();
//2.2转换地址VO
List<AddressVO> addressVOList = BeanUtil.copyToList(addresses, AddressVO.class);
//2.3用户地址分批处理,相同用户的放入一个集合中
Map<Long, List<AddressVO>> addressMap = new HashMap<>(0);
if (CollUtil.isNotEmpty(addressVOList)) {
addressMap = addressVOList.stream().collect(Collectors.groupingBy(AddressVO::getUserId));
}
//3.转换VO返回
List<UserVO> list = new ArrayList<>(userList.size());
for (User user : userList) {
//转换user的po为vo
UserVO vo = BeanUtil.copyProperties(user,UserVO.class);
list.add(vo);
//转换地址vo
vo.setAddress(addressMap.get(user.getId()));
}
return list;
}
请注意,在分组的时候调用了addressVOList.stream().collect(Collectors.groupingBy(AddressVO::getUserId))
逻辑删除
是删除数据时,将其delete字段的值改为1,即为删除,且查询的时候,不仅条件上id相等,而且删除字段需要为0
只需要在yaml文件中配置mybatis的逻辑删除配置即可,此后调用删除与查正常调用即可
但是这种方法不太实用,所以不过多赘述
枚举处理器
状态多的时候不容易分辨,所以需要枚举类,但是实体类虽然转换了,但是从数据库读出来数据还是数字,那么Mubatis有办法,但是需求不太不符合,于是MybatisPlus出了一个新的枚举处理器
@Getter
public enum UserStatus {
NORMAL(1,"正常"),
FROZEN(2,"冻结"),
;
@EnumValue
private final int value;
@JsonValue
private final String desc;
UserStatus(int value, String desc) {
this.value = value;
this.desc = desc;
}
}
@ApiModelProperty("使用状态(1正常 2冻结)")
private UserStatus status;
第一:要给枚举中与数据库对应的value值添加@EnumValue注解
要给实体类和VO类的status全都设置为枚举类型的
JSON处理器
在转换的时候,JSON对象其实就是一个字符串,我们要是想取出JSON中的某个值的话,就要定义一个这样接收数据的实体类,就可以接收,但是需要我们使用JSON转换器
MyBatisPlus需要利用@TableName(autoResultMap = true)
还有@TableField(typeHandler = JacksonTypeHandler.class)来开启转换器
@Data
@NoArgsConstructor
@AllArgsConstructor(staticName = "of")
public class UserInfo {
private Integer age;
private String intro;
private String gender;
}
@Test
void testInsert() {
User user = new User();
user.setId(5L);
user.setUsername("Lucy");
user.setPassword("123");
user.setPhone("18688990011");
user.setBalance(200);
user.setInfo(UserInfo.of(24,"英文老师","female"));
user.setCreateTime(LocalDateTime.now());
user.setUpdateTime(LocalDateTime.now());
userMapper.insert(user);//原本要写一大段Mappper方法还有配置的xml文件里面的方法,现在直接调用就可以
//这是Mybatis的插入方法
}
分页插件
一个案例
公共的分页查询实体类,哪个需要哪个继承它
定义的是分页结果的数据