BaseMapper 基础映射器
RuoYi-Plus 提供的增强型 MyBatis-Plus BaseMapper,扩展了更多实用的数据访问方法。
📋 基础用法
接口继承
java
// 实体类
@Data
@TableName("sys_user")
public class SysUser extends BaseEntity {
@TableId(value = "user_id")
private Long userId;
@TableField("user_name")
private String userName;
@TableField("nick_name")
private String nickName;
@TableField("email")
private String email;
@TableField("status")
private String status;
}
// Mapper接口
@Mapper
public interface SysUserMapper extends BaseMapperPlus<SysUser, UserVo> {
// 继承BaseMapperPlus后,自动获得所有增强方法
// 无需编写基础CRUD方法
}
// VO类
@Data
public class UserVo {
private Long userId;
private String userName;
private String nickName;
private String email;
private String status;
private String statusName; // 字典转换后的状态名称
private Date createTime;
}基础操作
java
@Service
public class UserServiceImpl implements IUserService {
@Resource
private SysUserMapper userMapper;
public void basicOperations() {
// 1. 插入数据
SysUser user = new SysUser();
user.setUserName("admin");
user.setNickName("管理员");
userMapper.insert(user);
// 2. 根据ID查询
SysUser found = userMapper.selectById(1L);
// 3. 查询VO对象
UserVo userVo = userMapper.selectVoById(1L);
// 4. 条件查询
LambdaQueryWrapper<SysUser> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(SysUser::getStatus, "0");
List<SysUser> users = userMapper.selectList(wrapper);
// 5. 查询VO列表
List<UserVo> userVos = userMapper.selectVoList(wrapper);
// 6. 分页查询
Page<UserVo> page = new Page<>(1, 10);
Page<UserVo> result = userMapper.selectVoPage(page, wrapper);
// 7. 更新数据
user.setNickName("超级管理员");
userMapper.updateById(user);
// 8. 删除数据
userMapper.deleteById(1L);
}
}🎯 核心方法
BaseMapperPlus 接口
java
/**
* 自定义 Mapper 接口, 实现 自定义扩展
*/
public interface BaseMapperPlus<T, V> extends BaseMapper<T> {
/**
* 根据 entity 条件,查询全部记录(并翻译)
*/
default List<V> selectVoList(AbstractWrapper<T, String, ?> wrapper) {
return selectVoList(wrapper, this.currentVoClass());
}
/**
* 根据 entity 条件,查询全部记录(并翻译)
*/
<C> List<C> selectVoList(AbstractWrapper<T, String, ?> wrapper, Class<C> clazz);
/**
* 根据 Wrapper 条件,查询总记录数
*/
default Long selectVoCount(AbstractWrapper<T, String, ?> wrapper) {
return selectCount(wrapper);
}
/**
* 根据 ID 查询一条数据(并翻译)
*/
default V selectVoById(Serializable id) {
return selectVoById(id, this.currentVoClass());
}
/**
* 根据 ID 查询一条数据(并翻译)
*/
<C> C selectVoById(Serializable id, Class<C> clazz);
/**
* 根据 entity 条件,查询一条记录(并翻译)
*/
default V selectVoOne(AbstractWrapper<T, String, ?> wrapper) {
return selectVoOne(wrapper, this.currentVoClass());
}
/**
* 根据 entity 条件,查询一条记录(并翻译)
*/
<C> C selectVoOne(AbstractWrapper<T, String, ?> wrapper, Class<C> clazz);
/**
* 根据 entity 条件,分页查询记录(并翻译)
*/
default Page<V> selectVoPage(IPage<T> page, AbstractWrapper<T, String, ?> wrapper) {
return selectVoPage(page, wrapper, this.currentVoClass());
}
/**
* 根据 entity 条件,分页查询记录(并翻译)
*/
<C> Page<C> selectVoPage(IPage<T> page, AbstractWrapper<T, String, ?> wrapper, Class<C> clazz);
/**
* 批量新增数据,含主键返回
*/
int insertBatch(Collection<T> entityList);
/**
* 批量新增或修改数据
*/
int insertOrUpdateBatch(Collection<T> entityList);
/**
* 获取当前VO类型
*/
Class<V> currentVoClass();
}实现原理
java
/**
* 自定义 Mapper 接口实现
*/
public class BaseMapperPlusImpl<T, V> implements BaseMapperPlus<T, V> {
@Override
public <C> List<C> selectVoList(AbstractWrapper<T, String, ?> wrapper, Class<C> clazz) {
List<T> list = this.selectList(wrapper);
if (CollUtil.isEmpty(list)) {
return CollUtil.newArrayList();
}
return BeanUtil.copyToList(list, clazz);
}
@Override
public <C> C selectVoById(Serializable id, Class<C> clazz) {
T obj = this.selectById(id);
if (ObjectUtil.isNull(obj)) {
return null;
}
return BeanUtil.copyProperties(obj, clazz);
}
@Override
public <C> Page<C> selectVoPage(IPage<T> page, AbstractWrapper<T, String, ?> wrapper, Class<C> clazz) {
Page<T> result = this.selectPage(page, wrapper);
return PageUtil.build(result, clazz);
}
@Override
public int insertBatch(Collection<T> entityList) {
// 使用MyBatis-Plus的批量插入
return mybatisBatch(entityList, (sqlSession, entity) -> {
String mapperName = this.getClass().getName();
sqlSession.insert(mapperName + ".insert", entity);
});
}
@Override
public Class<V> currentVoClass() {
return (Class<V>) ReflectUtil.getTypeArgument(this.getClass(), 1);
}
}🔧 高级功能
条件构造器
java
@Service
public class UserServiceImpl {
@Resource
private SysUserMapper userMapper;
/**
* 复杂条件查询
*/
public List<UserVo> complexQuery(UserBo bo) {
LambdaQueryWrapper<SysUser> wrapper = new LambdaQueryWrapper<SysUser>()
// 等值查询
.eq(StringUtils.isNotBlank(bo.getUserName()), SysUser::getUserName, bo.getUserName())
.eq(StringUtils.isNotBlank(bo.getStatus()), SysUser::getStatus, bo.getStatus())
// 模糊查询
.like(StringUtils.isNotBlank(bo.getNickName()), SysUser::getNickName, bo.getNickName())
// 范围查询
.between(ObjectUtil.isAllNotEmpty(bo.getBeginTime(), bo.getEndTime()),
SysUser::getCreateTime, bo.getBeginTime(), bo.getEndTime())
// 排序
.orderByDesc(SysUser::getCreateTime)
.orderByAsc(SysUser::getUserId);
return userMapper.selectVoList(wrapper);
}
/**
* 动态条件查询
*/
public List<UserVo> dynamicQuery(UserBo bo) {
// 使用PlusLambdaQuery构建动态条件
return userMapper.selectVoList(
PlusLambdaQuery.lambdaQuery(SysUser.class)
.like(SysUser::getUserName, bo.getUserName())
.like(SysUser::getNickName, bo.getNickName())
.eq(SysUser::getStatus, bo.getStatus())
.between(SysUser::getCreateTime, bo.getBeginTime(), bo.getEndTime())
.orderByDesc(SysUser::getCreateTime)
);
}
}批量操作
java
@Service
public class UserServiceImpl {
@Resource
private SysUserMapper userMapper;
/**
* 批量插入
*/
@Transactional(rollbackFor = Exception.class)
public Boolean batchInsert(List<UserBo> boList) {
List<SysUser> users = BeanUtil.copyToList(boList, SysUser.class);
// 方式1:使用增强的批量插入
int result = userMapper.insertBatch(users);
// 方式2:使用MyBatis-Plus的saveBatch
// return this.saveBatch(users);
return result > 0;
}
/**
* 批量更新
*/
@Transactional(rollbackFor = Exception.class)
public Boolean batchUpdate(List<UserBo> boList) {
List<SysUser> users = BeanUtil.copyToList(boList, SysUser.class);
// 批量更新
return this.updateBatchById(users);
}
/**
* 批量删除
*/
@Transactional(rollbackFor = Exception.class)
public Boolean batchDelete(List<Long> ids, Boolean isValid) {
if (isValid) {
// 业务验证
for (Long id : ids) {
SysUser user = userMapper.selectById(id);
if (ObjectUtil.isNull(user)) {
throw new ServiceException("用户不存在");
}
if ("admin".equals(user.getUserName())) {
throw new ServiceException("系统管理员不能删除");
}
}
}
// 批量删除
return userMapper.deleteBatchIds(ids) > 0;
}
/**
* 批量插入或更新
*/
@Transactional(rollbackFor = Exception.class)
public Boolean batchInsertOrUpdate(List<UserBo> boList) {
List<SysUser> users = BeanUtil.copyToList(boList, SysUser.class);
// 使用增强的批量插入或更新
int result = userMapper.insertOrUpdateBatch(users);
return result > 0;
}
}分页查询
java
@Service
public class UserServiceImpl {
@Resource
private SysUserMapper userMapper;
/**
* 分页查询
*/
public PageResult<UserVo> queryPageList(UserBo bo, PageQuery pageQuery) {
// 构建查询条件
LambdaQueryWrapper<SysUser> wrapper = new LambdaQueryWrapper<SysUser>()
.like(StringUtils.isNotBlank(bo.getUserName()), SysUser::getUserName, bo.getUserName())
.eq(StringUtils.isNotBlank(bo.getStatus()), SysUser::getStatus, bo.getStatus())
.orderByDesc(SysUser::getCreateTime);
// 分页查询并转换为VO
Page<UserVo> result = userMapper.selectVoPage(pageQuery.build(), wrapper);
return PageResult.build(result);
}
/**
* 自定义分页查询
*/
public PageResult<UserVo> customPageQuery(UserBo bo, PageQuery pageQuery) {
// 构建分页对象
Page<SysUser> page = pageQuery.build();
// 设置不查询总数,提升性能
page.setSearchCount(false);
// 执行查询
Page<UserVo> result = userMapper.selectVoPage(page,
new LambdaQueryWrapper<SysUser>()
.like(StringUtils.isNotBlank(bo.getUserName()), SysUser::getUserName, bo.getUserName())
.orderByDesc(SysUser::getCreateTime)
);
return PageResult.build(result);
}
}🔄 数据转换
VO转换
java
// 自动转换配置
@Configuration
public class MapperConfig {
/**
* 自定义类型转换器
*/
@Bean
public DefaultConversionService conversionService() {
DefaultConversionService conversionService = new DefaultConversionService();
// 添加自定义转换器
conversionService.addConverter(new StringToDateConverter());
conversionService.addConverter(new DateToStringConverter());
return conversionService;
}
}
// VO转换示例
@Service
public class UserServiceImpl {
/**
* 实体转VO(自动字典翻译)
*/
public UserVo convertToVo(SysUser user) {
// 基础属性复制
UserVo vo = BeanUtil.copyProperties(user, UserVo.class);
// 字典翻译
vo.setStatusName(DictUtils.getDictLabel("sys_user_status", user.getStatus()));
vo.setSexName(DictUtils.getDictLabel("sys_user_sex", user.getSex()));
// 关联数据查询
if (ObjectUtil.isNotNull(user.getDeptId())) {
SysDept dept = deptMapper.selectById(user.getDeptId());
vo.setDeptName(dept.getDeptName());
}
return vo;
}
/**
* 批量转换
*/
public List<UserVo> convertToVoList(List<SysUser> users) {
if (CollUtil.isEmpty(users)) {
return CollUtil.newArrayList();
}
return users.stream()
.map(this::convertToVo)
.collect(Collectors.toList());
}
}字典翻译
java
// 字典翻译注解
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface DictTranslate {
/**
* 字典类型
*/
String dictType();
/**
* 翻译字段名(默认为当前字段名+Name)
*/
String targetField() default "";
}
// VO类使用字典翻译
@Data
public class UserVo {
private Long userId;
private String userName;
@DictTranslate(dictType = "sys_user_status")
private String status;
private String statusName; // 自动翻译字段
@DictTranslate(dictType = "sys_user_sex", targetField = "sexLabel")
private String sex;
private String sexLabel; // 自定义翻译字段名
}
// 自动翻译处理器
@Component
public class DictTranslateProcessor {
/**
* 处理字典翻译
*/
public <T> T translate(T obj) {
if (ObjectUtil.isNull(obj)) {
return obj;
}
Class<?> clazz = obj.getClass();
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
DictTranslate annotation = field.getAnnotation(DictTranslate.class);
if (annotation != null) {
try {
field.setAccessible(true);
Object value = field.get(obj);
if (ObjectUtil.isNotNull(value)) {
String dictLabel = DictUtils.getDictLabel(annotation.dictType(), String.valueOf(value));
// 确定目标字段名
String targetFieldName = StringUtils.isNotBlank(annotation.targetField())
? annotation.targetField()
: field.getName() + "Name";
// 设置翻译值
Field targetField = clazz.getDeclaredField(targetFieldName);
targetField.setAccessible(true);
targetField.set(obj, dictLabel);
}
} catch (Exception e) {
log.warn("字典翻译失败: {}", e.getMessage());
}
}
}
return obj;
}
/**
* 批量翻译
*/
public <T> List<T> translateList(List<T> list) {
if (CollUtil.isEmpty(list)) {
return list;
}
return list.stream()
.map(this::translate)
.collect(Collectors.toList());
}
}🔍 性能优化
查询优化
java
@Service
public class UserServiceImpl {
/**
* 只查询需要的字段
*/
public List<UserVo> selectSpecificColumns() {
LambdaQueryWrapper<SysUser> wrapper = new LambdaQueryWrapper<SysUser>()
.select(SysUser::getUserId, SysUser::getUserName, SysUser::getNickName, SysUser::getStatus)
.eq(SysUser::getStatus, "0");
return userMapper.selectVoList(wrapper);
}
/**
* 避免N+1查询
*/
public List<UserVo> avoidN1Query() {
// 一次性查询所有用户
List<SysUser> users = userMapper.selectList(null);
// 一次性查询所有部门
List<Long> deptIds = users.stream()
.map(SysUser::getDeptId)
.filter(ObjectUtil::isNotNull)
.distinct()
.collect(Collectors.toList());
Map<Long, SysDept> deptMap = CollUtil.isNotEmpty(deptIds)
? deptMapper.selectBatchIds(deptIds).stream()
.collect(Collectors.toMap(SysDept::getDeptId, Function.identity()))
: new HashMap<>();
// 组装VO
return users.stream().map(user -> {
UserVo vo = BeanUtil.copyProperties(user, UserVo.class);
SysDept dept = deptMap.get(user.getDeptId());
if (dept != null) {
vo.setDeptName(dept.getDeptName());
}
return vo;
}).collect(Collectors.toList());
}
/**
* 使用缓存
*/
@Cacheable(value = "user", key = "#id")
public UserVo selectUserById(Long id) {
return userMapper.selectVoById(id);
}
/**
* 流式查询大数据量
*/
public void processLargeData() {
userMapper.selectList(new LambdaQueryWrapper<SysUser>()
.eq(SysUser::getStatus, "0"))
.stream()
.forEach(user -> {
// 处理每个用户
processUser(user);
});
}
private void processUser(SysUser user) {
// 用户处理逻辑
}
}BaseMapper 为 RuoYi-Plus 提供了强大的数据访问能力,通过增强的方法和自动化转换,大大提升了开发效率和代码质量。
🔗 自定义SQL查询
XML映射文件
xml
<!-- SysUserMapper.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.system.mapper.SysUserMapper">
<!-- 结果映射 -->
<resultMap id="UserVoResultMap" type="com.ruoyi.system.domain.vo.UserVo">
<id column="user_id" property="userId"/>
<result column="user_name" property="userName"/>
<result column="nick_name" property="nickName"/>
<result column="email" property="email"/>
<result column="dept_name" property="deptName"/>
<result column="status" property="status"/>
<result column="create_time" property="createTime"/>
</resultMap>
<!-- 关联查询 -->
<select id="selectUserWithDept" resultMap="UserVoResultMap">
SELECT
u.user_id,
u.user_name,
u.nick_name,
u.email,
u.status,
u.create_time,
d.dept_name
FROM sys_user u
LEFT JOIN sys_dept d ON u.dept_id = d.dept_id
WHERE u.del_flag = '0'
<if test="userName != null and userName != ''">
AND u.user_name LIKE CONCAT('%', #{userName}, '%')
</if>
<if test="status != null and status != ''">
AND u.status = #{status}
</if>
ORDER BY u.create_time DESC
</select>
<!-- 批量查询 -->
<select id="selectUsersByIds" resultMap="UserVoResultMap">
SELECT
u.user_id,
u.user_name,
u.nick_name,
u.email,
u.status
FROM sys_user u
WHERE u.user_id IN
<foreach collection="ids" item="id" open="(" separator="," close=")">
#{id}
</foreach>
AND u.del_flag = '0'
</select>
<!-- 统计查询 -->
<select id="selectUserStatistics" resultType="com.ruoyi.system.domain.vo.UserStatVo">
SELECT
u.dept_id,
d.dept_name,
COUNT(u.user_id) as user_count,
SUM(CASE WHEN u.status = '0' THEN 1 ELSE 0 END) as active_count,
SUM(CASE WHEN u.status = '1' THEN 1 ELSE 0 END) as disabled_count
FROM sys_user u
LEFT JOIN sys_dept d ON u.dept_id = d.dept_id
WHERE u.del_flag = '0'
GROUP BY u.dept_id, d.dept_name
ORDER BY user_count DESC
</select>
</mapper>Mapper接口定义
java
@Mapper
public interface SysUserMapper extends BaseMapperPlus<SysUser, UserVo> {
/**
* 关联查询用户和部门
*/
List<UserVo> selectUserWithDept(@Param("userName") String userName,
@Param("status") String status);
/**
* 批量查询用户
*/
List<UserVo> selectUsersByIds(@Param("ids") List<Long> ids);
/**
* 用户统计
*/
List<UserStatVo> selectUserStatistics();
/**
* 使用注解方式定义SQL
*/
@Select("SELECT * FROM sys_user WHERE user_name = #{userName} AND del_flag = '0'")
SysUser selectByUserName(@Param("userName") String userName);
/**
* 动态SQL注解
*/
@Select("<script>" +
"SELECT * FROM sys_user WHERE del_flag = '0'" +
"<if test='status != null'> AND status = #{status}</if>" +
"<if test='deptId != null'> AND dept_id = #{deptId}</if>" +
"ORDER BY create_time DESC" +
"</script>")
List<SysUser> selectByCondition(@Param("status") String status,
@Param("deptId") Long deptId);
/**
* 更新注解
*/
@Update("UPDATE sys_user SET login_date = #{loginDate}, login_ip = #{loginIp} " +
"WHERE user_id = #{userId}")
int updateLoginInfo(@Param("userId") Long userId,
@Param("loginDate") Date loginDate,
@Param("loginIp") String loginIp);
}多表关联查询
java
@Service
public class UserServiceImpl {
@Resource
private SysUserMapper userMapper;
/**
* 复杂多表关联查询
*/
public List<UserDetailVo> selectUserDetails(UserQueryBo query) {
// 使用MyBatis-Plus的Wrapper进行关联查询
return userMapper.selectJoinList(UserDetailVo.class,
new MPJLambdaWrapper<SysUser>()
.selectAll(SysUser.class)
.selectAs(SysDept::getDeptName, UserDetailVo::getDeptName)
.selectAs(SysRole::getRoleName, UserDetailVo::getRoleNames)
.leftJoin(SysDept.class, SysDept::getDeptId, SysUser::getDeptId)
.leftJoin(SysUserRole.class, SysUserRole::getUserId, SysUser::getUserId)
.leftJoin(SysRole.class, SysRole::getRoleId, SysUserRole::getRoleId)
.like(StringUtils.isNotBlank(query.getUserName()),
SysUser::getUserName, query.getUserName())
.eq(StringUtils.isNotBlank(query.getStatus()),
SysUser::getStatus, query.getStatus())
.eq(SysUser::getDelFlag, "0")
.groupBy(SysUser::getUserId)
.orderByDesc(SysUser::getCreateTime)
);
}
/**
* 子查询
*/
public List<UserVo> selectUsersWithSubQuery() {
return userMapper.selectVoList(
new LambdaQueryWrapper<SysUser>()
.inSql(SysUser::getDeptId,
"SELECT dept_id FROM sys_dept WHERE status = '0'")
.eq(SysUser::getDelFlag, "0")
);
}
/**
* EXISTS子句
*/
public List<UserVo> selectUsersWithExists() {
return userMapper.selectVoList(
new LambdaQueryWrapper<SysUser>()
.exists("SELECT 1 FROM sys_user_role WHERE sys_user.user_id = sys_user_role.user_id")
.eq(SysUser::getDelFlag, "0")
);
}
}🗑️ 软删除处理
配置软删除
java
/**
* 实体基类,包含软删除字段
*/
@Data
public class BaseEntity implements Serializable {
/** 删除标志(0代表存在 1代表删除) */
@TableLogic(value = "0", delval = "1")
@TableField("del_flag")
private String delFlag;
/** 创建者 */
@TableField(fill = FieldFill.INSERT)
private String createBy;
/** 创建时间 */
@TableField(fill = FieldFill.INSERT)
private Date createTime;
/** 更新者 */
@TableField(fill = FieldFill.INSERT_UPDATE)
private String updateBy;
/** 更新时间 */
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
}软删除操作
java
@Service
public class UserServiceImpl {
@Resource
private SysUserMapper userMapper;
/**
* 软删除用户
*/
public Boolean deleteUser(Long userId) {
// 直接调用deleteById,MyBatis-Plus会自动转换为UPDATE语句
// UPDATE sys_user SET del_flag = '1' WHERE user_id = ?
return userMapper.deleteById(userId) > 0;
}
/**
* 批量软删除
*/
public Boolean batchDelete(List<Long> userIds) {
// 批量软删除
return userMapper.deleteBatchIds(userIds) > 0;
}
/**
* 查询已删除的数据
*/
public List<SysUser> selectDeletedUsers() {
// 使用自定义SQL查询已删除数据
return userMapper.selectList(
new QueryWrapper<SysUser>()
.eq("del_flag", "1")
// 忽略逻辑删除
.last("/* 忽略逻辑删除 */")
);
}
/**
* 恢复已删除的数据
*/
public Boolean restoreUser(Long userId) {
SysUser user = new SysUser();
user.setUserId(userId);
user.setDelFlag("0");
// 使用update语句恢复
return userMapper.update(user,
new UpdateWrapper<SysUser>()
.eq("user_id", userId)
.eq("del_flag", "1")
.last("/* 忽略逻辑删除 */")
) > 0;
}
/**
* 物理删除(谨慎使用)
*/
@Transactional(rollbackFor = Exception.class)
public Boolean physicalDelete(Long userId) {
// 使用原生SQL物理删除
return SqlHelper.execute(SysUser.class, mapper -> {
return mapper.delete(
new QueryWrapper<SysUser>()
.eq("user_id", userId)
.last("/* 忽略逻辑删除 */")
) > 0;
});
}
}🔒 乐观锁实现
配置乐观锁
java
/**
* 实体类添加版本字段
*/
@Data
@TableName("sys_config")
public class SysConfig extends BaseEntity {
@TableId(value = "config_id")
private Long configId;
private String configName;
private String configKey;
private String configValue;
/** 乐观锁版本号 */
@Version
@TableField("version")
private Integer version;
}
/**
* MyBatis-Plus配置乐观锁插件
*/
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 分页插件
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
// 乐观锁插件
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
// 防止全表更新与删除插件
interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());
return interceptor;
}
}乐观锁使用
java
@Service
@Slf4j
public class ConfigServiceImpl {
@Resource
private SysConfigMapper configMapper;
/**
* 更新配置(带乐观锁)
*/
public Boolean updateConfig(ConfigBo bo) {
// 查询当前数据
SysConfig config = configMapper.selectById(bo.getConfigId());
if (config == null) {
throw new ServiceException("配置不存在");
}
// 更新数据
config.setConfigValue(bo.getConfigValue());
config.setRemark(bo.getRemark());
// version字段不需要手动设置,插件会自动处理
// 执行更新
// UPDATE sys_config SET config_value = ?, remark = ?, version = version + 1
// WHERE config_id = ? AND version = ?
int result = configMapper.updateById(config);
if (result == 0) {
throw new ServiceException("数据已被其他用户修改,请刷新后重试");
}
return true;
}
/**
* 带重试的更新
*/
@Retryable(value = ServiceException.class, maxAttempts = 3, backoff = @Backoff(delay = 100))
public Boolean updateConfigWithRetry(ConfigBo bo) {
return updateConfig(bo);
}
/**
* 手动处理乐观锁冲突
*/
public Boolean updateConfigWithConflictHandle(ConfigBo bo) {
int maxRetries = 3;
int attempt = 0;
while (attempt < maxRetries) {
try {
// 查询最新数据
SysConfig config = configMapper.selectById(bo.getConfigId());
if (config == null) {
throw new ServiceException("配置不存在");
}
// 更新数据
config.setConfigValue(bo.getConfigValue());
int result = configMapper.updateById(config);
if (result > 0) {
return true;
}
// 更新失败,可能是版本冲突
attempt++;
log.warn("乐观锁冲突,重试第{}次", attempt);
if (attempt < maxRetries) {
Thread.sleep(100 * attempt); // 退避等待
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new ServiceException("操作被中断");
}
}
throw new ServiceException("更新失败,请稍后重试");
}
}✨ 自动填充
配置自动填充处理器
java
/**
* MyBatis-Plus 自动填充处理器
*/
@Slf4j
@Component
public class CreateAndUpdateMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
try {
// 创建时间
if (metaObject.hasSetter("createTime")) {
this.strictInsertFill(metaObject, "createTime", Date.class, new Date());
}
// 创建者
if (metaObject.hasSetter("createBy")) {
String username = getUsername();
this.strictInsertFill(metaObject, "createBy", String.class, username);
}
// 部门ID
if (metaObject.hasSetter("createDept")) {
Long deptId = getDeptId();
this.strictInsertFill(metaObject, "createDept", Long.class, deptId);
}
// 更新时间(插入时也填充)
if (metaObject.hasSetter("updateTime")) {
this.strictInsertFill(metaObject, "updateTime", Date.class, new Date());
}
// 更新者
if (metaObject.hasSetter("updateBy")) {
String username = getUsername();
this.strictInsertFill(metaObject, "updateBy", String.class, username);
}
// 删除标志
if (metaObject.hasSetter("delFlag")) {
this.strictInsertFill(metaObject, "delFlag", String.class, "0");
}
// 租户ID
if (metaObject.hasSetter("tenantId")) {
String tenantId = getTenantId();
this.strictInsertFill(metaObject, "tenantId", String.class, tenantId);
}
} catch (Exception e) {
log.error("自动填充异常: {}", e.getMessage());
}
}
@Override
public void updateFill(MetaObject metaObject) {
try {
// 更新时间
if (metaObject.hasSetter("updateTime")) {
this.strictUpdateFill(metaObject, "updateTime", Date.class, new Date());
}
// 更新者
if (metaObject.hasSetter("updateBy")) {
String username = getUsername();
this.strictUpdateFill(metaObject, "updateBy", String.class, username);
}
} catch (Exception e) {
log.error("自动填充异常: {}", e.getMessage());
}
}
/**
* 获取当前登录用户名
*/
private String getUsername() {
try {
LoginUser loginUser = LoginHelper.getLoginUser();
return loginUser != null ? loginUser.getUsername() : "system";
} catch (Exception e) {
return "system";
}
}
/**
* 获取当前部门ID
*/
private Long getDeptId() {
try {
LoginUser loginUser = LoginHelper.getLoginUser();
return loginUser != null ? loginUser.getDeptId() : null;
} catch (Exception e) {
return null;
}
}
/**
* 获取当前租户ID
*/
private String getTenantId() {
try {
return TenantHelper.getTenantId();
} catch (Exception e) {
return null;
}
}
}实体类配置
java
/**
* 实体基类
*/
@Data
public class BaseEntity implements Serializable {
/** 创建者 */
@TableField(fill = FieldFill.INSERT)
private String createBy;
/** 创建时间 */
@TableField(fill = FieldFill.INSERT)
private Date createTime;
/** 创建部门 */
@TableField(fill = FieldFill.INSERT)
private Long createDept;
/** 更新者 */
@TableField(fill = FieldFill.INSERT_UPDATE)
private String updateBy;
/** 更新时间 */
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
/** 删除标志 */
@TableLogic(value = "0", delval = "1")
@TableField(value = "del_flag", fill = FieldFill.INSERT)
private String delFlag;
/** 租户ID */
@TableField(fill = FieldFill.INSERT)
private String tenantId;
}🔄 类型处理器
自定义类型处理器
java
/**
* JSON类型处理器
* 用于处理JSON字段的序列化和反序列化
*/
@MappedTypes({Object.class})
@MappedJdbcTypes(JdbcType.VARCHAR)
public class JsonTypeHandler<T> extends BaseTypeHandler<T> {
private final Class<T> type;
public JsonTypeHandler(Class<T> type) {
this.type = type;
}
@Override
public void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType)
throws SQLException {
ps.setString(i, JsonUtils.toJsonString(parameter));
}
@Override
public T getNullableResult(ResultSet rs, String columnName) throws SQLException {
String json = rs.getString(columnName);
return parseJson(json);
}
@Override
public T getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
String json = rs.getString(columnIndex);
return parseJson(json);
}
@Override
public T getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
String json = cs.getString(columnIndex);
return parseJson(json);
}
private T parseJson(String json) {
if (StringUtils.isBlank(json)) {
return null;
}
return JsonUtils.parseObject(json, type);
}
}
/**
* List<String> 类型处理器
*/
@MappedTypes({List.class})
@MappedJdbcTypes(JdbcType.VARCHAR)
public class StringListTypeHandler extends BaseTypeHandler<List<String>> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i, List<String> parameter,
JdbcType jdbcType) throws SQLException {
ps.setString(i, String.join(",", parameter));
}
@Override
public List<String> getNullableResult(ResultSet rs, String columnName) throws SQLException {
String value = rs.getString(columnName);
return parseList(value);
}
@Override
public List<String> getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
String value = rs.getString(columnIndex);
return parseList(value);
}
@Override
public List<String> getNullableResult(CallableStatement cs, int columnIndex)
throws SQLException {
String value = cs.getString(columnIndex);
return parseList(value);
}
private List<String> parseList(String value) {
if (StringUtils.isBlank(value)) {
return new ArrayList<>();
}
return Arrays.asList(value.split(","));
}
}
/**
* 加密字段类型处理器
*/
@MappedTypes({String.class})
@MappedJdbcTypes(JdbcType.VARCHAR)
public class EncryptTypeHandler extends BaseTypeHandler<String> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i, String parameter,
JdbcType jdbcType) throws SQLException {
// 写入数据库时加密
ps.setString(i, EncryptUtils.encrypt(parameter));
}
@Override
public String getNullableResult(ResultSet rs, String columnName) throws SQLException {
String value = rs.getString(columnName);
return decrypt(value);
}
@Override
public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
String value = rs.getString(columnIndex);
return decrypt(value);
}
@Override
public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
String value = cs.getString(columnIndex);
return decrypt(value);
}
private String decrypt(String value) {
if (StringUtils.isBlank(value)) {
return value;
}
try {
return EncryptUtils.decrypt(value);
} catch (Exception e) {
return value; // 解密失败返回原值
}
}
}实体类使用类型处理器
java
@Data
@TableName(value = "sys_config", autoResultMap = true)
public class SysConfig extends BaseEntity {
@TableId(value = "config_id")
private Long configId;
private String configName;
private String configKey;
/** JSON配置值 */
@TableField(typeHandler = JsonTypeHandler.class)
private Map<String, Object> configJson;
/** 标签列表 */
@TableField(typeHandler = StringListTypeHandler.class)
private List<String> tags;
/** 加密字段 */
@TableField(typeHandler = EncryptTypeHandler.class)
private String secretValue;
}🛡️ 数据权限
数据权限处理器
java
/**
* 数据权限处理器
*/
@Component
@Slf4j
public class DataPermissionHandler implements DataPermissionInterceptor {
@Override
public Expression getSqlSegment(Expression where, String mappedStatementId) {
// 获取当前登录用户
LoginUser loginUser = LoginHelper.getLoginUser();
if (loginUser == null || loginUser.isAdmin()) {
// 未登录或管理员,不过滤
return where;
}
// 获取数据权限配置
DataScope dataScope = getDataScope(mappedStatementId);
if (dataScope == null) {
return where;
}
// 构建数据权限SQL
Expression dataPermissionExpression = buildDataPermission(loginUser, dataScope);
if (where == null) {
return dataPermissionExpression;
}
// 合并条件
return new AndExpression(where, dataPermissionExpression);
}
/**
* 构建数据权限表达式
*/
private Expression buildDataPermission(LoginUser user, DataScope dataScope) {
switch (dataScope.getType()) {
case ALL:
// 全部数据权限
return null;
case DEPT:
// 本部门数据
return new EqualsTo(
new Column(dataScope.getDeptColumn()),
new LongValue(user.getDeptId())
);
case DEPT_AND_CHILD:
// 本部门及子部门数据
return new InExpression(
new Column(dataScope.getDeptColumn()),
buildDeptSubQuery(user.getDeptId())
);
case SELF:
// 仅本人数据
return new EqualsTo(
new Column(dataScope.getUserColumn()),
new LongValue(user.getUserId())
);
case CUSTOM:
// 自定义数据权限
return buildCustomPermission(user, dataScope);
default:
return null;
}
}
/**
* 构建部门子查询
*/
private SubSelect buildDeptSubQuery(Long deptId) {
PlainSelect selectBody = new PlainSelect();
selectBody.setSelectItems(Collections.singletonList(new AllColumns()));
selectBody.setFromItem(new Table("sys_dept"));
selectBody.setWhere(new LikeExpression(
new Column("ancestors"),
new StringValue("%" + deptId + "%")
));
SubSelect subSelect = new SubSelect();
subSelect.setSelectBody(selectBody);
return subSelect;
}
}
/**
* 数据权限注解
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface DataScope {
/**
* 权限类型
*/
DataScopeType type() default DataScopeType.DEPT;
/**
* 部门表别名
*/
String deptAlias() default "";
/**
* 部门字段名
*/
String deptColumn() default "dept_id";
/**
* 用户字段名
*/
String userColumn() default "user_id";
}
/**
* 数据权限类型
*/
public enum DataScopeType {
/** 全部数据权限 */
ALL,
/** 本部门数据权限 */
DEPT,
/** 本部门及子部门数据权限 */
DEPT_AND_CHILD,
/** 仅本人数据权限 */
SELF,
/** 自定义数据权限 */
CUSTOM
}使用数据权限
java
@Service
public class UserServiceImpl {
@Resource
private SysUserMapper userMapper;
/**
* 查询用户列表(自动应用数据权限)
*/
@DataScope(type = DataScopeType.DEPT_AND_CHILD, deptColumn = "dept_id")
public List<UserVo> selectUserList(UserBo bo) {
return userMapper.selectVoList(
new LambdaQueryWrapper<SysUser>()
.like(StringUtils.isNotBlank(bo.getUserName()),
SysUser::getUserName, bo.getUserName())
.eq(SysUser::getDelFlag, "0")
);
}
/**
* 查询本人数据
*/
@DataScope(type = DataScopeType.SELF, userColumn = "user_id")
public List<UserVo> selectMyData() {
return userMapper.selectVoList(null);
}
}🔐 SQL注入防护
参数校验
java
/**
* SQL注入防护工具类
*/
public final class SqlInjectionUtils {
private SqlInjectionUtils() {}
/** SQL注入关键字正则 */
private static final Pattern SQL_INJECTION_PATTERN = Pattern.compile(
"('.+--)|(--)|(\\|)|(%7C)|(;)|" +
"(\\bselect\\b)|" +
"(\\binsert\\b)|" +
"(\\bdelete\\b)|" +
"(\\bupdate\\b)|" +
"(\\bdrop\\b)|" +
"(\\btruncate\\b)|" +
"(\\bcreate\\b)|" +
"(\\balter\\b)|" +
"(\\bexec\\b)|" +
"(\\bunion\\b)|" +
"(\\bor\\b)|" +
"(\\band\\b)",
Pattern.CASE_INSENSITIVE
);
/**
* 检查是否包含SQL注入风险
*/
public static boolean hasSqlInjection(String value) {
if (StringUtils.isBlank(value)) {
return false;
}
return SQL_INJECTION_PATTERN.matcher(value).find();
}
/**
* 检查参数并抛出异常
*/
public static void checkSqlInjection(String... values) {
for (String value : values) {
if (hasSqlInjection(value)) {
throw new ServiceException("参数存在SQL注入风险");
}
}
}
/**
* 过滤SQL注入字符
*/
public static String filterSqlInjection(String value) {
if (StringUtils.isBlank(value)) {
return value;
}
return value.replaceAll("(?i)(select|insert|delete|update|drop|truncate|" +
"create|alter|exec|union|and|or|--|;)", "");
}
/**
* 转义特殊字符
*/
public static String escapeSql(String value) {
if (StringUtils.isBlank(value)) {
return value;
}
return value.replace("'", "''")
.replace("\\", "\\\\")
.replace("%", "\\%")
.replace("_", "\\_");
}
}安全查询
java
@Service
public class SafeQueryService {
@Resource
private SysUserMapper userMapper;
/**
* 安全的动态排序查询
*/
public List<UserVo> safeOrderQuery(String orderByColumn, String orderDirection) {
// 校验排序字段(白名单方式)
Set<String> allowedColumns = Set.of("user_id", "user_name", "create_time");
if (!allowedColumns.contains(orderByColumn)) {
throw new ServiceException("不支持的排序字段: " + orderByColumn);
}
// 校验排序方向
String direction = "desc".equalsIgnoreCase(orderDirection) ? "desc" : "asc";
return userMapper.selectVoList(
new QueryWrapper<SysUser>()
.eq("del_flag", "0")
.orderBy(true, "asc".equals(direction), orderByColumn)
);
}
/**
* 安全的模糊查询
*/
public List<UserVo> safeLikeQuery(String keyword) {
// 转义特殊字符
String safeKeyword = SqlInjectionUtils.escapeSql(keyword);
return userMapper.selectVoList(
new LambdaQueryWrapper<SysUser>()
.like(SysUser::getUserName, safeKeyword)
.eq(SysUser::getDelFlag, "0")
);
}
/**
* 使用参数化查询
*/
public SysUser safeParameterizedQuery(String userName) {
// 使用MyBatis-Plus的条件构造器,自动参数化
return userMapper.selectOne(
new LambdaQueryWrapper<SysUser>()
.eq(SysUser::getUserName, userName)
.eq(SysUser::getDelFlag, "0")
);
}
}📝 事务管理
事务配置
java
@Service
@Slf4j
public class TransactionalService {
@Resource
private SysUserMapper userMapper;
@Resource
private SysUserRoleMapper userRoleMapper;
/**
* 基础事务
*/
@Transactional(rollbackFor = Exception.class)
public Boolean createUser(UserBo bo) {
// 新增用户
SysUser user = BeanUtil.copyProperties(bo, SysUser.class);
userMapper.insert(user);
// 新增用户角色关联
if (CollUtil.isNotEmpty(bo.getRoleIds())) {
List<SysUserRole> userRoles = bo.getRoleIds().stream()
.map(roleId -> {
SysUserRole ur = new SysUserRole();
ur.setUserId(user.getUserId());
ur.setRoleId(roleId);
return ur;
})
.collect(Collectors.toList());
userRoleMapper.insertBatch(userRoles);
}
return true;
}
/**
* 只读事务
*/
@Transactional(readOnly = true)
public UserVo queryUser(Long userId) {
return userMapper.selectVoById(userId);
}
/**
* 独立事务
*/
@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
public void logOperation(String operation) {
// 独立事务记录操作日志
// 即使外层事务回滚,这里的日志也会保存
}
/**
* 嵌套事务
*/
@Transactional(propagation = Propagation.NESTED, rollbackFor = Exception.class)
public void nestedOperation() {
// 嵌套事务
// 可以独立回滚到保存点,不影响外层事务
}
/**
* 手动控制事务
*/
public Boolean manualTransaction(List<UserBo> boList) {
TransactionStatus status = TransactionHelper.begin();
try {
for (UserBo bo : boList) {
SysUser user = BeanUtil.copyProperties(bo, SysUser.class);
userMapper.insert(user);
}
TransactionHelper.commit(status);
return true;
} catch (Exception e) {
TransactionHelper.rollback(status);
throw e;
}
}
}📋 注意事项
1. 性能注意事项
- 避免N+1查询: 使用批量查询代替循环单条查询
- 只查询需要的字段: 使用select()方法指定字段
- 合理使用索引: 确保WHERE条件字段有索引
- 分页查询大数据: 避免一次性查询全部数据
- 使用缓存: 对热点数据使用Redis缓存
java
// ❌ 不推荐:N+1查询
for (Long userId : userIds) {
UserVo user = userMapper.selectVoById(userId);
}
// ✅ 推荐:批量查询
List<UserVo> users = userMapper.selectVoList(
new LambdaQueryWrapper<SysUser>().in(SysUser::getUserId, userIds)
);2. 安全注意事项
- 使用参数化查询: 避免SQL拼接
- 校验用户输入: 防止SQL注入
- 限制返回字段: 不返回敏感字段
- 数据权限控制: 根据用户权限过滤数据
- 操作日志记录: 记录敏感数据操作
3. 代码规范
- 统一使用LambdaQueryWrapper: 避免硬编码字段名
- VO与Entity分离: 查询返回VO,更新使用Entity
- 事务边界清晰: Service层统一管理事务
- 异常处理规范: 捕获并转换数据库异常
4. 批量操作注意事项
- 批量大小控制: 单次批量不超过1000条
- 事务分割: 超大批量操作分批提交
- 主键生成: 批量插入使用数据库自增或UUID
- 异常处理: 批量操作失败需要回滚
java
// 分批处理大数据量
public void batchProcess(List<UserBo> boList) {
int batchSize = 500;
List<List<UserBo>> batches = ListUtil.split(boList, batchSize);
for (List<UserBo> batch : batches) {
processBatch(batch);
}
}
@Transactional(rollbackFor = Exception.class)
public void processBatch(List<UserBo> batch) {
List<SysUser> users = BeanUtil.copyToList(batch, SysUser.class);
userMapper.insertBatch(users);
}5. 软删除注意事项
- 统一使用del_flag: 所有表保持一致的软删除字段
- 唯一索引处理: 软删除数据可能影响唯一索引
- 关联删除: 关联表需要同步软删除
- 数据清理: 定期清理已删除数据
6. 乐观锁注意事项
- version字段类型: 推荐使用Integer类型
- 并发冲突处理: 提供友好的错误提示
- 重试机制: 关键操作添加重试逻辑
- version更新: 只有updateById和update会触发version更新
