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

多表查询|嵌套查询结论

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

猜你喜欢

转载自blog.csdn.net/weixin_45245455/article/details/107986101
今日推荐