MyBatis系列--关系映射

关联关系概述

在关系型数据库中,多表之间存在3种关联关系,分别为一对1、一对多和多对多。

  1. 一对一:在任意一方引入对方主键作为外键。
  2. 一对多:在"多"的一方添加"一"的一方的主键作为外键。
  3. 多对多: 产生中间关系表,引入两张表的主键作为外键,两个主键成为联合主键或使用新的字段作为主键。

对象之间也存在3种关联关系.

  1. 一对一的关系: 在本类中定义对方类型的对象,比如A类中定义B类类型的属性b、在B类中定义A类类型的属性a。
  2. 一对多的关系:一个A类类型对应多个B类类型的情况,需要在A类中以集合的方法引入B类类型的对象,在B类中定义A类类型的属性a。
  3. 多对多的关系:在A类中定义B类类型的集合,在B类中定义A类类型的集合。

一对一

例如,一个学生只有一本学生证,同时一本学生证也只对应一个学生。MyBatis正是通过前面我们所讲的< resultMap >元素中的< association >子元素,来处理一对一关系的。
< association >元素有以下几个属性:

  • property: 指定映射到的实体类对象属性,与表字段一一对应。
  • column:指定表中对应的字段。
  • javaType: 指定映射到实体对象属性的类型。
  • select:指定引入嵌套查询的子SQL语句,用于关联映射中的嵌套查询。
  • fetchType: 指定在关联查询时是否启用延迟加载,有lazy和eager两个属性值,默认值为lazy(默认关联映射延迟加载)。

< association >元素有以下两种配置方式:

<!--方式一: 嵌套查询-->
<assoctation property="card" column="card_id" 
javaType="com.ssm.po.StudentIdCard" 
select="mapper.StudentIdCardMapper.findCodeById"/>

<!--方式二:嵌套结果-->
<assoctation property="card" javaType="com.ssm.po.StudentIdCard">
	<id property="id" column="card_id"/>
	<result property="code" column="code"/>
</assoctation>

嵌套查询是指通过执行另一条SQL映射语句来返回预期的复杂类型;
嵌套结果是使用嵌套结果映射来处理重复的联合结果的子集。

代码实现

以学生和学生证之间的一对一关联关系为例。

查询学生及其管理的学生证信息是先通过查询学生表中的主键来获取学生信息,然后通过表中的外键来获取学生证表中的学生证号信息。

MybatisUtil用来获取SqlSession,代码如下:

package com.ssm.util;

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 java.io.IOException;
import java.io.InputStream;

public class MybatisUtil {
    private  static SqlSessionFactory sqlSessionFactory = null;

