👉 MyBatis参数传递
📋 概述
参数传递是十分重要的功能,它能有效地将应用程序中的数据传递给预定的SQL语句或存储过程,实现动态条件查询、分页查询、数据更新等功能。
在《快速入门》这一章节中,我们只能查询用户名为"fatgod"
的用户信息,但有了参数传递功能,我们就能根据指定的用户名来查询用户信息了。
在MyBatis中,Mapper接口中的一个方法通常会映射一个SQL语句,方法中的参数列表值可以作为参数传递给这个SQL语句,而该SQL语句中可以通过符号${}
和#{}
接收这些参数。
需要注意的是,SQL语句不一定来自于XML映射文件,也可以来自于注解。例如,在《快速入门》这一章节中的best()
方法对应的SQL语句可以直接书写于注解@Select
中。
tip: 查询、新增、更改、删除操作对应的XML标签分别为
<select>
、<insert>
、<update>
和<delete>
,对应的方法注解分别为@Select
、@Insert
、@Update
和@Delete
。
📍 ${}和#{}
${}
占位符本质是字符串拼接,可以把整个SQL语句看成一个普通的字符串,而${}
会被替换成相对应的参数值。这种参数接收方式非常灵活,可定制性强,不仅可以实现动态条件等功能,还能实现动态拼接列名、表名、条件等复杂功能。然而,需要注意这种灵活性也带来了SQL 注入的风险,因此在使用时,应用程序需谨慎处理用户输入的参数值,确保 SQL的安全性。
例如,以下的getUsersByCondition
方法可以查询出符合给定条件condition
的用户列表,brushAllUsers
方法可以将fg_user
表中的给定字段刷成给定值。
tips:
- 当数据记录大于一行时,需用集合类型接收。
- 当对表作增删改操作时,方法返回值可设置成
void
或int
类型,后者表示受影响的行数,但如果SQL来自于注解,则只能使用void
类型返回值。
public interface UserMapper {
@Select("select * from fg_user where username = 'fatgod'")
User best();
List<User> getUserByCondition(String condition);
int brushAllUsers(String field, String value);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!--xml文件的dtd约束-->
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.fatgod.learn.mybatis.mapper.UserMapper">
<select id="getUserByCondition" resultType="cn.fatgod.learn.mybatis.entity.User">
select * from fg_user where ${condition}
</select>
<update id="brushAllUsers">
update fg_user set ${field} = ${value}
</update>
</mapper>
下面,我们来测试一下getUserByCondition
和brushAllUsers
方法。具体来说,利用getUserByCondition
方法获取年龄大于等于 brushAllUsers
方法将所有用户的邮箱号刷成
public class MybatisTest {
@Test
public void test() {
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder()
.build(ResourceUtil.getStream("mybatis-config.xml"));
try (SqlSession sqlSession = sqlSessionFactory.openSession(true)) {
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
List<User> users = userMapper.getUserByCondition("age >= 18 and username like 'fat%'");
int rows = userMapper.brushAllUsers("email", "'y13511310612@163.com'");
System.out.println("getUserByCondition方法查询的用户列表:" + users);
System.out.println("brushAllUsers方法影响的行数:" + rows);
}
}
}
输出的结果如下,可以看到,两个方法成功运行😁。
tip:
SqlSession
实例默认是手动提交事务的,但能够使用sqlSessionFactory.openSession(true)
的方式来创建自动提交事务的SqlSession
实例。
#{}
占位符本质是MyBatis对JDBC中PreparedStatement
预编译语句中的?
占位符的一种封装和扩展。这种参数接收方式相对安全有效,能够有效解决SQL注入问题。然而,这种方式也存在一定的局限性,参数传递必须符合?
占位符的语法规范,通常只能为某个字段设定值,缺乏一些灵活性。
例如,以下的saveUser
方法可以根据指定的User
实体对象新增一条用户数据记录,deleteUserById
方法可以根据指定的id
删除用户。
public interface UserMapper {
@Select("select * from fg_user where username = 'fatgod'")
User best();
List<User> getUserByCondition(String condition);
int brushAllUsers(String field, String value);
int saveUser(User user);
int deleteUserById(long id);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!--xml文件的dtd约束-->
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.fatgod.learn.mybatis.mapper.UserMapper">
<select id="getUserByCondition" resultType="cn.fatgod.learn.mybatis.entity.User">
select * from fg_user where ${condition}
</select>
<update id="brushAllUsers">
update fg_user set ${field} = ${value}
</update>
<insert id="saveUser">
insert fg_user(id, username, age, email)
values (#{id}, #{username}, #{age}, #{email})
</insert>
<delete id="deleteUserById">
delete from fg_user where id = #{id}
</delete>
</mapper>
下面,我们来测试一下saveUser
和deleteUserById
方法。具体来说,利用saveUser
方法新增一条id
为 username
为age
为 email
为 deleteUserById
方法删除一个id
为
public class MybatisTest {
@Test
public void test() {
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder()
.build(ResourceUtil.getStream("mybatis-config.xml"));
try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user = new User().setId(500L).setUsername("Leon")
.setAge(22).setEmail("Leon24@163.com");
int rows1 = userMapper.saveUser(user);
int rows2 = userMapper.deleteUserById(300);
System.out.println("saveUser方法影响的用行数:" + rows1);
System.out.println("deleteUserById方法影响的行数:" + rows2);
sqlSession.commit(); //手动提交事务
}
}
}
输出的结果如下,可以看到,两个方法成功运行😁。
🔧 多种参数传递的情况
为了演示多种参数传递情况。我们可以把UserMapper
接口和UserMapper.xml
映射文件中的内容都清空(每次演示前,都会清空)。
public interface UserMapper {
}
<?xml version="1.0" encoding="UTF-8" ?>
<!--xml文件的dtd约束-->
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.fatgod.learn.mybatis.mapper.UserMapper">
</mapper>
同时,SqlSessionFactory
生产SqlSession
实例的这一过程,我们可以将其封装成一个静态方法。
public class SqlSessionGenerator {
@Getter
private final static SqlSessionFactory FACTORY;
static {
FACTORY = new SqlSessionFactoryBuilder()
.build(ResourceUtil.getStream("mybatis-config.xml"));
}
public static SqlSession generate(boolean autoCommit) {
return FACTORY.openSession(autoCommit);
}
public static SqlSession generate() {
return generate(false);
}
}
一个简单数据类型
本文中的简单数据类型指的是Java中的基本数据类型、基本数据类型包装类、String类型、JDK内置的时间日期类型等。
如果方法的参数列表只有一个且为简单数据类型,那么在SQL语句中可以使用任意的占位符名称来接收这个参数值。为了增强代码语义,我们建议使用方法的参数(形参)名称。
例如,以下的getByUsername
方法可以根据指定的用户名获取用户信息。由于用户名在数据库层面是唯一的,因此该方法的返回值类型应为实体类User
,而非集合类型。
public interface UserMapper {
User getByUsername(String username);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!--xml文件的dtd约束-->
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.fatgod.learn.mybatis.mapper.UserMapper">
<select id="getByUsername" resultType="cn.fatgod.learn.mybatis.entity.User">
select * from fg_user where username = #{username}
</select>
</mapper>
下面为getByUsername
方法的测试用例,用户名输入为fatgod
。
public class MybatisTest {
@Test
public void testGetByUsername() {
try (SqlSession sqlSession = SqlSessionGenerator.generate()) {
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.getByUsername("fatgod");
System.out.println(user);
}
}
}
输出的结果如下,可以看到,我们成功查询到了用户名为fatgod
的用户信息。
多个简单数据类型
如果方法的参数列表为多个简单数据类型,那么在SQL语句中可以使用参数(形参)名称或者param + 参数索引(从1开始)
这两种占位符名称来接收指定的参数值。
在较低的MyBatis版本中,其可能并不支持形参名称占位,它们支持参数索引(从0开始)
或者arg + 参数索引(从0开始)
这两种方式,例如#{0}
、#{arg0}
、#{1}
、#{arg1}
。但都支持param + 参数索引(从1开始)
这种方式,例如#{param1}
、#{param2}
。
除了以上几种方式外,MyBatis还提供了一种声明式的参数接收方式,即使用注解@Param
。通过给方法参数添加@Param
注解,可以在SQL语句中使用该注解的value
值作为占位符名称来接收对应的参数值。MyBatis官方也推荐使用这种声明式的方法来传递接收参数,同时需要注意的是,此时依旧可以通过param + 参数索引(从1开始)
作为占位符名称来接收参数。
例如,以下的checkEmail
方法可以获取指定用户名和邮箱号的用户数据记录数。
public interface UserMapper {
int checkEmail(@Param("username") String username, @Param("email") String email);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!--xml文件的dtd约束-->
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.fatgod.learn.mybatis.mapper.UserMapper">
<select id="checkEmail" resultType="int">
select count(1) from fg_user where username = #{username} and email = #{email}
</select>
</mapper>
下面为checkEmail
方法的测试用例,用户名和邮箱号输入分别为fatgod
和Fakeemail@qq.com
。如果记录数大于
public class MybatisTest {
@Test
public void testCheckEmail() {
try (SqlSession sqlSession = SqlSessionGenerator.generate()) {
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
int count = mapper.checkEmail("fatgod", "Fakeemail@qq.com");
System.out.println(count > 0 ? "邮箱号正确" : "邮箱号错误");
}
}
}
输出的结果如下,可以看到,用户名fatgod
与邮箱号Fakeemail@qq.com
并不匹配。
Map类型
如果方法的参数为Map类型,那么在SQL语句中可以使用键名作为占位符名称来接收该键映射的值。此外,Map类型的方法参数也可以使用@Param
注解修饰,此时占位符的命名规则为@Param注解的value值.键名
,比如map.username
。
例如,以下的getByRange
方法可以根据指定字段的指定范围获取用户信息集合。
public interface UserMapper {
List<User> getByRange(Map<String, String> map);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!--xml文件的dtd约束-->
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.fatgod.learn.mybatis.mapper.UserMapper">
<select id="getByRange" resultType="cn.fatgod.learn.mybatis.entity.User">
select * from fg_user where ${field} in (${range});
</select>
</mapper>
下面为getByRange
方法的测试用例,输入的Map为{field: "age", range: "15,16,17,18"}
。
public class MybatisTest {
@Test
public void testUpdateById() {
try (SqlSession sqlSession = SqlSessionGenerator.generate()) {
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
Map<String, String> map = new HashMap<>(2);
map.put("field", "age");
map.put("range", "15,16,17,18");
List<User> users = mapper.getByRange(map);
System.out.println(users);
}
}
}
输出的结果如下,可以看到,我们成功查询到了年龄为
实体类型
如果方法的参数为实体类型,那么在SQL语句中可以使用属性名作为占位符名称来接收对应的属性值。此外,实体类型的方法参数也可以使用@Param
注解修饰,此时占位符的命名规则为@Param注解的value值.属性名
,比如user.username
。
例如,以下的updateById
方法可以根据指定的主键修改用户信息。
public interface UserMapper {
int updateById(User user);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!--xml文件的dtd约束-->
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.fatgod.learn.mybatis.mapper.UserMapper">
<update id="updateById">
update fg_user set username = #{username}, age = #{age},email = #{email} where id = #{id}
</update>
</mapper>
下面为updateById
方法的测试用例,需修改的用户实体数据为{id: 200, email: "littlebaby@136.com", username: "littlebaby", age: 2}
。
public class MybatisTest {
@Test
public void testUpdateById() {
try (SqlSession sqlSession = SqlSessionGenerator.generate(true)) {
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = new User().setId(200L).setEmail("littlebaby@136.com")
.setUsername("littlebaby").setAge(2);
int rows = mapper.updateById(user);
System.out.println("更改受影响的行数:" + rows);
}
}
}
输出的结果如下,可以看到,我们成功修改了主键id
为