Musim bunga JDBC

1. Gambaran keseluruhan

Dalam artikel ini, kita akan membahas kes penggunaan praktikal modul Spring JDBC.

Semua kelas di Spring JDBC dibahagikan kepada empat pakej berasingan:

  • teras - fungsi teras JDBC. Beberapa kelas penting di bawah pakej ini termasuk JdbcTemplate , SimpleJdbcInsert, SimpleJdbcCall dan NamedParameterJdbcTemplate .
  • sumber data - kelas utiliti untuk mengakses sumber data. Ia juga mempunyai berbagai implementasi sumber data untuk menguji kod JDBC di luar wadah Jakarta EE.
  • objek - Akses DB secara berorientasikan objek. Ini membolehkan melaksanakan pertanyaan dan mengembalikan hasilnya sebagai objek perniagaan. Ini juga memetakan hasil pertanyaan antara lajur dan sifat objek perniagaan.
  • sokongan - kelas sokongan untuk kelas di bawahpakej inti dan objek . Cth menyediakanfungsi terjemahan SQLException .

2. Konfigurasi

Sebagai permulaan, mari kita mulakan dengan beberapa konfigurasi ringkas sumber data (kami akan menggunakan pangkalan data MySQL untuk contoh ini):

@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; } }

Sebagai alternatif, kami juga dapat memanfaatkan pangkalan data tertanam untuk pengembangan atau pengujian - berikut adalah konfigurasi cepat yang membuat contoh pangkalan data tertanam H2 dan mempopularkannya dengan skrip SQL sederhana:

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

Akhirnya - perkara yang sama, tentu saja, dapat dilakukan menggunakan konfigurasi XML untuk sumber data :

3. Pertanyaan Templat dan Menjalankan Jdbc

3.1. Pertanyaan Asas

Templat JDBC adalah API utama di mana kami akan mengakses sebahagian besar fungsi yang kami minati:

  • penciptaan dan penutupan hubungan
  • melaksanakan penyataan dan panggilan prosedur tersimpan
  • berulang melalui ResultSet dan mengembalikan hasil

Pertama, mari kita mulakan dengan contoh mudah untuk melihat apa yang dapat dilakukan oleh JdbcTemplate :

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

dan juga INSERT ringkas:

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

Perhatikan sintaks standard menyediakan parameter - menggunakan watak `?`. Seterusnya - mari kita cari alternatif untuk sintaks ini.

3.2. Pertanyaan Dengan Parameter Dinamakan

Untuk mendapatkan sokongan untuk parameter bernama , kami akan menggunakan templat JDBC lain yang disediakan oleh rangka kerja - NamedParameterJdbcTemplate .

Selain itu, ini membungkus JbdcTemplate dan memberikan alternatif kepada sintaks tradisional menggunakan “ ? "Untuk menentukan parameter. Di bawah tudung, ia menggantikan parameter yang disebut dengan JDBC "?" placeholder dan perwakilan ke JDCTemplate yang dibungkus untuk melaksanakan pertanyaan:

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

Perhatikan bagaimana kita menggunakan MapSqlParameterSource untuk memberikan nilai untuk parameter yang dinamakan.

Sebagai contoh, mari kita lihat contoh di bawah yang menggunakan sifat dari kacang untuk menentukan parameter yang dinamakan:

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);

Perhatikan bagaimana kita sekarang menggunakan implementasi BeanPropertySqlParameterSource dan bukannya menentukan parameter bernama secara manual seperti sebelumnya.

3.3. Memetakan Hasil Pertanyaan ke Objek Java

Satu lagi ciri yang sangat berguna ialah kemampuan memetakan hasil pertanyaan ke objek Java - dengan menerapkan antara muka RowMapper .

Sebagai contoh - untuk setiap baris yang dikembalikan oleh pertanyaan, Spring menggunakan baris mapper untuk mengisi kacang 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; } }

Selanjutnya, kita sekarang dapat meneruskan pemetaan baris ke API pertanyaan dan mendapatkan objek Java yang lengkap:

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

4. Terjemahan Pengecualian

Spring hadir dengan hirarki pengecualian datanya sendiri - dengan DataAccessException sebagai pengecualian akar - dan ia menerjemahkan semua pengecualian mentah yang mendasarinya.

And so we keep our sanity by not having to handle low-level persistence exceptions and benefit from the fact that Spring wraps the low-level exceptions in DataAccessException or one of its sub-classes.

Also, this keeps the exception handling mechanism independent of the underlying database we are using.

Besides, the default SQLErrorCodeSQLExceptionTranslator, we can also provide our own implementation of SQLExceptionTranslator.

Here's a quick example of a custom implementation, customizing the error message when there is a duplicate key violation, which results in error code 23505 when using 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; } }

To use this custom exception translator, we need to pass it to the JdbcTemplate by calling setExceptionTranslator() method:

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

5. JDBC Operations Using SimpleJdbc Classes

SimpleJdbc classes provide an easy way to configure and execute SQL statements. These classes use database metadata to build basic queries. SimpleJdbcInsert and SimpleJdbcCall classes provide an easier way to execute insert and stored procedure calls.

5.1. SimpleJdbcInsert

Let's take a look at executing simple insert statements with minimal configuration.

The INSERT statement is generated based on the configuration of SimpleJdbcInsert and all we need is to provide the Table name, Column names and values.

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

Itu sahaja, hanya dengan melakukan konfigurasi ini sahaja, aplikasi kita sudah siap dan kita dapat menggunakannya untuk operasi pangkalan data yang lain.

Konfigurasi eksplisit yang kita lihat di bahagian sebelumnya untuk aplikasi Spring standard kini disertakan sebagai sebahagian daripada konfigurasi automatik Spring Boot.

8. Kesimpulannya

Dalam artikel ini, kami melihat abstraksi JDBC dalam Spring Framework, merangkumi pelbagai kemampuan yang disediakan oleh Spring JDBC dengan contoh praktikal.

Juga, kami melihat bagaimana kami dapat dengan cepat memulakan Spring JDBC menggunakan starter Spring Boot JDBC.

Kod sumber untuk contoh boleh didapati di GitHub.