自己如何做公司网站朗读者外国人做的汉字网站
自己如何做公司网站,朗读者外国人做的汉字网站,wordpress用redis,如何请人做网站1. MyBatis 动态 SQL 概述MyBatis 是一款优秀的持久层框架#xff0c;它支持定制化 SQL、存储过程以及高级映射。在实际开发中#xff0c;我们常常需要根据不同的条件拼接复杂的 SQL 语句#xff0c;例如多条件查询、动态更新字段、批量插入等。如果手动拼接 SQL#xff0c…1. MyBatis 动态 SQL 概述MyBatis 是一款优秀的持久层框架它支持定制化 SQL、存储过程以及高级映射。在实际开发中我们常常需要根据不同的条件拼接复杂的 SQL 语句例如多条件查询、动态更新字段、批量插入等。如果手动拼接 SQL不仅繁琐而且容易出错更重要的是可能面临 SQL 注入的风险。MyBatis 的动态 SQL 功能正是为了解决这一问题而设计的。它通过提供一系列 XML 标签或注解让我们能够以声明式的方式编写动态 SQL框架会在运行时根据传入的参数动态生成最终的 SQL 语句。这大大简化了开发工作提高了代码的可维护性和安全性。动态 SQL 的核心思想根据不同的条件动态地组装 SQL 语句的不同部分。MyBatis 提供了以下几种主要的动态 SQL 元素ifchoose(when, otherwise)trim(where, set)foreachbindsql/include这些元素都基于 OGNL (Object-Graph Navigation Language) 表达式来判断条件是否成立。本文将从基础到进阶通过大量实例带你彻底掌握 MyBatis 动态 SQL 的使用。无论你是初学者还是有一定经验的开发者相信都能从中获益。2. 环境准备在开始示例之前我们先搭建一个简单的演示环境。2.1 数据库表结构假设我们有一张用户表user结构如下sqlCREATE TABLE user ( id bigint(20) NOT NULL AUTO_INCREMENT, name varchar(50) DEFAULT NULL COMMENT 姓名, age int(11) DEFAULT NULL COMMENT 年龄, email varchar(100) DEFAULT NULL COMMENT 邮箱, create_time datetime DEFAULT NULL COMMENT 创建时间, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;2.2 对应的实体类javapublic class User { private Long id; private String name; private Integer age; private String email; private Date createTime; // 省略 getter 和 setter }2.3 Mapper 接口javapublic interface UserMapper { // 后续动态 SQL 对应的方法 ListUser selectByCondition(User user); int updateByIdSelective(User user); int insertBatch(ListUser users); // ... 其他方法 }2.4 MyBatis 配置文件确保 MyBatis 配置正确并且开启了驼峰命名映射可选xmlsettings setting namemapUnderscoreToCamelCase valuetrue/ /settings接下来我们正式进入动态 SQL 的学习。3. if 元素条件判断if是最常用的动态 SQL 元素它可以根据条件包含 SQL 片段。用法类似于 Java 中的 if 语句。典型场景多条件查询当某个条件不为空时才将该条件加入到 WHERE 子句中。示例根据传入的 User 对象中的非空属性进行查询。xmlselect idselectByCondition resultTypecom.example.entity.User SELECT * FROM user WHERE 11 if testname ! null and name ! AND name LIKE CONCAT(%, #{name}, %) /if if testage ! null AND age #{age} /if if testemail ! null and email ! AND email #{email} /if /select说明test属性中写 OGNL 表达式返回值必须是布尔类型。name ! null and name ! 是常见的字符串非空判断。WHERE 11是一个技巧可以保证后续的 AND 都能正确拼接后面我们会介绍更好的方式where 元素。LIKE CONCAT(%, #{name}, %)使用 CONCAT 函数拼接模糊查询条件避免手动拼接可能引发的 SQL 注入。执行示例调用userMapper.selectByCondition(user)假设 user 对象中只有name和age有值生成的 SQL 为sqlSELECT * FROM user WHERE 11 AND name LIKE CONCAT(%, ?, %) AND age ?如果所有属性都为空则只查询SELECT * FROM user WHERE 11相当于全表查询实际中可能需要处理。注意事项在if标签内部可以直接使用参数对象的属性无需加#{}OGNL 表达式直接访问属性。对于数值类型判断是否为 null 即可对于字符串类型通常还需要判断空字符串。注意 SQL 关键字如 AND的位置确保拼接后语法正确。4. choose、when、otherwise 元素多条件分支类似于 Java 中的switch语句choose元素可以从多个条件中选择一个。它包含when和otherwise子元素。典型场景按照优先级进行查询例如如果有姓名条件则按姓名查否则如果有年龄条件则按年龄查否则查询所有 email 不为空的人。xmlselect idselectByChoose resultTypecom.example.entity.User SELECT * FROM user WHERE 11 choose when testname ! null and name ! AND name #{name} /when when testage ! null AND age #{age} /when otherwise AND email IS NOT NULL /otherwise /choose /select说明choose标签中至少有一个when最多一个otherwise。当第一个when条件成立时只会执行该when中的 SQL 片段后续的when和otherwise不再判断。如果所有when都不成立则执行otherwise中的内容。执行示例若name不为空则 SQL 为SELECT * FROM user WHERE 11 AND name ?若name为空但age不为空则 SQL 为SELECT * FROM user WHERE 11 AND age ?若name和age都为空则 SQL 为SELECT * FROM user WHERE 11 AND email IS NOT NULL注意choose是“只选其一”的逻辑与if的“多选多”不同。5. trim、where、set 元素动态处理SQL片段在使用if进行条件拼接时我们常常需要处理多余的 AND 或 OR 关键字或者处理 SET 子句中的多余逗号。where、set和trim就是为此而生的。5.1 where 元素where标签可以自动处理第一个条件的 AND/OR 前缀。当标签内至少有一个条件成立时它会动态地插入WHERE关键字并智能地去掉首个条件的AND或OR。优化前面的查询示例xmlselect idselectByCondition resultTypecom.example.entity.User SELECT * FROM user where if testname ! null and name ! AND name LIKE CONCAT(%, #{name}, %) /if if testage ! null AND age #{age} /if if testemail ! null and email ! AND email #{email} /if /where /select执行分析如果只有一个条件成立例如name不为空则生成的 SQL 为sqlSELECT * FROM user WHERE name LIKE CONCAT(%, ?, %)注意AND被自动去掉了。如果多个条件成立where标签会去掉第一个ANDsqlSELECT * FROM user WHERE name LIKE CONCAT(%, ?, %) AND age ?如果没有条件成立where标签不会生成WHERE子句sqlSELECT * FROM user原理where标签会判断内部是否有内容如果有则插入WHERE并去除内部第一个AND或OR如果有的话。5.2 set 元素set标签用于动态更新语句它可以动态地生成SET子句并智能地去掉最后一个多余的逗号。典型场景更新用户信息只更新传入的非空字段。xmlupdate idupdateByIdSelective parameterTypecom.example.entity.User UPDATE user set if testname ! null and name ! name #{name}, /if if testage ! null age #{age}, /if if testemail ! null and email ! email #{email}, /if if testcreateTime ! null create_time #{createTime}, /if /set WHERE id #{id} /update说明set标签会动态地插入SET关键字。它会自动去除最后一个条件后面的逗号如果有。如果没有任何条件成立set标签不会生成任何内容这时可能会导致 SQL 语法错误例如UPDATE user WHERE id ?因此在实际中通常至少保证有一个更新字段或者通过业务逻辑保证。执行示例如果只有name和age不为空生成的 SQL 为sqlUPDATE user SET name ?, age ? WHERE id ?注意age ?后面的逗号被自动去除了。5.3 trim 元素trim元素是where和set的底层实现它提供了更通用的前后缀处理能力。我们可以用trim自定义任何需要添加前缀、后缀并覆盖特定内容的场景。trim标签有四个主要属性prefix当标签内有内容时添加的前缀例如WHERE、SET。suffix当标签内有内容时添加的后缀。prefixOverrides忽略去除内容中匹配的前缀例如AND、OR。suffixOverrides忽略去除内容中匹配的后缀例如逗号,。用 trim 模拟 wherexmlselect idselectByCondition resultTypecom.example.entity.User SELECT * FROM user trim prefixWHERE prefixOverridesAND |OR if testname ! null and name ! AND name LIKE CONCAT(%, #{name}, %) /if if testage ! null AND age #{age} /if /trim /select用 trim 模拟 setxmlupdate idupdateByIdSelective parameterTypecom.example.entity.User UPDATE user trim prefixSET suffixOverrides, if testname ! null and name ! name #{name}, /if if testage ! null age #{age}, /if /trim WHERE id #{id} /update说明prefixOverrides中的多个值用|分隔注意空格也要包含例如AND |OR表示去除AND或OR以及后面的一个空格。suffixOverrides,表示去除内容末尾可能出现的逗号。使用trim可以让我们完全控制动态 SQL 片段的前后缀非常灵活。6. foreach 元素循环遍历集合foreach用于遍历集合List、Set、数组或 Map通常用于构建 IN 条件或批量操作。常用属性collection要遍历的集合/数组/Map 的名称。注意区分传入参数的类型如果传入的是 List 类型默认 key 为list如果传入的是数组默认 key 为array如果传入的是 Mapkey 为 map 中对应的 key。也可以在接口方法上使用Param指定名称推荐这种方式。item每次迭代的当前元素别名。index当前迭代的索引从0开始对于 Map 是键。open整个循环体开始拼接的字符串如(。close整个循环体结束拼接的字符串如)。separator每次迭代之间的分隔符如,。6.1 使用 IN 查询xmlselect idselectByIds resultTypecom.example.entity.User SELECT * FROM user WHERE id IN foreach collectionids itemid open( separator, close) #{id} /foreach /select对应的 Mapper 方法javaListUser selectByIds(Param(ids) ListLong ids);如果传入ids [1, 2, 3]生成的 SQL 为sqlSELECT * FROM user WHERE id IN (1,2,3)6.2 批量插入xmlinsert idinsertBatch INSERT INTO user (name, age, email, create_time) VALUES foreach collectionlist itemuser separator, (#{user.name}, #{user.age}, #{user.email}, #{user.createTime}) /foreach /insertMapper 方法javaint insertBatch(Param(list) ListUser users);注意这里collection写的是list因为 MyBatis 默认将 List 类型的参数命名为list。更明确的写法是使用Param(users)然后collectionusers。6.3 批量更新不同条件批量更新通常需要多条 SQL 语句可以使用foreach生成多条语句但要注意数据库连接是否允许多条语句执行需在连接 URL 中添加allowMultiQueriestrue。xmlupdate idupdateBatch parameterTypelist foreach collectionlist itemuser separator; UPDATE user set if testuser.name ! nullname #{user.name},/if if testuser.age ! nullage #{user.age},/if /set WHERE id #{user.id} /foreach /update这种方式生成的 SQL 类似于sqlUPDATE user SET name ? WHERE id ? ; UPDATE user SET age ? WHERE id ? ;注意事项大批量操作时注意性能可以分段执行。确保分隔符separator正确避免 SQL 语法错误。对于复杂的批量更新可以考虑使用批量执行器BatchExecutor或者使用其他框架特性。6.4 遍历 Map当参数是 Map 类型时foreach可以遍历 Map 的 entry。xmlselect idselectByMap resultTypecom.example.entity.User SELECT * FROM user WHERE foreach collection_parameter indexkey itemval separator AND ${key} #{val} /foreach /select这里使用${key}直接注入列名注意 SQL 注入风险#{val}是参数值。通常不推荐直接使用${}除非你能保证 key 的安全性。7. bind 元素绑定变量bind标签允许我们在当前上下文中创建一个变量并绑定一个值通常用于处理模糊查询的拼接或者复用一些表达式。示例模糊查询的两种写法之前我们使用CONCAT函数但如果在某些数据库如 Oracle中字符串拼接方式不同可以使用bind进行兼容处理。xmlselect idselectByLike resultTypecom.example.entity.User bind namepattern value% name % / SELECT * FROM user WHERE name LIKE #{pattern} /selectMapper 方法javaListUser selectByLike(Param(name) String name);这里value属性是 OGNL 表达式它将传入的name参数前后加上%并赋值给变量pattern。然后在 SQL 中使用#{pattern}引用。优点将拼接逻辑放在 XML 中更清晰且可以在多个地方复用。还可以结合 if 使用xmlif testname ! null bind namenameLike value% name %/ AND name LIKE #{nameLike} /if注意事项bind标签定义的位置通常在其后的 SQL 片段之前一般放在 SQL 语句的最前面。变量名不要和已有参数名冲突。8. sql 与 include 片段复用在编写 SQL 时我们常常会遇到一些重复的 SQL 片段比如查询的列、通用的 WHERE 条件等。MyBatis 提供了sql标签来定义可重用的片段然后通过include标签引入。8.1 定义 SQL 片段xmlsql idbaseColumns id, name, age, email, create_time /sql sql idwhereCondition where if testname ! null and name ! AND name LIKE CONCAT(%, #{name}, %) /if if testage ! null AND age #{age} /if /where /sql8.2 引入 SQL 片段xmlselect idselectByCondition resultTypecom.example.entity.User SELECT include refidbaseColumns/ FROM user include refidwhereCondition/ /select8.3 向片段传递参数include中可以包含自定义属性这些属性可以在片段内部通过${prop}使用注意是字符串替换不是预编译。示例定义一个支持别名的列片段。xmlsql idbaseColumnsWithAlias if test_databaseId mysql id, name, age /if if test_databaseId oracle id, name, age /if !-- 还可以使用 include 传入的属性 -- ${alias}.id, ${alias}.name, ${alias}.age /sql select idselectWithAlias resultTypecom.example.entity.User SELECT include refidbaseColumnsWithAlias property namealias valueu/ /include FROM user u /select注意property标签定义的属性只能用于替换${}不能用于#{}。且属性值不需要引号MyBatis 会直接替换。8.4 结合动态属性在 MyBatis 3.5.2 之后include还支持动态传递属性例如xmlsql idsomeSql ${prefix}Table /sql select idselectSome SELECT * FROM include refidsomeSql property nameprefix valueuser/ /include /select最佳实践合理使用sql和include可以极大减少重复代码提高可维护性。9. 动态 SQL 在注解中的使用虽然 XML 是 MyBatis 动态 SQL 的主要阵地但 MyBatis 也支持在注解中使用动态 SQL主要通过SelectProvider、InsertProvider、UpdateProvider、DeleteProvider等注解来指定一个类的方法该方法动态生成 SQL 字符串。这种方式适用于简单场景或者习惯使用注解的开发人员但复杂的动态 SQL 推荐使用 XML可读性和维护性更好。示例使用 Provider 实现动态查询创建一个 Provider 类javapublic class UserSqlProvider { public String selectByCondition(User user) { return new SQL() {{ SELECT(id, name, age, email, create_time); FROM(user); if (user.getName() ! null !user.getName().isEmpty()) { WHERE(name like concat(%, #{name}, %)); } if (user.getAge() ! null) { WHERE(age #{age}); } }}.toString(); } }在 Mapper 接口中引用javaSelectProvider(type UserSqlProvider.class, method selectByCondition) ListUser selectByCondition(User user);这里使用了 MyBatis 提供的SQL类来构建 SQL避免了手动拼接字符串的繁琐。也可以直接返回一个字符串但SQL类更安全。注意事项Provider 方法返回的 SQL 中参数占位符使用#{property}MyBatis 在执行时会自动替换。动态条件需自行处理可以使用 Java 代码控制。对于复杂逻辑Provider 类可能会变得臃肿。注解中使用脚本script从 MyBatis 3.4 开始也可以在注解中使用script标签包裹 XML 格式的动态 SQL。例如javaSelect(script SELECT * FROM user where if testname ! null AND name #{name}/if /where /script) ListUser selectByCondition(User user);这种方式结合了注解的简洁和 XML 的灵活性但要注意字符串中的引号转义。10. 高级动态 SQL 与 OGNL 表达式动态 SQL 的test属性中写的是 OGNL 表达式。OGNL 是 Apache 的一个表达式语言功能强大。熟悉 OGNL 可以帮助我们写出更灵活的条件判断。10.1 OGNL 常用操作访问属性直接写属性名如name、user.age。方法调用可以调用对象的方法例如name.length()、list.isEmpty()。静态方法可以使用classmethod(args)调用静态方法如java.lang.Mathrandom()。静态属性classfield。操作符算术,-,*,/,%逻辑and,or,not关系,!,,,,字符串连接例如% name %三元运算符condition ? expr1 : expr2集合操作list[index]、map[key]、list.contains(value)等。特殊对象MyBatis 在 OGNL 上下文中添加了几个隐含对象_parameter整个参数对象单个参数就是该对象多个参数时是一个 Map。_databaseId当前数据库的标识在配置了 databaseIdProvider 时可用。还有一些如org.apache.ibatis.scripting.xmltags.OgnlCacheMETHOD等。10.2 复杂条件示例判断数组/集合是否为空xmlif testlist ! null and list.size() 0 AND id IN foreach collectionlist itemid open( separator, close) #{id} /foreach /if判断字符串是否以某个前缀开头xmlif testname ! null and name.indexOf(张) 0 AND name #{name} /if使用静态方法如判断是否为数字xmlif testcom.example.util.StringUtilsisNumeric(age.toString()) AND age #{age} /if嵌套属性访问xmlif testuser.address.city ! null AND city #{user.address.city} /if10.3 OGNL 与 bind 结合bind的value属性也是一个 OGNL 表达式可以完成更复杂的计算然后将结果绑定给变量。xmlbind nameageRangeStart valueage - 5/ bind nameageRangeEnd valueage 5/ AND age BETWEEN #{ageRangeStart} AND #{ageRangeEnd}注意事项OGNL 表达式中的字符串比较要注意 null 处理避免空指针。例如name admin当 name 为 null 时不会抛异常OGNL 会处理。11. 多数据库支持与动态SQL实际项目中可能面临多数据库适配的问题不同数据库的 SQL 语法有差异如分页、函数名等。MyBatis 提供了databaseIdProvider来支持多数据库并且可以在动态 SQL 中使用_databaseId来区分。11.1 配置 databaseIdProvider在 MyBatis 配置文件或 Spring 整合中中配置xmldatabaseIdProvider typeDB_VENDOR property nameMySQL valuemysql/ property nameOracle valueoracle/ property nameSQL Server valuesqlserver/ /databaseIdProviderMyBatis 会根据 DatabaseMetaData 获取数据库产品名称并映射为对应的值如 mysql、oracle。11.2 在动态 SQL 中使用 _databaseId示例分页语句适配xmlselect idselectPage resultTypecom.example.entity.User if test_databaseId mysql SELECT * FROM user LIMIT #{offset}, #{limit} /if if test_databaseId oracle SELECT * FROM ( SELECT rownum rn, t.* FROM ( SELECT * FROM user ) t WHERE rownum lt; #{offset} #{limit} ) WHERE rn #{offset} /if /select使用 choose 更加清晰xmlselect idselectPage resultTypecom.example.entity.User SELECT * FROM user choose when test_databaseId mysql LIMIT #{offset}, #{limit} /when when test_databaseId oracle !-- Oracle 分页写法 -- /when /choose /select11.3 在 sql 片段中使用 _databaseId可以为不同数据库定义不同的列片段xmlsql idcolumns choose when test_databaseId mysql id, name, age /when when test_databaseId oracle id, name, nvl(age, 0) as age /when /choose /sql注意_databaseId是 MyBatis 内置变量在 XML 动态 SQL 的test中可以直接使用。12. 动态 SQL 的底层原理浅析理解 MyBatis 动态 SQL 的底层原理有助于我们更好地使用它并且在出现问题时能快速定位。12.1 SQL 解析过程解析配置文件MyBatis 启动时解析 XML 配置文件包括 Mapper XML将每个 SQL 映射语句解析为一个MappedStatement对象。对于包含动态 SQL 的语句会将 SQL 内容解析为一个SqlSource。动态 SQL 节点在解析过程中MyBatis 会将 XML 中的每个标签如if、where解析为对应的SqlNode对象。例如if对应IfSqlNodewhere对应WhereSqlNode继承TrimSqlNode。这些SqlNode形成一个树状结构。参数绑定与执行当执行查询时MyBatis 会调用SqlSource的getBoundSql(Object parameterObject)方法。该方法会遍历所有SqlNode调用其apply()方法根据传入的参数动态判断是否需要拼接该节点对应的 SQL 片段并同时记录参数映射。最终生成一个完整的 SQL 字符串和对应的参数列表。12.2 核心接口与类SqlNode动态 SQL 节点的接口定义boolean apply(DynamicContext context)方法。apply()返回true表示节点有内容并将 SQL 片段追加到DynamicContext中。DynamicContext贯穿整个动态 SQL 生成过程的上下文内部维护一个StringBuilder用于拼接 SQL以及一个绑定的参数列表。SqlSource表示一个 SQL 的来源分为静态StaticSqlSource和动态DynamicSqlSource、RawSqlSource。动态的会每次执行时重新解析。12.3 示例IfSqlNode 的实现简化后的IfSqlNode大致如下javapublic class IfSqlNode implements SqlNode { private final ExpressionEvaluator evaluator; private final String test; private final SqlNode contents; public boolean apply(DynamicContext context) { if (evaluator.evaluateBoolean(test, context.getBindings())) { contents.apply(context); return true; } return false; } }它首先评估test表达式是否为真如果为真则继续执行子节点contents的apply方法。12.4 动态 SQL 生成的缓存为了提高性能MyBatis 会缓存动态 SQL 生成的结果吗对于动态 SQL含有${}或动态标签每次执行时都需要重新解析因为参数不同可能导致 SQL 结构不同。但对于只包含#{}的静态 SQL只会解析一次。因此动态 SQL 的开销略大但一般可以忽略。理解这些原理可以帮助我们更好地调试复杂 SQL以及优化性能瓶颈。13. 常见问题与最佳实践13.1 常见问题Q1: 动态 SQL 中的 AND/OR 位置问题导致 SQL 语法错误原因在if标签中如果第一个条件不满足而第二个条件满足可能会出现多余的 AND/OR 前缀或者缺少前缀。解决方法使用where或trim标签自动处理前缀。Q2: 集合参数名为list还是array如果直接传入 ListMyBatis 默认将参数命名为list直接传入数组命名为array。建议使用Param明确指定名称避免混淆。Q3: 动态 SQL 中#{}和${}的区别#{}是预编译参数占位符会生成?并设置参数值安全防止 SQL 注入。${}是字符串直接替换存在 SQL 注入风险仅用于表名、列名等动态部分且必须确保传入值安全如使用白名单校验。Q4: 模糊查询中 % 的位置可以在 Java 代码中拼接好传入也可以在 XML 中用bind或CONCAT函数处理。建议在 XML 中处理保持 Java 层的简洁。Q5: 批量插入性能问题对于大批量数据MyBatis 的 foreach 生成多条 values 的 insert 语句可能受数据库 SQL 长度限制。可以考虑分段插入或者使用 MyBatis 的 BatchExecutor通过 SqlSession 的 flush 机制。13.2 最佳实践尽量使用where、set、trim避免手动拼接WHERE 11使 SQL 更优雅。合理使用bind将参数处理逻辑集中在 XML 中提高可读性。对集合类型使用Param注解明确参数名避免依赖于默认规则。警惕${}注入原则上禁止在动态条件值中使用${}除非动态表名/列名且做好安全检查。复杂动态 SQL 优先使用 XMLXML 格式更清晰支持注释易于维护。注解适用于简单场景。善用 OGNL 表达式学习常用的 OGNL 语法可以让条件判断更简洁。使用databaseId处理多数据库差异将差异封装在同一个 Mapper 中保持上层调用的一致性。注意 SQL 片段复用抽取重复的列、条件通过sql和include复用减少重复代码。测试动态 SQL编写单元测试验证不同条件下生成的 SQL 是否符合预期可以通过 MyBatis 的日志输出查看。性能考虑虽然动态 SQL 开销不大但避免在循环中调用包含复杂动态 SQL 的查询。对于批量操作考虑使用批量执行器。14. 总结MyBatis 动态 SQL 是其最强大的特性之一它使我们能够以简洁优雅的方式编写灵活的 SQL 语句极大提高了开发效率和代码可维护性。本文从基础到高级详细介绍了if基础条件判断。choose/when/otherwise多选一分支。trim/where/set处理 SQL 片段的前后缀问题。foreach遍历集合用于 IN 查询或批量操作。bind变量绑定与复用。sql/includeSQL 片段复用。注解动态 SQL使用 Provider 或脚本注解。OGNL 表达式增强条件判断能力。多数据库支持利用 _databaseId 适配不同数据库。底层原理理解 SqlNode 和解析过程。最佳实践避免常见错误写出高质量的动态 SQL。