MyBatis:关联映射(一对多映射、多对多映射、继承映射)

当关联实体为多个时(实际包括1—NN—N两种情况),首先需要使用集合(如List或Set)来容纳多个关联实体,然后在XML Mapper文件中使用<collection.../>元素进行映射。

<collection.../>元素与前面介绍的<association.../>非常相似,他们支持的属性也基本相同,区别只是<collection.../>元素额外支持一个ofType属性,该属性用于指定关联实体(集合元素)的类型,而javaType属性则用于指定集合本身的类型(如ArrayList、HashSet等)。<collection.../>同样支持三种映射策略:

  • 基于嵌套select的一对多映射。
  • 基于多表连接查询的一对多映射。
  • 基于多结果集的一对多映射。

1,基于嵌套select的一对多映射

在使用基于嵌套select的一对多映射策略时,除需要为<collection.../>指定property、javaType、ofType、jdbcType、typeHandler等通用属性之外,还需要指定select、column、fetchType这三个属性,其用法与<association.../>元素完全相同。

这里同样采用Person和Address两个实体,只不过这里一个Person对应多个Address实体。

public class Person {
    private Integer id;
    private String name;
    private Integer age;
    private List<Address> addresses;
    //省略构造器,set,get方法
}
-----------------------------
public interface PersonMapper {
    Person findPersonById(Integer id);
}
-----------------------------
<?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.ysy.MyBatis.Dao.PersonMapper">
    <select id="findPersonById" resultMap="personMap">
        select * from person_inf where person_id=#{id}
    </select>
    <resultMap id="personMap" type="com.ysy.MyBatis.Bean.Person">
        <id column="person_id"  property="id"/>
        <result column="person_name" property="name"/>
        <result column="person_age" property="age"/>
        <!--使用select指定的select语句抓取关联实体,将当前实体的person_id列的值作为参数错传给select语句ofType属性指定关联实体(集合元素)的类型-->
        <collection property="addresses" javaType="ArrayList" ofType="com.ysy.MyBatis.Bean.Address" column="person_id"
                    select="com.ysy.MyBatis.Dao.AddressMapper.findAddressByOwner" fetchType="lazy"/>
    </resultMap>
</mapper>
public class Address {
    private Integer id;
    private String detail;
    private Person person;
    //省略构造器,get,set方法
}
----------------------------------------
public interface AddressMapper {
    Address getAddress(Integer id);
    ArrayList<Address> findAddressByOwner(Integer id);
}
----------------------------------------
<?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.ysy.MyBatis.Dao.AddressMapper">
    <select id="getAddress" resultMap="addressMap">
        select * from address_inf where addr_id=#{id}
    </select>
    <resultMap id="addressMap" type="com.ysy.MyBatis.Bean.Address">
        <id column="addr_id"  property="id"/>
        <result column="addr_detail" property="detail"/>
        <association property="person" javaType="com.ysy.MyBatis.Bean.Person" column="owner_id"
                     select="com.ysy.MyBatis.Dao.PersonMapper.findPersonById" fetchType="lazy"/>
    </resultMap>
    <select id="findAddressByOwner" resultMap="addressMap">
        select * from address_inf where owner_id=#{id}
    </select>
</mapper>
  • Address实体是1—N关联关系中N的一端,它的关联实体依旧是单个Person实体,因此,每个Address只需要一个Person实体。
  • Person实体是1—N关联关系中的1的一端,它的关联实体依然是多个Address实体,因此,Person类中需要使用List<Address>类型的变量来代替关联实体。

<collection.../>元素定义了1—N的关联实体,<collection.../>元素的用法与<association.../>元素非常相似,区别只是它增加了ofType="address",用于指定关联实体(集合元素)的类型是Address类名。

对于基于嵌套select的映射策略,不管是1—N还是1—1,都建议指定fetchType="lazy"应对策略。尤其是当知道关联实体有多个时(包括1—N、N—N),出于性能考虑,推荐策略就是“基于嵌套select+延迟加载”的映射策略。这是因为:关联实体有多个,程序无法预先知道关联实体具体有多少个,可能有100个,也可能有1000个。当关联实体有很多时,如果在加载主表实体时就立即把所有关联实体(可能有1w)全部加载进来,并不是一种好的做法。

