I'm working in the Spring Framework. How do I pass object variable values into the Hibernate criteria restriction?
This is how I've written them:
#Repository
public class EventSearchDAOImpl implements EventSearchDAO<Event> {
#Autowired
private SessionFactory sessionFactory;
private Session session;
private Transaction trans;
#Override
public List<Event> getAll(Event event) {
session = sessionFactory.openSession();
System.out.println(event.getType()+event.getName());
return session.createCriteria(Event.class).add(Restrictions
.and(Restrictions.like("type", "%"+event.getType()+"%"),
Restrictions.like("name", "%"+event.getName()+"%")))
.list();
}
}
I printed the values in my output console to check via System.out.println(event.getType()+event.getName()); and they do seem to get passed successfully.
This is how I'm passing them via my DefaultController.java class:
#RequestMapping(value = "/searchEvent", method = RequestMethod.POST)
public String searchEvent(Event event, Model model) {
Event e = new Event();
e.setType(event.getType());
e.setName(event.getName());
model.addAttribute("check", eventSearchDAO.getAll(e));
return "redirect:/";
}
A form in my index.jsp page posts data into the above line of code.
To check if the code works, I added the following bit in my index.jsp:
<c:forEach var="i" items="${check}">
${i.name} || ${i.type} <br>
</c:forEach>
If I manually write the restrictions as
return session.createCriteria(Event.class).add(Restrictions
.and(Restrictions.like("type", "%fair%"),
Restrictions.like("name", "%auto%")))
.list();
and make a few tweaks in the controller accordingly, it works just fine. But when I do it by passing the object's variables, it doesn't seem to work. That's my problem. Please help me fix this.
Turns out there was a mistake in my Controller class DefaultController.java, I should've wrote:
return "index";
instead of:
return "redirect:/";
Yes, I'm an idiot.
Related
So far in my Java code with Spring Boot I was using models, or POJO objects to achieve better control of my objects, etc. Usually I am creating Entities, Repositories, Services, Rest controllers, just like documentation and courses are suggesting.
Now however I am working with Thymeleaf templates, HTML a bit of Bootstrap and CSS in order to create browser interface. For methods in #Controller, as parameter, I am passing Model from Spring Model UI like this:
#GetMapping("/employees")
private String viewAllEmployees(Model employeeModel) {
employeeModel.addAttribute("listEmployees", employeeService.getAllEmployees());
return "employeeList";
}
My question is: How can I use my POJO objects instead of org.springframework.ui.Model;?
My first guess was this:
public class EmployeeModel implements Model{
private long employeeId;
private String firstName;
private String lastName;
private String email;
private String phone;
private long companyId;
//getter and setter methods
}
And in order to do that I have to #Override Model methods which is fine with me. And it looks like Java, Spring etc. does not complain in compile time, and I can use this POJO object in my #Controller like this:
#Controller
public class EmployeeController {
#Autowired
private EmployeeService employeeService;
#GetMapping("/employees")
private String viewAllEmployees(EmployeeModel employeeModel) {
employeeModel.addAttribute("listEmployees", employeeService.getAllEmployees());
return "employeeList";
}}
I run the code and it starts, shows my /home endpoint which works cool, however when I want to go to my /employees endpoing where it should show my eployees list it throws this:
Method [private java.lang.String com.bojan.thyme.thymeApp.controller.EmployeeController.viewAllEmployees(com.bojan.thyme.thymeApp.model.EmployeeModel)] with argument values:[0] [type=org.springframework.validation.support.BindingAwareModelMap] [value={}] ] with root cause java.lang.IllegalArgumentException: argument type mismatch
exception.
Please note that Rest controller is working perfectly in browser and Postman.
Is it possible that String as a method is the problem? Should my method be of some other type like List<EmployeeModel> or maybe EmployeeModel itself? If it is so, how to tell the method that I want my employeeList.html to be returned?
I sincerely hope that someone can halp me with this one :)
How can I use my POJO objects instead of org.springframework.ui.Model;?
I don't think that is the best practice when you are working with Thymeleaf. According to their documentation, you should attach your Objects to your Model. So in your controller you would be manipulating models that contain your Pojos.
Example:
#RequestMapping(value = "message", method = RequestMethod.GET)
public ModelAndView messages() {
ModelAndView mav = new ModelAndView("message/list");
mav.addObject("messages", messageRepository.findAll());
return mav;
}
You should always use org.springframework.ui.Model as argument. This class is basically a Map with key/value pairs that are made available to Thymeleaf for rendering.
Your first example is how you should do it:
#GetMapping("/employees") //<1>
private String viewAllEmployees(Model model) {
model.addAttribute("employees", employeeService.getAllEmployees()); // <2>
return "employeeList"; // <3>
}
<1> This is the URL that the view will be rendered on
<2> Add any Java object you want as attribute(s) to the model
<3> Return the name of the Thymeleaf template. In a default Spring Boot with Thymeleaf application, this will refer to the template at src/main/resources/templates/employeeList.html. In that template, you will be able to access your model value with ${employees}.
I have a controller method which retrieves an User, then I've got mapped their UserConfig, and then with that UserConfig I retrieve the MainBrands (lazy collection of UserConfiguration).
Let me clarify this:
User Entity:
#Entity
#Table(name = "app_user")
public class User extends BaseEntity {
private UserConfig userConfig;
#OneToOne(mappedBy = "user", cascade = CascadeType.ALL)
public UserConfig getUserConfig() {
return userConfig;
}
//more props..
}
UserConfig Entity:
#Entity
#Table(name = "user_config")
public class UserConfig extends BaseEntity {
private Set<MainBrand> mainBrands;
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(...)
public Set<MainBrand> getMainBrands() {
return mainBrands;
}
//more props..
}
And my UserService:
public interface UserService {
public User getById(Long id);
}
So my question is about "best practices" of transactional annotations. I have read more than once, that put #Transactional at Controller level, is bad practice. But in this case I wanna do at Controller:
#RequestMapping(method = RequestMethod.GET, value = "/")
public ModelAndView getMainPage(Long userId) {
ModelAndView = new ModelAndView("/home");
//do stuff
User user = userService.getById(userId);
//some stuff with user
modelAndView.addObject("username", user.getUsername());
//...
List<String> brandsNames = new ArrayList<>();
for(MainBrand mainBrand : user.getUserConfig().getMainBrands()){
brandsNames.add(mainBrand.getName());
}
}
That will fail if don't put the #Transactional annotation at Controller level, because of LazyInitializationException.
So, that's the choices that I've thinked out:
1) With the user make a call to an "UserConfigService" (it's not created now) like userConfigService.getUserConfigByUserId(userId): that's make me think that if I already have the binding at User class, why I would call it again? And I am just creating a new service only for this method.
2) Put the #Transactional annotation at controller level: which makes another problem for my, but it doesn't care in this post.
3) Call the getUserConfig() & getUserConfig().getMainBrands() at UserService so then the collection get initialized: don't like because whenever I use the getById it will initialize the collection even if I do not need it.
So what it would be a good practice for this case? On internet there are always perfect and beautiful examples, but when we start to give some business logic to the project, it turns hard to have a clean code.
Thanks, and sorry for my english.
LazyInitializationException is not related to transactional , it is related to relationship between objects, if your object has a lazy relation,you must fetch your MainBrands objects in your userService.getById(userId) query method before you return your user.
Transactional annotation must be in service class, you can create as many service classes as you need.
I'm trying to get the old entity in a #HandleBeforeSave event.
#Component
#RepositoryEventHandler(Customer.class)
public class CustomerEventHandler {
private CustomerRepository customerRepository;
#Autowired
public CustomerEventHandler(CustomerRepository customerRepository) {
this.customerRepository = customerRepository;
}
#HandleBeforeSave
public void handleBeforeSave(Customer customer) {
System.out.println("handleBeforeSave :: customer.id = " + customer.getId());
System.out.println("handleBeforeSave :: new customer.name = " + customer.getName());
Customer old = customerRepository.findOne(customer.getId());
System.out.println("handleBeforeSave :: new customer.name = " + customer.getName());
System.out.println("handleBeforeSave :: old customer.name = " + old.getName());
}
}
In the event I try to get the old entity using the findOne method but this return the new event. Probably because of Hibernate/Repository caching in the current session.
Is there a way to get the old entity?
I need this to determine if a given property is changed or not. In case the property is changes I need to perform some action.
If using Hibernate, you could simply detach the new version from the session and load the old version:
#RepositoryEventHandler
#Component
public class PersonEventHandler {
#PersistenceContext
private EntityManager entityManager;
#HandleBeforeSave
public void handlePersonSave(Person newPerson) {
entityManager.detach(newPerson);
Person currentPerson = personRepository.findOne(newPerson.getId());
if (!newPerson.getName().equals(currentPerson.getName)) {
//react on name change
}
}
}
Thanks Marcel Overdijk, for creating the ticket -> https://jira.spring.io/browse/DATAREST-373
I saw the other workarounds for this issue and want to contribute my workaround as well, cause I think it´s quite simple to implement.
First, set a transient flag in your domain model (e.g. Account):
#JsonIgnore
#Transient
private boolean passwordReset;
#JsonIgnore
public boolean isPasswordReset() {
return passwordReset;
}
#JsonProperty
public void setPasswordReset(boolean passwordReset) {
this.passwordReset = passwordReset;
}
Second, check the flag in your EventHandler:
#Component
#RepositoryEventHandler
public class AccountRepositoryEventHandler {
#Resource
private PasswordEncoder passwordEncoder;
#HandleBeforeSave
public void onResetPassword(Account account) {
if (account.isPasswordReset()) {
account.setPassword(encodePassword(account.getPassword()));
}
}
private String encodePassword(String plainPassword) {
return passwordEncoder.encode(plainPassword);
}
}
Note: For this solution you need to send an additionally resetPassword = true parameter!
For me, I´m sending a HTTP PATCH to my resource endpoint with the following request payload:
{
"passwordReset": true,
"password": "someNewSecurePassword"
}
You're currently using a spring-data abstraction over hibernate.
If the find returns the new values, spring-data has apparently already attached the object to the hibernate session.
I think you have three options:
Fetch the object in a separate session/transaction before the current season is flushed. This is awkward and requires very subtle configuration.
Fetch the previous version before spring attached the new object. This is quite doable. You could do it in the service layer before handing the object to the repository. You can, however not save an object too an hibernate session when another infect with the same type and id it's known to our. Use merge or evict in that case.
Use a lower level hibernate interceptor as described here. As you see the onFlushDirty has both values as parameters. Take note though, that hibernate normally does not query for previous state of you simply save an already persisted entity. In stead a simple update is issued in the db (no select). You can force the select by configuring select-before-update on your entity.
Create following and extend your entities with it:
#MappedSuperclass
public class OEntity<T> {
#Transient
T originalObj;
#Transient
public T getOriginalObj(){
return this.originalObj;
}
#PostLoad
public void onLoad(){
ObjectMapper mapper = new ObjectMapper();
try {
String serialized = mapper.writeValueAsString(this);
this.originalObj = (T) mapper.readValue(serialized, this.getClass());
} catch (Exception e) {
e.printStackTrace();
}
}
}
I had exactly this need and resolved adding a transient field to the entity to keep the old value, and modifying the setter method to store the previous value in the transient field.
Since json deserializing uses setter methods to map rest data to the entity, in the RepositoryEventHandler I will check the transient field to track changes.
#Column(name="STATUS")
private FundStatus status;
#JsonIgnore
private transient FundStatus oldStatus;
public FundStatus getStatus() {
return status;
}
public FundStatus getOldStatus() {
return this.oldStatus;
}
public void setStatus(FundStatus status) {
this.oldStatus = this.status;
this.status = status;
}
from application logs:
2017-11-23 10:17:56,715 CompartmentRepositoryEventHandler - beforeSave begin
CompartmentEntity [status=ACTIVE, oldStatus=CREATED]
Spring Data Rest can't and likely won't ever be able to do this due to where the events are fired from. If you're using Hibernate you can use Hibernate spi events and event listeners to do this, you can implement PreUpdateEventListener and then register your class with the EventListenerRegistry in the sessionFactory. I created a small spring library to handle all of the setup for you.
https://github.com/teastman/spring-data-hibernate-event
If you're using Spring Boot, the gist of it works like this, add the dependency:
<dependency>
<groupId>io.github.teastman</groupId>
<artifactId>spring-data-hibernate-event</artifactId>
<version>1.0.0</version>
</dependency>
Then add the annotation #HibernateEventListener to any method where the first parameter is the entity you want to listen to, and the second parameter is the Hibernate event that you want to listen for. I've also added the static util function getPropertyIndex to more easily get access to the specific property you want to check, but you can also just look at the raw Hibernate event.
#HibernateEventListener
public void onUpdate(MyEntity entity, PreUpdateEvent event) {
int index = getPropertyIndex(event, "name");
if (event.getOldState()[index] != event.getState()[index]) {
// The name changed.
}
}
Just another solution using model:
public class Customer {
#JsonIgnore
private String name;
#JsonIgnore
#Transient
private String newName;
public void setName(String name){
this.name = name;
}
#JsonProperty("name")
public void setNewName(String newName){
this.newName = newName;
}
#JsonProperty
public void getName(String name){
return name;
}
public void getNewName(String newName){
return newName;
}
}
Alternative to consider. Might be reasonable if you need some special handling for this use-case then treat it separately. Do not allow direct property writing on the object. Create a separate endpoint with a custom controller to rename customer.
Example request:
POST /customers/{id}/identity
{
"name": "New name"
}
I had the same problem, but I wanted the old entity available in the save(S entity) method of a REST repository implementation (Spring Data REST).
What I did was to load the old entity using a 'clean' entity manager from which I create my QueryDSL query:
#Override
#Transactional
public <S extends Entity> S save(S entity) {
EntityManager cleanEM = entityManager.getEntityManagerFactory().createEntityManager();
JPAQuery<AccessControl> query = new JPAQuery<AccessControl>(cleanEM);
//here do what I need with the query which can retrieve all old values
cleanEM.close();
return super.save(entity);
}
The following worked for me. Without starting a new thread the hibernate session will provide the already updated version. Starting another thread is a way to have a separate JPA session.
#PreUpdate
Thread.start {
if (entity instanceof MyEntity) {
entity.previous = myEntityCrudRepository.findById(entity?.id).get()
}
}.join()
Just let me know if anybody would like more context.
Don't know if you're still after an answer, and this is probably a bit 'hacky', but you could form a query with an EntityManager and fetch the object that way ...
#Autowired
EntityManager em;
#HandleBeforeSave
public void handleBeforeSave(Customer obj) {
Query q = em.createQuery("SELECT a FROM CustomerRepository a WHERE a.id=" + obj.getId());
Customer ret = q.getSingleResult();
// ret should contain the 'before' object...
}
I am trying setting a generic class for my project. So far I get this code:
https://github.com/klebermo/blog.cms/blob/master/src/main/java/com/config/generic/controller/GenericController.java
In this moment, I am stuck with this method:
#RequestMapping(value="cadastra", method=RequestMethod.POST)
#ResponseBody
public String cadastra(#ModelAttribute("") E object, BindingResult result) {
if(service.cadastra(object))
return "yes";
else
return "not";
}
the parameter for the annotation ModelAtribute should be the name of an entity class from my project, which I have stored in the class member entity_class, but can't use because the annotation only accept constant values.
Also, I have this same problem with the annotation PreAutorize. I will use this annotation like that:
#PreAuthorize("hasPermission(#user, '<<permission_name>>')")
#RequestMapping(value="cadastra")
public ModelAndView cadastra() {
ModelAndView mav = new ModelAndView();
mav.setViewName("privado/"+this.entity_name+"/cadastra");
return mav;
}
and in my methods from my generic service too. The permission name follows this rule:
<<action_name>>_<<entity_name>>
and for each entity class I will have three permissions: cadastra (new_item), altera (change_item) and remove (remove_item).
Anyone can point a direction of how to solve this?
For the first question #ModelAttribute, what I've understood, you have several form calling the same Mapping method cadastra to verify if the informations are correct.
I think you can use a form parameter to tell which entity class it is, and use wrapper to wrap all of the entities that call this method.
create an Entity:
#Entity
public class Wrapper{
private Entity1 e1;
.....
in your jsp:
<form:form modelAttribute="wrapper" .../>
<form:hidden value="classname" .../>
<form:input path=e1.someProperty .../>
in your controller:
#RequestParam("classname") String classname;//used to get which entity it's
service.cadastra(wrapper.getEntityX())
for the second question I don't know much about #PreAuthorize annotation.
When or where do entities get created?
Do they get created when the XHTML page loads and accesses the entities via the managed bean?
Or do they get automatically created in the managed bean?
Do we need to manually create it from the managed bean's constructor?
Please see the code below (some necessary code might not have been copied.)
The entity would be:
public class PersonalInfo implements Serializable {
#Size(max = 50)
#Column(name = "FIRST_NAME", length = 50)
private String firstName;
// some getters and setters
}
the web page would be:
<h:form>
<h:outputText value="first name"/>
<h:inputText value="#{personalInforController.personalInfo.firstName}" />
<h:commandButton value="hit me"
action="#{personalInforController.create}"
immediate="true"/>
</h:form>
and the backing bean would be:
#Named(value = "personalInfoController")
#SessionScoped
public class PersonalInforController {
#EJB
PersonalInfoFacade ejbFacade;
PersonalInfo personalInfo;
String defaultPage = "index";
public String create() {
try {
ejbFacade.create(personalInfo);
return "prepareCreate";
} catch (Exception e) {
return "success";
}
}
}
In the example code given, the create action indeed doesn't seem to be able to work. The entity must be created by the backing bean before that.
If it's a simple entity, either the constructor or an #PostConstruct method would work. For instance:
#Named(value = "personalInfoController")
#SessionScoped
public class PersonalInforController {
#EJB
PersonalInfoFacade ejbFacade;
PersonalInfo personalInfo;
String defaultPage = "index";
#PostConstruct
public void init() {
personalInfo = new PersonalInfo();
}
public String create() {
try {
ejbFacade.create(personalInfo);
return "prepareCreate";
} catch (Exception e) {
return "success";
}
}
Some notes about the code. It's highly suspicious, and most likely plain wrong, to declare your bean to be #SessionScoped. If personalInfo is being edited in two tabs or windows you'll be in a world of hurt. I suggest making your bean #ViewScoped (for CDI, there's a separate extension made by the Seam3 that enables this, if you can't/won't use this extension consider using #ManagedBean instead of #Named).
Also, you might want to declare your instance variables to be private and give ejbFacade a better name (e.g. personalInfoFacade). I also doubt whether immediate is necessary on the commandButton, and since the outputText is obviously a label for the given inputText, you might want to consider using outputLabel and the for attribute.