How can I handle any field change in Entity (Spring Boot) - java

For example I have User entity class.
This User have a status field.
When status is changed I need to call method from my UserService.
I have this code, but I think injecting 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 handleChangeStatus() {
if (!status.equals(lastStatus)) {
userService.doSomething();
}
}
}

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.

Related

How to configure direct field access on #Valid in Spring?

How can I tell spring-web to validate my dto without having to use getter/setter?
#PostMapping(path = "/test")
public void test(#Valid #RequestBody WebDTO dto) {
}
public class WebDTO {
#Valid //triggers nested validation
private List<Person> persons;
//getter+setter for person
#JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY)
public static class Person {
#NotBlank
public String name;
public int age;
}
}
Result:
"java.lang.IllegalStateException","message":"JSR-303 validated property
'persons[0].name' does not have a corresponding accessor for Spring data
binding - check your DataBinder's configuration (bean property versus direct field access)"}
Special requirement: I still want to add #AssertTrue on boolean getters to provide crossfield validation, like:
#AssertTrue
#XmlTransient
#JsonIgnore
public boolean isNameValid() {
//...
}
you have to configure Spring DataBinder to use direct field access.
#ControllerAdvice
public class ControllerAdviceConfiguration {
#InitBinder
private void initDirectFieldAccess(DataBinder dataBinder) {
dataBinder.initDirectFieldAccess();
}
}
Try something like this:
#Access(AccessType.FIELD)
public String name;

How to pass an object to a ModelAttrbiute in MockMVC post?

User model:
#Entity
#Table(name="user")
public class User {
#Id #GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer id;
#NotBlank
#Column(name="username")
private String username;
#NotEmpty
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(name="user_role", joinColumns = {#JoinColumn(name="user_id")},
inverseJoinColumns = {#JoinColumn(name="role_id")})
private Set<Role> roles;
}
Controller:
#RequestMapping(value = {"/users/edit/{id}"}, method = RequestMethod.POST)
public String editUser(ModelMap model, #Valid #ModelAttribute("user") User user, BindingResult result) {
if(result.hasErrors()) {
return "AddUserView";
}
return "redirect:/users";
}
Test with MockMVC:
#Test
public void performUpdateUserTest() throws Throwable {
mockMvc.perform(post("/users/edit/{id}", user.getId())
.param("username", "User"));
}
Well, fine, I can pass a param username as always using param(). But what should I do with ROLES? This field is a separate object. I can't pass it using param(). Then how is it possible to pass it in the test?
The only way out I found is to create an entity and pass it using .flashAttr():
#Test
public void performUpdateUserTest() throws Throwable {
User user = new User("User", new HashSet<Role>(Arrays.asList(new Role("USER"))));
mockMvc.perform(post("/users/edit/{id}", user.getId())
.flashAttr("user", user));
}
But then, what if I need to test that user can't be updated because of binding error in the ROLES field(ROLES can't be null, and suppose, it was set as null)? Thus, I'm not able to create user(and use it with .flashAttr) already with a binding error as the exception will be thrown. And I still have to pass it separately.
Well, after a long time of searching, I found out that I should add a converter to the MockMVC. What converter is you can read HERE, for instance.
I had it already in my project but didn't realize that it didn't work with MockMVC.
So, you can add the converter to MockMVC like that:
#Autowired
private StringToRoleConverter stringToRoleConverter;
#Before
public void init() {
FormattingConversionService cs = new FormattingConversionService();
cs.addConverter(stringToRoleConverter);
mockMvc = MockMvcBuilders.standaloneSetup(userController)
.setConversionService(cs)
.build();
}
Converter itself:
#Component
public class StringToRoleConverter implements Converter<String, Role> {
#Autowired
private RoleService roleService;
#Override
public Role convert(String id) {
Role role = roleService.findById(Integer.valueOf(id));
return role;
}
}
And then I can add param like that:
mockMvc.perform(post("/users/edit/{id}", user.getId())
.param("roles", "2"))
though I'm passing a string there, it will be converter to Role with the help of Spring converter.

Spring data rest validation for PUT and PATCH running after DB update

I have a SDR project and I am successfully validating the user entity for POST request but as soon as I update an existing entity using either PATCH or PUT the DB is updated BEFORE the validation is executed (the validator is being executed and error is returned but the DB is being updated anyway).
Do I need to setup a separate config for update ? Am I missing an extra step for that?
Entity
#Entity
#JsonIgnoreProperties(ignoreUnknown = true)
public class Member {
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "member_id_gen")
#SequenceGenerator(name = "member_id_gen", sequenceName = "member_id_seq")
#Id
#JsonIgnore
private long id;
#Version
private Integer version;
#NotNull
protected String firstName;
#NotNull
protected String lastName;
#Valid
protected String email;
}
Repository
#RepositoryRestResource(collectionResourceRel = "members", path = "member")
public interface MemberRepository extends PagingAndSortingRepository<Member, Long> {
public Member findByFirstName(String firstName);
public Member findByLastName(String lastName);
}
Validator
#Component
public class BeforeSaveMemberValidator implements Validator {
public BeforeSaveMemberValidator() {}
private String EMAIL_REGEX = "^[a-zA-Z0-9._%+-]+#[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,6}$";
#Override
public boolean supports(Class<?> clazz) {
return Member.class.equals(clazz);
}
#Override
public void validate(Object target, Errors errors) {
Member member = (Member) target;
if(ObjectUtils.isEmpty(member.getFirstName())) {
errors.rejectValue("firstName", "member.firstName.empty");
}
if(ObjectUtils.isEmpty(member.getLastName())) {
errors.rejectValue("lastName", "member.lastName.empty");
}
if(!ObjectUtils.isEmpty(member.getDni()) && !member.getDni().matches("^[a-zA-Z0-9]*$")) {
errors.rejectValue("dni", "member.dni.invalid");
}
if(!ObjectUtils.isEmpty(member.getEmail()) && !member.getEmail().matches(EMAIL_REGEX)) {
errors.rejectValue("email", "member.email.notValid");
}
}
}
BeforeSave service
#Service
#RepositoryEventHandler(Member.class)
public class MemberService {
#HandleBeforeCreate
#HandleBeforeSave
#Transactional
public void beforeCreate(Member member) {
...
}
}
I think you should rename your validator, for example, to MemberValidator then assign it as described here:
#Override
protected void configureValidatingRepositoryEventListener(ValidatingRepositoryEventListener v) {
v.addValidator("beforeCreate", new MemberValidator());
v.addValidator("beforeSave", new MemberValidator());
}
But I suggest you to use Bean validation instead of your custom validators. To use it in SDR project you can inject LocalValidatorFactoryBean, then assign it for 'beforeCreate' and 'beforeSave' events in configureValidatingRepositoryEventListener:
#Configuration
#RequiredArgsConstructor // Lombok annotation
public class RepoRestConfig extends RepositoryRestConfigurerAdapter {
#NonNull private final LocalValidatorFactoryBean validatorFactoryBean;
#Override
public void configureValidatingRepositoryEventListener(ValidatingRepositoryEventListener v) {
v.addValidator("beforeCreate", validatorFactoryBean);
v.addValidator("beforeSave", validatorFactoryBean);
super.configureValidatingRepositoryEventListener(v);
}
}
In this case your SDR will automatically validate payloads of POST, PUT and PATCH requests for all exposed SDR repositories.
See my example for more details.

Callback methods do not work for Hibernate beans

I do everything according to http://docs.jboss.org/hibernate/orm/4.0/hem/en-US/html/listeners.html, but neither in-bean methods nor external ones are ever executed. What might be the cause?
#Entity
#EntityListeners(EntityListener.class)
public class User {
#Id
#Column
#GeneratedValue
private Integer id;
// etc...
#PostConstruct
#PostLoad
#PostPersist
#PostUpdate
public void magic() {
System.out.println("YES I AM EXECUTED!");
System.exit(123);
}
}
OR
#Entity
#EntityListeners(MyListener.class)
public class User {
#Id
#Column
#GeneratedValue
private Integer id;
// etc...
}
+
public class MyListener {
#PostPersist
void postPersist(Object object) {
System.out.println("CAN'T BELEIVE I SEE THIS!");
System.exit(234);
}
}
My code creates, saves and loads beans, but nothing happens on the listeners. This is a piece of the repository thats perform the operations:
#Repository
public class UserRepositoryImpl implements UserRepository {
#Autowired
private SessionFactory sessionFactory;
#Override
public User get(Integer id) {
return sessionFactory.getCurrentSession().get(User.class, id);
}
#Override
public User save(User user) {
Session session = sessionFactory.getCurrentSession();
user = (User) session.merge(user);
session.saveOrUpdate(user);
return user;
}
// etc...
}
Repository methods are called from services like this one:
#Service
#Transactional
public class UserServiceImpl implements UserService {
#Autowired
private UserRepository userRepository;
#Override
public void something() {
// just some repo calls + extra logic
}
}
I do not think I got something special here.
JPA interceptors mechanism work only when you manipulate entities via JPA EntityManager, they have no effect when you're using Hibernate Session directly.
You'll have to implement Hibernate native interceptors if you want to use the Session API.

Spring mvc: Transactional annotation

I have two tables Employee and Address as shown:
public class Employee {
private Integer id;
private String firstName;
private String lastName;
private boolean employeeStatus;
private Address address;
//getters setters
}
public class Address {
private Integer id;
private String country;
private String city;
private String street;
private Integer emp_id;
//getters setters
}
#Repository("employeeDao")
public class EmployeeDaoImpl implements EmployeeDao {
private JdbcTemplate jdbcTemplate;
#Autowired
public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
#Override
public void insertEmployee(Employee e)
{
String sql = "INSERT INTO tbl_employee (dept_id,firstName,lastName,employeeStatus) values(?,?,?,?,?)";
this.jdbcTemplate.update(sql,new
Object[]{e.getDept_id(),e.getFirstName(),e.getLastName(),e.isEmployeeStatus()});
// INSERT ADDRESS????
}
// Other Methods
}
Now i want to implement Transactional while inserting the employee and address table attributes. I am abit confused here. Does #transactional annotation over the method does the required job? So far i understood that. Also, is it best practice to insert address from where i am inserting employee attributes? I also read somewhere that the transactional should be implemented from service layer than Dao. How would transactional can be implemented in this case?
EDIT
Since it is recommended to use #transactional in service layer, service layer became like:
#Service("employeeService")
#Transactional
public class EmployeeServiceImpl implements EmployeeService{
#Autowired
EmployeeDao employeeDao;
#Autowired
AddressDao addressDao;
#Override
public void insertEmployee(Employee e) {
employeeDao.insertEmployee(e);
addressDao.insertAddress(e.address);
}
}
is it the right way to perform transactional? Also can anyone explain #Transactional(propagation = Propagation.SUPPORTS, readOnly = true) instead of plain #Transactional ?
Alltough the #Transactional annotation would do the job, transactions are usually defined on service level. This way one business call is in one transaction, making sure everything succeeds or fails together.
you can read about #transactional in combination with jdbctemplate here

Categories

Resources