【发布时间】: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"->"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