Specifications and (null) Many-To-One Relationship - spring-data-jpa

I have an MVC Controller that return a List of Contacts as JSON. On frontend side i use jquery datatables plugin. There is a searchfield on the frontend to filter the entity list.
my entities:
#Entity
public class Contact implements Serializable {
protected final static Logger LOGGER = LoggerFactory.getLogger(Contact.class);
private static final long serialVersionUID = -3691953100225344828L;
#Id
#GeneratedValue(generator = "hibernate-uuid")
#Column(length = 36, unique = true)
private String id;
#Version
#JsonIgnore
private int version;
private String firstname;
private String lastname;
#ManyToOne
private Company company;
... GETTER/SETTER ...
}
and
#Entity
public class Company implements Serializable {
protected final static Logger LOGGER = LoggerFactory.getLogger(Company.class);
private static final long serialVersionUID = -7863930456400256944L;
#Id
#GeneratedValue(generator = "hibernate-uuid")
#Column(length = 36, unique = true)
private String id;
private String companyName;
private String companyName1;
private String companyName2;
... GETTER/SETTER ...
}
I use server side processing for the search field and on server side i use specifications.
public class ContactSpecifications {
public static Specification<Contact> contactFirstnameLike(final String needle) {
return new Specification<Contact>() {
#Override
public Predicate toPredicate(Root<Contact> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
return cb.like(cb.lower(root.<String> get(Contact_.firstname)), needle != null ? needle.toLowerCase() : null);
}
};
}
public static Specification<Contact> contactLastnameLike(final String needle) {
return new Specification<Contact>() {
#Override
public Predicate toPredicate(Root<Contact> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
return cb.like(cb.lower(root.<String> get(Contact_.lastname)), needle != null ? needle.toLowerCase() : null);
}
};
}
public static Specification<Contact> contactFullnameLike(final String needle) {
return new Specification<Contact>() {
#Override
public Predicate toPredicate(Root<Contact> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
return cb.or(cb.like(cb.lower(root.<String> get(Contact_.lastname)), needle != null ? needle.toLowerCase() : null), cb.like(cb.lower(root.<String> get(Contact_.firstname)), needle != null ? needle.toLowerCase() : null));
}
};
}
public static Specification<Contact> contactCompanyCompanyNameLike(final String needle) {
return new Specification<Contact>() {
#Override
public Predicate toPredicate(Root<Contact> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
final Path<Company> company = root.<Company> get(Contact_.company);
return cb.like(cb.lower(company.<String> get(Company_.companyName)), needle != null ? needle.toLowerCase() : null);
}
};
}
}
My db query
contactRepository.findAll(specifications, new PageRequest(0,100));
and specifications are
specifications = Specifications.where(ContactSpecifications.contactFullnameLike(needle)).or(ContactSpecifications.contactCompanyCompanyNameLike(needle));
needle is the search key from the frontend and mask with surrounding % (for example "%asdf%")
My problem is, if the contact has no company the specifications not working as expected.
For example i have 3 Contacts:
Lastname: Schmitz, Firstname: Max, Company: (null)
Lastname: Schmitz, Firstname: Moritz, Company: XY
Lastname: Muster, Firstname: Max, Company: XY
If i now enter Schmitz as search key, only contact 2 returned, contact 1 not.
If i enter max as search key, only contact 3 returned, contact 1 not
Only if the search key is null/empty, all contacts returned
I miss something?
kind regards
Rizzi

answer myself ;)
After research the sql queries i found the solution. I have to rewrite my specifications. On related entities i have to add an left join path to prevent criteria builder automatically use cross/inner joins.
Inner join only returned entities, that has all fields set. if some entity relation is null, this entity is drop from result list. Normal inner join behaviour.
so...
Correct specification must be like this.
public static Specification<Contact> contactCompanyCompanyNameLike(final String needle) {
return new Specification<Contact>() {
#Override
public Predicate toPredicate(Root<Contact> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
final Join<Contact,Company> company = root.join(Contact_.company, JoinType.LEFT);
return cb.like(cb.lower(company.<String> get(Company_.companyName)), needle != null ? needle.toLowerCase() : null);
}
};
}
With this small modifications it's starts working correctly now.
kind regards Rizzi

Related

method in ArangoRepository extension class using COLLECT in query annotation to group by and count not working

I have a simple node like this below
#Document("users")
public class User {
#Id // db document field: _key
private String id;
#ArangoId // db document field: _id
private String arangoId;
private String firstName;
private String lastName;
private String country;
public User() {
super();
}
public User(String id) {
this.id = id;
}
public User(String id, String country) {
this.id = id;
this.country = country;
}
// getter & setter
#Override
public String toString() {
return "User [id=" + id + ", name=" + firstName + ", surname=" + lastName + "]";
}
public String getId() {
return id;
}
}
here is the repository class but the method getListOfCountryAndNumUsers returns null even though i have inserted users with different countries into the database.
public interface UserRepository extends ArangoRepository<User, String> {
#Query("FOR u IN users COLLECT country = u.country WITH COUNT INTO length RETURN
{\"country\" : country, \"count\" : length }")
Iterable<CountryAndNumUsers> getListOfCountryAndNumUsers();
}
I think the problem could be with the the syntax of my query in the query annotation. I didnt see any direct example of using collect operation in the spring data arango db part of arangodb documentation here but I saw the collect operation in the section "high level operations" of arangoDb documentation here
Please Help. Thanks. !
So I discovered my error. It was in a class I didn't add in the question. That is the class for the return object of the method getListOfCountryAndNumUsers()
i.e class CountryAndNumUsers.
public class CountryAndNumUsers {
private String country;
private Integer numberOfUsers;
public CountryAndNumUsers(String country, Integer numberOfUsers) {
this.country = country;
this.numberOfUsers = numberOfUsers;
}
public String getCountry() {
return country;
}
public Integer getNumberOfUsers() {
return numberOfUsers;
}
}
so there was a mapping mismatch since the query returns an object with different field names. I changed the query to this below so that it matches
#Query("FOR u IN users COLLECT country = u.country WITH COUNT INTO length RETURN {\"country\" : country, \"numberOfUsers\" : length }")

Spring boot JPA - Custom Repository for multiple Entity

I've got some difficulties with my JPA Rest Project.
I have build my repositories for each of my entity (my tables in my database), and it works fine.
For example, a part of my entity "Personne" :
#Entity
public class Personne {
private Long id;
private String nom;
private String prenom;
private Date dateNaissance;
private String telDomicile;
private String telPortable;
private String telAutre;
private String telCommentaire;
private String fax;
private String mail;
private String commentaire;
private Timestamp dateSuppr;
private String sexe;
private Patient patientById;
private Adresse adresseByAdresseId;
#Id
#JsonProperty(value = "dataId")
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
}
And myRepository with a #Query :
#Transactional
#RepositoryRestResource(collectionResourceRel = "personne", path = "personne", excerptProjection = InlinePersonne.class)
public interface PersonneRepo extends JpaRepository<Personne, Long> {
#Query("from Personne p where p.nom = ?1 and p.prenom = ?2")
public Personne customRequest(String nom, String prenom);
}
My problem : the return result is always a type "Personne".
I would like to make a native request that sends me back an object, with customized properties.
Example of the wished return :
{object :
{name : String,
surname : String,
age : int },
adresse :{
city : String,
street : String
}
}
Is it possible to do that ?
I really need it because I have to make complex requests on many tables.
Thank you.
You could use interface-base projections:
First you create interfaces that reflect the fields you need:
interface PersonSummary {
String getName();
String getSurename();
int getAge();
AddressSummary getAddress();
interface AddressSummary {
String getCity();
String getStreet();
}
}
Then you indicate your custom query what interface it needs to extend and instantiate to populate the information:
public interface PersonneRepo extends JpaRepository<Personne, Long> {
// All your other abstract method
// Brand new query
#Query("Select p.name, p.surname, p.age, p.city, p.street from Personne p where p.nom = ?1 and p.prenom = ?2")
public PersonSummary customRequest(String nom, String prenom);
}
You would be receiving an object like this:
{
name : String,
surname : String,
age : int,
address :{
city : String,
street : String
}
}
You would need to test how flexible is this functionality in the terms of the composition complexity of the object you want to receive.

