Пример работы магии Spring Boot, Spring Data JPA и аудита сущностей.
Хотя вся конфигурация будет описана в классах с использованием 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
@Transactional
@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, что это класс-сущность@Transactional
– включаем поддержку транзакций@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.
Автор: Barlog