JPA/Hibernate:传递给持久化的分离实体

Pau*_*ald 211 java hibernate jpa

我有一个JPA持久化对象模型,它包含多对一关系:一个帐户有许多交易.交易有一个帐户.

这是代码的片段:

@Entity
public class Transaction {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @ManyToOne(cascade = {CascadeType.ALL},fetch= FetchType.EAGER)
    private Account fromAccount;
....

@Entity
public class Account {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    @OneToMany(cascade = {CascadeType.ALL},fetch= FetchType.EAGER, mappedBy = "fromAccount")
    private Set<Transaction> transactions;
Run Code Online (Sandbox Code Playgroud)

我能够创建一个Account对象,向其添加事务,并正确地持久保存Account对象.但是,当我创建一个事务,使用现有已经存在的帐户,并持久化事务时,我得到一个例外:

if (account.getId()!=null) {
    account = entityManager.merge(account);
}
Transaction transaction = new Transaction(account,"other stuff");
 // the below fails with a "detached entity" message. why?
entityManager.persist(transaction);
Run Code Online (Sandbox Code Playgroud)

因此,我能够持久保存包含交易的账户,但不能持有具有账户的交易.我认为这是因为帐户可能没有附加,但这段代码仍然给了我同样的例外:

@Entity
public class Transaction {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @ManyToOne(cascade = {CascadeType.ALL},fetch= FetchType.EAGER)
    private Account fromAccount;
....

@Entity
public class Account {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    @OneToMany(cascade = {CascadeType.ALL},fetch= FetchType.EAGER, mappedBy = "fromAccount")
    private Set<Transaction> transactions;
Run Code Online (Sandbox Code Playgroud)

如何正确保存与已持久的Account对象关联的事务?

och*_*lad 242

解决方案很简单,只需使用CascadeType.MERGE而不是CascadeType.PERSISTCascadeType.ALL.

我遇到了同样的问题并CascadeType.MERGE为我工作过.

我希望你排序.

  • @VadimKirilchuk这对我也很有用,而且它很有意义.由于Transaction是PERSISTED,它也会尝试PERSIST帐户,因为Account已经在db中,所以它不起作用.但是使用CascadeType.MERGE,帐户会自动合并. (19认同)
  • 令人惊讶的是,一个人也为我工作.由于CascadeType.ALL包含所有其他级联类型... WTF,因此没有任何意义.我有春季4.0.4,春季数据jpa 1.8.0和hibernate 4.X ..有没有人有任何想法为什么ALL不起作用,但MERGE呢? (5认同)
  • 如果您不使用事务,则会发生这种情况。 (4认同)
  • 谢谢你,兄弟.如果您对引用键的限制为NOT NULL,则无法避免插入持久对象.所以这是唯一的解决方案.再次感谢你. (3认同)

Sym*_*Sym 119

这是典型的双向一致性问题.在此链接以及此链接对此进行了详细讨论.

根据前两个链接中的文章,您需要在双向关系的两侧修复您的setter.One侧的示例设置器位于此链接中.

许多方面的示例设置器在此链接中.

在更正了setter之后,您希望将Entity访问类型声明为"Property".声明"属性"访问类型的最佳做法是将所有注释从成员属性移动到相应的getter.值得注意的是,不要在实体类中混合使用"Field"和"Property"访问类型,否则JSR-317规范不会定义行为.

