Document @Transaction annotation
See original GitHub issueIt would be great to have some documentation on the @Transaction annotation. I just lost several hours of debugging to find @Transaction handling was behaving exceedingly unintuitively.
My end goal was to hand-write a dynamic query but join an existing wrapping transaction started explicitly by a unit test:
@Before
h.begin()
@After
h.rollback
@Test
userDao.getUserMoreComplicated(...)
public interface MyDAO implements GetHandle {
@SqlQuery("thought this was transactional...")
@Transaction // nope.
public User getUser(@Bind("id") String id);
@Transaction // does not join existing transaction either...
public User getUserMoreComplicated(String arg1, Integer arg2, etc.) {
return withHandle(new HandleCallback<User>() {
public User withHandle(Handle handle) throws Exception
{
return handle.createQuery("my super duper dynamically constructed query").map(User.class).list();
}
}
}
}
What I found was that getUserMoreComplicated
was always committing, while getUser
was apparently correctly rolling back (not committing). Similarly, explicitly using handle.withTransaction
resulted in commit. And leaving them all off naturally resulted in no transactionn and no rollback if no wrapping transaction was explicitly set up.
There are several things wrong here. To start getUser
was not working as I assumed. It turns out that @Transaction
is mutually exclusive with every other annotation. See below in SqlObject:
// these else ifs are all mutually exclusive...
for (final ResolvedMethod method : d.getMemberMethods()) {
final Method raw_method = method.getRawMember();
if (raw_method.isAnnotationPresent(SqlQuery.class)) {
handlers.put(raw_method, new QueryHandler(sqlObjectType, method, ResultReturnThing.forType(method)));
}
else if (raw_method.isAnnotationPresent(SqlUpdate.class)) {
handlers.put(raw_method, new UpdateHandler(sqlObjectType, method));
}
else if (raw_method.isAnnotationPresent(SqlBatch.class)) {
handlers.put(raw_method, new BatchHandler(sqlObjectType, method));
}
else if (raw_method.isAnnotationPresent(SqlCall.class)) {
handlers.put(raw_method, new CallHandler(sqlObjectType, method));
}
else if(raw_method.isAnnotationPresent(CreateSqlObject.class)) {
handlers.put(raw_method, new CreateSqlObjectHandler(raw_method.getReturnType()));
}
else if (method.getName().equals("close") && method.getRawMember().getParameterTypes().length == 0) {
handlers.put(raw_method, new CloseHandler());
}
else if (raw_method.isAnnotationPresent(Transaction.class)) {
handlers.put(raw_method, new PassThroughTransactionHandler(raw_method, raw_method.getAnnotation(Transaction.class)));
}
else if (mixinHandlers.containsKey(raw_method)) {
handlers.put(raw_method, mixinHandlers.get(raw_method));
}
else {
handlers.put(raw_method, new PassThroughHandler(raw_method));
}
}
You can’t have @Transaction @SqlUpdate
or @Transaction @SqlQuery
, it just doesn’t work like Spring transactions. Secondly, this explains why getUserMoreComplicated
was always committing. @Transaction
is the only annotation present on that method, so @Transaction
functionality is enabled, and what this actually does is unilaterally commit:
try {
handle.begin();
returnValue = callback.inTransaction(handle, status);
if (!failed.get()) {
handle.commit();
}
}
Could it be I missed critical docs on @Transaction
? At the very least its scope of functionality seems very counter-intuitive to me, and probably anybody coming from a declarative transaction background. Is the intent really for @Transaction
to not apply to SQL Object methods, and for it to forcefully commit? If so, is there any way at all to write reusable code that joins an existing transaction (e.g. unit tests) or starts a new one on demand (production)?
Issue Analytics
- State:
- Created 10 years ago
- Reactions:3
- Comments:10 (6 by maintainers)
Top GitHub Comments
I have documented it here. http://manikandan-k.github.io/2015/05/10/Transactions_in_jdbi.html See whether it helps.
👍 on documenting this better.