Привет, Хабр! Представляю Вашему вниманию перевод руководства «Spring MVC + Spring Data JPA + Hibernate — CRUD Example» автора Nam Ha Minh.

В этом руководстве по Java Spring вы узнаете, как настроить Spring MVC приложение для работы с Spring Data JPA, разработав простое веб-приложение, которое позволяет управлять информацией о клиентах.

По завершению этого руководства, вы сможете создать веб-приложение Java основанное на технологиях Spring MVC и Spring Data JPA, которое выглядит следующим образом:

image

Программы и технологии используемые в этом руководстве: Java 8, Apache Tomcat 9, MySQL Server 5.7, Eclipse IDE 4.7 (Oxygen), Spring Framework 5.1, Hibernate 5.4, Spring Data JPA 2.1.5 и Servlet 3.1.

Начнём с создания базы данных.

1. Создание базы данных


Мы будем использовать MySQL. В нашем пример мы будем работать с данными в таблице customer, которая находится в схеме с именем sales. Таблица customer имеет 4 поля: id, name, email и address:

image

Вы можете запустить следующий MySQL скрипт для создания схемы и таблицы:

CREATE DATABASE `sales`;
CREATE TABLE `customer` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(45) NOT NULL,
  `email` varchar(45) NOT NULL,
  `address` varchar(45) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

2. Создание проекта в Eclipse


Создайте Dynamic Web Project в Eclipse, и конвертируйте его в проект Maven: для этого нажмите ПКМ по проекту, выберете Configure > Convert to Maven Project. В открывшемся диалоговом окне Create new POM введите следующую информацию:

— Group Id: net.codejava
— Artifact Id: CustomerManager

Также убедитесь, что версия JRE для проекта Java 8 или новее.

Затем, откройте pom.xml (файл Maven), чтобы настроить зависимости для этого проекта. Объявите свойства версий для Spring и Hibernate Frameworks:

<properties>
    <spring.version>5.1.5.RELEASE</spring.version>
    <hibernate.version>5.4.1.Final</hibernate.version>
</properties>

Укажите зависимость для Spring Framework:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>${spring.version}</version>
</dependency>

Для создания веб-приложений Spring MVC:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>${spring.version}</version>
</dependency>

Для использования Spring Data JPA:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-orm</artifactId>
    <version>${spring.version}</version>
</dependency>
<dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-jpa</artifactId>
    <version>2.1.5.RELEASE</version>
</dependency>

Мы используем Hibernate в качестве реализации JPA, поэтому добавим следующую зависимость:

<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-core</artifactId>
    <version>${hibernate.version}</version>
</dependency>

Для того, чтобы приложение работало с MySQL, нам нужна зависимость для драйвера MySQL JDBC:

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.14</version>
    <scope>runtime</scope>
</dependency>

И теперь зависимости для Java Servlet, JSP и JSTL:

<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>3.1.0</version>
    <scope>provided</scope>
</dependency>
<dependency>
    <groupId>javax.servlet.jsp</groupId>
    <artifactId>javax.servlet.jsp-api</artifactId>
    <version>2.3.1</version>
    <scope>provided</scope>
</dependency>
<dependency>
    <groupId>jstl</groupId>
    <artifactId>jstl</artifactId>
    <version>1.2</version>
</dependency>

Создайте два Java пакета в корне проекта:
net.codejava.config: для классов конфигурации.
net.codejava.customer: для классов приложения.

3. Создание файла настройки JPA


Поскольку мы используем JPA, нам нужно определить свойства для подключения к базе данных в файле persistence.xml, а не в hibernate.cfg.xml. Создайте новый каталог с именем META-INF в исходной папке проекта, чтобы поместить в него файл persistence.xml:

image

И впишите в этот файл код представленный ниже:

<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://xmlns.jcp.org/xml/ns/persistence"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence
          http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd"
    version="2.1">
     
    <persistence-unit name="SalesDB">
        <properties>
            <property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/sales" />
            <property name="javax.persistence.jdbc.user" value="root" />
            <property name="javax.persistence.jdbc.password" value="(password)" />
            <property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver" />
            <property name="hibernate.show_sql" value="true" />
            <property name="hibernate.format_sql" value="true" />
        </properties>
    </persistence-unit>
     
</persistence>

Как вы можете видеть, мы указываем свойства для соединения с базой данных, такие как URL, user, password и класс JDBC драйвера. Так же обратите внимание, что имя SalesDB будет использоваться нами в коде конфигурации.

4. Создание Model Class


Создайте класс Customer, который сопоставляется с таблицей customer в базе данных следующим образом:

package net.codejava.customer;
 
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
 
@Entity
public class Customer {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
 
    private String name;
    private String email;
    private String address;
 
    protected Customer() {
    }
 
    protected Customer(String name, String email, String address) {
        this.name = name;
        this.email = email;
        this.address = address;
    }
 
    // геттеры и сеттеры не показаны для краткости. Создайте их с помощью средств своей IDE, или вручную.
 
}

Как вы могли увидеть, мы используем аннотацию @Entity для сопоставления этого класса с таблицей customer (имя класса совпадает с именем таблицы). Все имена полей класса идентичны именам полей в таблице. Поле id имеет аннотации @Id и @GeneratedValue, чтобы указать, что это поле является первичным ключом и его значение генерируется автоматически.

5. Конфигурация Spring MVC и Spring Data JPA


Далее, напишем Java код для настройки Spring MVC и Spring Data JPA. Мы будем использовать конфигурацию на основе Java, так как она проще, чем XML.

Настройка Spring Dispatcher Servlet


Для использования Spring MVC в нашем приложении, нам надо зарегистрировать Spring Dispatcher Servlet при запуске приложения, написав следующий класс:

package net.codejava.config;
 
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRegistration;
 
import org.springframework.web.WebApplicationInitializer;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
 
public class WebAppInitializer implements WebApplicationInitializer {
    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        AnnotationConfigWebApplicationContext appContext = new AnnotationConfigWebApplicationContext();
        appContext.register(WebMvcConfig.class);
          
        ServletRegistration.Dynamic dispatcher = servletContext.addServlet(
                "SpringDispatcher", new DispatcherServlet(appContext));
        dispatcher.setLoadOnStartup(1);
        dispatcher.addMapping("/");
          
    }
}

Метод onStartup() этого класса будет автоматически вызываться сервлетом при загрузке приложения. Spring Dispatcher Servlet обарабатывает все запросы cопоставляя URL "/" и ищет конфигурацию в классе WebMvcConfig, которй описан ниже.

Настройка Spring MVC


Создайте класс WebMvcConfig в пакете net.codejava.config, содержащий следующий код:

package net.codejava.config;
 
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
 
@Configuration
@ComponentScan("net.codejava ")
public class WebMvcConfig {
    @Bean(name = "viewResolver")
    public InternalResourceViewResolver getViewResolver() {
        InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
        viewResolver.setPrefix("/WEB-INF/views/");
        viewResolver.setSuffix(".jsp");
        return viewResolver;
    }
}

Этот класс помечен аннотацией @Configuration, сообщающей Spring, что это файл конфигурации. Аннотация @ComponentScan говорит Spring искать классы конфигурации в пакете net.codejava.

В этом классе мы создаём bean-компонент, который распознаёт представления (View), с помощью указания префикса и суффикса для этих представлений. Поэтому создайте каталог views внутри каталога WebContent/WEB-INF для хранения JSP файлов.

Здесь вы можете добавить и другие конфигурации Spring MVC.

Настройка Spring Data JPA


Для работы с Spring Data JPA нам надо создать два beans-компонента: EntityManagerFactory и JpaTransactionManager. Поэтому создадим другой конфигурационный класс JpaConfig:

package net.codejava.config;
 
import javax.persistence.EntityManagerFactory;
 
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalEntityManagerFactoryBean;
import org.springframework.transaction.annotation.EnableTransactionManagement;
 
@Configuration
@EnableJpaRepositories(basePackages = {"net.codejava.customer"})
@EnableTransactionManagement
public class JpaConfig {
    @Bean
    public LocalEntityManagerFactoryBean entityManagerFactory() {
        LocalEntityManagerFactoryBean factoryBean = new LocalEntityManagerFactoryBean();
        factoryBean.setPersistenceUnitName("SalesDB");
          
        return factoryBean;
    }
      
    @Bean
    public JpaTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
        JpaTransactionManager transactionManager = new JpaTransactionManager();
        transactionManager.setEntityManagerFactory(entityManagerFactory);
          
        return transactionManager;
    } 
}

Здесь мы используем две важные аннотации:

  • @EnableJpaRepositories: сообщает Spring Data JPA, что нужно искать классы репозитория в указанном пакете (net.codejava) для внедрения соответсвующего кода во время выполнения.
  • @EnableTransactionManagement: сообщает Spring Data JPA, чтобы тот генерировал код для управления транзакциями во время выполнения.

В этом классе первый метод создаёт экземпляр EntityManagerFactory для управления Persistence Unit нашей SalesDB (это имя указано выше в persistence.xml).

Последний метод создаёт экземпляр JpaTransactionManager для EntityManagerFactory, созданный методом ранее.

Это минимальная необходимая конфигурация для использования Spring Data JPA.

Создание Repository Interface


Создайте интерфейс CustomerRepository, который расширяет определенный в Spring Data JPA интерфейс CrudRepository:

package net.codejava.customer;
 
import java.util.List;
 
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.query.Param;
 
public interface CustomerRepository extends CrudRepository<Customer, Long> {
     
}

Это почти весь код, который нам нужен для доступа к данным. Просто, согласитесь? С Spring Data JPA нам не нужно писать DAO (Java Data Acces Object) код. Просто объявите интерфейс, расширяющий интерфейс CrudRepository, в котором определены такие методы CRUD как: save(), findAll(), findById(), deleteById() и т.д. Во время выполнения Spring Data JPA автоматически сгенерирует код.

Обратите внимание, что мы должны указать тип класса модели и тип поля первичного ключа при расширении CrudRepository: CrudRepository<Customer, Long>.

7. Создание Service Class


Затем, создайте класс CustomerService:

package net.codejava.customer;
 
import java.util.List;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
 
@Service
@Transactional
public class CustomerService {
    @Autowired CustomerRepository repo;
     
    public void save(Customer customer) {
        repo.save(customer);
    }
     
    public List<Customer> listAll() {
        return (List<Customer>) repo.findAll();
    }
     
    public Customer get(Long id) {
        return repo.findById(id).get();
    }
     
    public void delete(Long id) {
        repo.deleteById(id);
    }
     
}

Обратите внимание на аннотацию @Transactional, которой помечен наш класс. Это означает, что все методы этого класса будут перехвачены Spring Data JPA для управления транзакциями. И экземпляр интерфейса CustomerRepository будет внедрён в этот класс:

@Autowired CustomerRepository repo;

Это похоже на магию, поскольку мы не пишем DAO код, но Spring Data JPA автоматически создаст реализацию во время выполнения.

Как вы можете видеть, все методы в данном классе предназначены для операций CRUD. Он просто делегирует весь вызов объекту CustomerRepository. Вам может показаться этот класс избыточным, но он необходим, для отделения уровня business/service от уровня repository/DAO.

8. Создание контроллера Spring MVC


Создайте класс CustomerContoroller для обработки всех запросов от клиентов:

package net.codejava.customer;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
 
 
@Controller
public class CustomerController {
 
    @Autowired
    private CustomerService customerService;
 
    // здесь будут методы обработки
}

Это типичный класс Spring MVC Controller, который аннотирован с помощью @Controller. Вы можете увидеть, что экземпляр CustomerService внедряется в этот объект с помощью аннотации @Autowired.

Мы напишем методы обработки в следующих секциях.

9. Добавление списка клиентов


На домашней странице нашего приложения будут отображаться все клиенты, для этого добавьте соответствующий обрабатывающий метод в наш CustomerController класс:

@RequestMapping("/")
public ModelAndView home() {
    List<Customer> listCustomer = customerService.listAll();
    ModelAndView mav = new ModelAndView("index");
    mav.addObject("listCustomer", listCustomer);
    return mav;
}

Домашняя страница просмотра (index.jsp) должна выглядеть следующим образом:

<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
    pageEncoding="ISO-8859-1"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>   
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01
    Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Customer Manager</title>
</head>
<body>
<div align="center">
    <h2>Customer Manager</h2>
    <form method="get" action="search">
        <input type="text" name="keyword" />  
        <input type="submit" value="Search" />
    </form>
    <h3><a href="/new">New Customer</a></h3>
    <table border="1" cellpadding="5">
        <tr>
            <th>ID</th>
            <th>Name</th>
            <th>E-mail</th>
            <th>Address</th>
            <th>Action</th>
        </tr>
        <c:forEach items="${listCustomer}" var="customer">
        <tr>
            <td>${customer.id}</td>
            <td>${customer.name}</td>
            <td>${customer.email}</td>
            <td>${customer.address}</td>
            <td>
                <a href="/edit?id=${customer.id}">Edit</a>
                   
                <a href="/delete?id=${customer.id}">Delete</a>
            </td>
        </tr>
        </c:forEach>
    </table>
</div>   
</body>
</html>

Теперь вы можете запустить веб-приложение. Добавьте несколько строк в таблицу customer и перейдите по адресу http://localhost:8080/CustomerManager/, и вы увидите что-то похожее:

image

10. Добавление нового пользователя


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

@RequestMapping("/new")
public String newCustomerForm(Map<String, Object> model) {
    Customer customer = new Customer();
    model.put("customer", customer);
    return "new_customer";
}

Напишем саму JSP форму с именем new_customer.jsp:

<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
    pageEncoding="ISO-8859-1"%>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%>      
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
    "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>New Customer</title>
</head>
<body>
    <div align="center">
        <h2>New Customer</h2>
        <form:form action="save" method="post" modelAttribute="customer">
            <table border="0" cellpadding="5">
                <tr>
                    <td>Name: </td>
                    <td><form:input path="name" /></td>
                </tr>
                <tr>
                    <td>Email: </td>
                    <td><form:input path="email" /></td>
                </tr>
                <tr>
                    <td>Address: </td>
                    <td><form:input path="address" /></td>
                </tr>    
                <tr>
                    <td colspan="2"><input type="submit" value="Save"></td>
                </tr>                    
            </table>
        </form:form>
    </div>
</body>
</html>

Теперь на главной странице у вас появится ссылка New Customer, при нажатии на которую вы увидите новую форму:

image

Второй метод-обработчик будет обрабатывать кнопку Save в этой форме:

@RequestMapping(value = "/save", method = RequestMethod.POST)
public String saveCustomer(@ModelAttribute("customer") Customer customer) {
    customerService.save(customer);
    return "redirect:/";
}

Как вы можете видеть, она перенаправляет клиента на домашнюю страницу, после успешного сохранения пользователя.

11. Изменение данных пользователя


Чтобы реализовать функцию редактирования клиента, добавим следующий метод-обработчик в класс CustomerController:

@RequestMapping("/edit")
public ModelAndView editCustomerForm(@RequestParam long id) {
    ModelAndView mav = new ModelAndView("edit_customer");
    Customer customer = customerService.get(id);
    mav.addObject("customer", customer);
 
    return mav;
}

Напишем форму edit_customer.jsp, которая вызывается этим методом:

<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
    pageEncoding="ISO-8859-1"%>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%>      
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
    "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Edit Customer</title>
</head>
<body>
    <div align="center">
        <h2>Edit Customer</h2>
        <form:form action="save" method="post" modelAttribute="customer">
            <table border="0" cellpadding="5">
                <tr>
                    <td>ID: </td>
                    <td>${customer.id}
                        <form:hidden path="id"/>
                    </td>
                </tr>        
                <tr>
                    <td>Name: </td>
                    <td><form:input path="name" /></td>
                </tr>
                <tr>
                    <td>Email: </td>
                    <td><form:input path="email" /></td>
                </tr>
                <tr>
                    <td>Address: </td>
                    <td><form:input path="address" /></td>
                </tr>    
                <tr>
                    <td colspan="2"><input type="submit" value="Save"></td>
                </tr>                    
            </table>
        </form:form>
    </div>
