SSM框架之MyBatis3专题3:关联
短信预约 -IT技能 免费直播动态提醒
- 当查询内容涉及具有关联关系的多个表时,就需要使用关联关系查询。根据表与表之间的关联关系的不同,关联查询分为四种:
1、一对一关联查询;
2、一对多关联查询;
3、多对一关联查询;
4、多对多关联查询; - 由于日常工作中最常见的关联关系是一对多、多对一与多对多,所以这里就不专门只讲解一对一关联查询了,其解决方案与多对一解决方案是相同的。
1.1 一对多关联查询
- 这里的一对多关联查询是指,在查询一方对象的时候,同时将其所关联的多方对象也都查询出来。
- 下面以国家Country与部长Minister间的一对多关系进行演示。
1.1.1 定义实体
- 在定义实体时,若定义的是双向关联,即双方的属性中均有对方对象作为域属性出现,那么它们在定义各自的toString()方法时需要注意,只让某一方可以输出另一方即可,不要让双方的toString()方法均可输出对方。这样会形成递归调用,程序出错。
1.1.2 定义数据库表
1.1.3 定义Dao层接口
public interface ICountryDao { Country selectCountryById(int cid); }
1.1.4 定义测试类
public class Mytest { private SqlSession session; private ICountryDao dao; @Before public void setUp() { session = MyBatisUtils.getSqlSession(); dao = session.getMapper(ICountryDao.class); } @After public void tearDown() { if(session != null) { session.close(); } } @Test public void test01() { Country country = dao.selectCountryById(1); System.out.println(country); } }
1.1.5 定义映射文件
1、多表连接查询方式
<mapper namespace="com.eason.mybatis.dao.ICountryDao"> <resultMap type="Country" id="countryMapper"> <id column="cid" property="cid"/> <result column="cname" property="cname"/> <!-- 关联属性的映射文件 --> <collection property="ministers" ofType="Minister"> <id column="mid" property="mid"/> <result column="mname" property="mname"/> </collection> </resultMap> <!-- 多表连接查询 --> <select id="selectCountryById" resultMap="countryMapper"> select cid, cname, mid, mname from t_country, t_minister where cid=#{xxx} and cid=countryId </select> </mapper>
- 注意,此时即使字段名与属性名相同,在<resultMap/>中也写出它们的映射关系。因为框架是依据这个<resultMap/>封装对象的。
- 另外,在映射文件中使用<collection/>标签体现出两个实体对象间的关联关系。其两个属性的意义为:
- property:指定关联属性,即Country类中的集合属性;
- ofType:集合属性的泛型类型;
2、多表单独查询方式
- 多表连接查询方式是将多张表进行拼接,连为一张表后进行查询。其查询的本质是一张表。而多表单独查询方式是多张表各自查询各自的相关内容,需要多张表的联合数据了,则将主表的查询结果联合其他表的查询结果,封装为一个对象。
- 当然,这多个查询是可以跨越多个映射文件的。即是可以跨越多个namespace的。在使用其他namespace的查询时,添加上其所在的namespace即可。
<mapper namespace="com.eason.mybatis.dao.ICountryDao"> <select id="selectMinisterByCountry" resultType="Minister"> select mid, mname from t_minister where countryId=#{ooo} </select> <resultMap type="Country" id="countryMapper"> <id column="cid" property="cid"/> <result column="cname" property="cname"/> <!-- 关联属性的映射文件 --> <!-- 集合的数据来自于指定的select查询 --> <collection property="ministers" ofType="Minister" select="selectMinisterByCountry" column="cid"></collection> </resultMap> <!-- 多表连接查询 --> <select id="selectCountryById" resultMap="countryMapper"> select cid, cname from t_country where cid=#{xxx} </select> </mapper>
- 关联属性<collection/>的数据来自于一个查询<selectMinisterByCountry/>。而该查询<selectMinisterByCountry/>的动态参数countryId=#{ooo}的值来自于查询<selectCountryById/>的查询结果字段cid。
1.2 多对一关联查询
- 这里的多对一关联查询是指,在查询多方对象的时候,同时将其所关联的一方对象也查询出来。
- 由于在查询多方对象时也是一个一个查询,所以多对一关联查询,其实就是一对一关联查询。即一对一关联查询的实现方式与多对一的实现方式是相同的。
- 下面以部长Minister与国家Country间的多对一关联进行演示。
1.2.1 定义实体
public class Minister { private Integer mid; private String mname; private Country country; //setter and getter //toString }
public class Country { private Integer cid; private String cname; //setter and getter() //toString }
1.2.2 定义数据库表
1.2.3 定义Dao层接口
public interface ICountryDao { Minister selectMinisterById(int mid); }
1.2.4 定义测试类
@Test public void test02() { Minister minister = dao.selectMinisterById(2); System.out.println(minister); }
1.2.5 定义映射文件
1、多表连接查询方式:
<mapper namespace="com.eason.mybatis.dao.ICountryDao"> <resultMap type="Minister" id="ministerMapper"> <id column="mid" property="mid"/> <result column="mname" property="mname"/> <association property="country" javaType="Country"> <id column="cid" property="cid"/> <result column="cname" property="cname"/> </association> </resultMap> <select id="selectMinisterById" resultMap="ministerMapper"> select mid, mname, cid, cname from t_minister,t_country where mid=#{xxx} and countryId = cid </select> </mapper>
- 注意:在映射文件中使用<association/>标签体现出两个实体对象间的关联关系。
- property:指定关联属性,即Minister类中的country属性。
- javaType:关联属性的类型。
2、多表单独查询方式<mapper namespace="com.eason.mybatis.dao.ICountryDao"> <select id="selectCountryById" resultType="Country"> select * from t_country where cid=#{ooo} </select> <resultMap type="Minister" id="ministerMapper"> <id column="mid" property="mid"/> <result column="mname" property="mname"/> <association property="country" javaType="Country" select="selectCountryById" column="countryId"></association> </resultMap> <select id="selectMinisterById" resultMap="ministerMapper"> select mid, mname, countryId from t_minister where mid=#{xxx} </select> </mapper>
1.3 自关联查询
- 所谓自关联是指,自己即充当一方,又充当多方,是1:n或者n:1的变型。例如,对于新闻栏目NewsColumn,可以充当一方,即父栏目,也可以充当多方,即子栏目。而反映到DB表中,只有一张表,这张表中具有一个外键,用于表示该栏目的父栏目。一级栏目没有父栏目,所以可以将其外键值是为0,而子栏目具有外键值。
- 为了便于理解,将自关联分为两种情况来讲解,一种是当做1:n讲解,即当前类作为一方,其包含多方的集合域属性。一种是当做n:1讲解,即当前类作为多方,其包含一方的域属性。
- 下面以新闻栏目为例进行讲解。由于Column是DBMS中的关键字,为了避免误解,将新闻栏目实体类定义为NewsLabel。
1.3.1 自关联的DB表
1.3.2 以一对多方式处理
- 以一对多方式处理,即一方可以看到多方。该处理方式的应用场景比较多,例如在页面上点击父栏目,显示出其子栏目。再如,将鼠标定位在窗口中的某菜单项上会显示其所有子菜单项等。
1、查询指定栏目的所有子孙栏目: - 根据指定的id,仅查询出其所有子栏目。当然,包括其所有辈分的孙子栏目。即,给出的查询id实际为父栏目id。
- 定义实体类:
public class NewsLabel { private Integer id; private String name; //关联属性,指定子栏目,即多方 private Set<NewsLabel> children; //getter and setter //toString() }
- 定义Dao接口:
public interface INewsLabelDao { List<NewsLabel> selectChidrenByParentId(int pid); }
- 定义mapper映射:这里通过select语句的递归调用实现查询所有下级栏目的功能。查询结果的集合数据<collection/>来自于递归调用的selectChidrenByParentId查询。与第一次进行该查询不同的是,第一次的pid动态参数值来自于调用方法传递来的实参,而<collection/>中查询语句的pid动态参数数值来自于上一次的查询结果的id值。
<mapper namespace="com.eason.mybatis.dao.INewsLabelDao"> <!-- 形成递归,因为查询结果再次调用了selectChidrenByParentId查询 --> <resultMap type="NewsLabel" id="newsLabelMapper"> <id column="id" property="id"/> <result column="name" property="name"/> <collection property="children" ofType="NewsLabel" select="selectChidrenByParentId" column="id"></collection> </resultMap> <!-- 根据pid查询其子栏目 --> <select id="selectChidrenByParentId" resultMap="newsLabelMapper"> select id, name from t_newslabel where pid = #{xxx} </select> </mapper>
- 定义测试类:
public class Mytest { private SqlSession session; private INewsLabelDao dao; @Before public void setUp() { session = MyBatisUtils.getSqlSession(); dao = session.getMapper(INewsLabelDao.class); } @After public void tearDown() { if(session != null) { session.close(); } } @Test public void test02() { List<NewsLabel> children = dao.selectChidrenByParentId(1); for(NewsLabel newsLabel : children) { System.out.println(newsLabel); } } }
2、查询指定栏目以及所有子孙栏目
- 这里的查询结果,即要包含指定id的当前栏目,还要包含其所有辈分的孙子栏目。即给出的id实际为当前要查询的栏目的id。
- 修改Dao接口:
public interface INewsLabelDao { NewsLabel selectNewsLabelById(int id); }
-
修改mapper映射:
<mapper namespace="com.eason.mybatis.dao.INewsLabelDao"> <select id="selectNewsLabelByParentId" resultMap="newsLabelMapper"> select id, name from t_newslabel where pid = #{ooo} </select> <resultMap type="NewsLabel" id="newsLabelMapper"> <id column="id" property="id"/> <result column="name" property="name"/> <collection property="children" ofType="NewsLabel" select="selectNewsLabelByParentId" column="id"></collection> </resultMap> <select id="selectNewsLabelById" resultMap="newsLabelMapper"> select id, name from t_newslabel where id = #{xxx} </select> </mapper>
- 修改测试类:
@Test public void test02() { NewsLabel newsLabel = dao.selectNewsLabelById(1); System.out.println(newsLabel); }
1.3.3 以多对一方式处理
- 以多对一方式处理,即多方可以看到一方。该处理方式的应用功能场景,例如在网页上显示当前页面的站内位置。
-
定义实体类:
public class NewsLabel { private Integer id; private String name; private NewsLabel parent; //setter and getter() //toString }
- 定义mapper映射:
<mapper namespace="com.eason.mybatis.dao.INewsLabelDao"> <resultMap type="NewsLabel" id="newslabelMapper"> <id column="id" property="id"/> <result column="name" property="name"/> <association property="parent" javaType="NewsLabel" select="selectParentByParentId" column="pid"> </association> </resultMap> <select id="selectParentByParentId" resultMap="newslabelMapper"> select id,name,pid from t_newslabel where id=#{xxx} </select> </mapper>
- 定义测试类:
@Test public void test03() { NewsLabel newsLabel = dao.selectParentByParentId(3); System.out.println(newsLabel); }
1.4 多对多关联查询
- 什么是多对多关联关系?一个学生可以选择多门课程,而一门课程可以由多个学生选。这就是典型的多对多关联关系。所以,所谓多对多关系,其实是由两个互反的一对多关系组成。一般情况下,多对多关系都会通过一个中间表来建立,例如选课表。
1.4.1 定义实体
- 在定义双向关联(双方均可看到对方的关联关系)的实体的toString()方法时,只会让一方的toString()方法中可以输出对方,不要让双方均可输出对方。否则将会出现的递归现象,程序会报错。
public class Student { private Integer sid; private String sname; private Set<Course> courses; //setter and getter() //toString() }
public class Course { private Integer cid; private String cname; private Set<Student> students; //setter and getter() //toString() }
1.4.2 定义数据库表
1.4.3 定义Dao层接口
public interface IStudentDao { Student selectStudentById(int id); }
1.4.4 定义mapper映射
- 多对多关联关系也是通过映射文件<resultMap/>的<collection/>体现的。但是,需要注意的是SQL语句中是对三张表的连接查询。
<mapper namespace="com.eason.mybatis.dao.IStudentDao"> <resultMap type="Student" id="studentMapper"> <id column="sid" property="sid"/> <result column="sname" property="sname"/> <collection property="courses" ofType="Course"> <id column="cid" property="cid"/> <result column="cname" property="cname"/> </collection> </resultMap> <select id="selectStudentById" resultMap="studentMapper"> select sid, sname, cid, cname from t_student, t_middle, t_course where sid = studentId and cid = courseId and sid = #{xxx} </select> </mapper>
1.4.5 定义测试类
@Test public void test05() { Student student = dao.selectStudentById(2); System.out.println(student); }
- MyBatis中的延迟加载,也称之为懒加载,是指在进行关联查询时,按照设置规则推迟对关联对象的select查询。延迟加载可以有效的减少数据库压力。
- 需要注意的是,MyBatis的延迟加载只是对关联对象的查询有延迟设置,对于主加载对象都是直接执行查询语句的。
2.1 关联对象加载时机
- MyBatis根据对关联对象查询的select语句的执行时期,分为三种类型:直接加载、侵入式延迟加载和深度延迟加载。
1、直接加载:执行完对主加载对象的select语句,马上执行对关联对象的select查询。
2、侵入式延迟:执行完对主加载对象的查询时,不会执行对关联对象的查询。但当要访问主加载对象的详情时,就会马上执行关联对象的select查询。即对关联对象的查询执行,侵入到了主加载对象的详情访问中。也可以这样理解:将关联对象的详情侵入到了主加载对象的详情中,即将关联对象的详情作为主加载对象的详情的一部分出现。
3、深度延迟:执行对主加载对象的查询时,不会执行对关联对象的查询。访问主加载对象的详情时也不会执行关联对象的select查询。只有当真正访问关联对象的详情时,才会执行对关联对象的select查询。 - 需要注意的是,延迟加载的应用要求,关联对象的查询与主加载对象的查询必须是分别进行的select语句,不能够是多表连接所进行的select查询。因为,多表连接查询,其实质是对一张表的查询,对由多个表连接后形成的一张表的查询。会一次性将多张表的所有信息查询出来。
- MyBatis中对于延迟加载设置,可以应用到一对一、一对多、多对多的所有关联关系查询中。
- 下面以一对多关联关系查询为例,讲解MyBatis中的延迟加载应用。
2.2 直接加载
- 修改主配置文件:在主配置文件的<properties/>与<typeAliases/>标签之间,添加<setting/>标签,用于完成全局参数设置。
<!-- 注册属性文件 --> <properties resource="jdbc.properties"></properties> <!-- 全局参数设置 --> <settings> <setting name="lazyLoadingEnabled" value="false"/> </settings> <!-- 注册类的别名 --> <typeAliases> <package name="com.eason.mybatis.beans"/> </typeAliases>
- 在MyBatis帮助文档中Ctrl+F查询关键字“lazy”,则可查询出延迟加载的相关参数名称以及取值。
- 全局属性lazyLoadingEnabled的值只要设置为false,那么,对于关联对象的查询,将采用直接加载。即在查询过主加载对象后,会马上查询关联对象。(对于标签的书写位置,是由约束文件进行规定好的,不能随便写。在<configuration/>标签上点击F2,可查看的顺序以及数量要求。)
- 标签数量上要求说明用到符号为:
1、?:表示子标签可以没有,若有的话,最多只能有一个,即小于等于1;
2、*:表示子标签可以没有,可以有多个,即大于等于0;
3、+:表示子标签最少要有一个,即大于等于1;
4、没有符号:表示有且只能够有一个,即等于1;2.3 深度延迟加载
- 修改主配置文件的<settrings/>,将延迟加载开关lazyLoadingEnabled开启(设置为true),将侵入式延迟加载开关aggressiveLazyLoading关闭(设置为false)。
<!-- 全局参数设置 --> <settings> <!-- 延迟加载总开关 --> <setting name="lazyLoadingEnabled" value="true"/> <!-- 侵入式延迟加载开关 --> <setting name="aggressiveLazyLoading" value="false"/> </settings>
2.4 侵入式延迟加载
- 修改主配置文件的<settings/>,将延迟加载开关lazyLoadingEnabled开启(设置为true),将侵入式延迟加载开关aggressiveLazyLoading也开启(设置为true,默认为true)。
<!-- 全局参数设置 --> <settings> <!-- 延迟加载总开关 --> <setting name="lazyLoadingEnabled" value="true"/> <!-- 侵入式延迟加载开关 --> <setting name="aggressiveLazyLoading" value="true"/> </settings>
- 需要注意的是,该延迟策略也是一种延迟加载,需要在延迟加载开关lazyLoadingEnabled开启时才会其作用。若lazyLoadingEnabled为false,则aggressiveLazyLoading无论取何值,均不会起作用。
免责声明:
① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。
② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341