Javaデータベース接続(JDBC)入門

JDBCとは

JDBC(Java Database Connection)は、Javaアプリケーションからデータベースにアクセスするための標準APIです。このAPIを使用することで、さまざまなデータベース製品に対して統一された方法で接続や操作を行うことができます。

本稿では、JDBCの基本的な使用方法について、コード例を交えながら解説します。

接続前の準備

JDBCを使用するには、使用するデータベースのJDBCドライバーをクラスパスに含める必要があります。MySQLの場合は、mysql-connector-java.jarなどのJDBCドライバーをプロジェクトに追加してください。

データベース接続の基本手順

JDBCによるデータベース操作は、以下の4つのステップで行います。

  1. JDBCドライバーのロード(登録)
  2. データベースへの接続確立
  3. SQL文の実行
  4. リソースの解放

方法1:Statementインターフェースの使用

Statementインターフェースは、基本的なSQL文の実行に使用します。以下に、学生情報をデータベースに挿入する例を示します。

public class DatabaseSample {
    
    public static void main(String[] args) {
        Connection dbConnection = null;
        Statement sqlExecutor = null;
        
        try {
            // ステップ1:JDBCドライバーの登録
            Class.forName("com.mysql.cj.jdbc.Driver");
            
            // ステップ2:データベースへの接続
            String jdbcUrl = "jdbc:mysql://localhost:3306/school_db";
            String userName = "admin";
            String password = "password123";
            dbConnection = DriverManager.getConnection(jdbcUrl, userName, password);
            
            // ステップ3:SQL実行オブジェクトの取得
            sqlExecutor = dbConnection.createStatement();
            
            // ステップ4:SQL文の構築と実行
            String insertSql = "INSERT INTO students(student_id, name, gender, class_id) "
                             + "VALUES(105, '田中太郎', '男性', 2)";
            int affectedRows = sqlExecutor.executeUpdate(insertSql);
            
            System.out.println("挿入件数:" + affectedRows);
            
        } catch (Exception error) {
            error.printStackTrace();
        } finally {
            // ステップ5:リソースの解放
            closeQuietly(sqlExecutor);
            closeQuietly(dbConnection);
        }
    }
    
    private static void closeQuietly(AutoCloseable closeable) {
        if (closeable != null) {
            try {
                closeable.close();
            } catch (Exception ignored) {
                // クローズ時の例外はentionally無視
            }
        }
    }
}

この方法では、SQL文を直接文字列として記述するため、シンプルで直感的な実装が可能です。ただし、セキュリティ上の問題(SQLインジェクション攻撃への脆弱性)や型の安全管理の観点から、実務では次に説明するPreparedStatementの使用が推奨されます。

方法2:PreparedStatementインターフェースの使用(推奨)

PreparedStatementは、プレースホルダー(?)を使用したプリコンパイルSQLを実行するためのインターフェースです。SQLインジェクションの防止やパフォーマンスの向上に効果的です。

public class StudentDao {
    
    private static final String DRIVER_CLASS = "com.mysql.cj.jdbc.Driver";
    private static final String CONNECTION_URL = "jdbc:mysql://localhost:3306/school_db";
    private static final String DB_USER = "admin";
    private static final String DB_PASSWORD = "password123";
    
    /**
     * 学生情報をデータベースに追加する
     * @param student 追加する学生オブジェクト
     * @return 追加が成功した場合はtrue
     */
    public boolean registerStudent(Student student) {
        String sql = "INSERT INTO students(student_id, name, gender, class_id) VALUES(?, ?, ?, ?)";
        
        try (Connection conn = establishConnection();
             PreparedStatement pstmt = conn.prepareStatement(sql)) {
            
            // パラメータの設定(1から始まるインデックス)
            pstmt.setInt(1, student.getStudentId());
            pstmt.setString(2, student.getName());
            pstmt.setString(3, student.getGender());
            pstmt.setInt(4, student.getClassId());
            
            // SQL実行
            return pstmt.executeUpdate() > 0;
            
        } catch (Exception error) {
            System.err.println("学生登録エラー:" + error.getMessage());
            return false;
        }
    }
    
