1. MyBatis与MyBatis-Plus注解全景解析
作为Java持久层框架的两种经典实现,MyBatis和MyBatis-Plus通过注解机制大幅简化了数据库操作。先看MyBatis原生注解体系:
1.1 MyBatis核心CRUD注解
@Select 注解支持OGNL表达式动态SQL:
java复制@Select("SELECT * FROM users WHERE dept_id = #{deptId}")
List<User> findByDept(@Param("deptId") Long deptId);
注意:参数传递必须使用@Param明确指定参数名,否则在复杂SQL中可能出现绑定异常
@Insert 注解处理主键回显的两种方式:
java复制// 方式1:使用useGeneratedKeys(MySQL适用)
@Insert("INSERT INTO users(name) VALUES(#{name})")
@Options(useGeneratedKeys=true, keyProperty="id")
int insertUser(User user);
// 方式2:SELECT LAST_INSERT_ID()(多数据库兼容)
@Insert("INSERT INTO users(name) VALUES(#{name})")
@SelectKey(statement="SELECT LAST_INSERT_ID()",
keyProperty="id",
resultType=Long.class,
before=false)
int insertUser(User user);
@Update/@Delete 注解的批量操作陷阱:
java复制@Update("<script>" +
"UPDATE users SET status=#{status} WHERE id IN " +
"<foreach item='id' collection='ids' open='(' separator=',' close=')'>" +
"#{id}" +
"</foreach>" +
"</script>")
int batchUpdateStatus(@Param("ids") List<Long> ids, @Param("status") int status);
警告:批量操作必须用
<script>包裹动态SQL,否则XML特殊字符会导致解析失败
1.2 参数处理进阶技巧
@Param 的隐式别名机制:
java复制// 方法1:显式命名
@Select("SELECT * FROM users WHERE name = #{name} AND age = #{param2}")
User findByNameAndAge(@Param("name") String name, int age);
// 方法2:默认param1...paramN
@Select("SELECT * FROM users WHERE name = #{param1} AND age = #{param2}")
User findByNameAndAge(String name, int age);
@MapKey 处理结果集映射:
java复制@Select("SELECT * FROM users")
@MapKey("id") // 指定用User的id属性作为Map的key
Map<Long, User> getUserMap();
2. MyBatis-Plus增强注解解析
2.1 实体类映射注解
@TableName 动态表名实战:
java复制@TableName(value = "users", schema = "sys")
public class User {
// 动态表名场景
@TableName(exist = false)
private String actualTableName;
public String getActualTableName() {
return "user_" + LocalDate.now().getYear();
}
}
@TableField 的三种特殊用法:
java复制public class User {
@TableField(value = "is_deleted", fill = FieldFill.INSERT)
private Integer deleted;
@TableField(exist = false)
private String tempData;
@TableField(condition = SqlCondition.LIKE)
private String name;
}
2.2 条件构造器注解
@InterceptorIgnore 过滤自动填充:
java复制@InterceptorIgnore(tenantLine = "true")
public interface UserMapper extends BaseMapper<User> {
@InterceptorIgnore(illegalSql = "true")
@Select("select * from ${tableName}")
List<User> selectFromTable(@Param("tableName") String tableName);
}
@SqlParser 注解的多租户控制(3.4.0+已废弃,改用@InterceptorIgnore):
java复制@SqlParser(filter = true) // 旧版用法
public interface UserMapper extends BaseMapper<User> {
// 查询忽略租户限制
}
3. 动态SQL安全实践
3.1 ${}与#{}的抉择
危险示例(SQL注入漏洞):
java复制@Select("SELECT * FROM ${tableName} WHERE name = '${name}'")
List<User> unsafeQuery(@Param("tableName") String tableName,
@Param("name") String name);
安全改造方案:
java复制@Select("SELECT * FROM ${tableName} WHERE name = #{name}")
List<User> safeQuery(@Param("tableName") String tableName,
@Param("name") String name);
关键原则:表名/列名等SQL关键字用${},数据值必须用#{}
3.2 注解式动态SQL
@Lang 驱动动态SQL:
java复制@Lang(SimpleDriver.class)
@Select("SELECT * FROM users WHERE 1=1 " +
"<if test='name != null'> AND name = #{name} </if>")
List<User> findUsers(@Param("name") String name);
4. 扩展功能集成方案
4.1 分页插件注解
@Page 注解的三种模式:
java复制// 方式1:传统分页
@Select("SELECT * FROM users")
IPage<User> selectPage(IPage<User> page);
// 方式2:DTO分页
@Select("SELECT * FROM users WHERE age > #{param2.age}")
IPage<User> selectDtoPage(IPage<User> page, @Param("param2") UserQuery query);
// 方式3:XML分页(需配置分页插件)
UserMapper.xml:
<select id="selectXmlPage" resultType="User">
SELECT * FROM users
</select>
4.2 逻辑删除全局配置
application.yml配置示例:
yaml复制mybatis-plus:
global-config:
db-config:
logic-delete-field: deleted # 全局逻辑删除字段
logic-not-delete-value: 0 # 未删除值
logic-delete-value: 1 # 删除值
实体类注解:
java复制@TableLogic
private Integer deleted;
5. 生产环境避坑指南
5.1 注解冲突场景
@Transactional 与MyBatis混合事务:
java复制@Service
public class UserService {
@Transactional
public void updateUser(User user) {
// 方法1:直接调用Mapper
userMapper.updateById(user); // 可能失效
// 方法2:自注入调用
UserService self = (UserService) AopContext.currentProxy();
self.innerUpdate(user);
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void innerUpdate(User user) {
userMapper.updateById(user);
}
}
5.2 性能优化要点
@CacheNamespace 二级缓存配置:
java复制@CacheNamespace(implementation = MybatisRedisCache.class,
eviction = MybatisRedisCache.class)
public interface UserMapper extends BaseMapper<User> {
@Options(useCache = true, flushCache = Options.FlushCachePolicy.FALSE)
@Select("SELECT * FROM users WHERE id = #{id}")
User selectByIdWithCache(Long id);
}
@BatchResult 批处理优化:
java复制@Insert("<script>" +
"INSERT INTO users(name, age) VALUES " +
"<foreach collection='list' item='item' separator=','>" +
"(#{item.name}, #{item.age})" +
"</foreach>" +
"</script>")
@Options(batch = true)
int batchInsert(@Param("list") List<User> users);
6. 最新特性实战
6.1 数据脱敏集成
@JsonSerialize 配合自定义注解:
java复制public class User {
@DataMasking(maskFunc = DataMaskingFunc.ALL_MASK)
private String idCard;
@DataMasking(maskFunc = DataMaskingFunc.BANK_CARD_MASK)
private String bankCard;
}
// 自定义注解
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside
@JsonSerialize(using = DataMaskingSerializer.class)
public @interface DataMasking {
DataMaskingFunc maskFunc() default DataMaskingFunc.NO_MASK;
}
6.2 多数据源注解
@DS 动态数据源切换:
java复制@Service
@DS("master") // 默认数据源
public class UserService {
@DS("slave1") // 方法级覆盖
public User getFromSlave(Long id) {
return userMapper.selectById(id);
}
}
在复杂查询场景中,建议结合QueryWrapper构建类型安全的查询条件:
java复制@Select("SELECT * FROM users ${ew.customSqlSegment}")
List<User> selectByWrapper(@Param(Constants.WRAPPER) QueryWrapper<User> wrapper);
实际开发中,MyBatis-Plus的LambdaQueryWrapper能提供更好的类型安全:
java复制public List<User> findUsers(String name, Integer minAge) {
return lambdaQuery()
.eq(StringUtils.isNotBlank(name), User::getName, name)
.ge(minAge != null, User::getAge, minAge)
.list();
}
