Issue
For example I have Entity User.
User have a status field.
When status changed I need call method from UserService.
I have worst solution. But inject service into Entity is very bad.
@Entity
public class User {
private String status;
@Transient
private String lastStatus;
@Transient
@Autowired
private UserService userService;
public String getStatus() {
return status;
}
public void setStatus(String status) {
lastStatus = this.status;
this.status = status;
}
@PreUpdate
public void handleChangeStaus() {
if (!status.equals(lastStatus)) {
userService.doSomething();
}
}
}
Solution
One way to do this is to publish domain events from your User
entity class and then to create a listener for this events.
First, let's create an event:
@AllArgsConstructor
@Getter
public class UserStatusChangeEvent {
private final UUID userId;
private final String lastStatus;
private final String newStatus;
}
Then your User
entity class should implement AbstractAggregateRoot
interface with default domain event publishing mechanism to let your entity publish events and publish one in your setStatus
method:
@Entity
@Getter
public class User implements AbstractAggregateRoot<User> {
private UUID userId;
private String status;
@Transient
private String lastStatus;
public void setStatus(String status) {
lastStatus = this.status;
this.status = status;
registerEvent(new UserStatusChangeEvent(userId, lastStatus, status));
}
}
Then create a separate class with a listener, define it as a bean (@Component
) and inject there your UserService
:
@RequiredArgsConstructor
@Component
public class UserStatusListener {
private final UserService userService;
@EventListener
public void onStatusChange(UserStatusChangeEvent statusChangeEvent) {
if (!statusChangeEvent.getNewStatus().equals(statusChangeEvent.getLastStatus())) {
userService.doSomething();
}
}
}
Spring will do the "magic" for you - publish your event as a application event and register your listener on startup.
Note, that spring-data will publish your domain events only after save
or saveAll
method called on a repository of your entity, so no save - no events.
Also instead of @EventListener
you can use @TransactionalEventListener
with different transaction phase if you want your listener to work depending on the transaction (success, failure, before or after, etc.).
P.S. I used Lombok annotations on classes to simplify code and inject fields through the constructor.
Answered By - AndrewThomas
Answer Checked By - Pedro (JavaFixing Volunteer)