1. MyBatis与MyBatis-Plus注解全景解析
在Java持久层开发中,MyBatis和MyBatis-Plus的注解系统如同瑞士军刀般实用。这些注解不仅能减少XML配置的繁琐,还能让SQL操作更加直观和类型安全。作为每天与数据库打交道的开发者,合理运用这些注解可以显著提升CRUD操作的开发效率。
MyBatis的核心注解诞生于需要更灵活操作SQL的场景,而MyBatis-Plus的注解则进一步简化了常见操作。两者配合使用,既能保持MyBatis的灵活性,又能享受MyBatis-Plus的便捷性。值得注意的是,最新版本的MyBatis-Plus 3.5+对注解功能做了重要增强,支持更多开箱即用的特性。
2. MyBatis核心注解深度剖析
2.1 基础映射注解
@Mapper注解是MyBatis的入口点,标注在接口上表示这是一个MyBatis映射器。实际开发中我常遇到的一个坑是忘记在Spring Boot启动类添加@MapperScan导致注入失败。正确的做法是:
java复制@SpringBootApplication
@MapperScan("com.example.mapper")
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
@Select、@Insert、@Update、@Delete这四个注解对应着SQL的CRUD操作。在复杂查询时,我推荐使用@Select配合<script>标签实现动态SQL:
java复制@Select("<script>" +
"SELECT * FROM users " +
"<where>" +
" <if test='name != null'>AND name = #{name}</if>" +
" <if test='age != null'>AND age = #{age}</if>" +
"</where>" +
"</script>")
List<User> findUsers(@Param("name") String name, @Param("age") Integer age);
特别注意:在MyBatis 3.5+版本中,
@Select等注解可以直接使用${}进行字符串替换,但这会带来SQL注入风险。奇安信等安全扫描工具会将其标记为漏洞,建议始终使用#{}参数化查询。
2.2 参数处理注解
@Param注解解决了多参数方法的参数绑定问题。当方法参数超过一个时,必须使用@Param为每个参数命名:
java复制@Select("SELECT * FROM users WHERE name = #{name} AND age = #{age}")
User findByNameAndAge(@Param("name") String name,
@Param("age") int age);
@Results和@Result注解组合用于处理复杂的结果映射。在关联查询时特别有用:
java复制@Results(id = "userResultMap", value = {
@Result(property = "id", column = "user_id"),
@Result(property = "name", column = "user_name"),
@Result(property = "department",
column = "dept_id",
one = @One(select = "com.example.mapper.DepartmentMapper.findById"))
})
@Select("SELECT * FROM users WHERE id = #{id}")
User findUserWithDepartment(Long id);
3. MyBatis-Plus增强注解详解
3.1 实体类注解
@TableName注解解决了表名与实体类名的映射问题。在表名有前缀或特殊命名时特别有用:
java复制@TableName("t_user")
public class User {
// 类内容
}
@TableId注解用于标识主键字段,支持多种主键策略:
java复制@TableId(value = "user_id", type = IdType.AUTO)
private Long id;
主键策略IdType包含:
- AUTO:数据库ID自增
- NONE:无状态,需要手动设置
- INPUT:用户输入ID
- ASSIGN_ID:分配ID(默认雪花算法)
- ASSIGN_UUID:分配UUID
@TableField注解处理字段与列名的映射,还能控制自动填充策略:
java复制@TableField(value = "create_time", fill = FieldFill.INSERT)
private Date createTime;
3.2 CRUD操作注解
@Mapper在MyBatis-Plus中有增强版,配合BaseMapper使用:
java复制public interface UserMapper extends BaseMapper<User> {
// 自定义方法
}
@Select等注解在MyBatis-Plus中可以省略,因为BaseMapper已提供基本CRUD方法。但对于复杂查询,仍然需要自定义:
java复制@Select("SELECT * FROM user ${ew.customSqlSegment}")
List<User> selectAll(@Param(Constants.WRAPPER) Wrapper<User> wrapper);
4. 高级特性与实战技巧
4.1 动态表名处理
在分表场景下,可以使用@Interceptor配合动态表名处理器:
java复制public class DynamicTableNameInterceptor implements InnerInterceptor {
@Override
public void beforeQuery(Executor executor, MappedStatement ms,
Object parameter, RowBounds rowBounds,
ResultHandler resultHandler,
BoundSql boundSql) {
// 动态修改SQL中的表名
}
}
4.2 逻辑删除实现
MyBatis-Plus通过注解轻松实现逻辑删除:
java复制@TableLogic
private Integer deleted;
配置application.yml:
yaml复制mybatis-plus:
global-config:
db-config:
logic-delete-field: deleted
logic-not-delete-value: 0
logic-delete-value: 1
4.3 字段自动填充
实现MetaObjectHandler接口配合@TableField注解实现自动填充:
java复制@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
this.strictInsertFill(metaObject, "createTime", Date.class, new Date());
}
@Override
public void updateFill(MetaObject metaObject) {
this.strictUpdateFill(metaObject, "updateTime", Date.class, new Date());
}
}
5. 安全与性能优化
5.1 防止SQL注入
避免在注解中使用${}进行字符串拼接,这是SQL注入的主要来源。正确的做法是:
java复制// 错误示范(有风险)
@Select("SELECT * FROM ${tableName} WHERE name = #{name}")
// 正确做法
@Select("SELECT * FROM users WHERE name = #{name}")
5.2 批量操作优化
MyBatis-Plus提供了高效的批量操作方法:
java复制// 批量插入
userMapper.insertBatchSomeColumn(list);
// 批量更新
userMapper.updateBatchById(list);
性能提示:默认情况下MyBatis-Plus的批量操作不会返回影响行数,如果需要获取,需通过自定义SQL实现。
5.3 分页查询实现
MyBatis-Plus的分页需要先配置分页拦截器:
java复制@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
然后使用Page对象进行分页查询:
java复制Page<User> page = new Page<>(1, 10);
userMapper.selectPage(page, Wrappers.<User>query().eq("status", 1));
6. 常见问题排查
6.1 注解不生效问题
- 检查是否添加了
@Mapper或@MapperScan - 确认MyBatis配置是否正确加载
- 检查方法是否为public
- 查看是否有同名的XML映射文件冲突
6.2 参数绑定异常
- 多参数方法必须使用
@Param注解 - 参数名与SQL中的占位符必须一致
- 复杂对象作为参数时,直接使用属性名即可
6.3 动态SQL问题
<script>标签内不要有多余的空格或换行- 动态SQL中的条件判断要处理好null值
- 避免在注解中使用过于复杂的动态SQL,考虑改用XML配置
7. 注解最佳实践
- 简单CRUD优先使用MyBatis-Plus的BaseMapper方法
- 复杂查询使用
@Select注解配合动态SQL - 结果映射优先考虑使用
@Results注解而非XML配置 - 分页查询使用MyBatis-Plus的分页插件
- 字段填充使用
@TableField配合MetaObjectHandler - 敏感数据脱敏可以使用自定义注解如
@DataMasking:
java复制@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@JsonSerialize(using = DataMaskingSerializer.class)
public @interface DataMasking {
DataMaskingFunc maskFunc() default DataMaskingFunc.NO_MASK;
}
public enum DataMaskingFunc {
// 不同脱敏规则
ID_CARD, PHONE, BANK_CARD
}
在实际项目中,我通常会建立一个注解使用的规范文档,规定团队中各种场景下的注解使用标准。比如:
- 简单查询直接使用BaseMapper方法
- 3表以上关联查询使用XML配置
- 所有更新操作必须添加
@Transactional - 敏感字段必须添加脱敏注解
这样的规范能保证代码风格统一,也便于后续维护。
