Mùa xuân JDBC

1. Khái quát chung

Trong bài viết này, chúng ta sẽ xem xét các trường hợp sử dụng thực tế của mô-đun Spring JDBC.

Tất cả các lớp trong Spring JDBC được chia thành bốn gói riêng biệt:

  • cốt lõi - chức năng cốt lõi của JDBC. Một số lớp quan trọng trong gói này bao gồm JdbcTemplate , SimpleJdbcInsert, SimpleJdbcCall NamedParameterJdbcTemplate .
  • nguồn dữ liệu - các lớp tiện ích để truy cập nguồn dữ liệu. Nó cũng có nhiều triển khai nguồn dữ liệu khác nhau để kiểm tra mã JDBC bên ngoài vùng chứa Jakarta EE.
  • object - DB truy cập theo hướng đối tượng. Nó cho phép thực hiện các truy vấn và trả về kết quả như một đối tượng nghiệp vụ. Nó cũng ánh xạ các kết quả truy vấn giữa các cột và thuộc tính của các đối tượng nghiệp vụ.
  • support - hỗ trợ các lớp cho các lớp dướicác gói lõi vàgói đối tượng . Ví dụ: cung cấpchức năng dịch SQLException .

2. Cấu hình

Để bắt đầu, hãy bắt đầu với một số cấu hình đơn giản của nguồn dữ liệu (chúng tôi sẽ sử dụng cơ sở dữ liệu MySQL cho ví dụ này):

@Configuration @ComponentScan("com.baeldung.jdbc") public class SpringJdbcConfig { @Bean public DataSource mysqlDataSource() { DriverManagerDataSource dataSource = new DriverManagerDataSource(); dataSource.setDriverClassName("com.mysql.jdbc.Driver"); dataSource.setUrl("jdbc:mysql://localhost:3306/springjdbc"); dataSource.setUsername("guest_user"); dataSource.setPassword("guest_password"); return dataSource; } }

Ngoài ra, chúng ta cũng có thể sử dụng tốt cơ sở dữ liệu nhúng để phát triển hoặc thử nghiệm - đây là một cấu hình nhanh tạo một phiên bản của cơ sở dữ liệu nhúng H2 và điền trước nó bằng các tập lệnh SQL đơn giản:

@Bean public DataSource dataSource() { return new EmbeddedDatabaseBuilder() .setType(EmbeddedDatabaseType.H2) .addScript("classpath:jdbc/schema.sql") .addScript("classpath:jdbc/test-data.sql").build(); } 

Cuối cùng - tất nhiên, điều tương tự cũng có thể được thực hiện bằng cách sử dụng cấu hình XML cho nguồn dữ liệu :

3. Các truy vấn JdbcTemplate và Running

3.1. Truy vấn cơ bản

Mẫu JDBC là API chính mà qua đó chúng tôi sẽ truy cập hầu hết các chức năng mà chúng tôi quan tâm:

  • tạo và đóng các kết nối
  • thực hiện các câu lệnh và lệnh gọi thủ tục được lưu trữ
  • lặp lại ResultSet và trả về kết quả

Đầu tiên, hãy bắt đầu với một ví dụ đơn giản để xem JdbcTemplate có thể làm gì:

int result = jdbcTemplate.queryForObject( "SELECT COUNT(*) FROM EMPLOYEE", Integer.class); 

và đây cũng là một INSERT đơn giản:

public int addEmplyee(int id) { return jdbcTemplate.update( "INSERT INTO EMPLOYEE VALUES (?, ?, ?, ?)", id, "Bill", "Gates", "USA"); }

Lưu ý cú pháp tiêu chuẩn của việc cung cấp các tham số - sử dụng ký tự `?`. Tiếp theo - hãy xem xét một thay thế cho cú pháp này.

3.2. Truy vấn có tham số được đặt tên

Để nhận hỗ trợ cho các tham số được đặt tên , chúng tôi sẽ sử dụng mẫu JDBC khác được cung cấp bởi khuôn khổ - NamedParameterJdbcTemplate .

Ngoài ra, điều này bao bọc JbdcTemplate và cung cấp một giải pháp thay thế cho cú pháp truyền thống bằng cách sử dụng “ ? ”Để chỉ định các tham số. Dưới mui xe, nó thay thế các tham số được đặt tên thành JDBC “?” trình giữ chỗ và ủy quyền cho JDCTemplate được bọc để thực thi các truy vấn:

SqlParameterSource namedParameters = new MapSqlParameterSource().addValue("id", 1); return namedParameterJdbcTemplate.queryForObject( "SELECT FIRST_NAME FROM EMPLOYEE WHERE ID = :id", namedParameters, String.class);

Lưu ý cách chúng tôi đang sử dụng MapSqlParameterSource để cung cấp các giá trị cho các tham số được đặt tên.

