web-dev-qa-db-ja.com

JDBI SQLオブジェクトAPIで1対多の関係を作成するにはどうすればよいですか?

私は、JDBIを使用してdropwizardでシンプルなRESTアプリケーションを作成しています。次のステップは、他のリソースと1対多の関係を持つ新しいリソースを統合することです。これまではできませんでした。 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);

}

ただし、メソッドに戻り値として単一のオブジェクトがある場合AccountではなくList <Account>)MapperクラスのresultSetの複数行にアクセスする方法はないようです。私が見つけることができる接近する唯一の解決策は https://groups.google.com/d/msg/jdbi/4e4EP-gVwEQ/02CRStgYGtgJ で説明されていますが、それもまた、非常にエレガントに見えない単一のオブジェクト。 (そして、リソースクラスでは適切に使用できません。)

Fluent APIでFolder2を使用する方法があるようです。しかし、私はそれをdropwizardと適切に統合する方法がわからないので、dropwizardのドキュメントで推奨されているように、JDBIのSQLオブジェクトAPIに固執します。

JDBIでSQLオブジェクトAPIを使用して1対多のマッピングを取得する方法は本当にありませんか?これはデータベースの基本的な使用例であり、何かが欠けていると思います。

すべての助けは大歓迎です、
ティルマン

28
Tilman

わかりました。たくさん検索した結果、これに対処する方法が2つあります。

最初のオプションは、各列のオブジェクトを取得し、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();
}
_

これは小規模な結合操作では実行可能ですが、これはデータベースが行うことになっているものであるため、私にはあまりエレガントではないようです。ただし、アプリケーションがかなり小さく、大量のマッパーコードを記述したくなかったため、この方法を採用することにしました。

2番目のオプションは、結合クエリの結果を取得して次のようにオブジェクトにマッピングするマッパーを作成することです。

_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に配置できるため、1つのオプションは、次のようにpublic/protectedメソッドのペアでロジックをラップすることです。

_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);
    }
}
_

リレーショナルデータの操作には通常多くの結合があるため、これはまだ少し扱いに​​くく、低レベルであるように見えます。 SQLオブジェクトAPIを使用して、より良い方法を確認するか、JDBIでこのための抽象操作をサポートしたいと思います。

30
Tilman

私には、1対多および1対1の関係を維持するのに非常に役立つ小さなライブラリがあります。また、デフォルトのマッパーにより多くの機能を提供します。

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

4
Manikandan

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));
        }
    }
}
_

これで、このリデューサーを結合を返すクエリに適用できます。

_@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()が必要です。

4
spinlok

Brian McAllistair(JDBIの作成者の1人)が、結合された各行を中間オブジェクトにマッピングし、行をターゲットオブジェクトに折りたたむことによってこれを行う古いgoogleグループの投稿があります。

こちらの説明を参照してくださいここにテストコードがあります

個人的には、中間構造用の追加のDBOオブジェクトとマッパーを作成することを意味するため、これは少し満足できないようです。それでも、この回答は完全にするために含める必要があると思います!

1
Matthew