SSM 企业轻量级开发框架之 03-动态 SQL
AI 摘要
动态 SQL 处理简单的多参数查询
if 标签
通常情况下在实现多条件查询时,需要实现动态 SQL 的构建,如果没有实现动态 SQL 的构建,可能会出现 SQL 语句错误的问题。
示例:多条件查询可能存在的问题
用户表数据访问接口(cn.duozai.dao.SysUserMapper):
public interface SysUserMapper {
// ...
/**
* 根据条件(Params)查询用户列表
*
* @param realName 真实姓名
* @param roleId 用户角色id
* @return 用户列表
*/
List<SysUser> getUserListByParams(@Param("realName") String realName, @Param("rId") Integer roleId);
// ...
}用户表数据访问接口 SQL 映射文件(cn/duozai/dao/SysUserMapper.xml):
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.duozai.dao.SysUserMapper">
<!-- ... -->
<select id="getUserListByParams" resultType="cn.duozai.entity.SysUser">
SELECT * FROM t_sys_user
WHERE realName LIKE CONCAT('%', #{realName}, '%') AND roleId = #{rId}
</select>
<!-- ... -->
</mapper>测试类(test.SysUserMapperTest):
public class SysUserMapperTest {
// ...
/**
* 根据条件(Params)查询用户列表
*
* @return void
*/
@Test
public void getUserListByParams() {
SqlSession sqlSession = MyBatisUtil.createSqlSession();
// 只查询真实姓名包含“刘”的用户,不根据用户角色id查询
List<SysUser> sysUserList = sqlSession.getMapper(SysUserMapper.class)
.getUserListByParams("刘", null);
for (SysUser sysUser : sysUserList) {
logger.debug("SysUser => " + sysUser.getRealName());
}
MyBatisUtil.closeSqlSession(sqlSession);
}
// ...
}示例效果:
查询条件不符合要求的,不应该拼接到 SQL 语句中。
在 MyBatis 中,if 标签用于实现 SQL 语句的动态拼接功能,使用 if 标签可以有效地避免多条件查询拼接 SQL 语句时存在的隐患,保证了业务的正确性。
示例:if 标签
用户表数据访问接口 SQL 映射文件(cn/duozai/dao/SysUserMapper.xml):
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.duozai.dao.SysUserMapper">
<!-- ... -->
<select id="getUserListByParams" resultType="cn.duozai.entity.SysUser">
SELECT * FROM t_sys_user
WHERE
<if test="realName != null">
realName LIKE CONCAT('%', #{realName}, '%')
</if>
<if test="rId != null">
AND roleId = #{rId}
</if>
</select>
</mapper>示例效果:
where 标签
MyBatis 提供了 where 标签,用于对 SQL 语句中的 where 关键字进行简化处理,直接替换 SQL 语句中的 where 关键字,可以智能地处理其内部多余的 and、or 等关键字,避免多余字符带来的语法错误。
示例:if + where 标签
测试类(test.SysUserMapperTest):
public class SysUserMapperTest {
// ...
/**
* 根据条件(Params)查询用户列表
*
* @return void
*/
@Test
public void getUserListByParams() {
SqlSession sqlSession = MyBatisUtil.createSqlSession();
// 只根据用户角色id查询,不根据真实姓名查询
List<SysUser> sysUserList = sqlSession.getMapper(SysUserMapper.class)
.getUserListByParams(null, 2);
for (SysUser sysUser : sysUserList) {
logger.debug("SysUser => " + sysUser.getRealName());
}
MyBatisUtil.closeSqlSession(sqlSession);
}
// ...
}示例效果:
用户表数据访问接口 SQL 映射文件(cn/duozai/dao/SysUserMapper.xml):
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.duozai.dao.SysUserMapper">
<!-- ... -->
<select id="getUserListByParams" resultType="cn.duozai.entity.SysUser">
SELECT * FROM t_sys_user
<where>
<if test="realName != null">
AND realName LIKE CONCAT('%', #{realName}, '%')
</if>
<if test="rId != null">
AND roleId = #{rId}
</if>
</where>
</select>
</mapper>示例效果:
choose + when + otherwise 标签
choose 是一个组合标签,通常与 when、otherwise 标签配合使用。
choose 标签的基本用法:
<choose>
<when test="判断条件">
...
</when>
<when test="判断条件">
...
</when>
...
<otherwise>
...
</otherwise>
</choose>choose、when、otherwise 实现了类似于 Java 中 switch 语句的功能。
示例:choose + when + otherwise 标签
用户表数据访问接口(cn.duozai.dao.SysUserMapper):
public interface SysUserMapper {
// ...
/**
* 根据条件(Params)查询用户列表,三选一
*
* @param account 账号
* @param realName 真实姓名
* @param roleId 用户角色id
* @return 用户列表
*/
List<SysUser> getUserListByChoose(@Param("account") String account, @Param("realName") String realName, @Param("rId") Integer roleId);
}用户表数据访问接口 SQL 映射文件(cn/duozai/dao/SysUserMapper.xml):
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.duozai.dao.SysUserMapper">
<!-- ... -->
<select id="getUserListByChoose" resultType="cn.duozai.entity.SysUser">
SELECT * FROM t_sys_user
<where>
<choose>
<when test="account != null">
AND account = #{account}
</when>
<when test="realName != null">
AND realName LIKE CONCAT('%', #{realName}, '%')
</when>
<otherwise>
AND roleId = #{rId}
</otherwise>
</choose>
</where>
</select>
</mapper>测试类(test.SysUserMapperTest):
public class SysUserMapperTest {
// ...
/**
* 根据条件(Params)查询用户列表,三选一
*
* @return void
*/
@Test
public void getUserListByChoose() {
SqlSession sqlSession = MyBatisUtil.createSqlSession();
List<SysUser> sysUserList = sqlSession.getMapper(SysUserMapper.class)
.getUserListByChoose("limingxing", null, 2);
for (SysUser sysUser : sysUserList) {
logger.debug("SysUser => " + sysUser.getRealName() + " roleId => " + sysUser.getRoleId() + " account => " + sysUser.getAccount());
}
MyBatisUtil.closeSqlSession(sqlSession);
}
}示例效果:
动态 SQL 处理集合参数
foreach 标签
foreach 标签用于在 SQL 语句中对集合进行迭代操作,更方便地构建出包含动态数量参数的 SQL 语句。
foreach 标签的基本用法:
<foreach collection="参数名称" item="元素别名"
open="起始位置的拼接字符" close="结束位置的拼接字符" separator="元素之间的连接符">
...#{元素别名}
</foreach>使用 foreach 标签处理数组类型参数
使用 foreach 标签可以遍历数组类型的参数,该参数在 foreach 标签中的参数名称默认为 array,可以使用 @Param 注解为参数取别名。
示例:使用 foreach 标签处理数组类型参数
用户表数据访问接口(cn.duozai.dao.SysUserMapper):
public interface SysUserMapper {
// ...
/**
* 根据用户角色id数组查询用户列表
*
* @param roleIds 用户角色id数组
* @return 用户列表
*/
List<SysUser> getUserListByRoleIdArray(@Param("roleIdsArray") Integer[] roleIds);
}用户表数据访问接口 SQL 映射文件(cn/duozai/dao/SysUserMapper.xml):
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.duozai.dao.SysUserMapper">
<!-- ... -->
<select id="getUserListByRoleIdArray" resultType="cn.duozai.entity.SysUser">
<!--
拼接SQL语句:SELECT * FROM t_sys_user WHERE roleId in (1, 2, 3)
-->
SELECT * FROM t_sys_user WHERE roleId in
<foreach collection="roleIdsArray" item="item" open="(" separator="," close=")">
#{item}
</foreach>
</select>
</mapper>测试类(test.SysUserMapperTest):
public class SysUserMapperTest {
// ...
/**
* 根据用户角色id数组查询用户列表
*
* @return void
*/
@Test
public void getUserListByRoleIdArray() {
SqlSession sqlSession = MyBatisUtil.createSqlSession();
Integer[] roleIds = {1,2};
List<SysUser> sysUserList = sqlSession.getMapper(SysUserMapper.class)
.getUserListByRoleIdArray(roleIds);
for (SysUser sysUser : sysUserList) {
System.out.println("SysUser => " + sysUser.getRealName() + " roleId => " + sysUser.getRoleId());
}
MyBatisUtil.closeSqlSession(sqlSession);
}
}示例效果:
使用 foreach 标签处理 List 类型参数
使用 foreach 标签可以遍历 List 类型的参数,该参数在 foreach 标签中的参数名称默认为 list,可以使用 @Param 注解为参数取别名。
示例:使用 foreach 标签处理 List 类型参数
用户表数据访问接口(cn.duozai.dao.SysUserMapper):
public interface SysUserMapper {
// ...
/**
* 根据用户角色id列表查询用户列表
*
* @param roleIds 用户角色id列表
* @return 用户列表
*/
List<SysUser> getUserListByRoleIdList(List<Integer> roleIds);
}用户表数据访问接口 SQL 映射文件(cn/duozai/dao/SysUserMapper.xml):
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.duozai.dao.SysUserMapper">
<!-- ... -->
<select id="getUserListByRoleIdList" resultType="cn.duozai.entity.SysUser">
SELECT * FROM t_sys_user WHERE roleId in
<!-- 数组参数的默认名为array,List参数的默认名为list -->
<foreach collection="list" item="item" open="(" separator="," close=")">
#{item}
</foreach>
</select>
</mapper>测试类(test.SysUserMapperTest):
public class SysUserMapperTest {
// ...
/**
* 根据用户角色id列表查询用户列表
*
* @return void
*/
@Test
public void getUserListByRoleIdList() {
SqlSession sqlSession = MyBatisUtil.createSqlSession();
List<Integer> roleIds = new ArrayList<>();
roleIds.add(1);
roleIds.add(2);
List<SysUser> sysUserList = sqlSession.getMapper(SysUserMapper.class)
.getUserListByRoleIdList(roleIds);
for (SysUser sysUser : sysUserList) {
logger.debug("SysUser => " + sysUser.getRealName() + " roleId => " + sysUser.getRoleId());
}
MyBatisUtil.closeSqlSession(sqlSession);
}
}示例效果:
动态 SQL 处理更新功能
set 标签
通常情况下在实现修改功能时,需要实现动态 SQL 的构建,如果没有实现动态 SQL 的构建,可能会出现 SQL 语句错误的问题。
示例:修改操作可能存在的问题
用户表数据访问接口(cn.duozai.dao.SysUserMapper):
public interface SysUserMapper {
// ...
/**
* 修改用户信息
*
* @param sysUser 用户表实体类对象
* @return SQL语句影响行数
*/
int updateSysUser(SysUser sysUser);
}用户表数据访问接口 SQL 映射文件(cn/duozai/dao/SysUserMapper.xml):
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.duozai.dao.SysUserMapper">
<!-- ... -->
<update id="updateSysUser" parameterType="cn.duozai.entity.SysUser">
UPDATE t_sys_user SET realName = #{realName}, phone = #{phone}
WHERE id = #{id}
</update>
</mapper>测试类(test.SysUserMapperTest):
public class SysUserMapperTest {
// ...
/**
* 修改用户信息
*
* @return void
*/
@Test
public void updateSysUser() {
SqlSession sqlSession = MyBatisUtil.createSqlSession();
// 将要修改的用户信息保存到实体类中
// 根据用户id修改真实姓名
SysUser sysUser = new SysUser();
sysUser.setId(2);
sysUser.setRealName("明星");
int result = sqlSession.getMapper(SysUserMapper.class)
.updateSysUser(sysUser);
MyBatisUtil.closeSqlSession(sqlSession);
}
}示例效果:
只对部分属性的值进行修改时,没有做修改的属性不应该拼接到 SQL 语句中。
在 MyBatis 中,可以使用 set + if 标签实现 SQL 修改语句的动态拼接功能,set 标签直接替换 SQL 语句中的 set 关键字,可以智能地处理其内部多余的字符,避免多余字符带来的语法错误。
示例:if + set 标签
用户表数据访问接口 SQL 映射文件(cn/duozai/dao/SysUserMapper.xml):
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.duozai.dao.SysUserMapper">
<!-- ... -->
<update id="updateSysUser" parameterType="cn.duozai.entity.SysUser">
UPDATE t_sys_user
<set>
<if test="realName != null">
realName = #{realName},
</if>
<if test="phone != null">
phone = #{phone},
</if>
</set>
WHERE id = #{id}
</update>
</mapper>示例效果:
动态 SQL 扩展
trim 标签
trim 标签可以实现类似 where、set 标签的功能,用于自动拼接或忽略 SQL 语句中的关键字。
trim 标签的基本用法:
<trim prefix="要拼接的前缀" suffix="要拼接的后缀"
prefixOverrides="要忽略的前缀,用|隔开" suffixOverrides="要忽略的后缀,用|隔开">
...
</trim>trim 标签高度灵活,通用性强,可以根据实际业务逻辑精确控制要添加或去除的内容。
示例:trim 标签
用户表数据访问接口(cn.duozai.dao.SysUserMapper):
public interface SysUserMapper {
// ...
/**
* 修改用户信息
*
* @param sysUser 用户表实体类对象
* @return SQL语句影响行数
*/
int updateSysUser(SysUser sysUser);
}用户表数据访问接口 SQL 映射文件(cn/duozai/dao/SysUserMapper.xml):
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.duozai.dao.SysUserMapper">
<!-- ... -->
<update id="updateSysUser" parameterType="cn.duozai.entity.SysUser">
UPDATE t_sys_user
<trim prefix="set" suffixOverrides=",">
<if test="realName != null">
realName = #{realName},
</if>
<if test="phone != null">
phone = #{phone},
</if>
</trim>
WHERE id = #{id}
</update>
</mapper>分页实现
MyBatis 并没有对分页功能做过多的处理,而是使用数据库自带的分页功能。
MySQL 实现分页的方式:
- 使用 LIMIT m, n 语法,其中 m \= (页码 - 1) * 页大小,n \= 页大小。
- 使用 LIMIT a OFFSET b 语法,其中 a \= 页大小,b \= (页码 - 1) * 页大小。
示例:分页查询
用户表数据访问接口(cn.duozai.dao.SysUserMapper):
public interface SysUserMapper {
// ...
/**
* 分页查询用户列表
*
* @param m (页码-1)*页大小
* @param n 页大小
* @return 用户列表
*/
List<SysUser> getUserListByPage(@Param("m") Integer m, @Param("n") Integer n);
}用户表数据访问接口 SQL 映射文件(cn/duozai/dao/SysUserMapper.xml):
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.duozai.dao.SysUserMapper">
<!-- ... -->
<select id="getUserListByPage" resultType="cn.duozai.entity.SysUser">
SELECT * FROM t_sys_user
LIMIT #{m}, #{n}
</select>
</mapper>测试类(test.SysUserMapperTest):
public class SysUserMapperTest {
// ...
/**
* 分页查询用户列表
*
* @return void
*/
@Test
public void getUserListByPage() {
SqlSession sqlSession = MyBatisUtil.createSqlSession();
// 定义页码和页大小
int pageIndex = 2;
int pageSize = 5;
// 计算 LIMIT m, n
int m = (pageIndex-1)*pageSize;
List<SysUser> sysUserList = sqlSession.getMapper(SysUserMapper.class)
.getUserListByPage(m, pageSize);
for (SysUser sysUser : sysUserList) {
logger.debug("SysUser => " + sysUser.getRealName());
}
MyBatisUtil.closeSqlSession(sqlSession);
}
}示例效果: