代码之家  ›  专栏  ›  技术社区  ›  Er KK Chopra

已分离实体通过多个单列[重复]传递到持久化

  •  0
  • Er KK Chopra  · 技术社区  · 6 年前

    Account 有很多 Transactions A. Transaction 账户 .

    下面是一段代码:

    @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;
    

    我能够创建一个 对象,向其添加事务,并持久化 账户 对象正确。但是,当我创建交易时, ,我得到一个例外:

    原因:org.hibernate。PersistentObjectException:传递给persist的分离实体:com.paulsanwald.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);
    

    如何正确保存 对象

    0 回复  |  直到 5 年前
        1
  •  364
  •   Damiano    7 年前

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

    级联类型.MERGE 为我工作。

        2
  •  150
  •   Kalle Richter    7 年前

    这是一个典型的双向一致性问题。本文对此进行了详细讨论 this link 以及 this link.

    根据前2个链接中的文章,您需要修复双向关系两侧的setter。一侧的示例setter位于 this link.

    this link.

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

        3
  •  55
  •   Eugen Labun rbrayb    6 年前

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

    @Entity class Transaction {
        @ManyToOne // no cascading here!
        private Account account;
    }
    

    ( FetchType.EAGER @ManyToOne )

    交易 Account persist(transaction) , persist(account) 也将被调用。

    persist ( 交易 账户 在这种情况下,因为它已经在DB中)。

    因此你得到了例外 “已分离实体已传递给persist” 实体是指!而不是 交易 坚持 在…上


    你通常不想在孩子和父母之间传播。不幸的是,书中(即使是好的书)和网络中都有许多代码示例,它们正是这样做的。我不知道,为什么……也许有时候只是简单地一遍又一遍地抄袭,没有多少思考。。。

    remove(transaction) account (顺便说一句,所有其他交易!)也将从数据库中删除。但那不是你的意图,是吗?

        4
  •  32
  •   Angad Bansode    7 年前

        5
  •  28
  •   Vlad Mihalcea    4 年前

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

    @ManyToOne(fetch= FetchType.LAZY)
    

    fetch FetchType.LAZY

    设置协会双方

    只要有双向关联,就需要使用 addChild removeChild 父实体中的方法:

    public void addTransaction(Transaction transaction) {
        transcations.add(transaction);
        transaction.setAccount(this);
    }
    
    public void removeTransaction(Transaction transaction) {
        transcations.remove(transaction);
        transaction.setAccount(null);
    }
    
        6
  •  16
  •   Zds    6 年前

    被附加到事务,而是返回一个新的、现在已附加的实体。这意味着,如果任何人仍然拥有旧的实体对象,对其所做的更改将被悄悄忽略,并在提交时丢弃。

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

    @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);
    }
    
        7
  •  14
  •   dan    12 年前

    可能在这种情况下,您获得了 account 使用合并逻辑的对象,以及 persist 用于持久化新对象,如果层次结构中有一个已持久化的对象,它将发出抱怨。你应该使用 saveOrUpdate 坚持 .

        8
  •  8
  •   jfk    4 年前

    实体

    @Data
    @Entity
    @Table(name = "COURSE")
    public class Course  {
    
        @Id
        @GeneratedValue
        private Long id;
    }
    

    保存实体(JUnit)

    Course course = new Course(10L, "testcourse", "DummyCourse");
    testEntityManager.persist(course);
    

    修理

    Course course = new Course(null, "testcourse", "DummyCourse");
    testEntityManager.persist(course);
    

    结论:

        9
  •  7
  •   JJ Zabkar    5 年前

    我基于JPA的Spring数据回答:我只是添加了一个 @Transactional

    工作原理

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

    参考:

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

        10
  •  6
  •   FazoM    8 年前

    equals()

    例如 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!
        }
      }
    

    检查。这是因为hibernate意味着您不想更新旧对象,但只要您更改子集合上的元素,您就会传递一个新对象来持久化。
    equals hashCode

        11
  •  5
  •   NemesisX00    12 年前

    在实体定义中,您没有指定 @JoinColumn 对于 Account 加入到 Transaction

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

    编辑:嗯,我想如果你使用 @Table 类上的注释。呵呵.)

        12
  •  4
  •   dan    8 年前

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

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

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

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

    id 当应用程序代码已经被管理时,它的价值。

        13
  •  1
  •   Yaocl    10 年前

    可能是OpenJPA的bug,回滚时重置@Version字段,但pcVersionInit保持为true。我有一个声明@Version字段的实体。我可以通过重置pcVersionInit字段来解决这个问题。但这不是一个好主意。我认为当有级联持久实体时,它不起作用。

        private static Field PC_VERSION_INIT = null;
        static {
            try {
                PC_VERSION_INIT = AbstractEntity.class.getDeclaredField("pcVersionInit");
                PC_VERSION_INIT.setAccessible(true);
            } catch (NoSuchFieldException | SecurityException e) {
            }
        }
    
        public T call(final EntityManager em) {
                    if (PC_VERSION_INIT != null && isDetached(entity)) {
                        try {
                            PC_VERSION_INIT.set(entity, false);
                        } catch (IllegalArgumentException | IllegalAccessException e) {
                        }
                    }
                    em.persist(entity);
                    return entity;
                }
    
                /**
                 * @param entity
                 * @param detached
                 * @return
                 */
                private boolean isDetached(final Object entity) {
                    if (entity instanceof PersistenceCapable) {
                        PersistenceCapable pc = (PersistenceCapable) entity;
                        if (pc.pcIsDetached() == Boolean.TRUE) {
                            return true;
                        }
                    }
                    return false;
                }
    
        14
  •  1
  •   stakahop    7 年前

    foreach(Account account : accounts){
        account.setTransaction(transactionObj);
    }
    

    // list of existing accounts
    List<Account> accounts = new ArrayList<>(transactionObj.getAccounts());
    
    foreach(Account account : accounts){
        account.setId(null);
    }
    
    transactionObj.setAccounts(accounts);
    
    // just persist transactionObj using EntityManager merge() method.
    
        15
  •  1
  •   James Siva    6 年前
    cascadeType.MERGE,fetch= FetchType.LAZY
    
        16
  •  1
  •   SateeshKasaboina    5 年前

    @OneToMany(mappedBy=“xxxx”,cascade={CascadeType.MERGE,cascadeTipe.PERSIST,CascadeType.REMOVE})为我工作。

        17
  •  1
  •   Shahid Hussain Abbasi    5 年前

    这发生在我身上,因为我没有设置Id(Id不是自动生成的)。并试图用关系“manytone”来拯救

        18
  •  1
  •   John Doe    3 年前

    下面是我的实体。标记id是用@GeneratedValue(策略=GenerationType.AUTO)注释的,这意味着id将由Hibernate生成。 创建实体对象时不要设置它 . 请注意,如果实体id字段没有用@GeneratedValue标记,那么不手动为id分配一个值也是一种犯罪,这将受到警告 IdentifierGenerationException:在调用save()之前,必须手动分配此类的ID

    @Entity
    @Data
    @NamedQuery(name = "SimpleObject.findAll", query="Select s FROM SimpleObject s")
    public class SimpleObject {
    
        @Id
        @GeneratedValue(strategy = GenerationType.AUTO)
        private Long id;
    
        @Column
        private String key;
    
        @Column
        private String value;
    
    }
    

    public class SimpleObjectMain {
    
        public static void main(String[] args) {
    
            System.out.println("Hello Hello From SimpleObjectMain");
    
            SimpleObject simpleObject = new SimpleObject();
            simpleObject.setId(420L); // Not right, when id is a generated value then no need to set this.
            simpleObject.setKey("Friend");
            simpleObject.setValue("Bani");
    
            EntityManager entityManager = EntityManagerUtil.getEntityManager();
            entityManager.getTransaction().begin();
            entityManager.persist(simpleObject);
            entityManager.getTransaction().commit();
    
            List<SimpleObject> simpleObjectList = entityManager.createNamedQuery("SimpleObject.findAll").getResultList();
            for(SimpleObject simple : simpleObjectList){
                System.out.println(simple);
            }
    
            entityManager.close();
            
        }
    }
    

    当我试图保存它时,它正在扔它

    PersistentObjectException: detached entity passed to persist.
    

    我所需要修复的就是删除main方法中simpleObject的id设置行。

        19
  •  0
  •   H.Ostwal    6 年前

    就我而言,我在提交交易时 方法。 关于更改 坚持 方法,得到解决。

        20
  •  0
  •   Chakresh Tiwari    5 年前

    这样就行了。

        21
  •  0
  •   tomaytotomato    3 年前

    我遇到这个问题的另一个原因是事务中的实体不受Hibernate版本控制。

    添加 @Version 所有映射实体的注释

    @Entity
    public class Customer {
    
        @Id
        @GeneratedValue(strategy = GenerationType.AUTO)
        private UUID id;
    
        @Version
        private Integer version;
    
        @OneToMany(cascade = CascadeType.ALL)
        @JoinColumn(name = "orders")
        private CustomerOrders orders;
    
    }
    
    @Entity
    public class CustomerOrders {
    
        @Id
        @GeneratedValue(strategy = GenerationType.AUTO)
        private UUID id;
    
        @Version
        private Integer version;
    
        private BigDecimal value;
    
    }
    
        22
  •  0
  •   MrSolarius    3 年前

    要解决这个问题,不需要使用特定的装饰器。只需使用以下方式合并实体:

    entityManager.merge(transaction);