第二讲:Mybatis关联映射

第二讲:Mybatis关联映射

回顾:

parameterType:指定输入参数的数据类型

单个参数:(建议在语句标签上添加该属性)

​ 简单类型数据:所有的基本数据类型(包括包装类型)和引用类型(String和其他内置API的类型如Date)

​ 参数在语句中的使用:通过#{参数名}获取输入参数的值,当然#{}中参数名可以自定义,但是一般都是使用方法参数名

​ 复杂类型数据:对象类和Map类型

​ 参数在语句中的使用:通过#{属性名}或#{map的key值}获取输入参数的值

多个参数:(建议在标签中不设置属性)

​ 不建议使用默认占位别名arg0,arg1…或 param1,param2…

​ 建议:在每个参数前通过注解@Param定义别名,在语句中使用#{别名}方式获取参数值

本日目标

掌握基于Mybatis的连表查询结果映射(关联映射)

​ 一对一:主表一条数据关联从表一条数据(用户和用户详细信息)(提高数据库的查询效率,主表的字段比从表的字段用得多),借助主键或外键关联

​ 一对多(多对一):主表一条数据关联从表多条数据(订单和订单项),借助外键(一定在多方)或中间表关联

​ 多对多:必须是通过双向说明,两边看过去都是一对多,主表一条数据关联从表多条数据,同样从表一条数据关联主表多条数据(学生和课程),只能借助中间表

Mybatis关联映射说明:

连表查询实现步骤:

1)分析关系:当前主要查询的实体和需要关联查询的实体之间关系(一对一还是一对多)

2)定义关联映射属性:一对一使用对象属性,一对多使用对象集合属性

​ User和UserInfo,需要查询User的同时需要获取UserInfo,那么就需要关联映射查询

​ 分析关系:一对一

​ 定义关联属性:在User中定义:private UserInfo userInfo;

3)定义mapper接口方法,关联xml的Statement(namespace+id)

4)在xml映射文件中给Statement编写连表查询sql

5)需要自定义ResultMap进行结果集封装

6)在ResultMap中进行本身属性封装和关联属性封装(掌握两个标签就好)

​ 本身属性封装和关联属性封装基本一致:通过id和result标签进行属性封装(id标签是必须存在的

嵌套查询实现步骤:

前面三步和连表查询一致

4)在xml映射文件中给Statement编写单表查询sql(前提是表之间使用外键关联,而且外键在当前表中)

5)需要自定义ResultMap进行结果集封装

6)在ResultMap中进行本身属性封装和关联属性封装

​ 本身属性封装:通过id和result标签进行属性封装

​ 关联属性封装是通过调用其他查询语句完成封装

嵌套查询问题:容易出现N+1问题(主表数据查询一次,从表数据查询多次),对比连表查询语句来看效率低下

1.Mybatis 多表查询之一对一(掌握)

关联描述中:是双向分别描述的,从哪方去看,哪方就是主体

User和Account的关系需要在主体是哪方:

​ 从User方看双方关系:一对多关系

​ 从Account方看关系:一对一关系

本次案例主要以最为简单的用户和账户的模型来分析Mybatis 多表关系。用户为User 表,账户为Account
表。

一对一关系:

​ 一个账户(Account)只能属于一个用户(User)

​ 一个书籍(Book)只有一个作者(Author)

基于主键设计:

1573498153128

基于外键:

1589420503737

1.1 一对一查询(多对一)

需求
查询所有账户信息,关联查询用户信息。
注意:

​ 从账户角度看用户:一对一

​ 从用户角度看账户:一对多

如果基于外键关联,那么外键一定在多方上面

2.1.1 方式一:通过继承方式(了解)
2.1.1.1 定义账户信息的实体类
package com.yaorange.entity;

import java.io.Serializable;

public class Account implements Serializable {
    private Integer id;
    private Double money;
    private Integer userId;

    public Integer getId() {
        return id;
    }

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

    public Double getMoney() {
        return money;
    }

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

    public Integer getUserId() {
        return userId;
    }

    public void setUserId(Integer userId) {
        this.userId = userId;
    }

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

2.1.1.2 编写Sql语句

实现查询账户信息时,也要查询账户所对应的用户信息。

SELECT a.id,a.money,a.user_id as userId, u.name, u.address FROM account a LEFT JOIN user u on a.user_id = u.id

在MySQL中测试的查询结果如下:

1605152035820

2.1.1.3 定义AccountUser类

为了能够封装上面SQL语句的查询结果,定义类中要包含账户信息同时还要包含用户信息,所以我们要在定义AccountUser类时可以继承Account类。

package com.yaorange.entity;

import java.io.Serializable;

public class AccountUser extends Account implements Serializable {
    private String name;
    private String address;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAddress() {
        return address;
    }

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

    @Override
    public String toString() {
        return super.toString()+" User{" +
                "name='" + name + '\'' +
                ", address='" + address + '\'' +
                '}';
    }
}
2.1.1.4 定义账户的持久层Dao接口
public interface AccountDao {
    /** 
     * 查询所有账户,同时获取账户的所属用户名称以及它的地址信息 
     * @return 
     */ 
    List<AccountUser> findAll();
}
2.1.1.5 定义AccountDao.xml文件中的查询配置信息
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.yaorange.mapper.AccountDao"> 
    <!-- 配置查询所有操作--> 
    <select id="findAll" resultType="Accountuser"> 
        SELECT a.id,a.money,a.user_id as userId, u.name, u.address FROM account a LEFT JOIN user u on a.user_id = u.id 
    </select> 
</mapper>

注意:因为上面查询的结果中包含了账户信息同时还包含了用户信息,所以我们的返回值类型returnType的值设置为AccountUser类型,这样就可以接收账户信息和用户信息了。

2.1.1.6 创建AccountTest测试类
package com.yaorange.mapper;

import com.yaorange.entity.AccountUser;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import java.io.InputStream;
import java.util.List;

import static org.junit.Assert.*;

public class AccountMapperTest {
    private InputStream inputStream;
    private SqlSession sqlSession;
    private AccountMapper mapper;

    @Before
    public void setUp() throws Exception {
        inputStream = Resources.getResourceAsStream("mybatis-config.xml");
        SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        sqlSession = sessionFactory.openSession();
        mapper = sqlSession.getMapper(AccountMapper.class);
    }

    @After
    public void tearDown() throws Exception {
        sqlSession.commit();
        sqlSession.close();
        inputStream.close();
    }

    @Test
    public void findAllTest(){
        List<AccountUser> users = mapper.findAll();

        for (AccountUser user : users) {
            System.out.println(user);
        }
    }
}
2.1.1.8 小结:

定义专门的po类作为输出类型,其中包含了sql查询结果集所有的字段(本次是通过继承实体)。此方法较为简单,企业中使用普遍。不建议

2.1.2 方式二

使用resultMap,定义专门的resultMap用于映射一对一查询结果。

通过面向对象的(has a)关系可以得知,我们可以在Account类中加入一个User类的对象来代表这个账户是哪个用户的。

2.1.2.1 新增User类
package com.yaorange.entity;

public class User {
    private Integer id;
    private String name;
    private String address;

    public Integer getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAddress() {
        return address;
    }

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

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", address='" + address + '\'' +
                '}';
    }
}
修改Account类

在Account类中加入User类的对象作为Account类的一个属性(关联属性)。

package com.yaorange.entity;

import java.io.Serializable;

public class Account implements Serializable {
    private Integer id;
    private Double money;

    //该字段在数据库中表示一个用户数据
//    private Integer userId;//数据库关联映射字段
    //新增关联属性:一对一,所以使用对象属性
    private User user;

    public Integer getId() {
        return id;
    }

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

    public Double getMoney() {
        return money;
    }

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

    public User getUser() {
        return user;
    }

    public void setUser(User user) {
        this.user = user;
    }

    @Override
    public String toString() {
        return "Account{" +
                "id=" + id +
                ", money=" + money +
                ", user=" + user +
                '}';
    }
}
2.1.2.2 修改AccountDao接口中的方法
	/** 
     * 查询所有账户,同时获取账户的所属用户名称以及它的地址信息 
     * @return 
     */ 
    List<Account> getAll();

注意:第二种方式,将返回值改 为了Account类型。

因为Account类中包含了一个User类的对象,它可以封装账户所对应的用户信息。