如果需要使用注解,则需要使用@Many注解来代替<collection.../>元素——严格来说,@Many并不等于<collection.../>元素,而是@Result+@Many才等于<collection.../>元素。@Many注解根本不能被单独使用(它不能修饰任何程序单元),它只能作为@Result的many属性的值。该注解只能指定如下两个属性:

  • select:等同于<collection.../>元素的select属性。
  • fetchType:等同于<collection.../>元素的fetchType属性。

<collection.../>元素支持的property、javaType、jdbcType、typeHandler、column等属性,则北直街放在@Result注解中指定。

public interface PersonMapper {
    @Select("select * from person_inf where person_id=#{id}")
    @Results(id="personMap",value = {
            @Result(column = "person_id",property = "id",id = true),
            @Result(column = "person_name",property = "name"),
            @Result(column = "person_age",property = "age"),
            @Result(property = "addresses",javaType = java.util.ArrayList.class,column = "person_id",
            many = @Many(select = "com.ysy.MyBatis.Dao.AddressMapper.findAddressByOwner",fetchType = FetchType.LAZY))
    })
    Person findPersonById(Integer id);
}
---------------------------------------------------------
public interface AddressMapper {
    @Select("select * from address_inf where addr_id=#{id}")
    @Results(id = "addressMap",value = {
            @Result(column = "addr_id",property = "id",id = true),
            @Result(column = "addr_detail",property = "detail"),
            @Result(property = "person",javaType = Person.class,column = "owner_id",
            one = @One(select = "com.ysy.MyBatis.Dao.PersonMapper.findPersonById",fetchType = FetchType.LAZY))
    })
    Address getAddress(Integer id);
    @Select("select * from address_inf where owner_id=#{id}")
    @ResultMap("addressMap")
    ArrayList<Address> findAddressByOwner(Integer id);
}

2,基于多表连接查询的一对多映射

如果多表连接查询语句一次返回了两个关联表的记录,那么MyBatis依然可以使用<collection.../>元素来映射一对多关联。你唯一需要担心的是:当关联实体有多个时,一次性加载太多的数据记录可能会导致系统的内存压力增加。

在使用<collection.../>元素定义基于多表连接查询的一对多映射策略下时,同时可指定resultMap、columnPrefix、nutNullColumn、autoMapping这4个属性,它们的功能和用户与<association.../>元素的完全相同。

<?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.ysy.MyBatis.Dao.PersonMapper">
    <select id="findPersonById" resultMap="personMap">
        select p.*, a.addr_id addr_id, a.addr_detail addr_detail from person_inf p
        join address_inf a on a.owner_id = p.person_id
        where person_id=#{id}
    </select>
    <resultMap id="personMap" type="com.ysy.MyBatis.Bean.Person">
        <id column="person_id"  property="id"/>
        <result column="person_name" property="name"/>
        <result column="person_age" property="age"/>
        <!--使用select指定的select语句抓取关联实体,将当前实体的person_id列的值作为参数错传给select语句ofType属性指定关联实体(集合元素)的类型-->
        <collection property="addresses" javaType="ArrayList" ofType="com.ysy.MyBatis.Bean.Address" column="person_id"
                    resultMap="com.ysy.MyBatis.Dao.AddressMapper.addressMap" fetchType="eager"/>
    </resultMap>
</mapper>
-----------------------------------------------------------
<?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.ysy.MyBatis.Dao.AddressMapper">
    <select id="getAddress" resultMap="addressMap">
        select a.addr_id addr_id,a.addr_detail addr_detail,p.*
        from address_inf a join person_inf p
        on a.owner_id = p.person_id
        where a.addr_id = #{id}
    </select>
    <resultMap id="addressMap" type="com.ysy.MyBatis.Bean.Address">
        <id column="addr_id"  property="id"/>
        <result column="addr_detail" property="detail"/>
        <association property="person" javaType="com.ysy.MyBatis.Bean.Person" column="owner_id"
                     resultMap="com.ysy.MyBatis.Dao.PersonMapper.personMap" fetchType="eager"/>
    </resultMap>
    <select id="findAddressByOwner" resultMap="addressMap">
        select * from address_inf where owner_id=#{id}
    </select>
</mapper>

上面映射文件中定义的<collection.../>元素定义了resultMap属性,该属性负责将多表连接查询结果中关于关联实体的列映射成关联实体。使用<collection.../>元素与使用<association.../>元素的区别在于:使用<collection.../>元素时要通过ofType属性指定关联实体(集合元素)的类型,通过javaType属性指定集合本身的类型。

