如何级联持久化仅新实体

7
我遇到了一些困难,不知道如何正确地设置JPA持久性(使用EclipseLink和transaction-type=“RESOURCE_LOCAL”)以适配以下实体:
@Entity
public class User {
    // snip various members

    @ManyToMany
    private List<Company> companies;

    public void setCompanies(List<Company> companies) {
           this.companies = companies;
    }
}

@Entity
public class Company {
    // snip various members
}

我试图为公司列表设置级联,以便如果列表中有一个尚未先前持久化的新公司,则它将自动与用户一起被持久化。
User newUser = new User();

Company newCompany = new Company();
List<Company> companies = new ArrayList<Company>();
companies.add(newCompany);

newUser.setCompanies(companies);

entityManager.persist(newUser);

通过在@ManyToMany上设置cascadeType.PERSIST,这个问题就可以很好地解决。但是,如果公司列表中包含先前持久化的公司,我会收到一个MySQLIntegrityConstraintViolationException,因为它试图持久化(INSERT)具有相同主键的新公司:
User newUser = new User();

Company oldCompany = companyDAO.find(oldCompanyId);
List<Company> companies = new ArrayList<Company>();
companies.add(oldCompany);

newUser.setCompanies(companies);

entityManager.persist(newUser);

那么应该如何设置,以便新公司自动持久化,而现有公司仅添加到用户-公司映射中?

你尝试过CascadeType.MERGE吗? - RNJ
已经尝试过MERGE,但新的公司并没有自动持久化。 - Rolf
2个回答

10
在Hibernate中,最好理解级联的方式是,如果在父级上调用方法X,则它将在每个子级上调用方法X。 因此,是的,如果您在用户上调用persist,则它将在每个子级上调用persist,而不管它们是否已被持久化。

这种情况并不理想地处理级联。 级联持久化是针对所有子对象都随父对象一起创建(例如,如果用户拥有“技能”列表),更适用于一对多的情况。

我个人不会在这种情况下使用级联。 在不需要级联时过度使用级联可能会减慢应用程序速度。

如果您认为必须使用级联,则可以使用级联合并。 如果实体尚未持久化,则Merge将持久化实体。 但是,Merge具有某些非常奇怪的副作用,这可能是您没有注意到它正在工作的原因。 考虑以下示例:

x = new Foo();
y = new Foo();

em.persist(x);
Foo z = em.merge(y);

//x is associated with the persistence context
//y is NOT associated with the persistence context
//z is associated with the persistence context

谢谢您的见解。也许我会完全跳过级联操作。但是,我的问题实际上是关于CascadeType.MERGE,它在持久化新用户和新公司时并没有起作用。这导致了一个InvalidDataAccessApiUsageException异常,即“找到一个新对象,但该关系未标记为级联PERSIST”。 - Rolf
所以我最终放弃了级联,而是修改了负责保存用户实体的DAO,使其在持久化或合并用户之前,先迭代用户的公司,并首先持久化/合并这些公司。我最初没有意识到的是,更新用户和公司之间的关系不需要级联--由于关系只是用户的属性之一,因此在更新用户时自动处理该关系。 - Rolf

5
你的问题在于破坏了持久化上下文。托管对象只应引用其他托管对象,因此让你的新对象引用现有的分离对象是错误的。
你需要做的是进行find()操作以获得现有分离对象的托管版本,并使你的新对象引用它,然后调用persist()方法。
你也可以使用merge()方法而不是persist()方法,它应该解决您的对象引用问题。请注意,merge()方法不会使一个分离对象成为托管对象,它会返回一个管理的分离对象拷贝。

执行find()方法以获取现有分离对象的托管版本,并让您的新对象引用它,然后对其调用persist()方法。但这不就是我正在做的吗?companyDAO.find(oldCompanyId) 调用entityManager.find()并返回结果,该结果添加到用户的公司列表中,然后使用entityManager.persist(newUser)持久化用户。 - Rolf
1
@Rolf 在这种情况下的问题可能是您正在使用不同的实体管理器。 oldCompany 是通过 dao 的实体管理器获取的,然后另一个实体管理器用于持久化新实体。在持久化 em 的眼中,oldCompany 基本上是分离的。 - Ville Myrskyneva

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