2.1.2.3 重新定义AccountDao.xml文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.yaorange.dao.AccountDao">
    <!--一次性配置方式:-->
    <!--定义一对一结果映射处理Map-->
    <resultMap id="acccountMap" type="Account">
        <id column="id" property="id"/>
        <result column="money" property="money"/>
        <!--association:用于一对一映射,将结果集指定列映射为对象-->
        <!--collection:用于一对多映射,将结果集指定列映射为集合对象-->
        <association property="user" javaType="User">
            <id column="user_id" property="id"/>
            <result column="name" property="name"/>
            <result property="address" column="address"/>
        </association>
    </resultMap>

    <!--高重用方式(实际开发中的选择)-->
    <!--1)定义基本map,封装当前实体对象属性封装-->
    <resultMap id="baseResultMap" type="Account">
        <id column="id" property="id"/>
        <result column="money" property="money"/>
    </resultMap>
    <!--2)定义关联映射map,通过extends属性继承基本map-->
    <resultMap id="accountAndUserMap" type="Account" extends="baseResultMap">
        <!--在标签中通过resultMap属性引用其他配置文件中的resultMap进行结果映射-->
        <association property="user" javaType="User" resultMap="com.yaorange.dao.UserDao.baseResultMap"/>
        <!--自行进行结果映射-->
        <!--<association property="user" javaType="User">-->
            <!--<id column="user_id" property="id"/>-->
            <!--<result column="name" property="name"/>-->
            <!--<result property="address" column="address"/>-->
        <!--</association>-->
    </resultMap>

    <select id="getAll" resultMap="accountAndUserMap">
        SELECT a.account_id as id,a.money,a.user_id as userId, u.sex,u.name, u.address FROM account a LEFT JOIN user u on a.user_id = u.user_id
    </select>


</mapper>

UserDao.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.yaorange.dao.UserDao">
    
    <resultMap id="baseResultMap" type="User">
        <id column="user_id" property="id"/>
        <result column="name" property="name"/>
        <result property="birthday" column="birthday"/>
        <result property="address" column="address"/>
    </resultMap>
    
</mapper>
2.1.2.4 在AccountTest类中加入测试方法
package com.yaorange.service;

import com.yaorange.dao.AccountDao;
import com.yaorange.entity.Account;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import java.io.InputStream;
import java.util.List;

public class AccountTest {
    private InputStream inputStream;
    private SqlSession sqlSession;
    private AccountDao accountDao;

    @Before
    public void setUp() throws Exception {
        inputStream = Resources.getResourceAsStream("mybatis-config.xml");
        SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        sqlSession = sessionFactory.openSession();
        accountDao = sqlSession.getMapper(AccountDao.class);
    }

    @After
    public void tearDown() throws Exception {
        sqlSession.commit();
        sqlSession.close();
        inputStream.close();
    }

    @Test
    public void getAllTest(){
        List<Account> accountList = accountDao.selectAll();
        for (Account account : accountList) {
            System.out.println(account);
        }
    }
}

1.2 懒加载(延迟加载,了解原理和意义)

在上面的使用方式中,都是一次性将所有数据都进行查询获取,然后在ResultMap中进行映射,如果一对一查询中,关联属性user,使用的不是很频繁,而是偶尔使用,那么可以进行延迟加载配置

懒加载(延迟加载):也就是先查询账户Account,查询Account时不查询User,当程序逻辑中在需要使用User属性的数据时再去查询User

定义接口:

AccountMapper:添加方法

Account getAccountById(Integer id);

UserMapper:添加方法

User getUserById(Integer id);
配置映射文件:

在关联映射标签中使用fetchType=“lazy”

AccountMapper.xml

	<!--公共字段-->
    <sql id="accountColums">
        id,money,user_id
    </sql>
    <!--通过id查询账户-->
    <select id="getAccountById" resultMap="accountMap1">
        select <include refid="accountColums"/> from account where id=#{id}
    </select>
    <!--优化配置-->
    <resultMap id="baseResultMap" type="Account">
        <!--映射当前实体的本身字段-->
        <id column="id" property="id"/>
        <result column="money" property="money"/>
    </resultMap>
    <!--账户查询结果映射-->
    <resultMap id="accountMap1" type="Account" extends="baseResultMap">
        <!--映射关联对象属性:association:映射对象属性的,collection:映射对象集合属性的-->
        <!--嵌套查询方式配置:延迟加载只能在嵌套查询中配置-->
        <!--column:指定用于嵌套查询的参数字段名-->
        <!--select:指定嵌套查询的语句:namespace+id-->
        <!--fetchType:指定嵌套查询的加载方式,值为lazy(延迟加载) 和 eager(及时加载)-->
        <association property="user" javaType="User" column="uid" select="com.yaorange.mapper.UserMapper.getUserById" fetchType="lazy">
        </association>
    </resultMap>

