如何使用 JDBI SQL 对象 API 创建一对多关系?
Posted
技术标签:
【中文标题】如何使用 JDBI SQL 对象 API 创建一对多关系?【英文标题】:How to create a one-to-many relationship with JDBI SQL object API? 【发布时间】:2014-08-11 19:00:18 【问题描述】:我正在使用 JDBI 创建一个带有 dropwizard 的简单 REST 应用程序。下一步是集成与另一个具有一对多关系的新资源。直到现在我还想不出如何在我的 DAO 中创建一个方法来检索一个包含另一个表中的对象列表的对象。
POJO 表示会是这样的:
用户 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;
帐户 POJO:
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 users;
public void setUsers(List<User> 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);
但是当该方法具有单个对象作为返回值(Account 而不是 List)时,似乎无法访问超过一行Mapper 类中的结果集。我能找到的唯一接近的解决方案在https://groups.google.com/d/msg/jdbi/4e4EP-gVwEQ/02CRStgYGtgJ 中进行了描述,但该解决方案也只返回一个带有单个对象的 Set,这看起来不是很优雅。 (并且不能被资源类正确使用。)
似乎有一种方法可以在流畅的 API 中使用 Folder2。但我不知道如何将它与 dropwizard 正确集成,我宁愿按照 dropwizard 文档中的建议坚持使用 JDBI 的 SQL 对象 API。
真的没有办法在 JDBI 中使用 SQL 对象 API 获得一对多的映射吗?这是一个数据库的基本用例,我认为我一定遗漏了一些东西。
非常感谢所有帮助, 蒂尔曼
【问题讨论】:
【参考方案1】:在 JDBI v3 中,您可以使用@UseRowReducer 来实现这一点。在连接结果的每一行上调用行缩减器,您可以将其“累积”到单个对象中。在您的情况下,一个简单的实现如下所示:
public class AccountUserReducer implements LinkedHashMapRowReducer<Integer, Account>
@Override
public void accumulate(final Map<Integer, Account> map, final RowView rowView)
final Account account = map.computeIfAbsent(rowView.getColumn("a_id", Integer.class),
id -> rowView.getRow(Account.class));
if (rowView.getColumn("u_id", Integer.class) != null)
account.addUser(rowView.getRow(User.class));
您现在可以将此 reducer 应用于返回连接的查询:
@RegisterBeanMapper(value = Account.class, prefix = "a")
@RegisterBeanMapper(value = User.class, prefix = "u")
@SqlQuery("SELECT a.id a_id, a.name a_name, u.id u_id, u.name u_name FROM " +
"Account a LEFT JOIN User u ON u.accountId = a.id WHERE " +
"a.id = :id")
@UseRowReducer(AccountUserReducer.class)
Account getAccount(@Bind("id") int id);
请注意,您的 User
和 Account
行/bean 映射器可以保持不变;他们只知道如何分别映射用户和帐户表的单个行。您的Account
类将需要一个方法addUser()
,每次调用行归约器时都会调用该方法。
【讨论】:
我正在努力实现这一点,但我只在结果中看到了 id 字段。如果我深入研究实际的 rowView 对象,我可以很容易地找到所有字段,但是如何让我的 addUser 函数可以访问它们?现在,唯一同时映射到 User 和 Account 的字段是 id。 您确定所有 DB 列名都与 POJO 中的属性名匹配吗?如果没有,@RegisterBeanMapper
可能不适合您;您还可以使用常规 @RegisterRowMapper
并为您的 POJO 实现自己的行映射器(这就是我所做的)
啊..所以我的用户表中有一个列 account_id,它是一个 FK 值。在我将它作为 u.account_id u_account_id 添加到选择列表之前,它没有在 addUser() 中显示值。我希望此信息对其他人有所帮助!
很高兴您解决了问题!
@UserRowReducer 方法的一个警告是它不允许对 ResultSet 进行流式处理,因此如果您要处理大量记录,这对您不起作用。
【参考方案2】:
有一个旧的 google 群组帖子,其中 Brian McAllistair(JDBI 的作者之一)通过将每个连接的行映射到一个中间对象,然后将这些行折叠到目标对象中来做到这一点。
See the discussion here。 There's test code here.
就个人而言,这似乎有点不满意,因为这意味着为临时结构编写额外的 DBO 对象和映射器。我仍然认为这个答案应该包括在内!
【讨论】:
【参考方案3】:好的,经过大量搜索,我看到了两种处理方法:
第一个选项是为每一列检索一个对象并将其合并到资源处的 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 似乎为具有非集合返回类型的SELECT
查询强加了LIMIT 1
。如果将 DAO 声明为抽象类,则可以将具体方法放入 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 支持抽象操作。
【讨论】:
您可以将第一个选项移至 DAO 类,只需将其抽象化即可。看到这个答案:***.com/a/26831146/846644 这是一个非常好的映射器技巧!我为 DAO 方法上的列表/非列表返回类型添加了注释和解决方法,因为它让我措手不及。 这里值得注意的是,第二个选项可能根本不是线程安全的(由于父 Account 对象),除非您知道 JDBI 每个都使用此 ResultSetMapper 的新实例它执行查询的时间,我真的不知道,但仍然认为依赖于一个脆弱的假设。 现在在 JDBI v3 中有一流的支持来实现这一点。看我的回答***.com/a/52586791/1314028【参考方案4】:我有一个小型库,对于维护一对多和一对一的关系非常有用。 它还为默认映射器提供了更多功能。
https://github.com/Manikandan-K/jdbi-folder
【讨论】:
以上是关于如何使用 JDBI SQL 对象 API 创建一对多关系?的主要内容,如果未能解决你的问题,请参考以下文章
将 JDBI 与不支持预准备语句的 JDBC 驱动程序一起使用
Spring Boot 2.1.4 + JDBI + HikariCP + PostgreSQL 发生错误后连接未释放到池