前言
MyBatis是一个流行的Java持久化框架,它提供了一种简单而强大的方式来映射Java对象和关系数据库之间的数据。在MyBatis中,关系关联配置是一种用于定义对象之间关系的方式,它允许我们在查询数据库时同时获取相关联的对象。
在MyBatis中,有三种主要的关系关联配置方式:一对一(One-to-One)、一对多(One-to-Many)和多对多(Many-to-Many)。
一. Mybatis关系关联配置的优劣
1.1 MyBatis中的关系关联配置具有以下好处:
- 简化数据查询:通过关系关联配置,可以在查询数据时自动加载相关联的数据,无需手动编写复杂的SQL语句。这简化了数据查询的过程,减少了开发人员的工作量。
- 提高代码可读性:通过关系关联配置,可以将数据表之间的关系直观地映射到Java对象中。这使得代码更易于理解和维护,提高了代码的可读性。
- 减少数据库访问次数:通过关系关联配置,可以一次性加载多个相关联的数据,减少了与数据库的交互次数。这可以提高系统的性能和响应速度。
- 支持对象导航:通过关系关联配置,可以在Java对象中直接访问相关联的数据,而无需手动编写额外的查询语句。这简化了代码的编写,提高了开发效率。
1.2 然而,MyBatis中的关系关联配置也存在一些缺点:
- 学习成本较高:关系关联配置需要了解MyBatis的配置文件和相关标签的使用方法。对于初学者来说,可能需要花费一些时间来学习和理解这些配置。
- 配置复杂性:对于复杂的关系关联,配置文件可能会变得复杂和冗长。这可能增加了维护和调试的难度。
- 性能问题:如果关系关联配置不合理,可能会导致数据加载过多或过少,从而影响系统的性能。因此,在配置关系关联时需要注意性能优化的问题。
二. 关联关系的方向
在MyBatis中,关系关联的方向可以分为两种:单向关联和双向关联。
单向关联
单向关联表示关系只在一个方向上存在,其中一个表可以关联到另一个表,但反过来不行。在单向关联中,只有一个表的对象包含了关联对象的引用,而关联对象本身不包含对关联表的引用。
例如,假设我们有两个表:User和Order,一个用户可以有多个订单。在这种情况下,我们可以在User对象中定义一个关联属性List<Order> orders,表示一个用户关联多个订单。但是,在Order对象中并不包含对用户的引用。
单向关联的配置在MyBatis中非常简单,只需要在<resultMap>中使用<collection>或<association>标签来定义关联即可。
双向关联
双向关联表示关系在两个表之间是相互关联的,每个表的对象都包含了对另一个表的引用。这样,我们可以通过一个表的对象访问到关联表的对象,也可以通过关联表的对象访问到原始表的对象。
以前面的例子为例,我们可以在User对象中定义一个关联属性List<Order> orders,表示一个用户关联多个订单。同时,在Order对象中也定义一个关联属性User user,表示一个订单关联一个用户。
双向关联的配置相对复杂一些,需要在<resultMap>中使用<collection>或<association>标签来定义关联,并在关联对象中使用<association>标签来定义反向关联。
需要注意的是,无论是单向关联还是双向关联,都需要在MyBatis的映射文件中进行配置。关联的方向取决于我们在配置时定义的关联属性和关联对象的引用。
总结来说,MyBatis中的关系关联可以是单向关联或双向关联。单向关联表示关系只在一个方向上存在,而双向关联表示关系在两个表之间是相互关联的。在配置关系关联时,我们需要定义关联属性和关联对象的引用,以确定关联的方向。
三. 一对一关联配置
一对一关联配置用于表示两个对象之间的一对一关系(例如夫妻关系,人与身份证)。在数据库中,这通常通过外键来实现。在MyBatis中,我们可以使用<association>元素来配置一对一关联。该元素通常嵌套在<resultMap>元素中,并使用property属性指定关联对象的属性名。
以订单表(order)和订单项表(orderItem)为例,一个订单项对应一个订单。
编写一个vo类继承orderItem,在映射文件中可以通过这个类拿到订单表和订单项表的每个属性:
package com.xissl.vo; import com.xissl.model.HOrder; import com.xissl.model.HOrderItem; /** * @author xissl * @create 2023-09-04 9:52 */ public class OrderItemVo extends HOrderItem { private HOrder horder; public HOrder getHorder() { return horder; } public void setHorder(HOrder horder) { this.horder = horder; } }
在映射文件中配置一对一关联,HOrderItemMapper.xml
<resultMap id="OrderItemVoMap" type="com.xissl.vo.OrderItemVo"> <result column="order_item_id" property="orderItemId"></result> <result column="product_id" property="productId"></result> <result column="quantity" property="quantity"></result> <result column="oid" property="oid"></result> <association property="horder" javaType="com.xissl.model.HOrder"> <result column="order_id" property="orderId"></result> <result column="order_no" property="orderNo"></result> </association> </resultMap> <select id="selectByOrderItemId" resultMap="OrderItemVoMap" parameterType="java.lang.Integer"> select * from t_hibernate_order o,t_hibernate_order_item oi where o.order_id=oi.oid and order_item_id= #{orderItemId} </select>
在上面的示例中,我们首先定义了一个resultMap,用于映射OrderItem对象和Order对象之间的关系。在resultMap中,我们使用association元素来配置一对一关联。association元素中的property属性指定了在OrderItem对象中表示Order对象的属性名,javaType属性指定了关联对象的类型。
然后,我们定义了一个查询语句selectByOrderItemId,使用resultMap来映射查询结果。
编写mapper层接口:
OrderItemVo selectByOrderItemId(@Param("orderItemId") Integer orderItemId);
业务逻辑层:
package com.xissl.biz; import com.xissl.model.HOrderItem; import com.xissl.vo.OrderItemVo; import org.apache.ibatis.annotations.Param; public interface HOrderItemBiz { OrderItemVo selectByOrderItemId(Integer orderItemId); }
实现业务层接口:
package com.xissl.biz.impl; import com.xissl.biz.HOrderItemBiz; import com.xissl.mapper.HOrderItemMapper; import com.xissl.vo.OrderItemVo; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; /** * @author xissl * @create 2023-09-04 9:59 */ @Service public class HOrderItemBizImpl implements HOrderItemBiz { @Autowired private HOrderItemMapper hOrderItemMapper; @Override public OrderItemVo selectByOrderItemId(Integer orderItemId) { return hOrderItemMapper.selectByOrderItemId(orderItemId); } }
测试代码及结果:
package com.xissl.biz.impl; import com.xissl.biz.HOrderItemBiz; import com.xissl.vo.OrderItemVo; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import static org.junit.Assert.*; /** * @author xissl * @create 2023-09-04 10:01 */ @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations={"classpath:spring-context.xml"}) public class HOrderItemBizImplTest { @Autowired private HOrderItemBiz hOrderItemBiz; @Test public void selectByOrderItemId() { OrderItemVo orderItemVo = hOrderItemBiz.selectByOrderItemId(27); System.out.println(orderItemVo); System.out.println(orderItemVo.getHorder()); } }
四. 一对多关联配置
一对多关联配置用于表示一个对象与多个相关对象之间的关系(例如用户与用户的订单,锁和钥匙)。在数据库中,这通常通过外键来实现。在MyBatis中,我们可以使用<collection>元素来配置一对多关联。该元素通常嵌套在<resultMap>元素中,并使用property属性指定关联对象的属性名。
以订单表(order)和订单项表(orderItem)为例,一个订单可以有多个订单项。
编写一个vo类继承order,在映射文件中可以通过这个类拿到订单表和订单项表的每个属性:
package com.xissl.vo; import com.xissl.model.HOrder; import com.xissl.model.HOrderItem; import java.util.ArrayList; import java.util.List; /** * @author xissl * @create 2023-09-04 8:49 */ public class OrderVo extends HOrder { private List<HOrderItem> orderItems = new ArrayList<>(); public List<HOrderItem> getOrderItems() { return orderItems; } public void setOrderItems(List<HOrderItem> orderItems) { this.orderItems = orderItems; } }
在映射文件中配置一对多关联,HOrderMapper.xml
<resultMap id="OrderVoMap" type="com.xissl.vo.OrderVo" > <result column="order_id" property="orderId"></result> <result column="order_no" property="orderNo"></result> <collection property="orderItems" ofType="com.xissl.model.HOrderItem"> <result column="order_item_id" property="orderItemId"></result> <result column="product_id" property="productId"></result> <result column="quantity" property="quantity"></result> <result column="oid" property="oid"></result> </collection> </resultMap> <select id="selectByOid" resultMap="OrderVoMap" parameterType="java.lang.Integer"> select * from t_hibernate_order o,t_hibernate_order_item oi where o.order_id=oi.oid and order_id= #{oid} </select>
在上面的示例中,我们首先定义了一个resultMap,用于映射Order对象和OrderItem对象之间的关系。在resultMap中,我们使用collection元素来配置一对多关联。collection元素中的property属性指定了在Order对象中表示订单项表的属性名,ofType属性指定了关联对象的类型。
然后,我们定义了一个查询语句selectByOid,使用resultMap来映射查询结果。
编写mapper层接口:
OrderVo selectByOid(@Param("oid") Integer oid);
业务逻辑层:
OrderVo selectByOid(Integer oid);
实现业务层接口:
package com.xissl.biz.impl; import com.xissl.biz.HOrderBiz; import com.xissl.mapper.HOrderMapper; import com.xissl.vo.OrderVo; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; /** * @author xissl * @create 2023-09-04 9:08 */ @Service public class HOrderBizImpl implements HOrderBiz { @Autowired private HOrderMapper hOrderMapper; @Override public OrderVo selectByOid(Integer oid) { return hOrderMapper.selectByOid(oid); } }
测试代码及结果:
package com.xissl.biz.impl; import com.xissl.biz.HOrderBiz; import com.xissl.vo.OrderVo; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import static org.junit.Assert.*; /** * @author xissl * @create 2023-09-04 9:10 */ @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations={"classpath:spring-context.xml"}) public class HOrderBizImplTest { @Autowired private HOrderBiz hOrderBiz; @Test public void selectByOid() { OrderVo orderVo = hOrderBiz.selectByOid(7); System.out.println(orderVo); orderVo.getOrderItems().forEach(System.out::println); } }
五. 多对多关联配置
多对多关联配置用于表示两个对象之间的多对多关系(例如老师和学生)。在数据库中,这通常通过中间表来实现。在MyBatis中,我们可以使用<collection>元素来配置多对多关联。该元素通常嵌套在<resultMap>元素中,并使用property属性指定关联对象的属性名。
多对多关系就是和一对多的关系的大同小异,只是多对多的关系可以看成两个一对多关系。
vo类 BookVo:
package com.xissl.vo; import com.xissl.model.HBook; import com.xissl.model.HCategory; import java.util.List; /** * @author xissl * @create 2023-09-04 10:43 */ public class HBookVo extends HBook { private List<HCategory> categories; public List<HCategory> getCategories() { return categories; } public void setCategories(List<HCategory> categories) { this.categories = categories; } }
CategoryVo:
package com.xissl.vo; import com.xissl.model.HBook; import com.xissl.model.HCategory; import java.util.ArrayList; import java.util.List; /** * @author xissl * @create 2023-09-04 11:03 */ public class CategroyVo extends HCategory { private List<HBook> hbooks = new ArrayList<>(); public List<HBook> getHbooks() { return hbooks; } public void setHbooks(List<HBook> hbooks) { this.hbooks = hbooks; } }
映射文件 HBookMapper.xml
<resultMap id="HBookVoMap" type="com.xissl.vo.HBookVo"> <result column="book_id" property="bookId"></result> <result column="book_name" property="bookName"></result> <result column="price" property="price"></result> <collection property="categories" ofType="com.xissl.model.HCategory"> <result column="category_id" property="categoryId"></result> <result column="category_name" property="categoryName"></result> </collection> </resultMap> <!-- 根据书籍id查询出书籍信息及所属类别--> <select id="selectByBid" resultMap="HBookVoMap" parameterType="java.lang.Integer"> select * from t_hibernate_book b, t_hibernate_book_category bc, t_hibernate_category c where b.book_id = bc.bid and bc.cid = c.category_id and b.book_id= #{bid} </select>
HCategoryMapper.xml
<resultMap id="CategoryVoMap" type="com.xissl.vo.CategroyVo"> <result column="category_id" property="categoryId"></result> <result column="category_name" property="categoryName"></result> <collection property="hbooks" ofType="com.xissl.model.HBook"> <result column="book_id" property="bookId"></result> <result column="book_name" property="bookName"></result> <result column="price" property="price"></result> </collection> </resultMap> <select id="selectByCategroyId" resultMap="CategoryVoMap" parameterType="java.lang.Integer"> select * from t_hibernate_book b, t_hibernate_book_category bc, t_hibernate_category c where b.book_id = bc.bid and bc.cid = c.category_id and c.category_id= #{cid} </select>
mapper层接口 BookMapper:
HBookVo selectByBid(@Param("bid") Integer bid);
CategoryMapper
HBookVo selectByBid(@Param("bid") Integer bid);
业务逻辑层:
HBookVo selectByBid(Integer bid);
CategroyVo selectByCategroyId(Integer cid);
实现业务层 BookBiz
package com.xissl.biz.impl; import com.xissl.biz.HBookBiz; import com.xissl.mapper.HBookMapper; import com.xissl.vo.HBookVo; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; /** * @author xissl * @create 2023-09-04 10:55 */ @Service public class HBookBizImpl implements HBookBiz { @Autowired private HBookMapper hBookMapper; @Override public HBookVo selectByBid(Integer bid) { return hBookMapper.selectByBid(bid); } }
实现CategoryBiz
package com.xissl.biz.impl; import com.xissl.biz.HCategoryBiz; import com.xissl.mapper.HCategoryMapper; import com.xissl.vo.CategroyVo; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; /** * @author xissl * @create 2023-09-04 11:11 */ @Service public class HCategoryBizImpl implements HCategoryBiz { @Autowired private HCategoryMapper hCategoryMapper; @Override public CategroyVo selectByCategroyId(Integer cid) { return hCategoryMapper.selectByCategroyId(cid); } }
测试代码及结果:
package com.xissl.biz.impl; import com.xissl.biz.HBookBiz; import com.xissl.vo.HBookVo; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import static org.junit.Assert.*; /** * @author xissl * @create 2023-09-04 10:56 */ @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations={"classpath:spring-context.xml"}) public class HBookBizImplTest { @Autowired private HBookBiz hBookBiz; @Test public void selectByBid() { HBookVo hBookVo = hBookBiz.selectByBid(8); System.out.println(hBookVo); hBookVo.getCategories().forEach(System.out::println); } }
package com.xissl.biz.impl; import com.xissl.biz.HCategoryBiz; import com.xissl.vo.CategroyVo; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import static org.junit.Assert.*; /** * @author xissl * @create 2023-09-04 11:12 */ @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations={"classpath:spring-context.xml"}) public class HCategoryBizImplTest { @Autowired private HCategoryBiz hCategoryBiz; @Test public void selectByCategroyId() { CategroyVo categroyVo = hCategoryBiz.selectByCategroyId(8); System.out.println(categroyVo); categroyVo.getHbooks().forEach(System.out::println); } }
除了上述关系关联配置方式,MyBatis还提供了其他一些配置选项,如延迟加载(Lazy Loading)和级联操作(Cascade)。延迟加载允许我们在需要时才加载关联对象的数据,而级联操作允许我们在操作主对象时同时操作关联对象。
总结起来,MyBatis中的关系关联配置提供了一种灵活而强大的方式来处理对象之间的关系。通过合理配置关系关联,我们可以轻松地进行复杂的数据库查询和操作。