我有一个Order
类,其列表为OrderTransactions
,我将其映射为一对多的Hibernate映射,如下所示:
@OneToMany(targetEntity = OrderTransaction.class, cascade = CascadeType.ALL)
public List<OrderTransaction> getOrderTransactions() {
return orderTransactions;
}
这些Order
也有一个字段orderStatus
,用于使用以下条件进行过滤:
public List<Order> getOrderForProduct(OrderFilter orderFilter) {
Criteria criteria = getHibernateSession()
.createCriteria(Order.class)
.add(Restrictions.in("orderStatus", orderFilter.getStatusesToShow()));
return criteria.list();
}
这样可行,结果与预期一致。
现在这是我的问题:为什么,当我将fetch类型明确设置为EAGER
时,Order
在结果列表中多次出现?
@OneToMany(targetEntity = OrderTransaction.class, fetch = FetchType.EAGER, cascade = CascadeType.ALL)
public List<OrderTransaction> getOrderTransactions() {
return orderTransactions;
}
我如何更改条件代码以使用新设置达到相同的结果?
答案 0 :(得分:111)
如果我正确理解您的配置,这实际上是预期的行为。
您在任何结果中获得相同的Order
实例,但从现在开始与OrderTransaction
进行连接,它必须返回相同数量的结果,而常规sql join将返回
所以实际上它应该多次 apear。作者(Gavin King)本人here对此进行了很好的解释: 它既解释了原因,又解释了如何获得截然不同的结果
<小时/> 在Hibernate FAQ中也提到了:
对于为集合启用了外部联接提取的查询,Hibernate不会返回不同的结果(即使我使用了distinct 关键词)?首先,您需要了解SQL以及OUTER JOIN的工作方式 在SQL中。如果你没有完全理解和理解外部联接 SQL,不要继续阅读此FAQ项目,但请参阅SQL手册或 教程。否则您将无法理解以下说明 你会在Hibernate论坛上抱怨这种行为。
可能返回相同引用的重复引用的典型示例 订单对象:
List result = session.createCriteria(Order.class) .setFetchMode("lineItems", FetchMode.JOIN) .list();
<class name="Order"> ... <set name="lineItems" fetch="join">
List result = session.createCriteria(Order.class) .list(); List result = session.createQuery("select o from Order o left join fetch o.lineItems").list();
所有这些示例都生成相同的SQL语句:
SELECT o.*, l.* from ORDER o LEFT OUTER JOIN LINE_ITEMS l ON o.ID = l.ORDER_ID
想知道重复的原因在哪里?查看SQL结果集, Hibernate不会在外部左侧隐藏这些重复项 加入结果但返回驱动表的所有重复项。如果 您在数据库中有5个订单,每个订单有3个订单项, 结果集将是15行。这些查询的Java结果列表 将有15个元素,所有类型顺序。只有5个订单实例 由Hibernate创建,但SQL结果集的副本是 保留为对这5个实例的重复引用。如果你不 理解这最后一句话,你需要阅读Java和 Java堆上的实例与对它的引用之间的区别 这样的例子。
(为什么左外连接?如果你有一个没有线的额外订单 项目,结果集将是16行,其中NULL填充右侧 其中,订单项数据用于其他订单。你想要订单 即使他们没有订单项,对吧?如果没有,请使用内部联接 获取你的HQL)。
默认情况下,Hibernate不会过滤掉这些重复的引用。 有些人(不是你)真的想要这个。你怎么能过滤掉它们?
像这样:
Collection result = new LinkedHashSet( session.create*(...).list() );
答案 1 :(得分:90)
除了Eran所提到的,另一种获得所需行为的方法是设置结果转换器:
criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
答案 2 :(得分:41)
试
@Fetch (FetchMode.SELECT)
例如
@OneToMany(targetEntity = OrderTransaction.class, fetch = FetchType.EAGER, cascade = CascadeType.ALL)
@Fetch (FetchMode.SELECT)
public List<OrderTransaction> getOrderTransactions() {
return orderTransactions;
}
答案 3 :(得分:18)
不要使用List和ArrayList,而是使用Set和HashSet。
@OneToMany(targetEntity = OrderTransaction.class, cascade = CascadeType.ALL)
public Set<OrderTransaction> getOrderTransactions() {
return orderTransactions;
}
答案 4 :(得分:3)
使用Java 8和Streams我在实用程序方法中添加了这个返回语句:
return results.stream().distinct().collect(Collectors.toList());
Streams删除重复非常快。我在我的Entity类中使用注释,如下所示:
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
@JoinTable(name = "STUDENT_COURSES")
private List<Course> courses;
我认为在我的应用程序中使用会话方法,我需要来自数据库的数据。我完成的Closse会议。 Ofcourse设置我的Entity类使用leasy fetch类型。我去重构。
答案 5 :(得分:1)
我有同样的问题来获取2个相关的集合:用户有2个角色(Set)和2个饭菜(List)和饭菜是重复的。
@Table(name = "users")
public class User extends AbstractNamedEntity {
@CollectionTable(name = "user_roles", joinColumns = @JoinColumn(name = "user_id"))
@Column(name = "role")
@ElementCollection(fetch = FetchType.EAGER)
@BatchSize(size = 200)
private Set<Role> roles;
@OneToMany(fetch = FetchType.LAZY, mappedBy = "user")
@OrderBy("dateTime DESC")
protected List<Meal> meals;
...
}
DISTINCT没有帮助(DATA-JPA查询):
@EntityGraph(attributePaths={"meals", "roles"})
@QueryHints({@QueryHint(name= org.hibernate.jpa.QueryHints.HINT_PASS_DISTINCT_THROUGH, value = "false")}) // remove unnecessary distinct from select
@Query("SELECT DISTINCT u FROM User u WHERE u.id=?1")
User getWithMeals(int id);
最后,我找到了2个解决方案:
最终解决方案:
@EntityGraph(attributePaths = {"meals"}, type = EntityGraph.EntityGraphType.LOAD)
@Query("SELECT u FROM User u WHERE u.id=?1")
User getWithMeals(int id);
更新:除非org.springframework.data.jpa.repository.EntityGraph.EntityGraphType#FETCH
实体图的属性节点指定的属性被视为FetchType.EAGER,未指定的属性被视为FetchType.LAZY
此类角色也被提取。
答案 6 :(得分:1)
而不是使用像这样的黑客:
Set
而非List
criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
这不会修改您的sql查询,我们可以使用(引用JPA规范)
q.select(emp).distinct(true);
确实会修改生成的sql查询,因此其中包含DISTINCT
。
答案 7 :(得分:0)
应用外部联接并带来重复的结果听起来并不好。剩下的唯一解决方案是使用流过滤结果。感谢java8提供了更简单的过滤方法。
return results.stream().distinct().collect(Collectors.toList());