    static{
        try{
            String resource="mybatis-config.xml";
            InputStream inputStream = Resources.getResourceAsStream(resource);
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


    //获取SqlSession的方法
    public  static SqlSession getSession(){
        return  sqlSessionFactory.openSession();
    }
}

1.创建数据表,打开SQLyong,在test数据库下创建student和student_code表。代码如下:

#使用数据库test
USER test

#创建一个名为student_code(学生证)表
CREATE TABLE student_code(
	id INT PRIMARY KEY AUTO_INCREMENT,
	CODE VARCHAR(10)
)

# 插入两条数据
INSERT  INTO student_code(CODE)VALUES('184114')
INSERT  INTO student_code(CODE)VALUES('18030135')

#创建一个名称为student(学生),并设置外键为学生证表的id
CREATE TABLE student(
	id INT PRIMARY KEY AUTO_INCREMENT,
	NAME VARCHAR(32),
	sex CHAR(1),
	card_id INT UNIQUE,
	FOREIGN KEY (card_id) REFERENCES student_code(id)
)

#插入两条数据
INSERT INTO student(NAME,sex,card_id)VALUES("小鑫","女",1)
INSERT INTO student(NAME,sex,card_id)VALUES("猪心","男",2)

2.创建Maven项目,引入pom坐标,创建目录等,这里就不多少了
3.创建持久化类,学生证类和学生类,代码如下:

StudentCard.java

package com.ssm.po;

public class StudentCard {
    private  Integer id;
    private  String code;

    public Integer getId() {
        return id;
    }

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

    public String getCode() {
        return code;
    }

    public void setCode(String code) {
        this.code = code;
    }

    @Override
    public String toString() {
        return "StudentCard{" +
                "id=" + id +
                ", code='" + code + '\'' +
                '}';
    }
}

Student.java

package com.ssm.po;

public class Student {
    private Integer id;
    private  String name;
    private  String sex;
    private  StudentCard studentCard;

    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 getSex() {
        return sex;
    }

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

    public StudentCard getStudentCard() {
        return studentCard;
    }

    public void setStudentCard(StudentCard studentCard) {
        this.studentCard = studentCard;
    }

    @Override
    public String toString() {
        return "Student{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", sex='" + sex + '\'' +
                ", studentCard=" + studentCard +
                '}';
    }
}

4.创建学生证映射文件(StudentCardMapper.xml)和学生映射文件(StudentMapper.xml),代码如下:

StudentCardMapper.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="mapper.StudentCardMapper">
    <!--根据id获取学生证信息-->
    <select id="findStudentCardById" parameterType="Integer" resultType="com.ssm.po.StudentCard">
        select * from student_code where id = #{id}
    </select>
</mapper>

StudentMapper.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="mapper.StudentMapper">
    <!--嵌套查询,通过执行一条SQL映射语句来返回预期的特殊类型-->
    <select id="findStudentById" parameterType="Integer"
            resultMap="StudentIdCardWithStudentResult">
          select * from student where id=#{id}
    </select>
    <resultMap id="StudentIdCardWithStudentResult" type="com.ssm.po.Student">
        <id property="id" column="id"/>
        <result property="name" column="name"/>
        <result property="sex" column="sex"/>
        <!--一对一,association使用select属性引入另一条SQL语句-->
        <association property="studentCard"
                     column="card_id"
                     javaType="com.ssm.po.StudentCard"
                     select="mapper.StudentCardMapper.findStudentCardById">
        </association>
    </resultMap>
</mapper>

在StudentMapper.xml中,返回的学生对象中有一个关联的studentCard属性, 所以使用的是嵌套查询。在嵌套查询中先执行一个简单的SQL,将学生的基本返回,然后再进行结果映射时将关联对在< assoctation >元素中使用select属性执行另一条SQL语句(StudentCardMapper.xml中的SQL),并将学生信息中的外键(card_id)当作参数传递过去。

5.在核心配置文件mybatis-config.xml中引入Mapper

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">

<!--mybatis的主配置文件-->
<configuration>
    <properties resource="db.properties"/>
    <!--配置环境 默认的环境id为mysql-->
    <environments default="mysql">
        <!--配置mysql的环境-->
        <environment id="mysql">
            <!--配置事务的类型-->
            <transactionManager type="JDBC"></transactionManager>
            <!--配置数据源(连接池)-->
            <dataSource type="POOLED">
                <!--配置连接数据库的4个基本信息-->
                <property name="driver" value="${jdbc.driver}"></property>
                <property name="url" value="${jdbc.url}"></property>
                <property name="username" value="${jdbc.username}"></property>
                <property name="password" value="${jdbc.password}"></property>
            </dataSource>
        </environment>
    </environments>

    <!--指定映射配置文件的位置,映射配置文件指的是每个dao独立的配置文件-->
    <!--如果是用注解来配置的话,此处应该使用class属性指定被注解的dao全限定类名-->
    <mappers>
        <mapper resource="mapper/StudentMapper.xml"/>
        <mapper resource="mapper/StudentCardMapper.xml"/>
    </mappers>
</configuration>

6.编写测试方法,代码如下:

public  static  void findStudentByIdTest(){
        SqlSession sqlSession = MybatisUtil.getSession();
        //查询id为1的学生信息
        Student student = sqlSession.selectOne("mapper.StudentMapper.findStudentById",1);
        System.out.println(student);
        sqlSession.close();
    }

我们成功的获取到了学生基本信息和和该学生对应的学生证信息,结果如下:
在这里插入图片描述
由于嵌套查询的方式要执行多条SQL语句,这对于大型数据集合来说,会极大地消耗数据库性能,并且会降低查询效率。MyBatis提供了嵌套结果的方式来进行关联查询。

修改StudentMapper.xml中代码,使用MyBatis嵌套结果进行查询

<mapper namespace="mapper.StudentMapper">
    <!--嵌套查询,通过执行一条SQL映射语句来返回预期的特殊类型-->
    <select id="findStudentById" parameterType="Integer"
            resultMap="StudentIdCardWithStudentResult">
         select s.*,sidcard.code
         from student as s,student_code as sidcard
         where s.card_id=sidcard.id and s.id=#{id}
    </select>
    <resultMap id="StudentIdCardWithStudentResult" type="com.ssm.po.Student">
        <id property="id" column="id"/>
        <result property="name" column="name"/>
        <result property="sex" column="sex"/>
        <!--一对一,association使用select属性引入另一条SQL语句-->
        <!--<association property="studentCard"-->
                     <!--column="card_id"-->
                     <!--javaType="com.ssm.po.StudentCard"-->
                     <!--select="mapper.StudentCardMapper.findStudentCardById">-->
        <!--</association>-->

        <association property="studentCard" javaType="com.ssm.po.StudentCard">
            <id property="id" column="card_id"/>
            <result property="code" column="code"/>
        </association>
    </resultMap>
</mapper>

MyBatis嵌套结果的方式只编写了一条复杂的多表关联的SQL语句,并且在< association>元素中继续使用相关子元素进行数据库表字段和实体类属性的一一映射。

注意
在使用MyBatis嵌套查询方式进行关联查询映射时,使用MyBatis的延迟加载在一定程序上可以降低运行消耗并提高查询效率。MyBatis默认是没有开启延迟加载,需要在核心配置文件mybatis-config.xml中< settings >元素内进行配置,代码如下:

<settings>
        <!--打开延迟加载的开关-->
        <setting name="lazyLoadingEnabled" value="true"/>
        <!--将积极加载改为消极加载,即按需加载-->
        <setting name="aggressiveLazyLoading" value="false"/>
    </settings>

在映射文件中,MyBatis关联映射的< association >元素和< collection >元素中都已默认配置了延迟加载属性,即默认属性fetchType=“lazy”(属性fetchType="eager"表示立即加载),所以在配置文件中开启延迟加载后,无须在映射文件中再做配置。

一对多

在实际应用中,应用更多的关联关系是一对多(或多对一)。例如:一个班级有多个学生,即多个学生属于一个班级。 在MyBatis中使用< resultMap >元素中的< collection >来处理一对多关联关系的。 < collection >子元素的属性和< association >元素相同,不同的是< collection >子元素中包含一个ofType属性,用于指定实体对象中集合类属性所包含的元素类型。

< collection >元素有以下两种示例进行配置

<!--嵌套查询-->
<collection property="studentList" column="id"
			ofType="com.ssm.po.Student"
			select="mapper.StudentMapper.selectStudent"/>

<!--嵌套结果-->
<collection property="studentList"
			ofType="com.ssm.po.Student">
			<id property="id" column="student_id"
			<result property="username" column="username"/>
</collection>
代码实现

1.在test数据库中,创建两个数据表banji和student1,代码如下:

#创建一个名称为banji的表
CREATE TABLE banji(
    id INT PRIMARY KEY AUTO_INCREMENT,
    NAME VARCHAR(32)
)

#插入两条数据
INSERT INTO banji VALUES(1,"软件技术1班");
INSERT INTO banji VALUES(2,"软件技术2班");

#创建一个student1表
CREATE TABLE student1(
	id INT PRIMARY KEY AUTO_INCREMENT,
	NAME VARCHAR(32),
	sex CHAR(1),
	banji_id INT,
	FOREIGN KEY (banji_id) REFERENCES banji(id)
)

#插入3条数据
INSERT INTO student1 VALUES(1,"小鑫",'m',1)
INSERT INTO student1 VALUES(2,"邵祎",'l',1)
INSERT INTO student1 VALUES(3,"猪心",'l',2)

2.创建持久化类

Banji.java

package com.ssm.po;

import java.util.List;

public class Banji {
    private  Integer id;
    private  String name;
    private List<Student1> student1List;


    public List<Student1> getStudent1List() {
        return student1List;
    }

    public void setStudent1List(List<Student1> student1List) {
        this.student1List = student1List;
    }

    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;
    }

    @Override
    public String toString() {
        return "Banji{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", student1List=" + student1List +
                '}';
    }
}

Student1.java

package com.ssm.po;

public class Student1 {
    private Integer id;
    private  String name;
    private  String sex;

    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 getSex() {
        return sex;
    }

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

