Подтвердить что ты не робот

Как создать отношения "один ко многим" с API-интерфейсом JDBI SQL?

Я создаю простое приложение REST с dropwizard с помощью JDBI. Следующим шагом будет интеграция нового ресурса, который имеет отношения "один ко многим" с другим. До сих пор я не мог понять, как создать метод в моем DAO, который извлекает один объект, содержащий список объектов из другой таблицы.

Представления 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);

}

Но когда метод имеет один объект как возвращаемое значение (Учетная запись вместо List <Account> ), похоже, нет способа получить доступ к более чем одной строке набора результатов в классе Mapper. Единственное решение, которое можно найти, описано в https://groups.google.com/d/msg/jdbi/4e4EP-gVwEQ/02CRStgYGtgJ, но оно также возвращает только набор с одним объектом, который не кажется очень элегантным. (И не может быть правильно использован классами resouce.)

Кажется, что используется метод Folder2 в свободном API. Но я не знаю, как правильно интегрировать с dropwizard, и я предпочел бы использовать API-интерфейс JDBI SQL, как рекомендовано в документации dropwizard.

Нет ли способа получить сопоставление "один ко многим", используя API-интерфейс SQL в JDBI? Это такой базовый вариант использования базы данных, что, я думаю, мне что-то не хватает.

Вся помощь очень ценится,
  Тильман

4b9b3361

Ответ 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.

Ответ 2

У меня есть небольшая библиотека, которая будет очень полезна для поддержания отношений от одного до многих и от одного к одному. Он также предоставляет больше возможностей для карт-макетов по умолчанию.

https://github.com/Manikandan-K/jdbi-folder

Ответ 3

Там есть старая публикация групп google, где Брайан Макаллистер (один из авторов JDBI) делает это, сопоставляя каждую связанную строку с промежуточным объектом, а затем складывая строки в целевой объект.

См. обсуждение здесь. Здесь есть тестовый код.

Лично это кажется немного неудовлетворительным, поскольку это означает запись дополнительного объекта DBO и mapper для временной структуры. Тем не менее, я думаю, что этот ответ должен быть включен для полноты!