Пример работы магии Spring Boot, Spring Data JPA и аудита сущностей.
Хотя вся конфигурация будет описана в классах с использованием Java Config, в приложении есть файл
В качестве базы данных будем использовать H2 Database Engine.
По-умолчанию Spring Boot для Spring Data JPA при подключении драйвера базы данных HSQL, H2 или Derby создаёт DataSource с in-memory базой данных и инициализирует её файлами
Также помимо DataSource Spring Boot любезно создаст и EntityManagerFactory, который найдёт сущности в любом месте приложения.
Для дальнейшей настройки приложения создадим класс AppConfig:
Этим занимается класс
Теперь необходимо создать классы сущностей, начнём с User:
Наследуемся от абстрактного класса
Для удобства добавим свойство
Соответствие свойств и полей таблицы, а также имени таблицы Spring установит сам, разумеется при их совпадении, единственно допускается наличие или отсутствие разделителя ввиде подчёркивания.
Второй класс
Теперь создаём для каждой сущности репазиторий:
Собственно это всё создание репазитория, благодаря абстрактному классу
Теперь создадим сервис
А теперь, собственно, класс приложения:
Магия Spring работает.
Исходники приложения: Spring Data JPA Audit and Version Example.
P.S.
Нюансы:
* в проект добавлена зависимость Joda-Time, без неё магия с timestamp не работает и прийдётся вручную указать поля createdDate и lastModifiedDate и их тип.
* пользователь добавлен именно с такими полями:
Если версия будет NULL — возникнет ошибка в дебрях Spring Data, если указать у USER в качестве создателя или модификатора его самого-же — возникнет ошибка из-за бесконечной циклической ссылки внутри Spring Data.
Хотя вся конфигурация будет описана в классах с использованием Java Config, в приложении есть файл
application.properties. Используется он потому, что эти настройки Spring Boot подхватывает на самой ранней стадии инициализации, а некоторые дефолтные настройки стоит заменить.В качестве базы данных будем использовать H2 Database Engine.
По-умолчанию Spring Boot для Spring Data JPA при подключении драйвера базы данных HSQL, H2 или Derby создаёт DataSource с in-memory базой данных и инициализирует её файлами
schema.sql и data.sql из ресурсов приложения. Также по умолчанию используется hibernate.hbm2ddl.auto=create-drop, после чего мы получаем девственно чистую базу данных с таблицами, сгенерированными из сущностей. Зачем так сделано — загадка, но эту автогенерацию надо отключить параметром в файле application.properties: spring.jpa.hibernate.ddl-auto=noneТакже помимо DataSource Spring Boot любезно создаст и EntityManagerFactory, который найдёт сущности в любом месте приложения.
Для дальнейшей настройки приложения создадим класс AppConfig:
@Configuration @EnableTransactionManagement @EnableJpaAuditing public class AppConfig { @Bean public AuditorAware<User> auditorProvider() { return new AuditorAwareImpl(); } }
@Configuration– сообщает Spring Boot, что этот файл содержит бины для настройки приложения;@EnableTransactionManagement– включает поддержку транзакций и создаёт необходимые бины с настройками по-умолчанию;@EnableJpaAuditing– включает поддержку аудита, но бин для работы этой поддержки всё же прийдётся написать самим, чтобы объяснить spring'у откуда брать пользователя;
Этим занимается класс
AuditorAwareImpl:public class AuditorAwareImpl implements AuditorAware<User> { @Autowired private CurrentUserService currentUserService; @Override public User getCurrentAuditor() { return currentUserService.getCurrentUser(); } }
CurrentUserService – это сервис, который будет отдавать обект User, создадим его немного позже.Теперь необходимо создать классы сущностей, начнём с User:
@Entity @EntityListeners({AuditingEntityListener.class}) public class User extends AbstractAuditable<User, Long> { @Basic @Column private String name; public String getName() { return name; } public void setName(String data) { this.name = data; } @Version @Column private Long version; public Long getVersion() { return version; } public void setVersion(Long version) { this.version = version; } @Override public String toString() { return "User {" + "id='" + getId() + "', " + "name='" + getName() + "'} "; } }
Наследуемся от абстрактного класса
AbstractAuditable<U, PK> из Spring Data, где U — это тип, отвещающий за пользователя, а PK — тип основного ключа. В итоге этого наследования в сущности уже будут следующие свойства: id, createdBy, createdDate, lastModifiedBy и lastModifiedDate.Для удобства добавим свойство
name, а для номера версии свойство version, которым будет управлять Spring Data. Собственно Spring Data будет управлять всеми полями, кроме name.@Entity– сообщаем JPA, что это класс-сущность@EntityListeners({AuditingEntityListener.class})– добавляем для сущности дефолтный класс-слушатель из Spring Data. Из-за этой строки отпадает необходимость в файлеorm.xmlс такой же настройкой.
Соответствие свойств и полей таблицы, а также имени таблицы Spring установит сам, разумеется при их совпадении, единственно допускается наличие или отсутствие разделителя ввиде подчёркивания.
Второй класс
Foo приводить не буду, у него вместо name свойство data.Теперь создаём для каждой сущности репазиторий:
public interface UserRepository extends CrudRepository<User, Long> { }
Собственно это всё создание репазитория, благодаря абстрактному классу
CrudRepository<T, ID>, где T — тип сущности, ID — тип основного ключа. Остальную реализацию репазитория берёт на себя Spring Data.Теперь создадим сервис
CurrentUserService, которые нужен только лишь для демонстрации работы аудита с двумя разными пользователями и создан для порядка и красоты.@Service public class CurrentUserService { private Long currentUserID = 1L; @Autowired private UserRepository userRepository; public User getCurrentUser() { return userRepository.findOne(currentUserID); } public void setCurrentUserToJohn() { currentUserID = 1L; } public void setCurrentUserToDoe() { currentUserID = 2L; } }
@Service– сообщает Spring'у, что это класс реализует внутренний сервис.@Autowired– с помощью внедрения зависимостей создаёт экземпляр репазитория пользователей.
А теперь, собственно, класс приложения:
@ComponentScan @EnableAutoConfiguration public class App implements CommandLineRunner { public static void main(String[] args) { SpringApplication.run(App.class, args); } @Autowired private FooRepository fooRepository; @Autowired private CurrentUserService currentUserService; @Override public void run(String... args) { Foo o = new Foo(); o.setData("test data"); fooRepository.save(o); fooRepository.findAll().forEach(System.out::println); currentUserService.setCurrentUserToDoe(); o.setData("New test data"); fooRepository.save(o); fooRepository.findAll().forEach(System.out::println); } }
Магия Spring работает.
Исходники приложения: Spring Data JPA Audit and Version Example.
P.S.
Нюансы:
* в проект добавлена зависимость Joda-Time, без неё магия с timestamp не работает и прийдётся вручную указать поля createdDate и lastModifiedDate и их тип.
* пользователь добавлен именно с такими полями:
insert into USER (ID, NAME, VERSION) values (1, 'John', 0);
Если версия будет NULL — возникнет ошибка в дебрях Spring Data, если указать у USER в качестве создателя или модификатора его самого-же — возникнет ошибка из-за бесконечной циклической ссылки внутри Spring Data.
