一文读懂:MybatisPlus从入门到进阶

news2024/9/21 12:47:06

快速入门

简介

在项目开发中,Mybatis已经为我们简化了代码编写。

但是我们仍需要编写很多单表CURD语句,MybatisPlus可以进一步简化Mybatis。

MybatisPlus官方文档:https://www.baomidou.com/,感谢苞米豆黑马程序员

MybatisPlus无侵入的提供了代码生成、自动分页、逻辑删除、自动填充等功能。

需要注意,MybatisPlus提高了单表查询开发效率,复杂的多表查询还需要结合Mybatis配置文件使用。

引入依赖

MybatisPlus提供了starter,包含Mybatis以及MybatisPlus的相关依赖:

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.5.3.1</version>
</dependency>

定义Mapper

为了简化单表CURD,MybatisPlus提供了BaseMapper接口。

BaseMapper实现了单表CURD,我们定义的Mapper接口只需要继承它:

package com.test.mp.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.itheima.mp.domain.po.User;

public interface UserMapper extends BaseMapper<User> {
}

这样,对于单表简单的CURD,我们不再需要编写SQL语句。

image-20240206100338815

原理

MybatisPlus是如何判断我们查询的表和字段名呢?

MybatisPlus通过扫描实体类,基于反射获取实体类信息作为数据库表信息。

在定义Mapper继承BaseMapper<User>时,我们指定了泛型<User>。

这个泛型就是数据库对应的POJO,MybatisPlus就是根据这个泛型进行推断生成SQL:

  • 把POJO的类名驼峰转下划线作为表名
  • 把POJO所有变量名驼峰转下划线作为字段名,并根据变量类型推断字段类型
  • 把名为id的字段作为主键

常用注解

@TableName

如果POJO类名不符合约定,与表明不一致。

可以使用@TableName注解指定表名。

例如,POJO类名为UserMapper,实际的数据库表名为tb_user:

package com.test.mp.domain.po;

import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

import java.time.LocalDateTime;

@Data
@TableName("tb_user")
public class User {
    private Long id;
    private String username;
    private String password;
    private String phone;
    private String info;
    private Integer status;
    private Integer balance;
    private LocalDateTime createTime;
    private LocalDateTime updateTime;
}

@TableId

如果POJO类里没有名为id的成员变量,或者主键不是id。

可以使用@TableId注解指定主键成员变量,它有2个属性:

  • value:数据库主键字段名(成员变量名和数据库字段名不一致时使用)
  • type:主键的策略(默认为雪花算法,常用IdType.NONE)

其中,常用的type有如下三种:

  1. AUTO:数据库自增长,即AUTO_INCREMENT。
  2. INPUT:通过set方法自行输入。
  3. ASSIGN_ID:分配ID,接口identifierGenerator的方法nextId来生成id,默认实现类为DefaultIdentifierGenerator雪花算法。