3,基于嵌套select的多对多映射

对于关联实体是多个的情况(包括N—N关联关系、1—N关联关系),建议采用“基于嵌套的select的映射 + 延迟加载”的策略,这样能保证具有较好的性能。对于N—N关联关系,数据表之间必须通过关联表来建立关联关系。

create table person_inf(
	person_id	int PRIMARY Key auto_increment,
	person_name	varchar(255),
	person_age int
)

create table address_inf(
	addr_id int PRIMARY KEY auto_increment,
	addr_detail varchar(255)
)

create table person_address(
	owner_id int,
	address_id int,
	primary key(owner_id,address_id),
	foreign key(owner_id) REFERENCES person_inf(person_id),
	foreign key(address_id) REFERENCES address_inf(addr_id)
)

接下来,两个实体的映射文件都使用<collection.../>元素映射关联实体,然后通过select属性来指定嵌套select语句。

public interface PersonMapper {
    Person findPersonByAddr(Integer id);
}
-------------------------------------
public interface AddressMapper {
    Address findAddressByOwner(Integer id);
}
-------------------------------------
<?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.ysy.MyBatis.Dao.PersonMapper">
    <resultMap id="personMap" type="com.ysy.MyBatis.Bean.Person">
        <id column="person_id"  property="id"/>
        <result column="person_name" property="name"/>
        <result column="person_age" property="age"/>
        <!--使用select指定的select语句抓取关联实体,将当前实体的person_id列的值作为参数错传给select语句ofType属性指定关联实体(集合元素)的类型-->
        <collection property="addresses" javaType="ArrayList" ofType="com.ysy.MyBatis.Bean.Address" column="person_id"
                    select="com.ysy.MyBatis.Dao.AddressMapper.findAddressByOwner" fetchType="lazy"/>
    </resultMap>
    <select id="findPersonByAddr" resultMap="personMap">
        select p.* from person_inf p
        join person_address pa
        on p.person_id = pa.owner_id
        where pa.address_id = #{id}
    </select>
</mapper>
-------------------------------------
<?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.ysy.MyBatis.Dao.AddressMapper">
    <resultMap id="addressMap" type="com.ysy.MyBatis.Bean.Address">
        <id column="addr_id"  property="id"/>
        <result column="addr_detail" property="detail"/>
        <collection property="person" javaType="com.ysy.MyBatis.Bean.Person" column="addr_id" ofType="com.ysy.MyBatis.Bean.Person"
                     select="com.ysy.MyBatis.Dao.PersonMapper.findPersonByAddr" fetchType="lazy"/>
    </resultMap>
    <select id="findAddressByOwner" resultMap="addressMap">
        select a.* from address_inf a
        join person_address pa
        on a.addr_id = pa.address_id
        where pa.owner_id = #{id}
    </select>
</mapper>

4,基于多表连接的多对多映射

如果使用多表连接查询将关联实体对应的数据也查询出来,那么MyBatis可以在两端都是由<collection.../>元素来映射关联实体,这样即可有效地管理N—N关联映射。

<?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.ysy.MyBatis.Dao.PersonMapper">
    <resultMap id="personMap" type="com.ysy.MyBatis.Bean.Person">
        <id column="person_id"  property="id"/>
        <result column="person_name" property="name"/>
        <result column="person_age" property="age"/>
        <!--使用select指定的select语句抓取关联实体,将当前实体的person_id列的值作为参数错传给select语句ofType属性指定关联实体(集合元素)的类型-->
        <collection property="addresses" javaType="ArrayList" ofType="com.ysy.MyBatis.Bean.Address" column="person_id"
                    select="com.ysy.MyBatis.Dao.AddressMapper.findAddressByOwner" fetchType="lazy"/>
    </resultMap>
    <select id="findPersonById" resultMap="personMap">
        select p.*,a.*
        from person_inf p
        join person_address pa
        on p.person_id = pa.owner_id
        join address_inf a
        on pa.address_id = a.addr_id
        where p.person_id=#{id}
    </select>