    private Connection establishConnection() throws Exception {
        Class.forName(DRIVER_CLASS);
        return DriverManager.getConnection(CONNECTION_URL, DB_USER, DB_PASSWORD);
    }
}

PreparedStatementでは、setInt、setString、setDoubleなど、パラメータの型に応じたメソッドを使用して値を設定します。設定するパラメータの位置は、SQL文内の?の順序に対応しています(1から始まるインデックス)。

パラメータ設定メソッドの早見表

データ型 設定メソッド
int setInt(int index, int value)
long setLong(int index, long value)
double setDouble(int index, double value)
String setString(int index, String value)
Date setDate(int index, Date value)

ResultSetによる検索結果の取得

SELECT文の実行結果は、ResultSetオブジェクトとして返されます。ResultSetのnext()メソッドを使用することで、結果セット内の行を順次処理できます。

/**
 * すべての学生情報を取得する
 * @return 学生情報のリスト
 */
public List<Student> retrieveAllStudents() {
    String querySql = "SELECT id, student_id, name, gender, class_id FROM students";
    List<Student> studentList = new ArrayList<>();
    
    try (Connection conn = establishConnection();
         Statement stmt = conn.createStatement();
         ResultSet result = stmt.executeQuery(querySql)) {
        
        // 検索結果の解析とオブジェクトへの変換
        while (result.next()) {
            Student entity = new Student();
            entity.setId(result.getInt("id"));
            entity.setStudentId(result.getInt("student_id"));
            entity.setName(result.getString("name"));
            entity.setGender(result.getString("gender"));
            entity.setClassId(result.getInt("class_id"));
            studentList.add(entity);
        }
        
    } catch (Exception error) {
        System.err.println("学生情報取得エラー:" + error.getMessage());
    }
    
    return studentList;
}

/**
 * 指定されたIDの学生情報を取得する
 * @param studentId 検索する学生ID
 * @return 見つかった学生情報(存在しない場合はnull)
 */
public Student findByStudentId(int studentId) {
    String querySql = "SELECT id, student_id, name, gender, class_id FROM students WHERE student_id = ?";
    
    try (Connection conn = establishConnection();
         PreparedStatement pstmt = conn.prepareStatement(querySql)) {
        
        pstmt.setInt(1, studentId);
        
        try (ResultSet result = pstmt.executeQuery()) {
            if (result.next()) {
                Student entity = new Student();
                entity.setId(result.getInt("id"));
                entity.setStudentId(result.getInt("student_id"));
                entity.setName(result.getString("name"));
                entity.setGender(result.getString("gender"));
                entity.setClassId(result.getInt("class_id"));
                return entity;
            }
        }
        
    } catch (Exception error) {
        System.err.println("学生検索エラー:" + error.getMessage());
    }
    
    return null;
}

ResultSetの主要メソッド

メソッド 戻り値の型 説明
next() boolean 次の行へ移動。存在する場合はtrue
getInt(String columnName) int 整数型の値を取得
getString(String columnName) String 文字列型の値を取得
getDouble(String columnName) double 浮動小数点型の値を取得
getDate(String columnName) java.sql.Date 日付型の値を取得

検索結果の反復処理

複数の行が返される検索結果を処理する場合は、whileループを使用してResultSetのカーソルを移動させます。next()メソッドは、カーソルを次の行に移動させ、その行が存在するかを返します。

// カーソル移動の挙動
while (result.next()) {
    // 現在の行にデータが存在する場合にのみ実行される
    String studentName = result.getString("name");
    int classId = result.getInt("class_id");
    
    // データの処理...
}

最初のnext()コールにより、結果セットの最初の行にカーソルが移動します。データがなくなる(最後の行を超えた)とnext()はfalseを返し、ループが終了します。

ユーティリティクラスによるコードの再利用

JDBCコードでは、データベース接続の確立とリソース解放処理が繰り返し発生します。これらの処理をユーティリティクラスに集約することで、コードの保守性と可読性を向上させることができます。

public final class DbConnector {
    
    private static final String DRIVER = "com.mysql.cj.jdbc.Driver";
    private static final String URL = "jdbc:mysql://localhost:3306/school_db";
    private static final String USER = "admin";
    private static final String PASSWORD = "password123";
    
