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.0IdType.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: 指定数据库中的字段名。如果你的实体类字段名与数据库字段名不同,使用这个属性来指定正确的数据库字段名。
javapublic 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;
}