Mybatis教程 | 第四篇:Mybatis的关联映射

前言

关联关系是面向对象分析、面向对象设计最终的思想,Mybatis完全可以理解这种关联关系,如果关系得当,Mybatis的关联映射将可以大大简化持久层数据的访问。关联关系大致可以分为以下情况:

1、一对一,比如一个人只能有一张身份证,而一张身份证只能属于一个人;

2、一对多,比如一个部门有多个员工,而一个员工只能属于一个部门;

3、多对多,比如在购物系统中,一个订单可以有多个商品,而一个商品也可以属于多个订单。

当然,还有更复杂的关系,同样在购物系统中,一个用户可以有多个订单,而一个订单只能属于一个用户,再加上商品的关系就是一对多夹杂多对多的关系,但是万变都不离其中。

下面就围绕这几种关系来看看Mybatis是怎么处理的。

一对一

1、搭建mybatis环境和日志环境(参考第一篇第二篇)并创建如下两张表tb_user和tb_card,插入两条数据:

2、创建相应的实体类对象(用user去关联card)、dao层接口和mapper文件

持久化对象如下:

public class Card {

	private Integer cardId;//主键id
	
	private String cardCode;//身份证号
	
	//省略相应的get/set和构造方法
}
public class User{
	
	private Integer userId;//主键userId
	
	private String userName;//姓名
	
	private String userGender;//性别
	
	private Integer userAge;//年龄
	
	private Card card;//人和身份证是一对一的关系
	
	//省略相应的set/get方法和构造方法
}

dao层接口:

public interface CardDao {

	Card getCardById(Integer cardId);
}
public interface UserDao {

	User getUserById(Integer userId);
}

