Webサイトに入力機能を追加する (3)

さて、前回は入力画面で入力されたリクエストパラメータが単体で問題ないかの検証を行いました。

今回は入力された試合情報がすでに登録済みの試合情報と重複していないかの検証を行っていこうと思います。

同一の試合情報が2回入力されるといった場合だけでなく、同じチームが同じ日、あるいは同じ節に2回試合を行うことはありません。

また、現在のJ1リーグは1試合ずつの総当たり戦となっていますので、ホームチームとアウェイチームが同じ組み合わせの試合が2回行われることもありません。

これらを検証するためにはデータベースに登録済みの試合情報を参照して判定を行う必要があります。

データベースからの試合情報の取り出しは以前の回で作ってみましたので、今回も同じように作っていきます。

ところで、試合情報に重複がないかを検索する場合、検索をどこで行うかを考える必要があります。

同一節にあるチームの試合があったかどうかを判定する処理をデータベース検索で行う場合はこんな感じになるかと思います。

resultset = statement.executeQuery("SELECt * FROM matches WHERE section=節番号 AND (home='チーム名' OR away='チーム名')");
if (resultset.next()) {

  // 重複があった

} else {

  // 重複はない

}

同様に同一日にあるチームの試合があったかどうかを判定する処理は以下のようになります。

resultset = statement.executeQuery("SELECt * FROM matches WHERE date='試合開催日' AND (home='チーム名' OR away='チーム名')");
if (resultset.next()) {
  // 重複があった

} else {

  // 重複はない

}

3番目に同じ組み合わせの試合があったかどうかを判定する処理は以下のようになります。

resultset = statement.executeQuery("SELECt * FROM matches WHERE home='ホームチーム名' AND away='アウェイチーム名'");
if (resultset.next()) {
  // 重複があった

} else {

  // 重複はない

}

これらで検出された例外を報告する例外クラスを作っておきます。

public class DuplicatedMatchException extends Exception {
  private Match duplicated_match;
  private Match reference_match;

  public DuplicatedMatchEception(

      String message,

      Match duplicated_match,

      Match reference_match)

  {

    super(message);

    this.duplicated_match = duplicated_match;

    this.reference_match = reference_match;

  }

  public Match getDuplicatedMatch() {

    return duplicated_match;

  }

  public Match getReferenceMatch() {

    return reference_match;

  }

}

前回作成した例外クラスはメッセージを記録するのみでしたが、今回はどの試合情報が重複となっているかを情報として扱えるように重複した試合の試合情報 (duplicated_match) と登録済みの試合情報 (reference_match) を保存するようにしています。

試合の重複検査は入力側というより試合規定に関するものなので、Matchクラスに組み込むほうがよいかと思います。ということでMatchクラスでの実装は以下のような感じになります。

public class Match 
{

  private int section;

  private LocalDate date;

  private String home;

  private String away;

  private int goals_for;

  private int goals_against;


  private static final String JDBC_DRIVER = "com.mysql.cj.jdbc.Driver";

  private static final String DB_URL = "jdbc:mysql://localhost:3306/standings";

  private static final String DB_USER = "sampleUser";

  private static final String DB_PASSWORD = "SU_pass";

  // 中略 - 前回実装した部分を省略します

  public void checkDuplication() throws DuplicatedMatchException, SQLException

  {

    Connection conn = null;

    PreparedStatement pstmt = null;

    ResultSet rset = null;

    try {

      Class.forName(JDBC_DRIVER);

      conn = DriverManager.getConnection(DB_URL, DB_USER, DB_PASSWORD);

      pstmt = conn.prepareStatement("SELECT * FROM matches WHERE section=? AND (home=? OR away=?)");

      pstmt.setInt(1, section);

      pstmt.setString(2, home);

      pstmt.setString(3, home);

      rset = pstmt.executeQuery();

      if (rset.next()) {

        Match ref_match = new Match();

        ref_match.setSection(rset.getInt("section"));

        ref_match.setDate(rset.getDate("date"));

        ref_match.setHome(rset.getString("home"));

        ref_match.setAway(rset.getString("away"));

        ref_match.setGoalsFor(rset.getInt("goals_for"));

        ref_match.setGoalsAgainst(rset.getInt("goals_against"));

        throw new DuplicatedMatchException(

            "同一日にホームチームの試合が登録済みです",

            this, ref_match);

      }

      pstmt.close();

      // 以下、同様に検査を実施する

    } finally {

      try {

        if (pstmt != null) pstmt.close();

        if (conn != null) conn.close();

      } catch (SQLException ex) {

      }

    }

  }

}

ここまで検査を1回分しか書いていませんが、データベース検索の準備と例外発行の準備でかなりの行数を使うために非常に長くなっています。

こういった場合はデータベース検索をメソッドの外に追い出すのがよさそうです。

