概述

昨天排查问题的时候,发现使用hibernate关联对象时,订单实体类对象死活无法获取关联的订单明细列表

Order order = orderDao.findById(201L);
//明明数据库表里有关联的订单明细,但是此处的就是查询不到
List<OrderItem> orderImemList = order.getOrderItemList();

原因在于,OrderItem对象中关联了产品对象product,在它头上的注解是这样的:

    /**
* 产品
*/
@ManyToOne(optional = false, fetch = FetchType.EAGER)
@JoinColumn(name = "product_id", nullable = false)
private Product product;

fetch = FetchType.EAGER 且 nullable=false,执行 order.getOrderItemList()时,hibernate底层的查询语句是inner join产品表的,但是订单明细中恰好有1个产品id是空的,结果就是本来有3条订单明细,却只查出了2条

   select
orderiteml0_.order_id as order3_0_2_,
orderiteml0_.id as id1_2_,
orderiteml0_.id as id1_1_,
orderiteml0_.order_id as order3_1_1_,
orderiteml0_.product_id as product4_1_1_,
orderiteml0_.QUANTITY as QUANTITY1_1_,
product1_.id as id2_0_,
product1_.PRODUCT_NAME as PRODUCT2_2_0_
from
T_ORDER_ITEM orderiteml0_
inner join
T_PRODUCT product1_
on orderiteml0_.product_id=product1_.id
where
orderiteml0_.order_id=?
order by
orderiteml0_.id

fetchType的选择

JPA定义实体之间的关系有如下几种:

  • @OneToOne
  • @ManyToOne
  • @OneToMany
  • @ManyToMany

在定义它们的时候可以通过fetch属性指定加载方式,有两个值:

  • FetchType.LAZY:延迟加载,等到get的时候才会去查询
  • FetchType.EAGER:急加载,在查询主对象的时候就一起查询出来了

问题复现

数据库准备

-- region 创建表
-- 订单表
create table T_ORDER(
id INTEGER PRIMARY KEY
);
COMMENT ON TABLE T_ORDER is '订单表';
COMMENT ON COLUMN T_ORDER.id is '订单id';
-- 订单明细表
create table T_ORDER_ITEM(
id INTEGER PRIMARY KEY,
order_id INTEGER,
product_id INTEGER,
quantity INTEGER
);
COMMENT ON TABLE T_ORDER_ITEM is '订单明细表';
COMMENT ON COLUMN T_ORDER_ITEM.order_id is '订单id';
COMMENT ON COLUMN T_ORDER_ITEM.product_id is '产品id';
COMMENT ON COLUMN T_ORDER_ITEM.quantity is '数量';
-- 产品表
create table T_PRODUCT(
id INTEGER PRIMARY KEY,
product_name varchar2(50)
);
COMMENT ON TABLE T_PRODUCT is '产品表';
COMMENT ON COLUMN T_PRODUCT.product_name is '产品名称';
-- endregion -- region 插入测试数据
insert into T_PRODUCT(id, product_name) values (101, '产品A');
insert into T_PRODUCT(id, product_name) values (102, '产品B'); insert into T_ORDER (id) values (201); insert into T_ORDER_ITEM(id, order_id, product_id, quantity) values (301, 201, 101, 10);
insert into T_ORDER_ITEM(id, order_id, product_id, quantity) values (302, 201, 102, 2);
-- 注意:为了复现问题,一条记录的 productId 设置为空,一个设置为不存在的productId
insert into T_ORDER_ITEM(id, order_id, product_id, quantity) values (303, 201, '', 2);
insert into T_ORDER_ITEM(id, order_id, product_id, quantity) values (304, 201, 999, 12);
--endregion

数据表对应的实体类

订单类

@Entity
@DynamicInsert
@DynamicUpdate
@Table(name = "T_ORDER")
public class Order implements Serializable { @Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id; /**
* 订单明细
*/
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "order")
@OrderBy(value = "id")
private List<OrderItem> orderItemList = new ArrayList<>(); //...setter 和 getter
}

订单明细类

@Entity
@DynamicInsert
@DynamicUpdate
@Table(name = "T_ORDER_ITEM")
public class OrderItem implements Serializable { @Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id; /**
* 订单
*/
@ManyToOne(optional = false, fetch = FetchType.LAZY)
@JoinColumn(name = "order_id", nullable = false)
private Order order; /**
* 产品
*/
@ManyToOne(optional = false, fetch = FetchType.EAGER)
@JoinColumn(name = "product_id", nullable = false)
private Product product; /**
* 数量
*/
@Column(name = "QUANTITY")
private int quantity; //...setter和getter }

产品类

@Entity
@DynamicInsert
@DynamicUpdate
@Table(name = "T_PRODUCT")
public class Product implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id; /**
* 产品名称
*/
@Column(name = "PRODUCT_NAME")
private String productName; //...setter和getter
}

