MyBatis(六) —— 高级映射

1. 数据模型分析

MyBatis高级映射涉及到联合查询,包括一对一、一对多、多对多等复杂查询。本文中所用的数据来自于MyBatis(二)—— 入门程序之单表增删查改。这里面有用户表、商品表、订单表、订单明细表。订单关联用户,订单明细关联订单和商品,以下是用visio2010画的数据库模型图。
MyBatis(六) —— 高级映射_第1张图片
根据上述模型图和表的实际含义,可以推测出下面的对应关系:

  1. 订单——用户: 一个订单对应一个用户,属于一对一的关系;
  2. 用户——订单:一个用户可以创建多个订单,属于一对多的关系;
  3. 订单——订单明细:一个订单可以对应多个订单明细,属于一对多的关系;
  4. 订单明细——订单:一个订单明细对应于一个订单,属于一对一的关系;
  5. 订单明细——商品:一个订单明细对应一个商品,属于一对一的关系;
  6. 商品——订单明细:一个商品可以对应多个订单明细,属于一对多的关系;
  7. 订单——商品:订单和商品可以通过商品明细建立关系,属于多对多的关系;
  8. 用户——商品:用户和商品是多对多的关系。

2. 一对一映射

2.1 需求

查询订单信息,并且关联与此订单相关的用户信息。本例中,没有查询所有的用户信息,只查询用户名、性别和地址信息。

2.2 用resultType作为输出映射

1. POJO扩展类

这里需要用到的POJO类是Orders类,因为用户信息没有全部查询,所以不需要使用到User类。但是因为是联合查询,所以单个POJO类已经不能满足需求了,我们需要扩展POJO类。

因为订单是主查询,所以在Orders类的基础上做扩展。

public class OrdersCustom extends Orders {
	
	private String sex;
	private String username;
	private String address;
	
	public String getSex() {
		return sex;
	}

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

	public String getUsername() {
		return username;
	}

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

	public String getAddress() {
		return address;
	}

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

	@Override
	public String toString() {
		return "OrdersCustom [username=" + username + ", sex=" + sex
				+ ", address=" + address + "]";
	}
}

注意,如果要输出的话,建议重写一下toString方法,要不然看不到对应的字段(本人就犯了这样低级的错误,还傻乎乎的查遍了所有地方,最后栽在一个toString上……)。

2. Mapper.java

public interface OrdersMapper {
	public List<OrdersCustom> findOdersAndUser() throws Exception;
}

3. Mapper.xml




<mapper namespace="com.xxx.mapper.OrdersMapper">
	<select id="findOdersAndUser" resultType="com.xxx.pojo.OrdersCustom">
		<include refid="selectOrdersAndUser">include>
	select>
	
	
	<sql id="selectOrdersAndUser">
		select orders.*,
			   user.username,
			   user.sex,
			   user.address
		from 
			   orders, user
		where 
			   orders.user_id = user.id;
	sql>
mapper>	

4. 全局配置文件中加载映射文件

//others remain the same
<mappers>
	<mapper resource="com/xxx/mapper/UserMapper.xml"/>
mappers>

5. 测试

public class OrdersMappertest {
	private SqlSessionFactory sqlSessionFactory;
	@Before
	public void setUp() throws Exception {
		String resource = "SqlMapConfig.xml";
		InputStream inputStream = org.apache.ibatis.io.Resources
				.getResourceAsStream(resource);
		sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
	}

	@Test
	public void testfindOdersAndUser() throws Exception {
		//try-with-resources to auto close Sqlsession
		try(SqlSession sqlSession = sqlSessionFactory.openSession();){
			OrdersMapper mapper = sqlSession.getMapper(OrdersMapper.class);
			List<OrdersCustom> order_user = mapper.findOdersAndUser();
			System.out.println( order_user );
		}
	}
}

输出如下(截取部分):
在这里插入图片描述

2.3 使用resultMap进行输出映射

1. POJO 扩展类。将原来单独列出的属性替换成User属性,因为映射文件里需要用这个属性进行关联。

public class OrdersCustom extends Orders {

	private User user;
	
	public User getUser() {
		return user;
	}

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

	@Override
	public String toString() {
		return "OrdersCustom ["+ super.toString() + ",user=" + user + "]";
	}
}

2. 映射文件Mapper.xml

	<resultMap type="com.xxx.pojo.OrdersCustom" id="OrdersAndUserMap">
		<id column="id" property="id"/>
		<result column="user_id" property="userId"/>
		<result column="number" property="number"/>
		<result column="createtime" property="createtime"/>
		<result column="note" property="note"/>
		
		<association property="user" javaType="com.xxx.pojo.User">
			<id column="user_id" property="id"/>
			<result column="sex" property="sex"/>
			<result column="username" property="username"/>
			<result column="address" property="address"/>
		association>
	resultMap>
	
	<select id="findOdersAndUserByResultMap" resultMap="OrdersAndUserMap">
		<include refid="selectOrdersAndUser">include>
	select>

