FAQ
I just did a quick test using:

DataContext dataContext = DataContext.createDataContext();

User user = dataContext.newObject(User.class);

user.setFirstName("System");
user.setLastName("Administrator");
user.setUsername("admin");

dataContext.commitChanges();

user.setFirstName("System"); // This isn't a real change

System.out.println(dataContext.hasChanges());

dataContext.commitChanges();

In a nutshell, create a user, commit it, set a value to the same
value, then check hasChanges(). The output is:


INFO: INSERT INTO Users (first_name, id, last_name, password,
username) VALUES (?, ?, ?, ?, ?)
INFO: [bind: 1->first_name:'System', 2->id:200,
3->last_name:'Administrator', 4->password:NULL, 5->username:'admin']
true

The "true" is that the dataContext has changes, even though the second
commitChanges() doesn't do anything (there are no real changes).
Should we modify CayenneDataObject's writeProperty() to check if the
old value and new value are equal before calling propertyChanged()? I
was also noticing Embeddables doing something similar.

Thanks,

mrg

Search Discussions

  • Robert Zeigler at Mar 11, 2011 at 8:52 pm
    Seems reasonable to me. Especially since commitChanges is a no-op in that situation.
    Also, that's how property change listeners in java beans work... only fire if the old and new values are different (see http://download.oracle.com/javase/7/docs/api/java/beans/PropertyChangeSupport.html#firePropertyChange(java.beans.PropertyChangeEvent). Not that these are property change listeners... but it meets prior expectation.

    Robert
    On Mar 11, 2011, at 3/112:22 PM , Michael Gentry wrote:

    I just did a quick test using:

    DataContext dataContext = DataContext.createDataContext();

    User user = dataContext.newObject(User.class);

    user.setFirstName("System");
    user.setLastName("Administrator");
    user.setUsername("admin");

    dataContext.commitChanges();

    user.setFirstName("System"); // This isn't a real change

    System.out.println(dataContext.hasChanges());

    dataContext.commitChanges();

    In a nutshell, create a user, commit it, set a value to the same
    value, then check hasChanges(). The output is:


    INFO: INSERT INTO Users (first_name, id, last_name, password,
    username) VALUES (?, ?, ?, ?, ?)
    INFO: [bind: 1->first_name:'System', 2->id:200,
    3->last_name:'Administrator', 4->password:NULL, 5->username:'admin']
    true

    The "true" is that the dataContext has changes, even though the second
    commitChanges() doesn't do anything (there are no real changes).
    Should we modify CayenneDataObject's writeProperty() to check if the
    old value and new value are equal before calling propertyChanged()? I
    was also noticing Embeddables doing something similar.

    Thanks,

    mrg
  • Andrus Adamchik at Mar 11, 2011 at 8:54 pm
    I'd rather we don't introduce a potentially expensive comparison in the superclass setters. A setter may be called many times before a commit, so the assumption is that it is much cheaper to only do comparisons once during commit.

    I guess you can generate special setters with a custom template. Or we may run pre-commit logic from within 'hasChanges'...

    Andrus


    On Mar 11, 2011, at 10:22 PM, Michael Gentry wrote:
    I just did a quick test using:

    DataContext dataContext = DataContext.createDataContext();

    User user = dataContext.newObject(User.class);

    user.setFirstName("System");
    user.setLastName("Administrator");
    user.setUsername("admin");

    dataContext.commitChanges();

    user.setFirstName("System"); // This isn't a real change

    System.out.println(dataContext.hasChanges());

    dataContext.commitChanges();

    In a nutshell, create a user, commit it, set a value to the same
    value, then check hasChanges(). The output is:


    INFO: INSERT INTO Users (first_name, id, last_name, password,
    username) VALUES (?, ?, ?, ?, ?)
    INFO: [bind: 1->first_name:'System', 2->id:200,
    3->last_name:'Administrator', 4->password:NULL, 5->username:'admin']
    true

    The "true" is that the dataContext has changes, even though the second
    commitChanges() doesn't do anything (there are no real changes).
    Should we modify CayenneDataObject's writeProperty() to check if the
    old value and new value are equal before calling propertyChanged()? I
    was also noticing Embeddables doing something similar.

    Thanks,

    mrg
  • Michael Gentry at Mar 11, 2011 at 9:15 pm
    My particular use case in my Tapestry application is there are many
    different pages the user can edit before deciding to save. I wanted
    to display an "Unsaved Changes" message as they navigate around from
    page to page (combination of JavaScript for the current page and
    dataContext.hasChanges() when they move to a new page). However,
    Tapestry is always calling the setters when the form is submitted
    (page navigation, but not commitChanges()) and this is causing my
    "Unsaved Changes" message to always appear, even when they don't
    actually change values, because Tapestry has called all the setters
    with the original value.

    mrg

    On Fri, Mar 11, 2011 at 3:54 PM, Andrus Adamchik wrote:
    I'd rather we don't introduce a potentially expensive comparison in the superclass setters. A setter may be called many times before a commit, so the assumption is that it is much cheaper to only do comparisons once during commit.

    I guess you can generate special setters with a custom template. Or we may run pre-commit logic from within 'hasChanges'...

    Andrus


    On Mar 11, 2011, at 10:22 PM, Michael Gentry wrote:
    I just did a quick test using:

    DataContext dataContext = DataContext.createDataContext();

    User user = dataContext.newObject(User.class);

    user.setFirstName("System");
    user.setLastName("Administrator");
    user.setUsername("admin");

    dataContext.commitChanges();

    user.setFirstName("System"); // This isn't a real change

    System.out.println(dataContext.hasChanges());

    dataContext.commitChanges();

    In a nutshell, create a user, commit it, set a value to the same
    value, then check hasChanges().  The output is:


    INFO: INSERT INTO Users (first_name, id, last_name, password,
    username) VALUES (?, ?, ?, ?, ?)
    INFO: [bind: 1->first_name:'System', 2->id:200,
    3->last_name:'Administrator', 4->password:NULL, 5->username:'admin']
    true

    The "true" is that the dataContext has changes, even though the second
    commitChanges() doesn't do anything (there are no real changes).
    Should we modify CayenneDataObject's writeProperty() to check if the
    old value and new value are equal before calling propertyChanged()?  I
    was also noticing Embeddables doing something similar.

    Thanks,

    mrg
  • Robert Zeigler at Mar 11, 2011 at 9:41 pm
    As a workaround, you could probably have a base class that overrides writeProperty and checks to see if the new value is the same as the old value before writing? (or a custom superclass template).

    Robert
    On Mar 11, 2011, at 3/113:14 PM , Michael Gentry wrote:

    My particular use case in my Tapestry application is there are many
    different pages the user can edit before deciding to save. I wanted
    to display an "Unsaved Changes" message as they navigate around from
    page to page (combination of JavaScript for the current page and
    dataContext.hasChanges() when they move to a new page). However,
    Tapestry is always calling the setters when the form is submitted
    (page navigation, but not commitChanges()) and this is causing my
    "Unsaved Changes" message to always appear, even when they don't
    actually change values, because Tapestry has called all the setters
    with the original value.

    mrg

    On Fri, Mar 11, 2011 at 3:54 PM, Andrus Adamchik wrote:
    I'd rather we don't introduce a potentially expensive comparison in the superclass setters. A setter may be called many times before a commit, so the assumption is that it is much cheaper to only do comparisons once during commit.

    I guess you can generate special setters with a custom template. Or we may run pre-commit logic from within 'hasChanges'...

    Andrus


    On Mar 11, 2011, at 10:22 PM, Michael Gentry wrote:
    I just did a quick test using:

    DataContext dataContext = DataContext.createDataContext();

    User user = dataContext.newObject(User.class);

    user.setFirstName("System");
    user.setLastName("Administrator");
    user.setUsername("admin");

    dataContext.commitChanges();

    user.setFirstName("System"); // This isn't a real change

    System.out.println(dataContext.hasChanges());

    dataContext.commitChanges();

    In a nutshell, create a user, commit it, set a value to the same
    value, then check hasChanges(). The output is:


    INFO: INSERT INTO Users (first_name, id, last_name, password,
    username) VALUES (?, ?, ?, ?, ?)
    INFO: [bind: 1->first_name:'System', 2->id:200,
    3->last_name:'Administrator', 4->password:NULL, 5->username:'admin']
    true

    The "true" is that the dataContext has changes, even though the second
    commitChanges() doesn't do anything (there are no real changes).
    Should we modify CayenneDataObject's writeProperty() to check if the
    old value and new value are equal before calling propertyChanged()? I
    was also noticing Embeddables doing something similar.

    Thanks,

    mrg
  • Michael Gentry at Mar 12, 2011 at 1:34 am
    Hi Robert,

    That was my first attempt. I overrode writeProperty() but then
    discovered that Embeddables don't call writeProperty(), but use a
    completely different method of updating attributes.

    Thanks,

    mrg


    On Fri, Mar 11, 2011 at 4:40 PM, Robert Zeigler
    wrote:
    As a workaround, you could probably have a base class that overrides writeProperty and checks to see if the new value is the same as the old value before writing? (or a custom superclass template).

    Robert
    On Mar 11, 2011, at 3/113:14 PM , Michael Gentry wrote:

    My particular use case in my Tapestry application is there are many
    different pages the user can edit before deciding to save.  I wanted
    to display an "Unsaved Changes" message as they navigate around from
    page to page (combination of JavaScript for the current page and
    dataContext.hasChanges() when they move to a new page).  However,
    Tapestry is always calling the setters when the form is submitted
    (page navigation, but not commitChanges()) and this is causing my
    "Unsaved Changes" message to always appear, even when they don't
    actually change values, because Tapestry has called all the setters
    with the original value.

    mrg

    On Fri, Mar 11, 2011 at 3:54 PM, Andrus Adamchik wrote:
    I'd rather we don't introduce a potentially expensive comparison in the superclass setters. A setter may be called many times before a commit, so the assumption is that it is much cheaper to only do comparisons once during commit.

    I guess you can generate special setters with a custom template. Or we may run pre-commit logic from within 'hasChanges'...

    Andrus


    On Mar 11, 2011, at 10:22 PM, Michael Gentry wrote:
    I just did a quick test using:

    DataContext dataContext = DataContext.createDataContext();

    User user = dataContext.newObject(User.class);

    user.setFirstName("System");
    user.setLastName("Administrator");
    user.setUsername("admin");

    dataContext.commitChanges();

    user.setFirstName("System"); // This isn't a real change

    System.out.println(dataContext.hasChanges());

    dataContext.commitChanges();

    In a nutshell, create a user, commit it, set a value to the same
    value, then check hasChanges().  The output is:


    INFO: INSERT INTO Users (first_name, id, last_name, password,
    username) VALUES (?, ?, ?, ?, ?)
    INFO: [bind: 1->first_name:'System', 2->id:200,
    3->last_name:'Administrator', 4->password:NULL, 5->username:'admin']
    true

    The "true" is that the dataContext has changes, even though the second
    commitChanges() doesn't do anything (there are no real changes).
    Should we modify CayenneDataObject's writeProperty() to check if the
    old value and new value are equal before calling propertyChanged()?  I
    was also noticing Embeddables doing something similar.

    Thanks,

    mrg
  • Robert Zeigler at Mar 12, 2011 at 4:58 am
    Ah. I see. Yeah, I haven't messed with embeddables. Sucky.

    Robert
    On Mar 11, 2011, at 3/117:33 PM , Michael Gentry wrote:

    Hi Robert,

    That was my first attempt. I overrode writeProperty() but then
    discovered that Embeddables don't call writeProperty(), but use a
    completely different method of updating attributes.

    Thanks,

    mrg


    On Fri, Mar 11, 2011 at 4:40 PM, Robert Zeigler
    wrote:
    As a workaround, you could probably have a base class that overrides writeProperty and checks to see if the new value is the same as the old value before writing? (or a custom superclass template).

    Robert
    On Mar 11, 2011, at 3/113:14 PM , Michael Gentry wrote:

    My particular use case in my Tapestry application is there are many
    different pages the user can edit before deciding to save. I wanted
    to display an "Unsaved Changes" message as they navigate around from
    page to page (combination of JavaScript for the current page and
    dataContext.hasChanges() when they move to a new page). However,
    Tapestry is always calling the setters when the form is submitted
    (page navigation, but not commitChanges()) and this is causing my
    "Unsaved Changes" message to always appear, even when they don't
    actually change values, because Tapestry has called all the setters
    with the original value.

    mrg

    On Fri, Mar 11, 2011 at 3:54 PM, Andrus Adamchik wrote:
    I'd rather we don't introduce a potentially expensive comparison in the superclass setters. A setter may be called many times before a commit, so the assumption is that it is much cheaper to only do comparisons once during commit.

    I guess you can generate special setters with a custom template. Or we may run pre-commit logic from within 'hasChanges'...

    Andrus


    On Mar 11, 2011, at 10:22 PM, Michael Gentry wrote:
    I just did a quick test using:

    DataContext dataContext = DataContext.createDataContext();

    User user = dataContext.newObject(User.class);

    user.setFirstName("System");
    user.setLastName("Administrator");
    user.setUsername("admin");

    dataContext.commitChanges();

    user.setFirstName("System"); // This isn't a real change

    System.out.println(dataContext.hasChanges());

    dataContext.commitChanges();

    In a nutshell, create a user, commit it, set a value to the same
    value, then check hasChanges(). The output is:


    INFO: INSERT INTO Users (first_name, id, last_name, password,
    username) VALUES (?, ?, ?, ?, ?)
    INFO: [bind: 1->first_name:'System', 2->id:200,
    3->last_name:'Administrator', 4->password:NULL, 5->username:'admin']
    true

    The "true" is that the dataContext has changes, even though the second
    commitChanges() doesn't do anything (there are no real changes).
    Should we modify CayenneDataObject's writeProperty() to check if the
    old value and new value are equal before calling propertyChanged()? I
    was also noticing Embeddables doing something similar.

    Thanks,

    mrg
  • Andrus Adamchik at Mar 12, 2011 at 8:44 am
    Yeah, I totally understand how this can be useful. I am trying to analyze the situation and just like with most things in Cayenne core it can get pretty deep. Some factors to consider:

    1. Performance (I mentioned about that before).

    2. "Circular changes" canceling modifications:

    assertEquals("A", o.getP());
    o.setP("B"); // modified
    o.setP("A"); // modified? (property change is "canceled", but maybe other properties are also modified)

    3. "Uncommittable changes". An example - objects with modified to-many relationships are treated as modified, but during commit their DB records are not updated.

    So I am leaning towards either a method like ObjectContext.hasCommittabledChanges() that would do deeper analysis of the object graph, or maybe a more involved changeset API that I started to draft under cayenne-lifecycle, that would among other things allow the caller to obtain a more nuanced and detailed view of the graph changes (overall changes, changes per object, committable/uncommitable changes etc.)

    And there's of course we shouldn't forget ROP....

    Andrus

    On Mar 11, 2011, at 11:14 PM, Michael Gentry wrote:
    My particular use case in my Tapestry application is there are many
    different pages the user can edit before deciding to save. I wanted
    to display an "Unsaved Changes" message as they navigate around from
    page to page (combination of JavaScript for the current page and
    dataContext.hasChanges() when they move to a new page). However,
    Tapestry is always calling the setters when the form is submitted
    (page navigation, but not commitChanges()) and this is causing my
    "Unsaved Changes" message to always appear, even when they don't
    actually change values, because Tapestry has called all the setters
    with the original value.

    mrg

    On Fri, Mar 11, 2011 at 3:54 PM, Andrus Adamchik wrote:
    I'd rather we don't introduce a potentially expensive comparison in the superclass setters. A setter may be called many times before a commit, so the assumption is that it is much cheaper to only do comparisons once during commit.

    I guess you can generate special setters with a custom template. Or we may run pre-commit logic from within 'hasChanges'...

    Andrus


    On Mar 11, 2011, at 10:22 PM, Michael Gentry wrote:
    I just did a quick test using:

    DataContext dataContext = DataContext.createDataContext();

    User user = dataContext.newObject(User.class);

    user.setFirstName("System");
    user.setLastName("Administrator");
    user.setUsername("admin");

    dataContext.commitChanges();

    user.setFirstName("System"); // This isn't a real change

    System.out.println(dataContext.hasChanges());

    dataContext.commitChanges();

    In a nutshell, create a user, commit it, set a value to the same
    value, then check hasChanges(). The output is:


    INFO: INSERT INTO Users (first_name, id, last_name, password,
    username) VALUES (?, ?, ?, ?, ?)
    INFO: [bind: 1->first_name:'System', 2->id:200,
    3->last_name:'Administrator', 4->password:NULL, 5->username:'admin']
    true

    The "true" is that the dataContext has changes, even though the second
    commitChanges() doesn't do anything (there are no real changes).
    Should we modify CayenneDataObject's writeProperty() to check if the
    old value and new value are equal before calling propertyChanged()? I
    was also noticing Embeddables doing something similar.

    Thanks,

    mrg
  • Michael Gentry at Mar 12, 2011 at 1:42 pm

    On Sat, Mar 12, 2011 at 3:43 AM, Andrus Adamchik wrote:
    Yeah, I totally understand how this can be useful. I am trying to analyze the situation and just like with most things in Cayenne core it can get pretty deep.
    Yeah, whenever I go into the stack it takes me a while to remember things.
    Some factors to consider:

    1. Performance (I mentioned about that before).
    I completely understand this.
    2. "Circular changes" canceling modifications:

    assertEquals("A", o.getP());
    o.setP("B"); // modified
    o.setP("A"); // modified? (property change is "canceled", but maybe other properties are also modified)
    I'd be OK with a circular change showing up as modified. They made a
    change. Then they made another change. I'm OK with that. (I'm
    thinking of data entry in the UI here.)
    3. "Uncommittable changes". An example - objects with modified to-many relationships are treated as modified, but during commit their DB records are not updated.
    I'm not sure this is a big issue. The object graph has indeed been
    modified in this case. Yes, it would be more ideal if the parent of
    the to-many didn't show up when you called modifiedObjects(), but for
    the boolean hasChanges(), it would work out fine.
    So I am leaning towards either a method like ObjectContext.hasCommittabledChanges() that would do deeper analysis of the object graph, or maybe a more involved changeset API that I started to draft under cayenne-lifecycle, that would among other things allow the caller to obtain a more nuanced and detailed view of the graph changes (overall changes, changes per object, committable/uncommitable changes etc.)
    Would hasCommittableChanges() really add any other value over
    hasChanges() if hasChanges() was "fixed"? Or perhaps you are worried
    about backward compatibility of the API?
    And there's of course we shouldn't forget ROP....
    Sadly, I often do. :-(
    Andrus
    mrg

    PS. Would has*Changes() fix Embeddables, too? (Although I'm actually
    trying to get rid of the ones in the current project.)

Related Discussions

Discussion Navigation
viewthread | post
Discussion Overview
groupdev @
categoriescayenne
postedMar 11, '11 at 8:23p
activeMar 12, '11 at 1:42p
posts9
users3
websitecayenne.apache.org

People

Translate

site design / logo © 2021 Grokbase