Generic search with spring-data - spring

I use spring data which I found very interesting but there is a problem I want a generic way to search the field of entity.
I got a entity which have many field
public class Lostcard implements java.io.Serializable {
private Integer id;
private String nom;
private String prenom;
private String cin;
#DateTimeFormat(pattern = "MM/dd/yyyy")
private Date dateDeclaration;
#DateTimeFormat(pattern = "MM/dd/yyyy")
private Date dateDuplicata;
private String annexeAdmin;
[...]
So I want to do this:
public interface LostcardRepository extends JpaRepository<Lostcard, Integer> {
List<Lostcard> findByNom(String nom);
List<Lostcard> findByPrenom(String prenom);
List<Lostcard> findByCin(String cin);
[...]
}
There is not a generic way like findByProperty(String property, Object value) ?

The easiest way in my opinion is to use Specification. You have to make your interface extends also JpaSpecificationExecutor and then you can use your own Specification to execute query.
public interface LostcardRepository extends JpaRepository<Lostcard, Integer>, JpaSpecificationExecutor<Lostcard> {
...
}
Then implement class similar to the one below:
public class PropertySpecifications {
public static Specification<Lostcard> byProperty(final String propertyName, final Object propertyValue) {
return new Specification<Lostcard>() {
#Override
public Predicate toPredicate(Root<Lostcard> candidateRoot, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
return criteriaBuilder.equal(candidateRoot.get(propertyName), propertyValue);
}
};
}
}
Then you can execute query:
lostcardRepository.findAll(Specifications.where(PropertySpecifications.byProperty("property", "value")));

You can declare a query with parameters. You can get more complex with that, but using JpaRepository query methods you can only query for existing entity fields.
#Query("SELECT p FROM Lostcard p WHERE p.yourfield = (:field)")
public Lostcard findByProperty(#Param("field") String property);

Related

Spring data jpa Specification: How to filter a parent object by its children object property

My entity classes are following
#Entity
#table
public class User {
#OneToOne
private UserProfile userProfile;
// others
}
#Entity
#Table
public class UserProfile {
#OneToOne
private Country country;
}
#Entity
#Table
public class Country {
#OneToMany
private List<Region> regions;
}
Now I want to get all the user in a particular region. I know the sql but I want to do it by spring data jpa Specification. Following code should not work, because regions is a list and I am trying to match with a single value. How to fetch regions list and compare with single object?
public static Specification<User> userFilterByRegion(String region){
return new Specification<User>() {
#Override
public Predicate toPredicate(Root<User> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
return criteriaBuilder.equal(root.get("userProfile").get("country").get("regions").get("name"), regionalEntity);
}
};
}
Edit: Thanks for the help. Actually I am looking for the equivalent criteria query for the following JPQL
SELECT u FROM User u JOIN FETCH u.userProfile.country.regions ur WHERE ur.name=:<region_name>
Try this. This should work
criteriaBuilder.isMember(regionalEntity, root.get("userProfile").get("country").get("regions"))
You can define the condition for equality by overriding Equals method(also Hashcode) in Region class
Snippet from my code
// string constants make maintenance easier if they are mentioned in several lines
private static final String CONST_CLIENT = "client";
private static final String CONST_CLIENT_TYPE = "clientType";
private static final String CONST_ID = "id";
private static final String CONST_POST_OFFICE = "postOffice";
private static final String CONST_INDEX = "index";
...
#Override
public Predicate toPredicate(Root<Claim> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
List<Predicate> predicates = new ArrayList<Predicate>();
// we get list of clients and compare client's type
predicates.add(cb.equal(root
.<Client>get(CONST_CLIENT)
.<ClientType>get(CONST_CLIENT_TYPE)
.<Long>get(CONST_ID), clientTypeId));
// Set<String> indexes = new HashSet<>();
predicates.add(root
.<PostOffice>get(CONST_POST_OFFICE)
.<String>get(CONST_INDEX).in(indexes));
// more predicates added
return return andTogether(predicates, cb);
}
private Predicate andTogether(List<Predicate> predicates, CriteriaBuilder cb) {
return cb.and(predicates.toArray(new Predicate[0]));
}
If you are sure, that you need only one predicate, usage of List may be an overkill.

No composite key property found for type error in Spring JPA2

I have an error in spring JPA
org.springframework.data.mapping.PropertyReferenceException: No property CompanyId found for type CompanyUserDetail!
#Embeddable
public class CompanyUserKey implements Serializable {
public CompanyUserKey() {
}
#Column(name = "company_id")
private UUID companyId;
#Column(name = "user_name")
private String userName;
public UUID getCompanyId() {
return companyId;
}
public void setCompanyId(UUID companyId) {
this.companyId = companyId;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
}
#Entity
#Table(name = "company_user_detail")
public class CompanyUserDetail {
#EmbeddedId
CompanyUserKey companyUserkey;
public CompanyUserKey getCompanyUserkey() {
return companyUserkey;
}
public void setCompanyUserkey(CompanyUserKey companyUserkey) {
this.companyUserkey = companyUserkey;
}
}
I am trying to access below method Service layer
#Component
public interface CompanyUserRepository extends JpaRepository<CompanyUserDetail, CompanyUserKey> {
public List<CompanyUserDetail> findByCompanyId(UUID companyId);
}
How can I achieve this ?
Thanks
Since in java model your CompanyUserKey is a property in the CompanyUserDetail class, I believe you should use full path (companyUserkey.companyId) to reach companyId:
public List<CompanyUserDetail> findByCompanyUserkeyCompanyId(UUID companyId);
Also note that you have a naming inconsistency: field in CompanyUserDetail is named companyUserkey instead of companyUserKey.
Assuming you are not using spring-data-jpa's auto generated implementations, your method contents might look something like the following:
FROM CompanyUserDetail c WHERE c.companyUserKey.companyId = :companyId
Now simply provide that query to the EntityManager
entityManager.createQuery( queryString, CompanyUserDetail.class )
.setParameter( "companyId", companyId )
.getResultList();
The key points are:
Query uses a named bind parameter called :companyId (not the leading :).
Parameter values are bound in a secondary step using setParameter method variants.
createQuery uses a second argument to influence type safety so that the return value from getResultList is a List<CompanyUserDetail> just like you requested.
Looking at spring-data-jpa's implementation however, I suspect it could look like this:
public interface CustomerUserRepository
extends JpaRepository<CompanyUserDetail, CompanyUserKey> {
#Query("select c FROM CompanyUserDetail c WHERE c.companyUserKey.companyId = :companyId")
List<CompanyUserDetail> findByCompanyId(#Param("companyId") UUID companyId);
}

Can we build Spring Data JPA specification out of a composite key attribute

I am using Spring Data JPA specifications for generic queries to my entities. So far it has worked well. Now the problem happened when I try to use these on composite keys used via embeddedId annotation. So here I need to use a nested property to query which say if the object is Foo and I am trying to write a criteria over id. id1 for example.
#Entity
#Data
public class Foo implements Serializable {
private static final long serialVersionUID = 1L;
/** The key. */
#EmbeddedId
private FooKey id;
}
#Embeddable
#Data
public class FooKey implements Serializable {
private static final long serialVersionUID = 1L;
/** The key. */
private String id1;
private String id2;
}
In the specification I am trying to do
#Override
public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
// get root type
root.get(property).getJavaType()
But this doesn't work for nested attributes like in this case. Is there any way I can be able to build predicates for properties in the composite key.
Example of Equal:
#Override
public Predicate toPredicate(Root<Foo> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
builder.equal(root.get("id").get("id1"),"my val");
You can build a function like this if you want to use multiple levels. In my experience, 2 levels are more than sufficient. But depends on you.
protected Path<Comparable> getPath(Root<EntityOrModel> root) {
Path<Comparable> path;
if (criteria.getKey().contains(".")) {
String[] split = criteria.getKey().split("\\.");
int keyPosition = 0;
path = root.get(split[keyPosition]);
for (String criteriaKeys : split) {
if (keyPosition > 0) {
path = path.get(criteriaKeys);
}
keyPosition++;
}
} else {
path = root.get(criteria.getKey());
}
return path;
}
Then set
#Override
public Predicate toPredicate(Root<EntityOrModel> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
Path<Comparable> path = getPath(root);
// ...
return builder.equal(path, value)
}

Is it possible to avoid typecast on custom #Query?

Imagine that we have an entity:
#Entity
public class Person implements Serializable {
#Id
private String name;
private Long age;
private Boolean isMad;
...
}
And a repository with a trivial (and unnecessary) example for a custom query:
#Repository
public interface PersonRepository extends PagingAndSortingRepository<Info, String> {
#Query("select p.isMad, count(*) from Person p group by p.isMad")
List<Object> aggregateByMadness();
}
Now to parse this List we need to do something like this:
for (Object element : list) {
Object[] result = (Object[]) element;
Boolean isMad = (Boolean) result[0];
Long count = (Long) result[1];
}
which is a pain, can we cast the result of the query directly to List of a POJO?
Yes, you could use the JPQL construction expression:
package com.foo;
public class Madness {
public Madness(boolean isMad, Number count) { /* ...*/ }
}
And in your repository:
#Query("select new com.foo.Madness(p.isMad, count(*)) from Person p group by p.isMad")
List<Madness> aggregateByMadness();

Store enum name, not value in database using EBean

I have this enum :
public enum DocumentTypes {
PDF("PDF Document"), JPG("Image files (JPG)"), DOC("Microsoft Word documents");
private final String displayName;
DocumentTypes(final String display) {
this.displayName = display;
}
#Override
public String toString() {
return this.displayName;
}
}
And a model like this :
#Entity
#Table(name = "documents")
public class Document extends Model {
#Id
public Long id;
#Constraints.Required
#Formats.NonEmpty
#Enumerated(EnumType.STRING)
#Column(length=20, nullable=false)
public DocumentTypes type;
#Constraints.Required
#Formats.NonEmpty
#Column(nullable=false)
public String document;
}
I match the enum using this in my controller :
DynamicForm form = form().bindFromRequest();
// ...
Document doc = new Document();
doc.type = DocumentTypes.valueOf(form.field("type").value());
doc.save();
The problem is that in database, it's stored as "Microsoft Word documents", but I would prefer to store it as DOC.
How can I do that?
You can define it very fine granular with the Anotation EnumMapping or EnumValue. This works with the old version org.avaje.ebean.
It seems that there was a complete rewrite of the code. In the actual version there is a different approach.

Resources