Перейти к основному содержимому

Весна JDBC

· 7 мин. чтения

Упражнение: Сложение двух чисел

Даны два неотрицательный целых числа в виде непустых связных списков. Их цифры хранятся в обратном порядке. И каждый елемент списка содержить ровно одну цифру. Сложите эти два числа и верните сумму в виде связного списка ...

ANDROMEDA

1. Обзор

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

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

  • core — основная функциональность JDBC. Некоторые из важных классов этого пакета включают JdbcTemplate , SimpleJdbcInsert , SimpleJdbcCall и NamedParameterJdbcTemplate .
  • datasource — служебные классы для доступа к источнику данных. Он также имеет различные реализации источников данных для тестирования кода JDBC вне контейнера Jakarta EE.
  • object — доступ к БД объектно-ориентированным способом. Это позволяет выполнять запросы и возвращать результаты в виде бизнес-объекта. Он также сопоставляет результаты запроса между столбцами и свойствами бизнес-объектов.
  • поддержка — классы поддержки для классов в основных и объектных пакетах, например, обеспечивает функциональность перевода SQLException

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

Начнем с простой настройки источника данных.

Мы будем использовать базу данных MySQL:

@Configuration
@ComponentScan("com.foreach.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 для источника данных :

<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" 
destroy-method="close">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/springjdbc"/>
<property name="username" value="guest_user"/>
<property name="password" value="guest_password"/>
</bean>

3. JdbcTemplate и выполнение запросов

3.1. Основные запросы

Шаблон JDBC — это основной API, через который мы получим доступ к большей части интересующей нас функциональности:

  • создание и закрытие соединений
  • запущенные операторы и вызовы хранимых процедур
  • перебор ResultSet и возврат результатов

Во-первых, давайте начнем с простого примера, чтобы увидеть, что может сделать JdbcTemplate :

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

А вот простой INSERT:

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

Обратите внимание на стандартный синтаксис предоставления параметров с использованием ? персонаж.

Далее давайте рассмотрим альтернативу этому синтаксису.

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

Чтобы получить поддержку именованных параметров , мы будем использовать другой шаблон JDBC, предоставляемый фреймворком — NamedParameterJdbcTemplate .

Кроме того, это обертка JbdcTemplate и предоставляет альтернативу традиционному синтаксису с использованием ? указать параметры.

Под капотом он заменяет именованные параметры на JDBC ? заполнитель и делегаты в обернутый JDCTemplate для выполнения запросов:

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

Обратите внимание, как мы используем MapSqlParameterSource для предоставления значений именованных параметров.

Давайте рассмотрим использование свойств bean-компонента для определения именованных параметров:

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 использует средство сопоставления строк для заполнения java-бина:

public class EmployeeRowMapper implements RowMapper<Employee> {
@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;
}
}

Чтобы использовать этот пользовательский транслятор исключений, нам нужно передать его в JdbcTemplate , вызвав метод setExceptionTranslator() :

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

5. Операции JDBC с использованием классов SimpleJdbc

Классы SimpleJdbc предоставляют простой способ настройки и выполнения операторов SQL. Эти классы используют метаданные базы данных для построения базовых запросов. Таким образом, классы SimpleJdbcInsert и SimpleJdbcCall предоставляют более простой способ выполнения вставок и вызовов хранимых процедур.

5.1. SimpleJdbcInsert

Давайте рассмотрим запуск простых операторов вставки с минимальной конфигурацией.

Оператор INSERT создается на основе конфигурации SimpleJdbcInsert . Все, что нам нужно, это указать имя таблицы, имена столбцов и значения.

Во-первых, давайте создадим SimpleJdbcInsert :

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

Затем давайте укажем имена и значения столбцов и запустим операцию:

public int addEmplyee(Employee emp) {
Map<String, Object> parameters = new HashMap<String, Object>();
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);
}

Кроме того, мы можем использовать API executeAndReturnKey() , чтобы позволить базе данных сгенерировать первичный ключ . Нам также нужно настроить фактический автоматически сгенерированный столбец:

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

Number id = simpleJdbcInsert.executeAndReturnKey(parameters);
System.out.println("Generated id - " + id.longValue());

Наконец, мы также можем передать эти данные, используя BeanPropertySqlParameterSource и MapSqlParameterSource .

5.2. Хранимые процедуры с SimpleJdbcCall

Давайте также рассмотрим запуск хранимых процедур.

Мы будем использовать абстракцию SimpleJdbcCall :

SimpleJdbcCall simpleJdbcCall = new SimpleJdbcCall(dataSource)
.withProcedureName("READ_EMPLOYEE");
public Employee getEmployeeUsingSimpleJdbcCall(int id) {
SqlParameterSource in = new MapSqlParameterSource().addValue("in_id", id);
Map<String, Object> 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. Пакетные операции

Еще один простой вариант использования — объединение нескольких операций вместе.

6.1. Основные пакетные операции с использованием JdbcTemplate

Используя JdbcTemplate , пакетные операции можно запускать через API batchUpdate() .

Интересной частью здесь является краткая, но очень полезная реализация BatchPreparedStatementSetter :

public int[] batchUpdateUsingJdbcTemplate(List<Employee> 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. Пакетные операции с использованием NamedParameterJdbcTemplate

У нас также есть возможность пакетных операций с API NamedParameterJdbcTemplatebatchUpdate() .

Этот API проще предыдущего. Таким образом, нет необходимости реализовывать какие-либо дополнительные интерфейсы для установки параметров, поскольку он имеет внутренний подготовленный установщик операторов для установки значений параметров.

Вместо этого значения параметров можно передать методу batchUpdate() в виде массива 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 с Spring Boot

Spring Boot предоставляет стартер spring-boot-starter-jdbc для использования JDBC с реляционными базами данных.

Как и в случае с каждым стартером Spring Boot, этот помогает нам быстро запустить наше приложение.

7.1. Зависимость от Maven

Нам понадобится зависимость spring-boot-starter-jdbc в качестве основной. Нам также понадобится зависимость для базы данных, которую мы будем использовать. В нашем случае это MySQL :

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>

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

Spring Boot автоматически настраивает источник данных для нас. Нам просто нужно указать свойства в файле свойств :

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 .