    @Override
    public String toString() {
        return "Student1{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", sex='" + sex + '\'' +
                '}';
    }
}

3.创建班级映射文件BanjiMapper.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="mapper.BanjiMapper">
    <select id="findByBanJiStudent" parameterType="Integer" resultMap="resultMap">
        SELECT  * from banji where id=#{id}
    </select>

    <resultMap id="resultMap" type="com.ssm.po.Banji">
        <id property="id" column="id"/>
        <result property="name" column="name"/>

        <collection property="student1List" column="id"
        ofType="com.ssm.po.Student1"
        select="mapper.StudentMapper.findStudentByBanJiId"/>
    </resultMap>
</mapper>

4.在StudentMapper.xml添加一个通过班级id(外键)查询学生的配置

<select id="findStudentByBanJiId" parameterType="Integer" resultType="com.ssm.po.Student1">
        SELECT  * from student1 where banji_id=#{id}
    </select>

5.在mybatis-config中注册映射文件

 	<mappers>
        <mapper resource="mapper/StudentMapper.xml"/>
        <mapper resource="mapper/StudentCardMapper.xml"/>
        <mapper resource="mapper/BanjiMapper.xml"/>
    </mappers>

6.编写测试方法

 public  static  void findBanjiByStudentTest(){
        SqlSession sqlSession = MybatisUtil.getSession();
        //查询id为1的学生信息
        Banji banji = sqlSession.selectOne("mapper.BanjiMapper.findByBanJiStudent",1);
        System.out.println(banji);
        sqlSession.close();
    }

成功地实现了,通过查询班级信息将其班级下的所有学生也查询出来了,结果如下:
在这里插入图片描述

上面使用的嵌套查询方法,现在使用嵌套结果来进行查询,修改BanjiMapper.xml,代码如下:

<select id="findByBanJiStudent1" parameterType="Integer" resultMap="resultMap1">
        SELECT b.*,s.id as s_id,s.name as s_username,s.sex
        from banji as b,student1 as s
        where b.id=s.banji_id and b.id=#{id}
    </select>

