#はじめに
Java言語初学者ですが、SpringBootフレームワークを使用してSQLを操作する際に、
1対多のテーブルを結合した結果を取得する方法でハマったため、
1対1のテーブル結合の方法と、1対多のテーブル結合の方法を比較したメモとして残します。
#環境
OS | version | framework | 使用環境 |
---|---|---|---|
windows10 | Java11 | spring boot | Spring Tool Suite 4 |
・Java11でSpring Data JDBCを使用できる状態 | |||
・動作するデータベース用意済み | |||
・1対多の関係でテーブルを2つ作成済み | |||
・テーブルにデータを格納済み |
※今回の説明では「clabs」、「members」で記載します。
※また今回はテーブル結合方法の説明の為必要最低限の記載だけします。(importなどは省略)
#1対1のテーブルを結合して取得する方法
1対1のテーブルを結合してデータを取得する際は以下の方法を使用します。
※1対1のテーブル説明の為、1つのクラブには1人しか入れないこととします。
####Domainクラス
データベースに登録しているカラムをフィールド変数として記載します。
Member.javaにはテーブル結合に必要なフィールド変数を追加しています。
public class clab {
/** ID */
private Integer id;
/** クラブ名 */
private String name;
//以下にGetter,Setter,ToStringなどの記載
...
}
public class member {
/** ID */
private Integer id;
/** メンバー名 */
private String name;
/** クラブID */
private Integer clabId;
/** クラブ名 */
private String clabName;
//以下にGetter,Setter,ToStringなどの記載
...
}
####Repositoryクラス
RowMapperを使用します。
ざっくり説明するとSQL実行時にDBから検索してきたResultSetオブジェクトをRowMapperに渡す動作をします。(1行分のデータを取得してくる)
@Repository
public class ClabRepository {
//データベース操作を行うための変数宣言
@Autowired
private NamedParameterJdbcTemplate template;
//ResultSetオブジェクトに格納された1行分のデータをMember型の変数にセットしてreturnする
private static final RowMapper<Member> MEMBER_ROWMAPPER = (resultSet,i) -> {
Member member = new Member();
member.setId(resultSet.getInt("m_id"))
member.setString(resultSet.getString("m_name"));
member.getClabId(resultSet.getInt("m_clab_id"));
member.getClabName(resultSet.getString("c_name"));
return member;
}
//SQLを実行して結果をreturnする
public List<Member> findAllMemberAndClab() {
String sql = "SELECT m.id as m_id, m.name as m_name, m.clab_id as m_clab_id, c.name as c_name FROM members as m LEFT OUTER JOIN clabs as c ON m.clab_id = c.id;";
List<Member> memberList = template.query(sql, MEMBER_ROWMAPPER);
return memberList;
}
}
Repositoryクラスに記載したコードの流れとしては、SQLを実行したときに、フレームワークから検索してきたResultSetオブジェクトをRowMapperに渡す。
裏側で1行分のデータをResultSetオブジェクトに格納して、それらをmember変数に格納してmemberList変数に格納していく。
SQLで該当するデータをすべてmemberListに格納できたらreturnする。
#1対多のテーブルを結合して取得する方法
1対多のテーブルを結合してデータを取得する際は以下の方法を使用します。
※1対多のテーブル説明の為、1つのクラブには複数人登録できることとします。
####Domainクラス
データベースに登録しているカラムをフィールド変数として記載します。
Clab.javaにはmembersテーブルのデータを格納するためのList型の変数を追加しています。
Member.javaにはテーブル結合に必要なフィールド変数を追加しています。
public class clab {
/** ID */
private Integer id;
/** クラブ名 */
private String name;
/** メンバーリスト */
private List<Member> memberList;
//以下にGetter,Setter,ToStringなどの記載
...
}
public class member {
/** ID */
private Integer id;
/** メンバー名 */
private String name;
/** クラブID */
private Integer clabId;
//以下にGetter,Setter,ToStringなどの記載
...
}
####Repositoryクラス
ResultSetExtractorを使用します。
ざっくり説明するとSQL実行時にDBから検索してきたResultSetオブジェクトを自分でセットする必要があります。(複数行分のデータを取得してくる)
@Repository
public class ClabRepository {
//データベース操作を行うための変数宣言
@Autowired
private NamedParameterJdbcTemplate template;
//ResultSetオブジェクトに格納された複数行分のデータをList<Clab>変数にセットしてreturnする
private static final ResultSetExtractor<List<Clab>> CLAB_MEMBER_RESULTSET = (rs) -> {
//初めにデータを格納するための変数を宣言
List<Clab> clabList = new ArrayList<>();
//メンバーを格納するためのList<Member>変数を宣言(値はNullを格納しておく)
List<Member memberList = null;
//clabsテーブルは結合した際に複数行にわたり同じデータが出力される可能性があるため、前のClabテーブルのIDを保持するための変数を宣言
int beforeIdNum = 0;
//ResultSetオブジェクトに格納された複数のデータをList<Clab>変数に格納していく
while(rs.next()) {
//現在検索しているClabテーブルのIDを格納するための変数を宣言
int nowIdNum = rs.getInt("c_id");
//現在検索しているClabテーブルのIDと前のClabテーブルのIDが違う場合は新たにClabオブジェクトを作成する
if (nowIdNum != beforeIdNum) {
Clab clab = new Clab();
clab.setId(nowIdNum);
clab.setName(rs.getString("c_name"));
//メンバーがいた際にClabオブジェクトのmemberListに格納するため空のArrayListをセットしておく
memberList = new ArrayList<Member>();
clab.setMemberList(memberList);
clabList.add(clab);
}
//ClabにMemberがいない場合はMemberオブジェクトを作成しないようにする
if (rs.getInt("m_id") != 0) {
Member member = new Member();
member.setId(rs.getInt("m_id"));
member.setName(rs.getString("m_name"));
//memberをclabオブジェクト内にセットされているmemberListに直接追加する
memberList.add(member);
}
//現在検索しているClabテーブルのIDを前のClabテーブルのIDを入れるbeforeIdNumに代入する
beforeIdNum = nowIdNum;
}
return clabList;
}
public List<Clab> findAll() {
String sql = "SELECT c.id as c_id, c.name as c_name, m.id as m_id, m.name as m_name FROM clabs as c LEFT OUTER JOIN members as m ON c.id = m.clab_id;";
List<Clab> clabList = template.query(sql, CLAB_MEMBER_RESULTSET);
return clabList;
}
}
Repositoryクラスに記載したコードの流れとしては、SQLを実行したときに、フレームワークから検索してきたResultSetオブジェクトをResultSetExtractorに渡す。
その際にSQL実行した際に検索したすべての行をResultSetオブジェクトに格納するため、
ResultSetオブジェクトの**next()**メソッドを使用して、オブジェクトにデータがある間ループ処理を行うようにする。
clabsテーブルは取得したデータ内容が重複することがあるため同じIDの場合はオブジェクトを生成しないようにする。
ResultSetオブジェクトにデータがなくなった際ループ処理が終了して、memberListをreturnする。
#まとめ
1対多のテーブル結合をRowMapperを使用してClabRepositoryとMemberRepositoryに分けて実施することで再現することができるのですが、SQLを何回も実施しないといけなくなるので、実行時間が長くなってしまいます。そのためテーブルの結合をする際はResultSetExtractorを使用したほうがいいといえます。
#参考文献
以下のGitHubを参考にさせていただきました。
・GitHub