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方法无论如何都会触发关联对象的加载。

多表查询|嵌套查询结论

  • 如果我们确定要使用当前对象数据以及其关联对象的数据时,使用多表关联查询效率最高
  • 如果我们不确定要使用到当前对象关联的对象数据,这种情况下使用嵌套查询并结合延迟加载策略,可以提高资源利用率。