Ví dụ: hãy xem ví dụ dưới đây sử dụng các thuộc tính từ bean để xác định các tham số được đặt tên:

Employee employee = new Employee(); employee.setFirstName("James"); String SELECT_BY_ID = "SELECT COUNT(*) FROM EMPLOYEE WHERE FIRST_NAME = :firstName"; SqlParameterSource namedParameters = new BeanPropertySqlParameterSource(employee); return namedParameterJdbcTemplate.queryForObject( SELECT_BY_ID, namedParameters, Integer.class);

Lưu ý cách chúng tôi hiện đang sử dụng triển khai BeanPropertySqlParameterSource thay vì chỉ định các tham số được đặt tên theo cách thủ công như trước đây.

3.3. Ánh xạ kết quả truy vấn đến đối tượng Java

Một tính năng rất hữu ích khác là khả năng ánh xạ kết quả truy vấn tới các đối tượng Java - bằng cách triển khai giao diện RowMapper .

Ví dụ - đối với mỗi hàng được trả về bởi truy vấn, Spring sử dụng trình ánh xạ hàng để điền đậu java:

public class EmployeeRowMapper implements RowMapper { @Override public Employee mapRow(ResultSet rs, int rowNum) throws SQLException { Employee employee = new Employee(); employee.setId(rs.getInt("ID")); employee.setFirstName(rs.getString("FIRST_NAME")); employee.setLastName(rs.getString("LAST_NAME")); employee.setAddress(rs.getString("ADDRESS")); return employee; } }

Sau đó, bây giờ chúng ta có thể chuyển trình ánh xạ hàng tới API truy vấn và nhận các đối tượng Java được điền đầy đủ:

String query = "SELECT * FROM EMPLOYEE WHERE ID = ?"; Employee employee = jdbcTemplate.queryForObject( query, new Object[] { id }, new EmployeeRowMapper());

4. Bản dịch ngoại lệ

Spring đi kèm với hệ thống phân cấp ngoại lệ dữ liệu của riêng nó - với DataAccessException là ngoại lệ gốc - và nó dịch tất cả các ngoại lệ thô cơ bản sang nó.

Và do đó, chúng tôi giữ sự tỉnh táo của mình bằng cách không phải xử lý các ngoại lệ bền bỉ cấp thấp và hưởng lợi từ thực tế là Spring kết thúc các ngoại lệ cấp thấp trong DataAccessException hoặc một trong các lớp con của nó.

Ngoài ra, điều này giữ cho cơ chế xử lý ngoại lệ độc lập với cơ sở dữ liệu cơ bản mà chúng tôi đang sử dụng.

Bên cạnh đó, SQLErrorCodeSQLExceptionTranslator mặc định , chúng tôi cũng có thể cung cấp triển khai SQLExceptionTranslator của riêng mình .

Dưới đây là một ví dụ nhanh về triển khai tùy chỉnh, tùy chỉnh thông báo lỗi khi có vi phạm khóa trùng lặp, dẫn đến mã lỗi 23505 khi sử dụng H2:

public class CustomSQLErrorCodeTranslator extends SQLErrorCodeSQLExceptionTranslator { @Override protected DataAccessException customTranslate(String task, String sql, SQLException sqlException) { if (sqlException.getErrorCode() == 23505) { return new DuplicateKeyException( "Custom Exception translator - Integrity constraint violation.", sqlException); } return null; } }

Để sử dụng trình biên dịch ngoại lệ tùy chỉnh này, chúng ta cần chuyển nó tới JdbcTemplate bằng cách gọi phương thức setExceptionTranslator () :

CustomSQLErrorCodeTranslator customSQLErrorCodeTranslator = new CustomSQLErrorCodeTranslator(); jdbcTemplate.setExceptionTranslator(customSQLErrorCodeTranslator);

5. Hoạt động JDBC sử dụng các lớp SimpleJdbc

Các lớp SimpleJdbc cung cấp một cách dễ dàng để cấu hình và thực thi các câu lệnh SQL. Các lớp này sử dụng siêu dữ liệu cơ sở dữ liệu để xây dựng các truy vấn cơ bản. Các lớp SimpleJdbcInsertSimpleJdbcCall cung cấp một cách dễ dàng hơn để thực hiện các lệnh gọi thủ tục chèn và lưu trữ.

5.1. SimpleJdbcInsert

Chúng ta hãy xem cách thực thi các câu lệnh chèn đơn giản với cấu hình tối thiểu.

Câu lệnh INSERT được tạo dựa trên cấu hình của SimpleJdbcInsert và tất cả những gì chúng ta cần là cung cấp tên Bảng, tên Cột và giá trị.

First, let's create a SimpleJdbcInsert:

SimpleJdbcInsert simpleJdbcInsert = new SimpleJdbcInsert(dataSource).withTableName("EMPLOYEE");

