Transaction for POJOs

[亡魂溺海] 提交于 2019-12-08 07:55:41

问题


I'm implementing a method that does something like:

...
try {
   myPojo.setProperty("foo");
   myService.execute(myPojo);
} catch (Exception e) {
   logger.error(e.getMessage(), e);
}
...

If some exception is thrown by my service from this try block on pojo property will have the new value. Is there some way to start a kind of transaction for pojo changes and roll it back if something goes wrong?

Something like:

PojoTransaction pt = startPojoTransaction();
transactionedPojo = pt.handleByTransaction(myPojo);
try {
   transactionedPojo.setProperty("foo");
   myService.execute(transactionedPojo);
   pt.commit;
} catch (Exception e) {
   logger.error(e.getMessage(), e);
}

Or something similar...


回答1:


I toyed around with the idea, this is far from perfect, just a simple proof of concept. There are pitfalls in this implementation:

  • It only tries to call a parameterless constructor of the given source object to create the target-copy, would need some logic to select a correct constructor (or only support Cloneables?)
  • Only copies fields declared in the class, not from superclasses (this problem can be solved walking through the inheritance tree and copying any superclass fields)
  • If the fields are complex types, only the references are copied to target-object, so any changes to them will not be transactional, as both the source and target share the same instance (solvable by recursively creating copies of nested objects and copying their values, requires walking through the entire object-graph, starting from source, and then doing it vice-versa on commit-time)

But, improving from here, I believe it could become very usable. Here's the POC:

import java.lang.reflect.Field;

import org.junit.Assert;
import org.junit.Test;

public class PojoTransactionTest
{
    public static class PojoTransaction<T>
    {
        /**
         * This is the original (unmodified) object
         */
        private T source;

        /**
         * This is the object modified by within the transaction
         */
        private T target;

        /**
         * Creates a new transaction for the given source object
         * @param source    Source object to modify transactionally
         */
        public PojoTransaction(T source)
        {
            try
            {
                this.source = source;
                this.target = (T)source.getClass().newInstance(); //Note: this only supports parameterless constructors

                copyState(source, target);
            }
            catch(Exception e)
            {
                throw new RuntimeException("Failed to create PojoTransaction", e);
            }
        }

        /**
         * Copies state (member fields) from object to another
         * @param from      Object to copy from
         * @param to        Object to copy to
         * @throws IllegalAccessException
         */
        private void copyState(T from, T to) throws IllegalAccessException
        {
            //Copy internal state to target, note that this will NOT copy fields from superclasses
            for(Field f : from.getClass().getDeclaredFields())
            {
                f.setAccessible(true);
                f.set(to, f.get(from));
            }
        }

        /**
         * Returns the transaction target object, this is the one you should modify during transaction
         * @return Target object
         */
        public T getTransactionTarget()
        {
            return target;
        }

        /**
         * Copies the changes from target object back to original object
         */
        public void commit()
        {
            try
            {
                copyState(target, source);
            }
            catch(Exception e)
            {
                throw new RuntimeException("Failed to change state of original object", e);
            }
        }
    }

    public static class TestData
    {
        private String strValue = "TEST";
        private int intValue = 1;
        private float floatValue = 3.1415f;

        public String getStrValue()
        {
            return strValue;
        }

        public void setStrValue(String strValue)
        {
            this.strValue = strValue;
        }

        public int getIntValue()
        {
            return intValue;
        }

        public void setIntValue(int intValue)
        {
            this.intValue = intValue;
        }

        public float getFloatValue()
        {
            return floatValue;
        }

        public void setFloatValue(float floatValue)
        {
            this.floatValue = floatValue;
        }

    }

    @Test
    public void testTransaction()
    {
        //Create some test data
        TestData orig = new TestData();

        //Create transaction for the test data, get the "transaction target"-object from transaction
        PojoTransaction<TestData> tx = new PojoTransaction<TestData>(orig);
        TestData target = tx.getTransactionTarget();
        target.setFloatValue(1.0f);
        target.setIntValue(5);
        target.setStrValue("Another string");

        //Original object is still at the original values
        Assert.assertEquals(1, orig.getIntValue());
        Assert.assertEquals(3.1415f, orig.getFloatValue(), 0.001f);
        Assert.assertEquals("TEST", orig.getStrValue());

        //Commit transaction
        tx.commit();

        //The "orig"-object should now have the changes made to "transaction target"-object
        Assert.assertEquals(5, orig.getIntValue());
        Assert.assertEquals(1.0f, orig.getFloatValue(), 0.001f);
        Assert.assertEquals("Another string", orig.getStrValue());
    }

}



回答2:


Take a look at the Memento Pattern, it includes a Java example.
http://en.wikipedia.org/wiki/Memento_pattern




回答3:


The question is a bit vague, but it sounds like you are wrestling with the basic design pattern for transaction management. You would benefit greatly from the experience that has gone into the production of the pattern used here:

http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/transaction.html

Perhaps Spring Transaction management would suit you well for your project anyway.



来源:https://stackoverflow.com/questions/10732726/transaction-for-pojos

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!