MyBatis进行关联,property属性指定POJO中定义的属性,注意,要与POJO中定义的属性对应起来;JavaType 指定这个属性的Java类型。

3. Mapper.java

public List<OrdersCustom> findOdersAndUserByResultMap() throws Exception;

4. 测试

@Test
	public void findOdersAndUserByResultMap() throws Exception {
		try(SqlSession sqlSession = sqlSessionFactory.openSession();){
			OrdersMapper mapper = sqlSession.getMapper(OrdersMapper.class);
			List<OrdersCustom> list = mapper.findOdersAndUserByResultMap();
			System.out.println( list );
		}
	}

输出(截取部分):
在这里插入图片描述
注意,由于toString的关系,输出时用户信息中会有字段显示为null,因为我们输出的是整个user但是没有查询里面的某些字段,无法映射上,所以显示为null

3. 一对多映射

3.1 需求

查询订单及该订单对应的明细,前面说过,一个订单可以包含多个明细,所以订单关联查询订单明细属于一对多的关系。

3.2 开发

1. POJO扩展类。在Orders的导出类中添加List属性(OrderDetail是订单明细表对应的POJO类,这里就直接略过了),最终MyBatis会将订单映射到Orders中,订单明细映射到这个List中。

public class OrdersCustom extends Orders {
	List<Orderdetail> orderdetails = new ArrayList<Orderdetail>();
	public List<Orderdetail> getOrderdetails() {
		return orderdetails;
	}
	public void setOrderdetails(List<Orderdetail> orderdetails){
		this.orderdetails = orderdetails;
	}
}

2. Mapper.java

public List<OrdersCustom> findOrderAndOrderDetails() throws Exception;

3. Mapper.xml

<resultMap type="com.xxx.pojo.OrdersCustom" id="ordersAndOrderdetailResulMap" >
		<id column="id" property="id"/>
		<result column="user_id" property="userId"/>
		<result column="number" property="number"/>
		<result column="createtime" property="createtime"/>
		<result column="note" property="note"/>
		
		<collection property="orderdetails" ofType="com.xxx.pojo.Orderdetail">
			<id column="orderdetail_id" property="id"/>
			<result column="items_id" property="itemsId" />
			<result column="items_num" property="itemsNum" />
			<result column="orders_id" property="ordersId" />
		collection>
	resultMap>
	
	<select id="findOrderAndOrderDetails" resultMap="ordersAndOrderdetailResulMap">
		select orders.*,
			   orderdetail.id orderdetail_id,
			   orderdetail.items_id,
			   orderdetail.items_num,
			   orderdetail.orders_id
		from 
			   orders, orderdetail
		where 
			   orderdetail.orders_id = orders.id;	  
	select>

需要说明一下,原来的教程里面,把用户表User也关联进去了,个人觉得,作为案例来说没有必要,因此把关联查询用户表的那一部分进行了删减。当把用户也关联进去的时候,因为前面2.3节已经定义了一个resultMap,所以可以用extends属性继承原有的resultMap(),这样就可以不用写订单和用户映射的部分了。

因为一个订单可能对应多个明细,所以用来进行映射。中的property属性指定POJO扩展类中定义的属性,最终多条记录会映射到这个属性上;ofType指定List的限定类型。本例中,在扩展类中定义了List orderdetails = new ArrayList();,所以property="orderdetails" ofType="com.shao.pojo.Orderdetail"

中的指定订单明细表的唯一表示(即主键),所以查询时订单明细的id不能忽略,但是因为订单表中有一个外键指向订单明细的id,订单明细中又有一个id,两个会重复,所以查询时给订单明细的id定义一个别名 orderdetail.id orderdetail_id,

4. 测试

@Test
	public void findOrderAndOrderDetails() throws Exception {
		try(SqlSession sqlSession = sqlSessionFactory.openSession( );){
			OrdersMapper mapper = sqlSession.getMapper(OrdersMapper.class);
			List<OrdersCustom> list = mapper.findOrderAndOrderDetails();
			System.out.println( list.get(0).getOrderdetails() );
		}
	}

输出如下(截取部分)
在这里插入图片描述

4. 多对多映射

4.1 需求

查询用户和用户购买的商品。用户和商品没有直接的联系,但是订单关联用户,订单明细关联订单和商品,可以通过中间的这些关系把用户和商品对上。

4.2 开发

1. POJO扩展类。因为几个表都要涉及到,所以几个POJO类都要进行扩展。一个用户对应多个订单,在UserCustom 中添加List,将订单和用户关联上;一个订单对应多条明细,在OrdersCustom 中添加List,将订单和订单明细关联上;一条明细对应一个商品,在OrderdetailCustom中添加Items,将订单明细和商品关联上。绕了一圈之后,用户和商品关联上了。

