6.mybatisplus学习-BaseMapper、Service、常用注解

BaseMapper

java 复制代码
/**
 * Mapper 继承该接口后,无需编写 mapper.xml 文件,即可获得CRUD功能
 * <p>这个 Mapper 支持 id 泛型</p>
 *
 * @author hubin
 * @since 2016-01-23
 */
public interface BaseMapper<T> extends Mapper<T> {

    /**
     * 插入一条记录
     *
     * @param entity 实体对象
     */
    int insert(T entity);

    /**
     * 根据 ID 删除
     *
     * @param id 主键ID
     */
    int deleteById(Serializable id);

    /**
     * 根据实体(ID)删除
     *
     * @param entity 实体对象
     * @since 3.4.4
     */
    int deleteById(T entity);

    /**
     * 根据 columnMap 条件,删除记录
     *
     * @param columnMap 表字段 map 对象
     */
    default int deleteByMap(Map<String, Object> columnMap) {
        return this.delete(Wrappers.<T>query().allEq(columnMap));
    }

    /**
     * 根据 entity 条件,删除记录
     *
     * @param queryWrapper 实体对象封装操作类(可以为 null,里面的 entity 用于生成 where 语句)
     */
    int delete(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

    /**
     * 删除(根据ID或实体 批量删除)
     *
     * @param idList 主键ID列表或实体列表(不能为 null 以及 empty)
     */
    int deleteBatchIds(@Param(Constants.COLL) Collection<?> idList);

    /**
     * 根据 ID 修改
     *
     * @param entity 实体对象
     */
    int updateById(@Param(Constants.ENTITY) T entity);

    /**
     * 根据 whereEntity 条件,更新记录
     *
     * @param entity        实体对象 (set 条件值,可以为 null)
     * @param updateWrapper 实体对象封装操作类(可以为 null,里面的 entity 用于生成 where 语句)
     */
    int update(@Param(Constants.ENTITY) T entity, @Param(Constants.WRAPPER) Wrapper<T> updateWrapper);

    /**
     * 根据 Wrapper 更新记录
     *
     * @param updateWrapper {@link UpdateWrapper} or {@link LambdaUpdateWrapper}
     * @since 3.5.4
     */
    default int update(@Param(Constants.WRAPPER) Wrapper<T> updateWrapper) {
        return update(null, updateWrapper);
    }

    /**
     * 根据 ID 查询
     *
     * @param id 主键ID
     */
    T selectById(Serializable id);

    /**
     * 查询(根据ID 批量查询)
     *
     * @param idList 主键ID列表(不能为 null 以及 empty)
     */
    List<T> selectBatchIds(@Param(Constants.COLL) Collection<? extends Serializable> idList);

    /**
     * 查询(根据ID 批量查询)
     *
     * @param idList        idList 主键ID列表(不能为 null 以及 empty)
     * @param resultHandler resultHandler 结果处理器 {@link ResultHandler}
     * @since 3.5.4
     */
    void selectBatchIds(@Param(Constants.COLL) Collection<? extends Serializable> idList, ResultHandler<T> resultHandler);

    /**
     * 查询(根据 columnMap 条件)
     *
     * @param columnMap 表字段 map 对象
     */
    default List<T> selectByMap(Map<String, Object> columnMap) {
        return this.selectList(Wrappers.<T>query().allEq(columnMap));
    }

    /**
     * 查询(根据 columnMap 条件)
     *
     * @param columnMap     表字段 map 对象
     * @param resultHandler resultHandler 结果处理器 {@link ResultHandler}
     * @since 3.5.4
     */
    default void selectByMap(Map<String, Object> columnMap, ResultHandler<T> resultHandler) {
        this.selectList(Wrappers.<T>query().allEq(columnMap), resultHandler);
    }

    /**
     * 根据 entity 条件,查询一条记录
     * <p>查询一条记录,例如 qw.last("limit 1") 限制取一条记录, 注意:多条数据会报异常</p>
     *
     * @param queryWrapper 实体对象封装操作类(可以为 null)
     */
    default T selectOne(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper) {
        return this.selectOne(queryWrapper, true);
    }

    /**
     * 根据 entity 条件,查询一条记录,现在会根据{@code throwEx}参数判断是否抛出异常,如果为false就直接返回一条数据
     * <p>查询一条记录,例如 qw.last("limit 1") 限制取一条记录, 注意:多条数据会报异常</p>
     *
     * @param queryWrapper 实体对象封装操作类(可以为 null)
     * @param throwEx      boolean 参数,为true如果存在多个结果直接抛出异常
     */
    default T selectOne(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper, boolean throwEx) {
        List<T> list = this.selectList(queryWrapper);
        int size = list.size();
        if (size == 1) {
            return list.get(0);
        } else if (size > 1) {
            if (throwEx) {
                throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + size);
            }
            return list.get(0);
        }
        return null;
    }