    <resultMap id="resultMap1" type="com.ssm.po.Banji">
        <id property="id" column="id"/>
        <result property="name" column="name"/>

        <collection property="student1List" ofType="com.ssm.po.Student1">
            <id property="id" column="s_id"/>
            <result property="name" column="s_username"/>
            <result property="sex" column="sex"/>
        </collection>
    </resultMap>

查询的结果和嵌套查询的结果一致,且只执行了一次查询。

多对多查询

在实际项目开发中,多对多的关联关系是非常常见的。以学生和课程为例,一个学生可以选修多门课程,而一门课程有可以被多个学生选修,学生和课程就属于多对多的关联关系。

在数据库中,多对多的关联关系通常使用一个中间表来维护,中间表选课表(electiveCourse)中的学生id(student_id)作为外键参照学生表的id,课程id(course_id)作为外键参照课程表的id。关系如下图所示:
在这里插入图片描述

代码实现

1.在test创建数据库创建course(课程表)和electiveCourse(中间边),由于student表在前面创建过了,这里就不重新创建了,代码如下:

#创建课程表
CREATE TABLE course(
	id INT PRIMARY KEY AUTO_INCREMENT,
	NAME VARCHAR(32),
	CODE VARCHAR(32)
)

#插入两条数据
INSERT INTO course VALUES(1,"JavaWeb开发",'08113226')
INSERT INTO course VALUES(2,"Andorid程序开发",'08113228')

#创建中间表
CREATE TABLE electiveCourse(
	id INT PRIMARY KEY AUTO_INCREMENT,
	student_id INT,
	course_id INT,
	FOREIGN KEY(student_id) REFERENCES student(id),
	FOREIGN KEY(course_id) REFERENCES course(id)
)

#插入3条数据
INSERT INTO electiveCourse VALUES(1,1,1)
INSERT INTO electiveCourse VALUES(2,1,2)
INSERT INTO electiveCourse VALUES(3,2,2)

2.定义持久化类Course,代码如下

package com.ssm.po;

import java.util.List;

public class course {
    private Integer id;
    private  String name;
    private  String code;

    private List<Student> studentList;//与学生集合的关联属性

    @Override
    public String toString() {
        return "course{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", code='" + code + '\'' +
                ", studentList=" + studentList +
                '}';
    }

    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 getCode() {
        return code;
    }

