Ответ 1
Хорошо, после многого поиска я вижу два способа решения этой проблемы:
первая опция предназначена для извлечения объекта для каждого столбца и объединения его в код 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();
}
Это возможно для небольших операций соединения, но для меня это выглядит не очень элегантно, поскольку это то, что должна делать база данных. Тем не менее, я решил пойти по этому пути, поскольку мое приложение довольно мало, и я не хотел писать много кода картографа.
Параметр второй состоит в том, чтобы написать mapper, который извлекает результат запроса соединения и сопоставляет его с объектом следующим образом:
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 1
на SELECT
запросы, у которых есть тип возврата, не относящийся к коллекции. В вашем 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, поддерживающий абстрактную операцию для этого с API-интерфейсом SQL.