mybatis个人复习资料二

222 阅读10分钟

1.mybatis中的多表查询

表之间的关系有几种:
    一对多
    多对一
    一对一
    多对多
举例:
    一个用户可以下多个订单  : 一对多
    多个订单属于同一个用户  : 多对一
    一个人只能有一张身份证  : 一对一
    
    多个学生可以被多个老师教过  : 多对多
    多个老师可以教多个学生
特例:
    如果拿出每一个订单,他都属于每一个用户
    所以Mybatis就把多对一看成了一对一。

mybatis中的多表查询:
    示例:用户和账户
            一个用户可以有多个账户
            一个账户只能属于一个用户(多个账户也可以属于同一个用户)
    步骤:
        1.建立两张表:用户表,账户表
            让用户表和账户表之间具备一对多的关系:需要使用外键在账户表中添加
        2.建立两个实体类:用户实体类和账户实体类
            让用户和账户的实体类能体现出来一对多的关系
        3.建立两个配置文件
            用户的配置文件
            账户的配置文件
        4.实现配置
            当我们查询用户时,可以同时得到用户下所包含的账户信息
            当我们查询账户时,可以同时得到账户的所属用户信息。

1.1 查询所有账户,并且带有所属用户的用户名称和地址信息

1.1.1 代码实现1: 不常用的方式

* sql:select a.*, u.username, u.address from user u, account a where u.id = a.uid
```
* 1.Account表对应的实体类
public class Account implements Serializable {

    private Integer id;
    private Integer uid;
    private Double money;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public Integer getUid() {
        return uid;
    }

    public void setUid(Integer uid) {
        this.uid = uid;
    }

    public Double getMoney() {
        return money;
    }

    public void setMoney(Double money) {
        this.money = money;
    }

    @Override
    public String toString() {
        return "Account{" +
                "id=" + id +
                ", uid=" + uid +
                ", money=" + money +
                '}';
    }
}

* 2.查询得到的所属用户的用户名称和地址信息封装成到:AccountUser类
public class AccountUser extends Account {

    //AccountUser继承Account只是为了能打印出Account表的信息
    //将查询得到的username和address封装到这个类中
    private String username;
    private String address;

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    @Override
    public String toString() {
        return super.toString() + "              AccountUser{" +
                "username='" + username + '\'' +
                ", address='" + address + '\'' +
                '}';
    }
}

3. IAccountDao
List<AccountUser> findAllAccount();

4. AccountTest
    /**
     * 测试查询所有账户,并且带有所属用户的用户名称和地址信息
     *    select a.*, u.username, u.address from user u, account a where u.id = a.uid
     * @return
     */
    @Test
    public void testFindAllAccountUser(){
        List<AccountUser> accountUsers = accountDao.findAllAccount();
        for (AccountUser accountUser : accountUsers) {
            System.out.println(accountUser);
        }
    }

5.IAccountDao.xml
   <!--查询所有账户,并且带有用户名称和地址信息-->
    <select id="findAllAccount" resultType="com.itheima.domain.AccountUser">
        select a.*, u.username, u.address from user u, account a where u.id = a.uid
    </select>
```

1.1.2 代码实现2: 常用的方式

* 从表实体应该包含一个主表实体的对象引用
  使用resultMap在IAccountDao.xml定义专门的resultMap用于映射一对一查询结果
  我们可以在Account类中加入一个User类的对象来代表这个账户是哪个用户的。
```
* 1.Account表对应的实体类
    private Integer id;
    private Integer uid;
    private Double money;

    //从表实体应该包含一个主表实体的对象引用
    private User user;
    
* 2.IAccountDao
    List<AccountUser> findAllAccount();
    
* 3.IAccountDao.xml
    <resultMap id="accountUserMap" type="com.itheima.domain.Account">
        <!--主键字段的对应-->
        <!--这里account的id列在下面的sql中起了别名,所以这里也用别名-->
        <id property="id" column="aid"></id>
        <!--非主键字段的对应-->
        <result property="uid" column="uid"></result>
        <result property="money" column="money"></result>
        <!--一对一的关系映射:配置封装user表中的信息-->
        <!--javaType指明信息封装到哪个对象-->
        <association property="user" column="uid" javaType="com.itheima.domain.User">
        <!--上面的column属性可写可不写 但是在延迟或者立即加载那里需要写上 因为要用他做下一次查询-->
            <id property="id" column="id"></id>
            <result column="username" property="username"></result>
            <result column="sex" property="sex"></result>
            <result column="address" property="address"></result>
            <result column="birthday" property="birthday"></result>
        </association>
    </resultMap>


    <!--查询所有方法-->
    <select id="findAll" resultMap="accountUserMap">
        <!--因为这里的结果:user表中出现了id列,account表中也有id列,所以我们为其中一个id列起别名a.id as aid-->
        select u.*, a.id as aid, a.uid, a.money from account a, user u where u.id = a.uid;
    </select>
```

