目录
MyBatis----04----多表操作…嵌套查询…类加载机制
1. 多表关联查询
1.1 准备表结构
表结构介绍:
1.2 准备实体类
Account实体:
public class Account {
private Integer id;//主键
private Double money;//余额
//关联唯一的用户 => 多对一
private User user;
User实体:
public class User {
private Integer id;//主键
private String name;//用户名
private String password;//用户密码
//一个用户下有多个账户 => 一对多
private List<Account> accounts;
//一个用户有多个角色 => 多对多
private List<Role> roles;
Role实体:
public class Role {
private Integer id;//主键
private String role_name;//角色名称
private String role_desc;//角色描述
//一个角色下包含多个用户 => 多对多
private List<User> users;
1.3 多对一|一对多
1.3.1 采用别名方式进行映射(不推荐)
<select id="findAll1" resultType="Account">
SELECT
a.*,
u.id 'user.id',
u.name 'user.name',
u.password 'user.password'
FROM
account a
LEFT JOIN t_user u
ON a.uid = u.id
</select>
1.3.2 使用ResultMap进行映射
ResultMap配置:
<!-- 结果映射配置 -->
<resultMap id="accountMap1" type="Account">
<id property="id" column="id"></id>
<result property="money" column="money"></result>
<!-- user.id表示映射user属性中的name属性 -->
<result property="user.id" column="uid"></result>
<result property="user.name" column="name"></result>
<result property="user.password" column="password"></result>
</resultMap>
select元素配置:
<select id="findAll2" resultMap="accountMap1">
SELECT
*
FROM
account a
LEFT JOIN t_user u
ON a.uid = u.id
</select>
1.3.3 使用resultMap+association进行映射配置
resultMap配置:
<resultMap id="accountMap2" type="Account">
<id property="id" column="id"></id>
<result property="money" column="money"></result>
<!-- association:表示要封装一个对象类型的属性
property:属性名
javaType:属性对应的java类型
-->
<association property="user" javaType="User">
<!-- 配置User对象中的属性与列的映射 -->
<result property="id" column="uid"></result>
<result property="name" column="name"></result>
<result property="password" column="password"></result>
</association>
</resultMap>
select元素配置:
<select id="findAll3" resultMap="accountMap2">
SELECT
*
FROM
account a
LEFT JOIN t_user u
ON a.uid = u.id
</select>
1.4 一对多|多对多
1.4.1 一对多
resultMap配置:
<resultMap id="userMapperWithAccount" type="User">
<id property="id" column="id"></id>
<result property="name" column="name"></result>
<result property="password" column="password"></result>
<!-- collection:表示将集合进行封装
property:集合属性名
ofType:集合中封装的对象类型
-->
<collection property="accounts" ofType="Account">
<!-- 集合中的对象的属性|列映射配置 -->
<id property="id" column="aid"></id>
<result property="money" column="money"></result>
</collection>
</resultMap>
select元素配置:
<select id="findAllWithAccount" resultMap="userMapperWithAccount">
SELECT
*,a.id 'aid'
FROM
t_user u
LEFT JOIN account a
ON u.id = a.uid
</select>
1.4.2 多对多
resultMap配置:
<resultMap id="userMapperWithRoles" type="User">
<id property="id" column="id"></id>
<result property="name" column="name"></result>
<result property="password" column="password"></result>
<!-- collection:表示将集合进行封装
property:集合属性名
ofType:集合中封装的对象类型
-->
<collection property="roles" ofType="Role">
<!-- 集合中的对象的属性|列映射配置 -->
<id property="id" column="rid"></id>
<result property="role_name" column="role_name"></result>
<result property="role_desc" column="role_desc"></result>
</collection>
</resultMap>
select配置:
<select id="findAllWithRoles" resultMap="userMapperWithRoles">
SELECT
*
FROM
t_user u
LEFT JOIN user_role ur
ON u.id = ur.uid
LEFT JOIN role r
ON ur.rid = r.id
</select>
2. 嵌套查询
我们在进行多表查询时,查询多表数据有两种查询方式,一种是直接使用表连接语句,将多表中的数据一次查询出来,还有一种查询办法,将查询氛围多条sql语句。
例1,查询User以及关联的Account(表连接查询):
例2,查询User以及关联的Account(执行多条sql)
这两种查询方式,但从执行效率角度来考虑,例1的效率会更高一些,但是实际开发中,业务角度来看,我们可能有的时候只会用到User一级的数据,Account这一级的数据使用不到,如果仍然使用例1方案,那么将会浪费我们的资源。包括查询不需要的Account的资源浪费,对返回Account结果的封装保存资源浪费。
总结:
- 例1方案,适用于确定要使用级联数据时,使用表连接可以提高查询效率;
- 例2方案,适用于对数据使用情况不确定时,可以根据实际使用情况灵活进行查询,比如只用到User以及数据,就不会查询Account。嵌套查询属于方案2查询
2.1 多对一|一对一
例子:查询Account以及Account关联的User对象。
定义AccountMapper中的方法:
//根据id查询accoun
Account findById(int id);
定义AccountMapper.xml中的配置:
<resultMap id="accountMap1" type="Account">
<id property="id" column="id"></id>
<result property="money" column="money"></result>
<!-- association:表示要封装一个对象类型的属性
property:属性名
javaType:属性对应的java类型
select:访问该属性时执行什么查询获得该属性
column:执行select属性指定的查询时,使用哪一列的值作为参数传给select查询
fetchType:加载策略选择
-->
<association property="user" javaType="User"
select="com.leo.mapper.UserMapper.findById"
column="uid" fetchType="lazy"
/>
</resultMap>
<select id="findById" resultMap="accountMap1">
select * from account where id =#{id}
</select>
定义UserMapper中的findById方法:
//根据id查询User
User findById(int id);
定义UserMapper.xml:
<select id="findById" resultType="User">
select * from t_user where id = #{id}
</select>
执行测试:
public void testFindById(){
AccountMapper accountMapper = session.getMapper(AccountMapper.class);
//获得id为1的Account
Account account = accountMapper.findById(1);
//打印Account本身的属性
System.out.println(account.getId() + "==" + account.getMoney());
//打印Account关联的User属性
System.out.println(account.getUser());
}
测试结果(控制台):
==> Preparing: select * from account where id =?
==> Parameters: 1(Integer)
<== Columns: ID, UID, MONEY
<== Row: 1, 1, 1000.0
<== Total: 1
1==1000.0
==> Preparing: select * from t_user where id = ?
==> Parameters: 1(Integer)
<== Columns: id, name, password
<== Row: 1, tom, 1234
<== Total: 1
User{id=1, name='tom', password='1234', accounts=null, roles=null}
结论:
通过测试代码,以及结合控制台打印可以看出,当我们执行zccountMapper.findById方法时,只查询了Account数据,在执行System.out.println(account.getUser())语句时,访问了User属性,触发了MyBatis查询User。
2.2 一对多
UserMapper方法:
//根据id查询User
User findById(int id);
UserMapper.xml:
<resultMap id="userMapperWithAccount" type="User">
<id property="id" column="id"></id>
<result property="name" column="name"></result>
<result property="password" column="password"></result>
<!-- collection:表示将集合进行封装
property:集合属性名
ofType:集合中封装的对象类型
select:当我们访问集合数据时,集合数据调用哪个方法去查
column:指定调用select方法时,使用哪一列的值作为select指定方法的参数
fetchType:指定加载类型
-->
<collection property="accounts" ofType="Account"
select="com.leo.mapper.AccountMapper.findByUid"
column="id" fetchType="lazy"
/>
</resultMap>
<select id="findById" resultType="User" resultMap="userMapperWithAccount">
select * from t_user where id = #{id}
</select>
AccountMapper接口:
//根据用户id,查询用户关联的账户
List<Account> findByUid(int uid);
AccountMapper.xml:
<select id="findByUid" resultType="Account">
select * from account where uid = #{uid}
</select>
测试代码;
@Test
public void testFindById(){
UserMapper userMapper = session.getMapper(UserMapper.class);
User user = userMapper.findById(1);
//没有访问关联的Account
System.out.println(user.getName() + "=>" + user.getId());
//访问关联的Account -> 触发调用com.leo.mapper.UserMapper.findById
System.out.println(user.getAccounts());
}
测试结果(工作台):
==> Preparing: select * from t_user where id = ?
==> Parameters: 1(Integer)
<== Columns: id, name, password
<== Row: 1, tom, 1234
<== Total: 1
tom=>1
==> Preparing: select * from account where uid = ?
==> Parameters: 1(Integer)
<== Columns: ID, UID, MONEY
<== Row: 1, 1, 1000.0
<== Row: 4, 1, 888.0
<== Total: 2
2.3 多对多
UserMapper接口:
//根据id查询User -> 级联查询User关联的Role
User findById2(int id);
UserMapper.xml:
<resultMap id="userMapperWithRole" type="User">
<id property="id" column="id"></id>
<result property="name" column="name"></result>
<result property="password" column="password"></result>
<!-- collection:表示将集合进行封装
property:集合属性名
ofType:集合中封装的对象类型
select:当我们访问集合数据时,集合数据调用哪个方法去查,调用的查询如果就在当前mapper中,只写方法名即可
column:指定调用select方法时,使用哪一列的值作为select指定方法的参数
fetchType:指定加载类型
-->
<collection property="roles" ofType="Role"
select="findRolesByUserId"
column="id" fetchType="lazy"
/>
</resultMap>
<select id="findById2" resultMap="userMapperWithRole">
select * from t_user where id = #{id}
</select>
UserMapper接口中定义findRoleByUserId方法;
//跟据用户id查询关联的对象
List<Role> findRolesByUserId(int uid);
UserMapper.xml中定义findRoleByUserId:
<select id="findRolesByUserId" resultType="Role">
SELECT
r.*
FROM
user_role ur,role r
WHERE
ur.rid = r.id
AND
ur.uid = 1
</select>
测试代码:
@Test
public void testFindById2(){
UserMapper userMapper = session.getMapper(UserMapper.class);
User user = userMapper.findById2(1);
//没有访问关联的Account
System.out.println(user.getName() + "=>" + user.getId());
//访问关联的Role -> 触发调用com.leo.mapper.UserMapper.findRoleByUserId
System.out.println(user.getRoles());
}
测试结果(工作台):
==> Preparing: select * from t_user where id = ?
==> Parameters: 1(Integer)
<== Columns: id, name, password
<== Row: 1, tom, 1234
<== Total: 1
tom=>1
==> Preparing: SELECT r.* FROM user_role ur,role r WHERE ur.rid = r.id AND ur.uid = 1
==> Parameters:
<== Columns: ID, ROLE_NAME, ROLE_DESC
<== Row: 1, 院长, 管理整个学院
<== Row: 2, 总裁, 管理 整个公司
<== Total: 2
[Role{id=1, role_name='院长', role_desc='管理整个学院', users=null}, Role{id=2, role_name='总裁', role_desc='管理 整个公司', users=null}]
3 加载策略
3.1 什么时加载策略
当多个模型之间存在联系时,在加载一个模型的数据时,是否随之加载与其关联的模型数据的策略,我们就称之为加载策略。
例如,在前面嵌套查询的案例中,一堆多的例子,我们首先根据id查询了User对象,User关联了Account对象。
我们是否需要在第一时间加载User关联的Account,还是等到使用User关联的Account时,再加载Account,这就是加载策略的选择。
总结:
- 立即加载:无论是否使用关联属性(模型),都立即查询;
- 延迟加载(懒加载):当访问(使用)关联属性时,采取加载关联数据。
3.2 MyBatis加载策略配置
全局配置,在mybatis-config.xml中配置:
<!-- 全局配置 -->
<settings>
<!-- 是否启用延迟加载的开关
true:延迟加载
false:立即加载
-->
<setting name="lazyLoadingEnable" value="true"/>
在xxxMapper.xml中配置:
<!-- fetchType:指定加载策略,回覆盖mybatis-config.xml配置的全局配置
lazy:延迟加载
eager:立即加载
-->
<association property="user" javaType="User"
select="com.leo.mapper.UserMapper.findById"
column="uid" fetchType="lazy"
/>
<!-- fetchType:指定加载策略,回覆盖mybatis-config.xml配置的全局配置
lazy:延迟加载
eager:立即加载
-->
<collection property="roles" ofType="Role"
select="findRolesByUserId"
column="id" fetchType="lazy"
/>
注意:默认情况,调用对象的toString | equals | clone | hashcode这4个方法都会触发关联对象的加载。调用关联对象的getXXX方法也会触发关联对象的加载
当然,我们可以在mybatis-config.xml中修改默认的规则:
<!-- 配置触发加载关联属性的方法,只有toString会触发 -->
<setting name="lazyLoadTriggerMethods" value="toString"/>
注意:getXXX方法无论如何都会触发关联对象的加载。
多表查询|嵌套查询结论
- 如果我们确定要使用当前对象数据以及其关联对象的数据时,使用多表关联查询效率最高
- 如果我们不确定要使用到当前对象关联的对象数据,这种情况下使用嵌套查询并结合延迟加载策略,可以提高资源利用率。