(通常情况下,我们按照MybatisPlus规范定义POJO和数据库字段,因此只需要指定@TableId(type=IdType.AUTO)

例如:

package com.test.mp.domain.po;

import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.Data;

import java.time.LocalDateTime;

@Data
@TableName("tb_user")
public class User {
    @TableId(value="uid", type=IdType.AUTO)
    private Long uid;
    private String username;
    private String password;
    private String phone;
    private String info;
    private Integer status;
    private Integer balance;
    private LocalDateTime createTime;
    private LocalDateTime updateTime;
}

@TableField

和TableId的用途一样,只不过不指定主键。只指定成员变量和数据库字段名对应关系。

除此之外,有3种特殊情况也需要用到@TableField注解:

  1. 成员变量是Boolean类型且以is开头,如isMarried。MybatisPlus通过反射获取成员变量名时会去掉is,得到Married。
  2. 成员变量名和sql关键字冲突,如order。
  3. 成员变量不是数据库字段。

因此,一般不建议成员变量使用is开头或与sql关键字冲突。如果非要这么使用,需要用@TableField注解指定和数据库字段的映射关系。

例如:

package com.itheima.mp.domain.po;

import com.baomidou.mybatisplus.annotation.TableField;
import lombok.Data;

import java.time.LocalDateTime;

@Data
public class User {
    private Long id;
    private String username;
    private String password;
    @TableField("`is_married`")
    private boolean isMarried;
    @TableField("`order`")
    private Integer orderr;
    @TableField(exist=false)
    private Integer notInTable;
}

常见配置

MybatisPlus的配置项继承了Mybatis原生配置,并在此基础上新增自己的配置。如:

mybatis-plus:
 type-aliases-package: com.test.mp.domain.pojo		# 别名扫描包
 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						# 更新策略:只更新非空字段

核心功能

MybatisPlus为我们提供的CURD是根据id为条件的,如果我们需要复杂条件的CURD,需要用到一些核心功能。

条件构造器

在MybatisPlus提供的BaseMapper接口按ctrl+f12,发现很多方法的参数有Wrapper类型:

image-20240206182305020

选中代码中的Wrapper类名按ctrl+h显示所有子类,右击Wrapper选择Diagrams -> Show Diagrams Popup。

然后右击Wrapper选择Show Implementations查看继承关系图,这些Wrapper就是条件构造器:

image-20240206204009908

QueryWrapper

QueryWrapper常用于构建select、delete、update的where条件部分。

我们以QueryWrapper为例,查询名字中带o的且存款大于等于1000元的用户的id、username、info和balance字段:

SELECT id, username, info, balance From user WHERE username LIKE ? AND balance >= ?;
@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:

UPDATE user SET balance = 2000 WHERE (username = "jack");
@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

UpdateWrapper通常只有在set语句比较特殊才使用。

我们以UpdateWrapper为例,更新id为1, 2, 4的用户的余额,扣200。

UPDATE user SET balance = balance - 200 WHERE id in (1, 2, 4);
@Test
void testUpdateByWrapper() {
    // 1. 要更新的id集合
    List<Long> ids = List.of(1L, 2L, 4L);
    
    // 2. 设置set语句并构建更新条件
    UpdateWrapper<User> wrapper = new UpdateWrapper<User>()
    .setSql("balance = balance -200")
    .in("id", ids);
    
    // 3. 更新
    userMapper.update(null, wrapper);
}

LambdaWrapper

LambdaWrapper包括:LambdaQueryWrapper和LambdaUpdateWrapper。

我们以LambdaQueryWrapper为例,前面使用QueryWrapper时,我们使用了字符串硬编码。

为了避免硬编码,MybatisPlus推荐使用LambdaQueryWrapper,通过lambda方法引用来调用:

@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);
}

自定义SQL

在前面的UpdateWrapper中,我们将set更新的sql语句写在了业务层代码中,不符合实际开发低耦合的规范。

比较常见的用法是用MybatisPlus的Wrapper来构建复杂的Where条件,然后自己定义SQL语句中剩下的部分。

(对于多表操作,也可以用这种方法构建Where条件,然后编写剩余部分的SQL语句)

例如,我们将id在指定范围内的用户的余额扣减指定值:

  1. 基于Wrapper构建where条件:

    @Test
    void testCustomSqlUpdate() {
        // 1.更新条件
        List<Long> ids = List.of(1L, 2L, 4L);
        int amount = 200;
    
        // 2.定义条件
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<User>()
        .in(User::getId, ids);
    
        // 3.调用自定义SQL方法
        userMapper.updateBanlanceByIds(wrapper, amount);
    }
    
  2. 在mapper方法参数中用Param注解声明wrapper变量名称,必须是ew:

    // 注解声明的Wrapper的变量名称必须是ew,也可以使用常量值Constants.WRAPPER
    public interface UserMapper extends BaseMapper<User> {
        void updateBanlanceByIds(@Param(Constants.WRAPPER) LambdaQueryWrapper<User> wrapper, @Param("amount") int amount);
    }
    
  3. 自定义SQL,并使用传递下来的Wrapper条件:

    <update id="updateBanlanceByIds">
    	UPDATE user SET balance = balance - #{amount} ${ew.customSqlSegment}
    </update>
    

Service接口

原理

MybatisPlus已经帮我们简化了单表CURD操作,但我们还需要写Service层代码。

为了进一步简化,MybatisPlus为我们提供了IService接口,这个接口提供了简单CURD的Service抽象方法。

我们定义的UserService接口可以继承IService,IService包含简单CURD的抽象方法。但是会出现一个问题。

IService接口非常多,UserServiceImpl在实现接口时需要实现全部方法,如下图所示:

image-20240206215249641

为了解决这个问题,MybatisPlus又为我们提供了ServiceImpl实现Iservice所有的抽象方法,我们的UserServiceImpl只需要继承它即可:

image-20240206215121665

基本用法

