【问题标题】:AppEngine and Datanucleus persists already persisted entityAppEngine 和 Datanucleus 持久化已持久化的实体
【发布时间】:2014-01-26 15:59:00
【问题描述】:

我有一个带有 AppEngine 的 Android 客户端作为后端。还有一个 JPA 和 datanucleus 持久性提供程序。客户端和服务器通过 REST + JSON 进行通信。当用户在手机上登录应用程序时,我正在从服务器检索用户信息并将其存储在手机上。之后,我正在创建一个对象“A”并尝试将其保存在服务器上,提供现有用户作为该对象中的一个字段。结果,我的对象“A”和用户都被存储了,但是对于用户来说,创建了重复的条目。

根据 Datanucleus 文档,此行为现在很清楚:具有 PK 字段集的对象是瞬态的,除非它与持久性分离。。将 datanucleus.allowAttachOfTransient 设置为 true 没有帮助(JPA 的此属性的文档说:“当您使用瞬态对象(设置了 PK 字段)调用 EM.merge 时,如果您启用这个功能然后它会首先检查数据存储中是否存在具有相同身份的对象,如果存在,将合并到该对象中(而不是仅仅尝试持久化一个新对象“),但首先我正在持久化对象 A,而不是合并它。

映射:

@MappedSuperclass
class BaseEntity {

    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    @JsonIgnore
    private Key id;

// more field, getters and setters
}

@Entity
class User extends BaseEntity {

private String name;
// more fields, getters and setters
}

   @Entity
   class A extends BaseEntity {

    @Basic
    @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
    private User user;
    // more fields, getters and setters
    }

persistence.xml:

 <?xml version="1.0" encoding="UTF-8" ?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
        http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd" version="1.0">

    <persistence-unit name="transactions-optional">
        <provider>org.datanucleus.api.jpa.PersistenceProviderImpl</provider>

        <class>com.example.A</class>
        <class>com.example.User</class>

        <properties>
            <property name="datanucleus.NontransactionalRead" value="true" />
            <property name="datanucleus.NontransactionalWrite" value="true" />
            <property name="datanucleus.ConnectionURL" value="appengine" />
            <property name="datanucleus.singletonEMFForName" value="true" />
            <property name="datanucleus.allowAttachOfTransient" value="true" />
            <property name="datanucleus.maxFetchDepth" value="2"/> 
            <property name="datanucleus.DetachAllOnCommit" value="true"/> 
        </properties>
    </persistence-unit>  
</persistence>

存储库:

@Repository
public class ARepository {
    @PersistenceContext
        private EntityManager entityManager;

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public A saveA(A a) {
       try {
    if (a.getId() != null) {
        entityManager.merge(a);
        } else {
            entityManager.persist(a);
        }
       } finally {
        entityManager.close();
       }
       return a;
    }

public void setEntityManager(EntityManager entityManager) {
this.entityManager = entityManager;
}

}

更新 2.:日志 如果我在存储 A 之前没有加载和设置用户,日志会显示以下行:

Object "com.exampe.User@7886c691" (id="com.example.User:A(6192449487634432)/User(6473924464345088)") has a lifecycle change : "P_NEW"->"DETACHED_CLEAN"

这当然是意料之中的。

当我在保存 A 之前加载和设置用户时,会记录以下输出:

[INFO] jaan 28, 2014 6:20:22 PM org.datanucleus.state.LifeCycleState changeState
[INFO] FINE: Object "com.example.User@31991a3c" (id="com.example.User:A(5629499534213120)/User(5066549580791808)") has a lifecycle change : "HOLLOW"->"DETACHED_CLEAN"
[INFO] jaan 28, 2014 6:20:22 PM org.datanucleus.store.connection.ConnectionManagerImpl closeAllConnections
[INFO] FINE: Connection found in the pool : com.google.appengine.datanucleus.DatastoreConnectionFactoryImpl$DatastoreManagedConnection@23fc3932 for key=org.datanucleus.ObjectManagerImpl@40f1413 in factory=ConnectionFactory:nontx[com.google.appengine.datanucleus.DatastoreConnectionFactoryImpl@79eeed79] but owner object closing so closing connection
[INFO] jaan 28, 2014 6:20:22 PM org.datanucleus.store.connection.ConnectionManagerImpl$1 managedConnectionPostClose
[INFO] FINE: Connection removed from the pool : com.google.appengine.datanucleus.DatastoreConnectionFactoryImpl$DatastoreManagedConnection@23fc3932 for key=org.datanucleus.ObjectManagerImpl@40f1413 in factory=ConnectionFactory:nontx[com.google.appengine.datanucleus.DatastoreConnectionFactoryImpl@79eeed79]
[INFO] jaan 28, 2014 6:20:39 PM org.datanucleus.state.LifeCycleState changeState
[INFO] FINE: Object "com.example.User@716f5d2f" (id="com.example.User:A(5629499534213120)/User(5066549580791808)") has a lifecycle change : "P_CLEAN"->"P_NONTRANS"
[INFO] jaan 28, 2014 6:20:39 PM com.google.appengine.datanucleus.DatastoreConnectionFactoryImpl$DatastoreManagedConnection <init>
[INFO] FINE: Created ManagedConnection using DatastoreService = com.google.appengine.api.datastore.DatastoreServiceImpl@1cc63c88
[INFO] jaan 28, 2014 6:20:39 PM org.datanucleus.store.connection.ConnectionManagerImpl allocateConnection
[INFO] FINE: Connection added to the pool : com.google.appengine.datanucleus.DatastoreConnectionFactoryImpl$DatastoreManagedConnection@438518d4 for key=org.datanucleus.ObjectManagerImpl@5116a644 in factory=ConnectionFactory:tx[com.google.appengine.datanucleus.DatastoreConnectionFactoryImpl@2aa8911c]
[INFO] jaan 28, 2014 6:20:39 PM com.google.appengine.datanucleus.MetaDataValidator warn
[INFO] WARNING: Meta-data warning for com.example.A.user: Error in meta-data for com.example.A.user : The datastore does not support joins and therefore cannot honor requests to place related objects in the default fetch group.  The field will be fetched lazily on first access.  You can modify this warning by setting the datanucleus.appengine.ignorableMetaDataBehavior property in your config.  A value of NONE will silence the warning.  A value of ERROR will turn the warning into an exception

这很奇怪,"P_CLEAN"-&gt;"P_NONTRANS" lyfecycle 更改后没有任何反应,可能与缓存、事务有关?所以我已经开始设置 spring 事务但遇到以下异常:

[INFO] Caused by: javax.persistence.PersistenceException: Illegal argument
[INFO]  at org.datanucleus.api.jpa.NucleusJPAHelper.getJPAExceptionForNucleusException(NucleusJPAHelper.java:298)
[INFO]  at org.datanucleus.api.jpa.JPAEntityTransaction.commit(JPAEntityTransaction.java:122)
[INFO]  at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:512)
[INFO]  ... 62 more
[INFO] Caused by: java.lang.IllegalArgumentException: transaction has expired or is invalid
[INFO]  at com.google.appengine.api.datastore.DatastoreApiHelper.translateError(DatastoreApiHelper.java:39)
[INFO]  at com.google.appengine.api.datastore.DatastoreApiHelper$1.convertException(DatastoreApiHelper.java:76)
[INFO]  at com.google.appengine.api.utils.FutureWrapper.get(FutureWrapper.java:94)
[INFO]  at com.google.appengine.api.datastore.Batcher$ReorderingMultiFuture.get(Batcher.java:129)
[INFO]  at com.google.appengine.api.datastore.FutureHelper$TxnAwareFuture.get(FutureHelper.java:171)
[INFO]  at com.google.appengine.api.utils.FutureWrapper.get(FutureWrapper.java:86)
[INFO]  at com.google.appengine.api.datastore.FutureHelper.getInternal(FutureHelper.java:71)
[INFO]  at com.google.appengine.api.datastore.FutureHelper.quietGet(FutureHelper.java:32)
[INFO]  at com.google.appengine.api.datastore.DatastoreServiceImpl.put(DatastoreServiceImpl.java:86)
[INFO]  at com.google.appengine.datanucleus.WrappedDatastoreService.put(WrappedDatastoreService.java:112)
[INFO]  at com.google.appengine.datanucleus.EntityUtils.putEntitiesIntoDatastore(EntityUtils.java:764)
[INFO]  at com.google.appengine.datanucleus.DatastorePersistenceHandler.insertObjectsInternal(DatastorePersistenceHandler.java:314)
[INFO]  at com.google.appengine.datanucleus.DatastorePersistenceHandler.insertObject(DatastorePersistenceHandler.java:218)
[INFO]  at org.datanucleus.state.JDOStateManager.internalMakePersistent(JDOStateManager.java:2377)
[INFO]  at org.datanucleus.state.JDOStateManager.flush(JDOStateManager.java:3769)
[INFO]  at org.datanucleus.store.mapped.mapping.PersistableMapping.setObjectAsValue(PersistableMapping.java:446)
[INFO]  at org.datanucleus.store.mapped.mapping.PersistableMapping.setObject(PersistableMapping.java:321)
[INFO]  at com.google.appengine.datanucleus.StoreFieldManager.storeRelations(StoreFieldManager.java:777)
[INFO]  at com.google.appengine.datanucleus.DatastorePersistenceHandler.insertObjectsInternal(DatastorePersistenceHandler.java:367)
[INFO]  at com.google.appengine.datanucleus.DatastorePersistenceHandler.insertObject(DatastorePersistenceHandler.java:218)
[INFO]  at org.datanucleus.state.JDOStateManager.internalMakePersistent(JDOStateManager.java:2377)
[INFO]  at org.datanucleus.state.JDOStateManager.flush(JDOStateManager.java:3769)
[INFO]  at org.datanucleus.ObjectManagerImpl.flushInternalWithOrdering(ObjectManagerImpl.java:3884)
[INFO]  at org.datanucleus.ObjectManagerImpl.flushInternal(ObjectManagerImpl.java:3807)
[INFO]  at org.datanucleus.ObjectManagerImpl.flush(ObjectManagerImpl.java:3747)
[INFO]  at org.datanucleus.ObjectManagerImpl.preCommit(ObjectManagerImpl.java:4137)
[INFO]  at org.datanucleus.ObjectManagerImpl.transactionPreCommit(ObjectManagerImpl.java:428)
[INFO]  at org.datanucleus.TransactionImpl.internalPreCommit(TransactionImpl.java:400)
[INFO]  at org.datanucleus.TransactionImpl.commit(TransactionImpl.java:288)
[INFO]  at org.datanucleus.api.jpa.JPAEntityTransaction.commit(JPAEntityTransaction.java:103)

spring 配置如下所示:

<mvc:annotation-driven />
    <tx:annotation-driven transaction-manager="transactionManager"/>

    <bean id="jacksonMessageConverter" class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter"/>

     <bean id="persistenceUnitManager" class="org.springframework.orm.jpa.persistenceunit.DefaultPersistenceUnitManager">
        <property name="persistenceXmlLocation">
            <value>classpath*:/META-INF/persistence.xml</value>
        </property>
    </bean>
    <bean id="entityManagerFactory"
        class="org.springframework.orm.jpa.LocalEntityManagerFactoryBean"
        lazy-init="true">
        <property name="persistenceUnitName" value="transactions-optional" />
    </bean>

    <bean name="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
        <property name="entityManagerFactory" ref="entityManagerFactory" />
    </bean>

【问题讨论】:

  • 你不能设置持久对象的公共字段(因为它们不会被拦截),使用setter
  • 对不起,我用的是setter,最后一部分只是为了说明。
  • 密钥是 Google 的密钥 developers.google.com/appengine/docs/python/datastore/keyclass(他们不允许在 appengine 中使用 Longs 作为关系标识符),这就是我必须使用它的原因。从日志中可以看出: (id="com.example.User:A(5629499534213120)/User(5066549580791808)") 实际上是一个键(实体标识符)

标签: java google-app-engine jpa datanucleus


【解决方案1】:

那么这个对象实际上是处于分离状态吗?当它已被管理并关闭持久性上下文或显式调用 “em.detach” 时,它处于 分离 状态。其他任何东西都可能处于 transient 状态。日志显示了对象的状态。显然,您可以启用持久性属性

datanucleus.allowAttachOfTransient

根据the docs,但是您还没有通过您发布的内容启用它。

您似乎也对您使用的 API 感到困惑,引用了 JDO PMF 规范,然后说您正在使用 JPA。

【讨论】:

  • 感谢您的回答。如您所见,我正在使用 JPA 注释(我也有 persistence.xml),我发布了 JDO 配置以防万一,因为它是默认为应用程序引擎项目生成的(但我确实不知道它和 datanucleus) .我将尝试添加您提到的属性。你,我不明白为什么它应该有帮助。两个对象都正确持久化。它只是看不到,用户对象有一个非空键,因此不应该被持久化,而只是被引用。
  • 正如我已经说过的,对象必须处于“分离”状态才能被附加,并且设置一些字段是不够的......它必须被分离。使用 GAE,您可以将 JPA 注释与 JDO 持久性 FWIW 一起使用。 “persistence.xml”是你应该发布的,而不是 jdoconfig.xml(如果不使用 JDO,你应该删除它)
  • 这就是日志的作用,告诉你你的对象处于什么状态。
【解决方案2】:

我看到的第一个错误是您对关系使用了@Basic 注释。请参阅allowed types of the Basic annotation 的文档。

改为使用@JoinColumn 注释(如果您想更改任何默认设置)。

我看到的第二个问题是,无论 ID 是什么,您都会保留实体。查看有问题的正确代码(在存储库中):

    if (a.getId() != null) {
         entityManager.merge(a);
    } else {
         entityManager.persist(a);//this line should be in an ELSE branch (in your code it is not)
    }

【讨论】:

  • Thnx,我将使用 JoinColumn asasp 尝试您的解决方案,目前我手头没有这个项目。关于第二个问题 - 你是对的,我已经解决了这个问题,它并没有阻止你。
  • 正如@DataNucleus 所说,永远不要设置像a.user=myUser 这样的字段,并且不要让您的字段始终私有并调用setter/getter。此外,我有点困惑,因为你描述了太多的测试。请保留我发布的代码,不要预先加载和预先设置用户,然后如果发生异常则发布。
  • Thnx 提醒一下,但我总是使用带有私有字段的设置器。这只是为了说明,但好吧,我会尽量避免这样的插图,如果它让人们如此困惑;)
  • 不幸的是,更改为 @JoinColumn 并没有帮助。我的弹簧配置可能有问题(尤其是事务)?目前我正在尝试设置 spring 事务,但它失败了。请参阅原始帖子中的弹簧配置。
【解决方案3】:

在我添加以下属性后它开始工作:

<property name="datanucleus.appengine.datastoreEnableXGTransactions" value="true"/>
<property name="datanucleus.appengine.relationDefault" value="unowned"/>

在保存 A 之前,我仍然需要预加载 User 对象并将其设置为 A。我很确定你面前有很多问题......

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-11-15
    • 1970-01-01
    • 2017-10-10
    • 1970-01-01
    • 2015-04-08
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多