    /**
     * 根据 Wrapper 条件,判断是否存在记录
     *
     * @param queryWrapper 实体对象封装操作类
     * @return 是否存在记录
     */
    default boolean exists(Wrapper<T> queryWrapper) {
        Long count = this.selectCount(queryWrapper);
        return null != count && count > 0;
    }

    /**
     * 根据 Wrapper 条件,查询总记录数
     *
     * @param queryWrapper 实体对象封装操作类(可以为 null)
     */
    Long selectCount(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

    /**
     * 根据 entity 条件,查询全部记录
     *
     * @param queryWrapper 实体对象封装操作类(可以为 null)
     */
    List<T> selectList(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

    /**
     * 根据 entity 条件,查询全部记录
     *
     * @param queryWrapper  实体对象封装操作类(可以为 null)
     * @param resultHandler 结果处理器 {@link ResultHandler}
     * @since 3.5.4
     */
    void selectList(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper, ResultHandler<T> resultHandler);

    /**
     * 根据 entity 条件,查询全部记录(并翻页)
     *
     * @param page         分页查询条件
     * @param queryWrapper 实体对象封装操作类(可以为 null)
     * @since 3.5.3.2
     */
    List<T> selectList(IPage<T> page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

    /**
     * 根据 entity 条件,查询全部记录(并翻页)
     * @param page          分页查询条件
     * @param queryWrapper  实体对象封装操作类(可以为 null)
     * @param resultHandler 结果处理器 {@link ResultHandler}
     * @since 3.5.4
     */
    void selectList(IPage<T> page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper, ResultHandler<T> resultHandler);


    /**
     * 根据 Wrapper 条件,查询全部记录
     *
     * @param queryWrapper 实体对象封装操作类
     */
    List<Map<String, Object>> selectMaps(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

    /**
     * 根据 Wrapper 条件,查询全部记录
     *
     * @param queryWrapper  实体对象封装操作类
     * @param resultHandler 结果处理器 {@link ResultHandler}
     * @since 3.5.4
     */
    void selectMaps(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper, ResultHandler<Map<String, Object>> resultHandler);

    /**
     * 根据 Wrapper 条件,查询全部记录(并翻页)
     *
     * @param page         分页查询条件
     * @param queryWrapper 实体对象封装操作类
     * @since 3.5.3.2
     */
    List<Map<String, Object>> selectMaps(IPage<? extends Map<String, Object>> page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

    /**
     * 根据 Wrapper 条件,查询全部记录(并翻页)
     *
     * @param page          分页查询条件
     * @param queryWrapper  实体对象封装操作类
     * @param resultHandler 结果处理器 {@link ResultHandler}
     * @since 3.5.4
     */
    void selectMaps(IPage<? extends Map<String, Object>> page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper, ResultHandler<Map<String, Object>> resultHandler);

    /**
     * 根据 Wrapper 条件,查询全部记录
     * <p>注意: 只返回第一个字段的值</p>
     *
     * @param queryWrapper 实体对象封装操作类(可以为 null)
     */
    <E> List<E> selectObjs(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

    /**
     * 根据 Wrapper 条件,查询全部记录
     * <p>注意: 只返回第一个字段的值</p>
     *
     * @param queryWrapper  实体对象封装操作类(可以为 null)
     * @param resultHandler 结果处理器 {@link ResultHandler}
     * @since 3.5.4
     */
    <E> void selectObjs(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper, ResultHandler<E> resultHandler);

    /**
     * 根据 entity 条件,查询全部记录(并翻页)
     *
     * @param page         分页查询条件
     * @param queryWrapper 实体对象封装操作类(可以为 null)
     */
    default <P extends IPage<T>> P selectPage(P page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper) {
        page.setRecords(selectList(page, queryWrapper));
        return page;
    }

    /**
     * 根据 Wrapper 条件,查询全部记录(并翻页)
     *
     * @param page         分页查询条件
     * @param queryWrapper 实体对象封装操作类
     */
    default <P extends IPage<Map<String, Object>>> P selectMapsPage(P page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper) {
        page.setRecords(selectMaps(page, queryWrapper));
        return page;
    }

}
测试
java 复制代码
@SpringBootTest
public class MybatisPlusTest {

    @Autowired //依赖注入
    private StudentMapper studentMapper;

    @Test
    public void getStudentList(){
        List<Student> students = studentMapper.selectList(null);
        System.out.println(students);
    }

    @Test
    public void insertStudent(){
        Student student = new Student(null, "李json", 35, "lissd2@gmail.com");
        int i = studentMapper.insert(student);
        System.out.println("结果:"+i);
        System.out.println(student.getId());
    }

    @Test
    public void deleteStu(){
        //根据id删除
        int i = studentMapper.deleteById(1L);
        System.out.println("结果:"+i);
        //根据map删除
        HashMap<String, Object> map = new HashMap<>();
        map.put("name", "小花");
        map.put("age", 12);
        studentMapper.deleteByMap(map);
        //批量删除
        List<Long> list = Arrays.asList(1L, 2L, 3L);
        studentMapper.deleteBatchIds(list);
    }

    @Test
    public void updateStu(){
        //根据id修改
        Student student = new Student();
        student.setId(4L);
        student.setAge(230);
        student.setName("韩立");
        int i = studentMapper.updateById(student);
        System.out.println("结果:"+i);
    }

    @Test
    public void searchStu(){
        Student student = studentMapper.selectById(4L);
        System.out.println(student);

        HashMap<String, Object> map = new HashMap<>();
        map.put("name", "韩立");
        map.put("age", "230");
        List<Student> stu = studentMapper.selectByMap(map);
        System.out.println(stu);

        List<Long> list = Arrays.asList(1L, 2L, 3L);
        List<Student> students = studentMapper.selectBatchIds(list);
        System.out.println(students);

        List<Student> stuList = studentMapper.selectList(null);
        stuList.forEach(System.out::println);
    }
}
自定义操作数据库方法

由于mybatis-plus特性是只做增强不做改变 ,所以之前mybatis如何做,现在也一样。

映射文件路径可以自定义设置:

yml 复制代码
...
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  # 设置mapper文件的路径 默认路径是 classpath*:/mapper/**/*.xml
  mapper-locations: ...

通用分层

Repository Interface

在版本 v3.5.9 之后的版本重构 service 模块抽象为 repository 建议使用 IRepository 避免语义层面混乱,使用上与接口 IService 用法一致

遵循 Controller(表现层) -> Service(业务逻辑层) -> Repository(逻辑数据访问层) -> Mapper(物理数据访问层) 的分层架构

如果想 架构更加简单一点 追求简洁也可以合并 Service(业务逻辑层) Repository(逻辑数据访问层) 这两层统一为 Service(业务逻辑数据处理层) 需要注意的是无论何种架构分层 Mapper(物理数据访问层) 都不建议编写任何业务逻辑数据处理代码。

通用Service
  • IService 接口中的方法命名遵循了一定的规范,如 get 用于查询单行,remove 用于删除,list 用于查询集合,page 用于分页查询,这样可以避免与 Mapper 层的方法混淆。
  • 泛型 T 为任意实体对象
  • 建议如果存在自定义通用 Service 方法的可能,请创建自己的IBaseService继承Mybatis-Plus 提供
    的基类

官网地址: https://baomidou.com/guides/data-interface/#repository-interface

IService,这里的泛型T为实体对象

java 复制代码
/**
 * 顶级 Service
 *
 * @author hubin
 * @since 2018-06-23
 */
public interface IService<T> {
    ...
}

ServiceImpl

java 复制代码
/**
 * IService 实现类( 泛型:M 是 mapper 对象,T 是实体 )
 *
 * @author hubin
 * @since 2018-06-23
 */
@SuppressWarnings("unchecked")
public class ServiceImpl<M extends BaseMapper<T>, T> implements IService<T> {
...
}

创建自己的service

java 复制代码
package com.light.mybatisplus.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.light.mybatisplus.mapper.StudentMapper;
import com.light.mybatisplus.pojo.Student;
import com.light.mybatisplus.service.StudentService;
import org.springframework.stereotype.Service;

/**
 * 好处:
 * 不用都实现IService所有接口
 * 可以使用通用IService提供的功能
 * 也可以自定义功能
 */
@Service
public class StudentServiceImpl extends ServiceImpl<StudentMapper, Student> implements StudentService {

}

测试

java 复制代码
@SpringBootTest
public class MyBatisPlusServiceTest {

    @Autowired
    private StudentService service;

    @Test
    public void testSerivce(){
        //查询总记录数
        System.out.println(service.count());
    }

    @Test
    public void testInsertMore(){
        ArrayList<Student> stus = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            Student student = new Student();
            student.setName("light"+i);
            student.setAge(30 + i);
            stus.add(student);
        }
        boolean b = service.saveBatch(stus);
        System.out.println(b);
    }

}

@TableName

主要表示实体类映射的表名,也就是说pojo目录下类对应的数名库表名。

java 复制代码
package com.light.mybatisplus.pojo;


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

@TableName("student") //对应的数据库表名
@AllArgsConstructor
@NoArgsConstructor
@Data
public class Student {
    private Long id;
    private String name;
    private Integer age;
    private String email;
}

可以在配置文件设置所有的表的前缀:

yml 复制代码
...
mybatis-plus:
  ...
  global-config:
    db-config:
      # 设置所有表的前缀
      table-prefix: zzx_

@TableId

由于mybatis默认只能以id设置命名主键,所以当实体类中属性和表中字段名称不一致时,可以使用此注解标注主键。

  • value属性:设置表示主键的字段名,和表中名称一致的时候不用设置
  • type属性:指定主键的生成策略,默认IdType.NONE
    • IdType.AUTO:使用数据库自增 ID 作为主键。
    • IdType.NONE:无特定生成策略,如果全局配置中有 IdType 相关的配置,则会跟随全局配置。
    • IdType.INPUT:在插入数据前,由用户自行设置主键值。
    • IdType.ASSIGN_ID:自动分配 ID,适用于 Long、Integer、String 类型的主键。默认使用雪花算法通过 IdentifierGenerator 的 nextId 实现。@since 3.3.0
    • IdType.ASSIGN_UUID:自动分配 UUID,适用于 String 类型的主键。默认实现为 IdentifierGenerator 的 nextUUID 方法。@since 3.3.0
      如果没有手动设置此注解,默认会以雪花算法作为策略。但是如果手动设置此注解,默认是IdType.NONE,需要手动指定策略。
      如果手动设置id值(例如:xxx.setId(20L)),以手动设置为准,不会创建雪花算法数据。
全局设置IdType
yml 复制代码
...
global-config:
  db-config:
    # 设置所有表的前缀
    table-prefix: zzx_
    # 设置主键生成策略
    id-type: auto
雪花算法

需要选择合适的方案去应对数据规模的增长,以应对逐渐增长的访问压力和数据量。

数据库的扩展方式主要包括:业务分库、主从复制,数据库分表。

数据库分表

  • 垂直分表:简单来说就是建立关联分表。
  • 水平分表:根据数据量来进行分表,多少数据量根据数据库性能
    • 主键自增:可以随着数据的增加平滑地扩充新的表。

      复杂点:分段大小的选取。分段太小会导致切分后子表数量过多,增加维护复杂度;分段太大可能会导致单表依然存在性能问题,一般建议分段大小在100万至2000万之间,昙体需要根据业务选取合适的分段大小。

      缺点:分布不均匀。假如按照1000万来进行分表,有可能某个分段实际存储的数据量只有1条,而另外一个分段实际存储的数据量有1000万条。

    • 取模:同样以用户ID为例,假如我们一开始就规划了10个数据库表,可以简单地用user_id%10的值来表示数据所属的数据库表编号,ID为985的用户放到编号为5的子表中,ID为10086的用户放到编号为6的子表中。这样分布比较均匀。

      复杂点:初始表数量的准确性(因为要根据user_id%10这个算法)。表数量太多维护比较麻烦,表数量太少又可能导致单表性能存在问题。

      缺点:扩充新的表很麻烦,所有数据都要重分布。

    • 雪花算法:雪花算法是由Twiter公布的分布式主键生成算法,它能够保证不同表的主键的不重复性,以及相同表的主键的有序性(后添加的数据比之前添加的数据的id大)。

      算法原理:

      • 第1位是符号位,占用1bit。正数是0,负数是1。以id一般是正数,最高位是0。
      • 第2位到41bit时间截(毫秒级),存储的是时间截的差值(当前时间截-开始时间截)
      • 中间的10-bit位可表示机器数,5个bit是数据中心,5个bit的机器ID,可以部署在1024个节点
      • 最后12-bit位是自增序列

      有了雪花id,在实际应用中分表按照业务分片来分。比如用户表 1 亿数据,按业务分片。最好的办法按照user_id来分, 可以分 4 个库,每库 32 张表,共 128 张表:

      TEXT 复制代码
      // 路由
      slot = user_id % 128
      db_index = slot / 32      // 0~3
      table_index = slot % 32   // 0~31
      // 查询表
      slot = user_id % 128
      db = slot / 32
      table = slot % 32
      select * from user_{table} where user_id = ?

      也可以根据手机号映射表

@TableField

mybatisplus中默认可以将下划线表字段转换成映射实体类的驼峰命名的属性,不用做任何配置。

  • value: 指定数据库中的字段名。如果你的实体类字段名与数据库字段名不同,使用这个属性来指定正确的数据库字段名。

    java 复制代码
    public class Student {
        @TableId("id")
        private Long id;
        @TableField("stu_name") //name属性字段对应表中的stu_name
        private String name;
        private Integer age;
        private String email;
    }
  • exist: 指示这个字段是否存在于数据库表中。如果设置为 false,MyBatis-Plus 在生成 SQL 时会忽略这个字段 默认值:true

  • condition: 在执行实体查询(EntityQuery)时,指定字段的条件表达式。这允许你自定义字段在 WHERE 子句中的比较方式。无值则默认为全局的 %s=#{%s} 为准。

    java 复制代码
    @TableName("sys_user")
    
    public class User {
        @TableId
        private Long id;
        private String name;
        @TableField(condition = "%s > #{%s}") // 自定义 age 字段的条件表达式
        private Integer age;
        private String email;
    }
  • update: 在执行更新操作时,指定字段在 SET 子句中的表达式。这个属性的优先级高于 el 属性,允许你自定义字段的更新逻辑。

    java 复制代码
    @TableName("sys_user")
    public class User {
        @TableId
        private Long id;
        private String name;
        private Integer age;
        private String email;
        // 自定义更新时的表达式,每次更新用户信息时,自动将 version 字段的值增加 1。
        @TableField(update="%s+1") 
        private Integer version;
    
    }

@TableLogic

该注解用于标记实体类中的字段作为逻辑删除字段(软删除)。

@OrderBy

该注解用于指定实体类中的字段在执行查询操作时的默认排序方式。通过在实体类字段上使用@OrderBy注解,可以确保在执行查询时,如果没有显式指定排序条件,MyBatis-Plus 将按照注解中定义的排序规则返回结果。

java 复制代码
@TableName("sys_user")
public class User {
	@TableId
	private Long id;
	@TableField("nickname") // 映射到数据库字段 "nickname"
	private String name;
	@OrderBy(asc = false, sort = 10) // 指定默认排序为倒序,优先级为10
	private Integer age;
	private String email;
}
相关推荐
微信开发api1 小时前
基于WTAPI构建社群运营平台:自动拉群与会话承接链路设计
java·大数据·网络·数据库·微信·自动化
小七在进步1 小时前
类和对象(五)
java·开发语言·算法
驭渊的小故事2 小时前
SpringBoot 配置文件详解:properties 与 yml 从入门到实战
java·开发语言·笔记
Wx-bishekaifayuan2 小时前
Springcloud中学在线学习平台58883-计算机课程设计、毕业设计
java·vue.js·spring boot·学习·spring·spring cloud·课程设计
一嘴一个橘子2 小时前
java 中使用 lua 脚本
java
码智社2 小时前
Spring Framework 全方位技术解析
java
wangqiaowq2 小时前
AST 安全求值
java·开发语言
Java_2017_csdn3 小时前
OFC、ERP、OMS、WMS、TMS 对比-详解
java
EatFan11 小时前
Spring Boot 4 落地观察:从 yudao-cloud、matecloud、JPower 看国产脚手架的升级路线与迁移清单
java·spring boot·后端·spring cloud·微服务·后端开发·jdk 21