</mapper>
----------------------------------------------------------------
<?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.ysy.MyBatis.Dao.AddressMapper">
    <resultMap id="addressMap" type="com.ysy.MyBatis.Bean.Address">
        <id column="addr_id"  property="id"/>
        <result column="addr_detail" property="detail"/>
        <collection property="person" javaType="com.ysy.MyBatis.Bean.Person" column="addr_id" ofType="com.ysy.MyBatis.Bean.Person"
                     select="com.ysy.MyBatis.Dao.PersonMapper.findPersonById" fetchType="lazy"/>
    </resultMap>
    <select id="findAddressByOwner" resultMap="addressMap">
        select a.*,p.*
        from address_inf a
        join person_address pa
        on a.addr_id = pa.address_id
        join person_inf p
        on pa.owner_id = p.person_id
        where addr_id = #{id}
    </select>
</mapper>

5,继承映射

对于面向对象编程,继承、多态是两个最基本的概念。MyBatis的继承映射可以被理解为两个持久化类之间的继承关系,例如老师和人之间的关系,老师继承了人,可以认为老师是一个特殊的人,因此在查询人的实例时,老师的实例也应该被查询出来。

MyBatis的继承映射策略是将整个继承树的所有实例保存在一个数据表中,为了有效地区分不同记录属于哪个实例,MyBatis需要为该表额外增加一个辨别者列——该列中不同的值代表不同的实例。

Person类与Custom类,其中Customer继承了Person:

public class Person {
    private Integer id;
    private String name;
    private Integer age;
    ......
}
---------------------------
public class Customer extends Person{
    private String comments;
    public String getComments() {return comments;}
    public void setComments(String comments) {this.comments = comments;}
}

MyBatis将会使用一个表来保存整个继承树,这意味着底层数据库将会用一个表同时保存Person和Customer实体,因此需要为该数据表额外增加一个“辨别者列

create table person_inf(
	person_id int PRIMARY KEY auto_increment,
	person_name varchar(255),
	person_age int,
	comments varchar(255),
	person_type int
)

在创建person_inf表时增加了一个person_type列,该列就是辨别者列——该列中不同的值代表不同的实例,此处用1代表Customer。

MyBatis在<resultMap.../>元素中添加<discriminator.../>子元素来定义辨别者列,该元素的用法很简单,它可支持如下4个属性:

  • column:指定辨别者列名。
  • javaType:指定该辨别者列对应的JAVA类型。
  • jdbcType:指定该辨别者对应的JDBC类型。
  • typeHandler:指定此处该辨别者列的类型处理器。

通常建议辨别者列使用int类型,这是由于int类型的数值在比较时具有更好的性能。不过,MyBatis也支持使用String类型的辨别者列。int类型的辨别者列具有更好的性能,但String类型的辨别者列具有更好的可读性。

接下来需要为<discriminator.../>元素添加<case.../>子元素,该子元素指定不同的值代表不同的类。<case.../>子元素可支持如下属性:

  • value:指定一个“值”,不同的值代表不同的类。
  • resultType:指定一个“类”,不同的值代表不同的类。value和resultType的对应关系就指定了“值与类”之间的对应关系。
  • resultMap:指定一个结果集映射的id,该结果集负责完成子类的映射。

<case.../>元素的resultType和resultMap两个属性只要指定其中之一即可:

  • 如果指定resultType属性,则意味着还需要<case.../>元素添加<result.../>子元素来完成列名与属性名之间的对应关系。
  • 如果指定resultMap属性,则需要额外定义一个<resultMap.../>元素来完成指定类的映射。
public interface PersonMapper {
    List<Person> findPersonByAge(Integer age);
}
--------------------------------------------
<?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.ysy.MyBatis.Dao.PersonMapper">
    <select id="findPersonByAge" resultMap="personMap">
        select * from person_inf where person_age>#{age}
    </select>
    <resultMap id="personMap" type="com.ysy.MyBatis.Bean.Person">
        <id column="person_id" property="id"/>
        <result column="person_name" property="name"/>
        <result column="person_age" property="age"/>
        <discriminator javaType="int" column="person_type">
            <case value="1" resultType="com.ysy.MyBatis.Bean.Customer">
                <result column="comments" property="comments"/>
            </case>
        </discriminator>
    </resultMap>
</mapper>

通过DeBug模式可以看到findPersonByAge()方法不仅返回了Person对象,也返回了Customer对象,对于person_type列的值为1的记录,MyBatis把它映射成了Customer对象。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

燕双嘤

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值