  • 请不要发布仅通过链接回答的答案。 (8认同)
  • 根本看不到这个答案与问题有什么关系? (3认同)
  • ps:@Id注释是hibernate用来标识访问类型的注释. (2认同)
  • 唯一的例外是:“将分离的实体传递给持久对象”为什么提高一致性会使它起作用?好的,一致性已修复,但对象仍然分离。 (2认同)

Vla*_*cea 20

去除子关联级联

因此,您需要@CascadeType.ALL@ManyToOne关联中删除。子实体不应级联到父关联。只有父实体应该级联到子实体。

@ManyToOne(fetch= FetchType.LAZY)
Run Code Online (Sandbox Code Playgroud)

请注意,我将该fetch属性设置为FetchType.LAZY因为急切获取对性能非常不利。

设置关联双方

每当您有双向关联时,您都需要使用父实体中的addChildremoveChild方法同步双方:

public void addTransaction(Transaction transaction) {
    transcations.add(transaction);
    transaction.setAccount(this);
}

public void removeTransaction(Transaction transaction) {
    transcations.remove(transaction);
    transaction.setAccount(null);
}
Run Code Online (Sandbox Code Playgroud)


Zds*_*Zds 14

使用合并是有风险和棘手的,因此在您的情况下这是一个肮脏的解决方法.至少需要记住,当您传递实体对象进行合并时,它会停止附加到事务,而是返回一个新的,现在附加的实体.这意味着如果任何人仍然拥有旧的实体对象,则对它的更改会被默认忽略并在提交时丢弃.

您没有在此处显示完整代码,因此我无法仔细检查您的交易模式.达到这种情况的一种方法是,如果在执行合并并保持活动时没有活动的事务.在这种情况下,持久性提供程序应为您执行的每个JPA操作打开一个新事务,并在调用返回之前立即提交并关闭它.如果是这种情况,合并将在第一个事务中运行,然后在merge方法返回之后,事务完成并关闭,并且返回的实体现在已分离.然后,它下面的持久化将打开第二个事务,并尝试引用已分离的实体,并给出异常.除非您非常了解自己在做什么,否则请始终将代码包装在事务中.

使用容器管理的事务,它看起来像这样.请注意:这假设该方法位于会话bean中并通过本地或远程接口调用.

@TransactionAttribute(TransactionAttributeType.REQUIRED)
public void storeAccount(Account account) {
    ...

    if (account.getId()!=null) {
        account = entityManager.merge(account);
    }

    Transaction transaction = new Transaction(account,"other stuff");

    entityManager.persist(account);
}
Run Code Online (Sandbox Code Playgroud)


Eug*_*bun 11

从子实体中删除级联Transaction,它应该是:

@Entity class Transaction {
    @ManyToOne // no cascading here!
    private Account account;
}
Run Code Online (Sandbox Code Playgroud)

FetchType.EAGER可以删除,也可以将其删除@ManyToOne

就这样!

为什么?通过在子实体上说“级联所有”,Transaction您需要将每个数据库操作传播到父实体Account。如果您这样做persist(transaction)persist(account)也会被调用。

但是只有临时(新)实体可以传递给persistTransaction在这种情况下)。分离(或其他非暂态)状态可能不会(Account在这种情况下,因为它已经存在于DB中)。

因此,您将获得异常“已将分离的实体传递给持久对象”。该Account实体的意思!不是Transaction你打电话persist


您通常不希望从孩子传播到父母。不幸的是,书中有很多代码示例(即使是很好的示例)也可以通过网络找到,它们确实做到了。我不知道为什么?也许有时候只是一遍又一遍地复制而没有太多的思考...

猜猜如果您remove(transaction)在@ManyToOne中仍称“层叠所有”会发生什么?的account(顺便说一句,与所有其他交易!)将被从数据库以及删除。但这不是您的意图,对吗?


dan*_*dan 9

可能在这种情况下,您account使用合并逻辑获取了对象,并persist用于持久化新对象,如果层次结构具有已持久化的对象,它将会抱怨.你应该saveOrUpdate在这种情况下使用,而不是persist.

  • 它是JPA,所以我认为类似的方法是.merge(),但这给了我同样的例外.要清楚,Transaction是一个新对象,Account不是. (2认同)

jaf*_*mlp 9

一个老问题,但最近遇到了同样的问题。在这里分享我的经验。

实体

@Data
@Entity
@Table(name = "COURSE")
public class Course  {