测试

public class App {
private static SessionFactory sessionFactory = HibernateUtil.getSessionFactory();
public static void main(String[] args) {
Long id = 201L;
Session session = sessionFactory.getCurrentSession();
session.beginTransaction();
Order order = (Order) session.get(Order.class, id);
//数据表里关联了3条订单明细,但查询结果总是只有2条
System.out.println(order.getOrderItemList().size());
session.getTransaction().commit();
}
}

HibernateUtil 工具类

public class HibernateUtil {
private static final SessionFactory sessionFactory = buildSessionFactory(); private static SessionFactory buildSessionFactory() {
try {
// Create the SessionFactory from hibernate.cfg.xml
return new Configuration().configure().buildSessionFactory();
} catch (Throwable ex) {
// Make sure you log the exception, as it might be swallowed
System.err.println("Initial SessionFactory creation failed." + ex);
throw new ExceptionInInitializerError(ex);
}
} public static SessionFactory getSessionFactory() {
return sessionFactory;
} public static void shutdown() {
// Close caches and connection pools
getSessionFactory().close();
} }

解决

问题点在于订单明细OrderItem属性product的加载方式

    /**
* 产品
*/
@ManyToOne(optional = false, fetch = FetchType.EAGER)
@JoinColumn(name = "product_id", nullable = false)
private Product product;

这导致 order.getOrderItemList() 时总是 inner join 产品表,查询SQL如下:

   select
orderiteml0_.order_id as order3_0_2_,
orderiteml0_.id as id1_2_,
orderiteml0_.id as id1_1_,
orderiteml0_.order_id as order3_1_1_,
orderiteml0_.product_id as product4_1_1_,
orderiteml0_.QUANTITY as QUANTITY1_1_,
product1_.id as id2_0_,
product1_.PRODUCT_NAME as PRODUCT2_2_0_
from
T_ORDER_ITEM orderiteml0_
inner join
T_PRODUCT product1_
on orderiteml0_.product_id=product1_.id
where
orderiteml0_.order_id=?
order by
orderiteml0_.id

**只需将 FetchType.EAGER 改为 FetchType.LAZY 即可,hibernate的底层查询SQL就会是:*

    select
orderiteml0_.order_id as order3_0_1_,
orderiteml0_.id as id1_1_,
orderiteml0_.id as id1_0_,
orderiteml0_.order_id as order3_1_0_,
orderiteml0_.product_id as product4_1_0_,
orderiteml0_.QUANTITY as QUANTITY1_0_
from
T_ORDER_ITEM orderiteml0_
where
orderiteml0_.order_id=?
order by
orderiteml0_.id

扩展

当急加载时,如果 @JoinColumn 的属性nullable 为true时表示可以为空,关联查询时 left outer join ;false则不能为空,关联查询时 inner join

    /**
* 产品
*/
@ManyToOne(optional = false, fetch = FetchType.EAGER)
@JoinColumn(name = "product_id", nullable = false)
private Product product;

总结

昨天下午5点多遇到这个问题,知道晚上快8点才找到原因。期间尝试了很多,最后将hibernate的底册执行的SQL打印出来,才发现了问题所在。

hibernate输出SQL的配置如下:

	<!-- 输出sql-->
<property name="show_sql">true</property>
<!-- 格式化sql -->
<property name="format_sql">true</property>

演示代码 https://gitee.com/anyway2025/issueDemo/tree/master/hibernate-demo-211202