UserMapper.xml:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.yaorange.mapper.UserMapper">
    <!--公共字段-->
    <sql id="userColums">
        id,name,sex,birthday,address
    </sql>
    
    <!--通过id查询用户-->
    <select id="getUserById" resultType="User">
        select <include refid="userColums"/> from user where id=#{uid}
    </select>
</mapper>
配置全局文件

官方文档:https://mybatis.org/mybatis-3/zh/configuration.html#settings

1589428768157

1589428806229

	<settings>
        <!--开启延迟加载-->
        <setting name="lazyLoadingEnabled" value="true"/>
        <!--配置触发延迟加载语句的执行的方法:覆盖默认配置,避免调用toString会触发延迟加载语句执行
        lazyLoadTriggerMethods:指定方法调用时会触发延迟加载语句的执行-->
        <setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode"/>
    </settings>
测试代码
@Test
    public void getAccountByIDTest() {
        AccountMapper mapper = sqlSession.getMapper(AccountMapper.class);
        Account account = mapper.getAccountById(2);
        System.out.println(account);//不会执行延迟加载属性,因为没有使用延迟加载关联属性的数据
        //执行下面代码时会触发延迟加载语句的执行:因为只要使用的延迟加载关联的属性时就执行语句
//        System.out.println(account.getUser());
    }
总结:

嵌套查询的使用:

在关联映射标签中使用select指定嵌套执行的statement的id(全路径:namespace+id),如果嵌套执行的语句需要参数就使用column属性指定当前表的字段名,在执行时就会将指定字段值传入嵌套sql中执行

如果想要指定嵌套查询的加载策略使用属性fetchType,值默认为eager表示及时加载,lazy表示使用是加载(延迟加载)

<association property="user" javaType="User" column="user_id" select="com.yaorange.dao.UserDao.getUserById"/>

2. Mybatis 多表查询之一对多(掌握)

2.1 一对多查询

需求:

​ 查询所有用户信息及用户关联的账户信息。

分析:

​ 用户信息和他的账户信息为一对多关系,并且查询过程中如果用户没有账户信息,此时也要将用户信息查询出来,我们想到了左外连接查询比较合适。

同理:

​ 用户和角色:一个用户可以有多个角色

​ 订单和订单项:一个订单有多个订单项

2.1.1 编写SQL语句
SELECT u.id,u.name,u.birthday,u.sex,u.address, acc.id account_id, acc.money FROM user u LEFT JOIN account acc ON u.id = acc.user_id
--或
SELECT u.id,u.name,u.birthday,u.sex,u.address, acc.id account_id, acc.money FROM account acc RIGHT JOIN user u on acc.user_id = u.id 

测试该SQL语句在MySQL客户端工具的查询结果如下:

1573500556421

理解简单连接和左右外连接测试语句:

SELECT u.*, acc.id id, acc.uid, acc.money FROM user u LEFT JOIN account acc ON u.id = acc.uid;
SELECT u.*, acc.id id, acc.uid, acc.money FROM user u RIGHT JOIN account acc ON u.id = acc.uid;
SELECT u.*, acc.id id, acc.uid, acc.money FROM user u , account acc where u.id = acc.uid;
2.1.2 User类加入List

一对一关联:使用的关联属性是对象属性(描述一)

一对多关联:使用的关联属性是对象集合属性(描述多)

public class User implements Serializable{
    private int id;
    private String username;// 用户姓名
    private String sex;// 性别
    private Date birthday;// 生日
    private String address;// 地址
    private List<Account> accounts;

    public List<Account> getAccounts() {
        return accounts;
    }

    public void setAccounts(List<Account> accounts) {
        this.accounts = accounts;
    }

    public int getId() {
        return id;
    }

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

    public String getUsername() {
        return username;
    }

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

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public Date getBirthday() {
        return birthday;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }

    public String getAddress() {
        return address;
    }

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

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", sex='" + sex + '\'' +
                ", birthday=" + birthday +
                ", address='" + address + '\'' +
                ", accounts=" + accounts +
                '}';
    }
}
2.1.3 用户持久层Dao接口中加入查询方法
/** 
* 查询所有用户,同时获取出每个用户下的所有账户信息 
* @return 
*/ 
List<User> findAll();
2.1.4 用户持久层Dao映射文件配置
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.yaorange.mapper.UserDao">
    <!-- 配置查询所有操作 -->
    <select id="selectAll" resultMap="userAndAccountMap">
        SELECT u.id,u.name,u.birthday,u.sex,u.address, acc.id as account_id, acc.money FROM user u LEFT JOIN account acc ON u.id = acc.user_id
    </select>

    <resultMap id="userAndAccountMap" type="User" extends="baseResultMap">
        <!--引用其他文件中的ResultMap进行数据封装-->
        <!--<collection property="accounts" ofType="Account" resultMap="com.yaorange.dao.AccountDao.baseResultMap"/>-->
        <!--语句中使用了别名区分重名字段时,需要自定义数据封装-->
        <collection property="accounts" ofType="Account">
            <id column="account_id" property="id"/>
            <result column="money" property="money"/>
        </collection>
    </resultMap>
</mapper>

collection :

​ 部分定义了用户关联的账户信息。表示关联查询结果集

property=“accList”:

​ 关联查询的结果集存储在User对象的上哪个属性。

ofType=“account”:

​ 指定关联查询的结果集中的对象类型即List中存储的对象类型。此处可以使用别名,也可以使用全限定名。

2.2.5 测试方法
	@Test
    public void findAll() throws IOException {
        //1.读取配置文件
        InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
        //2.创建SqlSessionFactory的构建者对象
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        //3.使用构建者创建工厂对象SqlSessionFactory
        SqlSessionFactory factory = builder.build(in);
        //4.使用SqlSessionFactory生产SqlSession对象
        SqlSession session = factory.openSession();
        //5.创建Dao的代理对象
        UserDao userDao = session.getMapper(UserDao.class);
        //6.执行查询方法
        List<User> users = userDao.findAll();
        for(User user : users) {
            System.out.println("-------每个用户的内容---------");
            System.out.println(user);
            System.out.println(user.getAccounts());
        }
        //7.释放资源
        session.close();
        in.close();
    }
任务:执行完成一对多嵌套查询

3. Mybatis 多表查询之多对多(掌握)

一对一和一对多配置都是基于单向说明:

如:从账户看用户:一个账户只能关联一个用户,所以是一对一

​ 从用户看账户:一个用户可以关联多个账户,所以是一对多

多对多是基于双向说明:

如:用户和角色:

​ 从用户看角色:一个用户可以关联多个角色,是一对多关系

​ 从角色看用户:一个角色可以关联多个用户,是一对多关系

只有满足上面情况,就可以说用户和角色之间是多对多关系

3.1 实现Role 到User 多对多

通过前面的学习,我们使用Mybatis 实现一对多关系的维护。多对多关系其实我们看成是双向的一对多关
系。

同理:学生和课程:也是多对多关系,一个学生可以有多个课程,一个课程中可以有多个学生

多对多就是通过双向查询:通过学生查询课程,也可以通过课程查询学生

而一对一和一对多都是描述单向查询:通过账户查询用户是一对一(多对一),通过用户查询账户是一对多

3.1.1 用户与角色的关系模型

用户与角色的多对多关系模型如下:
1573501463689
在MySQL 数据库中添加角色表,用户角色的中间表。
角色表
1573501494354
用户角色中间表

1573501508820

一对一关系设计:基于主键设计或基于外键设计

​ 基于主键设计:保证两张表的主键字段值相等的表示的是一个数据(设计时只能让最多一张表主键自增)

​ 基于外键设计:一般都是在主表添加外键字段值(也可以在从表添加外键),引用关联从表主键值(实际开发中,一般只添加形式上的外键(没有外键约束的外键))

一对多关系设计:基于外键或基于中间表设计

​ 基于外键设计(常用):只能在多方表中添加外键字段

​ 基于中间表设计:都是只能存储两列(分别为关联的两张表的主键值),表名一般都是两张表的名称使用_拼接

多对多关系设置:只能基于中间表

3.1.2 业务要求及实现SQL

需求:

​ 实现查询所有角色并且加载它所分配的用户信息。(需要查询获取所有角色,哪怕某个角色没有用户也需要显示,只是用户为空)

分析:

​ 查询角色我们需要用到Role表,但角色分配的用户的信息我们并不能直接找到用户信息,而是要通过中间表(USER_ROLE表)才能关联到用户信息。

下面是实现的SQL语句:

内连接只能查询有匹配关联的数据,可能会显示不完整

