AppEngine和Datanucleus持续存在实体

时间:2014-01-26 15:59:01

标签: java google-app-engine jpa datanucleus

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

根据Datanucleus文档,此行为现已明确:具有PK字段集的对象是瞬态的,除非它与持久性分离。。将 datanucleus.allowAttachOfTransient 设置为true无效(JPA的此属性文档说:“当您使用瞬态对象调用EM.merge时(设置了PK字段),如果启用此功能然后它将首先检查数据存储中是否存在具有相同标识的对象,如果存在,将合并到该对象(而不是仅仅尝试持久保存新对象),但起初我我坚持对象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"->"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 config看起来像这样:

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

3 个答案:

答案 0 :(得分:1)

这个对象实际上处于分离状态吗?它处于分离状态时,它已被管理并关闭持久性上下文,或者您显式调用“em.detach” transient 状态中的任何其他内容都可能存在。日志显示状态对象所在的位置。显然,您可以启用持久性属性

datanucleus.allowAttachOfTransient

按照the docs,但是你没有通过你发布的内容启用它。

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

答案 1 :(得分:0)

我看到的第一个错误是您使用@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)
    }

答案 2 :(得分:0)

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

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

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