2

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();
    }
}
SHiRKiT
  • 1,024
  • 3
  • 11
  • 30

3 Answers3

4

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.

DataNucleus
  • 15,497
  • 3
  • 32
  • 37
  • Hey, that's what I needed. Now, I have the best solution ever, with only 1 method for all my objects to save/update an object in the datastore. Just to let everyone knows, you have 2 options here: - Or you put the markDirty in every set that doesn't work, wich I don't recommend. I'm using GXT, and the way GXT works somehow ignores the JDO. - Or you put that in save method in the server side, wich I did and recommend. You can check for differences, but I find out that it's easier just to mark all fields as dirty. – SHiRKiT Sep 23 '11 at 13:05
1

You must use the JDOHelper while using reflection to make sure your fields get marked as dirty.

0

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

Shivan Dragon
  • 15,004
  • 9
  • 62
  • 103
  • Well, I'm using begin and commit methods to avoid this, but even though it fails. With or without commit I get the same result of the previous, for all methods I've shown here. – SHiRKiT Sep 20 '11 at 14:43
  • Try using the setter methods instead of the fields in your code that does introspection/reflection to set the values in the bean – Shivan Dragon Sep 20 '11 at 14:52
  • But the problem is if I do that, then I won't have a generic method for all my models. As I said, I could create an update method for each model I have, but I want to avoid that with all my strength. – SHiRKiT Sep 20 '11 at 14:58
  • I'm sorry, I ment, in your generic code where you do for (Field f : ob.getClass().getDeclaredFields()) {, do Method m .... then use the setter methods to set the values instead of the fields... – Shivan Dragon Sep 20 '11 at 15:00