1. MyBatis注解开发入门
如果你正在使用MyBatis框架,但又厌倦了XML配置文件的繁琐,那么注解开发绝对是你的福音。我刚开始接触MyBatis时,也是从XML配置入手的,后来发现注解方式简直不要太方便!特别是对于中小型项目,注解开发能显著提升编码效率。
@Select和@Update是MyBatis中最常用的两个注解,它们可以直接在Mapper接口的方法上编写SQL语句。想象一下,以前我们需要在XML文件中写SQL,然后在接口中定义方法,现在直接在接口方法上写SQL,代码量直接减半!
// 传统XML方式需要额外编写映射文件 public interface UserMapper { User getUserById(Integer id); } // 注解方式直接在接口中完成 public interface UserMapper { @Select("SELECT * FROM users WHERE id = #{id}") User getUserById(Integer id); }不过要注意,注解方式虽然方便,但在处理复杂SQL时可能会显得力不从心。我在实际项目中发现,当SQL超过5行时,可读性就会明显下降。这时候就需要用到动态SQL标签来优化了。
2. @Select注解深度解析
2.1 基础查询用法
@Select注解最基本的用法就是直接在注解中写SQL语句。我刚开始用时觉得特别神奇,Java代码里直接写SQL,还能自动映射结果!
@Select("SELECT id, username, email FROM users WHERE id = #{userId}") User selectUserById(@Param("userId") Integer id);这里有个小技巧:使用@Param注解明确指定参数名,这样在复杂SQL中更不容易出错。我曾经因为没加这个注解调试了半天,参数名对不上导致SQL执行失败。
2.2 结果集映射
当数据库字段名和Java属性名不一致时,我们可以用@Results和@Result注解来映射:
@Select("SELECT user_id, user_name, user_email FROM users") @Results({ @Result(property = "id", column = "user_id"), @Result(property = "username", column = "user_name"), @Result(property = "email", column = "user_email") }) List<User> selectAllUsers();在实际项目中,我建议字段名和属性名尽量保持一致,可以省去这些映射配置。如果必须使用不同命名,可以考虑在MyBatis配置中开启驼峰命名转换:
<settings> <setting name="mapUnderscoreToCamelCase" value="true"/> </settings>2.3 动态SQL构建
这才是@Select注解真正强大的地方!通过<script>标签,我们可以在注解中使用各种动态SQL标签:
@Select("<script>" + "SELECT * FROM users " + "<where> " + " <if test='username != null'>AND username = #{username}</if> " + " <if test='email != null'>AND email = #{email}</if> " + "</where> " + "</script>") List<User> selectUsersByCondition(@Param("username") String username, @Param("email") String email);我第一次看到这种写法时觉得很别扭,SQL被拆分成多个字符串用加号连接。但习惯后发现这种写法其实很灵活,特别是配合IDE的代码格式化功能后,可读性还不错。
3. @Update注解实战技巧
3.1 基础更新操作
@Update注解的基本用法和@Select类似:
@Update("UPDATE users SET username = #{username}, email = #{email} WHERE id = #{id}") int updateUser(User user);这里要注意返回值是int类型,表示受影响的行数。在实际业务中,我建议总是检查这个返回值,确保更新操作确实生效了。
3.2 动态更新字段
在更新操作中,我们经常只需要更新部分字段。这时候<set>和<if>标签就派上用场了:
@Update("<script>" + "UPDATE users " + "<set> " + " <if test='username != null'>username = #{username},</if> " + " <if test='email != null'>email = #{email},</if> " + "</set> " + "WHERE id = #{id} " + "</script>") int updateUserSelective(User user);这里有个坑我踩过:<set>标签会自动去掉最后一个逗号,所以即使最后一个<if>条件成立,生成的SQL也是正确的。刚开始我还手动处理逗号,结果发现根本不需要。
3.3 批量更新操作
批量更新是实际项目中很常见的需求,我们可以使用<foreach>标签实现:
@Update("<script>" + "UPDATE users " + "SET status = #{status} " + "WHERE id IN " + "<foreach collection='ids' item='id' open='(' separator=',' close=')'> " + " #{id} " + "</foreach> " + "</script>") int batchUpdateStatus(@Param("ids") List<Integer> ids, @Param("status") String status);这种写法性能比在Java中循环执行单条更新要好得多。我在一个用户管理系统中使用后,批量操作耗时从秒级降到了毫秒级。
4. 高级应用与性能优化
4.1 多表关联查询
注解方式同样支持多表查询,只是SQL会变得比较长:
@Select("SELECT u.*, a.province, a.city " + "FROM users u LEFT JOIN addresses a ON u.id = a.user_id " + "WHERE u.id = #{userId}") @Results({ @Result(property = "id", column = "id"), @Result(property = "address.province", column = "province"), @Result(property = "address.city", column = "city") }) User selectUserWithAddress(@Param("userId") Integer userId);对于特别复杂的关联查询,我建议还是使用XML方式,可读性会更好。或者考虑使用@One和@Many注解实现延迟加载。
4.2 分页查询优化
分页查询在注解中的实现需要注意SQL方言问题:
// MySQL分页 @Select("SELECT * FROM users LIMIT #{offset}, #{pageSize}") List<User> selectUsersByPage(@Param("offset") int offset, @Param("pageSize") int pageSize); // Oracle分页(使用ROWNUM) @Select("<script>" + "SELECT * FROM (" + " SELECT a.*, ROWNUM rn FROM (" + " SELECT * FROM users ORDER BY id" + " ) a WHERE ROWNUM <= #{end}" + ") WHERE rn > #{start}" + "</script>") List<User> selectUsersByPageForOracle(@Param("start") int start, @Param("end") int end);在实际项目中,我推荐使用PageHelper这类分页插件,它们能自动识别数据库方言并生成合适的分页SQL。
4.3 二级缓存配置
注解方式也可以配置二级缓存,只需要在Mapper接口上添加@CacheNamespace注解:
@CacheNamespace(implementation = MyCustomCache.class, eviction = MyCustomCache.class) public interface UserMapper { // 方法定义 }关于缓存,我有过惨痛教训:在电商系统中,商品信息使用了缓存但没设置合适的过期策略,导致价格更新后前端显示的还是旧价格。所以使用缓存一定要谨慎,考虑好数据一致性问题。
5. 最佳实践与常见问题
5.1 注解与XML的选择策略
根据我的经验,以下情况适合使用注解:
- SQL比较简单
- 项目规模较小
- 需要快速开发原型
以下情况建议使用XML:
- SQL非常复杂
- 需要大量动态SQL
- 团队有专门的DBA负责SQL优化
其实两者可以混用,我在实际项目中经常这样做:简单的CRUD用注解,复杂的统计查询用XML。
5.2 SQL注入防护
注解方式同样需要注意SQL注入问题。MyBatis的#{}语法会预编译参数,是安全的。但使用${}时要特别小心:
// 安全写法 @Select("SELECT * FROM users WHERE username = #{name}") User findByUsername(@Param("name") String name); // 危险写法(可能SQL注入) @Select("SELECT * FROM ${tableName} WHERE username = #{name}") User findByUsernameDynamicTable(@Param("tableName") String tableName, @Param("name") String name);5.3 日志调试技巧
当注解SQL执行出错时,我常用的调试方法是开启MyBatis的完整日志:
<settings> <setting name="logImpl" value="STDOUT_LOGGING"/> </settings>这样可以在控制台看到实际执行的SQL和参数,非常方便排查问题。
5.4 代码组织建议
对于较长的注解SQL,我建议这样组织代码:
- 每个SQL方法保持单一职责
- 复杂SQL拆分成多行,用加号连接
- 使用IDE的代码格式化功能保持缩进
- 添加必要的注释说明SQL用途
@Select("<script>" + "/* 根据条件查询活跃用户 */ " + "SELECT u.id, u.username, u.email " + "FROM users u " + "<where> " + " u.status = 'ACTIVE' " + " <if test='keyword != null'> " + " AND (u.username LIKE CONCAT('%', #{keyword}, '%') " + " OR u.email LIKE CONCAT('%', #{keyword}, '%')) " + " </if> " + "</where> " + "ORDER BY u.create_time DESC " + "</script>") List<User> selectActiveUsers(@Param("keyword") String keyword);这种写法虽然SQL被拆分成多个字符串,但通过良好的格式化和注释,可读性依然很高。