1.2 一对多

 需求:查询所有用户,同时获取用户下所有账户的信息
    1.SQL语句:不能用内连接,要用左外连接(将LEFT OUTER JOIN左边的数据都查询出来)。
        内连接:必行
        SELECT * FROM USER u, account a WHERE u.id = a.uid
        左外连接:
        SELECT u.*, a.`ID` AS aid, a.`MONEY`, a.`UID` FROM USER u LEFT OUTER JOIN account a ON u.id = a.`UID`
        或者右连接也行:
        SELECT a.`ID` AS aid, a.`MONEY`, a.`UID`, u.* FROM account a RIGHT OUTER JOIN USER u ON u.id = a.`UID`
    
    2.  User实体类
        //一对多关系映射:每个用户可能有多个账户
        //主表实体应该包含从表实体的集合引用(因为是一对多)
        private List<Account> accounts;

    <!--定义User的resultMap-->
    <resultMap id="userAccountMap" type="com.itheima.domain.User">
        <id property="id" column="id"></id>
        <result property="username" column="username"></result>
        <result property="sex" column="sex"></result>
        <result property="address" column="address"></result>
        <result property="birthday" column="birthday"></result>
        <!--配置 user对象中account集合的映射-->
        <collection property="accounts" ofType="com.itheima.domain.Account" column="id">
       <!--上面的column属性可写可不写 但是在延迟或者立即加载那里需要写上 因为要用他做下一次查询-->
       <id property="id" column="aid"></id>
            <result property="uid" column="uid"></result>
            <result property="money" column="money"></result>
        </collection>
    </resultMap>
    <!--查询所有方法-->
    <select id="findAll" resultMap="userAccountMap">
        SELECT u.*, a.id as aid, a.uid, a.money FROM USER u LEFT OUTER JOIN account a ON u.id = a.`UID`
    </select>

1.3 多对多

    SELECT u.*, r.id AS rid, r.role_name, r.role_desc FROM USER u LEFT OUTER JOIN user_role ur ON u.id = ur.uid LEFT OUTER JOIN role r ON r.id = ur.rid

2 延迟加载与立即加载

问题:在一对多情况中,当我们有一个用户,他有100个账户。
      在查询用户:的时候,要不要把关联的账户查出老?
      在查询账户的时候,要不要把关联的用户查出来?
      
1.查询用户:要不要把关联的账户查出老?
    * 我们要做的功能是查询用户(user),但是由于我们用户里面关联了一个账户集合(accounts),
且这个用户有100个账户,我们一查用户就会把这100个账户也查出来。显然这对内存是一个非常大的
开销。
    所以我们如果用不到这100个账户信息时,是完全不应该将它查询出来的。
    既然没有查出来,拿到用的时候又该怎么办呢?
    
    * 最好的解决办法:延迟加载
    在查询用户时,用户下的账户信息应该是,什么时候使用,什么时候查询。
    ---------------------延迟加载
    
2.查询账户:要不要把关联的用户查出来?
    通常情况下,在查询账户时,账户的所属用户信息,应该是随着账户查询时一起查询出来。
    ---------------------立即加载
    3.延迟加载    
    在真正使用数据时才发起查询,不用的时候不查询。按需加载(懒加载)
4.立即加载
    不管用不用,只要一调用方法,马上发起查询加载。
    
5.在对应的四种表关系中:
    一对多,多对多
        关联的对象是多:延迟加载
    多对一,一对一
        关联的对象是一:立即加载

2.1 延迟加载案例需求

需求:
    查询账户(Account)信息并且关联查询用户(User)信息。如果先查询账户(Account)信息即可
满足要求,当需要时再去查询关联的用户(User)信息。
    nyvatis第三天实现的多表操作时,我们使用了resultMap来实现一对一,一对多多关系的操作。主要
是通过association,collection实现一对一及一对多映射。而且association,collection具备延迟
功能。

注意:
    既然是延迟加载,那么我们查询出来的结果就应该是查询账户(Account)的信息,再按需查询
用户(User)的信息。
    sql:select * from account
    而不是:select u.*, a.id as aid, a.uid, a.money from account a, user u where u.id = a.uid;

2.1.1映射文件写法

