JDBC
JDBC 概要
JDBCは、主にjava.sqlおよびjavax.sqlパッケージに含まれるJavaベースのデータベースアクセス仕様のセットです。データベースベンダーはドライバを介してJDBCインタフェースを実現し、Javaプログラムは異なるタイプのリレーショナルデータベースを接続する統一インタフェースに向けてコードを書く。
JDBCは次の操作によく使用されます。
- データベース接続の確立と停止。
- 新規、変更、削除、クエリー文を実行します。
- クエリ結果セットを処理します。
- トランザクションを制御する。
- データベースおよび結果セットのメタデータを取得します。
MySQLドライバの
JDBCを使用してMy SQLに接続するには、My SQL Connector/Jドライバをプロジェクトの依存関係に追加する必要があります。
従来のJavaプロジェクトでは、ドライバJARファイルをプロジェクトのlibディレクトリに配置し、クラスライブラリに追加することができます。Mavenプロジェクトでは、プロジェクトの依存関係管理を通じてバージョンを駆動します。
My SQL 8の一般的なドライバクラス名は以下のとおりです。
com.mysql.cj.jdbc.Driver
最新のJDBCドライバは通常自動登録をサポートしますが、ドライバを明示的にロードするとJDBCの基本プロセスを理解できます。
try {
Class.forName("com.mysql.cj.jdbc.Driver");
} catch (ClassNotFoundException e) {
throw new RuntimeException("未找到 MySQL JDBC 驱动", e);
}
データベース接続の
DriverManager.getConnection()を使用して、Connectionオブジェクトを作成します。
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
public class ConnectionTest {
public static void main(String[] args) {
String url =
"jdbc:mysql://localhost:3306/java2601" +
"?serverTimezone=UTC&characterEncoding=utf8";
String username = "root";
String password = "root";
try (Connection connection = DriverManager.getConnection(
url,
username,
password
)) {
System.out.println("数据库连接成功:" + !connection.isClosed());
} catch (SQLException e) {
e.printStackTrace();
}
}
}JDBC URLの一般的な構成には、ユーザー名、パスワード、タイムゾーン、文字エンコーディング、SSL 設定などがあります。ユーザー名とパスワードは通常、getConnection()の独立したパラメータとして渡され、URLに直接書き込むことは推奨されません。
データベースアカウントとパスワードは、パブリックリポジトリにハードコードしないでください。実際のプロジェクトは通常、設定ファイル、環境変数、キー管理サービスを使用します。
JDBC 基本プログラミング手順
employeeテーブルへのレコードの挿入を例にとると、基本的な手順は次のとおりです。
ロード·ドライバ
Class.forName("com.mysql.cj.jdbc.Driver");
接続を取得する
Connection connection = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/java2601?serverTimezone=UTC&characterEncoding=utf8",
"root",
"root"
);
SQL 実行オブジェクトの作成
PreparedStatementが推奨される。
String sql = "insert into employee(first_name, salary, department_id) " +
"values(?, ?, ?)";
PreparedStatement statement = connection.prepareStatement(sql);
パラメータの設定と実行
statement.setString(1, "Tom");
statement.setDouble(2, 8000);
statement.setInt(3, 5001);
int affectedRows = statement.executeUpdate();
System.out.println("受影响行数:" + affectedRows);
リソースのシャットダウン
Connection、Statement、およびResultSetはすべて、シャットダウンする必要があるリソースです。try-with-resourcesを使用して自動的にシャットダウンすることを推奨します。
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class InsertEmployee {
public static void main(String[] args) {
String url =
"jdbc:mysql://localhost:3306/java2601" +
"?serverTimezone=UTC&characterEncoding=utf8";
String sql = "insert into employee(" +
"first_name, salary, department_id" +
") values(?, ?, ?)";
try (Connection connection = DriverManager.getConnection(
url,
"root",
"root"
); PreparedStatement statement = connection.prepareStatement(sql)) {
statement.setString(1, "张三");
statement.setDouble(2, 8000);
statement.setInt(3, 5001);
int affectedRows = statement.executeUpdate();
System.out.println("受影响行数:" + affectedRows);
} catch (SQLException e) {
e.printStackTrace();
}
}
}追加、削除、変更を実行
executeUpdate()は、INSERT、UPDATE、DELETEを実行するために使用され、影響を受けるロー数を返します。
追加データの追加
String sql = "insert into employee(" +
"first_name, job_id, salary, department_id" +
") values(?, ?, ?, ?)";
try (PreparedStatement statement = connection.prepareStatement(sql)) {
statement.setString(1, "Rose");
statement.setString(2, "程序员");
statement.setDouble(3, 9000);
statement.setInt(4, 5001);
statement.executeUpdate();
}データの変更
String sql = "update employee " +
"set first_name = ?, job_id = ?, salary = ?, department_id = ? " +
"where employee_id = ?";
try (PreparedStatement statement = connection.prepareStatement(sql)) {
statement.setString(1, "Jack");
statement.setString(2, "项目经理");
statement.setDouble(3, 12000);
statement.setInt(4, 5002);
statement.setInt(5, 101);
statement.executeUpdate();
}データを削除
String sql = "delete from employee where employee_id = ?";
try (PreparedStatement statement = connection.prepareStatement(sql)) {
statement.setInt(1, 101);
statement.executeUpdate();
}
ResultSetを使用したクエリー結果の処理
クエリ文を実行するためにexecuteQuery()を呼び出すと、ResultSetオブジェクトが返されます。
ResultSetは、カーソルを持つ2 次元の結果セットとして理解できます。初期カーソルは最初のローの前にあり、next()を呼び出すたびに1ロー下に移動します。
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class QueryEmployee {
public static void main(String[] args) {
String url =
"jdbc:mysql://localhost:3306/java2601" +
"?serverTimezone=UTC&characterEncoding=utf8";
String sql = "select " +
"employee_id as eid, first_name, job_id, salary " +
"from employee";
try (Connection connection = DriverManager.getConnection(
url,
"root",
"root"
); PreparedStatement statement = connection.prepareStatement(sql);
ResultSet resultSet = statement.executeQuery()) {
while (resultSet.next()) {
int employeeId = resultSet.getInt("eid");
String firstName = resultSet.getString("first_name");
String jobId = resultSet.getString("job_id");
double salary = resultSet.getDouble("salary");
System.out.println(
employeeId + "\t" +
firstName + "\t" +
jobId + "\t" +
salary
);
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}getXxx()は、列順序または列名でデータを取得できます。カラム番号は1から始まる.カラム名またはエイリアスを使用する方が一般的にわかりやすいです。
getInt()、getDouble()などのメソッドは、NULLになる可能性のある数値列の基本型デフォルト値を返します。データベース内のNULLを区別する必要がある場合は、wasNull()を呼び出すか、getObject()を使用してパッケージタイプを取得できます。
SQLインジェクションの問題点
以下の文字列連結方法はSQLインジェクションのリスクをもたらします。
String sql = "select * from db_users " +
"where username = '" + username + "' " +
"and password = '" + password + "'";
ユーザ入力はSQL 构文の直接の一部になります。攻撃者はクエリの意味を変更する特殊な入力を構築できます。
略称はPreparedStatement
PreparedStatementはパラメータ値を表すプレースホルダを使用し、タイプセーフなsetXxx()メソッドによってパラメータを設定します。パラメータ値はSQL 構造体として解析されないため、SQLインジェクションの問題のほとんどを回避できます。
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Scanner;
public class LoginTest {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入用户名:");
String username = scanner.nextLine();
System.out.println("请输入密码:");
String password = scanner.nextLine();
String sql = "select id from db_users " +
"where username = ? and password = ?";
try (Connection connection = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/java2601?serverTimezone=UTC&characterEncoding=utf8",
"root",
"root"
); PreparedStatement statement = connection.prepareStatement(sql)) {
statement.setString(1, username);
statement.setString(2, password);
try (ResultSet resultSet = statement.executeQuery()) {
if (resultSet.next()) {
System.out.println("登录成功");
} else {
System.out.println("登录失败");
}
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}例はJDBCクエリを示すためだけに使用されます。実際のシステムは、データベースに平文のパスワードを保持するのではなく、信頼できるパスワードハッシュアルゴリズムで処理された結果を保持し、ランダムな塩を使用する必要があります。
StatementとPreparedStatementの違い
| 対照 | Statement | PreparedStatement |
|---|---|---|
| SQLパラメータ#SQLパラメータ# | 通常は文字列で連結する。 | 使用する“プレースホルダ |
| SQLインジェクションのリスク | より高い。 | パラメータ値とSQL 構造体の分離{{ぱらめーた値 SQLこうぞうとのぶんかつ}} |
| 読みやすい | 動的結合はより複雑です。 | パラメータの位置が明確 |
| 繰り返しの実行 | 毎回完全なSQLを処理 | ドライバまたはデータベースが実行プランを再利用する場合がある |
| タイプの設定 | 依存文字列形式 | setString、setIntなどのメソッドを使用する |
業務コードではPreparedStatementを優先する。テーブル名、カラム名、ソート方向などのSQL 構造はプレースホルダを使用できず、信頼できるホワイトリストで選択する必要があります。
JDBC 共通 API {{JDBCよく使うAPI}}
Connection
Connectionは1 回のデータベース接続を表します。一般的な方法は:
| 方法 | 作用 |
|---|---|
prepareStatement() | 事前コンパイルされたSQLオブジェクトの作成 |
setAutoCommit(false) | オートコミットをクローズし、手動制御トランザクションを開始 |
commit() | 取引の提出 |
rollback() | 取引のロールバック |
setSavepoint() | セーブポイントの作成 |
rollback(Savepoint) | 指定されたセーブポイントへのロールバック |
getMetaData() | データベース·メタデータの取得 |
close() | 接続オフ |
複数のSQLが同じローカルトランザクションを構成するには、同じConnection内で実行される必要があります。
PreparedStatement
一般的な方法には、setString()、setInt()、setObject()、executeUpdate()、およびexecuteQuery()があります。
パラメータ番号は1から始まります。
ResultSet
一般的な方法には、next()、getString()、getInt()、getObject()、およびwasNull()があります。
JDBCトランザクション制御{{JDBCとらんざくしょんせいぎょ}}
次の例では、同じトランザクションで2つの従業員レコードを削除します。いずれかの操作が失敗した場合は、すべてロールバックします。
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class TransactionTest {
public static void deleteEmployees(
Connection connection,
int firstId,
int secondId
) throws SQLException {
String sql = "delete from employee where employee_id = ?";
boolean originalAutoCommit = connection.getAutoCommit();
try {
connection.setAutoCommit(false);
try (PreparedStatement statement = connection.prepareStatement(sql)) {
statement.setInt(1, firstId);
statement.executeUpdate();
statement.setInt(1, secondId);
statement.executeUpdate();
}
connection.commit();
} catch (SQLException e) {
connection.rollback();
throw e;
} finally {
connection.setAutoCommit(originalAutoCommit);
}
}
}接続プールを使用する場合は、接続を返却する前に接続状態を復元する必要があります。そうしないと、同じ物理接続を後で借用するコードが影響を受ける可能性があります。
セーブポイントセーブポイント
セーブポイントにより、トランザクションが中間状態にロールバックされることができます。
import java.sql.Connection;
import java.sql.Savepoint;
import java.sql.SQLException;
public class SavepointTest {
public static void execute(Connection connection) throws SQLException {
connection.setAutoCommit(false);
Savepoint savepoint = null;
try {
executeFirstSql(connection);
savepoint = connection.setSavepoint("after_first_sql");
executeSecondSql(connection);
connection.commit();
} catch (SQLException e) {
if (savepoint != null) {
connection.rollback(savepoint);
connection.commit();
} else {
connection.rollback();
}
throw e;
}
}
private static void executeFirstSql(Connection connection) {
}
private static void executeSecondSql(Connection connection) {
}
}データベース·メタデータ
DatabaseMetaDataでは、データベース製品名、バージョン、ドライバ名、テーブル構造などの情報を取得できます。
import java.sql.DatabaseMetaData;
DatabaseMetaData metadata = connection.getMetaData();
System.out.println(metadata.getDatabaseProductName());
System.out.println(metadata.getDatabaseProductVersion());
System.out.println(metadata.getDriverName());
結果セット·メタデータ
ResultSetMetaDataでは、結果セットのカラム数、カラム名、カラム·タイプなどの情報を取得できます。
import java.sql.ResultSetMetaData;
ResultSetMetaData metadata = resultSet.getMetaData();
int columnCount = metadata.getColumnCount();
for (int index = 1; index <= columnCount; index++) {
System.out.println(metadata.getColumnLabel(index));
System.out.println(metadata.getColumnTypeName(index));
}JDBCのカプセル化
結合作成プロセスのカプセル化
package com.hyxy.dao;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
public final class DBUtil {
private static final String URL =
"jdbc:mysql://localhost:3306/java2601" +
"?serverTimezone=UTC&characterEncoding=utf8";
private static final String USERNAME = "root";
private static final String PASSWORD = "root";
private DBUtil() {
}
static {
try {
Class.forName("com.mysql.cj.jdbc.Driver");
} catch (ClassNotFoundException e) {
throw new ExceptionInInitializerError(e);
}
}
public static Connection getConnection() throws SQLException {
return DriverManager.getConnection(URL, USERNAME, PASSWORD);
}
}実際のプロジェクトでは、DriverManager経由で物理接続を毎回直接作成するのではなく、データベース接続プールを使用します。
DAOとVO
DAOはData Access Objectの略で、データベースアクセスのロジックをカプセル化します。ビジネス層はDAOメソッドを使用して追加、削除、変更、チェックを完了し、ビジネスコード内のSQLの散在を回避します。
VOはValue Objectの略です。現在のコース例では、VOを使用してデータベースレコードを保持し、フィールドは通常テーブル構造に対応しています。実際のプロジェクトでもEntity、POJO、DTOなどの名前が使われることが多いが、意味は同じではない。
Apache Commons DbUtils
DbUtilsはJDBCの軽量カプセル化です。コアクラスQueryRunnerは、パラメータのバインディング、結果セットの処理、およびリソース管理を簡素化します。
外部接続を使用してQueryRunnerを作成すると、接続は呼び出し元の責任で閉じられます。
追加データの追加
import org.apache.commons.dbutils.QueryRunner;
import java.sql.Connection;
import java.sql.SQLException;
public class DbUtilsInsertTest {
public static void main(String[] args) {
QueryRunner runner = new QueryRunner();
String sql = "insert into employee(first_name, job_id) values(?, ?)";
try (Connection connection = DBUtil.getConnection()) {
int affectedRows = runner.update(
connection,
sql,
"Smith",
"财务总监"
);
System.out.println("受影响行数:" + affectedRows);
} catch (SQLException e) {
e.printStackTrace();
}
}
}主キーの取得
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.ScalarHandler;
import java.sql.Connection;
import java.sql.SQLException;
public class DbUtilsPrimaryKeyTest {
public static void main(String[] args) {
QueryRunner runner = new QueryRunner();
String sql = "insert into employee(first_name, job_id) values(?, ?)";
try (Connection connection = DBUtil.getConnection()) {
Number primaryKey = runner.insert(
connection,
sql,
new ScalarHandler<>(),
"Smith2",
"财务总监2"
);
System.out.println(primaryKey);
} catch (SQLException e) {
e.printStackTrace();
}
}
}ドライバが返す主キー値の種類は異なる場合があり、通常はBigIntegerに固定するよりもNumberを使用する方が安全です。
複数のレコードの検索
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;
public class DbUtilsListTest {
public static void main(String[] args) {
QueryRunner runner = new QueryRunner();
String sql = "select * from employee";
try (Connection connection = DBUtil.getConnection()) {
List<Employee> employees = runner.query(
connection,
sql,
new BeanListHandler<>(Employee.class)
);
for (Employee employee : employees) {
System.out.println(employee);
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}JavaBeanのプロパティー名は、クエリー結果の列ラベルと一致する必要があります。カラム名が矛盾している場合、SQLでエイリアスを使用できます。
select employee_id as employeeId,
first_name as firstName,
department_id as departmentId
from employee;
条件付き検索
String sql = "select * from employee " +
"where department_id = ? and salary >= ?";
try (Connection connection = DBUtil.getConnection()) {
List<Employee> employees = runner.query(
connection,
sql,
new BeanListHandler<>(Employee.class),
5001,
3000
);
}主キーによるクエリ
import org.apache.commons.dbutils.handlers.BeanHandler;
String sql = "select * from employee where employee_id = ?";
try (Connection connection = DBUtil.getConnection()) {
Employee employee = runner.query(
connection,
sql,
new BeanHandler<>(Employee.class),
101
);
System.out.println(employee);
}ユーザー管理の包括的な例
Userテーブルの作成
drop table if exists t_user;
create table t_user (
id int not null auto_increment,
username varchar(255) null,
pwd varchar(255) null,
email varchar(255) null,
primary key (id)
) engine = InnoDB
default character set = utf8mb4;
insert into t_user(username, pwd, email)
values('Jerry', '888888', 'jerry@126.com');ユーザーエンティティークラスの作成
package com.hyxy.vo;
public class User {
private int id;
private String username;
private String pwd;
private String email;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPwd() {
return pwd;
}
public void setPwd(String pwd) {
this.pwd = pwd;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", email='" + email + '\'' +
'}';
}
}パスワードの出力はtoString()では推奨されません。
ユーザー DAOの作成
package com.hyxy.dao;
import com.hyxy.vo.User;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;
public class UserDao {
private final QueryRunner runner = new QueryRunner();
public int register(User user) throws SQLException {
String sql = "insert into t_user(username, pwd, email) values(?, ?, ?)";
try (Connection connection = DBUtil.getConnection()) {
return runner.update(
connection,
sql,
user.getUsername(),
user.getPwd(),
user.getEmail()
);
}
}
public User login(String username, String password) throws SQLException {
String sql = "select id, username, email " +
"from t_user where username = ? and pwd = ?";
try (Connection connection = DBUtil.getConnection()) {
return runner.query(
connection,
sql,
new BeanHandler<>(User.class),
username,
password
);
}
}
public int update(User user) throws SQLException {
String sql = "update t_user " +
"set username = ?, pwd = ?, email = ? where id = ?";
try (Connection connection = DBUtil.getConnection()) {
return runner.update(
connection,
sql,
user.getUsername(),
user.getPwd(),
user.getEmail(),
user.getId()
);
}
}
public int deleteByUsername(String username) throws SQLException {
String sql = "delete from t_user where username = ?";
try (Connection connection = DBUtil.getConnection()) {
return runner.update(connection, sql, username);
}
}
public List<User> findAll() throws SQLException {
String sql = "select id, username, email from t_user";
try (Connection connection = DBUtil.getConnection()) {
return runner.query(
connection,
sql,
new BeanListHandler<>(User.class)
);
}
}
}例の注意事項
- 相互作用層はSQLを直接連結しないでください。
- データアクセスのロジックはDAOに、ビジネスルールはサービスに配置する必要があります。
- データベース·パスワードもユーザー·パスワードもハード·コードしないでください。
- ログイン機能は、平文比較ではなくパスワードハッシュを使用してください。
- 各 JDBCリソースは適切にシャットダウンする必要があります。
- 複数の関連する更新にはトランザクションによる一貫性が必要です。
気に入ったならばコメントを残してくださいね~