下面通过一个例子来演示IService:

  1. 定义IUserService接口继承IService接口,并指定泛型为POJO类:

    package com.test.mp.service;
    
    import com.baomidou.mybatisplus.extension.service.IService;
    import com.test.mp.domain.po.User;
    
    public interface IUserService extends IService<User> {
    }
    
  2. 定义IUserServiceImpl类实现IUserService接口,然后继承ServiceImpl类并指定Mapper和POJO的泛型:

    package com.test.mp.service.impl;
    
    import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
    import com.test.mp.domain.po.User;
    import com.test.mp.mapper.UserMapper;
    import com.test.mp.service.IUserService;
    import org.springframework.stereotype.Service;
    
    @Service
    public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
    }
    
  3. 测试,简单CURD无需编写Service和Mapper代码:

    image-20240207085140784
    @SpringBootTest
    class IUserServiceTest {
        @Autowired
        private IUserService userService;
    
        @Test
        void testSaveUser() {
            User user = new User();
    
            user.setUsername("Test");
            user.setPassword("123456");
            user.setPhone("18888888888");
            user.setBalance(200);
            user.setCreateTime(LocalDateTime.now());
            user.setUpdateTime(LocalDateTime.now());
    
            userService.save(user);
        }
        
        @Test
        void testQuery() {
            List<User> users = userService.listByIds(List.of(1L, 2L, 4L));
            users.forEach(System.out::println);
        }
    }
    

基础用法

下面以一个案例来演示MybatisPlus的强大的功能:实现对用户表的新增、删除、查询。

  1. 为了方便接口调试,我们引入Swagger相关依赖:

    <!--swagger-->
    <dependency>
        <groupId>com.github.xiaoymin</groupId>
        <artifactId>knife4j-openapi2-spring-boot-starter</artifactId>
        <version>4.1.0</version>
    </dependency>
    <!--web-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    
  2. 然后在Controller编写,对于简单的增、删、查,IService已经为我们封装好了相关方法。

    我们不需要再编写Service和Mapper代码就可以实现这些功能:

    @Api(tags = "用户管理接口")
    @RequestMapping("/users")
    @RestController
    @RequiredArgsConstructor
    public class UserController {
    	
        // @RequiredArgsConstructor注解通过构造函数给userService赋值
        private final IUserService userService;
    
        @ApiOperation("新增用户接口")
        @PostMapping
        public void saveUser(@RequestBody UserFormDTO userDTO) {
            User user = BeanUtil.copyProperties(userDTO, User.class);
            userService.save(user);
        }
    
        @ApiOperation("删除用户接口")
        @DeleteMapping("/{id}")
        public void deleteUser(@ApiParam("用户id") @PathVariable("id") Long id) {
            userService.removeById(id);
        }
    
        @ApiOperation("根据id查询用户接口")
        @GetMapping("/{id}")
        public UserVO queryUserById(@ApiParam("用户id") @PathVariable("id") Long id) {
            User user = userService.getById(id);
            return BeanUtil.copyProperties(user, UserVO.class);
        }
    
        @ApiOperation("根据id批量查询用户接口")
        @GetMapping
        public List<UserVO> queryUserByIds(@ApiParam("用户id") @RequestParam("ids") List<Long> ids) {
            List<User> users = userService.listByIds(ids);
            return BeanUtil.copyToList(users, UserVO.class);
        }
    }
    

自定义用法

对于一些比较复杂的查询,还需要我们自定义Service和Mapper代码。

以这样一个案例为例:扣减用户余额。

  1. 由于不是常规查询,因此我们在Controller编写代码调用自定义的Service方法。

    @Api(tags = "用户管理接口")
    @RequestMapping("/users")
    @RestController
    @RequiredArgsConstructor
    public class UserController {
    	
        // @RequiredArgsConstructor注解通过构造函数给userService赋值
        private final IUserService userService;
    
        @ApiOperation("扣减用户余额接口")
        @GetMapping("/{id}/deduction/{money}")
        public void deductMoneyById(@ApiParam("用户id") @PathVariable("id") Long id,
                                    @ApiParam("扣减的金额") @PathVariable("money") Integer money) {
            userService.deductBalance(id, money);
        }
    }
    
  2. 然后在ServiceImpl实现自定义的方法。首先判断是否合法,然后调用Mapper中自定义的方法:

    @Service
    public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
    
        @Override
        public void deductBalance(Long id, Integer money) {
            User user = getById(id);
    
            if (user == null || user.getStatus() == 2) {
                throw new RuntimeException("用户状态异常!");
            }
    
            if (user.getBalance() < money) {
                throw new RuntimeException("用户余额不足!");
            }
    
            baseMapper.deductBalance(id, money);
        }
    }
    
  3. 然后在Mapper中定义SQL语句,复杂的可以使用Wrapper,简单的可以使用注解或xml:

    public interface UserMapper extends BaseMapper<User> {
        @Update("UPDATE user SET balance = balance - #{money} WHERE id = #{id}")
        void deductBalance(@Param("id") Long id, @Param("money") Integer money);
    }
    

