问题
I have a problem that I can't be able to solve. I've tried to search on the web for solutions, but I didn't find any generic solution. I want to update an object, whatever it's class may be, in the datastore. For that, here's the code I'm using for the project
I'm using DataNucleus and Google AppEngine.
Here's my jdoconfig.xml
<?xml version="1.0" encoding="utf-8"?>
<jdoconfig xmlns="http://java.sun.com/xml/ns/jdo/jdoconfig"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://java.sun.com/xml/ns/jdo/jdoconfig">
<persistence-manager-factory name="transactions-optional">
<property name="javax.jdo.PersistenceManagerFactoryClass"
value="org.datanucleus.store.appengine.jdo.DatastoreJDOPersistenceManagerFactory"/>
<property name="javax.jdo.option.ConnectionURL" value="appengine"/>
<property name="javax.jdo.option.NontransactionalRead" value="true"/>
<property name="javax.jdo.option.NontransactionalWrite" value="true"/>
<property name="javax.jdo.option.RetainValues" value="true"/>
<property name="datanucleus.appengine.autoCreateDatastoreTxns" value="true"/>
</persistence-manager-factory>
</jdoconfig>
My PMF class
public final class PMF {
private static final PersistenceManagerFactory pmfInstance =
JDOHelper.getPersistenceManagerFactory("transactions-optional");
private PMF() {}
public static PersistenceManagerFactory get() {
return pmfInstance;
}
public static PersistenceManager getPersistenceManager() {
return pmfInstance.getPersistenceManager();
}
}
My BaseModel class, wich is a superclass for all the models of the project
@Inheritance(strategy = InheritanceStrategy.SUBCLASS_TABLE)
@PersistenceCapable(detachable = "true", identityType = IdentityType.APPLICATION)
public abstract class BaseModel implements Serializable {
@PrimaryKey
@Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
protected Key id;
public boolean equals(Object obj) {
try {
return obj instanceof BaseModel ? this.getId().getId() == ((BaseModel) obj).getId().getId() : false;
} catch (Exception e) {
return false;
}
}
}
Here's the class (Project
) I want to save
@PersistenceCapable(detachable = "true", identityType = IdentityType.APPLICATION)
public class Project extends BaseModel implements BeanModelTag {
private static final long serialVersionUID = 3318013676594981107L;
@Persistent
private String name;
@Persistent
private String description;
@Persistent
private Date deadline;
@Persistent
private Integer status;
@Persistent
private Key manager;
@Persistent
private Set<Key> team;
}
For that, I tried several things. This method below saves a NEW instance of Project with success, but when I call to for updating an already detached object, only the field deadline is updated, the other attributes aren't updated (and yet, I went in debug mode to check out if the other attributes where changed, and yes, they were, but only deadline get's saved).
public void save(BaseModel object) {
PersistenceManager pm = PMF.getPersistenceManager();
try {
pm.makePersistent(object);
} finally {
pm.close();
}
}
So, I tried the following code
public void update(Project object) {
PersistenceManager pm = PMF.get().getPersistenceManager();
try {
pm.currentTransaction().begin();
Project p = pm.getObjectById(Project.class, object.getId());
p.setName(object.getName());
p.setDeadline(object.getDeadline());
p.setDescription(object.getDescription());
p.setTeam(p.getTeam());
p.setStatus(object.getStatus());
pm.currentTransaction().commit();
} catch (Exception e) {
e.printStackTrace();
pm.currentTransaction().rollback();
} finally {
pm.close();
}
}
And it worked. Ok, it works in this way, but I need a generic method for all my models, so I tried this
public void update(BaseModel object) {
PersistenceManager pm = PMF.get().getPersistenceManager();
try {
pm.currentTransaction().begin();
BaseModel ob = pm.getObjectById(object.getClass(), object.getId());
for (Field f : ob.getClass().getDeclaredFields()) {
if (!f.toString().contains("final")) {
f.setAccessible(true);
for (Field g : object.getClass().getDeclaredFields()) {
g.setAccessible(true);
if (f.getName().equals(g.getName())) {
f.set(ob, g.get(object));
}
g.setAccessible(false);
}
}
f.setAccessible(false);
}
pm.makePersistent(ob);
pm.currentTransaction().commit();
} catch (Exception e) {
e.printStackTrace();
pm.currentTransaction().rollback();
} finally {
pm.close();
}
}
But it doesn't work at all, nothing gets saved, and yet when I System.out the attributes manually, they are changed. I tried with and without pm.makePersistent(ob);
with no luck. I don't know what to do. I have 120 models in this project that inherits from BaseModel
and I can't find a way to make an update that works with my model.
----------- Edit -----------
Thanks for the answer. Here's my solution for now. Of course, that printStrackTree will get out of there.
public void update(BaseModel object) {
PersistenceManager pm = PMF.get().getPersistenceManager();
try {
pm.currentTransaction().begin();
BaseModel ob = pm.getObjectById(object.getClass(), object.getId());
for (Field f : ob.getClass().getDeclaredFields()) {
if (!Pattern.compile("\\bfinal\\b").matcher(f.toString()).find()) {
f.setAccessible(true);
for (Field g : object.getClass().getDeclaredFields()) {
g.setAccessible(true);
if (f.getName().equals(g.getName())) {
f.set(ob, g.get(object));
JDOHelper.makeDirty(ob, f.getName());
}
g.setAccessible(false);
}
f.setAccessible(false);
}
}
pm.makePersistent(object);
pm.currentTransaction().commit();
} catch (Exception e) {
e.printStackTrace();
pm.currentTransaction().rollback();
} finally {
pm.close();
}
}
回答1:
Using reflection to update fields will not be detected by JDO bytecode enhanced methods. If you want to update fields direct (whether by field or by reflection) then you could call
JDOHelper.makeDirty(obj, "myFieldName");
after doing your update, and then the change will be registered by JDO and hence updated in the datastore.
回答2:
You must use the JDOHelper while using reflection to make sure your fields get marked as dirty.
回答3:
Yep, the issues is with transactions. If you want to have a GUARANTEED persistence of a modification at a given time, you have to commit/flush the transaction. I have a GAE app that uses JPA but I've had the exact same issue.
In order to avoid the boiler plate code, I've made a base class for my DAO's that thakes a Method object, start a transaction, exectures that method and then commits it (or rolls it back). Then in my dao's I pass a method handler and the arguments to this baseDao in order to have (semi) automatic transaction handling.
you can check out the code if you think it's somthing you'd find useful:
BaseDAO : http://myprojects2.googlecode.com/svn/trunk/javakata_project/Javakata_v2/src/com/appspot/javakata6425/server/dao/BaseDAO.java
ArticleDAO : http://myprojects2.googlecode.com/svn/trunk/javakata_project/Javakata_v2/src/com/appspot/javakata6425/server/dao/ArticleDAO.java
来源:https://stackoverflow.com/questions/7486710/updating-objects-in-gae