I am trying to learn JDO (and at the same time its GAE and Spring intricacies) by creating a small web app, and am having trouble getting updated domain objects to persist back to the database. I initially grab the entity from the DB and detach it so that I can show it to the user and allow them to change it. Once the user has made the changes and posts the form back to the app, I again grab the entity from the DB (detached), update its properties, and then call a pm.makePersistent(). The abbreviated code is as follows:
User Domain Object:
#PersistenceCapable(detachable="true")
public class User extends BaseEntity {
#Persistent
private String firstName = "";
#Persistent
private String middleInitial = "";
#Persistent
private String lastName = "";
}
DAO Read Method:
public User read(Key key) throws DataException {
PersistenceManager pm = PMF.get().getPersistenceManager();
User pkg, detached = null;
try {
pkg = (User) pm.getObjectById(User.class, key);
detached = pm.detachCopy(pkg);
detached.setIsAlreadyInDB(true);
}
catch (Exception e) {
throw new DataException("An error occured trying to read the User object. Details:\n" + e.getMessage());
}
finally {
pm.close();
}
return detached;
}
DAO Update Method:
private void update(User pkg) throws DataException {
PersistenceManager pm = PMF.get().getPersistenceManager();
Transaction tx = pm.currentTransaction();
try {
tx.begin();
pm.makePersistent(pkg);
tx.commit();
}
finally {
if (tx.isActive()) tx.rollback();
pm.close();
}
}
Now when I get down into the update method, I've proven to myself that I'm working with just the same object from my read via inspecting its hashCode(), I've changed a value using the domain object's setter method, I've even printed the changed value to the console to make sure it's getting done, and JDOHelper.isDirty() still returns false, and therefore none of the changes get persisted back to the database.
Any thoughts on what I'm missing or if I'm approaching this from the wrong angle? Thank you for helping out a JDO beginner!
JDOHelper.isDirty is for managed objects. A detached object is not managed. DataNucleus provides a helper method of its own to get the dirty fields while detached since the logic is implementation-specific
String[] dirtyFieldNames = NucleusJDOHelper.getDetachedObjectDirtyFields(obj, pm);
Related
I have encountered a curious bug or feature while writing code. Here's the situation:
We are using a PostgreSQL database, EclipseLink in a JavaEE project.
What I am doing in this part of the project is fetching an entity from the database i.e.:
User user = userController.get(userId);
Which then goes to our controller and fetches the user via a TypedQuery:
#Stateless
#LocalBean
public class UserController {
private EntityManager em;
public User get(Integer userId){
User retval = null;
TypedQuery<User> = em.createNamedQuery("User.findByUserId", User.class);
q.setParameter("userId", userId);
retval = q.getSingleResult();
}
public User update(final User modified){...}
}
And in my User class I have:
#NamedQuery(name = "User.findByUserId", query = "SELECT u FROM User u WHERE u.id = :userId"),
So the call goes, I get my user object with its respective data from the database.
In the class where I called the userController.get method I continue to modify the data on this object, and call our controller again to update this data on the database
user.setServiceId(1); //any id (integer) pointing to an existing service, this is a ManyToOne relationship
userController.update(user);
And here is where it gets funny. In our update method inside the controller class I have my modified User object and using this object I get the primary key userId and fetch the data again from the database to get the original:
#Stateless
#LocalBean
public class userController {
private EntityManager em;
public User get(Integer userId){...}
public User update(final User modified){
User retval = null;
if(modified != null){
try {
User original = get(modified.getId()); //Here I fetch the current state of the DB
if(original != null){
Set<Modifications> modifications = apply(original, modified); //Method to apply modifications
retval = em.merge(original); //Merge changes into database
em.flush(); //Force data to be persisted
catch(Exception e){
}
return retval;
}
}
However, the fields in the original object do not reflect the state of the database but instead contains the same data as the modified object. In this case, the serviceId on the database is null, and in the modified I set it to an ID. The original has its serviceId set to the same value as the modified object even though it should contain the fetched data from the database, in this case null
My current solution is to construct a new User object, after fetching the user from the database, and modify the data on that new object:
User user = userController.get(userId);
User newUser = new User(user);
newUser.setService(service);
userController.update(newUser);
Now when I do the update method, the original reflects the state of the database.
Or maybe it reflects the state of the user object that already exists in the persistence context?
But why does this happen? Since I do make a new get call with a SELECT statement to the database in my update method.
You are using the same EntityManager for everything, both the read and the 'merge', which in this case is then a no-op. Everything read in through an EM is managed, so that if you read it back again, you get the same instance back. As long as the User isn't being serialized, it is 'managed' by the EntityManager it was read from, and so that same instance, and its changes, are visible on any get calls on that ID.
You didn't show how you are getting EntityManagers, but I would guess is isn't container managed, as they would inject a new one for these calls, and then close them for you when done. You haven't shown any transaction logic on how the update and the em context it is using are hooked up, but I would suggest you create a new EntityManager for these calls. Flush also seems unnecessary, as if update is wrapped in a transaction, should handle flushing the update statement to the database without this extra call.
If user.setServiceId(1); is called when the "user" entity is managed, the call is going to update the database row.
you can check the manage entity lifecycle
You need to refresh the data after saving it to the database and to get the latest state of the object, as em.refresh(retval)
You can find the code added below.
#Stateless
#LocalBean
public class userController {
private EntityManager em;
public User get(Integer userId){...}
public User update(final User modified){
User retval = null;
if(modified != null){
try {
User original = get(modified.getId()); //Here I fetch the current state of the DB
if(original != null){
Set<Modifications> modifications = apply(original, modified); //Method to apply modifications
retval = em.merge(original); //Merge changes into database
em.flush(); //Force data to be persisted
em.refresh(retval); // This will fetch the updated data from database
catch(Exception e){
}
return retval;
}
}
I have an object "Chemical" that is updated according to entries in an HTML page. The data is returned to the Java code correctly, yet this one field is not updating or being created in the database. The chemical entity is defined as follows:
#Entity
#NamedQuery(name="Chemical.findAll", query="SELECT c FROM Chemical c")
public class Chemical implements Serializable {
#Id
#Column(name="chemical_id")
#GeneratedValue(strategy=GenerationType.IDENTITY)
private int chemicalId;
...
private string formula; <--- THE FIELD THAT WILL NOT UPDATE/BE CREATED
....
public void setFormula(String formula) {
this.formula = formula;
}
public String getFormula() {
return this.formula;
}
.....
The Java code that actually saves/updates the database:
public void saveOrUpdate(final T data) throws CPDPersistenceException {
final EntityManager em = getEntityManager();
try {
final EntityTransaction transaction = em.getTransaction();
transaction.begin();
em.merge(data);
transaction.commit();
} catch (final PersistenceException e) {
throw new PersistenceException(e);
}
}
After the code is executed, other fields have changed in the database if changes have been made. However, "formula" is not changed and remains a NULL field. Can anybody see why?
There is I think a difference between UPDATE and CREATE.
Try to replace em.merge(data) with em.persist(data)
Does it save the content the first time you create a new row with persist ?
What is the length of your string definition in the database ?
Check the database logs whenever the HQL is run from your code, do the log complain at one point during the persist ?
It might be necessary to first test if the row exists before you can run a merge.
I'm getting in trouble trying to separate the business and the persistance in my project.
The skeleton does more or less something like:
private UsuarioBO findById(String idUsuario) {
Usuario dao = getUsuarioDao().findById(idUsuario);
return new UsuarioBO(dao);
}
private void save(UsuarioBO bo){
Usuario dao = bo.bo2dao();
getUsuarioDao().save(dao);
}
Some clarifications about the code above:
UsuarioBO is a business objet and Usuario is an entity mapped to a DB
table.
The new UsuarioBO(dao) is just a mapping method and the
bo.bo2dao() is also a mapping method, but it creates a new empty entity
Usuario.
As you can see, the entity created in both methods is erased of memory on method finish.
The problem goes when i try to do this:
UsuarioBO example = findById("whatever");
save(example);
When i do this, hibernate tells me "there's an existing entity with the same identifier", and thats true! (It's creating an entity on findById() and another on save() ).
The only solution i've found is to use the entity as "bo". I mean, bring the entity object to the service, do whatever modifications on the entity directly, and when i finish do the save sending the entity in spite of a BO.
I'm pretty sure there's a better way of doing so, but how?
EDIT:
this is the save(dao) method:
public void save(Usuario usuario) throws Exception {
try {
getSession().saveOrUpdate(usuario);
} catch (RuntimeException e) {
//error treatment
throw e;
}
}
this is the bo2dao() method (its inside the BO bean):
public Usuario bo2dao() throws Exception {
Usuario dao = new Usuario();
try {
dao.setId(this.id);
dao.setPassword(this.password);
//other similar fields...
dao.setLastLoginTime(this.lastLoginTime);
Role r = new Role();
r.setId(LoginHelper.getRoleId(this.role.getName()));
dao.setRole(r);
Status s = new Status();
s.setId(LoginHelper.getStatusId(this.status.getName()));
dao.setStatus(s);
} catch (Exception e) {
throw e;
}
return dao;
}
In this case, Role and Status are also entity beans connected with Usuario (one user can only have one role and one status).
getRoleId() and getStatusId() returns the correspondent id from the name (for example: "UNLOCKED" returns 1)
I'm having a problem storing an object in the datastore. I have an object, MyObject, that I'm trying to store but when the code is executed nothing happens. I go to look at the datastore dashboard and MyObject isn't there. No exceptions are thrown and there are no errors.
Here's my object
#PersistenceCapable(identityType = IdentityType.APPLICATION)
public class MyObject{
#PrimaryKey
#Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
private Key key;
#Persistent
String name;
#Persistent
String beta;
#Persistent
double loc;
public MyObject(String name1){
name = name1;
}
//getters and setters
}
and here's the code to store the object
public static void saveMyObject(MyObject a)throws Exception{
PersistenceManager pm = PMF.get().getPersistenceManager();
try{
pm.makePersistent(a);
}
catch(Exception e){
throw e;
}
finally{
pm.close();
}
}
Can anyone see what I'm missing?
It looks like you are using JDO, so you might want either to add a JDO tag or else mention that somewhere....
I would replace
try
{
pm.makePersistent(a);
}
with
try
{
MyObject myObj = new MyObject(a.getName()); // or whatever the getter is
myObj.setField2(a.getField2()); // Copy 1 data member from a
... // Make a MyObject.copy(...) method?
pm.makePersistent(myObj);
}
The key thing is that JDO uses enhancement: magic bytecode that is inserted after main Java compilation. I manipulate my persistent entity objects within the lifecycle ("scope") of enhancement to get JDO to work.
I also use transactions for writing (I don't know your JDO auto-transaction setting(s)). I always use transactions when creating and persisting a new persistent entity. You might want to try that if the change above does not work.
i am experimenting with the Google App Engine, and the persist option JDO.
I would like to know if it is possible to map a transient object to a persist object? Or something to update the persistent object with the use of an transient object?
I the coding examples i see the following piece of code to update objects:
public void updateEmployeeTitle(User user, String newTitle) {
PersistenceManager pm = PMF.get().getPersistenceManager();
try {
Employee e = pm.getObjectById(Employee.class, user.getEmail());
if (titleChangeIsAuthorized(e, newTitle) {
e.setTitle(newTitle);
} else {
throw new UnauthorizedTitleChangeException(e, newTitle);
}
} finally {
pm.close();
}
}
But this is not what i want, does anybody know if i can update the whole object like JPA does: object.update();
So i would like something like this:
public User update(User u) {
PersistenceManager pm = PMF.get().getPersistenceManager();
User usr;
try {
usr = pm.getObjectById(User.class, u.getId());
// copy transient object u to persist object usr.
// on update of usr all changes in object u are persistent.
} finally {
pm.close();
}
return u;
}
A "transient" object has no "identity" so there is no way to locate it in a datastore. Think carefully if you want to use transient objects, or whether it would be better to just use detached objects. JPA uses the equivalent of a "detached" object. JDO can do that too and then you just call pm.makePersistent(detachedObj);
--Andy (DataNucleus)