mapper文件(记得在mybais的配置文件中扫描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.zepal.mybatis.dao.UserDao">
  
  <resultMap type="com.zepal.mybatis.domain.User" id="userMap">
  	<id column="user_id" property="userId"/>
  	<result column="user_name" property="userName"/>
  	<result column="user_gender" property="userGender"/>
  	<result column="user_age" property="userAge"/>
  	<!-- 一对一关联,select属性是通过namespace引入的card查询 -->
  	<association property="card" column="card_id"
	  	select="com.zepal.mybatis.dao.CardDao.getCardById" 
	  	javaType="com.zepal.mybatis.domain.Card"/>
  </resultMap>
  <select id="getUserById" parameterType="int" resultMap="userMap">
  	SELECT * FROM tb_user WHERE user_id = #{userId};
  </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.zepal.mybatis.dao.CardDao">

	<resultMap type="com.zepal.mybatis.domain.Card" id="cardMap">
		<id column="card_id" property="cardId"/>
		<result column="card_code" property="cardCode"/>
	</resultMap>
	<select id="getCardById" parameterType="int" resultMap="cardMap">
		SELECT * FROM tb_card WHERE card_id = #{cardId};
	</select>
</mapper>

说明:在查询user的时候,通过<association>标签引入了card的查询,其中select属性表示使用column属性的card_id值作为参数去执行CardMapper.xml中定义的getCardById,注意,它是通过CardMapper.xml中的命名空间namespqce引入的。

在<association>还有子标签,可以直接将需要关联对象的集合映射直接写在<association>里面,一般这种情况用在连接查询的时候,也就是sql中内连接、外连接之类的,注意上面称为关联查询,比如:

<association property="card" column="card_id"
	  	select="com.zepal.mybatis.dao.CardDao.getCardById" 
	  	javaType="com.zepal.mybatis.domain.Card">
	  	<id column="card_id" property="cardId"/>
	  	<result column="card_code" property="cardCode"/>	
	</association>

3、测试代码和结果

public class Test {

	public static void main(String[] args) {
		InputStream is = Test.class.getResourceAsStream("/mybatis/mybatis-config.xml");
		SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
		SqlSession sqlSession = sqlSessionFactory.openSession();
		Integer userId = 1;
		UserDao userDao = sqlSession.getMapper(UserDao.class);
		User user = userDao.getUserById(userId);
		System.out.println(user.toString());
		sqlSession.close();
	}
}
DEBUG [main] - ==>  Preparing: SELECT * FROM tb_user WHERE user_id = ?; 
DEBUG [main] - ==> Parameters: 1(Integer)
DEBUG [main] - <==      Total: 1
User [userId=1, userName=张三, userGender=男, userAge=18, card=Card [cardId=2, cardCode=12345619900801123x]]

一对多

1、按照上举例的部门的和员工的关系创建如下两张表tb_dept和tb_emp,并插入数据。Mybatis的配置环境继续延用。

2、创建相应的持久层对象、dao层接口和mapper文件

持久层对象

public class Emp {

	private Integer empId;
	
	private String empName;
	
	private Dept dept;//一个员工只能属于一个部门
	
	//省略get/set和构造方法
}
public class Dept {

	private Integer deptId;
	
	private String deptName;
	
	private List<Emp> emps;//一个部门有多个员工
	
	//省略get/set和构造方法
}

dao层接口(这里在EmpDao里面写了两个方法,很明显,如果我要通过dept去关联查询emp,应该调用的是getEmpByDeptId,我应该是通过dept_id去查询,这里注意一下

public interface DeptDao {

	Dept getDeptById(Integer deptId);
}
public interface EmpDao {

	Emp getEmpById(Integer empId);
	
	Emp getEmpByDeptId(Integer deptId);
}

mapper文件

<mapper namespace="com.zepal.mybatis.dao.DeptDao">

	<resultMap type="com.zepal.mybatis.domain.Dept" id="deptMap">
		<id column="dept_id" property="deptId"/>
		<result column="dept_name" property="deptName"/>
		<collection property="emps" column="dept_id" 
		javaType="java.util.ArrayList" ofType="com.zepal.mybatis.domain.Emp" 
		select="com.zepal.mybatis.dao.EmpDao.getEmpByDeptId"
		fetchType="lazy"/>
	</resultMap>
	
	<select id="getDeptById" parameterType="int" resultMap="deptMap">
		SELECT * FROM tb_dept WHERE dept_id = #{deptId};		
	</select>
</mapper>
<mapper namespace="com.zepal.mybatis.dao.EmpDao">

	<resultMap type="com.zepal.mybatis.domain.Emp" id="empMap">
		<id column="emp_id" property="empId"/>
		<result column="emp_name" property="empName"/>
		<!-- 注意这个地方 -->
		<association property="dept" column="dept_id"
		select="com.zepal.mybatis.dao.DeptDao.getDeptById" 
		javaType="com.zepal.mybatis.domain.Dept"/>
	</resultMap>
	
	<select id="getEmpById" parameterType="int" resultMap="empMap">
		SELECT * FROM tb_emp WHERE emp_id = #{empId};
	</select>
	
	<resultMap type="com.zepal.mybatis.domain.Emp" id="empResultMap">
		<id column="emp_id" property="empId"/>
		<result column="emp_name" property="empName"/>
		<!-- 注意这个地方 -->
		<!-- <association property=""></association> -->
	</resultMap>
	
	<select id="getEmpByDeptId" parameterType="int" resultMap="empResultMap">
		SELECT * FROM tb_emp WHERE dept_id = #{deptId};
	</select>
</mapper>

这里又有一个注意的地方,在通过dept去关联查询emp的同时,不能又反过来关联查询dept,这是一个无限递归逻辑,会造成内存溢出的(相当于A去找B,B又去找A,A再找B....),当然,也不能光写一个<association>标签,不加select属性,然后通过<result>去做集合映射,同样的道理,既然通过dept去查emp的同时,不能通过emp去查dept,那么emp里面就不包含dept,这个时候做集合映射是无意义的,结果同样是null,那么,只能单独通过emp去查询dept(这时候就是个一对一关系,同样也不能在emp查dept的同时用<collection>标签去关联查询emp,这里就不演示通过emp去查询dept了,一对一关系在上面已经演示过了)

这里同样是在<collection>利用select属性通过namesqpce去找到EmpMapper中相关的查询方法。这里的javaType是ArrayList,(List<Emp> emps,同样是全路径,虽然Mybatis为常用的java数据类型作了处理,能直接使用ArrayList,但我还是坚持全路径,不然真的不好排错,除了团队沟通很好、开发规范文档很完善),然后在通过ofType(字面意思就是javaType指向的对象的type,我是这么记的^_^)属性去设置list里面的泛型类。

这里多了一个fetchType属性,它有两种属性值lazy(懒加载)和eager(立即加载),这里mybatis为关联查询做的优化。比如说,当查询数据量特别大的时候,不仅查询费时,而且网络传输还费时。所以开启懒加载之后,当查询结果不需要用到关联对象时,mybatis并不会发送SQL语句去查询关联对象,只有当查询结果用到了关联查询的数据,它才会按需加载。

开启懒加载有两种方式,一种是全局开关,开启之后,所有的关联查询都会被懒加载,第二种就是上面的单独指定,第二种方式优先级是高于全局的,所以如果全局的和局部的冲突的话,会以局部为准。同时,开启懒加载需要mybatis设置中的aggressiveLazyLoading支持并将其设为false,在第二篇已经罗列过mybatis的所有设置。下面直接贴上示例

<settings>
		<setting name="lazyLoadingEnabled" value="true"/>
		<setting name="aggressiveLazyLoading" value="false"/>
		<setting name="logImpl" value="LOG4J"/>
	</settings>

3、测试代码和结果

开启懒加载不使用关联结果(mybatis将不会发送SQL语句去关联查询)

public class Test {

	public static void main(String[] args) {
		InputStream is = Test.class.getResourceAsStream("/mybatis/mybatis-config.xml");
		SqlSessionFactory sqlSessionFactory = new
		SqlSessionFactoryBuilder().build(is); 
		SqlSession sqlSession = sqlSessionFactory.openSession(); 
		Integer deptId = 1; 
		DeptDao deptDao = sqlSession.getMapper(DeptDao.class);
		Dept dept = deptDao.getDeptById(deptId); 
		//只打印deptName,不打印emp
		System.out.println(dept.getDeptName());
		sqlSession.close();
	}
}
DEBUG [main] - ==>  Preparing: SELECT * FROM tb_dept WHERE dept_id = ?; 
DEBUG [main] - ==> Parameters: 1(Integer)
DEBUG [main] - <==      Total: 1
研发中心

开启懒加载使用关联结果

public class Test {

	public static void main(String[] args) {
		InputStream is = Test.class.getResourceAsStream("/mybatis/mybatis-config.xml");
		SqlSessionFactory sqlSessionFactory = new
		SqlSessionFactoryBuilder().build(is); 
		SqlSession sqlSession = sqlSessionFactory.openSession(); 
		Integer deptId = 1; 
		DeptDao deptDao = sqlSession.getMapper(DeptDao.class);
		Dept dept = deptDao.getDeptById(deptId); 
		//只打印deptName,不打印emp
		//System.out.println(dept.getDeptName());
		//打印dept对象,使用emp对象
		System.out.println(dept.toString());
		sqlSession.close();
	}
}
DEBUG [main] - ==>  Preparing: SELECT * FROM tb_dept WHERE dept_id = ?; 
DEBUG [main] - ==> Parameters: 1(Integer)
DEBUG [main] - <==      Total: 1
DEBUG [main] - ==>  Preparing: SELECT * FROM tb_emp WHERE dept_id = ?; 
DEBUG [main] - ==> Parameters: 1(Integer)
DEBUG [main] - <==      Total: 2
Dept [deptId=1, deptName=研发中心, emps=[Emp [empId=1, empName=张三, dept=null], Emp [empId=2, empName=李四, dept=null]]]

很明显懒加载生效,且在查询结果中使用到关联对象的时候,发送了两条SQL。

多对多

1、按照上举例的订单的和商品的关系创建如下表tb_order、tb_product、tb_order_product_relation,并插入数据。Mybatis的配置环境继续延用。简单描述一下,订单1(张三的订单)中有红牛、冰红茶、雪碧三件商品,订单2(李四的订单)中有红牛和脉动两件商品。

2、创建相应的持久层对象、dao层接口和mapper文件

持久层对象

public class Order {

	private Integer orderId;
	
	private String orderDesc;
	
	private List<Product> products;//一个订单可以有多件商品

	//省略get/set和构造方法
}
public class Product {

	private Integer productId;
	
	private String productName;
	
	private List<Order> orders;//一件商品可以出现在多个商品中
}

dao层接口

public interface OrderDao {

	Order getOrderById(Integer orderId);
	
	//查询商品的时候可以关联查询该商品被哪些订单买了
	Order getOrderByProductId(Integer productId);
}
public interface ProductDao {

	Product getProductById(Integer productId);
	
	//查询订单的时候关联查询该订单有哪些商品
	Product getProductByOrderId(Integer orderId);
}

mapper文件

<mapper namespace="com.zepal.mybatis.dao.OrderDao">

	<resultMap type="com.zepal.mybatis.domain.Order" id="orderMap">
		<id column="order_id" property="orderId"/>
		<result column="order_desc" property="orderDesc"/>
		<collection property="products" column="order_id"
		javaType="java.util.ArrayList" 
		ofType="com.zepal.mybatis.domain.Product"
		select="com.zepal.mybatis.dao.ProductDao.getProductByOrderId"
		fetchType="lazy"/>
	</resultMap>

	<select id="getOrderById" parameterType="int" resultMap="orderMap">
		select * from tb_order where order_id = #{orderId};
	</select>
</mapper>
<mapper namespace="com.zepal.mybatis.dao.ProductDao">

	<resultMap type="com.zepal.mybatis.domain.Product" id="productMap">
		<id column="product_id" property="productId"/>
		<result column="product_name" property="productName"/>
	</resultMap>
	
	<select id="getProductByOrderId" parameterType="int" resultMap="productMap">
		SELECT * FROM tb_product WHERE product_id
			in
		(SELECT r.product_id FROM tb_order o, tb_order_product_relation r 
		WHERE o.order_id = r.order_id AND o.order_id = #{orderId});
	</select>
</mapper>

3、测试代码及运行结果

DEBUG [main] - ==>  Preparing: select * from tb_order where order_id = ?; 
DEBUG [main] - ==> Parameters: 1(Integer)
DEBUG [main] - <==      Total: 1
DEBUG [main] - ==>  Preparing: SELECT * FROM tb_product WHERE product_id in (SELECT r.product_id FROM tb_order o, tb_order_product_relation r WHERE o.order_id = r.order_id AND o.order_id = ?); 
DEBUG [main] - ==> Parameters: 1(Integer)
DEBUG [main] - <==      Total: 3
Order [orderId=1, orderDesc=张三的订单, products=[Product [productId=1, productName=红牛, orders=null], Product [productId=3, productName=冰红茶, orders=null], Product [productId=5, productName=雪碧, orders=null]]]

总结

在mybatis中,不管表与表之间的关系多复杂,mybatis都可以通过关联映射查询去获取结果。核心标签就是<collection>association。抓住以下几点:

1、一个对象关联的是单个对象就用<association>,一个对象关联的是多个对象就用<collection>。

2、完成关联映射的写法多种多样,唯一的核心就是抓住源头和目标,即通过谁去关联查询谁,就可以从中抽丝剥茧。唯一要注意的就是当通过A去关联映射B的同时,不能用再在B中去关联映射A,这是一个无限递归的逻辑,会报内存溢出异常的。

3、懒加载对Mybatis有一定优化作用。


此篇完结

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值