SELECT r.*,u.id uid, u.username username, u.birthday birthday, u.sex sex, u.address address FROM ROLE r 
INNER JOIN USER_ROLE ur ON ( r.id = ur.rid) 
INNER JOIN USER u ON (ur.uid = u.id);

语句相当于:

select r.*,u.id uid, u.username username, u.birthday birthday, u.sex sex, u.address address FROM ROLE r,USER_ROLE ur,USER u where r.id = ur.rid and ur.uid = u.id;

左外连

select r.id as rid,r.role_name,r.role_desc,u.* from role r
        left join user_role ur on r.id = ur.role_id
        left join user u on u.id = ur.user_id;

通过分析查询所有时需要使用外连接(本次选择左外连接)

3.1.3 编写角色实体类
public class Role implements Serializable {
    private Integer roleId;
    private String roleName;
    private String roleDesc; 
    //多对多的关系映射:一个角色可以赋予多个用户 
    private List<User> users;

    public Integer getRoleId() {
        return roleId;
    }

    public void setRoleId(Integer roleId) {
        this.roleId = roleId;
    }

    public String getRoleName() {
        return roleName;
    }

    public void setRoleName(String roleName) {
        this.roleName = roleName;
    }

    public String getRoleDesc() {
        return roleDesc;
    }

    public void setRoleDesc(String roleDesc) {
        this.roleDesc = roleDesc;
    }

    public List<User> getUsers() {
        return users;
    }

    public void setUsers(List<User> users) {
        this.users = users;
    }

    @Override
    public String toString() {
        return "Role{" +
                "roleId=" + roleId +
                ", roleName='" + roleName + '\'' +
                ", roleDesc='" + roleDesc + '\'' +
                ", users=" + users +
                '}';
    }
}
3.1.4 编写Role持久层接口
public interface RoleDao {
    /** 
     * 查询所有角色 
     * @return 
     */ 
    List<Role> findAll();
}
3.1.5 编写映射文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.yaorange.mapper.RoleDao">
    <!--查询所有-->
    <select id="findAll" resultMap="roleMap">
        select r.id as rid,r.role_name,r.role_desc,u.* from role r
        left join user_role ur on r.id = ur.role_id
        left join user u on u.id = ur.user_id;
    </select>

    <!--定义role表的ResultMap--> 
    <resultMap id="roleMap" type="Role">
        <id column="rid" property="roleId"/>
        <result column="role_name" property="roleName"/>
        <result column="role_desc" property="roleDesc"/>
        <collection property="users" ofType="User">
            <id column="id" property="id"/>
            <result column="name" property="name"/>
            <result column="birthday" property="birthday"/>
            <result column="sex" property="sex"/>
            <result column="address" property="address"/>
        </collection>
    </resultMap>
</mapper>
提示:实际开发中,一定注意给每个表的字段命名时,建议设置前缀或后缀,避免连表查询时结果集字段重名,

比如:上面配置中,因为有重名所以结果映射中就无法进行继承ResultMap方式

3.1.6 编写测试类
	@Test
    public void findAll() throws IOException {
        //1.读取配置文件
        InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
        //2.创建SqlSessionFactory的构建者对象
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        //3.使用构建者创建工厂对象SqlSessionFactory
        SqlSessionFactory factory = builder.build(in);
        //4.使用SqlSessionFactory生产SqlSession对象
        SqlSession session = factory.openSession();
        //5.创建Dao的代理对象
        RoleDao roleDao = session.getMapper(RoleDao.class);
        //6.执行查询方法
        List<Role> roles = roleDao.findAll();
        for(Role role : roles){
            System.out.println("---每个角色的信息----");
            System.out.println(role);
            System.out.println(role.getUsers());
        }
        //7.释放资源
        session.close();
        in.close();
    }

3.2 实现User到Role的多对多

3.2.1 User到Role的多对多

​ 从User出发,我们也可以发现一个用户可以具有多个角色,这样用户到角色的关系也还是一对多关系。这样我们就可以认为User与Role之间是多对多关系,可以被拆解成两个一对多关系来实现。

3.2.2 作业:实现User到Role的一对多查询

​ 需求:实现查询所有用户信息并关联查询出每个用户的角色列表。

面试题:

实现一对一,一对多和多对多的方式:

基于配置进行说明(使用association或collection标签配置)和实体设计说明(对象属性或集合属性描述关系)

注意:

​ association标签使用javaType属性指定映射的实体类型

​ collection标签使用ofType属性指定映射的实体类型

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值