Lambda查询

对于条件查询,以往我们需要写xml判断某个参数是否为空。MybatisPlus也为我们提供了简便用法Lambda以省略mapper编写。

现在,我们以这样一个案例为例:根据name、status、minBalance、maxBalance模糊查询用户。

  1. 在Controller编写代码调用Service层自定义方法:

    @Api(tags = "用户管理接口")
    @RequestMapping("/users")
    @RestController
    @RequiredArgsConstructor
    public class UserController {
    
        private final IUserService userService;
    
        @ApiOperation("根据复杂条件查询用户接口")
        @GetMapping("/list")
        public List<UserVO> queryUsers(UserQuery query) {
            List<User> users = userService.queryUsers(query.getName(), query.getStatus(), query.getMinBalance(), query.getMaxBalance());
            return BeanUtil.copyToList(users, UserVO.class);
        }
    }
    
  2. 在ServiceImpl定义方法,可以通过查询方法的链式调用进行查询。

    对于like、eq、gt、lt等方法,如果传入三个参数,第一个参数为condition。

    最后通过one、list、page、count等方法获取查询结果。

    @Service
    public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
        @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)
                    .gt(minBalance != null, User::getBalance, minBalance)
                    .lt(maxBalance != null, User::getBalance, maxBalance)
                    .list();
        }
    }
    

Lambda更新

我们以这样一个需求为例:根据id扣减用户余额,完成对用户状态、余额校验。如果扣减后余额为0,修改status为2。

前面我们已经完成了deductBalance业务流程,现在我们只需要修改service层对mapper调用为lambda即可。无需再写mapper代码。

@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {

    @Override
    public void deductBalance(Long id, Integer money) {
        User user = getById(id);

        if (user == null || user.getStatus() == 2) {
            throw new RuntimeException("用户状态异常!");
        }

        if (user.getBalance() < money) {
            throw new RuntimeException("用户余额不足!");
        }
        
        int remainBalance = user.getBalance() - money;
        lambdaUpdate()
                .set(User::getBalance, remainBalance)
                .set(remainBalance == 0, User::getStatus, 2)
                .eq(User::getId, id)
            	.update();
    }
}

但是这里还存在一个问题,如果有多个线程执行这个方法。如:ThreadA(扣100)、ThreadB(扣100)。

若同时执行到remainBalance,最终得到的remainBlance相同。最终两个线程只有一个生效。

因此,这里需要加线程锁。可以是乐观锁,也可以是悲观锁。

我们这里采用乐观锁(先比较再更新),在执行update前先使用.eq(User::getBalance, user.getBalance())判断。

如果条件不成立,说明已经有其它线程对Balance进行修改。当前update就失败了。

@Service
@Transactional
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {

    @Override
    public void deductBalance(Long id, Integer money) {
        User user = getById(id);

        if (user == null || user.getStatus() == 2) {
            throw new RuntimeException("用户状态异常!");
        }

        if (user.getBalance() < money) {
            throw new RuntimeException("用户余额不足!");
        }
        
        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();
    }
}

拓展功能

代码生成

前面,我们根据数据库表定义POJO、Mapper接口、Service接口、ServiceImpl和Controller。

对于不同的数据库表,定义的模板文件是相同的,区别就在于POJO字段名和文件的接口名及类名。

MybatisPlus为我们提供了代码生成器,可以根据数据库表一键生成这些文件:

  1. 安装MyabtisPlus插件:

    image-20240207114301736
  2. 然后打开菜单栏->Other->Config Database配置数据库信息:

    image-20240207114409176

    jdbc:mysql://127.0.0.1:3306/mp?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai
    
  3. 然后再次打开菜单栏->Other->Code Generator:

    image-20240207114726974

    image-20240207115207273
  4. 点击右下角code generatro即可生成相应的文件:

    image-20240207115704149

静态工具

IService接口提供的方法都是非静态方法,不同Service之间相互调用可能会导致循环依赖。

MybatisPlus为我们提供了DB静态工具,其中的方法为静态方法,避免了循环依赖问题,DB调用时需要传入Class参数。

