В этом руководстве узнаем, как использовать JdbcTemplate для запроса или сохранения данных в существующих таблицах базы данных, а затем предоставлять эти операции через REST API.
Используя JdbcTemplate, можно запускать запросы и хранимые процедуры из кода Java, что обеспечивает низкоуровневый доступ к базе данных.
Будем использовать учебную базу данных MySQL classicmodels. Ее можно найти здесь. Можно загрузить базу данных с этого сайта вместе с информацией об установке. Эта база данных на самом деле содержит несколько таблиц, но в этом руководстве будем использовать только таблицу Customers.
Создание проекта Spring Boot
Чтобы создать новый проект Spring Boot, воспользуемся Spring Initializr (https://start.spring.io/), который сгенерирует базовую структуру проекта Spring Boot. Вот pom.xml этого проекта:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.9</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.raven</groupId>
<artifactId>spring-boot-jdbctemplate</artifactId>
<version>1.0.0-SNAPSHOT</version>
<name>spring-boot-jdbctemplate</name>
<description>Spring Boot project with JDBCTemplate</description>
<properties>
<java.version>11</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-rest</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springdoc/springdoc-openapi-ui -->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-ui</artifactId>
<version>1.6.9</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Были добавлены следующие зависимости:
- Spring Boot DevTools – необходимые инструменты разработки;
- Rest Repositories – для предоставления репозиториев Spring Data через REST;
- JDBC API – API подключения к базе данных, который определяет, как клиент может подключаться и запрашивать базу данных;
- MySQL Driver – драйвер JDBC для MySQL (для другой БД необходимо выбрать соответствующую зависимость);
- Springdoc OpenAPI – для пользовательского интерфейса Swagger.
Подключение к базе данных
Установим некоторые настройки подключения к MySQL в файле application.properties:
application-description=@project.description@
application-version=@project.version@
logging.level.org.springframework.boot.autoconfigure=ERROR
api.response-codes.ok.desc=OK
api.response-codes.badRequest.desc=BAD_REQUEST
api.response-codes.notFound.desc=NOT_FOUND
## first db
spring.datasource.jdbcUrl=jdbc:mysql://172.17.0.2:3306/classicmodels?useSSL=false
spring.datasource.username=root
spring.datasource.password=admin@123
Конфигурация JdbcTemplate
Для настройки JdbcTemplate создали класс ApplicationConfiguration.java в пакете config:
package com.raven.jdbctemplate.config;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import javax.sql.DataSource;
@Configuration
public class ApplicationConfiguration {
@Bean(name = "dataSource")
@ConfigurationProperties(prefix = "spring.datasource")
public DataSource dataSource() {
return DataSourceBuilder.create().build();
}
@Bean(name = "jdbcTemplate")
public JdbcTemplate jdbcTemplate1(@Qualifier("dataSource") DataSource ds) {
return new JdbcTemplate(ds);
}
}
Мы аннотировали этот класс с помощью @Configuration, что указывает на то, что этот класс имеет один или несколько бинов, которые можно использовать во всем проекте.
Мы объявили два бина (dataSource и jdbcTemplate), используя аннотацию @Bean. Этими бинами будет управлять контекст Spring, поэтому нам не нужно создавать новый экземпляр всякий раз, когда хотим его использовать.
Аннотация @ConfigurationProperties используется для чтения значений свойств spring.datasource из файла application.properties и построения источника данных.
Используя аннотацию @Qualifier, указываем бин datasource в качестве DataSource для создания нового экземпляра JdbcTemplate.
Модель клиента
Сначала создаем CustomerModel, которая будет содержать данные таблицы клиентов. Вот класс CustomerModel в пакете model:
package com.raven.jdbctemplate.model;
public class CustomerModel {
private int customerNumber = 0;
private String customerName = "";
private String phone = "";
private String address1 = "";
private String city = "";
private String country = "";
public CustomerModel() {
super();
}
public CustomerModel(int customerNumber, String customerName, String phone, String address1, String city,
String country) {
super();
this.customerNumber = customerNumber;
this.customerName = customerName;
this.phone = phone;
this.address1 = address1;
this.city = city;
this.country = country;
}
public int getCustomerNumber() {
return customerNumber;
}
public void setCustomerNumber(int customerNumber) {
this.customerNumber = customerNumber;
}
public String getCustomerName() {
return customerName;
}
public void setCustomerName(String customerName) {
this.customerName = customerName;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public String getAddress1() {
return address1;
}
public void setAddress1(String address1) {
this.address1 = address1;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public String getCountry() {
return country;
}
public void setCountry(String country) {
this.country = country;
}
@Override
public String toString() {
return "CustomerModel [customerNumber=" + customerNumber + ", customerName=" + customerName + ", phone=" + phone
+ ", address1=" + address1 + ", city=" + city + ", country=" + country + "]";
}
}
Мы используем некоторые столбцы для сопоставления в CustomerModel, но можно добавить к сопоставлению другие столбцы из таблицы Customer.
Repository
Интерфейс CustomerRepository в пакете repository, который содержит список абстрактных методов, которые будут реализованы для выполнения CRUD-операций базы данных:
package com.raven.jdbctemplate.repository;
import java.util.List;
import java.util.Optional;
import com.raven.jdbctemplate.model.CustomerModel;
public interface CustomerRepository {
// gets the total record count
int count();
// saves a customer
int saveCustomer(CustomerModel customerModel);
// updates an existing customer
int updateCustomer(CustomerModel customerModel, int id);
// deletes ann existing customer
int deleteCustomer(int id);
// get all customer
List<CustomerModel> findAll();
// get a customer by CustomerNumber
Optional<CustomerModel> findByCustomerNumber(int id);
}
Реализация Repository
Теперь создаем класс CustomerJDBCRepository в пакете repository. Этот класс будет реализовывать интерфейс CustomerRepository и переопределять все методы работы CRUD:
package com.raven.jdbctemplate.repository;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import java.util.Optional;
import com.raven.jdbctemplate.model.CustomerModel;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Repository;
@Repository
public class CustomerJDBCRepository implements CustomerRepository {
@Qualifier("jdbcTemplate")
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public int count() {
return jdbcTemplate
.queryForObject("select count(*) from customers", Integer.class);
}
@Override
public int saveCustomer(CustomerModel customerModel) {
String sql = "INSERT INTO customers(customerName, contactLastName, contactFirstName, phone, addressLine1, city, country) " +
"VALUES(?,?,?,?,?,?,?)";
return jdbcTemplate.update(sql, customerModel.getCustomerName(),
customerModel.getCustomerName().split(" ")[1],
customerModel.getCustomerName().split(" ")[0],
customerModel.getPhone(),
customerModel.getAddress1(),
customerModel.getCity(),
customerModel.getCountry());
}
@Override
public int updateCustomer(CustomerModel customerModel, int id) {
String sql = "UPDATE customers " +
"SET customerName= ?, contactLastName= ?, contactFirstName= ?, phone=?, addressLine1=?, " +
"city= ?, country= ? WHERE customerNumber= ?;";
return jdbcTemplate.update(sql, customerModel.getCustomerName(),
customerModel.getCustomerName().split(" ")[1],
customerModel.getCustomerName().split(" ")[0],
customerModel.getPhone(),
customerModel.getAddress1(),
customerModel.getCity(),
customerModel.getCountry(),
id);
}
@Override
public int deleteCustomer(int id) {
String sql = "DELETE FROM customers WHERE customerNumber = ?";
return jdbcTemplate.update(sql, id);
}
@Override
public List<CustomerModel> findAll() {
String sql = "select customerNumber, customerName, phone, addressLine1, city, country from customers";
return jdbcTemplate.query(sql, new CustomerRowMapper());
}
@Override
public Optional<CustomerModel> findByCustomerNumber(int id) {
String sql = "select customerNumber, customerName, phone, addressLine1, city, country from customers where customerNumber = ?";
return jdbcTemplate.query(sql, new CustomerRowMapper(), id).stream().findFirst();
}
private class CustomerRowMapper implements RowMapper<CustomerModel> {
@Override
public CustomerModel mapRow(ResultSet rs, int rowNum) throws SQLException {
return new CustomerModel(rs.getInt("customerNumber"),
rs.getString("customerName"),
rs.getString("phone"),
rs.getString("addressLine1"),
rs.getString("city"),
rs.getString("country"));
}
}
}
Внутри этого класса объявили внутренний класс CustomerRowMapper, реализующий интерфейс RowMapper. RowMapper предоставляет метод для сопоставления строк с объектом, поэтому переопределяем метод mapRow(), который сопоставляет строки с CustomerModel из ResultSet. CustomerRowMapper используется для извлечения из базы данных информации об одном клиенте или списка данных о клиентах.
jdbcTemplate.queryForObject() используется для получения одной строки или значения. Здесь получаем только значение count(*).
Для извлечения данных использовали метод query() JdbcTemplate, который извлекает записи из базы данных на основе SQL-запроса и сопоставляет запись с объектом CustomerModel с помощью CustomerRowMapper.
Функция update() JdbcTemplate используется для выполнения операций вставки, обновления и удаления и принимает в качестве аргументов строку, содержащую SQL-запрос и значения.
Controller
Создадим класс CustomerController в пакете controller:
package com.raven.jdbctemplate.controller;
import com.raven.jdbctemplate.model.CustomerModel;
import com.raven.jdbctemplate.repository.CustomerJDBCRepository;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.ArraySchema;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.ExampleObject;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.parameters.RequestBody;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.List;
import java.util.Optional;
@RestController
@RequestMapping("/api/customer")
@Tag(description = "APIs related with Customers", name = "Customer")
public class CustomerController {
private CustomerJDBCRepository customerJDBCRepository;
@Autowired
public CustomerController(CustomerJDBCRepository customerJDBCRepository) {
this.customerJDBCRepository = customerJDBCRepository;
}
@Operation(summary = "Total record count",
description = "Get total Customer count")
@ApiResponses(value = {
@ApiResponse(responseCode = "200",
description = "OK",
content = {@Content(mediaType = "application/json",
array = @ArraySchema(schema = @Schema(implementation = CustomerModel.class)))}),
@ApiResponse(responseCode = "400",
description = "BAD_REQUEST"),
@ApiResponse(responseCode = "404",
description = "NOT_FOUND")})
@RequestMapping(value = "/getRecordNumber", method = RequestMethod.GET)
@ResponseBody
public int getRecordNumber() {
return customerJDBCRepository.count();
}
@Operation(summary = "All customer",
description = "Get all customer details")
@ApiResponses(value = {
@ApiResponse(responseCode = "200",
description = "OK",
content = {@Content(mediaType = "application/json",
array = @ArraySchema(schema = @Schema(implementation = CustomerModel.class)))}),
@ApiResponse(responseCode = "400",
description = "BAD_REQUEST"),
@ApiResponse(responseCode = "404",
description = "NOT_FOUND")})
@RequestMapping(value = "/getAllCustomer", method = RequestMethod.GET)
@ResponseBody
public List<CustomerModel> getAllCustomer() {
return customerJDBCRepository.findAll();
}
@Operation(summary = "Customer details by customer number",
description = "Customer details by customer number")
@ApiResponses(value = {
@ApiResponse(responseCode = "200",
description = "OK",
content = {@Content(mediaType = "application/json",
array = @ArraySchema(schema = @Schema(implementation = CustomerModel.class)))}),
@ApiResponse(responseCode = "400",
description = "BAD_REQUEST"),
@ApiResponse(responseCode = "404",
description = "NOT_FOUND")})
@RequestMapping(value = "/{id}", method = RequestMethod.GET)
@ResponseBody
public Optional<CustomerModel> getCustomerByNumber(@PathVariable("id") int id) {
return customerJDBCRepository.findByCustomerNumber(id);
}
@Operation(summary = "Save customer details",
description = "Save customer details")
@ApiResponses(value = {
@ApiResponse(responseCode = "200",
description = "OK"),
@ApiResponse(responseCode = "400",
description = "BAD_REQUEST"),
@ApiResponse(responseCode = "404",
description = "NOT_FOUND")})
@RequestMapping(value = "/save", method = RequestMethod.POST)
@ResponseBody
public int save(@Valid @RequestBody CustomerModel customerModel) {
return customerJDBCRepository.saveCustomer(customerModel);
}
@Operation(summary = "Update customer details",
description = "Update customer details using customer number")
@ApiResponses(value = {
@ApiResponse(responseCode = "200",
description = "OK"),
@ApiResponse(responseCode = "400",
description = "BAD_REQUEST"),
@ApiResponse(responseCode = "404",
description = "NOT_FOUND")})
@RequestMapping(value = "/update/{id}", method = RequestMethod.PUT)
@ResponseBody
public int update(@Valid @RequestBody CustomerModel customerModel, @PathVariable("id") int id) {
return customerJDBCRepository.updateCustomer(customerModel, id);
}
@Operation(summary = "Delete a customer",
description = "Delete a customer using customer number")
@ApiResponses(value = {
@ApiResponse(responseCode = "200",
description = "OK"),
@ApiResponse(responseCode = "400",
description = "BAD_REQUEST"),
@ApiResponse(responseCode = "404",
description = "NOT_FOUND")})
@RequestMapping(value = "/delete/{id}", method = RequestMethod.DELETE)
@ResponseBody
public int delete(@PathVariable("id") int id) {
return customerJDBCRepository.deleteCustomer(id);
}
}
Класс контроллера помечен аннотацией @RestController. Это сообщает Spring Boot, что это служба на основе REST, и она автоматически сериализует или десериализует данные запроса/ответа в JSON.
Мы указали базовый путь запроса как /api/customer в @RequestMapping. Аннотация @RequestMapping сообщает контейнеру Spring, что к этой службе может получить доступ указанная конечная точка HTTP.
@Tag – это аннотация Open API, используемая для настройки сведений о ресурсе, таких как имя ресурса, описание.
@Operation также является аннотацией Open API, используемой для настройки имени и описания API.
@ApiResponses – это еще одна аннотация Open API для указания формата ответа API, такого как форматы ответа об успехе и ошибке и т. д.
Пользовательский интерфейс Swagger доступен по адресу http://localhost:8080/swagger-ui/index.html, где можно увидеть список API:

Исходный код можно скачать здесь.