JPA - EntityManager 的 find 方法返回了重复数据。

3

我有一个包含两个子实体列表的实体。在调用EntityManager.find()时,它会基于两个列表的乘积返回重复项。

我使用Spring,Jackson和JPA与Hibernate和SQL Server。

当使用具有第一种子实体N个元素和第二种子实体M个元素的父级进行测试时,它总是返回这两个实体的N*M个元素。

例如下面有3任务5评论,而JPA返回15个元素。(任务列表的5个副本和评论列表的3个副本)

来自控制器的输出为:

Comments 15
Tasks 15

以下是其余的代码。

controller.java

@RequestMapping(method = RequestMethod.GET)
public String listAll(Model model) {

    Goal goal = new Goal();

    goal = service.getGoalById(25);

    System.out.println("Comments " + goal.getComments().size());
    System.out.println("Tasks " + goal.getTasks().size());

    return "home";
}

service.java

@Transactional
public Goal getGoalById(int goalId) {
    Goal goal = em.find(Goal.class, goalId);

    return goal;
}

goal.java

@Entity
@Table(name = "goal")
public class Goal {

@Id
@GeneratedValue
private int id;

@JsonManagedReference
@OneToMany(mappedBy = "tasksGoal", cascade = CascadeType.PERSIST, fetch = FetchType.EAGER)
private List<Task> projectTasks = new ArrayList<Task>();

@JsonManagedReference
@OneToMany(mappedBy = "commentsGoal", cascade = CascadeType.PERSIST, fetch = FetchType.EAGER)
private List<Comment> goalComments = new ArrayList<Comment>();

...

}

task.java

@Entity
@Table(name="projectTask")
public class Task {

@Id
@GeneratedValue
private int id;

@JsonBackReference
@ManyToOne (fetch = FetchType.LAZY)
@JoinColumn(name = "task_goal_id")
private Goal tasksGoal;

...

}

comment.java

@Entity
@Table (name="goalComment")
public class Comment {

@Id
@GeneratedValue
private int id;

@JsonBackReference
@ManyToOne (fetch = FetchType.LAZY)
@JoinColumn(name = "goal_id")
private Goal commentsGoal;

...

}

persistence.xml

<persistence xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.0"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
<persistence-unit name="persistenceUnit"
    transaction-type="RESOURCE_LOCAL">
    <provider>org.hibernate.ejb.HibernatePersistence</provider>
    <properties>
        <property name="hibernate.dialect" value="org.hibernate.dialect.SQLServer2008Dialect" />
        <property name="hibernate.hbm2ddl.auto" value="update" />
        <property name="hibernate.ejb.naming_strategy" value="org.hibernate.cfg.ImprovedNamingStrategy" />
        <property name="hibernate.connection.charSet" value="UTF-8" />

        <!-- Hibernate prints SQL -->
        <property name="hibernate.show_sql" value="true" />

    </properties>
</persistence-unit>


1
标记为重复。可能的快速修复措施。要么从关联中移除Eager fetch,要么将集合从List更改为Set。 - Alan Hay
非常感谢您的快速修复,我使用了Set。但是我仍然认为这个问题不是重复的。我没有使用HQL,但你会期望JPA在使用find方法时能够处理或者通知您这些错误。我完全理解为什么会发生这种情况,但是我并不认为很容易找到答案。总体而言,非常感谢您! :) - Veljko Rankovic
如果JPA提供程序出现错误,请在其上报一个bug(如果还没有)。它不应该返回重复项。可能是因为内部发出了一些不适合该情况的SQL语句(您可以检查一下)。 - Neil Stockton
2个回答

4
这是因为您使用了 fetch = FetchType.EAGER。Hibernate 试图在一次查询中获取所有内容,即对于与“主”对象链接的每个元素条目使用单个查询。Hibernate 创建 JOIN,尝试以一次查询方式获取所有集合。
如果您将 @Fetch(FetchMode.SELECT)注释添加到集合中,则可以成功解决此问题,但会增加 N+1 个查询成本。
如果您确实需要 FetchType.EAGER 并且不想用 Set 替换集合,则可以为集合使用 @Fetch(FetchMode.SELECT)注释: @JsonManagedReference @OneToMany(mappedBy = "tasksGoal", cascade = CascadeType.PERSIST, fetch = FetchType.EAGER) @Fetch(FetchMode.SELECT) private List projectTasks = new ArrayList();
@JsonManagedReference
@OneToMany(mappedBy = "commentsGoal", cascade = CascadeType.PERSIST, fetch = FetchType.EAGER)
@Fetch (FetchMode.SELECT)
private List<Comment> goalComments = new ArrayList<Comment>();

0
@Fetch (FetchMode.SELECT) 

可以帮助你。这是默认的获取策略。它启用了所有相关集合的延迟加载。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接