第一个案例:根据id查询用户并查询用户的所有地址。

@Override
public UserVO queryUserAndAddressById(Long id) {
    User user = getById(id);
    if (user == null || user.getStatus() == 2) {
    	throw new RuntimeException("用户状态异常!");
    }

    List<Address> addresses = Db.lambdaQuery(Address.class)
    .eq(Address::getUserId, id)
    .list();

    UserVO userVO = BeanUtil.copyProperties(user, UserVO.class);
    if (CollUtil.isNotEmpty(addresses)) {
    	userVO.setAddresses(BeanUtil.copyToList(addresses, AddressVO.class));
    }
    return userVO;
}

第二个案例:批量查询用户并查询用户的所有地址。

@Override
public List<UserVO> queryUserAndAddressByIds(List<Long> ids) {
    // 根据id查询user
    List<User> users = listByIds(ids);
    if (CollUtil.isEmpty(users)) {
    	return Collections.emptyList();
    }

    // 获取用户id集合
    List<Long> userIds = users.stream().map(User::getId).collect(Collectors.toList());

    // 根据id查询地址
    List<Address> addresses = Db.lambdaQuery(Address.class)
    	.in(Address::getUserId, userIds)
    	.list();

    // 转换地址为VO
    List<AddressVO> addressVOList = BeanUtil.copyToList(addresses, AddressVO.class);

    // 分类整理地址集合:相同用户的放入一个集合
    Map<Long, List<AddressVO>> addressMap = new HashMap<>(0);
    if (CollUtil.isNotEmpty(addressVOList)) {
    	addressMap = addressVOList.stream().collect(Collectors.groupingBy(AddressVO::getUserId));
    }

    // 转换VO返回
    List<UserVO> list = new ArrayList<>(users.size());
    for (User user : users) {
    	UserVO userVO = BeanUtil.copyProperties(user, UserVO.class);
    list.add(userVO);
    	userVO.setAddresses(addressMap.get(user.getId()));
    }
    return list;
}

逻辑删除

日常生活中,我们在网站上执行删除操作,实际上是软删除。只是在数据库中将数据标记为删除,并不是真正的从数据库中删除。

具体实现方法是在表中添加一个字段标记数据是否被删除,当删除数据时把标记置为1,查询时只查询标记为0的数据。

例如:逻辑删除字段为deleted

查询操作:

SELECT * FROM user WHERE deleted = 0;

删除操作:

UPDATE user SET deleted = 1 WHERE id = 1 AND deleted = 0;

MybatisPlus提供了逻辑删除功能,无需改变方法调用方式,而是在底层帮我们自动修改CURD语句。

我们需要做的就是在application.yaml文件中配置逻辑删除的字段名称和值即可。

mybatis-plus:
  global-config:
    db-config:
      logic-delete-field: deleted 	# 全局逻辑删除的实体字段名,字段类型可以是boolean、integer
      logic-delete-value: 1       	# 逻辑删除值(默认为1)
      logic-not-delete-value: 0   	# 逻辑未删除值(默认为0)

枚举处理器

如果POJO类中有一个枚举类型的成员变量,如:

@Getter
public enum UserStatus {
	NORMAL(1, "正常"),
    FREEZE(2, "冻结")
    ;
    
    private final int value;
    private final String desc;
    
    UserStatus(int value, String desc) {
        this.value = value;
        this.desc = desc;
    }
}
@Data
public class User {
    @TableId(type = IdType.AUTO)
    private Long id;
    private String username;
    private String password;
    private String phone;
    private String info;
    private Integer status;
    private Integer balance;
    private UserStatus status;
    private LocalDateTime createTime;
    private LocalDateTime updateTime;
}

对于status成员变量,数据库字段的类型是INT,POJO的类型的enum。

Mybatis有一个TypeHandler接口,它可以实现数据库和Java类型的转换。但它不能将Enum类型和数据库的类型进行转换。

MybatisPlus提供了MybatisEnumTypeHandler处理器,以实现Enum类型和数据库类的转换,用法如下:

  1. 在application.yml配置:

    mybatis-plus:
      configuration:
        default-enum-type-handler: com.baomidou.mybatisplus.core.handlers.MybatisEnumTypeHandler
    
  2. 在enum类上加@EnumValue注解:

    @Getter
    public enum UserStatus {
    	NORMAL(1, "正常"),
        FREEZE(2, "冻结")
        ;
        
        @EnumValue
        private final int value;
        private final String desc;
        
        UserStatus(int value, String desc) {
            this.value = value;
            this.desc = desc;
        }
    }
    

