Musim semi JDBC

1. Ikhtisar

Pada artikel ini, kita akan membahas kasus penggunaan praktis dari modul Spring JDBC.

Semua kelas di Spring JDBC dibagi menjadi empat paket terpisah:

  • inti - fungsionalitas inti JDBC. Beberapa kelas penting di bawah paket ini termasuk JdbcTemplate , SimpleJdbcInsert, SimpleJdbcCall dan NamedParameterJdbcTemplate .
  • sumber data - kelas utilitas untuk mengakses sumber data. Ia juga memiliki berbagai implementasi sumber data untuk menguji kode JDBC di luar container EE Jakarta.
  • object - Akses DB dengan cara berorientasi objek. Ini memungkinkan mengeksekusi kueri dan mengembalikan hasil sebagai objek bisnis. Ini juga memetakan hasil kueri antara kolom dan properti objek bisnis.
  • support - kelas dukungan untuk kelas di bawahpaket inti dan objek . Misalnya menyediakanfungsionalitas terjemahan SQLException .

2. Konfigurasi

Untuk memulai, mari kita mulai dengan beberapa konfigurasi sederhana dari sumber data (kita akan menggunakan database 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, kita juga dapat memanfaatkan database tertanam untuk pengembangan atau pengujian - berikut adalah konfigurasi cepat yang membuat instance dari database tertanam H2 dan mengisinya 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(); } 

Terakhir - hal yang sama, tentu saja, dapat dilakukan dengan menggunakan konfigurasi XML untuk sumber data :

3. JdbcTemplate dan Running Queries

3.1. Kueri Dasar

Template JDBC adalah API utama yang akan kami gunakan untuk mengakses sebagian besar fungsionalitas yang kami minati:

  • pembuatan dan penutupan koneksi
  • mengeksekusi pernyataan dan panggilan prosedur tersimpan
  • mengulang-ulang ResultSet dan mengembalikan hasil

Pertama, mari kita mulai dengan contoh sederhana untuk melihat apa yang bisa dilakukan JdbcTemplate :

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

dan juga inilah INSERT sederhana:

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

Perhatikan sintaks standar untuk menyediakan parameter - menggunakan karakter `?`. Selanjutnya - mari kita lihat alternatif dari sintaks ini.

3.2. Kueri Dengan Parameter Bernama

Untuk mendapatkan dukungan untuk parameter bernama , kita akan menggunakan template JDBC lain yang disediakan oleh framework - NamedParameterJdbcTemplate .

Selain itu, ini membungkus JbdcTemplate dan menyediakan alternatif untuk sintaks tradisional menggunakan “ ? "Untuk menentukan parameter. Di balik terpal, itu menggantikan parameter bernama ke JDBC "?" placeholder dan delegasi ke template JDCT yang dibungkus untuk mengeksekusi kueri:

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

Perhatikan bagaimana kami menggunakan MapSqlParameterSource untuk memberikan nilai untuk parameter bernama.

Misalnya, mari kita lihat contoh di bawah ini yang menggunakan properti dari kacang untuk menentukan parameter bernama:

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 kami sekarang menggunakan implementasi BeanPropertySqlParameterSource alih-alih menentukan parameter bernama secara manual seperti sebelumnya.

3.3. Memetakan Hasil Query ke Objek Java

Fitur lain yang sangat berguna adalah kemampuan untuk memetakan hasil kueri ke objek Java - dengan mengimplementasikan antarmuka RowMapper .

Misalnya - untuk setiap baris yang dikembalikan oleh kueri, Spring menggunakan mapper baris 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, sekarang kita bisa meneruskan mapper baris ke API kueri dan mendapatkan objek Java yang terisi penuh:

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

4. Terjemahan Pengecualian

Spring hadir dengan hierarki pengecualian datanya sendiri di luar kotak - dengan DataAccessException sebagai pengecualian root - dan ini menerjemahkan semua pengecualian mentah yang mendasarinya.

Jadi kami menjaga kewarasan kami dengan tidak harus menangani pengecualian persistensi tingkat rendah dan mendapatkan keuntungan dari fakta bahwa Spring membungkus pengecualian tingkat rendah di DataAccessException atau salah satu subkelasnya.

Selain itu, ini membuat mekanisme penanganan pengecualian tidak bergantung pada database dasar yang kita gunakan.

Selain itu, SQLErrorCodeSQLExceptionTranslator default , kami juga dapat menyediakan implementasi SQLExceptionTranslator kami sendiri .

Berikut adalah contoh cepat dari implementasi khusus, menyesuaikan pesan kesalahan ketika ada pelanggaran kunci duplikat, yang menghasilkan kode kesalahan 23505 saat menggunakan 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; } }

Untuk menggunakan penerjemah pengecualian khusus ini, kita perlu meneruskannya ke JdbcTemplate dengan memanggil metode setExceptionTranslator () :

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

5. Operasi JDBC Menggunakan Kelas SimpleJdbc

Kelas SimpleJdbc menyediakan cara mudah untuk mengkonfigurasi dan menjalankan pernyataan SQL. Kelas-kelas ini menggunakan metadata database untuk membangun kueri dasar. SimpleJdbcInsert dan SimpleJdbcCall kelas menyediakan cara yang lebih mudah untuk mengeksekusi insert dan panggilan prosedur yang tersimpan.

5.1. SimpleJdbcInsert

Mari kita lihat cara menjalankan pernyataan insert sederhana dengan konfigurasi minimal.

Pernyataan INSERT dihasilkan berdasarkan konfigurasi SimpleJdbcInsert dan yang kita butuhkan hanyalah memberikan nama Tabel, nama Kolom, dan nilai.

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 saja, hanya dengan melakukan konfigurasi ini saja, aplikasi kita sudah aktif dan berjalan dan kita bisa menggunakannya untuk operasi database lainnya.

Konfigurasi eksplisit yang kita lihat di bagian sebelumnya untuk aplikasi Spring standar sekarang disertakan sebagai bagian dari konfigurasi otomatis Spring Boot.

8. Kesimpulan

Pada artikel ini, kami melihat abstraksi JDBC di Spring Framework, yang mencakup berbagai kemampuan yang disediakan oleh Spring JDBC dengan contoh praktis.

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

Kode sumber untuk contoh tersedia di GitHub.