MyBatis----04----多表查询&&嵌套查询&&类加载机制
目录
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方法无论如何都会触发关联对象的加载。
多表查询|嵌套查询结论
- 如果我们确定要使用当前对象数据以及其关联对象的数据时,使用多表关联查询效率最高
- 如果我们不确定要使用到当前对象关联的对象数据,这种情况下使用嵌套查询并结合延迟加载策略,可以提高资源利用率。