Next, let's provide the Column names and values, and execute the operation:

public int addEmplyee(Employee emp) { Map parameters = new HashMap(); parameters.put("ID", emp.getId()); parameters.put("FIRST_NAME", emp.getFirstName()); parameters.put("LAST_NAME", emp.getLastName()); parameters.put("ADDRESS", emp.getAddress()); return simpleJdbcInsert.execute(parameters); }

Further, to allow the database to generate the primary key, we can make use of the executeAndReturnKey() API; we'll also need to configure the actual column that is auto-generated:

SimpleJdbcInsert simpleJdbcInsert = new SimpleJdbcInsert(dataSource) .withTableName("EMPLOYEE") .usingGeneratedKeyColumns("ID"); Number id = simpleJdbcInsert.executeAndReturnKey(parameters); System.out.println("Generated id - " + id.longValue());

Finally – we can also pass in this data by using the BeanPropertySqlParameterSource and MapSqlParameterSource.

5.2. Stored Procedures With SimpleJdbcCall

Also, let's take a look at executing stored procedures – we'll make use of the SimpleJdbcCall abstraction:

SimpleJdbcCall simpleJdbcCall = new SimpleJdbcCall(dataSource) .withProcedureName("READ_EMPLOYEE"); 
public Employee getEmployeeUsingSimpleJdbcCall(int id) { SqlParameterSource in = new MapSqlParameterSource().addValue("in_id", id); Map out = simpleJdbcCall.execute(in); Employee emp = new Employee(); emp.setFirstName((String) out.get("FIRST_NAME")); emp.setLastName((String) out.get("LAST_NAME")); return emp; }

6. Batch Operations

Another simple use case – batching multiple operations together.

6.1. Basic Batch Operations Using JdbcTemplate

Using JdbcTemplate, Batch Operations can be executed via the batchUpdate() API.

The interesting part here is the concise but highly useful BatchPreparedStatementSetter implementation:

public int[] batchUpdateUsingJdbcTemplate(List employees) { return jdbcTemplate.batchUpdate("INSERT INTO EMPLOYEE VALUES (?, ?, ?, ?)", new BatchPreparedStatementSetter() { @Override public void setValues(PreparedStatement ps, int i) throws SQLException { ps.setInt(1, employees.get(i).getId()); ps.setString(2, employees.get(i).getFirstName()); ps.setString(3, employees.get(i).getLastName()); ps.setString(4, employees.get(i).getAddress(); } @Override public int getBatchSize() { return 50; } }); }

6.2. Batch Operations Using NamedParameterJdbcTemplate

We also have the option of batching operations with the NamedParameterJdbcTemplatebatchUpdate() API.

This API is simpler than the previous one – no need to implement any extra interfaces to set the parameters, as it has an internal prepared statement setter to set the parameter values.

Instead, the parameter values can be passed to the batchUpdate() method as an array of SqlParameterSource.

SqlParameterSource[] batch = SqlParameterSourceUtils.createBatch(employees.toArray()); int[] updateCounts = namedParameterJdbcTemplate.batchUpdate( "INSERT INTO EMPLOYEE VALUES (:id, :firstName, :lastName, :address)", batch); return updateCounts;

7. Spring JDBC With Spring Boot

Spring Boot provides a starter spring-boot-starter-jdbc for using JDBC with relational databases.

As with every Spring Boot starter, this one also helps us in getting our application up and running quickly.

7.1. Maven Dependency

We'll need the spring-boot-starter-jdbc dependency as the primary one as well as a dependency for the database that we'll be using. In our case, this is MySQL:

 org.springframework.boot spring-boot-starter-jdbc   mysql mysql-connector-java runtime 

7.2. Configuration

Spring Boot configures the data source automatically for us. We just need to provide the properties in a properties file:

spring.datasource.url=jdbc:mysql://localhost:3306/springjdbc spring.datasource.username=guest_user spring.datasource.password=guest_password

Vậy là xong, chỉ cần thực hiện các cấu hình này thôi, ứng dụng của chúng ta đã bắt đầu chạy và chúng ta có thể sử dụng nó cho các hoạt động cơ sở dữ liệu khác.

Cấu hình rõ ràng mà chúng ta đã thấy trong phần trước cho một ứng dụng Spring tiêu chuẩn hiện được bao gồm như một phần của cấu hình tự động Spring Boot.

8. Kết luận

Trong bài viết này, chúng ta đã xem xét phần tóm tắt của JDBC trong Spring Framework, bao gồm các khả năng khác nhau được cung cấp bởi Spring JDBC với các ví dụ thực tế.

Ngoài ra, chúng tôi đã xem xét cách chúng tôi có thể nhanh chóng bắt đầu với Spring JDBC bằng cách sử dụng trình khởi động JDBC Spring Boot.

Mã nguồn cho các ví dụ có sẵn trên GitHub.