Best practices for transaction handling

1,345 views
Skip to first unread message

alec...@gmail.com

unread,
Sep 26, 2013, 12:23:32 PM9/26/13
to jd...@googlegroups.com
Hello,

JDBI provides several ways for handling transactions, some of which I listed below. Is there a "best practices" guidance for choosing one over the other? Are there any functional differences between them?

1. Make DAO extend Transactional<DAO> and wrap transaction in DAO.begin()/DAO.commit()-rollback()
2. Make DAO extend Transactional<DAO> and execute transaction in Transactional.inTransaction()
3. Use IDBI.begin()/IDBI.commit()-rollback() 
4. Create a handle with IDBI.open() and wrap transaction in Handle.begin()/Handle.commit()-rollback()
5. Create a handle with IDBI.open() and execute transaction in Handle.inTransaction()

I started using the first approach and ran into problems with deadlocking because I created dao1 and dao2 using onDemand() call and then used dao2 between dao1.begin() and dao1.commit() as follows:

dao1 = dbi.onDemand(DAO1.class);
dao2 = dbi.onDemand(DAO2.class);
dao1.begin();
dao1.uploadLogo(logo);
dao2.updateLogoId(logo.id); // ERROR: this causes deadlock

I think the right way to do this is somehow attach dao2 to dao1's handle after dao1.begin(), but I didn't know how to do this with Transactional objects, so I ditched this approach in favor of #3 as follows:

dbi.inTransaction(new VoidTransactionCallback()
            {
                @Override
                protected void execute(Handle handle, TransactionStatus status)
                        throws Exception
                {
          DAO1 dao1 = handle.attach(DAO1.class);
          DAO2 dao2 = handle.attach(DAO2.class);
          dao1.uploadLogo(logo);
dao2.updateLogoId(logo.id);
                }
            }
)

Could you also explain if it's safe to call methods on objects created via dbi.onDemand() from inside TransactionCallback?

Thanks,

Alec
 


Steven Schlansker

unread,
Sep 26, 2013, 5:20:46 PM9/26/13
to jd...@googlegroups.com

On Sep 26, 2013, at 9:23 AM, alec...@gmail.com wrote:

> Hello,
>
> JDBI provides several ways for handling transactions, some of which I listed below. Is there a "best practices" guidance for choosing one over the other? Are there any functional differences between them?

I believe all of the approaches are equivalent, assuming that you correctly translate the logic. (e.g. use try/finally where appropriate)

>
> 1. Make DAO extend Transactional<DAO> and wrap transaction in DAO.begin()/DAO.commit()-rollback()
> 2. Make DAO extend Transactional<DAO> and execute transaction in Transactional.inTransaction()
> 3. Use IDBI.begin()/IDBI.commit()-rollback()
> 4. Create a handle with IDBI.open() and wrap transaction in Handle.begin()/Handle.commit()-rollback()
> 5. Create a handle with IDBI.open() and execute transaction in Handle.inTransaction()
>

I like the "inTransaction" style above begin / commit / rollback since it is hard to do it wrong, and I prefer to use the Transactional<DAO> for the "simple" use case (where there is only one SqlObject DAO) and the Handle.inTransaction method for the "more complicated" use case where there may be non-SqlObject code mixed in or manual transaction control is helpful.

> I started using the first approach and ran into problems with deadlocking because I created dao1 and dao2 using onDemand() call and then used dao2 between dao1.begin() and dao1.commit() as follows:
>
> dao1 = dbi.onDemand(DAO1.class);
> dao2 = dbi.onDemand(DAO2.class);
> dao1.begin();
> dao1.uploadLogo(logo);
> dao2.updateLogoId(logo.id); // ERROR: this causes deadlock
>
> I think the right way to do this is somehow attach dao2 to dao1's handle after dao1.begin(), but I didn't know how to do this with Transactional objects, so I ditched this approach in favor of #3 as follows:
>
> dbi.inTransaction(new VoidTransactionCallback()
> {
> @Override
> protected void execute(Handle handle, TransactionStatus status)
> throws Exception
> {
> DAO1 dao1 = handle.attach(DAO1.class);
> DAO2 dao2 = handle.attach(DAO2.class);
> dao1.uploadLogo(logo);
> dao2.updateLogoId(logo.id);
> }
> }
> )

There's at least two other ways: the (now deprecated) Transmogrifier mixin and the (now preferred) @CreateSqlObject annotation, which may be clearer? But I think your code as written is OK.

>
> Could you also explain if it's safe to call methods on objects created via dbi.onDemand() from inside TransactionCallback?
>

I would not do this. Even if it possibly does work (I believe the SqlObject code does some ThreadLocal trickery under the hood, but haven't read the code in a while) it's a confusing way to write code (in that it does not express intent well) and is somewhat brittle on internal JDBI magic.

Be clear in your intent and make sure the two DAOs are attached to the same thing. Then it should all just work.

Hope that helps.

Reply all
Reply to author
Forward
0 new messages