Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Lifecycle annotations: @PreUpdate and @PreInsert not called on save() #15

Open
trotzig opened this issue Dec 27, 2011 · 0 comments
Open

Comments

@trotzig
Copy link

trotzig commented Dec 27, 2011

We often want to do stuff before an entity is stored in the database. A common case is that we want to keep track of the timestamp when an entity was last updated. For this, we have previously been overriding the insert() method:

        @Override
    public void insert() {
                 lastUpdated = new Date();
        super.insert();
    }

This is usually enough for our purposes, even though it's not pretty. One thing though that bothers us though is that we're never 100% sure that the method will actually be called on insert. If we call save() instead of insert(), our code is never executed. Also, if we make a batch insert through Model.insert(), our code is never executed.

We'd been thinking about adding annotations as a way of doing this instead of overriding. Then we came across the Lifecycle support which looked exactly like what we wanted. But it doesn't behave the way we though it would. Most importantly: why is there a need for a @presave annotation? save() is a handy way of inserting or updating an entity without caring whether it has been inserted before. But it should behave as if you would either call insert() or update(). With the current implementation, we have to tell developers not to use save() for the side effects they may cause.

Below is a test case demonstrating what's wrong (same test case as for the other lifecycle bug I added). For this issue, look at the preUpdateAndPreInsertShouldBeCalledOnSave() method.

package siena.lifecycle.test;

import java.util.Date;

import junit.framework.Assert;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import siena.Generator;
import siena.Id;
import siena.Model;
import siena.PersistenceManager;
import siena.PersistenceManagerFactory;
import siena.core.PersistenceManagerLifeCycleWrapper;
import siena.core.lifecycle.PreInsert;
import siena.core.lifecycle.PreUpdate;
import siena.gae.GaePersistenceManager;

import com.google.appengine.tools.development.testing.LocalDatastoreServiceTestConfig;
import com.google.appengine.tools.development.testing.LocalServiceTestHelper;
import com.google.appengine.tools.development.testing.LocalTaskQueueTestConfig;

public class LifeCycleTest {

    /**
     * A simple model class with lifecycle annotations
     * 
     * @author henper
     *
     */
    public static class LifeCycleEntity extends Model {

        @Id(Generator.AUTO_INCREMENT)
        public Long id;
        public Date created;
        public Date updated;

        public @PreInsert void onInsert() {
            created = new Date();
        }

        public @PreUpdate @PreInsert void onUpdate() {
            updated = new Date();
        }
    }


    @Test
    public void preInsertShouldWorkOnBatchInsert() {
        // If you are storing multiple entities at once, a batch insert is handy:
        LifeCycleEntity one = new LifeCycleEntity();
        LifeCycleEntity two = new LifeCycleEntity();
        Model.batch(LifeCycleEntity.class).insert(one, two);

        // It is expected that the @PreInsert is called once for each entity, before inserting them:
        Assert.assertNotNull("The \"created\" date was not updated for entity \"one\"", one.created);
        Assert.assertNotNull("The \"created\" date was not updated for entity \"two\"", two.created);
    }


    @Test
    public void preUpdateAndPreInsertShouldBeCalledOnSave() {
        LifeCycleEntity one = new LifeCycleEntity();
        one.save();
        Assert.assertNotNull("The \"created\" date was not updated on save()", one.created);
        Assert.assertNotNull("The \"updated\" date was not updated on save()", one.updated);
    }

    @Test
    public void preInsertShouldBeCalledOnInsert() {
        LifeCycleEntity one = new LifeCycleEntity();
        one.insert();
        Assert.assertNotNull("The \"created\" date was not updated on insert()", one.created);
        Assert.assertNotNull("The \"updated\" date was not updated on insert()", one.updated);
    }

    @Test
    public void preUpdateShouldBeCalledOnUpdate() {
        LifeCycleEntity one = new LifeCycleEntity();
        one.insert();
        one.update();
        Assert.assertNotNull("The \"created\" date was not updated on update()", one.created);
        Assert.assertNotNull("The \"updated\" date was not updated on update()", one.updated);
    }


    @Before
    public void setUp() throws Exception {
        helper.setUp();
    }

    @After
    public void tearDown() throws Exception {
        helper.tearDown();
    }


    @Before
    public void enableLifeCycleSupport() {
        PersistenceManager pm = new GaePersistenceManager();
        pm = new PersistenceManagerLifeCycleWrapper(pm);
        pm.init(null);
        PersistenceManagerFactory.install(pm, LifeCycleEntity.class);
    }
    private final LocalServiceTestHelper helper = new LocalServiceTestHelper(new LocalDatastoreServiceTestConfig(), new LocalTaskQueueTestConfig());
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant