JDBC 소개

자바 탑

방금 Spring 5 및 Spring Boot 2의 기본 사항에 초점을 맞춘 새로운 Learn Spring 과정을 발표했습니다 .

>> 과정 확인

1. 개요

이 기사에서는 데이터베이스에서 쿼리를 연결하고 실행하기위한 API 인 JDBC (Java Database Connectivity)를 살펴 보겠습니다.

JDBC는 적절한 드라이버가 제공되는 한 모든 데이터베이스에서 작동 할 수 있습니다.

2. JDBC 드라이버

JDBC 드라이버는 특정 유형의 데이터베이스에 연결하는 데 사용되는 JDBC API 구현입니다. JDBC 드라이버에는 여러 유형이 있습니다.

  • 유형 1 – 다른 데이터 액세스 API에 대한 매핑을 포함합니다. 이에 대한 예는 JDBC-ODBC 드라이버입니다.
  • 유형 2 – 대상 데이터베이스의 클라이언트 측 라이브러리를 사용하는 구현입니다. 기본 API 드라이버라고도 함
  • 유형 3 – 미들웨어를 사용하여 JDBC 호출을 데이터베이스 별 호출로 변환합니다. 네트워크 프로토콜 드라이버라고도합니다.
  • 유형 4 – JDBC 호출을 데이터베이스 별 호출로 변환하여 데이터베이스에 직접 연결합니다. 데이터베이스 프로토콜 드라이버 또는 씬 드라이버로 알려진

가장 일반적으로 사용되는 유형은 유형 4 입니다. 플랫폼 독립적 이라는 장점이 있기 때문 입니다. 데이터베이스 서버에 직접 연결하면 다른 유형에 비해 더 나은 성능을 제공합니다. 이러한 유형의 드라이버의 단점은 각 데이터베이스에 고유 한 프로토콜이 있다는 점을 고려할 때 데이터베이스에 따라 다르다는 것입니다.

3. 데이터베이스에 연결

데이터베이스에 연결하려면 드라이버를 초기화하고 데이터베이스 연결을 열기 만하면됩니다.

3.1. 드라이버 등록

이 예에서는 유형 4 데이터베이스 프로토콜 드라이버를 사용합니다.

MySQL 데이터베이스를 사용하고 있으므로 mysql-connector-java 종속성이 필요합니다 .

 mysql mysql-connector-java 6.0.6 

다음 으로 드라이버 클래스를 동적으로로드하는 Class.forName () 메서드를 사용하여 드라이버를 등록 해 보겠습니다 .

Class.forName("com.mysql.cj.jdbc.Driver");

이전 버전의 JDBC에서는 연결을 얻기 전에 먼저 Class.forName 메서드 를 호출하여 JDBC 드라이버를 초기화해야했습니다 . JDBC 4.0 부터는 클래스 경로에있는 모든 드라이버가 자동으로로드 됩니다. 따라서 현대 환경에서는 이 Class.forName 부분 이 필요하지 않습니다 .

3.2. 연결 생성

연결을 열기 위해 DriverManager 클래스 의 getConnection () 메소드를 사용할 수 있습니다 . 이 메소드에는 연결 URL 문자열 매개 변수 가 필요합니다 .

