Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring JPA Auditing empty createdBy

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.

like image 910
Serafeim Avatar asked Nov 27 '22 17:11

Serafeim


2 Answers

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.

like image 86
Maz Avatar answered Dec 03 '22 03:12

Maz


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.

like image 31
M. Deinum Avatar answered Dec 03 '22 02:12

M. Deinum