hibernate查询不到关联对象列表-fetchType的选择的更多相关文章

  1. Hibernate查询,返回new对象(注意这个新定义的类要有构造函数),使用sql带条件分页查询并且把结果显示到一个对象的集里面的解决方案

     IIndexDao package com.ucap.netcheck.dao; import com.ucap.netcheck.combination.beans.IndexCombinat ...

  2. Hibernate查询对象的方法浅析

    Hibernate 查询对象是根据对象的id查询的,只要你有id (id唯一),则无论你是否其他字段与传过来的对象一致,都会查到该id在数据库对应的对象.若是在关联查询中,所关联表的id为空,即所查表 ...

  3. Hibernate查询方式(补)

    -----------------siwuxie095                             Hibernate 查询方式         1.对象导航查询     根据已经加载的对 ...

  4. 【SSH进阶之路】Hibernate映射——一对一双向关联映射(六)

    上篇博文[SSH进阶之路]Hibernate映射--一对一单向关联映射(五),我们介绍了一对一的单向关联映射,单向是指仅仅能从人(Person)这端载入身份证端(IdCard),可是反过来.不能从身份 ...

  5. 【Hibernate步步为营】--双向关联一对一映射具体解释(一)

    一对一的映射在对象模型中是常常见到的,为了将对象模型转换为关系模型就必须在映射文件里进行配置,上篇文章讨论了一对一映射的单向关联的情况,重点是<one-to-one>标签的使用,须要在映射 ...

  6. 【SSH进阶之路】Hibernate映射——一对一单向关联映射(五)

    [SSH进阶之路]Hibernate基本原理(一) ,小编介绍了Hibernate的基本原理以及它的核心,採用对象化的思维操作关系型数据库. [SSH进阶之路]Hibernate搭建开发环境+简单实例 ...

  7. 【Hibernate步步为营】--单向关联一对一映射

    上篇文章对多对一的关联映射做了具体的分析,它在实现上能够有两种方式,而且这两种方式实现也非常easy,关键是标签<many-to-one>的使用,它分别指明了多端和一端的映射关系.这样的映 ...

  8. hibernate关联对象的增删改查------查

    本篇博客是之前博客hibernate关联对象的增删改查------查 的后继,本篇代码的设定都在前文已经写好,因此读这篇之前,请先移步上一篇博客 //代码片5 SessionFactory sessi ...

  9. Django框架基础知识08-表关联对象及多表查询

    1.自定义主键字段的创建 AutoFiled(pirmary_key=True) # 一般不会自定义,int类型,自增长 一般不自定义主键. 2.order_by asc desc from djan ...

随机推荐

  1. 什么是 zuul路由网关?

    (1)Zuul 包含了对请求的路由和过滤两个最主要的功能:其中 责将外部请求转发到具体的微服务实例上,是实现外部访问统一入口的基础而过滤器功能则负 请求的处理过程进行干预,是实现请求校验.服务聚合等功 ...

  2. ActiveMQ数据接收类型问题

    一.问题描述 最近开发了一个工具,功能是监听ActiveMQ消息然后做相应的处理,本地自测没有问题,但是部署在现场出现如下报错: [WARN ] [2020-08-27 19:49:42] [org. ...

  3. Django用mongoengine操作mongodb

    最近在写Django项目,使用的是mongodb,虽然Django不支持NoSQL,但是mongoengine可以帮助我们像ORM一样在Django项目中去操作mongodb 推荐一个可视化工具  R ...

  4. Java 中,throw 和 throws 有什么区别?

    throw 用于抛出 java.lang.Throwable 类的一个实例化对象,意思是说你可以通 过关键字 throw 抛出一个 Error 或者 一个 Exception,如:throw new ...

  5. swagger的作用和配置使用

    纯API项目中 引入swagger可以生成可视化的API接口页面     引入包 nuget包: Swashbuckle.AspNetCore(最新稳定版) 配置 1.配置Startup类Config ...

  6. 聊一聊Java8 Optional,让你的代码更加优雅

    码农在囧途 随着时间的推移,曾经我们觉得重要的东西,可能在今天看来是如此的浅薄和无知,同理,今天我们放不下,想不开,觉得重要的东西,多年后我们可能也会觉得也就那样,所以,今天的的所有烦恼,忧愁,想不开 ...

  7. 寄存器、特殊功能寄存器和ram之间的区别联系

    存储器在CPU外,一般指硬盘,U盘等可以在切断电源后保存资料的设备,容量一般比较大,缺点是读写速度都很慢,普通的机械硬盘读写速度一般是50MB/S左右. 内存和寄存器就是为了解决存储器读写速度慢而产生 ...

  8. HTML入门学习笔记(二)

    第三章 文本 段落 p <p>毫不奇怪,p是最常用到的HTML元素之一</p> 作者联系信息 address address并不是用于标记邮政地址,而是定义与HTML页面或页面 ...

  9. pushbutton 移动端弹出列表选择框

    pushbutton 移动端弹出列表选择框 移动端从下往上推动画效果端弹出列表选择框,适应所有主流移动端机型,支持Node引入,require引入;如有用得不爽可以随时提意见,谢谢. demo地址: ...

  10. React 可视化开发工具 Shadow Widget 非正经入门(之六:markdown)

    本系列博文从 Shadow Widget 作者的视角,解释该框架的设计要点.本篇讲解 Markdown 在 Shadow Widget 中的应用. Markdown 在 Shadow Widget 中 ...