Multiply unit's of work within one jersey call. dropwizard-hibernate

2,775 views
Skip to first unread message

Michael B

unread,
Feb 21, 2013, 7:39:05 AM2/21/13
to dropwiz...@googlegroups.com
By annotating your resource method with @UnitOfWork you generally get what you want one transaction that gets rolled back when something goes wrong or committed as just before the method returns.
What if you need two transactions, is there a 'nice' or preferred way to do this - in combination with AbstractDAO? The API around here seems restrictive.

Kind regards,

Michael

Coda Hale

unread,
Feb 21, 2013, 9:46:06 AM2/21/13
to dropwiz...@googlegroups.com
The Hibernate support was added in 0.6.0, so yes, the API is restrictive. It'll improve with time (and patches).

You'll need to manually manage sessions with SessionFactory if @UnitOfWork doesn't meet your needs. Check the unit of work adapter for how it's structured.
--
You received this message because you are subscribed to the Google Groups "dropwizard-user" group.
To unsubscribe from this group and stop receiving emails from it, send an email to dropwizard-us...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.
 
 


--
Coda Hale
http://codahale.com
Message has been deleted

je...@jerrycarter.org

unread,
May 25, 2013, 1:36:42 AM5/25/13
to dropwiz...@googlegroups.com
I was able to achieve multistep transactions in the following manner:

@Path("/example")
public class ExampleResource {
    private final ExampleDAO exampleDAO;
    private final SessionFactory sessionFactory;

    public ExampleResource(SessionFactory s, ExampleDAO d) {
        this.sessionFactory = s;
        this.exampleDAO = d;
    }

    @UnitOfWork
    @POST
    public void DoComplexProcess() {
        // Do first part of the process
        sessionFactory.getCurrentSession().getTransaction().commit();
        // Do second part of the process
    }
}

As you can see, this commits the first part before launching the second.  I also faced a situation where I wanted to commit an error report after some read-only steps (i.e. SELECT statements).  For that I followed:

@UnitOfWork
@POST
@Timed
public Result DoTrickValidation(Arguments args) {
    IntermediateResult intermediateResult = TestArguments(args);
    if (intermediateResult.isBad()) {
        WriteErrorEventToDatabase();
        sessionFactory.getCurrentSession().getTransaction().commit();
        generateErrorException(Response.Status.BAD_REQUEST, "invalid arguments");
    }

    // Then do the ordinary work.
    return result;
}

This works fairly well for simple cases.  Attacking the general case is more messay and requires taking complete control of transactions as per UnitOfWorkRequestDispatcher.java.

Hope this helps.

-=- Jerry

Tammy Soliman

unread,
Jan 13, 2014, 12:27:35 PM1/13/14
to dropwiz...@googlegroups.com
Hi Jerry,

I tried using your proposed solution but encountered issue in retrieving the auto-generated values (e.g. generated UUID or default value) from the database in the second part of the process using the ID. The first part of the process just persisting the same object.

Can we only retrieve the recently created data in a new session?

Tammy

Jerry Carter

unread,
Jan 13, 2014, 9:41:12 PM1/13/14
to dropwiz...@googlegroups.com
This is a familiar problem.  I require DB triggers for insert operations which then populate certain fields in the object.  This requires the following sequence:

(1) EntityManager persist(entity)
- This indicates the object should be eventually persisted but implementations are not required to do so immediately provided that they cache locally.

(2) EntityManager flush()
- This forces that new or updated managed entities be pushed out to the database.  This ensures that values are generated and triggers run.

(3) EntityManager refresh(entity)
- This reloads the entity from the database, thereby giving you the object with the generated values.

JPA makes this slightly painful since its architecture appears to assume that the database level contains no logic.  This may work for simple cases but falls apart for the more interesting scenarios.  This should work, but as always, test, test, test.

-=- Jerry


Tammy Soliman

unread,
Jan 14, 2014, 10:47:34 AM1/14/14
to dropwiz...@googlegroups.com
Thanks Jerry!

Your suggestion worked!

 sessionFactory.getCurrentSession().flush();    
 sessionFactory.getCurrentSession().refresh(YourObject);

Cheers,
Tammy

Michael B

unread,
Jan 24, 2014, 2:01:11 PM1/24/14
to dropwiz...@googlegroups.com
Sorry for not responding earlier,

As suggested by Jerry we attacked the more general case by Sub-classing AbstractDAO and using UnitOfWorkRequestDispatcher.java

public class AbstractDao<TYPE> extends AbstractDAO<TYPE> {

public interface UnitOfwork {
public void perform();
}
private final SessionFactory sessionFactory;

public AbstractDao(SessionFactory sessionFactory) {
super(sessionFactory);
this.sessionFactory = sessionFactory;
}

public void perform(UnitOfwork unitOfwork) {
final Session session = sessionFactory.openSession();
if(ManagedSessionContext.hasBind(sessionFactory)) {
throw new IllegalStateException("Already in a unit of work!");
}
try {
configureSession(session);
ManagedSessionContext.bind(session);
session.beginTransaction();
try {
unitOfwork.perform();
commitTransaction(session);
} catch (Exception e) {
rollbackTransaction(session);
this.<RuntimeException>rethrow(e);
}
} finally {
session.close();
ManagedSessionContext.unbind(sessionFactory);
}
}


private void configureSession(Session session) {
session.setDefaultReadOnly(false);
session.setCacheMode(CacheMode.NORMAL);
session.setFlushMode(FlushMode.AUTO);
}

private void rollbackTransaction(Session session) {
final Transaction txn = session.getTransaction();
if (txn != null && txn.isActive()) {
txn.rollback();
}
}

private void commitTransaction(Session session) {
final Transaction txn = session.getTransaction();
if (txn != null && txn.isActive()) {
txn.commit();
}
}

@SuppressWarnings("unchecked")
private <E extends Exception> void rethrow(Exception e) throws E {
throw (E) e;
}
}

Usage is roughly like so:

someDao.perform(new UnitOfwork() {
@Override
public void perform() {
petDao.store(pet);
reportDao.store(report);
}
});

You can shorten it in the future using lambda expressions because UnitOfWork is a SAM interface. Also note that the default values for DefaultReadOnly, CacheMode and FlushMode are hardcoded.
I'll leave it as an exercise to the reader to figure out how behavior differs from just calling commit() because if you need multiply units of work you probably know what your doing anyway.

Michael
Reply all
Reply to author
Forward
0 new messages