Весенний JDBC – тема этого руководства. Базы данных являются неотъемлемой частью большинства предприятий. Поэтому, когда речь идет о фреймворке Java EE, важна хорошая интеграция с JDBC.
Spring JDBC
Spring Framework обеспечивает отличную интеграцию с JDBC API и предоставляет утилитарный класс
JdbcTemplate
, который мы можем использовать, чтобы избежать шаблонного кода в логике операций с базой данных, таких как открытие/закрытие соединения, ResultSet, PreparedStatement и т. д. Давайте сначала рассмотрим простой пример приложения Spring JDBC, а затем увидим, как класс JdbcTemplate может помочь нам писать модульный код с легкостью, не беспокоясь о том, правильно ли закрыты ресурсы или нет. Инструмент Spring Tool Suite для разработки приложений на основе Spring очень полезен, поэтому мы будем использовать STS для создания нашего приложения Spring JDBC. Структура нашего окончательного проекта будет выглядеть как на изображении ниже. Создайте простой проект Spring Maven из меню STS, вы можете выбрать любое имя или придерживаться моего названия проекта, как SpringJDBCExample.
Зависимости Spring JDBC
Прежде всего, нам нужно включить зависимости Spring JDBC и драйверы базы данных в файл pom.xml проекта Maven. Мой окончательный файл pom.xml выглядит следующим образом.
<project xmlns="https://maven.apache.org/POM/4.0.0" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.springframework.samples</groupId>
<artifactId>SpringJDBCExample</artifactId>
<version>0.0.1-SNAPSHOT</version>
<properties>
<!-- Generic properties -->
<java.version>1.6</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<!-- Spring -->
<spring-framework.version>4.0.2.RELEASE</spring-framework.version>
<!-- Logging -->
<logback.version>1.0.13</logback.version>
<slf4j.version>1.7.5</slf4j.version>
</properties>
<dependencies>
<!-- Spring and Transactions -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring-framework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${spring-framework.version}</version>
</dependency>
<!-- Spring JDBC Support -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring-framework.version}</version>
</dependency>
<!-- MySQL Driver -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.0.5</version>
</dependency>
<!-- Logging with SLF4J & LogBack -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>${logback.version}</version>
<scope>runtime</scope>
</dependency>
</dependencies>
</project>
Большая часть автоматически создается STS, однако я обновил версию Spring Framework до последней, а именно 4.0.2.RELEASE. Также мы добавили необходимые артефакты spring-jdbc и mysql-connector-java. Первый содержит классы поддержки Spring JDBC, а второй – драйвер базы данных. Для тестирования мы используем базу данных MySQL, поэтому добавили зависимости от JConnector для MySQL. Если вы используете другую СУБД, то соответствующим образом измените зависимости.
Пример Spring JDBC – Настройка базы данных
Давайте создадим простую таблицу, которую мы будем использовать в нашем приложении для примера операций CRUD.
CREATE TABLE `Employee` (
`id` int(11) unsigned NOT NULL,
`name` varchar(20) DEFAULT NULL,
`role` varchar(20) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Пример Spring JDBC – Класс модели
Мы будем использовать паттерн DAO для операций с JDBC, поэтому давайте создадим java bean, которая будет моделировать нашу таблицу Employee.
package com.journaldev.spring.jdbc.model;
public class Employee {
private int id;
private String name;
private String role;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getRole() {
return role;
}
public void setRole(String role) {
this.role = role;
}
@Override
public String toString(){
return "{ID="+id+",Name="+name+",Role="+role+"}";
}
}
Пример Spring JDBC – Интерфейс и реализация DAO
Для шаблона DAO мы сначала создадим интерфейс, объявляющий все операции, которые мы хотим реализовать.
package com.journaldev.spring.jdbc.dao;
import java.util.List;
import com.journaldev.spring.jdbc.model.Employee;
// CRUD операции
public interface EmployeeDAO {
//Create
public void save(Employee employee);
//Read
public Employee getById(int id);
//Update
public void update(Employee employee);
//Delete
public void deleteById(int id);
// Получить все
public List getAll();
}
package com.journaldev.spring.jdbc.dao;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import javax.sql.DataSource;
import com.journaldev.spring.jdbc.model.Employee;
public class EmployeeDAOImpl implements EmployeeDAO {
private DataSource dataSource;
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
@Override
public void save(Employee employee) {
String query = "insert into Employee (id, name, role) values (?,?,?)";
Connection con = null;
PreparedStatement ps = null;
try{
con = dataSource.getConnection();
ps = con.prepareStatement(query);
ps.setInt(1, employee.getId());
ps.setString(2, employee.getName());
ps.setString(3, employee.getRole());
int out = ps.executeUpdate();
if(out !=0){
System.out.println("Employee saved with id="+employee.getId());
}else System.out.println("Employee save failed with id="+employee.getId());
}catch(SQLException e){
e.printStackTrace();
}finally{
try {
ps.close();
con.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
@Override
public Employee getById(int id) {
String query = "select name, role from Employee where id = ?";
Employee emp = null;
Connection con = null;
PreparedStatement ps = null;
ResultSet rs = null;
try{
con = dataSource.getConnection();
ps = con.prepareStatement(query);
ps.setInt(1, id);
rs = ps.executeQuery();
if(rs.next()){
emp = new Employee();
emp.setId(id);
emp.setName(rs.getString("name"));
emp.setRole(rs.getString("role"));
System.out.println("Employee Found::"+emp);
}else{
System.out.println("No Employee found with id="+id);
}
}catch(SQLException e){
e.printStackTrace();
}finally{
try {
rs.close();
ps.close();
con.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
return emp;
}
@Override
public void update(Employee employee) {
String query = "update Employee set name=?, role=? where id=?";
Connection con = null;
PreparedStatement ps = null;
try{
con = dataSource.getConnection();
ps = con.prepareStatement(query);
ps.setString(1, employee.getName());
ps.setString(2, employee.getRole());
ps.setInt(3, employee.getId());
int out = ps.executeUpdate();
if(out !=0){
System.out.println("Employee updated with id="+employee.getId());
}else System.out.println("No Employee found with id="+employee.getId());
}catch(SQLException e){
e.printStackTrace();
}finally{
try {
ps.close();
con.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
@Override
public void deleteById(int id) {
String query = "delete from Employee where id=?";
Connection con = null;
PreparedStatement ps = null;
try{
con = dataSource.getConnection();
ps = con.prepareStatement(query);
ps.setInt(1, id);
int out = ps.executeUpdate();
if(out !=0){
System.out.println("Employee deleted with id="+id);
}else System.out.println("No Employee found with id="+id);
}catch(SQLException e){
e.printStackTrace();
}finally{
try {
ps.close();
con.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
@Override
public List<Employee> getAll() {
String query = "select id, name, role from Employee";
List<Employee> empList = new ArrayList<Employee>();
Connection con = null;
PreparedStatement ps = null;
ResultSet rs = null;
try{
con = dataSource.getConnection();
ps = con.prepareStatement(query);
rs = ps.executeQuery();
while(rs.next()){
Employee emp = new Employee();
emp.setId(rs.getInt("id"));
emp.setName(rs.getString("name"));
emp.setRole(rs.getString("role"));
empList.add(emp);
}
}catch(SQLException e){
e.printStackTrace();
}finally{
try {
rs.close();
ps.close();
con.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
return empList;
}
}
Реализация операций CRUD проста в понимании. Если вы хотите узнать больше о DataSource, пожалуйста, прочтите Пример JDBC DataSource.
Пример Spring JDBC – Конфигурация бина
Если вы посмотрите на все классы выше, они все используют стандартный JDBC API, и нет ссылки на фреймворк Spring JDBC. Классы фреймворка Spring JDBC появляются в образе, когда мы создаем файл конфигурации Spring Bean и определяем бины. Мы создадим DataSource в файле контекста Spring Bean и установим его в наш класс реализации DAO. Мой файл конфигурации Spring Bean выглядит следующим образом.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="https://www.springframework.org/schema/beans"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="employeeDAO" class="com.journaldev.spring.jdbc.dao.EmployeeDAOImpl">
<property name="dataSource" ref="dataSource" />
</bean>
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/TestDB" />
<property name="username" value="pankaj" />
<property name="password" value="pankaj123" />
</bean>
</beans>
Во-первых, мы создаем объект DataSource класса DriverManagerDataSource. Этот класс предоставляет базовую реализацию DataSource, которую мы можем использовать. Мы передаем URL базы данных MySQL, имя пользователя и пароль в качестве свойств бина DataSource. Затем бин dataSource устанавливается для бина EmployeeDAOImpl, и мы готовы с нашей реализацией Spring JDBC. Реализация слабо связана, и если мы захотим переключиться на другую реализацию или перейти на другой сервер базы данных, все, что нам нужно, – сделать соответствующие изменения в конфигурации бинов. Это одно из основных преимуществ, предоставляемых фреймворком Spring JDBC.
Класс тестирования Spring JDBC
Давайте напишем простой тестовый класс, чтобы убедиться, что все работает правильно.
package com.journaldev.spring.jdbc.main;
import java.util.List;
import java.util.Random;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.journaldev.spring.jdbc.dao.EmployeeDAO;
import com.journaldev.spring.jdbc.model.Employee;
public class SpringMain {
public static void main(String[] args) {
// Получаем контекст Spring
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("spring.xml");
// Получаем бин EmployeeDAO
EmployeeDAO employeeDAO = ctx.getBean("employeeDAO", EmployeeDAO.class);
// Запускаем некоторые тесты для операций CRUD JDBC
Employee emp = new Employee();
int rand = new Random().nextInt(1000);
emp.setId(rand);
emp.setName("Pankaj");
emp.setRole("Java Developer");
//Create
employeeDAO.save(emp);
//Read
Employee emp1 = employeeDAO.getById(rand);
System.out.println("Employee Retrieved::"+emp1);
//Update
emp.setRole("CEO");
employeeDAO.update(emp);
// Получаем все
List empList = employeeDAO.getAll();
System.out.println(empList);
//Delete
employeeDAO.deleteById(rand);
// Закрываем контекст Spring
ctx.close();
System.out.println("DONE");
}
}
I am using Random Class to generate random number for employee id. When we run above program, we get following output.
Mar 25, 2014 12:54:18 PM org.springframework.context.support.ClassPathXmlApplicationContext prepareRefresh
INFO: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@4b9af9a9: startup date [Tue Mar 25 12:54:18 PDT 2014]; root of context hierarchy
Mar 25, 2014 12:54:18 PM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [spring.xml]
Mar 25, 2014 12:54:19 PM org.springframework.jdbc.datasource.DriverManagerDataSource setDriverClassName
INFO: Loaded JDBC driver: com.mysql.jdbc.Driver
Employee saved with id=726
Employee Found::{ID=726,Name=Pankaj,Role=Java Developer}
Employee Retrieved::{ID=726,Name=Pankaj,Role=Java Developer}
Employee updated with id=726
[{ID=726,Name=Pankaj,Role=CEO}]
Employee deleted with id=726
Mar 25, 2014 12:54:19 PM org.springframework.context.support.ClassPathXmlApplicationContext doClose
INFO: Closing org.springframework.context.support.ClassPathXmlApplicationContext@4b9af9a9: startup date [Tue Mar 25 12:54:18 PDT 2014]; root of context hierarchy
DONE
Пример Spring JdbcTemplate
Если вы посмотрите на класс реализации DAO, там много шаблонного кода, где мы открываем и закрываем Connection, PreparedStatements и ResultSet. Это может привести к утечке ресурсов, если кто-то забудет правильно закрыть ресурсы. Мы можем использовать класс \code{org.springframework.jdbc.core.JdbcTemplate}, чтобы избежать этих ошибок. Spring JdbcTemplate – это центральный класс в базовом пакете Spring JDBC, который предоставляет множество методов для выполнения запросов и автоматического разбора ResultSet для получения объекта или списка объектов. Все, что нам нужно, – это предоставить аргументы в виде массива объектов и реализовать интерфейсы обратного вызова, такие как \code{PreparedStatementSetter} и \code{RowMapper}, для сопоставления аргументов или преобразования данных ResultSet в объекты бина. Давайте посмотрим на другую реализацию EmployeeDAO, где мы будем использовать класс Spring JdbcTemplate для выполнения различных типов запросов.
package com.journaldev.spring.jdbc.dao;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import javax.sql.DataSource;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import com.journaldev.spring.jdbc.model.Employee;
public class EmployeeDAOJDBCTemplateImpl implements EmployeeDAO {
private DataSource dataSource;
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
@Override
public void save(Employee employee) {
String query = "insert into Employee (id, name, role) values (?,?,?)";
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
Object[] args = new Object[] {employee.getId(), employee.getName(), employee.getRole()};
int out = jdbcTemplate.update(query, args);
if(out !=0){
System.out.println("Employee saved with id="+employee.getId());
}else System.out.println("Employee save failed with id="+employee.getId());
}
@Override
public Employee getById(int id) {
String query = "select id, name, role from Employee where id = ?";
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
//используя анонимный класс RowMapper, мы можем создать отдельный RowMapper для повторного использования
Employee emp = jdbcTemplate.queryForObject(query, new Object[]{id}, new RowMapper(){
@Override
public Employee mapRow(ResultSet rs, int rowNum)
throws SQLException {
Employee emp = new Employee();
emp.setId(rs.getInt("id"));
emp.setName(rs.getString("name"));
emp.setRole(rs.getString("role"));
return emp;
}});
return emp;
}
@Override
public void update(Employee employee) {
String query = "update Employee set name=?, role=? where id=?";
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
Object[] args = new Object[] {employee.getName(), employee.getRole(), employee.getId()};
int out = jdbcTemplate.update(query, args);
if(out !=0){
System.out.println("Employee updated with id="+employee.getId());
}else System.out.println("No Employee found with id="+employee.getId());
}
@Override
public void deleteById(int id) {
String query = "delete from Employee where id=?";
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
int out = jdbcTemplate.update(query, id);
if(out !=0){
System.out.println("Employee deleted with id="+id);
}else System.out.println("No Employee found with id="+id);
}
@Override
public List getAll() {
String query = "select id, name, role from Employee";
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
List empList = new ArrayList();
List
Важные моменты для анализа вышеуказанного кода для Spring JdbcTemplate:
- Использование массива объектов для передачи аргументов PreparedStatement, мы также могли бы использовать реализацию PreparedStatementSetter, но передача массива объектов кажется более удобной.
- Отсутствие кода, связанного с открытием и закрытием соединений, операторов или набора результатов. Все это обрабатывается внутренне классом Spring JdbcTemplate.
- Реализация анонимного класса RowMapper для сопоставления данных ResultSet с объектом бина Employee в методе \diy7{queryForObject()}.
- Метод \diy9{queryForList()} возвращает список карт, где карта содержит данные строки, сопоставленные с ключом как именем столбца и значением из строки базы данных, соответствующей критериям.
Для использования реализации Spring JdbcTemplate всё, что нам нужно сделать, это изменить класс employeeDAO в файле конфигурации Spring Bean, как показано ниже. Когда вы запустите основной класс, вывод реализации Spring JdbcTemplate будет аналогичен тому, что было показано выше с обычной реализацией JDBC. Вот и всё для учебного примера Spring JDBC. Загрузите образец проекта по ссылке ниже и поиграйтесь с ним, чтобы узнать больше.
<bean id="employeeDAO" class="com.journaldev.spring.jdbc.dao.EmployeeDAOJDBCTemplateImpl">
<property name="dataSource" ref="dataSource" />
</bean>
Source:
https://www.digitalocean.com/community/tutorials/spring-jdbc-example