Mybatis参数处理
- 一、单个参数且为简单类型参数
- 二、参数为Map集合
- 三、参数为POJO类
- 四、多个参数
- 五、@Param注解
- 六、补充知识点:结果映射
一、单个参数且为简单类型参数
代码演示:
StudentMapper 接口
public interface StudentMapper {
/**
* 当接口中的参数只有一个(单个参数),并且参数类型为简单类型
*/
List<Student> selectById(Long id);
List<Student> selectByName(String name);
List<Student> selectByBirth(Date birth);
List<Student> selectBySex(Character sex);
}
StudentMapper.xml
<mapper namespace="com.powernode.mybatis.mapper.StudentMapper">
<select id="selectById" resultType="Student">
select * from t_student where id = #{id}
</select>
<select id="selectByName" resultType="Student">
select * from t_student where name = #{name}
</select>
<select id="selectByBirth" resultType="Student">
select * from t_student where birth = #{birth}
</select>
<select id="selectBySex" resultType="Student">
select * from t_student where sex = #{sex}
</select>
</mapper>
测试类
@Test
public void testSimple() throws ParseException {
SqlSession sqlSession = SqlSessionUtil.openSqlSession();
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
// List<Student> students = mapper.selectById(1L);
// List<Student> students = mapper.selectByName("张三");
// SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
// Date birth = sdf.parse("1990-02-01");
// List<Student> students = mapper.selectByBirth(birth);
Character character = Character.valueOf('男');
List<Student> students = mapper.selectBySex(character);
students.forEach(e -> System.out.println(e.toString()));
sqlSession.close();
}
运行结果
19:21:28.479 default [main] DEBUG c.p.m.m.StudentMapper.selectBySex - ==> Preparing: select * from t_student where sex = ?
19:21:28.536 default [main] DEBUG c.p.m.m.StudentMapper.selectBySex - ==> Parameters: 男(String)
19:21:28.589 default [main] DEBUG c.p.m.m.StudentMapper.selectBySex - <== Total: 1
Student{
id=1, name='张三', age=20, height=1.81, birth=Thu Feb 01 00:00:00 GMT+08:00 1990, sex=男}
补充一下:
1、<select id="selectById" resultType="Student" parameterType="java.lang.Long">
可以不用加parameterType属性,mybatis会底层会自动找到对应的参数类型
2、parameterType属性的作用:
告诉mybatis框架,我这个方法的参数类型是什么类型。
mybatis框架自身带有类型自动推断机制,所以大部分情况下parameterType属性都是可以省略不写的。
SQL语句最终是这样的:
select * from t student where id = ? JDBC代码是一定要给?传值的。
怎么传值?ps.setXxx(第几个问号,传什么值);
ps.setLong(1,1L);
ps.setstring(1,"zhangsan");
ps.setDate(1, new Date());
ps.setInt(1,100);
...
mybatis底层到底调用setXxx的哪个方法,取决于parameterType属性的值。
注意:mybatis框架实际上内置了很多别名。可以参考开发手册。
二、参数为Map集合
代码演示:
StudentMapper 接口
public interface StudentMapper {
/**
* Map集合参数
*/
int insertStudentMap(Map<String,Object> map);
}
StudentMapper.xml
<mapper namespace="com.powernode.mybatis.mapper.StudentMapper">
<insert id="insertStudentMap" parameterType="map">
insert into t_student values (null,#{name},#{age},#{height},#{birth},#{sex})
</insert>
</mapper>
测试类
@Test
public void testMap(){
SqlSession sqlSession = SqlSessionUtil.openSqlSession();
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
Map<String,Object> map = new HashMap<>();
map.put("name","小明");
map.put("age",23);
map.put("birth",new Date());
map.put("height",1.7);
map.put("sex","女");
int i = mapper.insertStudentMap(map);
System.out.println(i);
sqlSession.commit();
sqlSession.close();
}
三、参数为POJO类
代码演示:
StudentMapper 接口
public interface StudentMapper {
/**
* Pojo 参数
*/
int insertStudentPojo(Student student);
}
StudentMapper.xml
<mapper namespace="com.powernode.mybatis.mapper.StudentMapper">
<insert id="insertStudentPojo" parameterType="Student">
insert into t_student values (null,#{name},#{age},#{height},#{birth},#{sex})
</insert>
</mapper>
测试类
@Test
public void testPojo(){
SqlSession sqlSession = SqlSessionUtil.openSqlSession();
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
Student student = new Student();
student.setName("小猪");
student.setAge(16);
student.setHeight(1.9);
student.setSex('女');
student.setBirth(new Date());
int i = mapper.insertStudentPojo(student);
System.out.println(i);
sqlSession.commit();
sqlSession.close();
}
四、多个参数
根据name和sex查询Student信息。
如果是多个参数的话,mybatis框架底层是怎么做的呢?
mybatis框架会自动创建一个Map集合。并且Map集合是以这种方式存储参数的:
map.put("arg0",name); map.put("arg1",sex);
map.put("param1", name); map.put("param2", sex);
三种写法
<select id="selectByNameAndSex" resultType="student">
select * from t student where name = #{arge} and sex= #{arg1}
select *from t student where name =#{param1} and sex=#{param2}
select * from t_student where name = #{arg0} and sex = #{param2}
</select>
代码演示:
StudentMapper 接口
public interface StudentMapper {
/**
* 多个 参数:
* mybatis会自动创建一个map集合,
*/
List<Student> selectByNameAndSex(String name,Character sex);
}
StudentMapper.xml
<mapper namespace="com.powernode.mybatis.mapper.StudentMapper">
<select id="selectByNameAndSex" resultType="Student">
select * from t_student where name = #{arg0} and sex = #{arg1}
</select>
</mapper>
注意:这个arg0,arg1是mybatis底层框架自动生成的,必须怎么写
测试类
@Test
public void testPojo(){
SqlSession sqlSession = SqlSessionUtil.openSqlSession();
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
Student student = new Student();
student.setName("小猪");
student.setAge(16);
student.setHeight(1.9);
student.setSex('女');
student.setBirth(new Date());
int i = mapper.insertStudentPojo(student);
System.out.println(i);
sqlSession.commit();
sqlSession.close();
}
运行结果
19:34:05.620 default [main] DEBUG c.p.m.m.S.selectByNameAndSex - ==> Preparing: select * from t_student where name = ? and sex = ?
19:34:05.704 default [main] DEBUG c.p.m.m.S.selectByNameAndSex - ==> Parameters: 小美(String), 女(String)
19:34:05.756 default [main] DEBUG c.p.m.m.S.selectByNameAndSex - <== Total: 1
Student{
id=4, name='小美', age=16, height=1.9, birth=Sun Oct 23 00:00:00 GMT+08:00 2022, sex=女}
五、@Param注解
如果不想使用mybatis底层默认的参数,那么可以使用@Param注解来解决
代码演示:
StudentMapper 接口
public interface StudentMapper {
/**
* 使用param注解,指定 多个 参数的名字
*/
List<Student> selectByNameAndSex2(@Param("name") String name, @Param("sex") Character sex);
}
StudentMapper.xml
<mapper namespace="com.powernode.mybatis.mapper.StudentMapper">
<select id="selectByNameAndSex2" resultType="Student">
select * from t_student where name = #{name} and sex = #{sex}
</select>
</mapper>
注意:这个arg0,arg1是mybatis底层框架自动生成的,必须这么写
测试类
@Test
public void testParams(){
SqlSession sqlSession = SqlSessionUtil.openSqlSession();
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
List<Student> students = mapper.selectByNameAndSex("小美", '女');
students.forEach(e-> System.out.println(e.toString()));
sqlSession.close();
}
运行结果
19:34:05.620 default [main] DEBUG c.p.m.m.S.selectByNameAndSex - ==> Preparing: select * from t_student where name = ? and sex = ?
19:34:05.704 default [main] DEBUG c.p.m.m.S.selectByNameAndSex - ==> Parameters: 小美(String), 女(String)
19:34:05.756 default [main] DEBUG c.p.m.m.S.selectByNameAndSex - <== Total: 1
Student{
id=4, name='小美', age=16, height=1.9, birth=Sun Oct 23 00:00:00 GMT+08:00 2022, sex=女}
六、补充知识点:结果映射
resultMap 元素是 MyBatis 中最重要最强大的元素。它可以让你从 90% 的 JDBC ResultSets 数据提取代码中解放出来,并在一些情形下允许你进行一些 JDBC 不支持的操作。实际上,在为一些比如连接的复杂语句编写映射代码的时候,一份 resultMap 能够代替实现同等功能的数千行代码。ResultMap 的设计思想是,对简单的语句做到零配置,对于复杂一点的语句,只需要描述语句之间的关系就行了。
之前你已经见过简单映射语句的示例,它们没有显式指定 resultMap。比如:
<select id="selectUsers" resultType="map">
select id, username, hashedPassword
from some_table
where id = #{
id}
</select>
上述语句只是简单地将所有的列映射到 HashMap 的键上,这由 resultType 属性指定。虽然在大部分情况下都够用,但是 HashMap 并不是一个很好的领域模型。你的程序更可能会使用 JavaBean 或 POJO(Plain Old Java Objects,普通老式 Java 对象)作为领域模型。MyBatis 对两者都提供了支持。看看下面这个 JavaBean:
package com.someapp.model;
public class User {
private int id;
private String username;
private String hashedPassword;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getHashedPassword() {
return hashedPassword;
}
public void setHashedPassword(String hashedPassword) {
this.hashedPassword = hashedPassword;
}
}
基于JavaBean 的规范,上面这个类有 3 个属性:id,username 和 hashedPassword。这些属性会对应到 select 语句中的列名。这样的一个 JavaBean 可以被映射到 ResultSet,就像映射到 HashMap 一样简单。
<select id="selectUsers" resultType="com.someapp.model.User">
select id, username, hashedPassword
from some_table
where id = #{
id}
</select>
类型别名是你的好帮手。使用它们,你就可以不用输入类的全限定名了。比如:
<!-- mybatis-config.xml 中 -->
<typeAlias type="com.someapp.model.User" alias="User"/>
<!-- SQL 映射 XML 中 -->
<select id="selectUsers" resultType="User">
select id, username, hashedPassword
from some_table
where id = #{
id}
</select>
在这些情况下,MyBatis 会在幕后自动创建一个 ResultMap,再根据属性名来映射列到 JavaBean 的属性上。如果列名和属性名不能匹配上,可以在 SELECT 语句中设置列别名(这是一个基本的 SQL 特性)来完成匹配。比如:
<select id="selectUsers" resultType="User">
select
user_id as "id",
user_name as "userName",
hashed_password as "hashedPassword"
from some_table
where id = #{
id}
</select>
在学习了上面的知识后,你会发现上面的例子没有一个需要显式配置 ResultMap,这就是 ResultMap 的优秀之处——你完全可以不用显式地配置它们。 虽然上面的例子不用显式配置 ResultMap。 但为了讲解,我们来看看如果在刚刚的示例中,显式使用外部的 resultMap 会怎样,这也是解决列名不匹配的另外一种方式。
<resultMap id="userResultMap" type="User">
<id property="id" column="user_id" />
<result property="username" column="user_name"/>
<result property="password" column="hashed_password"/>
</resultMap>
然后在引用它的语句中设置 resultMap 属性就行了(注意我们去掉了 resultType 属性)。比如:
<select id="selectUsers" resultMap="userResultMap">
select user_id, user_name, hashed_password
from some_table
where id = #{
id}
</select>
高级结果映射
MyBatis 创建时的一个思想是:数据库不可能永远是你所想或所需的那个样子。 我们希望每个数据库都具备良好的第三范式或 BCNF 范式,可惜它们并不都是那样。 如果能有一种数据库映射模式,完美适配所有的应用程序,那就太好了,但可惜也没有。 而 ResultMap 就是 MyBatis 对这个问题的答案。
比如,我们如何映射下面这个语句?
<!-- 非常复杂的语句 -->
<select id="selectBlogDetails" resultMap="detailedBlogResultMap">
select
B.id as blog_id,
B.title as blog_title,
B.author_id as blog_author_id,
A.id as author_id,
A.username as author_username,
A.password as author_password,
A.email as author_email,
A.bio as author_bio,
A.favourite_section as author_favourite_section,
P.id as post_id,
P.blog_id as post_blog_id,
P.author_id as post_author_id,
P.created_on as post_created_on,
P.section as post_section,
P.subject as post_subject,
P.draft as draft,
P.body as post_body,
C.id as comment_id,
C.post_id as comment_post_id,
C.name as comment_name,
C.comment as comment_text,
T.id as tag_id,
T.name as tag_name
from Blog B
left outer join Author A on B.author_id = A.id
left outer join Post P on B.id = P.blog_id
left outer join Comment C on P.id = C.post_id
left outer join Post_Tag PT on PT.post_id = P.id
left outer join Tag T on PT.tag_id = T.id
where B.id = #{
id}
</select>
你可能想把它映射到一个智能的对象模型,这个对象表示了一篇博客,它由某位作者所写,有很多的博文,每篇博文有零或多条的评论和标签。 我们先来看看下面这个完整的例子,它是一个非常复杂的结果映射(假设作者,博客,博文,评论和标签都是类型别名)。 不用紧张,我们会一步一步地来说明。虽然它看起来令人望而生畏,但其实非常简单。
<!-- 非常复杂的结果映射 -->
<resultMap id="detailedBlogResultMap" type="Blog">
<constructor>
<idArg column="blog_id" javaType="int"/>
</constructor>
<result property="title" column="blog_title"/>
<association property="author" javaType="Author">
<id property="id" column="author_id"/>
<result property="username" column="author_username"/>
<result property="password" column="author_password"/>
<result property="email" column="author_email"/>
<result property="bio" column="author_bio"/>
<result property="favouriteSection" column="author_favourite_section"/>
</association>
<collection property="posts" ofType="Post">
<id property="id" column="post_id"/>
<result property="subject" column="post_subject"/>
<association property="author" javaType="Author"/>
<collection property="comments" ofType="Comment">
<id property="id" column="comment_id"/>
</collection>
<collection property="tags" ofType="Tag" >
<id property="id" column="tag_id"/>
</collection>
<discriminator javaType="int" column="draft">
<case value="1" resultType="DraftPost"/>
</discriminator>
</collection>
</resultMap>
resultMap 元素有很多子元素和一个值得深入探讨的结构。 下面是resultMap 元素的概念视图。
结果映射(resultMap)
constructor - 用于在实例化类时,注入结果到构造方法中
idArg - ID 参数;标记出作为 ID 的结果可以帮助提高整体性能
arg- 将被注入到构造方法的一个普通结果
id– 一个 ID 结果;标记出作为 ID 的结果可以帮助提高整体性能
result – 注入到字段或 JavaBean 属性的普通结果
association – 一个复杂类型的关联;许多结果将包装成这种类型
嵌套结果映射 – 关联可以是 resultMap 元素,或是对其它结果映射的引用
collection – 一个复杂类型的集合
嵌套结果映射 – 集合可以是 resultMap 元素,或是对其它结果映射的引用
discriminator – 使用结果值来决定使用哪个 resultMap
case – 基于某些值的结果映射
嵌套结果映射 – case 也是一个结果映射,因此具有相同的结构和元素;或者引用其它的结果映射
ResultMap 的属性列表
最好逐步建立结果映射。单元测试可以在这个过程中起到很大帮助。 如果你尝试一次性创建像上面示例那么巨大的结果映射,不仅容易出错,难度也会直线上升。 所以,从最简单的形态开始,逐步迭代。而且别忘了单元测试! 有时候,框架的行为像是一个黑盒子(无论是否开源)。因此,为了确保实现的行为与你的期望相一致,最好编写单元测试。 并且单元测试在提交 bug 时也能起到很大的作用。
下一部分将详细说明每个元素。
id& result
这些元素是结果映射的基础。id 和 result 元素都将一个列的值映射到一个简单数据类型(String, int, double, Date 等)的属性或字段。
这两者之间的唯一不同是,id 元素对应的属性会被标记为对象的标识符,在比较对象实例时使用。 这样可以提高整体的性能,尤其是进行缓存和嵌套结果映射(也就是连接映射)的时候。
两个元素都有一些属性:
Id和 Result 的属性
支持的JDBC 类型
为了以后可能的使用场景,MyBatis 通过内置的 jdbcType 枚举类型支持下面的 JDBC 类型。
构造方法
通过修改对象属性的方式,可以满足大多数的数据传输对象(Data Transfer Object, DTO)以及绝大部分领域模型的要求。但有些情况下你想使用不可变类。 一般来说,很少改变或基本不变的包含引用或数据的表,很适合使用不可变类。 构造方法注入允许你在初始化时为类设置属性的值,而不用暴露出公有方法。MyBatis 也支持私有属性和私有 JavaBean 属性来完成注入,但有一些人更青睐于通过构造方法进行注入。 constructor 元素就是为此而生的。
看看下面这个构造方法:
public class User {
//...
public User(Integer id, String username, int age) {
//...
}
//...
}
为了将结果注入构造方法,MyBatis 需要通过某种方式定位相应的构造方法。 在下面的例子中,MyBatis 搜索一个声明了三个形参的的构造方法,参数类型以 java.lang.Integer, java.lang.String 和 int 的顺序给出。
<constructor>
<idArg column="id" javaType="int"/>
<arg column="username" javaType="String"/>
<arg column="age" javaType="_int"/>
</constructor>