Пример магии Spring Data JPA Audit и Version

в 8:24, , рубрики: java, jpa, spring, spring data

Пример работы магии 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

Источник

Поделиться

* - обязательные к заполнению поля