try (Connection con = DriverManager .getConnection("jdbc:mysql://localhost:3306/myDb", "user1", "pass")) { // use con here }

때문에 연결이 입니다 AutoCloseable 자원, 우리는 내부를 사용해야 시도 -과 - 자원 블록 .

연결 URL의 구문은 사용되는 데이터베이스 유형에 따라 다릅니다. 몇 가지 예를 살펴 보겠습니다.

jdbc:mysql://localhost:3306/myDb?user=user1&password=pass
jdbc:postgresql://localhost/myDb
jdbc:hsqldb:mem:myDb

지정된 myDb 데이터베이스에 연결하려면 데이터베이스와 사용자를 생성하고 필요한 액세스 권한을 추가해야합니다.

CREATE DATABASE myDb; CREATE USER 'user1' IDENTIFIED BY 'pass'; GRANT ALL on myDb.* TO 'user1';

4. SQL 문 실행

SQL 명령을 데이터베이스로 보낼 때 Connection 개체를 사용하여 얻을 수있는 Statement , PreparedStatement 또는 CallableStatement 유형의 인스턴스를 사용할 수 있습니다 .

4.1. 성명서

인터페이스는 SQL 명령을 실행하기위한 필수 기능이 포함되어 있습니다.

먼저 Statement 객체를 생성 해 보겠습니다 .

try (Statement stmt = con.createStatement()) { // use stmt here }

다시 말하지만, 자동 리소스 관리를 위해 try-with-resources 블록 내에서 Statement를 사용해야합니다 .

어쨌든 세 가지 방법을 사용하여 SQL 명령어를 실행할 수 있습니다.

  • SELECT 명령어에 대한 executeQuery ()
  • 데이터 또는 데이터베이스 구조를 업데이트하기위한 executeUpdate ()
  • 결과를 알 수없는 경우 위의 두 경우 모두 execute ()를 사용할 수 있습니다.

execute () 메서드를 사용하여 데이터베이스에 학생 테이블을 추가해 보겠습니다 .

String tableSql = "CREATE TABLE IF NOT EXISTS employees" + "(emp_id int PRIMARY KEY AUTO_INCREMENT, name varchar(30)," + "position varchar(30), salary double)"; stmt.execute(tableSql);

사용시 실행 () 의 데이터를 업데이트하는 방법을 다음 stmt.getUpdateCount () 메소드는 영향 행의 수를 반환한다.

결과가 0이면 행이 영향을받지 않았거나 데이터베이스 구조 업데이트 명령이었습니다.

값이 -1이면 명령은 SELECT 쿼리입니다. 그런 다음 stmt.getResultSet () 사용하여 결과를 얻을 수 있습니다 .

다음으로 executeUpdate () 메서드를 사용하여 테이블에 레코드를 추가해 보겠습니다 .

String insertSql = "INSERT INTO employees(name, position, salary)" + " VALUES('john', 'developer', 2000)"; stmt.executeUpdate(insertSql);

이 메서드는 행을 업데이트하는 명령의 경우 영향을받는 행 수를 반환하고 데이터베이스 구조를 업데이트하는 명령의 경우 0을 반환합니다.

ResultSet 유형의 객체를 반환하는 executeQuery () 메서드를 사용하여 테이블에서 레코드를 검색 할 수 있습니다 .

String selectSql = "SELECT * FROM employees"; try (ResultSet resultSet = stmt.executeQuery(selectSql)) { // use resultSet here }

사용 후 ResultSet 인스턴스 를 닫아야합니다 . 그렇지 않으면 예상보다 훨씬 긴 기간 동안 기본 커서를 열어 둘 수 있습니다. 이를 위해 위의 예제와 같이 try-with-resources 블록 을 사용하는 것이 좋습니다 .

4.2. PreparedStatement

PreparedStatement 오브젝트는 사전 컴파일 된 SQL 시퀀스를 포함합니다. 물음표로 표시된 하나 이상의 매개 변수를 가질 수 있습니다.

주어진 매개 변수 에 따라 직원 테이블의 레코드를 업데이트 하는 PreparedStatement 를 생성 해 보겠습니다 .

String updatePositionSql = "UPDATE employees SET position=? WHERE emp_id=?"; try (PreparedStatement pstmt = con.prepareStatement(updatePositionSql)) { // use pstmt here }

PreparedStatement 에 매개 변수를 추가하려면 간단한 setter – setX ()를 사용할 수 있습니다. 여기서 X는 매개 변수의 유형이고 메소드 인수는 매개 변수의 순서와 값입니다.

pstmt.setString(1, "lead developer"); pstmt.setInt(2, 1);

이 명령문은 SQL String 매개 변수가 없는 executeQuery (), executeUpdate (), execute () 의 동일한 세 가지 메소드 중 하나로 실행됩니다 .

int rowsAffected = pstmt.executeUpdate();

4.3. CallableStatement

의 CallableStatement 인터페이스는 저장 프로 시저를 호출 허용한다.

To create a CallableStatement object, we can use the prepareCall() method of Connection:

String preparedSql = "{call insertEmployee(?,?,?,?)}"; try (CallableStatement cstmt = con.prepareCall(preparedSql)) { // use cstmt here }

Setting input parameter values for the stored procedure is done like in the PreparedStatement interface, using setX() methods:

cstmt.setString(2, "ana"); cstmt.setString(3, "tester"); cstmt.setDouble(4, 2000);

If the stored procedure has output parameters, we need to add them using the registerOutParameter() method:

cstmt.registerOutParameter(1, Types.INTEGER);

Then let's execute the statement and retrieve the returned value using a corresponding getX() method:

cstmt.execute(); int new_id = cstmt.getInt(1);

For example to work, we need to create the stored procedure in our MySql database:

delimiter // CREATE PROCEDURE insertEmployee(OUT emp_id int, IN emp_name varchar(30), IN position varchar(30), IN salary double) BEGIN INSERT INTO employees(name, position,salary) VALUES (emp_name,position,salary); SET emp_id = LAST_INSERT_ID(); END // delimiter ;

The insertEmployee procedure above will insert a new record into the employees table using the given parameters and return the id of the new record in the emp_id out parameter.

To be able to run a stored procedure from Java, the connection user needs to have access to the stored procedure's metadata. This can be achieved by granting rights to the user on all stored procedures in all databases:

GRANT ALL ON mysql.proc TO 'user1';

Alternatively, we can open the connection with the property noAccessToProcedureBodies set to true:

con = DriverManager.getConnection( "jdbc:mysql://localhost:3306/myDb?noAccessToProcedureBodies=true", "user1", "pass");

This will inform the JDBC API that the user does not have the rights to read the procedure metadata so that it will create all parameters as INOUT String parameters.

5. Parsing Query Results

After executing a query, the result is represented by a ResultSet object, which has a structure similar to a table, with lines and columns.

5.1. ResultSet Interface

The ResultSet uses the next() method to move to the next line.

Let's first create an Employee class to store our retrieved records:

public class Employee { private int id; private String name; private String position; private double salary; // standard constructor, getters, setters }

Next, let's traverse the ResultSet and create an Employee object for each record:

String selectSql = "SELECT * FROM employees"; try (ResultSet resultSet = stmt.executeQuery(selectSql)) { List employees = new ArrayList(); while (resultSet.next()) { Employee emp = new Employee(); emp.setId(resultSet.getInt("emp_id")); emp.setName(resultSet.getString("name")); emp.setPosition(resultSet.getString("position")); emp.setSalary(resultSet.getDouble("salary")); employees.add(emp); } }

Retrieving the value for each table cell can be done using methods of type getX() where X represents the type of the cell data.

The getX() methods can be used with an int parameter representing the order of the cell, or a String parameter representing the name of the column. The latter option is preferable in case we change the order of the columns in the query.

5.2. Updatable ResultSet

Implicitly, a ResultSet object can only be traversed forward and cannot be modified.

If we want to use the ResultSet to update data and traverse it in both directions, we need to create the Statement object with additional parameters:

stmt = con.createStatement( ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE );

To navigate this type of ResultSet, we can use one of the methods:

  • first(), last(), beforeFirst(), beforeLast() – to move to the first or last line of a ResultSet or to the line before these
  • next(), previous() – to navigate forward and backward in the ResultSet
  • getRow() – to obtain the current row number
  • moveToInsertRow(), moveToCurrentRow() – to move to a new empty row to insert and back to the current one if on a new row
  • absolute(int row) – to move to the specified row
  • relative(int nrRows) – to move the cursor the given number of rows

Updating the ResultSet can be done using methods with the format updateX() where X is the type of cell data. These methods only update the ResultSet object and not the database tables.

To persist the ResultSet changes to the database, we must further use one of the methods:

  • updateRow() – to persist the changes to the current row to the database
  • insertRow(), deleteRow() – to add a new row or delete the current one from the database
  • refreshRow() – to refresh the ResultSet with any changes in the database
  • cancelRowUpdates() – to cancel changes made to the current row

Let's take a look at an example of using some of these methods by updating data in the employee's table:

try (Statement updatableStmt = con.createStatement( ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE)) { try (ResultSet updatableResultSet = updatableStmt.executeQuery(selectSql)) { updatableResultSet.moveToInsertRow(); updatableResultSet.updateString("name", "mark"); updatableResultSet.updateString("position", "analyst"); updatableResultSet.updateDouble("salary", 2000); updatableResultSet.insertRow(); } }

6. Parsing Metadata

The JDBC API allows looking up information about the database, called metadata.

6.1. DatabaseMetadata

The DatabaseMetadata interface can be used to obtain general information about the database such as the tables, stored procedures, or SQL dialect.

Let's have a quick look at how we can retrieve information on the database tables:

DatabaseMetaData dbmd = con.getMetaData(); ResultSet tablesResultSet = dbmd.getTables(null, null, "%", null); while (tablesResultSet.next()) { LOG.info(tablesResultSet.getString("TABLE_NAME")); }

6.2. ResultSetMetadata

This interface can be used to find information about a certain ResultSet, such as the number and name of its columns:

ResultSetMetaData rsmd = rs.getMetaData(); int nrColumns = rsmd.getColumnCount(); IntStream.range(1, nrColumns).forEach(i -> { try { LOG.info(rsmd.getColumnName(i)); } catch (SQLException e) { e.printStackTrace(); } });

7. Handling Transactions

By default, each SQL statement is committed right after it is completed. However, it's also possible to control transactions programmatically.

This may be necessary in cases when we want to preserve data consistency, for example when we only want to commit a transaction if a previous one has completed successfully.

First, we need to set the autoCommit property of Connection to false, then use the commit() and rollback() methods to control the transaction.

Let's add a second update statement for the salary column after the employee position column update and wrap them both in a transaction. This way, the salary will be updated only if the position was successfully updated:

String updatePositionSql = "UPDATE employees SET position=? WHERE emp_id=?"; PreparedStatement pstmt = con.prepareStatement(updatePositionSql); pstmt.setString(1, "lead developer"); pstmt.setInt(2, 1); String updateSalarySql = "UPDATE employees SET salary=? WHERE emp_id=?"; PreparedStatement pstmt2 = con.prepareStatement(updateSalarySql); pstmt.setDouble(1, 3000); pstmt.setInt(2, 1); boolean autoCommit = con.getAutoCommit(); try { con.setAutoCommit(false); pstmt.executeUpdate(); pstmt2.executeUpdate(); con.commit(); } catch (SQLException exc) { con.rollback(); } finally { con.setAutoCommit(autoCommit); }

For the sake of brevity, we omit the try-with-resources blocks here.

8. Closing the Resources

When we're no longer using it, we need to close the connection to release database resources.

We can do this using the close() API:

con.close();

우리가에서 자원 사용하는 경우, 시도 -과 - 자원 블록을, 우리는 호출 할 필요는 없습니다 가까운 () 는 AS 명시 적 방법을 시도 -과 - 자원 블록이 자동으로 우리에게 있다고한다.

Statement , PreparedStatement , CallableStatementResultSet에 대해서도 마찬가지입니다 .

9. 결론

이 자습서에서는 JDBC API 작업의 기본 사항을 살펴 보았습니다.

항상 그렇듯이 예제의 전체 소스 코드는 GitHub에서 찾을 수 있습니다.

자바 바닥

방금 Spring 5 및 Spring Boot 2의 기본 사항에 초점을 맞춘 새로운 Learn Spring 과정을 발표했습니다 .

>> 과정 확인