mybatis 使用resultMap实现表间关联
AutoMapping
auto mapping,直译过来就是自动映射,工作原理大概如下:
假设我们有一张表,表名为person,包含id,name,age,addr这4个字段
mysql> desc person; +-------+-------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +-------+-------------+------+-----+---------+----------------+ | id | int(11) | NO | PRI | NULL | auto_increment | | name | varchar(30) | NO | | NULL | | | age | int(2) | NO | | NULL | | | addr | varchar(30) | NO | | NULL | | +-------+-------------+------+-----+---------+----------------+
同时我们会创建一个实体类Person来与这张person表进行对应,此时Person类的属性名称和person表中的字段名称一一对应,不仅是名称对应,数据类型也是一一对应的:
package lixin.gan.pojo; public class Person { private int id; private String name; private int age; private String addr; // 省略了构造方法、setter、getter、toString }
之后我们会创建PersonMapper.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="lixin.gan.mapper.PersonMapper"> <select id="selectAll" resultType="lixin.gan.pojo.Person"> select * from person </select> </mapper>
注意上面的resultType,这里的resultType已经指明了返回值的类型,于是mybatis在从结果集中取出一条数据后,会将每一行记录的字段名,对应到Person类的同名属性,将字段值赋值给Person类的同名属性。
此时需要注意:如果从person表中查询数据,取出结果集中的字段名称,和Person类中的属性值对应不上(名称不相同),那么返回的Person类的对象对应的那个属性就会设置为null。举个例子,person表中的name字段,应该auto mapping到Person类的name属性,但如果,Person类中,没有name属性,却有一个name1属性,那么再返回结果的时候,name1属性就会初始为null。
虽然resultType很方便,可以自动的实现映射,但是,我们往往需要进行自定义的映射,此时就可以使用resultMap了。
使用resultMap实现单表映射
假设实体类中Person.java的属性更改如下:
package lixin.gan.pojo; public class Person { private int id1; private String name1; private int age1; private String addr1; // 省略了构造方法、setter、getter、toString }
要想使用mybatis时,person表的字段仍能正确对应到Person类中的准确字段中,使用resultMap来指定对应关系,可以这样做:
<?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="lixin.gan.mapper.PersonMapper"> <!-- 定义数据表中的字段,与实体类中的属性对应关系 --> <resultMap type="lixin.gan.pojo.Person" id="testResultMap"> <!-- 数据表中的主键列字段使用<id />标签, 其他字段使用<result />标签 --> <!-- column表示的是数据表中的字段, property表示的是实体类中对应的属性 --> <id column="id" property="id1" /> <result column="name" property="name1" /> <result column="age" property="age1" /> <result column="addr" property="addr1" /> </resultMap> <!-- 此时不用resultType属性,而是使用resultMap属性,属性值就是前面定义的对应关系id --> <select id="selectAll" resultMap="testResultMap"> select * from person </select> </mapper>
使用resultMap实现n+1查询
n+1查询是指:先查询出某个表的全部信息,然后根据这个表的信息,去查询另外一个表的信息。
举下面一个例子:查询student表后,根据student表中的tid,查询对应的teacher表中信息。
Teacher类(对应teacher表),实体类定义如下:
package lixin.gan.pojo; public class Teacher { private int id; private String name; // 省略了构造方法、setter、getter、toString }
TeacherMapper.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="lixin.gan.mapper.TeacherMapper"> <resultMap type="lixin.gan.pojo.Teacher" id="teacherMap"> <id column="id" property="id" /> <result column="name" property="name" /> </resultMap> <select id="selectAllTeacher" resultMap="teacherMap"> select * from teacher </select> <select id="selectTeacherById" resultType="teacher" parameterType="int"> select * from teacher where id=#{0} </select> </mapper>
现在有一个Student类(对应student表),实体类定义如下:
package lixin.gan.pojo; public class Student { private int id; // 学生id private int age; // 学生年龄 private String name;// 学生姓名 private int tid; // 老师的id private Teacher teacher; // 包含一个Teacher对象 // 省略了构造方法、setter、getter、toString }
与此同时,StudentMapper.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="lixin.gan.mapper.StudentMapper"> <!-- 定义数据表中的字段,与实体类中的属性对应关系 --> <resultMap type="lixin.gan.pojo.Student" id="studentMap"> <id column="id" property="id" /> <result column="name" property="name" /> <result column="age" property="age" /> <result column="tid" property="tid" /> <!-- 如果当前xml对应的实体类中包含一个类对象,那么可以使用association标签来进行关联 --> <!-- property仍旧指的是实体类中的属性名,select表示要为该属性赋值,需要执行的查询操作(id),column表示传入的参数--> <association property="teacher" select="lixin.gan.mapper.TeacherMapper.selectTeacherById" column="tid" ></association> <!-- 如果当前的xml对应的实体来中包含一个容器集合,那么可以使用collection标签来进行关联 --> <!-- <collection property=""></collection> --> </resultMap> <select id="selectAllStudent" resultMap="studentMap"> select * from student </select> </mapper>
测试代码:
package lixin.gan.test; import java.io.InputStream; import java.util.List; import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; import lixin.gan.pojo.Student; public class Test { public static void main(String[] args) throws Exception{ InputStream config = Resources.getResourceAsStream("mybatis.xml"); SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(config); SqlSession session = factory.openSession(); List<Student> list = session.selectList("lixin.gan.mapper.StudentMapper.selectAllStudent"); for (Student stu : list) { System.out.println(stu); } } }
运行结果,打印log4j日志:
==> Preparing: select * from student ==> Parameters: ====> Preparing: select * from teacher where id=? ====> Parameters: 2(Integer) <==== Total: 1 ====> Preparing: select * from teacher where id=? ====> Parameters: 1(Integer) <==== Total: 1 ====> Preparing: select * from teacher where id=? ====> Parameters: 5(Integer) <==== Total: 1 ====> Preparing: select * from teacher where id=? ====> Parameters: 4(Integer) <==== Total: 1 <== Total: 4 Student [id=1, age=5, name=张三, tid=2, teacher=Teacher [id=2, name=黄老师]] Student [id=2, age=10, name=李四, tid=1, teacher=Teacher [id=1, name=李老师]] Student [id=3, age=25, name=王五, tid=5, teacher=Teacher [id=5, name=方老师]] Student [id=4, age=30, name=赵六, tid=4, teacher=Teacher [id=4, name=蔡老师]]
使用resultMap关联集合对象
关联集合对象,可以理解为,一个类A的某个属性,是包含另一个类B的集合。
举例:一个老师有多个学生,查询所有老师,并且查出老师的学生。
Student.java实体类如下:
package lixin.gan.pojo; public class Student { private int id; // 学生id private int age; // 学生年龄 private String name;// 学生姓名 private int tid; // 老师的id // 省略了构造方法,setter、getter、toString }
对应的StudentMapper.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="lixin.gan.mapper.StudentMapper"> <select id="selectStudentById" resultType="lixin.gan.pojo.Student"> select * from student where tid=#{0} </select> </mapper>
Teacher.java实体类内容如下:
package lixin.gan.pojo; import java.util.List; public class Teacher { private int id; private String name; private List<Student> list; // 省略了构造方法,setter、getter、toString }
TeacherMapper.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="lixin.gan.mapper.TeacherMapper"> <resultMap type="lixin.gan.pojo.Teacher" id="teacherMap"> <id column="id" property="id" /> <result column="name" property="name" /> <!-- 当关联的类型是集合类型,那么就需要使用collection标签,并且要指定ofType,表示集合中元素的值 --> <collection property="list" select="lixin.gan.mapper.StudentMapper.selectStudentById" ofType="lixin.gan.pojo.Student" column="id" ></collection> </resultMap> <select id="selectAllTeacher" resultMap="teacherMap"> select * from teacher </select> </mapper>
运行测试代码:
package lixin.gan.test; import java.io.InputStream; import java.util.List; import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; import lixin.gan.pojo.Teacher; public class Test { public static void main(String[] args) throws Exception{ InputStream config = Resources.getResourceAsStream("mybatis.xml"); SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(config); SqlSession session = factory.openSession(); List<Teacher> list = session.selectList("lixin.gan.mapper.TeacherMapper.selectAllTeacher"); for (Teacher teacher : list) { System.out.println(teacher); } } }
利用log4j打印的日志,以及程序运行输出如下:
==> Preparing: select * from teacher ==> Parameters: ====> Preparing: select * from student where tid=? ====> Parameters: 1(Integer) <==== Total: 1 ====> Preparing: select * from student where tid=? ====> Parameters: 2(Integer) <==== Total: 3 ====> Preparing: select * from student where tid=? ====> Parameters: 3(Integer) <==== Total: 0 ====> Preparing: select * from student where tid=? ====> Parameters: 4(Integer) <==== Total: 2 ====> Preparing: select * from student where tid=? ====> Parameters: 5(Integer) <==== Total: 1 <== Total: 5 Teacher [id=1, name=李老师, list=[Student [id=2, age=10, name=李四, tid=1]]] Teacher [id=2, name=黄老师, list=[Student [id=1, age=5, name=张三, tid=2], Student [id=5, age=22, name=小红, tid=2], Student [id=6, age=23, name=小花, tid=2]]] Teacher [id=3, name=王老师, list=[]] Teacher [id=4, name=蔡老师, list=[Student [id=4, age=30, name=赵六, tid=4], Student [id=7, age=30, name=小黄, tid=4]]] Teacher [id=5, name=方老师, list=[Student [id=3, age=25, name=王五, tid=5]]]