public Match findMatchBySectionAndTeam(int section, String team) {

  Match match = null;

  Connection conn = null;

  PreparedStatement pstmt = null;

  ResultSet rset = null;

  try {

    Class.forName(JDBC_DRIVER);

    conn = DriverManager.getConnection(DB_URL, DB_USER DB_PASSWORD);

    pstmt = conn.prepareStatement("SELECT * FROM matches WHERE section=? AND (home=? OR away=?)");

    pstmt.setInt(1, section);

    pstmt.setString(2, team);

    pstmt.setString(3, team);

    rset = pstmt.executeQuery();

    if (rset.next()) {

      match = new Match();

      match.setSection(rset.getInt("section"));

      match.setDate(rset.getDate("date"));

      match.setHome(rset.getString("home"));

      match.setAway(rset.getString("away"));

      match.setGoalsFor(rset.getInt("goals_for"));

      match.setGoalsAgainst(rset.getInt("goals_against"));

    }

  } finally {

    try {

      if (pstmt != null) pstmt.close();

      if (conn != null) conn.close();

    } catch (SQLException ex) {

    }

  }

  return match;

}

上は節とチーム名で試合情報を検索するものですが、同様に日付とチーム名で試合情報を検索するfindMatchByDateAndTeamメソッド、ホームとアウェイのチーム名で試合情報を検索するfindMatchByHomeAndAwayメソッドを作ることができます。

これらを使用することで上のcheckDuplationメソッドはかなりシンプルに読みやすくなります。

public void checkDuplication() throws DuplicatedMatchException
{
  Match ref_match;

  if ((ref_match = findMatchBySectionAndTeam(section, home)) != null) {

    throw new DuplicatedMatchException(

        "同じ節にホームチームの試合が登録済みです",

        this, ref_match);

  }

  if ((ref_match = findMatchBySectionAndTeam(section, away)) != null) {

    throw new DuplicatedMatchException(

        "同じ節にアウェイチームの試合が登録済みです",

        this, ref_match);

  }

  if ((ref_match = findMatchByDateAndTeam(date, home)) != null) {

    throw new DuplicatedMatchException(

        "同じ日にホームチームの試合が登録済みです",

        this, ref_match);

  }

  if ((ref_match = findMatchByDateAndTeam(date, away)) != null) {

    throw new DuplicatedMatchException(

        "同じ日にアウェイチームの試合が登録済みです",

        this, ref_match);

  }

  if ((ref_match = findMatchByHomeAndAway(home, away)) != null) {

    throw new DuplicatedMatchException(

        "同じホーム・アウェイの組み合わせの試合が登録済みです",

        this, ref_match);

  }

}

また、このような作り方をすることでデータベースアクセスの詳細を判定処理から切り離すことができます。

さらにデータベースアクセスに関連するメソッドをまとめて別のクラスを用意するプログラミング技法があり、Data Access Object (DAO) パターンと呼ばれています。

ここでもDAOパターンを使おうと思います。

まず、MatchDAOクラスを作成します。

public class MatchDAO {
  private static final String JDBC_DRIVER = "com.mysql.cj.jdbc.Driver";
  private static final String DB_URL = "jdbc:mysql://localhost:3306/standings";

  private static final String DB_USER = "sampleUser";

  private static final String DB_PASSWORD = "SU_pass";

  public Match findMatchBySectionAndTeam(int section, String team) {

    // 実装部分は省略

  }

  public Match findMatchByDateAndTeam(LocalDate date, String team) {

    // 実装部分は省略

  }

  public Match findMatchByHomeAndAway(String home String Away) {

    // 実装部分は省略

  }

}

次にMatchクラスからデータベースアクセスに関連する変数、メソッドを削除して、その代わりにMatchDAOオブジェクトを使用するように変更します。

public class Match
{
  private int section;

  private LocalDate date;

  private String home;

  private String away;

  private int goals_for;

  private int goals_against;


  private MatchDAO match_dao = new MatchDAO();


  // 中略 - 前回実装した部分を省略します

  public void checkDuplication() throws DuplicatedMatchException

  {

  Match ref_match;

  if ((ref_match = match_dao.findMatchBySectionAndTeam(section, home)) != null) {

    throw new DuplicatedMatchException(

        "同じ節にホームチームの試合が登録済みです",

        this, ref_match);

    }

    // 以降の実装部分は省略

  }

}

データベースアクセスが見えなくなったのは良いのですが、MatchDAOオブジェクトを作らなければいけないところがちょっと気持ち悪いです。この気持ち悪さを解消するには依存性の注入という手法が使えるのですが、今回はやめておきます。

さて、ここまでで検証部分は大体できたので、次回は最終確認のところを作っていきたいと思います。


0コメント

  • 1000 / 1000