Spring Data Audit, CreatedBy lost on Update [duplicate] - spring

I am using the auditing capabilities of Spring Data and have a class similar to this:
#Entity
#Audited
#EntityListeners(AuditingEntityListener.class)
#Table(name="Student")
public class Student {
#Id
#GeneratedValue (strategy = GenerationType.AUTO)
private Long id;
#CreatedBy
private String createdBy;
#CreatedDate
private Date createdDate;
#LastModifiedBy
private String lastModifiedBy;
#LastModifiedDate
private Date lastModifiedDate;
...
Now, I believe I have configured auditing fine because I can see that createdBy, createdDate, lastModifiedBy and lastModifiedDate all are getting the correct values when I update the domain objects.
However, my problem is that when I update an object I am losing the values of createdBy and createdDate. So, when I first create the object I have all four values, but when I update it createdBy and createdDate are nullified ! I am also using the Hibernate envers to keep a history of the domain objects.
Do you know why do I get this behavior ? Why do createdBy and createdDate are empty when I update the domain object ?
Update: To answer #m-deinum 's questions: Yes spring data JPA is configured correctly - everything else works fine - I really wouldn't like to post the configuration because as you udnerstand it will need a lot of space.
My AuditorAwareImpl is this
#Component
public class AuditorAwareImpl implements AuditorAware {
Logger logger = Logger.getLogger(AuditorAwareImpl.class);
#Autowired
ProfileService profileService;
#Override
public String getCurrentAuditor() {
return profileService.getMyUsername();
}
}
Finally, here's my update controller implementation:
#Autowired
private StudentFormValidator validator;
#Autowired
private StudentRepository studentRep;
#RequestMapping(value="/edit/{id}", method=RequestMethod.POST)
public String updateFromForm(
#PathVariable("id")Long id,
#Valid Student student, BindingResult result,
final RedirectAttributes redirectAttributes) {
Student s = studentRep.secureFind(id);
if(student == null || s == null) {
throw new ResourceNotFoundException();
}
validator.validate(student, result);
if (result.hasErrors()) {
return "students/form";
}
student.setId(id);
student.setSchool(profileService.getMySchool());
redirectAttributes.addFlashAttribute("message", "Επιτυχής προσθήκη!");
studentRep.save(student);
return "redirect:/students/list";
}
Update 2: Please take a look at a newer version
#RequestMapping(value="/edit/{id}", method=RequestMethod.GET)
public ModelAndView editForm(#PathVariable("id")Long id) {
ModelAndView mav = new ModelAndView("students/form");
Student student = studentRep.secureFind(id);
if(student == null) {
throw new ResourceNotFoundException();
}
mav.getModelMap().addAttribute(student);
mav.getModelMap().addAttribute("genders", GenderEnum.values());
mav.getModelMap().addAttribute("studentTypes", StudEnum.values());
return mav;
}
#RequestMapping(value="/edit/{id}", method=RequestMethod.POST)
public String updateFromForm(
#PathVariable("id")Long id,
#Valid #ModelAttribute Student student, BindingResult result,
final RedirectAttributes redirectAttributes, SessionStatus status) {
Student s = studentRep.secureFind(id);
if(student == null || s == null) {
throw new ResourceNotFoundException();
}
if (result.hasErrors()) {
return "students/form";
}
//student.setId(id);
student.setSchool(profileService.getMySchool());
studentRep.save(student);
redirectAttributes.addFlashAttribute("message", "Επιτυχής προσθήκη!");
status.setComplete();
return "redirect:/students/list";
}
This still leaves empty the createdBy and createdDate fields when I do an update :(
Also it does not get the School value (which is not contained in my form because it is related to the user currently editing) so I need to get it again from the SecurityContext... Have I done anything wrong ?
Update 3: For reference and to not miss it in the comments: The main problem was that I needed to include the #SessionAttributes annotation to my controller.

Use updatable attribute of #Column annotation like below.
#Column(name = "created_date", updatable = false)
private Date createdDate;
This will retain the created date on update operation.

Your method in your (#)Controller class is not that efficient. You don't want to (manually) retrieve the object and copy all the fields, relationships etc. over to it. Next to that with complex objects you will sooner or alter run into big trouble.
What you want is on your first method (the GET for showing the form) retrieve the user and store it in the session using #SessionAttributes. Next you want an #InitBinder annotated method to set your validator on the WebDataBinder so that spring will do the validation. This will leave your updateFromForm method nice and clean.
#Controller
#RequestMapping("/edit/{id}")
#SessionAttributes("student")
public EditStudentController
#Autowired
private StudentFormValidator validator;
#Autowired
private StudentRepository studentRep;
#InitBinder
public void initBinder(WebDataBinder binder) {
binder.setValidator(validator);
}
#RequestMapping(method=RequestMethod.GET)
public String showUpdateForm(Model model) {
model.addObject("student", studentRep.secureFind(id));
return "students/form";
}
#RequestMapping(method=RequestMethod.POST)
public String public String updateFromForm(#Valid #ModelAttribute Student student, BindingResult result, RedirectAttributes redirectAttributes, SessionStatus status) {
// Optionally you could check the ids if they are the same.
if (result.hasErrors()) {
return "students/form";
}
redirectAttributes.addFlashAttribute("message", "?p?t???? p??s????!");
studentRep.save(student);
status.setComplete(); // Will remove the student from the session
return "redirect:/students/list";
}
}
You will need to add the SessionStatus attribute to the method and mark the processing complete, so that Spring can cleanup your model from the session.
This way you don't have to copy around objects, etc. and Spring will do all the heave lifting and all your fields/relations will be properly set.

In my case #CreatedDate and #CreatedBy fields were not removed from databse during update, but were not queried by #Repository findOne method.
Changing
#Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
into
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
on #Entity class helped with that.

Related

Spring - How to use #RequestParam parameter of a Controller in a Custom Validator class?

I've got a problem about validation in Spring MVC with Hibernate.
I want a validator that valid user input, but the validation must be done out of the controller, so, in a separate validation class.
The situation: this is the head of my controller in which I want to do the validation. I need that id to retrieve a list of Booking of a specific car.
#PostMapping(value = "/rent")
public ModelAndView vehicleRent(#ModelAttribute("newBooking") Booking booking, BindingResult bindingResult, #RequestParam("id") long id) {
But if i want to separate the logic out of this controller creating a custom validator, i have this result:
public class BookingValidator implements Validator {
#Autowired
VehicleBO vehicleBo;
#Override
public boolean supports(Class<?> type) {
return Booking.class.isAssignableFrom(type);
}
#Override
public void validate(Object o, Errors errors) {
Booking booking = (Booking) o;
//other code
rejectIfBookingExists(booking, 0, errors, "validation.booking.startdate.exists");
}
}
public boolean rejectIfBookingExists(Booking booking, long id, Errors errors, String key){
boolean exists = false;
List<Booking> vehicleBookings = vehicleBo.getVehicleBookings(id);
if (booking != null || booking.getStartDate() != null || booking.getFinishDate() != null) {
for (Booking b : vehicleBookings) {
if (booking.getStartDate().before((b.getFinishDate())) || booking.getStartDate().equals(b.getFinishDate())) {
errors.rejectValue("startDate", key);
exists = true;
break;
}
}
}
return exists;
}
}
In this way I cannot retrieve the list because i don't have the required id, could you explain me how to do that? Or,there are other ways to solve this problem?
Thanks!
EDIT:
This is the Booking class, as you can see it has a Vehicle object mapped inside
#Entity
public class Booking implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#ManyToOne
#JoinTable(name="user_booking", joinColumns={#JoinColumn(name ="booking_id", referencedColumnName ="id")},
inverseJoinColumns={#JoinColumn(name ="user_id", referencedColumnName ="id")})
private User user;
#ManyToOne
#JoinColumn(name = "vehicle_id")
private Vehicle vehicle;
#DateTimeFormat(pattern = "dd/MM/yyyy")
private Date startDate;
#DateTimeFormat(pattern = "dd/MM/yyyy")
private Date finishDate;
public Booking() {
//getter and setter and other code
}
Any ideas?
Why don't you simply map the vehicle id as booking.vehicle.id in your form? Provided Vehicle has a no-arg constructor (which it probably does, being an entity), the Booking should come back in the POST request handler with an instantiated Vehicle, along with its id property set. You should then be able to access booking.vehicle.id from wihtin the validator.
You can use an input[type=hidden] for the booking.vehicle.id field. In your GET request for the view with the form, simply inject the vehicle id as a #PathVariable and copy it to your model, so that you could reference the value inside the form.

spring mvc +hibernate one to many delete option

i am working on spring mvc but i am facing this problem
Provided id of the wrong type. Expected: class java.lang.Long, got class java.lang.Integer
here is my dao code
#Override
public String delete(long id) {
/*sessionFactory.getCurrentSession().createQuery("DELETE FROM Invoice WHERE id = "+ id).executeUpdate();*/
sessionFactory.getCurrentSession().createQuery("DELETE FROM InvoiceBatch WHERE id = "+ id).executeUpdate();
return "Successfuly Deleted";
}
here is my contoller
#RequestMapping(value="/delete",method=RequestMethod.GET)
public ModelAndView deleteEmployee( #RequestParam("id") String id, ModelMap model)
{
Long eid =Long.parseLong(id);
model.put("message",invoicManager.delete(eid));
//model.put("", invoicManager.clientList());
return new ModelAndView("Invoices", model);
}
here is my pojo
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#Column(name="BATCH_REFERE
am i missing something
kindly help

Spring MVC with Hibernate Validator Mandatory for the database field, but not in the application

Problem with BindingResult hasErrors() in validation.
I have this code:
#RequestMapping(value = "/entity", params = "form", method = RequestMethod.POST)
public String submit(#Valid #ModelAttribute Entity entity, BindingResult result) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
entity.setCreatedBy(auth.getName());
if (result.hasErrors()) {
//Here the error of createdBy is null
return "entity/new";
} else {
entityService.save(entity);
return "redirect:/entity/list";
}
}
the entity class:
#Entity
#Table(name = "TABLE_X")
public class Entity implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#NotNull
#Column(name = "primary_key")
private String primaryKey;
#NotNull
#Column(name = "created_by")
private String createdBy;
//getters and setter
}
I need set the value of createdBy in controller but always show "may not be null" in view.
Please help.
Spring MVC 4, Hibernate Validator 5, Database Oracle 11g
You entity object is validated before Spring MVC invokes the submit() method. The result object is created at the same time. This line:
entity.setCreatedBy(auth.getName());
has absolutely no effect on the outcome of result.hasErrors().

Values of #PathVariable and #ModelAttribute overlapping

I have an User object stored in the session with #SessionAttributes. And a straight-forward method decorated with #ModelAttribute in order to initialize it whenever the session's value is null.
User class:
#Entity
#Table( name="USER")
public class User implements java.io.Serializable {
private Long id;
private String username;
private String password;
....
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name ="ID")
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
...
Controller:
#RequestMapping("/item")
#Controller
#SessionAttributes({"user"})
public class MyController {
#ModelAttribute method:
#ModelAttribute("user")
public User createUser(Principal principal) {
return userService.findByUsername(principal.getName());
}
It all seems to work as expected except in this particular method:
#RequestMapping(value = "/{id}", method = RequestMethod.GET)
public String showItem(#PathVariable("id") Long id, #ModelAttribute("user") User user,
Model uiModel) {
...
}
The problem is that User.id is being set with #PathVariable("id"). I believe I ran into this with #RequestParam too. I'm assuming that's because both have the same name and type. After reading Spring's documentation (see below) I'm assuming this is expected behavior:
The next step is data binding. The WebDataBinder class matches request parameter names — including query string parameters and form fields — to model attribute fields by name. Matching fields are populated after type conversion (from String to the target field type) has been applied where necessary.
However, I would think this scenario is fairly common, how are other people handling this? If my findings are correct and this is expected behavior (or bug), this seems to be very error prone.
Possible solutions:
Change #PathVariable("id") to #PathVariable("somethingElse"). Works but it's not as straightforward with #RequestParam (e.g. I don't know how to change jqgrid's request parameter id to something else but this is another issue).
Change #PathVariable("id") type from Long to Int. This will make User.id and id types differ but the cast to Long looks ugly :)
Don't use #ModelAttribute here and query the DB for User again. Not consistent with other methods and involves redundant DB calls.
Any suggestions?
How about this approach -
#RequestMapping(value = "/{id}", method = RequestMethod.GET)
public String showItem(#PathVariable("id") Long id,
Model uiModel) {
User user = (User)uiModel.asMap().get("user");
...
}
use #SessionAttribute
#RequestMapping(value = "/{id}", method = RequestMethod.GET)
public String showItem(#PathVariable("id") Long id, #SessionAttribute("user") User user,
Model uiModel) {
...
}

spring data mongodb MongoRepository.save(T entity) method not working?

The code is listed below:
#Document
#XmlRootElement
public class User {
#Indexed(unique=true)
private String username;
private String firstName;
private String lastName;
private String password;
...... omit setters and getters
}
public interface UserRepo extends MongoRepository<User, String>{
}
public User update(User user) {
User existingUser = userRepo.findByUsername(user.getUsername());
if (existingUser == null) {
return null;
}
existingUser.setFirstName(user.getFirstName());
existingUser.setLastName(user.getLastName());
return userRepo.save(existingUser);
}
when update method invoked, the finds the user based on username and finishes without any exceptions, the returned User obj has all updated value but the underlying mongodb document is not changed! Can anyone help? Thanks.
you need an Id field with #Id annotation

Resources