AI智能
改变未来

三大框架之—MyBaits第三章


第三章 关联关系查询

3.1关联查询

当查询内容涉及到具有关联关系的多个表时,就需要使用关联查询。
一共分为如下四种

3.1.1一对多关联查询

/11-one2many
下面以国家Country与部长Minister间的一对多关系进行演示。
国家类 一方可以看到多方 一对多
**private Set ministers;**声明一个集合

import java.util.Set;//一方可以看到多方 一对多public class Country {private Integer cid;private String cname;//关联属性private Set<Minister> ministers;//一对多关联,一般用set,无序且不能重复public Integer getCid() {return cid;}public String getCname() {return cname;}public Set<Minister> getMinisters() {return ministers;}public void setCid(Integer cid) {this.cid = cid;}public void setCname(String cname) {this.cname = cname;}public void setMinisters(Set<Minister> ministers) {this.ministers = ministers;}@Overridepublic String toString() {return \"Country [cid=\" + cid + \", cname=\" + cname + \", ministers=\" + ministers + \"]\";}}

部长类

public class Minister {private Integer mid;private String mname;public Integer getMid() {return mid;}public String getMname() {return mname;}public void setMid(Integer mid) {this.mid = mid;}public void setMname(String mname) {this.mname = mname;}@Overridepublic String toString() {return \"Minister [mid=\" + mid + \", mname=\" + mname + \"]\";}}

定义数据库表


Dao层接口

import com.bjpowernode.beans.Country;public interface ICountryDao {Country selectCountryById(int cid);}

测试类

public class MyTest {private ICountryDao dao;private SqlSession sqlSession;@Beforepublic void before() {sqlSession = MyBatisUtils.getSqlSession();dao = sqlSession.getMapper(ICountryDao.class);}@Afterpublic void after() {if (sqlSession != null) {sqlSession.close();}}@Testpublic void test01() {Country country = dao.selectCountryById(2);System.out.println(country);sqlSession.commit();}}

映射文件
因为查询包含的部长的是一个集合,用collection
与之前的例子对比,当我们返回的是一个Student类型的list集合,使用resultType,因为在测试类里已经为其赋值过了;当返回的是Country对象,包含两个类对象的查询时,需要在映射文件中使用resultMap为他们两个赋值。
而且前者是一个类,后者是关联查询多个类
这种写法用的多,因为可以使用延迟加载
一对多ofType

<mapper namespace=\"com.bjpowernode.dao.ICountryDao\"><select id=\"selectMinisterByCountry\" resultType=\"Minister\">select mid,mname from minister where countryId=#{ooo}</select><resultMap type=\"Country\" id=\"countryMapper\"><id column=\"cid\" property=\"cid\"/><result column=\"cname\" property=\"cname\"/><collection property=\"ministers\"ofType=\"Minister\"select=\"selectMinisterByCountry\"column=\"cid\"/></resultMap><select id=\"selectCountryById\" resultMap=\"countryMapper\">select cid,cname from country where cid=#{xxx}</select></mapper>

另外一种写法

<mapper namespace=\"com.bjpowernode.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,mnamefrom country,ministerwhere cid = countryId and cid=#{xxx}</select></mapper>

主配置文件不变

多对一关联查询

/13-many2one
指查询多方对象的时候,同时将其所关联的一方对象也查询出来。
一对一关联查询的实现方式与多对一的实现方式相同的。
定义实体类需要改变成员变量了
国家类删掉部长集合 其他set、get、toString方法省略

public class Country {private Integer cid;private String cname;}

部长类 声明Country成员变量
//关联属性
private Country country;

public class Minister {private Integer mid;private String mname;//关联属性private Country country;}

Dao层接口

public interface IMinisterDao {Minister selectMinisterById(int mid);}

测试类 其他省略

@Testpublic void test01() {Minister minister = dao.selectMinisterById(2);System.out.println(minister);sqlSession.commit();}

映射文件 Minister对应一个国家,所以使用了association为其赋值,且使用的是javaType

常用写法

<!-- 定义select 其中的ooo由column值得到 --><select id=\"selectCountryById\" resultType=\"Country\">select cid,cname from country where cid=#{ooo}</select><resultMap type=\"Minister\" id=\"ministerMapper\"><id column=\"mid\" property=\"mid\"/><result column=\"mname\" property=\"mname\"/><!-- 那这里这个country通过另外一个select语句得到 --><association property=\"country\"javaType=\"Country\"select=\"selectCountryById\"column=\"CountryId\"/></resultMap><select id=\"selectMinisterById\" resultMap=\"ministerMapper\"><!-- 通过ministerMapper找get,set方法为属性赋值 -->select mid,mname,countryId from minister where mid=#{xxx}</select>

另一种写法

```java<mapper namespace=\"com.bjpowernode.dao.IMinisterDao\"><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\"><!-- 通过ministerMapper找get,set方法为属性赋值 -->select mid,mname,cid,cnamefrom minister,countrywhere countryId=cid and mid=#{xxx}</select></mapper>

3.1.3 自关联查询

自己充当一方,又充当多方。这里举例新闻栏目NewsLabel
创建DB表

3.1.3 A、以一对多方式处理

1)查询指定栏目的所有子栏
/15-oneself-one2many
新闻栏目:当前的新闻栏目被看作是一方,即父栏目
所以添加set集合
set、get方法等省略

public class NewsLabel {private Integer id;private String name; //栏目名称private Set<NewsLabel> children;}

Dao层接口 返回类型是集合

public interface INewsLabelDao {List<NewsLabel> selectChildernByParent(int pid);}

定义mapper映射文件
这里通过select语句的递归调用实现查询所有下级栏目的功能。查询结果的结合数据collection来自于递归调用的selectChildernByParent查询。与第一次不同的是,第一次的pid动态参数来自于调用方法传来的实参,而collection中查询的pid来自于上次查询结果的id。

<resultMap type=\"NewsLabel\" id=\"newslabelMapper\"><id column=\"id\" property=\"id\"/><result column=\"name\" property=\"name\"/><collection property=\"children\"ofType=\"NewsLabel\"select=\"selectChildernByParent\"column=\"id\"/></resultMap><select id=\"selectChildernByParent\" resultMap=\"newslabelMapper\">select id,name from newslabel where pid=#{xxx}</select>

测试类 用foreach遍历list集合

@Testpublic void test01() {List<NewsLabel> children = dao.selectChildernByParent(1);for(NewsLabel newsLabel : children) {System.out.println(newsLabel);}sqlSession.commit();}

2)查询指定栏目及其所有的子栏目
/16-oneself-one2many-2
修改Dao层 返回的是NewsLabel对象,不是集合

public interface INewsLabelDao {NewsLabel selectNewsLabelById(int id);}

修改映射

<select id=\"selectNewsLabelByParent\" resultMap=\"newslabelMapper\">select id,name from 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=\"selectNewsLabelByParent\"column=\"id\"/></resultMap><select id=\"selectNewsLabelById\" resultMap=\"newslabelMapper\">select id,name from newslabel where id=#{xxx}</select>

修改测试

@Testpublic void test01() {NewsLabel newslabel = dao.selectNewsLabelById(1);System.out.println(newslabel);sqlSession.commit();}

3.1.3 B、以多对一方式处理

/17-oneself-many2one
新闻栏目:当前的新闻栏目被看作是多方,即子栏目

public class NewsLabel {private Integer id;private String name; //栏目名称private NewsLabel parent;}

Dao层接口

public interface INewsLabelDao {NewsLabel selectNewsLabelById(int id);}

映射

<resultMap type=\"NewsLabel\" id=\"newslabelMapper\"><id column=\"id\" property=\"id\"/><result column=\"name\" property=\"name\"/><association property=\"parent\"javaType=\"NewsLabel\"select=\"selectNewsLabelById\"column=\"pid\"/></resultMap><select id=\"selectNewsLabelById\" resultMap=\"newslabelMapper\">select id,name,pid from newslabel where id=#{xxx}</select>

测试

@Testpublic void test01() {NewsLabel newslabel = dao.selectNewsLabelById(3);System.out.println(newslabel);sqlSession.commit();}

3.1.4 多对多关联查询

一个学生可以选多门课程,而一门课程可以由多个学生选。这就是典型的多对多关系。所起,所谓多对多关系,其实是由两个互反的一对多关系组成。
一般,多对多都会通过一个中间表来建立。
建表



定义实体
只能让一方的toString()方法中可以输出对方,不能双方同时输出。否则会出现输出时的递归现象,程序报错。


定义Dao层

public interface IStudentDao {Student selectStudentById(int sid);}

定义mapper映射

<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,cnamefrom student,middle,coursewhere sid=studentId and cid=courseId and sid=#{xxx}</select>

定义测试

@Testpublic void test01() {Student student = dao.selectStudentById(1);System.out.println(student);sqlSession.commit();}

3.2 延迟加载

MyBatis根据对关联对象查询的select语句的执行时机,分为三种类型:直接加载、侵入式延迟加载与深度延迟加载。
直接加载:执行完主加载对象的select语句,马上执行对关联对象的select查询。
侵入式延迟:执行对主加载对象的查询时,不会执行对关联对象的查询。但当要访问主加载对象的详情时,会马上执行对关联对象的select查询。
深度延迟:执行对主加载对象的查询时,不会执行对关联对象的查询。访问主加载对象的详情时也不会执行关联对象的查询。只有当真正访问关联对象的详情时,才会执行对关联对象的select查询。
需要注意:延迟加载只能应用在,关联对象的查询与主加载对象的查询必须是分别的select语句,不能是多表连接。

直接加载

/19-lazyLoad
主配置文件
全局属性lazyLoadingEnabled的值只要设置为false,那么,对于关联对象的1查询,将再用直接加载。默认为false

<configuration><!--注册DB连接四要素属性文件  --><properties resource=\"jdbc.properties\"/><!-- 设置整个应用所使用的常量 --><settings><!-- 延迟加载的总开关 --><setting name=lazyLoadingEnabled\" value=\"true\"/></settings><!-- 定义类型别名 --><typeAliases><!-- <typeAlias type=\"com.bjpowernode.beans.Student\" alias=\"Student\"/> --><!-- 将指定包中所有类的简单类名当作其别名 --><package name=\"com.bjpowernode.beans\"/></typeAliases>

测试类 加断点

@Testpublic void test01() {Country country = dao.selectCountryById(2);//此处加断点System.out.println(country.getCname());System.out.println(country.getMinisters().size());}

结果:当程序执行到断点出语句时,不仅对country进行了查询,也对minister表同时进行了查询。

深度延迟加载

主配置
总开关打开,并将侵入式延迟加载开关关闭(置为false)。

<configuration><!--注册DB连接四要素属性文件  --><properties resource=\"jdbc.properties\"/><!-- 设置整个应用所使用的常量 --><settings><!-- 延迟加载的总开关 --><setting name=lazyLoadingEnabled\" value=\"true\"/><!-- 侵入式延迟加载开关 --><setting name=\"aggressiveLazyLoading\" value=\"false\"/></settings><!-- 定义类型别名 --><typeAliases><!-- <typeAlias type=\"com.bjpowernode.beans.Student\" alias=\"Student\"/> --><!-- 将指定包中所有类的简单类名当作其别名 --><package name=\"com.bjpowernode.beans\"/></typeAliases>

结果: 只有当代码执行到Minister对象详情时,底层才执行了select语句对minister表进行查询。

侵入式延迟加载

修改主配置文件

<configuration><!--注册DB连接四要素属性文件  --><properties resource=\"jdbc.properties\"/><!-- 设置整个应用所使用的常量 --><settings><!-- 延迟加载的总开关 --><setting name=lazyLoadingEnabled\" value=\"true\"/><!-- 侵入式延迟加载开关 --><setting name=\"aggressiveLazyLoading\" value=\"true\"/></settings><!-- 定义类型别名 --><typeAliases><!-- <typeAlias type=\"com.bjpowernode.beans.Student\" alias=\"Student\"/> --><!-- 将指定包中所有类的简单类名当作其别名 --><package name=\"com.bjpowernode.beans\"/></typeAliases>

当执行到断点时会立即查询country,但没有查询minister表。
但继续执行下一语句,对Country对象详情访问时,对minister表也进行了查询。

4、查询缓存

主要是为了提高查询访问速度。将用户对同一数据的重复查询过程简化,不用每次都从数据库查询获取结果。

4.1 一级查询缓存

MyBatis一级查询缓存时基于HashMap本地缓存,其作用域时SqlSession。在同一个SqlSession中两次执行相同的sql查询,会将第一次结果写入缓存,第二次直接读取,不再到数据库中查。
当一个SqlSession结束后,该SqlSession中一级缓存不复存在了。MyBatis默认一级查询缓存开启,不能关闭。

4.1.1 一级查询缓存的存在性证明

/20-sqlSessionCache
Dao层接口

public interface IStudentDao {void insertStudent(Student student);Student selectStudentById(int id);}

映射

<select id=\"selectStudentById\" resultType=\"Student\">select id,name,age,score from student where id=#{ooo}</select>

测试类

//证明一级缓存是存在的@Testpublic void test01() {//一级缓存的存在性Student student = dao.selectStudentById(2);System.out.println(student);Student student2 = dao.selectStudentById(2);System.out.println(student2);}

结果说明第二次是直接输出的。

4.1.2 从缓存读取数据的依据是SQL的id
缓存的底层实现是一个Map,Map的value是查询结果
Map的key,即查询依据,使用的ORM架构不同,查询依据是不同的。
Mybatis的查询依据是:SQL的id + SQL语句
Hibernate的查询依据是:查询结果对象的id,因为如下结果执行了两次select
修改映射文件

<select id=\"selectStudentById\" resultType=\"Student\">select id,name,age,score from student where id=#{ooo}</select><select id=\"selectStudentById2\" resultType=\"Student\">select id,name,age,score from student where id=#{ooo}</select>

修改Dao接口

public interface IStudentDao {void insertStudent(Student student);Student selectStudentById(int id);Student selectStudentById2(int id);}

修改测试类

@Testpublic void test02() {//一级缓存的存在性Student student = dao.selectStudentById(2);System.out.println(student);Student student2 = dao.selectStudentById2(2);System.out.println(student2);}

结果:发现两次结果一致,但第二次没有从缓存读取,而是直接从DB中查。

4.1.3 增删改对一级查询缓存的影响

增删改无论是否进行提交sqlSession.commit(),均会清空一级查询缓存,使查询再次从DB中selec。
映射文件中添加插入

<insert id=\"insertStudent\">insert into student(name,age,score) values(#{name}, #{age}, #{score})</insert><select id=\"selectStudentById\" resultType=\"Student\">select id,name,age,score from student where id=#{ooo}</select><select id=\"selectStudentById2\" resultType=\"Student\">select id,name,age,score from student where id=#{ooo}</select>

修改测试

@Testpublic void test03() {Student student = dao.selectStudentById(2);System.out.println(student);//增删改操作都会清空一级缓存,无论是否提交dao.insertStudent(new Student(\"赵六\", 26, 96.5));Student student2 = dao.selectStudentById(2);System.out.println(student2);}

结果:

4.2 内置二级查询缓存

二级缓存的生命周期会与整个应用同步,与SqlSession是否关闭无关。
目的不是共享,而是为了延长该查询结果的保存时间,提高系统性能。
但二级查询使用条件苛刻,实际用的也少,在这里不多赘述。

赞(0) 打赏
未经允许不得转载:爱站程序员基地 » 三大框架之—MyBaits第三章