public class UserCustom extends User {	
	List<OrdersCustom> orders = new ArrayList<OrdersCustom>();
	public List<OrdersCustom> getOrders() {
		return orders;
	}
	public void setOrders(List<OrdersCustom> orders) {
		this.orders = orders;
	}
}
public class OrdersCustom extends Orders {
	List<OrderdetailCustom> orderdetailsList = new ArrayList<OrderdetailCustom>();
	public List<OrderdetailCustom> getOrderdetailsList() {
		return orderdetailsList;
	}
	public void setOrderdetailsList(List<OrderdetailCustom> orderdetailsList) {
		this.orderdetailsList = orderdetailsList;
	}
}
public class OrderdetailCustom extends Orderdetail {
	Items items = new Items();
	public Items getItems() {
		return items;
	}
	public void setItems(Items items) {
		this.items = items;
	}
}

2. Mapper.java

public List<UserCustom> findUserAndItem() throws Exception;

3. Mapper.xml

<resultMap type="com.xxx.pojo.UserCustom" id="UserAndOrdersResultMap">
		<id column="user_id" property="id"/>
		<result column="username" property="username"/>
		<result column="sex" property="sex"/>
		<result column="address" property="address"/>
		
		<collection property="orders" ofType="com.xxx.pojo.OrdersCustom">
			<id column="id" property="id"/>
			<result column="number" property="number" />
			<result column="createtime" property="createtime" />
			<result column="note" property="note" />
			
			<collection property="orderdetailsList" ofType="com.xxx.pojo.OrderdetailCustom">
				<id column="orderdetail_id" property="id"/>
				<result column="items_id" property="itemsId" />
				<result column="items_num" property="itemsNum" />
				<result column="orders_id" property="ordersId" />
			
				<association property="items" javaType="com.xxx.pojo.Items">
					<id  column="items_id" property="id"/>
					<result column="name" property="name" />
					<result column="price" property="price" />
					<result column="pic" property="pic" />
					<result column="createtime" property="createtime" />
					<result column="detail" property="detail" />
				association>
			collection>
		collection>
	resultMap>	
	
	<select id="findUserAndItem" resultMap="UserAndOrdersResultMap">
		select orders.*,
			   user.username,
			   user.sex,
			   user.address,
			   orderdetail.id orderdetail_id,
			   orderdetail.items_id,
			   orderdetail.items_num,
			   orderdetail.orders_id,
			   items.id items_id,
			   items.name,
			   items.price,
			   items.pic,
			   items.createtime,
			   items.detail
		from 
			   orders, user, orderdetail,items
		where 
			   orders.user_id = user.id
		and    
			   orderdetail.orders_id = orders.id
		and
			   orderdetail.items_id = items.id	  
	select>

可以看到,因为嵌套了几层查询,所以配置会相对复杂一点,但其实有了前面的经验,这个配置应该也不难理解。用户关联订单,所以在里面嵌套了一个指定类型为订单的;订单关联订单明细,所以在里又嵌套了一个指定类型为订单明细的;订单明细关联商品,所以用进行关联。

4. 测试

@Test
	public void testfindUserAndItem() throws Exception {
		try(SqlSession sqlSession = sqlSessionFactory.openSession( );){
			OrdersMapper mapper = sqlSession.getMapper(OrdersMapper.class);
			List<UserCustom> list = mapper.findUserAndItem();
			System.out.println( list.get(0).getOrders().get(0).getOrderdetailsList().get(0).getItems() );
		}
	}

输出(截取部分):
MyBatis(六) —— 高级映射_第2张图片

5. 总结

本文用实际案例讲解了MyBatis中高级映射的开发方法,包含一对一映射、一对多映射和多对多映射。在最后的多对多映射案例中,因为映射双方没有直接的联系,所以需要通过中间的表将联系建立起来。

需要注意几点:

  1. 在高级映射的开发中,因为原生POJO已经无法满足开发需求,所以只能扩展,但是,一定要导出一个子类并在子类上去做扩展,这样才符合开闭原则,对软件的健壮性和维护都是必要的;
  2. 后面的几个映射没有提到在全局配置文件中加载映射文件的事情,因为映射文件在第一次时已经加载了,后面的映射只是在映射文件中添加了内容,实质上还是同一个映射文件,所以就没有过多的提;
  3. 关于一多一、一对多和多对多的映射,需要结合实际情况去考虑到底属于哪一个映射。

6. 参考文献

【1】传智 SpringMVC+MyBatis由浅入深 教程
【2】MyBatis User Guide

你可能感兴趣的:(由浅及深)