Mybatis学习——传入非标准数据,表单项的连接以及一对多与多对一详解
在先前的博客中,我们讨论了如何在Mapper.xml中配置集中常见的SQL语句,但有心的同学可能会发现之前提到的SQL语句都是些很基础的增删改操作,涉及到的传入参数类型要么是单一变量,如id;要么是引用类型中的参数是基本类型,如User类对象。结果类型也同样如此。
那么如果传入的参数类型,以及结果集对象稍微复杂一些时或者不那么标准时该怎么处理呢?这也是本篇文章所总结的内容。
参数集为非标准数据
首先需要说明的是,传入非标准数据是值既非标基础数据类型也非pojo中的引用类型。常见的例子有,希望通过分页查询数据;用户指定属性表单查询。这里仅对如何分页查询数据做一个简单的应用分析。更具体的分析待上手具体项目后再做总结。
分页查询数据
分页查询数据最重要的两个参数是start
和size
。前者指定了查询的开始id(不包括该id),后者指定从查询id开始,期望查询的个数,对应的sql语句如下:
select * from user limit 2,2 #表示查询user表 id=2开始,查询2个,最终查到了id=3以及id=4的数据项
因此,可以在UserMapper.xml
中配置如下的信息(UserMap
作结果集映射)
<select id="getUserByPage" parameterType="map" resultMap="UserMap">
select * from Mybatis.user limit #{start},#{size}
</select>
<resultMap id="UserMap" type="User">
<!-- <result column="id" property="id"/>-->
<!-- <result column="name" property="name"/>-->
<result column="pwd" property="password"/>
有了上述的sql标签,接下来要考虑的便是如何在传参的时候让sql语句知道传入的两个数据哪个数据是start
,哪个数据是size
呢?这里有两种解决方案:
-
解决方案一:在
UserMapper
接口中定义方法时,设置传入参数为map
类型,并且规定传入数据时传入的map中必须包含有start
和size
的key值:具体代码如下// 接口中的方法 List<User> getAllUsers(Map map); // 测试时传入数据 Map<String, Integer> map = new HashMap<>(); map.put("start", "2"); map.put("size", "2"); List<User> users = mapper.getUserByPage(map);
-
解决方案二:在
UserMapper
接口中直接定义传入两个参数,同时使用@Param
注解,告诉Mybatis该参数应当对应的sql语句中的参数,例如:// 接口中的方法 List<User> getAllUsers(@Param("start")int start, @Param("size")int size); // 测试方法的传参形式 List<User> users = mapper.getUserByPage(2,2);
- 使用这种方法时,相当于在定义接口方法时告诉了Mybatis,第一个参数对应于sql语句中的
#{start}
,第二个参数对应#{size}
。在后续的项目中使用这种方法更加频繁,因为可以预想得到,使用这种方法不需要构建map容器,更加快速有效。
- 使用这种方法时,相当于在定义接口方法时告诉了Mybatis,第一个参数对应于sql语句中的
结果集为非标准数据
pojo中的大部分类中的参数类型都是基本数据类型,然而如果某些类中的参数也是引用类型时该怎么办呢?
首先需要说明的是,在pojo中stutent类中有三个属性,分别是id,name,teacher
(引用类型),teacher类中也有三个属性分别是id,name,List<Student>
。
这里就以小狂神中的一对多和多对一的例子作为例子,具体分析这种自定义操作应当如何实现。
一对多
顾名思义,一对多就是一个实体类在可能对应着多个实体类。最典型的例子便是一个老师对应多个学生。在教务管理系统中,数据库中保存了有所有学生,老师的信息。
其中学生信息除了包含有id
,name
外还可能因为被分配一个导师而存在teacherId
项。通常,这种情况在数据库中会使用foreign key
引用老师的id。现在有个需求:在查询老师信息的同时,返回该老师负责的所有学生的姓名。
要解决上述问题,首先考虑如何书写sql语句。显然,这需要查询两个表,同时根据外键将表项连接起来,于是可以得到如下的sql语句:
select t.id tid, t.name tname, s.name sname
from student s, teacher t
where t.id = ?
有了sql语句便可以以此为模板,在xml中配置标签sql语句
<select id="getTeacherById" resultMap="TeacherStudentsMap">
select t.id tid, t.name tname, s.name sname
from student s, teacher t
where t.id = #{tid}
</select>
<resultMap id="TeacherStudentsMap" type="teacher">
<id property="id" column="tid"/>
<id property="name" column="tname"/>
<collection property="students" ofType="student">
<result property="id" column="sid"/>
<result property="name" column="sname"/>
</collection>
</resultMap>
可以发现,一对多查询的关键在于结果集映射。分析上述resultMap
的构造可以发现,其返回类型是teacher,同时由于teacher中存在一个容器,因此使用collection
标签,声明其对应于teacher中的students
属性,以及容器中存放类型为ofType="student"
。
最后再将查询结果数据中的column
映射进容器中的每一个对象实例中。
⭐️ 需要说明的是,这里大量使用别名,因此property
和column
的映射关系需要完全手动进行。查询结果中所期望包含的数据就一定要在sql语句中查询,同时也需要在resultMap
中映射。没有映射的结果在返回打印时会显示null或者0。
多对一
与一对多类似,多对一考虑的主体是学生,基于上述内容,再考虑一个需求:查询所有学生,返回学生的所有数据,包括老师信息。
同样考虑sql语句
select s.id sid, s.name sname, t.id tid, t.name tname
from student s,teacher t
where s.tid=t.id
以及xml中配置的sql标签
<select id="getStudents" resultMap="StudentsTeacherMap">
select s.id sid, s.name sname, t.id tid, t.name tname
from student s,teacher t
where s.tid=t.id
</select>
<resultMap id="StudentsTeacherMap" type="student">
<id property="id" column="sid"/>
<id property="name" column="sname"/>
<association property="teacher" javaType="teacher">
<id property="id" column="tid"/>
<id property="name" column="tname"/>
</association>
</resultMap>
此时,结果返回类型为student
,同时由于student
类中没有涉及到容器,仅仅只有teacher
一个引用类型,因此这里使用association
来做类的装载,其属性数据的映射类似于一对多。
小结
本博客分析了Mybatis传入数据为非单个基本数据类型,以及返回结果pojo类中存在引用类型以及容器类型时涉及到的多对一和一对多的情况。
关于传入数据为非单个基本数据类型时,考虑要么使用map集合,要么使用注解标识。其中后者由于其简便性在实际开发中使用较多。
关于多对一和一对多,两者的主要区别在于xml中配置结果集映射时有些许不同,具体的:
-
如果返回pojo类中存在容器类,则使用
collection
+ofType
+result
来实现映射 -
如果返回pojo类汇总存在单个引用类型,则使用
association
+javaType
+id
来实现最后需要注意的是,对于期望展现出来的数据都应当做结果集映射,否则打印结果为0或null。