此时,再次查询会默认返回枚举项,如:NORMAL、FREEZE。

如果想返回value或desc,可以在对应的成员变量上加@JsonValue注解。

JSON处理器

数据库字段有一个冷门的类型:json。在Java对应的POJO中我们一般用String成员变量映射这个字段。

Mybatis也为我们提供了自动从数据库的json转换为Java的字符串的功能。如:

image-20240208170159035

但是在Java中String类型不能直接访问json成员,为了解决这个问题,我们可以根据Json定义一个实体类:

image-20240208170357071

为了实现实体类和数据库json类型的转换,MybatisPlus提供了一个Json类型处理器。用法如下:

在对应的字段的@TableFirld注解上加入typeHandler = JacksonTypeHandler.class属性。

image-20240208170643673

但是这样需要在所有json字段都加注解,非常繁琐。我们可以采用另一种方法:

在@TableName注解上加入autoResultMap = true属性。

image-20240208171708166

插件功能

MybatisPlus提供的内置拦截器有:多租户插件、动态表名插件、分页插件、乐观锁插件、SQL性能规范插件等。

比较常用的插件就是分页插件,之前我们通过pageHelper实现分页,下面将介绍MybatisPlus分页插件的用法。

分页插件

首先,要在配置类中注册MybatisPlus的核心插件,同时添加分页插件:

@Configuration
public class MybatisConfig {

    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        // 初始化核心插件
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        // 添加分页插件
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        return interceptor;
    }
}

然后编写分页查询代码:

@Test
void testPageQuery() {
    // 1.分页查询,new Page()的两个参数分别是:页码、每页大小
    Page<User> p = userService.page(new Page<>(2, 2));
    // 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);
}

也支持排序:

int pageNo = 1, pageSize = 5;
// 分页参数
Page<User> page = Page.of(pageNo, pageSize);
// 排序参数, 通过OrderItem来指定
page.addOrder(new OrderItem("balance", false));

userService.page(page);

通用分页实体

很多业务都有分页查询需求,因此我们可以为分页查询定义一个通用的实体PageQuery:

@Data
public class PageQuery {
    private Integer pageNo;
    private Integer pageSize;
    private String sortBy;
    private Boolean isAsc;

    public <T>  Page<T> toMpPage(OrderItem ... orders){
        // 1.分页条件
        Page<T> p = Page.of(pageNo, pageSize);
        // 2.排序条件
        // 2.1.先看前端有没有传排序字段
        if (sortBy != null) {
            p.addOrder(new OrderItem(sortBy, isAsc));
            return p;
        }
        // 2.2.再看有没有手动指定排序字段
        if(orders != null){
            p.addOrder(orders);
        }
        return p;
    }

    public <T> Page<T> toMpPage(String defaultSortBy, boolean isAsc){
        return this.toMpPage(new OrderItem(defaultSortBy, isAsc));
    }

    public <T> Page<T> toMpPageDefaultSortByCreateTimeDesc() {
        return toMpPage("create_time", false);
    }

    public <T> Page<T> toMpPageDefaultSortByUpdateTimeDesc() {
        return toMpPage("update_time", false);
    }
}

然后定义一个返回结果的通用实体PageDTO:

@Data
@NoArgsConstructor
@AllArgsConstructor
public class PageDTO<V> {
    private Long total;
    private Long pages;
    private List<V> list;

    /**
     * 返回空分页结果
     * @param p MybatisPlus的分页结果
     * @param <V> 目标VO类型
     * @param <P> 原始PO类型
     * @return VO的分页对象
     */
    public static <V, P> PageDTO<V> empty(Page<P> p){
        return new PageDTO<>(p.getTotal(), p.getPages(), Collections.emptyList());
    }

    /**
     * 将MybatisPlus分页结果转为 VO分页结果
     * @param p MybatisPlus的分页结果
     * @param voClass 目标VO类型的字节码
     * @param <V> 目标VO类型
     * @param <P> 原始PO类型
     * @return VO的分页对象
     */
    public static <V, P> PageDTO<V> of(Page<P> p, Class<V> voClass) {
        // 1.非空校验
        List<P> records = p.getRecords();
        if (records == null || records.size() <= 0) {
            // 无数据,返回空结果
            return empty(p);
        }
        // 2.数据转换
        List<V> vos = BeanUtil.copyToList(records, voClass);
        // 3.封装返回
        return new PageDTO<>(p.getTotal(), p.getPages(), vos);
    }

