2014-06-20 201 views
22

我使用JDBI创建了一个简单的REST应用程序和dropwizard。下一步是整合一个与另一个具有一对多关系的新资源。直到现在我都无法弄清楚如何在我的DAO中创建一个方法来检索一个包含另一个表中对象列表的对象。如何使用JDBI SQL对象API创建一对多关系?

的POJO的描述是这样的:

public class User { 

    private int id; 
    private String name; 

    public User(int id, String name) { 
     this.id = id; 
     this.name = name; 
    } 

    public int getId() { 
     return id; 
    } 

    public void setId(int id) { 
     this.id = id; 
    } 

    public String getName() { 
     return name; 
    } 

    public void setName(String name) { 
     this.name = name; 
    } 
} 

public class Account { 

    private int id; 
    private String name; 
    private List<User> users; 

    public Account(int id, String name, List<User> users) { 
     this.id = id; 
     this.name = name; 
     this.users = users; 
    } 

    public int getId() { 
     return id; 
    } 

    public void setId(int id) { 
     this.id = id; 
    } 

    public String getName() { 
     return name; 
    } 

    public void setName(String name) { 
     this.name = name; 
    } 

    public List<User> getUsers() { 
     return name; 
    } 

    public void setUsers(List<Users> users) { 
     this.users = users; 
    } 

} 

的DAO应该是这个样子

public interface AccountDAO { 

    @Mapper(AccountMapper.class) 
    @SqlQuery("SELECT Account.id, Account.name, User.name as u_name FROM Account LEFT JOIN User ON User.accountId = Account.id WHERE Account.id = :id") 
    public Account getAccountById(@Bind("id") int id); 

} 

但当方法有一个对象作为返回值(帐户代替列表<帐户>)似乎无法访问Mapper类中resultSet的多行。我能找到的唯一解决方案是在https://groups.google.com/d/msg/jdbi/4e4EP-gVwEQ/02CRStgYGtgJ中描述的,但那个解决方案也只能返回一个Set,其中有一个看起来不太优雅的对象。 (并且不能被资源类正确使用。)

似乎有一种方法在流利的API中使用了Folder2。但我不知道如何正确地将它与dropwizard集成在一起,而且我更愿意按照dropwizard文档中的建议坚持JDBI的SQL对象API。

真的没有办法在JDBI中使用SQL对象API获得一对多映射吗?对于一个数据库来说,这是一个基本的用例,我认为我必须错过一些东西。

所有帮助是极大的赞赏,
   蒂尔曼

回答

27

OK,很多搜索后,我看到了两个办法处理这个:

第一个选项被检索的对象每列并将其合并到资源中的Java代码中(即在代码中进行连接,而不是由数据库完成)。 这将导致类似

@GET 
@Path("/{accountId}") 
public Response getAccount(@PathParam("accountId") Integer accountId) { 
    Account account = accountDao.getAccount(accountId); 
    account.setUsers(userDao.getUsersForAccount(accountId)); 
    return Response.ok(account).build(); 
} 

这是可行的较小的联接操作,但似乎不是很优雅我,因为这是后话了数据库应该做的事。但是,我决定采用这种方式,因为我的应用程序非常小,我不想写很多映射器代码。

第二个选项是写一个映射器,检索​​连接查询的结果,并且将其映射到这样的对象:

public class AccountMapper implements ResultSetMapper<Account> { 

    private Account account; 

    // this mapping method will get called for every row in the result set 
    public Account map(int index, ResultSet rs, StatementContext ctx) throws SQLException { 

     // for the first row of the result set, we create the wrapper object 
     if (index == 0) { 
      account = new Account(rs.getInt("id"), rs.getString("name"), new LinkedList<User>()); 
     } 

     // ...and with every line we add one of the joined users 
     User user = new User(rs.getInt("u_id"), rs.getString("u_name")); 
     if (user.getId() > 0) { 
      account.getUsers().add(user); 
     } 

     return account; 
    } 
} 

的DAO接口然后将有这样的方法:

public interface AccountDAO { 

    @Mapper(AccountMapper.class) 
    @SqlQuery("SELECT Account.id, Account.name, User.id as u_id, User.name as u_name FROM Account LEFT JOIN User ON User.accountId = Account.id WHERE Account.id = :id") 
    public List<Account> getAccountById(@Bind("id") int id); 

} 

注:你的抽象DAO类会悄悄编译,如果你使用非集合返回类型,如public Account getAccountById(...);。但是,即使SQL查询可能找到多行,您的映射器也将仅接收一行结果集,您的映射器将很乐意使用单个用户转换为单个帐户。JDBI似乎强加LIMIT 1SELECT具有非收集返回类型的查询。它有可能把具体的方法在你的DAO,如果你将它声明为一个抽象类,所以一个选项是收官之逻辑与公共/保护方法对,就像这样:

public abstract class AccountDAO { 

    @Mapper(AccountMapper.class) 
    @SqlQuery("SELECT Account.id, Account.name, User.id as u_id, User.name as u_name FROM Account LEFT JOIN User ON User.accountId = Account.id WHERE Account.id = :id") 
    protected abstract List<Account> _getAccountById(@Bind("id") int id); 

    public Account getAccountById(int id) { 
     List<Account> accountList = _getAccountById(id); 
     if (accountList == null || accountList.size() < 1) { 
      // Log it or report error if needed 
      return null; 
     } 
     // The mapper will have given a reference to the same value for every entry in the list 
     return accountList.get(accountList.size() - 1); 
    } 
} 

这似乎仍然是一个对我来说有点繁琐和低级,因为通常有很多联接处理关系数据。我希望看到更好的方法,或者让JDBI支持对SQL对象API的抽象操作。

+1

您可以通过简单地将其抽象为第一个选项移动到DAO类。看到这个答案:http://stackoverflow.com/a/26831146/846644 – Natan

+0

这是一个非常好的mapper技巧!我在DAO方法中添加了一个注释和一个解决列表/非列表返回类型的方法,因为它引起了我的不安。 –

+0

这里值得注意的是,第二个选项可能根本不是线程安全的(由于父母Account对象),除非您知道JDBI每次执行查询时都使用此ResultSetMapper的新实例,我真的不知道,但仍然认为依赖脆弱的假设。 – Hesham

0

有一个老谷歌组张贴其中布赖恩McAllistair(其中一个JDBI作者)通过映射每个接合行的临时对象,然后折叠行插入到目标对象执行此操作。

See the discussion hereThere's test code here

就个人而言,这似乎有点不令人满意,因为它意味着为临时结构编写额外的DBO对象和映射器。我仍然认为这个答案应该包括完整性!