I am using Liferay to develop a module. A part of involves fetching only those records form the database where the leave status of employees is Pending. the code that I have to fetch "Pending" records is:
#SuppressWarnings("unchecked")
public static List<Employee> getEmployeeData() throws PortalException, SystemException{
List<Employee> employeeDetails;
try{
int totalEmployees = EmployeeLocalServiceUtil.getEmployeesCount();
for(Employee emp: employeeDetails {
if(emp.getEmpStatus.equals("Pending") {
employeeDetails= EmployeeLocalServiceUtil.getEmployees(0,totalEmployees);
}
}
}catch(SystemException se){
employeeDetails = Collections.emptyList();
}
return employeeDetails;
}
The above code fetches all the details - Pending as well as non pending. This I know happens because of the statement in the above code:
employeeDetails= EmployeeLocalServiceUtil.getEmployees(0,totalEmployees);
since It fetches all the rows. So how should I structure and modify my code to get only the pending details?
As you are dealing with custom entity in Liferay, you can use finder tag in service.xml for such scenario. In service.xml define finder for empStatus field.
<finder name="EmpStatus" return-type="Collection">
<finder-column name="empStatus"/>
</finder>
This will create finder[findByEmpStatus(String status)] method in **Persistence.java which will return specific rows based on status , now You need to manually add methods to your *(Local)ServiceImpl.java files. Those methods will call your *persitence.finderMethodName() methods.
HTH,
A quick but really bad practice is keeping your code with this change :
List<Employee> employeeDetails = new ArrayList<Employee>;
try{
List<Employees> allEmployees = EmployeeLocalServiceUtil.getAllEmployees();
for(Employee emp: allEmployees {
if(emp.getEmpStatus.equals("Pending") {
employeeDetails.add(emp);
}
}return employeeDetails;
Now, the correct way to do this is :
add a Finder, as #Pankaj Kathiriya already proposed. Then, build services
go to EmployeeLocalServiceImpl, and add
public List getAllEmployeesByEmpStatus (String status) {
try {
return employeePersistence.findByEmpStatus(status);
} catch (SystemException e) {
e.printStackTrace(); return Collections.emptyList();
}
}
then build service again
Replace your code with
List employeeDetails = EmployeeLocalServiceUtil.getAllEmployeesByEmpStatus("Pending") ;
return employeeDetails;
I have a solution for this. I got a suggestion in the forum to use Dyanamic Query in liferay.
And it is pretty simple.
Read the wiki on Dynamic Query on liferay.
Well to answer my own question, here is the solution I found:
DynamicQuery dynamicQuery = DynamicQueryFactoryUtil.forClass(LeaveApplication.class);
dynamicQuery.add(PropertyFactoryUtil.forName("EmpStatus").eq("Pending"));
and Then I return of those people whose status is pending..
Hope it helps someone.. More suggestions are welcome :)
Related
Assume that "Project" is POJO. In service layer of my project, I wrote a function that is get a row from table.
#Override
public ProjectDto getAProject(Long id) {
try {
Project project = projectRepository.getOne(id);
if (project==null) {
throw new IllegalArgumentException("Project not found");
} else {
return modelMapper.map(project, ProjectDto.class);
}
} catch (EntityNotFoundException ex) {
throw new IllegalArgumentException("Project not found");
}
}
The function is working fine with already exist id values. But if I give non-exist value, an exception occur like following. Looks like "getOne()" function don't throw "EntityNotFoundException".
ModelMapper mapping errors: Error mapping com.issuemanagement.entity.Project to com.issuemanagement.dto.ProjectDto
that means the exception come from model mapper logic. Because "project" object filled with null values so couldn't map to DTO class. I modified the function as following to fix this.
#Override
public ProjectDto getAProject(Long id) {
boolean isExist = projectRepository.existsById(id);
if (isExist) {
Project project = projectRepository.getOne(id);
return modelMapper.map(project, ProjectDto.class);
} else {
throw new IllegalArgumentException("Project not found");
}
}
but in this way the program goes to DB for two times. I don't like it. How can I do this operation with just one transaction?
BTW, if I try to run "toString()" function of "project", it throw "EntityNotFoundException" but it's looks like not official way. or it is? I hope there should be a boolean variable in somewhere.
getOne() on JpaRepository will call getReference() on EntityManager under the hood which will return an instance whose state is lazily fetch .The EntityNotFoundException will not throw immediately but will only be thrown when its state is accessed at the first time .
It is normally used in the case that when you need to configure a #ManyToOne relationship for an entity (Let say configure a Department for an Employee) but you just have the ID of the related entity.(e.g. DepartmentId) . Using getOne() allows you to get a Department proxy instance such that you do not really need to query the database to get the actual Department instance just for setting up its relationship for an Employee.
In your case , you should use findById() which will return an empty Optional if the instance does not exist:
#Override
public ProjectDto getAProject(Long id) {
Project project = projectRepository.findById(id)
.orElseThrow(()->new IllegalArgumentException("Project not found"));
return modelMapper.map(project, ProjectDto.class);
}
I have implemented by project using Spring-Data-Rest. I am trying to do an update on an existing record in a table. But when I try to send only a few fields instead of all the fields(present in Entity class) through my request, Spring-Data-Rest thinking I am sending null/empty values. Finally when I go and see the database the fields which I am not sending through my request are overridden with null/empty values. So my understanding is that even though I am not sending these values, spring data rest sees them in the Entity class and sending these values as null/empty. My question here is, is there a way to disable the fields when doing UPDATE that I am not sending through the request. Appreciate you are any help.
Update: I was using PUT method. After reading the comments, I changed it to PATCH and its working perfectly now. Appreciate all the help
Before update, load object from database, using jpa method findById return object call target.
Then copy all fields that not null/empty from object-want-to-update to target, finally save the target object.
This is code example:
public void update(Object objectWantToUpdate) {
Object target = repository.findById(objectWantToUpdate.getId());
copyNonNullProperties(objectWantToUpdate, target);
repository.save(target);
}
public void copyNonNullProperties(Object source, Object target) {
BeanUtils.copyProperties(source, target, getNullPropertyNames(source));
}
public String[] getNullPropertyNames (Object source) {
final BeanWrapper src = new BeanWrapperImpl(source);
PropertyDescriptor[] propDesList = src.getPropertyDescriptors();
Set<String> emptyNames = new HashSet<String>();
for(PropertyDescriptor propDesc : propDesList) {
Object srcValue = src.getPropertyValue(propDesc.getName());
if (srcValue == null) {
emptyNames.add(propDesc.getName());
}
}
String[] result = new String[emptyNames.size()];
return emptyNames.toArray(result);
}
You can write custom update query which updates only particular fields:
#Override
public void saveManager(Manager manager) {
Query query = sessionFactory.getCurrentSession().createQuery("update Manager set username = :username, password = :password where id = :id");
query.setParameter("username", manager.getUsername());
query.setParameter("password", manager.getPassword());
query.setParameter("id", manager.getId());
query.executeUpdate();
}
As some of the comments pointed out using PATCH instead of PUT resolved the issue. Appreciate all the inputs. The following is from Spring Data Rest Documentation:
"The PUT method replaces the state of the target resource with the supplied request body.
The PATCH method is similar to the PUT method but partially updates the resources state."
https://docs.spring.io/spring-data/rest/docs/current/reference/html/#customizing-sdr.hiding-repository-crud-methods
Also, I like #Tran Quoc Vu answer but not implementing it for now since I dont have to use custom controller. If there is some logic(ex: validation) involved when updating the entity, I am in favor of using the custom controller.
I am working on a Spring-MVC project where I am using Hibernate for persistence. In one of the model classes I have a List which I want to persist. I am facing the problem as I don't know which dataType to use in PostgreSQL and do I need instruct hibernate in some way or other that I am trying to persist a List. Performance requirements are not that much of a problem on this list, as it does not get that much action. I am posting some code for reference, kindly let me know. Thanks a lot :
GroupAccount model class :
#Entity
#Table(name="groupaccount")
public class GroupAccount {
#Column(name = "blacklist")
private List<String> blacklist;
public List<String> getBlacklist() {
return blacklist;
}
public void setBlacklist(List<String> blacklist) {
this.blacklist = blacklist;
}
}
I would sometimes require to update the values of the blacklist, so I have a method in DAO which updates the groupAccount, I am pasting it below.
GroupAccountDAOImpl edit function :
#Override
public void editGroupAccount(GroupAccount groupAccount) {
session = this.sessionFactory.getCurrentSession();
GroupAccount groupAccount1 = (GroupAccount)session.get(GroupAccount.class,groupAccount.getGroupId());
if(!(groupAccount1==null)){
groupAccount.setOwnedcanvas(groupAccount1.getOwnedcanvas());
groupAccount.setGroupMembersSet(groupAccount1.getGroupMembersSet());
session.merge(groupAccount);
session.flush();
}
}
One use-case for adding users in blacklist :
List<String> blackListUsers;
blackListUsers = groupAccount.getBlacklist();
blackListUsers.add(memberForBlackListing.getMemberUsername());
groupAccount.setBlacklist(blackListUsers);
this.groupAccountService.editGroupAccount(groupAccount);
removeAllMemberships(memberId);
return true;
Any help would be nice. Thanks a lot. :-)
You can't map List<String> to a single column. For these cases, #ElementCollection is used
#ElementCollection
#CollectionTable(name="blacklist", joinColumns=#JoinColumn(name="group_account_id")
#Column(name = "name")
private List<String> blacklist;
This requires a database table named blacklist with columns name and group_account_id (which will be used as a foreign key to group_account table). Of course, table and column names are customizable.
My problem is that Hibernate does not read any data from DB if it was already inserted manually (I'm using MySQL). What I mean is that I dropped PATIENTS table and then Hibernate created it for me and after that I inserted data from PATIENTS.sql. When I launch my application there are no patients displayed in, for example, show_patients.jsp. But I still can access my application via login page using inserted records.
Here is my method in #Controller:
#RequestMapping(value = "/therapist/showPatients", method = RequestMethod.GET)
public String showExistingPatients(ModelMap map) {
List<Patient> patientsList = userService.getAllPatients();
map.addAttribute("patientsList", patientsList);
return "therapist/show_patients";
}
Here is my method in UserDAOImpl:
public List<Patient> selectAllPatients(){
DetachedCriteria criteria = DetachedCriteria.forClass(Patient.class);
List<Patient> patients = null;
try{
patients = getHibernateTemplate().findByCriteria(criteria);
} catch (DataAccessException ex){
ex.printStackTrace();
}
return patients;
}
The only way I can see any patients in show_patients.jsp is to add new patient via my application allowing Hiberante to save it. So my question is if there is any condition why this works only after Hibernate's save() or am I doing something wrong?
Two variants:
Query cache is turned on in Hibernate. Check settings; maybe, "evict" can be used for remove query results from cache.
Maybe, web page caching present on WebServer or browser settings, check required.
Let's say I have following classes. (only most important things included)
public class Client {
/* Some Properties */
}
public class ClientDocumentAssociation {
#ManyToOne
private Client client;
/* Some Properties */
}
#Indexed
public class Document {
#OneToOne
private ClientDocumentAssociation clientAssociation;
#Field(name = "text")
private String text;
/* Some Properties */
}
My basic document search is like this:
public List<AbstractDocument> searchDocuments(String text) {
if (text == null) {
return newArrayList();
}
FullTextEntityManager ftem = Search.getFullTextEntityManager(entityManagerProvider.get());
MultiFieldQueryParser parser = new MultiFieldQueryParser(DOCUMENT_FIELDS, new StandardAnalyzer());
parser.setDefaultOperator(Operator.AND);
FullTextQuery ftq;
try {
Query q = parser.parse(text + "*");
ftq = ftem.createFullTextQuery(q, Document.class);
ftq.setMaxResults(20);
List<AbstractDocument> results = ftq.getResultList();
return results;
} catch (ParseException e) {
e.printStackTrace();
}
return newArrayList();
}
Now, I want to be able to search for documents, but not in the scope of the whole index, but just find documents that belong to given Client. The only thing that comes to my mind is adding the association to the index and add client id to the appropriate field in search. But that does not seem right. There must be another option and that's what I am asking for.
Why does your initial idea seem to be wrong? In fact indexing all the data needed for your search is the recommended way of doing this. That's what #IndexedEmbedded is there for. Indexing the data will also give you more flexibility for changes in the query and/or new queries.
The other way you can do this is to use Filters. A filter can be applied to a Lucene search. Hibernate supports adding filters as annotations and enabling them at run time
Ok I actually found a solution. The thing I (and anyone who was searching for solution of the same problem) needed is setting up Criteria for the FullTextQuery.
Session session = (Session) ftem.getDelegate();
Criteria criteria = session.createCriteria(Document.class).createCriteria("clientAssociation").add(
Restrictions.eq("client", owner));
/* .... */
ftq.setCriteriaQuery(criteria);
Seems to work ok :)