关于MyBatis中映射对象关系的举例

2022-11-13 10:11:17 对象 映射 举例

MyBatis映射对象关系

双向many2one/one2many关系中的组合关系(级联)

上面已经看到了双向many2one/one2many关系的映射。

但是我们说,在关联关系中,还存在组合/聚合关系。

所谓聚合就是one方和many方可以独立存在;组合关系是强聚合关系,one方和many方解除关系后,就没有实际意义了。

在组合关系中,必须要处理的一个问题就是级联关系。

下面使用SaleBill和SaleBillItem对象来完成双向的many2one/one2many的组合关系。

对象设计如下:

//销售单对象
public class SaleBill {
    private Long id;
    private String sn;
    private Set<</span>SaleBillItem> items = new HashSet<</span>SaleBillItem>();
    //getter & setter
}  
        
//销售单明细对象
public class SaleBillItem {
    private Long id;
    private Double price;
    private SaleBill bill;
    //getter & setter
}
  • 对于组合关系,数据表的结构设计和many2one一样,都是many方一个外键引用one方的主键。
  • 在组合关系中,一般来说,明细项是不需要额外有一个Mapper文件的,所有的映射都应该放在One方的映射文件中。

下面来看看映射文件:    

<mapper namespace="cd.itcast.mybatis.salebill.SaleBillMapper">
<!-- 保存SaleBill对象,该调用必须放在SaleBillItem对象保存之前 -->
    <insert id="saveSaleBill" keyProperty="id" parameterType="SaleBill"
                useGeneratedKeys="true">
                INSERT INTO salebill(sn) values (#{sn})
    </insert>
<!-- 保存SaleBillItem对象 -->
    <insert id="saveSaleBillItem" keyProperty="id" parameterType="SaleBillItem" useGeneratedKeys="true">
            INSERT INTO salebillitem(price,bill_id) values (#{price},#{bill.id})
</insert>
<!-- 内嵌映射SaleBill对象,注意,因为是组合关系,所以不会出现单独去拿SaleBillItem对象的情况,所以只需要映射SaleBill主对象即可 -->
<resultMap type="SaleBill" id="salebillmap">
            <id property="id" column="id"/>
            <result column="sn" property="sn"/>
            <collection property="items" column="id" ofType="SaleBillItem" >
            <id column="item_id" property="id"/>
            <result column="item_price" property="price"/>
            <association property="bill" column="bill_id" resultMap="salebillmap" />
           </collection>
</resultMap>
 
<!-- 因为列表和单独的get方法都是采用内联的查询,所以把公用的sql提取出来 -->
    <sql id="select">
            SELECT b.*,item.id as item_id,item.price as item_price FROM salebill b LEFT JOIN salebillitem item ON b.id = item.bill_id
     </sql>
 
<!-- get -->
    <select id="get" parameterType="long" resultMap="salebillmap">
            <include refid="select"/> WHERE b.id = #{id}
     </select>
 
<!-- 列表查询 -->
    <select id="list" resultMap="salebillmap">
            <include refid="select"/>
     </select>
 
<!-- 删除SaleBill对象 -->
    <delete id="delete" parameterType="long">
            DELETE FROM salebill WHERE id = #{id}      
    </delete>
 
<!-- 删除SaleBill对象对应的Item对象,注意,因为有外键的约束,这条删除语句应该放在delete SaleBill之前执行。注意,传入的这个id应该是SaleBill的id -->
    <delete id="deleteItems" parameterType="long">
     DELETE FROM salebillitem WHERE bill_id = #{id}
    </delete>
</mapper>

映射文件的内容都已经注释详细。

主要注意一点的就是在设计映射文件的时候需要考虑组合关系的一般设计方式,所以在上面的配置文件中只做了SaleBill对象的映射。

这个配置文件还是很好理解的,其中所有的配置点在前面都已经介绍过。

在组合关系中,主要的问题就是一个级联保存,但是注意,在mybatis中设计组合的级联保存,删除,这个过程应该是我们自己来控制的。  

下面来看看怎么测试组合关系:

//先创建一个Mapper接口,使用接口来完成映射
public interface SaleBillMapper {
    void saveSaleBill(SaleBill sb);
    void saveSaleBillItem(SaleBillItem sbi);
    SaleBill get(Long id);
    //在接口中可以直接映射分页对象
    List<</span>SaleBill> list(RowBounds rb);
    void delete(Long id);
    void deleteItems(Long billId);
}

具体测试代码:

@Test
public void testSave() {
    //创建SaleBill对象
    SaleBill sb = new SaleBill();
    sb.setSn("001");
    //创建一个明细对象
    SaleBillItem sbi = new SaleBillItem();
    sbi.setPrice(100d);
    sbi.setBill(sb);
    //创建一个明细对象
    SaleBillItem sbi2 = new SaleBillItem();
    sbi2.setPrice(300d);
    sbi2.setBill(sb);
    //处理关系
    sb.getItems().add(sbi);
    sb.getItems().add(sbi2);
    //保存
    SqlSession session = MyBatisUtil.openSession();
    SaleBillMapper mapper = session.getMapper(SaleBillMapper.class);
    //注意,先保存SaleBill对象
    mapper.saveSaleBill(sb);
    //保存明细对象
    mapper.saveSaleBillItem(sbi);
    mapper.saveSaleBillItem(sbi2);
    session.commit();
    session.close();
}
 
@Test
public void testGet() {
    SqlSession session = MyBatisUtil.openSession();
    //得到SaleBill对象
    SaleBillMapper mapper = session.getMapper(SaleBillMapper.class);
    SaleBill sb = mapper.get(5l);
    Set<</span>SaleBillItem> items = sb.getItems();
    for (SaleBillItem item : items) {
            System.out.println(item.getPrice());
    }
    session.close();
}
 
@Test
public void testDelete() {
    SqlSession session = MyBatisUtil.openSession();
    SaleBillMapper mapper = session.getMapper(SaleBillMapper.class);
    mapper.deleteItems(1l);
    mapper.delete(1l);
    session.commit();
    session.close();
}    

Mybatis映射原理

MyBatis的真正强大之处在于它的映射语句,这也是它的魔力所在。由于它的映射语句异常强大,映射器的 XML 文件就显得相对简单。

MyBatis3.0相比2.0版本的一个最大变化,就是支持使用接口来调用方法。

  • 以前使用 SqlSession 通过命名空间调用 MyBatis 方法时,首先需要用到命名空间和方法id 组成的字符串来调用相应的方法 。
  • 当参数多于 1 个的时候,需要将所有参数放到一个 Map对象中 。 通过 Map 传递多个参数,使用起来很不方便,而且还无法避免很多重复的代码。
  • 使用接口调用方式就会方便很多, MyBatis 使用 Java 的动态代理可以直接通过接口来调用 相应 的方法,不需要提供接口的实现类,更不需要在实现类中使用 SqlSess 工∞以通过命名空 间间接调用 。
  • 另外,当有多个参数的时候,通过参数注解@ Par am 设置参数的名字省去了 手动构造 Map 参数的过程,尤其在 spring 中使用的时候,可以配置为自动扫描所有的接口类 ,直接将接口注入需要用到的地方。

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.cnpiec.ireader.dao.GetBookDataDao">
    <insert id="batchInsertBook" parameterType="Book">
        INSERT INTO BOOK (BOOKID,NAME,PARTNERNAME) VALUES
        <foreach collection="list" item="book" separator=",">
            (#{book.bookId}, #{book.name}, #{book.partnerName})
        </foreach>
    </insert>
</mapper>

需要注意的是<mapper>根标签的 name space 属性。当 Mapper 接口和 XML 文件关联的时候,命名空间口amespace 的值就需要配置成接口的全限定名称,例如 UserMapper 接口对应的 tk. mybatis . simple .mapper . UserMapper, MyBatis 内部就是通过这个值将接口和XML 关联起来的。

mybati s-config.xml 配置文件中的 mappers 元素中配置所有的 mapper ,部分配置代码如下 。

<mappers>
    <mapper resource=” tk/mybatis/simple/mapper/CountryMapper.xml ” / >
    <mapper resource=” tk/mybatis/simple/mapper/UserMapper . xml ” / >
    <mapper resource=” tk/mybatis/simple/mapper/RoleMapper.xml ” />
    <mapper resource=” tk/mybatis/simple/mapper/PrivilegeMapper . xml ” />
    <mapper resource=” tk/mybatis/simple/mapper/UserRoleMapper . xml ” />
    <mapper resource=” tk/mybatis/simple/mapper / RolePrivilegeMapper.xml ” />
</mappers>

更简单的配置方式,代码如下

<mappers>
    <package name= ” tk.mybatis . simple . mapper ” />
</mappers>

这种配置方式会先查找 tk.mybatis.simple . mapper 包下所有的接口,循环对接口进行如下操作。这种配置方式会先查找 tk.mybatis.simple . mapper 包下所有的接口,循环对接口进行判断接口对应的命名 空 间是否己经存在,如果不存在就抛出异常,存在就继续进行接下来的操作。加载接口对应的却也映射文件 , 将接口全限定名转换为路径.

为什么 Mapper 接口没有实现类却能被正常调用呢?

这是因为MyBaits 在 Mapper 接口上使用了动态代理的一种非常规的用法,熟悉这动态代理的用法不仅有利于理解 MyBatis 接口和 XML 的关系,还能开阔思路 。

从代理类中可以看到,当调用 一个接口的方法时,会先通过接口的全限定名称和当前调用的方法名的组合得到一个方法 id,这个 id 的值就是映射 XML 中口arnespa ce 和具体方法 id的组合。所以可以在代理方法中使用 sqlSession 以命名空间的方式调用方法。通过这种方式可以将接口和 XML 文件中的方法关联起来。这种代理方式和常规代理的不同之处在于,这里没有对某个具体类进行代理,而是通过代理转化成了对其他代码的调用。

由于方法参数和返回值存在很多种情况,因此 MyBatis 的内部实现会比上面的逻辑复杂得多,正是因为 MyBatis 对接口动态代理的实现,我们在使用接口方式的时候才会如此容易。 

以上为个人经验,希望能给大家一个参考,也希望大家多多支持。

相关文章