Пролетен JDBC

1. Общ преглед

В тази статия ще разгледаме практически случаи на използване на Spring JDBC модула.

Всички класове в Spring JDBC са разделени на четири отделни пакета:

  • ядро - основната функционалност на JDBC. Някои от важните класове по този пакет включват JdbcTemplate , SimpleJdbcInsert, SimpleJdbcCall и NamedParameterJdbcTemplate .
  • datasource - класове помощни програми за достъп до източник на данни. Той също така има различни изпълнения на източници на данни за тестване на JDBC код извън контейнера на Джакарта EE.
  • обект - достъп до DB по обектно-ориентиран начин. Позволява изпълнение на заявки и връщане на резултатите като бизнес обект. Той също така картографира резултатите от заявката между колоните и свойствата на бизнес обектите.
  • поддръжка - класове за поддръжка на класове под ядро и обектни пакети. Например предоставя функцията запреводна SQLException .

2. Конфигурация

Като начало, нека започнем с някаква проста конфигурация на източника на данни (за този пример ще използваме база данни MySQL):

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

Освен това можем да използваме добре вградена база данни за разработка или тестване - ето бърза конфигурация, която създава екземпляр на вградена база данни H2 и я попълва предварително с прости SQL скриптове:

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

И накрая - същото, разбира се, може да се направи с помощта на XML конфигуриране за източника на данни :

3. JdbcTemplate и изпълняващи се заявки

3.1. Основни заявки

Шаблонът JDBC е основният API, чрез който ще имаме достъп до повечето функционалности, които ни интересуват:

  • създаване и затваряне на връзки
  • изпълнение на оператори и съхранени процедурни повиквания
  • итерация над ResultSet и връщане на резултатите

Първо, нека започнем с прост пример, за да видим какво може да направи JdbcTemplate :

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

а също и тук е просто ВМЪКНЕТЕ:

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

Забележете стандартния синтаксис на предоставяне на параметри - като използвате символа `?`. След това - нека разгледаме алтернатива на този синтаксис.

3.2. Заявки с наименовани параметри

За да получим поддръжка за наименувани параметри , ще използваме другия JDBC шаблон, предоставен от рамката - NamedParameterJdbcTemplate .

Освен това това обгръща JbdcTemplate и предоставя алтернатива на традиционния синтаксис, използвайки “ ? ”, За да зададете параметри. Под капака той замества посочените параметри на JDBC „?“ placeholder и делегира на опакования JDCTemplate за изпълнение на заявките:

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

Забележете как използваме MapSqlParameterSource, за да предоставим стойностите за посочените параметри.

Например, нека разгледаме примера по-долу, който използва свойства от боб, за да определи имената на параметрите:

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

Обърнете внимание как сега използваме изпълненията на BeanPropertySqlParameterSource, вместо да задаваме посочените параметри ръчно, както преди.

3.3. Съпоставяне на резултатите от заявката с Java обект

Друга много полезна функция е възможността за съпоставяне на резултатите от заявките с обекти на Java - чрез внедряване на интерфейса RowMapper .

Например - за всеки ред, върнат от заявката, Spring използва mapper на редове, за да попълни 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; } }

Впоследствие вече можем да предадем преобразувателя на редове в API на заявката и да получим напълно попълнени Java обекти:

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

4. Превод на изключения

Spring идва със собствена йерархия на изключенията за данни - с DataAccessException като основно изключение - и тя превежда всички основни необработени изключения към нея.

И така, ние запазваме здравия си разум, като не се налага да се справяме с изключенията за устойчивост на ниско ниво и се възползваме от факта, че Spring обгръща изключенията на ниско ниво в DataAccessException или един от неговите подкласове.

Освен това това запазва механизма за обработка на изключенията независим от основната база данни, която използваме.

Освен това по подразбиране SQLErrorCodeSQLExceptionTranslator , ние също така можем да предоставим собствена реализация на SQLExceptionTranslator .

Ето бърз пример за персонализирана реализация, персонализиране на съобщението за грешка, когато има дублиране на нарушение на ключ, което води до код на грешка 23505 при използване на 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

Това е всичко, само като правим само тези конфигурации, нашето приложение работи и можем да го използваме за други операции с база данни.

Изричната конфигурация, която видяхме в предишния раздел за стандартно приложение Spring вече е включена като част от автоматичното конфигуриране на Spring Boot.

8. Заключение

В тази статия разгледахме абстракцията на JDBC в Spring Framework, обхващайки различните възможности, предоставени от Spring JDBC с практически примери.

Също така разгледахме как можем бързо да започнем с Spring JDBC с помощта на Spring Boot JDBC стартер.

Изходният код за примерите е достъпен в GitHub.