У меня есть приложение Spring Boot, которое имеет простое Entity, JpaRepository и Service.
Entity :
@Getter
@Setter
@EntityListeners(AuditingEntityListener.class)
public class User {
@Id
@GeneratedValue(strategy = GenerationType.TABLE)
private Long id;
@Version
private Long version;
@CreatedDate
private LocalDateTime created;
@LastModifiedDate
private LocalDateTime lastModified;
@CreatedBy
private String createdBy;
@LastModifiedBy
private String lastModifiedBy;
private String name;
private String email;
}
JpaRepository :
@Repository
public interface UserRepository extends JpaRepository<User, Long> {}
Сервис :
@Service
class UserService {
@Autowired
private UserRepository userRepository;
@Transactional
public User create(User user) {
Assert.isNull(user.getId(), "Can't create an existing user!");
return userRepository.saveAndFlush(user);
}
@Transactional
public User update(User user) {
Assert.notNull(user.getId(), "Can't update a new user!");
return userRepository.saveAndFlush(user);
}
}
В моем реальном приложении аннотация @Transactional
установлена в RestController
, но create()
иupdate()
никогда не выполняются в одной и той же транзакции
Когда я создаю нового пользователя следующим образом:
User user = new User();
user.setName("user");
user.setEmail("user@email");
userService.create(user);
Spring автоматически устанавливает все свойства аудита (созданные, lastModified, ...) и версия до 0.
Но когда я обновляю существующего пользователя следующим образом:
User user = new User();
user.setId(1L); // Id of the User I want to update
user.setVersion(5L) // Current version is 5L
user.setName("user");
user.setEmail("new@email"); // I want to update only the email
userService.update(user);
я получаю эту ошибку:
org.postgresql.util.PSQLException: ERROR: null value in column "created" violates not-null constraint
, что означает, чтоSpring Auditing не обрабатывает свойство created
.
Чтобы исправить это, я изменил свой метод update
в UserService
следующим образом:
@Transactional
public User update(User user) {
Assert.notNull(user.getId(), "Can't update a new user!");
User original = userRepository.getOne(user.getId())
original.setVersion(user.getVersion());
original.setName(user.getName());
original.setEmail(user.getEmail());
return userRepository.saveAndFlush(original);
}
это исправило PSQLException
но тогда версия больше не проверяется.Это означает, что если я установлю версию, отличную от текущей версии в базе данных, я не получу ошибку ObjectOptimisticLockingFailureException
, и обновленный пользователь будет сохранен в базе данных без ошибок.
IЯ могу заменить .getOne()
на .findById()
, но ничего не изменится
В конце я смог исправить обе мои проблемы следующим образом:
@Transactional
public User update(User user) {
Assert.notNull(user.getId(), "Can't update a new user!");
Assert.notNull(user.getVersion(), "Version must be defined!");
User original = userRepository.getOne(user.getId())
User update = new User();
update.setId(original.getId());
update.setCreated(original.getCreated());
update.setCreatedBy(original.getCreatedBy());
update.setVersion(user.getVersion());
update.setName(user.getName());
update.setEmail(user.getEmail());
return userRepository.saveAndFlush(update);
}
Мне все еще нужно проверить, что версия не равна нулю, иначе пользователь будет сохранен без проверки версии
, теперь поля аудита не выдают PSQLException
, а версия проверяется перед обновлением и ObjectOptimisticLockingFailureException
это бросить, когда это неправильно.
Но это решение ужасно , потому что мне нужно копировать свойства влево и вправо, а в больших объектах будет легко что-то забыть.
Знаете ли вы лучшее решение этой проблемы?