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) }) ListUser selectAllUsers();在实际项目中我建议字段名和属性名尽量保持一致可以省去这些映射配置。如果必须使用不同命名可以考虑在MyBatis配置中开启驼峰命名转换settings setting namemapUnderscoreToCamelCase valuetrue/ /settings2.3 动态SQL构建这才是Select注解真正强大的地方通过script标签我们可以在注解中使用各种动态SQL标签Select(script SELECT * FROM users where if testusername ! nullAND username #{username}/if if testemail ! nullAND email #{email}/if /where /script) ListUser 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 testusername ! nullusername #{username},/if if testemail ! nullemail #{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 collectionids itemid open( separator, close) #{id} /foreach /script) int batchUpdateStatus(Param(ids) ListInteger 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}) ListUser 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) ListUser 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比较简单项目规模较小需要快速开发原型以下情况建议使用XMLSQL非常复杂需要大量动态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 namelogImpl valueSTDOUT_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 testkeyword ! null AND (u.username LIKE CONCAT(%, #{keyword}, %) OR u.email LIKE CONCAT(%, #{keyword}, %)) /if /where ORDER BY u.create_time DESC /script) ListUser selectActiveUsers(Param(keyword) String keyword);这种写法虽然SQL被拆分成多个字符串但通过良好的格式化和注释可读性依然很高。
【Mybatis】@Select与@Update注解动态SQL实战精讲
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) }) ListUser selectAllUsers();在实际项目中我建议字段名和属性名尽量保持一致可以省去这些映射配置。如果必须使用不同命名可以考虑在MyBatis配置中开启驼峰命名转换settings setting namemapUnderscoreToCamelCase valuetrue/ /settings2.3 动态SQL构建这才是Select注解真正强大的地方通过script标签我们可以在注解中使用各种动态SQL标签Select(script SELECT * FROM users where if testusername ! nullAND username #{username}/if if testemail ! nullAND email #{email}/if /where /script) ListUser 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 testusername ! nullusername #{username},/if if testemail ! nullemail #{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 collectionids itemid open( separator, close) #{id} /foreach /script) int batchUpdateStatus(Param(ids) ListInteger 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}) ListUser 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) ListUser 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比较简单项目规模较小需要快速开发原型以下情况建议使用XMLSQL非常复杂需要大量动态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 namelogImpl valueSTDOUT_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 testkeyword ! null AND (u.username LIKE CONCAT(%, #{keyword}, %) OR u.email LIKE CONCAT(%, #{keyword}, %)) /if /where ORDER BY u.create_time DESC /script) ListUser selectActiveUsers(Param(keyword) String keyword);这种写法虽然SQL被拆分成多个字符串但通过良好的格式化和注释可读性依然很高。