    @Id
    @GeneratedValue
    private Long id;
}
Run Code Online (Sandbox Code Playgroud)

保存实体 (JUnit)

Course course = new Course(10L, "testcourse", "DummyCourse");
testEntityManager.persist(course);
Run Code Online (Sandbox Code Playgroud)

使固定

Course course = new Course(null, "testcourse", "DummyCourse");
testEntityManager.persist(course);
Run Code Online (Sandbox Code Playgroud)

结论:如果实体类具有主键(id)@GenerateValue,那么请确保您没有为主键(id)传递值


小智 8

不要将id(pk)传递给persist方法或尝试使用save()方法而不是persist().

  • 好建议!但仅当生成id时。如果已分配,则设置ID是正常的。 (2认同)

Nem*_*X00 5

在你的实体定义,你不指定@JoinColumnAccount加入到Transaction。你会想要这样的东西:

@Entity
public class Transaction {
    @ManyToOne(cascade = {CascadeType.ALL},fetch= FetchType.EAGER)
    @JoinColumn(name = "accountId", referencedColumnName = "id")
    private Account fromAccount;
}
Run Code Online (Sandbox Code Playgroud)

编辑:好吧,我想如果您@Table在课堂上使用注释会很有用。呵呵。:)

  • 是的,我不认为是这样,同样,我添加了 @JoinColumn(name = "fromAccount_id", referencedColumnName = "id") 但它没有用 :)。 (2认同)

dan*_*dan 5

即使您的注释已正确声明以正确管理一对多关系,您仍然可能会遇到这种精确的异常。将新的子对象添加Transaction到附加数据模型时,您需要管理主键值 -除非您不应该这样做。如果您在调用之前为按如下方式声明的子实体提供主键值persist(T),您将遇到此异常。

@Entity
public class Transaction {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
....
Run Code Online (Sandbox Code Playgroud)

在这种情况下,注释声明数据库将在插入时管理实体主键值的生成。自己提供一个(例如通过 Id 的 setter)会导致此异常。

或者,但实际上是相同的,此注释声明会导致相同的异常:

@Entity
public class Transaction {
    @Id
    @org.hibernate.annotations.GenericGenerator(name="system-uuid", strategy="uuid")
    @GeneratedValue(generator="system-uuid")
    private Long id;
....
Run Code Online (Sandbox Code Playgroud)

id因此,当应用程序代码已被管理时,请勿在应用程序代码中设置该值。


Faz*_*zoM 5

如果没有任何帮助并且您仍然收到此异常,请检查您的equals()方法 - 并且不要在其中包含子集合。特别是如果您具有嵌入式集合的深层结构(例如,A 包含 B,B 包含 C,等等)。

在以下示例中Account -> Transactions

  public class Account {

    private Long id;
    private String accountName;
    private Set<Transaction> transactions;

    @Override
    public boolean equals(Object obj) {
      if (this == obj)
        return true;
      if (obj == null)
        return false;
      if (!(obj instanceof Account))
        return false;
      Account other = (Account) obj;
      return Objects.equals(this.id, other.id)
          && Objects.equals(this.accountName, other.accountName)
          && Objects.equals(this.transactions, other.transactions); // <--- REMOVE THIS!
    }
  }
Run Code Online (Sandbox Code Playgroud)

在上面的示例中,从equals()支票中删除交易。这是因为休眠将暗示您不会尝试更新旧对象,而是在更改子集合上的元素时传递一个新对象以持久化。
当然,此解决方案并不适合所有应用程序,您应该仔细设计要包含在equalshashCode方法中的内容。


JJ *_*kar 5

我的基于 Spring Data JPA 的答案:我只是@Transactional在我的外部方法中添加了一个注释。

为什么有效

子实体立即分离,因为没有活动的 Hibernate Session 上下文。提供 Spring (Data JPA) 事务可确保存在 Hibernate Session。

参考:

https://vladmihalcea.com/a-beginners-guide-to-jpa-hibernate-entity-state-transitions/