开发中有很多** 多表查询**需求,这种情况如何让进行处理?
MyBatis
思想是:数据库不可能永远是你所想或所需的那个样子。
我们希望每个数据库都具备良好的第三范式或BCNF
范式,可惜它们并不都是那样。
如果能有一种数据库映射模式,完美适配所有的应用程序查询需求,那就太好了,而ResultMap
就是MyBatis
就是完美答案。
官方例子:如何映射下面这个语句?
<select id="selectBlogDetails" resultMap="detailedBlogResultMap">
select
B.id as blog_id,
B.title as blog_title,
B.author_id as blog_author_id,
A.id as author_id,
A.username as author_username,
A.password as author_password,
A.email as author_email,
A.bio as author_bio,
A.favourite_section as author_favourite_section,
P.id as post_id,
P.blog_id as post_blog_id,
P.author_id as post_author_id,
P.created_on as post_created_on,
P.section as post_section,
P.subject as post_subject,
P.draft as draft,
P.body as post_body,
C.id as comment_id,
C.post_id as comment_post_id,
C.name as comment_name,
C.comment as comment_text,
T.id as tag_id,
T.name as tag_name
from Blog B
left outer join Author A on B.author_id = A.id
left outer join Post P on B.id = P.blog_id
left outer join Comment C on P.id = C.post_id
left outer join Post_Tag PT on PT.post_id = P.id
left outer join Tag T on PT.tag_id = T.id
where B.id = #{id}
select>
你可能想把它映射到一个智能的对象模型,这个对象表示了一篇博客,它由某位作者所写,有很多的博文,每篇博文有零或多条的评论和标签。 我们先来看看下面这个完整的例子,它是一个非常复杂的结果映射(假设作者,博客,博文,评论和标签都是类型别名)。
虽然它看起来令人望而生畏,但其实非常简单。
<resultMap id="detailedBlogResultMap" type="Blog">
<constructor>
<idArg column="blog_id" javaType="int"/>
constructor>
<result property="title" column="blog_title"/>
<association property="author" javaType="Author">
<id property="id" column="author_id"/>
<result property="username" column="author_username"/>
<result property="password" column="author_password"/>
<result property="email" column="author_email"/>
<result property="bio" column="author_bio"/>
<result property="favouriteSection" column="author_favourite_section"/>
association>
<collection property="posts" ofType="Post">
<id property="id" column="post_id"/>
<result property="subject" column="post_subject"/>
<association property="author" javaType="Author"/>
<collection property="comments" ofType="Comment">
<id property="id" column="comment_id"/>
collection>
<collection property="tags" ofType="Tag" >
<id property="id" column="tag_id"/>
collection>
collection>
resultMap>
现在可能看不懂,接下来要学习将多表查询结果使用
ResultMap
标签映射到实体类对象上!
学习目标:
ResultMap
完成多表结果映射多表关系回顾:(双向查看)
实体类设计:对一关系下,类中只要包含单个对方对象类型属性即可!
例如:
public class Customer {
private Integer customerId;
private String customerName;
}
public class Order {
private Integer orderId;
private String orderName;
private Customer customer;// 体现的是对一的关系
}
实体类设计:
对多关系下,类中只要包含对方类型集合属性即可!
public class Customer {
private Integer customerId;
private String customerName;
private List<Order> orderList;// 体现的是对多的关系
}
public class Order {
private Integer orderId;
private String orderName;
private Customer customer;// 体现的是对一的关系
}
//查询客户和客户对应的订单集合 不要管!
多表结果实体类设计小技巧:
只有真实发生多表查询时,才需要设计和修改实体类,否则不提前设计和修改实体类!
无论多少张表联查,实体类设计都是两两考虑!
在查询映射的时候,只需要关注本次查询相关的属性!例如:查询订单和对应的客户,就不要关注客户中的订单集合!
数据库:
CREATE TABLE `t_customer` (`customer_id` INT NOT NULL AUTO_INCREMENT, `customer_name` CHAR(100), PRIMARY KEY (`customer_id`) );
CREATE TABLE `t_order` ( `order_id` INT NOT NULL AUTO_INCREMENT, `order_name` CHAR(100), `customer_id` INT, PRIMARY KEY (`order_id`) );
INSERT INTO `t_customer` (`customer_name`) VALUES ('c01');
INSERT INTO `t_order` (`order_name`, `customer_id`) VALUES ('o1', '1');
INSERT INTO `t_order` (`order_name`, `customer_id`) VALUES ('o2', '1');
INSERT INTO `t_order` (`order_name`, `customer_id`) VALUES ('o3', '1');
实际开发时,一般在开发过程中,不给数据库表设置外键约束。
原因是避免调试不方便。
一般是功能开发完成,再加外键约束检查是否有bug。
实体类设计:
稍后会进行订单关联客户查询,也会进行客户关联订单查询,所以在这先练习设计
@Data
public class Customer {
private Integer customerId;
private String customerName;
private List<Order> orderList;// 体现的是对多的关系
}
@Data
public class Order {
private Integer orderId;
private String orderName;
private Customer customer;// 体现的是对一的关系
}
相关依赖
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<version>1.18.28version>
dependency>
OrderMapper
接口public interface OrderMapper {
Order selectOrderWithCustomer(Integer orderId);
}
OrderMapper.xml
配置文件
<resultMap id="selectOrderWithCustomerResultMap" type="order">
<id column="order_id" property="orderId"/>
<result column="order_name" property="orderName"/>
<association property="customer" javaType="customer">
<id column="customer_id" property="customerId"/>
<result column="customer_name" property="customerName"/>
association>
resultMap>
<select id="selectOrderWithCustomer" resultMap="selectOrderWithCustomerResultMap">
SELECT order_id,order_name,c.customer_id,customer_name
FROM t_order o
LEFT JOIN t_customer c
ON o.customer_id=c.customer_id
WHERE o.order_id=#{orderId}
select>
对应关系可以参考下图:
Mybatis
全局注册mybatis-config.xml
文件
DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
settings>
<typeAliases>
<package name="com.atguigu.entity"/>
typeAliases>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis-example"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
dataSource>
environment>
environments>
<mappers>
<mapper resource="mappers/OrderMapper.xml"/>
mappers>
configuration>
junit
测试程序public class MyBatisTest {
private SqlSession session;
// junit会在每一个@Test方法前执行@BeforeEach方法
@BeforeEach
public void init() throws IOException {
session = new SqlSessionFactoryBuilder()
.build(
Resources.getResourceAsStream("mybatis-config.xml"))
.openSession();
}
@Test
public void testRelationshipToOne() {
OrderMapper orderMapper = session.getMapper(OrderMapper.class);
// 查询Order对象,检查是否同时查询了关联的Customer对象
Order order = orderMapper.selectOrderWithCustomer(2);
System.out.println(order);
}
// junit会在每一个@Test方法后执行@@AfterEach方法
@AfterEach
public void clear() {
session.commit();
session.close();
}
}
association
和javaType
CustomerMapper
接口public interface CustomerMapper {
Customer selectCustomerWithOrderList(Integer customerId);
}
CustomerMapper.xml
文件
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.atguigu.mapper.CustomerMapper">
<resultMap id="selectCustomerWithOrderListResultMap" type="customer">
<id column="customer_id" property="customerId"/>
<result column="customer_name" property="customerName"/>
<collection property="orderList" ofType="order">
<id column="order_id" property="orderId"/>
<result column="order_name" property="orderName"/>
collection>
resultMap>
<select id="selectCustomerWithOrderList" resultMap="selectCustomerWithOrderListResultMap">
SELECT c.customer_id,c.customer_name,o.order_id,o.order_name
FROM t_customer c
LEFT JOIN t_order o
ON c.customer_id=o.customer_id
WHERE c.customer_id=#{customerId}
select>
mapper>
对应关系可以参考下图:
Mybatis
全局注册mybatis-config.xml
文件
DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
settings>
<typeAliases>
<package name="com.atguigu.entity"/>
typeAliases>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis-example"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
dataSource>
environment>
environments>
<mappers>
<mapper resource="mappers/OrderMapper.xml"/>
<mapper resource="mappers/CustomerMapper.xml"/>
mappers>
configuration>
junit
测试程序@Test
public void testRelationshipToMulti() {
CustomerMapper customerMapper = session.getMapper(CustomerMapper.class);
// 查询Customer对象同时将关联的Order集合查询出来
Customer customer = customerMapper.selectCustomerWithOrderList(1);
System.out.println("customer.getCustomerId() = " + customer.getCustomerId());
System.out.println("customer.getCustomerName() = " + customer.getCustomerName());
List<Order> orderList = customer.getOrderList();
for (Order order : orderList) {
System.out.println("order = " + order);
}
}
setting属性 | 属性含义 | 可选值 | 默认值 |
---|---|---|---|
autoMappingBehavior | 指定 MyBatis 应如何自动映射列到字段或属性。 NONE 表示关闭自动映射;PARTIAL 只会自动映射没有定义嵌套结果映射的字段。 FULL 会自动映射任何复杂的结果集(无论是否嵌套)。 |
NONE , PARTIAL , FULL |
PARTIAL |
我们可以将autoMappingBehavior
设置为full
,进行多表resultMap
映射的时候,可以省略符合列和属性命名映射规则(列名=属性名,或者开启驼峰映射也可以自定映射)的result
标签!
修改
mybati-sconfig.xml
:
<setting name="autoMappingBehavior" value="FULL"/>
修改
CustomerMapper.xml
<resultMap id="customerMap" type="customer">
<id property="customerId" column="customer_id" />
<collection property="orderList" ofType="order" >
<id property="orderId" column="order_id" />
collection>
resultMap>
<select id="selectCustomerWithOrderList" resultMap="customerMap">
SELECT c.customer_id,c.customer_name,o.order_id,o.order_name
FROM t_customer c
LEFT JOIN t_order o
ON c.customer_id=o.customer_id
WHERE c.customer_id=#{customerId}
select>
测试:
@Test
public void testRelationshipToMulti() {
CustomerMapper customerMapper = session.getMapper(CustomerMapper.class);
// 查询Customer对象同时将关联的Order集合查询出来
Customer customer = customerMapper.selectCustomerWithOrderList(1);
System.out.println("customer.getCustomerId() = " + customer.getCustomerId());
System.out.println("customer.getCustomerName() = " + customer.getCustomerName());
List<Order> orderList = customer.getOrderList();
for (Order order : orderList) {
System.out.println("order = " + order);
}
}
关联关系 | 配置项关键词 | 所在配置文件和具体位置 |
---|---|---|
对一 | association 标签、javaType 属性、property 属性 |
Mapper 配置文件中的resultMap 标签内 |
对多 | collection 标签、ofType 属性、property 属性 |
Mapper 配置文件中的resultMap 标签内 |