Exception when selecting specific columns using Hibernate and Spring Data JPA

I have a table that has a bytea column (named 'pdf') and I don't want to always select it, specially when I'm returning a list from the database, due to performance issues.
I use native queries with spring data inside the repository to solve these types of situations before (when I used eclipselink), but with Hibernate, if I don't write all the columns in the query, it throws an exception.
For test purposes, I'm trying to select only the id from the User and I still get the exception.
Example: "SELET user.id FROM user WHERE user.id = '1'"
It throws an exception saying that it did not find name in the ResultSet, if I put name in the SQL, it then says age was not found and so on, until I have to write all the columns in the SQL.
Thanks in advance for any help.
What I have tried already:
Updating/Downgrading Hibernate and Spring Data with no luck.
Creating a new entity with only the columns I need, works, but it's a messy solution for me.
Maybe the problem is the combination of the frameworks I use and the way I use them, if someone wants, I could try to upload my whole project structure.
My code:
Entity
#Entity
#Table(name = "user", schema = "portal")
public class User implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#Column(name = "id")
private Integer id;
#Column(name = "pdf")
private byte[] pdf;
#Column(name = "name")
private String name;
#Column(name = "age")
private Integer age;
public User() {
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public byte[] getPdf() {
return pdf;
}
public void setPdf(byte[] pdf) {
this.pdf = pdf;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
#Override
public int hashCode() {
int hash = 0;
hash += (id != null ? id.hashCode() : 0);
return hash;
}
#Override
public boolean equals(Object object) {
// TODO: Warning - this method won't work in the case the id fields are not set
if (!(object instanceof Anexo)) {
return false;
}
Anexo other = (Anexo) object;
if ((this.id == null && other.id != null) || (this.id != null && !this.id.equals(other.id))) {
return false;
}
return true;
}
#Override
public String toString() {
return "br.gov.to.secad.portal.domain.User[ id=" + id + " ]";
}
}
Service
#Service
#Transactional(readOnly = true)
public class UserService implements Serializable {
private static final long serialVersionUID = 1L;
#Autowired
private IUserRepository userRepository;
public UserService() {
}
public User findOne() {
return userRepository.findOneSQL();
}
}
Repository
public interface IUserRepository extends JpaRepository<User, Serializable>, JpaSpecificationExecutor {
#Query(value = "SELECT user.id FROM user WHERE user.id = '1'", nativeQuery = true)
public User findOneSQL();
}
The exception:
org.postgresql.util.PSQLException: The column name name was not found in this ResultSet.
Solution
The solution is using an array of Object when I want to select anything less than what I've mapped on my Entity class, thats the limitation of Hibernate that I now understand.
So basically, the method will return Object[] and then I can iterate each position and instantiate a new entity of User with these values.
Example:
#Query(value = "SELECT user.id FROM user WHERE user.id = '1'", nativeQuery = true)
public Object[] findOneSQL();
I have faced the same problem, I know it is late but well there is a solution that I found elegant.
By the Spring documentation you can declare an interface and from here take the fields you want, in my case it has been something similar to this.
The interface to minimize the fields:
public interface CountryMinify {
String getName();
String getNameTranslation();
}
And my JpaRepository
public interface PlanetRepository extends JpaRepository<Planet, Long> {
#Query(value = "select p.name_country as name, p.name_country_translation as nameTranslation from vm_planet p where gid = ?1", nativeQuery = true)
CountryMinify findByCode(String codeCountry);
}
Keep in mind that the columns should be called the same as gos getter. For example: column name_country -> AS name and the getter of the interface is getName()
Try this
#Query(value = "SELECT user.id FROM user WHERE user.id = '1'", nativeQuery = true)
Integer findOneSQL();
Call the method like so
Integer user = userRepository.findOneSQL();
Edit 1 :
Since you are using native query you wont be able to use Projections which is a great way of accessing only certain entity fields. There is a JIRA ticket which is still under investigation.
Solution
Return List from your repository like so
#Query(value = "SELECT user.id, user.name FROM user WHERE user.id = '1'", nativeQuery = true)
List<Object[]> findOneSQL();
Iterate over the list of Objects and get your specific columns.
List<Object[]> userNative = userRepository.findOneSQL();
for (Object[] obj : userNative) {
System.out.println("User id : " + obj[0]);
System.out.println("User Name : " + obj[1]);
}

fetch one to many side with jpql

so I have done two entities with one to many relationship,
I have one category whohas many visitors,
and this is my code:
this is the Category entity :
#Entity
public class Category implements Serializable {
private Integer id;
private String name;
private List<Visitor> visitors = new ArrayList<Visitor>();
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
#OneToMany(cascade=CascadeType.REMOVE, fetch = FetchType.LAZY, mappedBy = "category", orphanRemoval = true)
public List<Visitor> getVisitors() {
return visitors;
}
public void setVisitors(List<Visitor> visitors) {
this.visitors = visitors;
}
}
and here is the Visitor Entity :
#Entity
public class Visitor extends User {
private String passport;
private String citizenship;
private String gender;
private Company company;
private Category category;
public String getPassport() {
return passport;
}
public void setPassport(String passport) {
this.passport = passport;
}
public String getCitizenship() {
return citizenship;
}
public void setCitizenship(String citizenship) {
this.citizenship = citizenship;
}
#ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
public Category getCategory() {
return category;
}
public void setCategory(Category category) {
this.category = category;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
#ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
public Company getCompany() {
return company;
}
public void setCompany(Company company) {
this.company = company;
}
and here is the service method who list all the visitors and works fine :
public List<Visitor> findAllVisitors() {
return em.createQuery(
"SELECT v from Visitor v left join fetch v.category",
Visitor.class).getResultList();
}
with this method I can list all the visitors each with his category object associated,
now the problem is in the other side of the relationship ,
here is the method who list the categories each with their visitors list :
public List<Category> findAllCategories() {
return em.createQuery("select c from Category c",
Category.class).getResultList();
}
I want to get the list of all the categories but when I call this method in a REST call , I get this result :
I want just to get a simple list of categories (id and name).
what is wrong in my code please help me i am confused.
UPDATE:
this is how I get JSON from persistence context with RESTful method :
#Inject
private CategoryServiceLocal categoryServiceLocal;
#GET
#Produces(MediaType.APPLICATION_JSON)
public List<Category> dofindAllCategories() {
return categoryServiceLocal.findAllCategories();
}
You have a lazy association from Category to visitors. To load all visitors you need to use left join fetch too.
select c from Category c left join fetch c.visitors
Please, use additional annotations to control how to JSON generated
Infinite Recursion with Jackson JSON and Hibernate JPA issue

Using Pageable to query a collection

I have two entities. A NewsCategory and a NewsItem which have a one-to-many relationship.
NewsCategory
#Entity
public class NewsCategory extends AbstractEntity<Long> {
private String name;
#OneToMany(cascade = CascadeType.ALL)
private List<NewsItem> items = new ArrayList<>();
public String getName() {
return name;
}
public void setName(final String name) {
this.name = name;
}
public List<NewsItem> getItems() {
return items;
}
}
NewsItem
#Entity
public class NewsItem extends AbstractEntity<Long> {
private String title;
private LocalDate startDate;
private LocalDate endDate;
private String resource;
#Column(columnDefinition = "text")
private String content;
// getters and setters...
}
Repository interface
I would like to have the items collection to be pageable but I'm having some difficulties with defining the repository interface for it.
This interface does not work like expected.
public interface NewsCategoryRepository extends JpaRepository<NewsCategory, Long> {
#Query("SELECT e.items FROM #{#entityName} e WHERE e = ?1")
public List<NewsItem> findItems(NewsCategory category, Pageable pageable);
}
When executing findItems() the following exception is thrown.
Caused by: org.hibernate.QueryException: illegal attempt to dereference collection [newscatego0_.id.items] with element property reference [startDate] [SELECT e.items FROM NewsCategory e WHERE e = ?1 order by e.items.startDate asc]
How can I modify the above interface so it will return a portion of the items property using Spring Data and Pageable?