</body>
</html>

Нажмите на гиперссылку Edit рядом с клиентом на домашней странице, вызовется форма редактирования клиента, которая будет выглядеть примерно вот так:



Метод-обработчик по-прежнему обрабатывает кнопку Save.

12. Удаление клиента


Для реализации функции удаления, напишите следующий метод-обработчик в классе CustomerController:

@RequestMapping("/delete")
public String deleteCustomerForm(@RequestParam long id) {
    customerService.delete(id);
    return "redirect:/";       
}

Нажмите на гиперссылку Delete рядом с клиентом на главной странице. Клиент удалится, а список обновится.

13. Поиск по клиентам


Наконец, давайте реализуем функцию поиска, которая позволяет пользователю искать клиентов, вводя ключевое слово. Функция поиска ищет ключевые слова в трёх полях: имя, email и адрес, для чего нам потребуется написать собственный метод в интерфейсе CustomerRepository:

package net.codejava.customer;
 
import java.util.List;
 
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.query.Param;
 
public interface CustomerRepository extends CrudRepository<Customer, Long> {
     
    @Query(value = "SELECT c FROM Customer c WHERE c.name LIKE '%' || :keyword || '%'"
            + " OR c.email LIKE '%' || :keyword || '%'"
            + " OR c.address LIKE '%' || :keyword || '%'")
    public List<Customer> search(@Param("keyword") String keyword);
}

Метод search() — это просто абстрактный метод, аннотированный с помощью @Query. Поисковый запрос является запросом JPA.

Затем добавьте метод в класс CustomerService:

public List<Customer> search(String keyword) {
    return repo.search(keyword);
}

Теперь добавьте метод-обработчик в класс CustomerController:

@RequestMapping("/search")
public ModelAndView search(@RequestParam String keyword) {
    List<Customer> result = customerService.search(keyword);
    ModelAndView mav = new ModelAndView("search");
    mav.addObject("result", result);
 
    return mav;    
}

И создайте страницу результата поиска search.jsp:

<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
    pageEncoding="ISO-8859-1"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>   
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
    "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Search Result</title>
</head>
<body>
<div align="center">
    <h2>Search Result</h2>
    <table border="1" cellpadding="5">
        <tr>
            <th>ID</th>
            <th>Name</th>
            <th>E-mail</th>
            <th>Address</th>
        </tr>
        <c:forEach items="${result}" var="customer">
        <tr>
            <td>${customer.id}</td>
            <td>${customer.name}</td>
            <td>${customer.email}</td>
            <td>${customer.address}</td>
        </tr>
        </c:forEach>
    </table>
</div>   
</body>
</html>

Для тестирования функции поиска, введите ключевое слово в поле поиска на домашней странице, и нажмите Enter. Вы увидите страницу результата поиска:


Выводы


В данном руководстве вы узнали как можно разработать веб-приложение Spring MVC, используя Spring Data JPA для доступа к данным. Как вы могли видеть, Spring Data JPA значительно уменьшает и упрощает код, который нам нужно писать.

Для сравнения, вот структура проекта в Eclipse IDE:


Благодарю за прочтение!

Комментарии (5)


  1. nanshakov
    09.12.2019 10:49
    +2

    Я думал в 2019 уже не конфигурируют через xml…


    1. zloyreznic
      09.12.2019 12:31

      и не пишут в Eclipse


  1. dporollo
    09.12.2019 13:48

    Таких статей миллионы, а подход из этой статьи действительно давно устарел… Стандартом де факто уже давно стал spring с бутом.


  1. rmuhamedgaliev
    09.12.2019 16:09

    Опять простое приложение так еще и на устаревший метод. Давно так не пишут уже проекты. Тем более на Habr уже таких статей вагон и 10 маленьких тележек.
    Автор лучше не делайте переводов старых решений.


    1. redskif
      10.12.2019 14:03
      +1

      Статья в оригинале не сказать что бы сильно старая (есть только дата обновления). Может быть для выбора устаревших решений для демонстрации основ все же есть причина?
      Можете посоветовать статью на Хабре, аналогичную этой, но более современную?