В своём предыдущем посте я описал начало разработки приложения с использованем Spring Boot. В этом посте я расскажу о работе с реляционными базами данных.
Из названия следует, что стандартный способ работы с реляционными базами данных реализован посредством Spring Data JPA. Основываясь на своём опыте, могу сказать, что возможности этого фреймворка и JPA полностью покрывают потребности практически любого проекта. Исключения составляют сложные запросы с вложенными запросами и кучей джоинов, но и они решаются при помощи хранимых процедур и представлений.
Теперь нашему проекту нужно добавить связь с базой данных. Для демонстрационного проекта вполне хватит и локальной БД вроде HSQLDB или H2:
В application.properties нужно добавить два свойства, что бы схема базы данных создавалась при запуске приложения:
Теперь всё готово для написания классов-сущностей и логики работы с базой данных.
Spring Boot и реляционные БД
Для работы с реляционным базами данных в Spring Boot есть стартер spring-boot-starter-data-jpa:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency>
Из названия следует, что стандартный способ работы с реляционными базами данных реализован посредством Spring Data JPA. Основываясь на своём опыте, могу сказать, что возможности этого фреймворка и JPA полностью покрывают потребности практически любого проекта. Исключения составляют сложные запросы с вложенными запросами и кучей джоинов, но и они решаются при помощи хранимых процедур и представлений.
Теперь нашему проекту нужно добавить связь с базой данных. Для демонстрационного проекта вполне хватит и локальной БД вроде HSQLDB или H2:
<dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> </dependency>
В application.properties нужно добавить два свойства, что бы схема базы данных создавалась при запуске приложения:
spring.jpa.generate-ddl=true spring.jpa.hibernate.ddl-auto=create-drop
Теперь всё готово для написания классов-сущностей и логики работы с базой данных.
Классы сущностей и репозитории
В простейшем хелпдеске есть как минимум две сущности: заявка с описанием проблемы и её обсуждение в виде комментариев.
Для заявки создадим класс Ticket:
@Entity public class Ticket implements Serializable { private static final long serialVersionUID = 20160403085401L; /** * Идентификатор, генерируется автоматически при создании новой записи. */ @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; /** * Краткое описание проблемы. */ private String issue; /** * Подробное описание проблемы. */ @Column(columnDefinition = "TEXT") private String description; /** * Дата создания заявки. */ @Temporal(javax.persistence.TemporalType.TIMESTAMP) private Date createDate; /** * Дата закрытия заявки. */ @Temporal(javax.persistence.TemporalType.TIMESTAMP) private Date resolveDate; // getters, setters, hashCode, equals, toString }
Для комментария к заявке создадим класс TicketComment:
При помощи JPA-аннотаций можно более подробно сконфигурировать классы сущностей и их маппинг в таблицы базы данных.
Теперь нам нужно определить два интерфейса для взаимодействия с базой данных. Для работы с заявками это будет TicketRepository:
@Entity public class TicketComment implements Serializable { private static final long serialVersionUID = 20160403085801L; /** * Идентификатор комментария. Генерируется автоматически при создании нового комментария. */ @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; /** * Комментарий. */ private String comment; /** * Дата создания комментария */ @Temporal(javax.persistence.TemporalType.TIMESTAMP) private Date createDate; /** * Заявка, к которой привязан комментарий. */ @ManyToOne private Ticket ticket; // getters, setters, hashCode, equals, toString }
При помощи JPA-аннотаций можно более подробно сконфигурировать классы сущностей и их маппинг в таблицы базы данных.
Теперь нам нужно определить два интерфейса для взаимодействия с базой данных. Для работы с заявками это будет TicketRepository:
@Repository public interface TicketRepository extends CrudRepository<Ticket, Long> { /** * Поиск всех незакрытых заявок. */ List<Ticket> findByResolveDateIsNull(Sort sort); /** * Получение полного списка заявок с возможностью сортировки. */ List<Ticket> findAll(Sort sort); /** * Закрытие заявки. */ @Transactional @Modifying @Query("update Ticket set resolveDate = now() where id = :id") int resolveTicket(@Param("id") long id); /** * Повторное открытие заявки. */ @Transactional @Modifying @Query("update Ticket set resolveDate = null where id = :id") int reopenTicket(@Param("id") long id); }
Рассмотрим код описанного интерфейса более подробно:
- Аннотация @Repository указывает, что данный класс или интерфейс является репозиторием и объект этого класса будет использоваться при внедрении зависимостей.
- Описанный интерфейс расширяет CrudRepository, в котором уже определены основные CRUD-операции. Так же можно расширить интерфейс JpaRepository. Дженерики указывают, что данный репозиторий работает с сущностью Ticket, а классом первичных ключей во всех операциях является Long.
- Метод findByResolveDateIsNull производит поиск всех записей сущности Ticket, у которых свойство resolveDate не задано.
- Методы resolveTicket и reopenTicket обновляют запись, делая заявку закрытой или открытой. Для методов, которые модифицируют данные в БД необходимы аннотации @Transactional и @Modifying.
- Начало названия метода определяет, что нужно сделать в запросе. Например, findBy вернёт записи, соотвествующие критерии выборки (SELECT), deleteBy - удалит (DELETE).
- Дальше указываются условия запроса. В приведённом примере условием является ResolveDateIsNull, что будет транслировано в resolve_date is null (на SQL). Если бы мы хотели просто произвести поиск по полному соответствию resolveDate, то сигнатура метода выглядела бы следующим образом: List<Ticket> findByResolveDate(Date date). Условия выборки можно объединять при помощи And или Or в зависимости от ситуации.
Возможности Spring Data JPA серьёзно ускоряют и упрощают процесс разработки, позволяя меньше отвлекаться на написание кода работы с БД и сконцентрироваться на написании бизнес-логики. Но, что если запрос к БД имеет больше 2-3 условий, или условия выборки несколько сложнее? В этом случае удобным будет использование аннотации @Query, которая позволяет описывать запросы в JPQL или SQL (при установленном свойстве native). Рассмотрим на примере репозитория комментариев, TicketCommentRepository:
@Repository public interface TicketCommentRepository extends CrudRepository<TicketComment, Long> { List<TicketComment> findByTicket(Ticket ticket); @Query(value = "select tc from TicketComment tc join ticket t where t.id = :id") List<TicketComment> findByTicketId(@Param("ticketId") long id); }
В свойстве value мы описали простой JPQL-запрос, в котором производится выборка всех TicketComment, у которых id вложенного объекта ticket соответствует искомому. Имена методов, у которых есть аннотация @Query, могут иметь свободную форму, Spring Data JPA реализует поведение этих методом на основании запроса.
По сути, оба описанных выше метода имеют одну и ту же функциональность - они возвращают список комментариев к указанной заявке.
На этом этапе у нас есть классы сущностей и репозитории с реализацией основных действий с БД. В следующем посте я опишу работу со Spring WebMVC на примере контроллеров и представлений с использованием Thymeleaf.
А что если...
- Нужна нормальная СУБД, а не H2?
- Добавляем в зависимости нужный драйвер:
<dependency> <groupId>org.postgresql</groupId> <artifactId>postgresql</artifactId> </dependency>
- Описываем свойства подключения в application.properties:
spring.datasource.platform=postgresql spring.datasource.url=jdbc:postgresql://localhost:5432/postgresql spring.datasource.username=postgresql spring.datasource.password=postgresql spring.datasource.driver-class-name=org.postgresql.Driver
- Нужна нестандартная реализация источника данных?
- Добавляем в зависимости библиотеку с желаемой реализацией источника данных:
<dependency> <groupId>com.zaxxer</groupId> <artifactId>HikariCP</artifactId> </dependency>
- В классе конфигурации создаём метод с аннотацией @Bean, в котором создаём объект источника данных:
@Value("${spring.datasource.type}") private String dataSourceClassName; @Value("${spring.datasource.url}") private String dataSourceUrl; @Value("${spring.datasource.username}") private String dataSourceUsername; @Value("${spring.datasource.password}") private String dataSourcePassword; @Bean public DataSource dataSource() { HikariDataSource dataSource = new HikariDataSource(); dataSource.setDataSourceClassName(dataSourceClassName); dataSource.setDataSourceProperties(getDataSourceProperties()); return dataSource; } private Properties getDataSourceProperties() { Properties properties = new Properties(); properties.setProperty("url", dataSourceUrl); properties.setProperty("user", dataSourceUsername); properties.setProperty("password", dataSourcePassword); return properties; }
Комментариев нет:
Отправить комментарий