import tacos.Order;
import tacos.data.OrderRepository;
@Controller
@RequestMapping("/orders")
@SessionAttributes("order")
public class OrderController {
private OrderRepository orderRepo;
public OrderController(OrderRepository orderRepo) {
this.orderRepo = orderRepo;
}
@GetMapping("/current")
public String orderForm() {
return "orderForm";
}
@PostMapping
public String processOrder(@Valid Order order, Errors errors,SessionStatus sessionStatus) {
if (errors.hasErrors()) {
return "orderForm";
}
orderRepo.save(order);
sessionStatus.setComplete();
return "redirect:/";
}
}
Помимо внедрения OrderRepository в контроллер, единственные существенные изменения в OrderController происходят в методе processOrder(). Здесь объект Order, представленный в форме (который также является тем же самым объектом Order, поддерживаемым в сеансе), сохраняется с помощью метода save() внедренного OrderRepository.
После того, как заказ будет сохранен, вам больше не нужно, чтобы он висел в сеансе. Фактически, если вы не очистите его, заказ остается в сеансе, включая связанные с ним тако, и следующий заказ начнется с тако, содержащимися в старом заказе. Поэтому метод processOrder() запрашивает параметр состояния сеанса и вызывает метод setComplete() для сброса сеанса.
Весь код сохранения JDBC закончен. Теперь вы можете запустить приложение Taco Cloud и проверить его работоспособность. Не стесняйтесь создавать столько тако и столько заказов, сколько вы хотите.
Вы также можете найти полезным покопаться в базе данных. Поскольку вы используете H2 в качестве встроенной базы данных, и поскольку у вас есть Spring Boot DevTools, вы можете обратиться в вашем браузере по адресу http://localhost:8080/h2-console чтобы увидеть консоль H2. По умолчанию поле JDBC URL должно быть установлено как jdbc:h2:mem:testdb. После входа в систему, вы должны быть в состоянии выполнить любой запрос к таблицам схемы Taco Cloud.
Spring JdbcTemplate, наряду с SimpleJdbcInsert, делает работу с реляционными базами данных значительно проще, чем простой vanilla JDBC. Но вы можете обнаружить, что JPA делает это еще проще. Давайте пересмотрим вашу работу и посмотрим, как использовать Spring Data, чтобы сделать сохранение данных еще проще.
3.2 Сохранение данных с помощью Spring Data JPA
Проект Spring Data представляет собой довольно крупный зонтичный (umbrella) проект, состоящий из нескольких подпроектов, большинство из которых сосредоточены на сохранении данных с различными типами баз данных. Некоторые из самых популярных Spring Data проектов включают в себя следующие:
Spring Data JPA — Сохранение JPA в реляционной базе данных
Spring Data MongoDB — Сохранение в базе данных документов Mongo
Spring Data Neo4j — Сохранение в базе данных графов Neo4j
Spring Data Redis — Сохранение в хранилище ключей и значений Redis
Spring Data Cassandra — Сохранение в базе данных Cassandra
Одной из наиболее интересных и полезных функций Spring Data для всех этих проектов является возможность автоматического создания репозиториев на основе интерфейса спецификации репозитория.
Чтобы увидеть, как работают Spring Data, потребуется начать все сначала, заменив репозитории на основе JDBC из ранее в этой главе репозиториями, созданными Spring Data JPA. Но сначала нужно добавить Spring Data JPA в сборку проекта.
3.2.1 Добавление Spring Data JPA в проект
Spring Data JPA доступны для Spring Boot приложений с JPA starter-ом. Эта зависимость стартера не только приносит в Spring Data JPA, но и транзитивно включает Hibernate как реализацию JPA:
Если вы хотите использовать другую реализацию JPA, то вам нужно, по крайней мере, исключить зависимость Hibernate и включить библиотеку JPA по вашему выбору. Например, чтобы использовать EclipseLink вместо Hibernate, необходимо изменить сборку следующим образом:
Обратите внимание, что могут потребоваться другие изменения в зависимости от выбранного варианта реализации JPA. Обратитесь к документации к выбранной реализации JPA для деталей. Теперь давайте вернемся к вашим доменным объектам и аннотируем их для сохранения JPA.
3.2.2 Аннотирование домена как сущностей
Как вы скоро увидите, Spring Data делает удивительные вещи, когда дело доходит до создания репозиториев. Но, к сожалению, это не очень помогает, когда дело доходит до аннотирования объектов домена аннотациями сопоставления JPA. Вам нужно будет открыть Ingredient, Taco и Order классы и добавить в них несколько аннотаций. Первым изменим класс Ingredient.
Листинг 3.16 аннотирование Ingredient для сохранения JPA
package tacos;
import javax.persistence.Entity;
import javax.persistence.Id;
import lombok.AccessLevel;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.RequiredArgsConstructor;
@Data
@RequiredArgsConstructor
@NoArgsConstructor(access=AccessLevel.PRIVATE, force=true)
@Entity
public class Ingredient {
@Id
private final String id;
private final String name;
private final Type type;
public static enum Type {
WRAP, PROTEIN, VEGGIES, CHEESE, SAUCE
}
}
Чтобы объявить это как сущность JPA, Ingredient должен быть аннотирован @Entity. И его свойство id должно быть аннотировано @Id, чтобы обозначить его как свойство, которое будет однозначно идентифицировать сущность в базе данных.
В дополнение к аннотациям, специфичным для JPA, вы также заметили, что вы добавили аннотацию @NoArgsConstructor на уровне класса. JPA требует, чтобы сущности имели конструктор без аргументов, поэтому @NoArgsConstructor Lombok-а делает это за вас. Однако, вы не хотите, чтобы имелась возможность использовать его, поэтому сделайте его private, установив атрибут доступа AccessLevel.PRIVATE. И поскольку есть final свойства, которые должны быть установлены, вы также устанавливаете атрибут force в true, что приводит к тому, что конструктор, сгенерированный Lombok-ом, устанавливает их в null.
Также добавляется @RequiredArgsConstructor. @Data неявно добавляет конструктор обязательных аргументов, но при использовании @NoArgsConstructor этот конструктор удаляется. Явный @RequiredArgsConstructor гарантирует, что у вас все еще будет конструктор обязательных аргументов в дополнение к закрытому конструктору без аргументов.
Теперь давайте перейдем к классу Taco и посмотрим, как аннотировать его как сущность JPA.
Листинг 3.17 аннотирование Taco как сущности
package tacos;
import java.util.Date;
import java.util.List;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToMany;
import javax.persistence.OneToMany;
import javax.persistence.PrePersist;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import lombok.Data;
@Data
@Entity
public class Taco {
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
@NotNull
@Size(min=5, message="Name must be at least 5 characters long")
private String name;
private Date createdAt;
@ManyToMany(targetEntity=Ingredient.class)
@Size(min=1, message="You must choose at least 1 ingredient")
private List
@PrePersist
void createdAt() {
this.createdAt = new Date();
}
}
Как и в случае с Ingredient, класс Taco теперь аннотируется @Entity и имеет свойство id, аннотированное @Id. Поскольку вы полагаетесь на базу данных для автоматического создания значения идентификатора, вы также аннотируете свойство id с помощью @GeneratedValue, указывая стратегию AUTO.
Чтобы объявить связь между Taco и связанным с ним списком Ingredient-ов, аннотируйте ingredients с помощью @ManyToMany. Taco может иметь много объектов -Ingredient, и Ingredient может быть частью многих Taco.
Вы также заметили, что есть новый метод createdAt(), который аннотируется @PrePersist. Вы будете использовать его, чтобы установить свойство createdAt в текущую дату и время, прежде чем сохранить Taco. Наконец, давайте аннотируем объект Order как сущность. В следующем листинге показан новый класс Order.
Листинг 3.18 Аннотирование Order как сущность JPA
package tacos;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;