Search code examples
javadropwizardjdbi

JDBI getting argument class in ResultSetMapper


I am using Dropwizard with JDBI. I have a typical dao for user data:

public interface UserDao
{
  @SqlQuery("select * from users where role = :id")
  @Mapper(UserMapper.class)
  String findNameById(@BindBean Role role);
}

The user itself has an attribute with a Role type:

class User
{
    private Role role;

    /* the rest: other attributes, getters, setters, etc. */
}

Role is contained in another table called roles. Now, I need to map Role in the mapper, but I do not want to change the SELECT ... statement to add the JOIN roles ... part. We all know how joins affect queries and in the long run I'd like to avoid any joins if possible.

I know, that ResultSetMapper interface has a map() method, which gets a StatementContext passed to it. That context has a getBinding() method, which returns a Binding class with all the data I need:

named = {HashMap$Node@1230} size = 3
  0 = {HashMap$Node@1234} "id" -> "1"
  1 = {HashMap$Node@1235} "name" -> "TestRole"
  2 = {HashMap$Node@1236} "class" -> "class com.example.Role"

But that class com.example.Role is not an instance of Role, it's an instance of Argument and I can't work with it.

So, is there a way to get that Role argument and I just don't see it or do I have to instantiate it (again...) from the binding arguments (obviously they are there as debugger shows)?


Solution

  • I finally solved it by using a custom binder. First, I modified UserDao to use @BindRole instead of @BindBean.

    Next, I had to create the binder for the role. Here the role is bound manually on separate values:

    @BindingAnnotation(BindRole.RoleBinderFactory.class)
    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.PARAMETER})
    public @interface BindRole
    {
        public static class RoleBinderFactory implements BinderFactory
        {
            public Binder build(Annotation annotation)
            {
                return new Binder<BindRole, User>()
                {
                    public void bind(SQLStatement q, BindRole bind, Role role)
                    {
                        q.bind("id", role.getId());
                        q.bind("name", role.getName());
                        q.define("role", role);
                    }
                };
            }
        }
    }
    

    Notice the define() method, it is responsible for setting attributes in StatementContext, so don't overlook it.

    Next, in the mapper I just have to get the Role with getArgument():

    Role role = new Role();
    role.setId(1);
    role.setName("TestRole");
    
    Role r = (Role) statementContext.getAttribute("role");
    boolean equals = e.equals(role);
    

    In the debugger equals is shown as true, so problem solved. Woohoo.