    public void setCode(String code) {
        this.code = code;
    }

    public List<Student> getStudentList() {
        return studentList;
    }

    public void setStudentList(List<Student> studentList) {
        this.studentList = studentList;
    }
}

3.在Student.java持久类中,添加课程集合信息,代码如下:

private List<course> courseList;//关联课程集合信息

4.创建课程实体映射文件CourseMapper.xml,并编写StudentMapper.xml文件,代码如下:

CourseMapper.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="mapper.CourseMapper">
    <select id="findCourseByStudent" parameterType="Integer"
            resultMap="courseMap">
        SELECT * FROM  course WHERE  id=#{id}
    </select>

    <resultMap id="courseMap" type="com.ssm.po.course">
        <id property="id" column="id"/>
        <result property="name" column="name"/>
        <result property="code" column="code"/>
        <collection property="studentList" column="id"
                    ofType="com.ssm.po.Student"
                    select="mapper.StudentMapper.findStudentById1"/>
    </resultMap>
</mapper>

在上述代码中,我们使用嵌套查询来查询课程及其关联的学生信息。

StudentMapper.xml

<select id="findStudentById1" parameterType="Integer" resultType="com.ssm.po.Student">
        SELECT  * from student where id in(
          select student_id from electiveCourse where course_id=#{id}
        )
    </select>

该SQL会根据课程id查询与该课程所关联的学生信息。由于课程和学生是多对多的关联关系,因此需要通过中间表来查询学生信息

4.将映射文件添加到核心配置mybatis-config.xml中

 	<mappers>
        <mapper resource="mapper/StudentMapper.xml"/>
        <mapper resource="mapper/CourseMapper.xml"/>
    </mappers>

5.编写测试方法,代码如下:

public  static  void findCourseByStudentTest(){
        SqlSession sqlSession = MybatisUtil.getSession();
        //查询id为1的课程中的学生信息
        course cc = sqlSession.selectOne("mapper.CourseMapper.findCourseByStudent",1);
        System.out.println(cc);
        sqlSession.close();
    }

执行代码,已成功查询出课程及其关联的学生信息,结果如下:
在这里插入图片描述
通过学生id查看该学生选择了几门课程是一样的逻辑,这里就不再给出了。

接下来使用嵌套结果来进行查询,其代码如下;

CourseMapper.xml

 <select id="findCourseByStudent2" parameterType="Integer"
            resultMap="courseMap1">
      SELECT c.*,s.id as s_id,s.name as s_name,s.sex as s_sex
      FROM  course as c,student as s,electiveCourse as e
      where c.id=e.course_id and e.student_id=s.id
      and c.id=#{id}
    </select>

    <resultMap id="courseMap1" type="com.ssm.po.course">
        <id property="id" column="id"/>
        <result property="name" column="name"/>
        <result property="code" column="code"/>
        <collection property="studentList" ofType="com.ssm.po.Student">
            <id property="id" column="s_id"/>
            <result property="name" column="s_name"/>
            <result property="sex" column="s_sex"/>
        </collection>
    </resultMap>

使用嵌套结果查询与嵌套查询的结果是一致的。

注意
如果两个持久类中有属性是一致的,且用于关联关系中的嵌套结果查询,需要在SQL语句中起别名查询,在对属性进行映射的时候,使用别名进行映射。

比如说上面我们根据课程id查询课程以及关联的学生,按照以前的写法,属性名对应字段名,代码如下:

 <select id="findCourseByStudent2" parameterType="Integer"
            resultMap="courseMap1">
      SELECT c.*,s.id as s_id,s.name as s_name,s.sex as s_sex
      FROM  course as c,student as s,electiveCourse as e
      where c.id=e.course_id and e.student_id=s.id
      and c.id=#{id}
    </select>

    <resultMap id="courseMap1" type="com.ssm.po.course">
        <id property="id" column="id"/>
        <result property="name" column="name"/>
        <result property="code" column="code"/>
        <collection property="studentList" ofType="com.ssm.po.Student">
            <id property="id" column="id"/>
            <result property="name" column="name"/>
            <result property="sex" column="sex"/>
        </collection>
    </resultMap>

运行结果可以发现,course表中的name字段映射到了Student表中的name字段
在这里插入图片描述
所以我们需要在SQL语句起别名,来进行区分。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值