MyBatis常用注解全解析:从基础CRUD到高级映射
MyBatis常用注解全解析:从基础CRUD到高级映射
本文全面解析MyBatis核心注解体系,涵盖基础操作、动态SQL、关系映射等高级特性,助你彻底掌握MyBatis注解开发精髓
一、MyBatis注解概述
1.1 注解 vs XML配置
MyBatis同时支持XML配置和注解两种方式实现SQL映射:
特性 | XML配置 | 注解 |
---|---|---|
可读性 | 高(SQL与Java分离) | 中(SQL嵌入代码) |
维护性 | 修改无需重新编译 | 修改需重新编译 |
灵活性 | 支持复杂动态SQL | 动态SQL有限 |
简洁性 | 文件较多 | 零配置 |
适用场景 | 大型复杂项目 | 中小型项目/快速开发 |
1.2 注解核心优势
- 零配置快速启动:无需XML文件
- 接口与SQL紧耦合:SQL直接定义在DAO接口
- 类型安全:编译时检查SQL语法
- 简化开发:减少文件切换
二、基础CRUD注解
2.1 @Select:查询操作
@Select("SELECT * FROM users WHERE id = #{id}")
User getUserById(@Param("id") int id);// 多参数查询
@Select("SELECT * FROM users WHERE name = #{name} AND age > #{age}")
List<User> findUsers(@Param("name") String name, @Param("age") int age);// 返回Map
@Select("SELECT id, name FROM users")
@MapKey("id")
Map<Integer, User> getUserMap();
高级特性:
- 支持结果映射(配合@Result/@Results)
- 支持动态SQL(配合
2.2 @Insert:插入操作
// 返回自增主键
@Insert("INSERT INTO users(name, email) VALUES(#{name}, #{email})")
@Options(useGeneratedKeys = true, keyProperty = "id")
int insertUser(User user);// 批量插入
@Insert({"<script>","INSERT INTO users (name, email) VALUES","<foreach collection='list' item='user' separator=','>","(#{user.name}, #{user.email})","</foreach>","</script>"
})
int batchInsertUsers(@Param("list") List<User> users);
2.3 @Update:更新操作
@Update("UPDATE users SET name=#{name}, email=#{email} WHERE id=#{id}")
int updateUser(User user);// 条件更新
@Update({"<script>","UPDATE users","<set>"," <if test='name != null'>name=#{name},</if>"," <if test='email != null'>email=#{email},</if>","</set>","WHERE id=#{id}","</script>"
})
int updateUserSelective(User user);
2.4 @Delete:删除操作
@Delete("DELETE FROM users WHERE id = #{id}")
int deleteUser(int id);// 批量删除
@Delete({"<script>","DELETE FROM users WHERE id IN","<foreach item='id' collection='ids' open='(' separator=',' close=')'>","#{id}","</foreach>","</script>"
})
int deleteUsersByIds(@Param("ids") List<Integer> ids);
三、结果映射注解
3.1 @Results与@Result
@Results(id = "userResultMap", value = {@Result(property = "id", column = "id", id = true),@Result(property = "name", column = "user_name"),@Result(property = "email", column = "user_email"),@Result(property = "createTime", column = "create_time", typeHandler = LocalDateTimeTypeHandler.class)
})
@Select("SELECT * FROM users WHERE id = #{id}")
User getUserWithResultMap(int id);
3.2 @ResultMap复用映射
@ResultMap("userResultMap")
@Select("SELECT * FROM users")
List<User> getAllUsers();
3.3 嵌套结果映射
@Results({@Result(property = "id", column = "id"),@Result(property = "orders", column = "id",many = @Many(select = "com.example.mapper.OrderMapper.findByUserId"))
})
@Select("SELECT * FROM users WHERE id = #{id}")
User getUserWithOrders(int id);
四、关系映射注解
4.1 @One:一对一映射
public class Order {private Integer id;private String orderNo;@Result(property = "user", column = "user_id",one = @One(select = "com.example.mapper.UserMapper.getUserById"))private User user;
}@Select("SELECT * FROM orders WHERE id = #{id}")
Order getOrderWithUser(int id);
4.2 @Many:一对多映射
public class User {private Integer id;private String name;@Result(property = "orders", column = "id",many = @Many(select = "com.example.mapper.OrderMapper.findByUserId"))private List<Order> orders;
}@Select("SELECT * FROM orders WHERE user_id = #{userId}")
List<Order> findByUserId(int userId);
4.3 嵌套查询 vs 嵌套结果
特性 | @One/@Many(嵌套查询) | 嵌套结果(JOIN查询) |
---|---|---|
执行方式 | 执行多次SQL | 执行一次SQL |
性能 | N+1查询问题 | 一次查询获取所有数据 |
适用场景 | 简单关系/数据量小 | 复杂关系/大数据量 |
灵活性 | 高(独立查询) | 低(需设计JOIN) |
代码量 | 少 | 多(需自定义结果映射) |
五、动态SQL注解
5.1 @SelectProvider
public class UserSqlProvider {public String findUsers(Map<String, Object> params) {return new SQL() {{SELECT("*");FROM("users");if (params.get("name") != null) {WHERE("name LIKE #{name}");}if (params.get("minAge") != null) {WHERE("age >= #{minAge}");}if (params.get("maxAge") != null) {WHERE("age <= #{maxAge}");}ORDER_BY("id DESC");}}.toString();}
}@SelectProvider(type = UserSqlProvider.class, method = "findUsers")
List<User> findUsers(Map<String, Object> params);
5.2 @InsertProvider
public class UserSqlProvider {public String insertUser(User user) {return new SQL() {{INSERT_INTO("users");if (user.getName() != null) {VALUES("name", "#{name}");}if (user.getEmail() != null) {VALUES("email", "#{email}");}if (user.getAge() > 0) {VALUES("age", "#{age}");}}}.toString();}
}@InsertProvider(type = UserSqlProvider.class, method = "insertUser")
int insertUserDynamic(User user);
5.3 动态SQL构建技巧
// 条件过滤
public String findActiveUsers(Map<String, Object> params) {SQL sql = new SQL().SELECT("*").FROM("users");if (params.containsKey("active")) {boolean active = (Boolean) params.get("active");if (active) {sql.WHERE("status = 1");} else {sql.WHERE("status = 0");}}if (params.containsKey("role")) {String role = (String) params.get("role");sql.WHERE("role = #{role}");}return sql.toString();
}
六、参数处理注解
6.1 @Param:多参数命名
@Select("SELECT * FROM users WHERE name = #{name} AND age = #{age}")
User findByNameAndAge(@Param("name") String name, @Param("age") int age
);
6.2 集合参数处理
@Select({"<script>","SELECT * FROM users WHERE id IN"," <foreach item='id' collection='ids' open='(' separator=',' close=')'>"," #{id}"," </foreach>","</script>"
})
List<User> findByIds(@Param("ids") List<Integer> ids);
6.3 Map参数处理
@SelectProvider(type = UserSqlProvider.class, method = "findByMap")
List<User> findByMap(Map<String, Object> params);
七、高级特性注解
7.1 @Options:执行选项配置
@Insert("INSERT INTO logs(content) VALUES(#{content})")
@Options(useGeneratedKeys = true, keyProperty = "id", keyColumn = "id")
int insertLog(Log log);// 超时设置
@Select("SELECT * FROM large_table")
@Options(timeout = 30) // 30秒超时
List<Record> getLargeData();// 缓存配置
@Select("SELECT * FROM configs")
@Options(useCache = false, flushCache = Options.FlushCachePolicy.TRUE)
List<Config> getConfigsNoCache();
7.2 @SelectKey:非自增主键获取
@Insert("INSERT INTO orders(order_no) VALUES(#{orderNo})")
@SelectKey(statement = "SELECT LAST_INSERT_ID()", keyProperty = "id", resultType = int.class, before = false)
int insertOrder(Order order);
7.3 @Flush:强制刷新语句
@Flush
List<BatchResult> flush();
八、注解与XML混合使用
8.1 XML中引用注解映射
<!-- UserMapper.xml -->
<resultMap id="userMap" type="User"><id property="id" column="id"/><result property="name" column="name"/><!-- 引用注解定义的映射 --><association property="department" select="com.example.mapper.DepartmentMapper.getById"column="dept_id"/>
</resultMap>
8.2 注解中引用XML映射
@ResultMap("com.example.mapper.UserMapper.userMap")
@Select("SELECT * FROM users WHERE id = #{id}")
User getUserById(int id);
8.3 混合使用最佳实践
- 基础CRUD:使用注解
- 复杂动态SQL:使用XML
- 结果映射:优先使用注解
- 关联查询:简单关系用注解,复杂关系用XML
- SQL重用:公共SQL片段使用XML定义
九、Spring Boot集成实践
9.1 配置MyBatis注解扫描
@Configuration
@MapperScan("com.example.mapper")
public class MyBatisConfig {@Beanpublic SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();sessionFactory.setDataSource(dataSource);// 配置其他属性return sessionFactory.getObject();}
}
9.2 事务管理配置
@Configuration
@EnableTransactionManagement
public class TransactionConfig {@Beanpublic PlatformTransactionManager transactionManager(DataSource dataSource) {return new DataSourceTransactionManager(dataSource);}
}// 使用事务
@Service
public class UserService {@Transactionalpublic void createUserWithProfile(User user, Profile profile) {userMapper.insert(user);profileMapper.insert(profile);}
}
9.3 分页集成(PageHelper)
@Select("SELECT * FROM users")
Page<User> getUsersByPage();// 控制器中使用
@GetMapping("/users")
public PageInfo<User> getUsers(@RequestParam(defaultValue = "1") int page,@RequestParam(defaultValue = "10") int size) {PageHelper.startPage(page, size);List<User> users = userMapper.getUsersByPage();return new PageInfo<>(users);
}
十、性能优化与最佳实践
10.1 注解开发性能陷阱
-
N+1查询问题:
// 当获取多个用户时,每个用户都会触发订单查询 @Select("SELECT * FROM users") @Results({@Result(property = "orders", column = "id",many = @Many(select = "findOrdersByUserId")) List<User> getAllUsersWithOrders();
解决方案:
- 使用JOIN查询代替嵌套查询
- 开启二级缓存
- 使用@ResultMap结合XML配置
-
大结果集内存溢出:
// 查询百万级数据 @Select("SELECT * FROM huge_table") List<HugeRecord> getAllHugeRecords();
解决方案:
- 使用分页查询
- 使用游标查询
@Select("SELECT * FROM huge_table") @Options(resultSetType = ResultSetType.FORWARD_ONLY, fetchSize = 1000) void streamHugeRecords(ResultHandler<HugeRecord> handler);
10.2 缓存优化策略
// 开启二级缓存
@CacheNamespace(implementation = MybatisRedisCache.class, eviction = LruCache.class,size = 1024)
public interface UserMapper {// ...
}// 缓存配置示例
public class MybatisRedisCache implements Cache {// 实现Redis缓存逻辑
}// 方法级缓存控制
@Select("SELECT * FROM users WHERE id = #{id}")
@Options(useCache = true, flushCache = Options.FlushCachePolicy.FALSE)
User getUserById(int id);
10.3 最佳实践总结
-
项目结构规划:
src/main/java └── com.example├── config├── controller├── service├── mapper│ ├── UserMapper.java│ ├── OrderMapper.java│ └── sqlprovider│ └── UserSqlProvider.java└── model
-
注解使用原则:
- 简单SQL使用@Select/@Insert等直接注解
- 动态SQL使用@*Provider
- 复杂关联查询使用XML
- 公共结果映射使用@ResultMap
-
性能优化点:
- 避免N+1查询
- 批量操作使用foreach
- 大数据集使用流式处理
- 合理配置缓存
-
团队协作规范:
- 统一SQL风格(关键字大写)
- 参数使用@Param明确命名
- 复杂SQL添加注释
- 提供SQL Provider的单元测试
十一、MyBatis注解进阶技巧
11.1 自定义类型处理器
// 枚举类型处理器
@MappedTypes(UserStatus.class)
@MappedJdbcTypes(JdbcType.INTEGER)
public class UserStatusTypeHandler extends BaseTypeHandler<UserStatus> {@Overridepublic void setNonNullParameter(PreparedStatement ps, int i, UserStatus parameter, JdbcType jdbcType) {ps.setInt(i, parameter.getCode());}@Overridepublic UserStatus getNullableResult(ResultSet rs, String columnName) {int code = rs.getInt(columnName);return UserStatus.fromCode(code);}
}// 在Mapper中使用
@Results({@Result(property = "status", column = "status", typeHandler = UserStatusTypeHandler.class)
})
@Select("SELECT * FROM users")
List<User> getAllUsers();
11.2 插件开发(拦截器)
@Intercepts({@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
})
public class QueryMetricsInterceptor implements Interceptor {@Overridepublic Object intercept(Invocation invocation) throws Throwable {long start = System.currentTimeMillis();Object result = invocation.proceed();long end = System.currentTimeMillis();MappedStatement ms = (MappedStatement) invocation.getArgs()[0];String method = ms.getId();System.out.println(method + " executed in " + (end - start) + "ms");return result;}
}// 注册拦截器
@Configuration
public class MyBatisConfig {@Beanpublic QueryMetricsInterceptor queryMetricsInterceptor() {return new QueryMetricsInterceptor();}
}
11.3 多数据源支持
// 主数据源配置
@Configuration
@MapperScan(basePackages = "com.example.mapper.primary", sqlSessionFactoryRef = "primarySqlSessionFactory")
public class PrimaryDataSourceConfig {@Bean@ConfigurationProperties("spring.datasource.primary")public DataSource primaryDataSource() {return DataSourceBuilder.create().build();}@Beanpublic SqlSessionFactory primarySqlSessionFactory(@Qualifier("primaryDataSource") DataSource dataSource) throws Exception {SqlSessionFactoryBean bean = new SqlSessionFactoryBean();bean.setDataSource(dataSource);return bean.getObject();}
}// 从数据源配置
@Configuration
@MapperScan(basePackages = "com.example.mapper.secondary", sqlSessionFactoryRef = "secondarySqlSessionFactory")
public class SecondaryDataSourceConfig {@Bean@ConfigurationProperties("spring.datasource.secondary")public DataSource secondaryDataSource() {return DataSourceBuilder.create().build();}@Beanpublic SqlSessionFactory secondarySqlSessionFactory(@Qualifier("secondaryDataSource") DataSource dataSource) throws Exception {SqlSessionFactoryBean bean = new SqlSessionFactoryBean();bean.setDataSource(dataSource);return bean.getObject();}
}
十二、总结
12.1 MyBatis注解核心价值
- 开发效率提升:减少XML配置,加速开发流程
- 代码可读性增强:SQL与Java代码共存
- 维护成本降低:修改点集中,无需多文件切换
- 类型安全保障:编译时检查SQL参数和结果
- 灵活扩展能力:支持自定义TypeHandler、Plugin
12.2 注解适用场景矩阵
项目类型 | 适用度 | 推荐策略 |
---|---|---|
小型项目/微服务 | ★★★★★ | 全注解开发 |
中型后台系统 | ★★★★☆ | 注解为主,复杂SQL用XML |
大型复杂系统 | ★★★☆☆ | XML为主,简单CRUD用注解 |
快速原型开发 | ★★★★★ | 全注解开发 |
遗留系统改造 | ★★☆☆☆ | 逐步引入,混合使用 |
12.3 未来发展趋势
- Kotlin DSL支持:更简洁的SQL构建方式
- 响应式集成:与Spring WebFlux深度整合
- GraalVM原生支持:提升启动速度和内存效率
- AI辅助SQL生成:智能SQL编写建议
- 云原生适配:更好支持Serverless架构
掌握MyBatis注解开发,不仅是学习一种技术手段,更是提升开发效率的关键技能。合理运用注解与XML的组合,根据项目需求选择最佳实践,方能在持久层开发中游刃有余。