Jdbi: Using "non-sqlobject" DAOs in transactions

Created on 21 Feb 2018  ·  5Comments  ·  Source: jdbi/jdbi

When using sqlobject-annotations to create DAOs it is possible to use multiple DAOs in a transaction by calling handle.attach(DAO.class), but if i want to write my DAOs without using annotated interfaces I cannot find a way to use multiple DAOs in one transaction, see example below.

In the example h.attach(ProductDAOAnnotated.class); works, but h.attach(ProductDAO.class); throws an NoSuchExtensionException exception.

Is there any way multiple "non-sqlobject" DAOs in a transaction or am I forced to use annotated interfaces in this case?

public class ProductService {

    private final Jdbi jdbi;

    public ProductService(Jdbi jdbi) {
        this.jdbi = jdbi;
    }

    public Product create(Product product) {
        return jdbi.inTransaction(h -> {

            // This works
            ProductDAOAnnotated productDAOAnnotated = h.attach(ProductDAOAnnotated.class);

            // This throws NoSuchExtensionException("Extension not found: " + extensionType)
            ProductDAO productDAO = h.attach(ProductDAO.class);

            // Some code here
            return null;
        });
    }

}

public interface ProductDAOAnnotated {
    @SqlUpdate("INSERT INTO product(name) VALUES(:name)")
    @RegisterRowMapper(Product.Mapper.class)
    @GetGeneratedKeys
    Product insert(@BindBean Product product);
}

public class ProductDAO {

    private final Jdbi jdbi;

    public ProductDAO(Jdbi jdbi) {
        this.jdbi = jdbi;
    }

    public Product insert(Product product) {
        return jdbi.withHandle(h ->
                h.createUpdate("INSERT INTO product(name) VALUES(:name)")
                        .bindBean(product)
                        .executeAndReturnGeneratedKeys()
                        .map(new Product.Mapper())
                        .findOnly()
        );
    }
}
question

All 5 comments

When you say attach(someclass) you are relying on Jdbi to create the implementation for you based on annotations.

That said, there is nothing to prevent you from simply reusing the open handle in ProductDAO:

public class ProductDAO {

    private final Handle handle;

    public ProductDAO(Handle handle) {
        this.handle = handle;
    }

    public Product insert(Product product) {
        return handle.createUpdate("INSERT INTO product(name) VALUES(:name)")
                        .bindBean(product)
                        .executeAndReturnGeneratedKeys()
                        .map(new Product.Mapper())
                        .findOnly();
    }
}

thus:

new ProductDao(h).insert(product);

One more thing:

In your original code example you are calling jdbi.inTransaction() in ProductService.create(), and jdbi.withHandle() in ProductDAO.insert(). Both of those calls would create separate, independent handles on distinct JDBC connections. So anything you did in the insert() method would not be in the create() method's transaction.

Ah yes, of course.

I still find it a bit annoying that I need to define the same function (insert) in two different ways, one for use in transactions (taking a handle) and one for "standalone" use.

In my case I'm writing a Spring Boot application and ProductDAO is a bean. It would be nice to reuse DAOs in a way that says "Take these DAOs and use them in a transaction" like using @Transactional in Spring normally would do, but making it more explicit like jdbi.inTransaction(...) does.

Looking through the open issues I guess that is what #987 and #983 are about too. If I understand it correctly I would get the behaviour I want by using @Transactional if #987 is merged?

Thanks for the answers.

You do not need to define the same DAO method twice to get fine grained control over transactions.

// invoke doStuff() outside of a transaction
jdbi.withExtension(MyDao.class, dao -> dao.doStuff());

jdbi.inTransaction(handle -> {
  MyDao dao = handle.attach(MyDao.class);
  // Invoke doStuff() as part of a transaction
  dao.doStuff();
});

So while it is possible to use @Transaction as a convenience, there are other options that give you finer control.

You may also be interested in the Transactional interface, which your SQL Object classes can extend. This allows you to control the transaction as part of the SQL Object itself:

public interface MyDao extends Transactional {
  @SqlUpdate("...")
  void doStuff();
}

jdbi.useExtension(MyDao.class, dao -> {
  dao.begin(); // begin transaction
  dao.doStuff();
  dao.commit();
}

Yes, by using interfaces I get the fine grained control. However if I want to write my DAOs not using SQL Objects it seems that I have to do as you say and pass in the handle as an argument.

In my case I had multiple DAOs written without SQL Objects used as Spring Beans. That means I can use them where I need by autowiring the wanted DAO, but when I need to call two (or more) separate DAOs in the same transaction I'm out of luck with JDBI as it is it seems. I would then have to implement a version of the DAO that takes in the handle as you mentioned. If @Transactional (the one from Spring) where supported I would be able to reuse my DAOs without having any special code for transactional use only.

I've changed to using SQL Object annotated interfaces for now.

Thanks for the answers!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

johanneszink picture johanneszink  ·  4Comments

jimmyhmiller picture jimmyhmiller  ·  6Comments

Shujito picture Shujito  ·  5Comments

anjeyy picture anjeyy  ·  3Comments

buremba picture buremba  ·  5Comments