    /**
     * 将MybatisPlus分页结果转为 VO分页结果,允许用户自定义PO到VO的转换方式
     * @param p MybatisPlus的分页结果
     * @param convertor PO到VO的转换函数
     * @param <V> 目标VO类型
     * @param <P> 原始PO类型
     * @return VO的分页对象
     */
    public static <V, P> PageDTO<V> of(Page<P> p, Function<P, V> convertor) {
        // 1.非空校验
        List<P> records = p.getRecords();
        if (records == null || records.size() <= 0) {
            // 无数据,返回空结果
            return empty(p);
        }
        // 2.数据转换
        List<V> vos = records.stream().map(convertor).collect(Collectors.toList());
        // 3.封装返回
        return new PageDTO<>(p.getTotal(), p.getPages(), vos);
    }
}

以后业务需要的分页查询和分页结果可以继承这两个通用实体。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1441371.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

表单标记(html)

前言 发现input的type属性还是有挺多的&#xff0c;这里把一些常用的总结一下。 HTML 输入类型 (w3school.com.cn)https://www.w3school.com.cn/html/html_form_input_types.asp text-文本 文本输入,如果文字太长&#xff0c;超出的部分就不会显示。 定义供文本输入的单行…

C语言操作符超详细总结

文章目录 1. 操作符的分类2. 二进制和进制转换2.1 2进制转10进制2.1.1 10进制转2进制数字 2.2 2进制转8进制和16进制2.2.1 2进制转8进制2.2.2 2进制转16进制 3. 原码、反码、补码4.移位操作符4.1 左移操作符4.2 右移操作符 5. 位操作符&#xff1a;&、|、^、~6. 逗号表达式…

vue3 之 通用组件统一注册全局

components/index.js // 把components中的所组件都进行全局化注册 // 通过插件的方式 import ImageView from ./ImageView/index.vue import Sku from ./XtxSku/index.vue export const componentPlugin {install (app) {// app.component(组件名字&#xff0c;组件配置对象)…

图解 V8 执行 JS 的过程

本文来分享 V8 引擎执行 JavaScript 的过程 1. JS 代码执行过程 在说V8的执行JavaScript代码的机制之前&#xff0c;我们先来看看编译型和解释型语言的区别。 编译型语言和解释型语言 我们知道&#xff0c;机器是不能直接理解代码的。所以&#xff0c;在执行程序之前&#xf…

Java_栈_队列

文章目录 一、栈&#xff08;Stack&#xff09;1.概念2.栈的使用3.栈的模拟实现1、定义接口2、定义栈3、成员4、构造方法5、判断空间是否满 full6、入栈 push7、出栈 pop8、获取栈顶元素 peek9、获取栈中有效元素个数 size10、检测栈是否为空 empty完整代码 4.练习1、有效括号2…

GEE Colab——如何利用Matplotlib在colab中进行图形制作

在colab中绘制图表 笔记本的一个常见用途是使用图表进行数据可视化。Colaboratory 提供多种图表工具作为 Python 导入,让这一工作变得简单。 Matplotlib Matplotlib 是最常用的图表工具包,详情请查看其文档,并通过示例获得灵感。 线性图 线性图是一种常见的图表类型,用…

LabVIEW网络测控系统

LabVIEW网络测控系统 介绍了基于LabVIEW的网络测控系统的开发与应用&#xff0c;通过网络技术实现了远程的数据采集、监控和控制。系统采用LabVIEW软件与网络通信技术相结合&#xff0c;提高了系统的灵活性和扩展性&#xff0c;适用于各种工业和科研领域的远程测控需求。 随着…

哈希表(Hash Table)-----运用实例【通过哈希表来管理雇员信息】(java详解) (✧∇✧)

目录 一.哈希表简介&#xff1a; 实例介绍&#xff1a; 类的创建与说明&#xff1a; 各功能图示&#xff1a; 1.class HashTab{ }; 2. class EmpLinkedList{ }&#xff1b; 3. class Emp{ }&#xff1b; 4.测试&#xff1a; 运行结果&#xff1a; 最后&#xff0c;完整…

springboot微信小程序uniapp学习计划与日程管理系统

基于springboot学习计划与日程管理系统&#xff0c;确定学习计划小程序的目标&#xff0c;明确用户需求&#xff0c;学习计划小程序的主要功能是帮助用户制定学习计划&#xff0c;并跟踪学习进度。页面设计主要包括主页、计划学习页、个人中心页等&#xff0c;然后用户可以利用…

Java汽车销售管理

技术架构&#xff1a; springboot mybatis Mysql5.7 vue2 npm node 有需要该项目的小伙伴可以私信我你的Q。 功能描述&#xff1a; 针对汽车销售提供客户信息、车辆信息、订单信息、销售人员管理、财务报表等功能&#xff0c;提供经理和销售两种角色进行管理 效果图&…

CTF--Web安全--SQL注入之‘绕过方法’

一、什么是绕过注入 众所周知&#xff0c;SQL注入是利用源码中的漏洞进行注入的&#xff0c;但是有攻击手段&#xff0c;就会有防御手段。很多题目和网站会在源码中设置反SQL注入的机制。SQL注入中常用的命令&#xff0c;符号&#xff0c;甚至空格&#xff0c;会在反SQL机制中…

预测模型:MATLAB线性回归

1. 线性回归模型的基本原理 线性回归是统计学中用来预测连续变量之间关系的一种方法。它假设变量之间存在线性关系&#xff0c;可以通过一个或多个自变量&#xff08;预测变量&#xff09;来预测因变量&#xff08;响应变量&#xff09;的值。基本的线性回归模型可以表示为&…

Ps:窗口排列

Ps菜单&#xff1a;窗口/排列 Window/Arrange Photoshop 的“窗口/排列” Arrange子菜单中提供了多种方式来组织和查看打开的文档窗口&#xff0c;这在处理多个文档或比较图像时非常有用。 ◆ ◆ ◆ 常用操作方法与技巧 1、同文档双窗口处理法 将同一个图像显示在两个窗口中&…

The Back-And-Forth Method (BFM) for Wasserstein Gradient Flows windows安装

本文记录了BFM算法代码在windows上的安装过程。 算法原网站&#xff1a;https://wasserstein-gradient-flows.netlify.app/ github&#xff1a;https://github.com/wonjunee/wgfBFMcodes 文章目录 FFTWwgfBFMcodesMATLABpython注 FFTW 官网/下载路径&#xff1a;https://ww…

备战蓝桥杯---动态规划(基础2)

本专题主要是介绍几个比较经典的题目&#xff1a; 假设我们令f[i]为前i个的最长不下降子序列&#xff0c;我们会发现难以转移方程很难写&#xff08;因为我们不知道最后一个数&#xff09;。 于是&#xff0c;我们令f[i]为以i结尾的最长不下降子序列&#xff0c;这样子我们就可…

Java基础知识练习题

1.对Java源文件进行编译操作的命令是&#xff08;B&#xff09; A.Java B.javac C.where is java D.javaw 2.下列命令中&#xff0c;用来运行Java程序的是&#xff08;A&#xff09;A.java B. javadoc C. jar D. javac 分析&#xff1a; 对Java源程序进行编译的命令是J…

Django的配置文件setting.py

BASE_DIR 项目路径&#xff1a;默认是已经打开的主项目路径 ​​​​​​​BASE_DIR os.path.dirname(os.path.dirname(os.path.abspath(__file__))) SECRET_KEY 密钥 SECRET_KEY (dh&_fm2hfn9y)35!_6#$a7q%%^onoy#-a8x18r4(6*8f(aniDEBUG 帮助调试&#xff0c;默认…

gcore服务器设置root账号密码登录

这个厂商很奇怪&#xff0c;默认只能用centos用户与公钥登录&#xff0c;但是这样有时候很麻烦。 他默认开启了SELinux&#xff0c;和强制ssh密钥登录。 下面所有操作在root模式下进行 SELinux设置为兼容模式 setenforce 0vi /etc/selinux/config然后将文件中的SELINUXenfo…

JVM之GC垃圾回收

GC垃圾回收 如何判断对象可以回收 引用计数法 如果有对象引用计数加一&#xff0c;没有对象引用&#xff0c;计数减一&#xff0c;如果计数为零&#xff0c;则回收 但是如果存在循环引用&#xff0c;即A对象引用B对象&#xff0c;B对象引用A对象&#xff0c;会造成内存泄漏 可…

Java 学习和实践笔记(3)

安装和配置成功&#xff1a; 运行第一个程序时出现这个错误&#xff1a;javac不是内部或外部命令&#xff0c;也不是可运行的程序或批处理文件。 找到这篇文章看了下&#xff1a;javac 不是内部或外部命令&#xff0c;也不是可运行的程序 或批处理文件。_javac 不是内部或外部…