    // インスタンス化を防止
    private DbConnector() {
        throw new UnsupportedOperationException("このクラスはインスタンス化できません");
    }
    
    /**
     * データベース接続を取得する
     * @return 接続オブジェクト
     */
    public static Connection getConnection() {
        try {
            Class.forName(DRIVER);
            return DriverManager.getConnection(URL, USER, PASSWORD);
        } catch (Exception error) {
            throw new RuntimeException("データベース接続に失敗しました", error);
        }
    }
    
    /**
     * すべてのリソースを安全に解放する
     */
    public static void closeResources(AutoCloseable... resources) {
        for (AutoCloseable resource : resources) {
            if (resource != null) {
                try {
                    resource.close();
                } catch (Exception closeError) {
                    // ログ出力などのエラー処理を実装
                    System.err.println("リソース解放エラー:" + closeError.getMessage());
                }
            }
        }
    }
    
    /**
     * 接続のみを安全に解放する
     */
    public static void closeQuietly(Connection connection) {
        if (connection != null) {
            try {
                connection.close();
            } catch (SQLException error) {
                System.err.println("接続クローズエラー:" + error.getMessage());
            }
        }
    }
}

ユーティリティクラスの導入により、個々のDAOクラスで接続処理やクローズ処理を記述する必要がなくなります。また、try-with-resources文と組み合わせることで、リソースの自動解放が保証され、リークを防止できます。

実践的なDAO実装例

public class StudentRepository {
    
    /**
     * 学生情報を登録する
     */
    public void create(Student student) {
        String sql = "INSERT INTO students(student_id, name, gender, class_id) VALUES(?, ?, ?, ?)";
        
        try (Connection conn = DbConnector.getConnection();
             PreparedStatement pstmt = conn.prepareStatement(sql)) {
            
            pstmt.setInt(1, student.getStudentId());
            pstmt.setString(2, student.getName());
            pstmt.setString(3, student.getGender());
            pstmt.setInt(4, student.getClassId());
            
            pstmt.executeUpdate();
            System.out.println("学生情報の登録を完了しました");
            
        } catch (Exception error) {
            throw new DataAccessException("学生情報の登録に失敗しました", error);
        }
    }
    
    /**
     * 指定された学生IDの学生情報を更新する
     */
    public void updateById(int studentId, Student updatedData) {
        String sql = "UPDATE students SET name = ?, gender = ?, class_id = ? WHERE student_id = ?";
        
        try (Connection conn = DbConnector.getConnection();
             PreparedStatement pstmt = conn.prepareStatement(sql)) {
            
            pstmt.setString(1, updatedData.getName());
            pstmt.setString(2, updatedData.getGender());
            pstmt.setInt(3, updatedData.getClassId());
            pstmt.setInt(4, studentId);
            
            int updatedCount = pstmt.executeUpdate();
            System.out.println("更新件数:" + updatedCount);
            
        } catch (Exception error) {
            throw new DataAccessException("学生情報の更新に失敗しました", error);
        }
    }
    
    /**
     * 指定された学生IDの学生情報を削除する
     */
    public void deleteById(int studentId) {
        String sql = "DELETE FROM students WHERE student_id = ?";
        
        try (Connection conn = DbConnector.getConnection();
             PreparedStatement pstmt = conn.prepareStatement(sql)) {
            
            pstmt.setInt(1, studentId);
            
            int deletedCount = pstmt.executeUpdate();
            System.out.println("削除件数:" + deletedCount);
            
        } catch (Exception error) {
            throw new DataAccessException("学生情報の削除に失敗しました", error);
        }
    }
}

まとめ

JDBCは、Javaアプリケーションからデータベースにアクセスするための標準的な手段です。本稿では、以下の内容を解説しました。

  • StatementとPreparedStatementによるSQL実行
  • ResultSetを使用した検索結果の取得
  • ループ処理による複数レコードの走査
  • ユーティリティクラスによるコードの最適化

実務環境では、セキュリティと保守性の観点から、PreparedStatementの使用と適切なリソース管理を徹底することを推奨します。

タグ: Java JDBC MySQL database SQL

6月27日 00:49 投稿