<mapper namespace="com.itheima.Dao.IAccountDao">
    <!--定义封装account和user的resultMap-->
    <!--配置查询结果的列名和实体类的属性名的对应关系-->
    <resultMap id="accountUserMap" type="com.itheima.domain.Account">
        <id property="id" column="id"></id>
        <result property="uid" column="uid"></result>
        <result property="money" column="money"></result>
        <association property="user" column="uid" javaType="User" 
           select="com.itheima.Dao.IUserDao.findById">
        </association>
    </resultMap>
    <select id="findAll" resultMap="accountUserMap">
        select * from account
    </select>
</mapper>这个是账户映射文件中
<select id="findById" parameterType="INT" resultType="User">
select * from user where id = #{id};
</select>这个是用户映射文件中

<!--定义User的resultMap-->
<resultMap id="userAccountMap" type="com.itheima.domain.User">
    <id property="id" column="id"></id>
    <result property="username" column="username"></result>
    <result property="sex" column="sex"></result>
    <result property="address" column="address"></result>
    <result property="birthday" column="birthday"></result>
    <!--配置 user对象中account集合的映射-->
    <!--注意:findAccountByUid需要的参数是user中的id-->
    <collection property="accounts" ofType="account" select="com.itheima.Dao.IAccountDao.findAccountByUid" column="id">
    </collection>
</resultMap>这个是用户映射文件中
<!--查询所有方法-->
<select id="findAll" resultMap="userAccountMap">
    SELECT * FROM USER
</select>这个是用户映射文件中
<!--根据用户id查询账户列表-->
<select id="findAccountByUid" resultType="account">
    <!--
        注意这里我们没有parameterType但是仍然写了#{uid}
        所以这个uid不是来自parameterType而是来自IUserDao.xml中调用该映射是穿过来的参数column="id",
        而且这个是user中的id,对应着account中的uid字段。
    -->
    select * from account where uid = #{uid}
</select>这个是账户映射文件中

2.1.2全局配置文件写法

3 注解开发

sqlmap中的mappers配置

如果只有一个接口使用注解的话这么写没有问题

但是如果涉及多个接口使用注解的话这么写就要重复写多个 所以改成如下

注意:只要你使用了注解开发,同时你的配置文件下包含了对应的IUserDao.xml,就会报错。
    他发现你用了注解,又用了IUserDao.xml,mybatis不知道该以谁为准。
    如果一个dao用注解,那么这个dao就不会用IUserDao.xml
    所以我们要么把IUserDao.xml移到不相关的配置文件,或者删掉。
    即我们使用注解开发时,不能在同一个dao文件下使用IUserDao.xml。

3.2 java实体类的属性和表中字段不对应

1. 在Sql语句中起别名
    显然不仅仅调用findAll()方法时要起别名,调用findByiD()方法也要起别名。
    因为起别名只作用在一个SQL,一个方法上,不太好。

2. @Results注解
    @Select("select * from user")
    @Results(value={
            @Result(id=true,column="id",property = "userId"),
            @Result(column="username",property = "userName"),
            @Result(column="sex",property = "userSex"),
            @Result(column="address",property = "userAddress"),
            @Result(column="birthday",property  = "userBirthday"),
    }, id="userMap")
    List<User> findAll();
    
    @Results 注解是一个 @Result 注解数组,
        @Result注解在这里用到3个值:
            id:默认值是false。 是不是主键,id列
            column :数据库表的列名
            property : 实体类的属性名
        @Results :
            id : 为 @Results指定一个唯一id值
    
3. 在其他方法处也可以引用这个注解:

 注意:@ResultMap()的value是一个数组:
        所以正确的写法:@ResultMap(value={"userMap"})
        
        如果只有一个属性value,可以省略
        如果value只有一个值:{}可以省略
        @ResultMap("userMap") 

3.3 多对一查询

积极加载
* Account实体类:
    ```
    public class Account implements Serializable {

        private Integer id;
        private Integer uid;
        private Double money;

        private User user;
    }
    ```
* IAccountDao:
    ```
    @Select("select * from account")
    @Results(id="accountMap", value={
            @Result(id=true, column="id", property="id"),
            @Result(column="uid", property="uid"),
            @Result(column="money", property="money"),
            @Result(property="user", column="uid", one=@One(select="com.itheima.dao.IUserDao.findById", fetchType= FetchType.EAGER)),

    })
    List<Account> findAll(); 
    ```
    one=@One(select="com.itheima.dao.IUserDao.findById", fetchType= FetchType.EAGER):
        one属性:是@Result中的属性,表示一对一关系,值是@One注解
            @One注解:两个属性:
                select:用于指定我们要调用的select映射信息
                fetchType:一对一关系中,你所指定的加载方式(LAZY,EAGER,DEFAULT)

3.4 一对多查询