Spring Data Rest: JOIN search by parent column is throwing error (o.s.d.r.w.RepositoryRestExceptionHandler : PersistentEntity must not be null!) - spring-data-rest

I am using Spring Data Rest for a Department-Employee relationship as shown below, and have a RepositoryRestResource defined on Employee.
class Department {
#Id
UUID Id;
String name;
#OneToMany(mappedBy = "employee", targetEntity = Employee.class, cascade = {CascadeType.ALL}, fetch = FetchType.LAZY)
#RestResource(exported = false)
private Set<Employee> employees;
}
class Employee {
#Id
UUID Id;
String firstName;
String lastName;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "dept_ref")
#RestResource(exported = false)
private Department department;
}
#Projection(name = "employeeProjection", types = {Employee.class})
interface EmployeeProjection {
String getFirstName();
String getLastName();
}
#RepositoryRestResource(excerptProjection = EmployeeProjection.class)
public interface EmployeeRepository extends CrudRepository<Employee, UUID> {
//this doesn't work
#Query(value = "SELECT emp.firstName as firstName , emp.lastName as lastName FROM Employee emp JOIN emp.department "
+ "WHERE emp.department.name = :departmentName ")
List<EmployeeProjection> findByDepartmentName(#Param("departmentName") String departmentName);
//this works
List<EmployeeProjection> findByFirstName(#Param("firstName") String firstName);
}
Issue is when I perform a join search as findByDepartmentName, I am getting below error.
2019-07-01 00:26:33.300 ERROR 7396 --- [nio-8443-exec-5] o.s.d.r.w.RepositoryRestExceptionHandler : PersistentEntity must not be null!
java.lang.IllegalArgumentException: PersistentEntity must not be null!
at org.springframework.util.Assert.notNull(Assert.java:134)
at org.springframework.data.rest.webmvc.PersistentEntityResource$Builder.<init>(PersistentEntityResource.java:140)

the issue got resolved by changing the return type to List<Employee> from List<EmployeeProjection>, also it still returns me the projection as its an excerptProjection. not sure why the Spring Data Rest expects this but it works.
List<Employee> findByDepartmentName(#Param("departmentName") String departmentName);

Related

Query Many-to-many jpa

I need to implement SELECT with a many-to-many relationship in #QUERY. Perhaps I am misinterpreting the information written in the documentation.
My query looks like this:
#Query("select massages.id from massages join string_massage on massages.id = string_massage.massage_id where string_massage.string_id = ?1")
List<MasageEntity> findMassagesIdByStringId(#Param("strings_id") long strings_id);
In my example, I use table names. The names are underlined as an error (without compilation). Maybe I should use Entities. Then how do I do it with many-to-many relationship?
I will show a part of my Entities.
I have two Entities. MasageEntity and RstringEntity.
//MasageEntity
#Entity
#Table(name = "massages")
public class MasageEntity {
#Id
#GeneratedValue (strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "string_text")
private String string_text;
#Column(name = "string_speed")
private Long string_speed;
#Column(name = "string_color_type")
private Long string_color_type;
#Column(name = "string_color")
private String string_color;
#Column(name = "string_timing_type")
private String string_timing_type;
#Column(name = "string_timing")
private String string_timing;
#Column(name = "showed")
private Long showed;
#ManyToMany(fetch = FetchType.LAZY,
cascade = {
CascadeType.PERSIST,
CascadeType.MERGE
})
#JoinTable(name = "string_massage",
joinColumns = { #JoinColumn(name = "massage_id") },
inverseJoinColumns = { #JoinColumn(name = "string_id") })
//RstringEntity
#Entity
#Table(name = "string")
public class RstringsEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "name")
private String name;
#Column(name="code")
private String code;
#ManyToMany(fetch = FetchType.LAZY,
cascade = {
CascadeType.PERSIST,
CascadeType.MERGE
}, mappedBy = "strings")
#JsonIgnore
private Set<MasageEntity> masagess = new HashSet<>();
public RstringsEntity() {}
There are multiple ways how to perform queries in Spring Boot: native SQL and JPQL.
In case of native queries we are using pure SQL language, defining query on DB level.
In case of Java Persistence Query Language (JPQL) we define query via entity objects.
Solution 1, native queries
You created the native query in repository, but to use it we need mark it like SQL nativeQuery = true. It required for framework to understang what query laguage do you use. #Query annotation use JPQL by defult, so that's the reason of your errors.
#Repository
public interface MessageRepository extends JpaRepository<MassageEntity, Long> {
//find MessageEntities by String ID via native query
#Query(value = "select massages.* from massages join string_massage on massages.id = string_massage.massage_id where string_massage.string_id = ?1", nativeQuery = true)
List<MassageEntity> findMassagesByStringIdNativeSQL(#Param("strings_id") long strings_id);
//find Message IDs by String ID via native query
#Query(value = "select massages.id from massages join string_massage on massages.id = string_massage.massage_id where string_massage.string_id = ?1", nativeQuery = true)
List<Long> findMassagesIdByStringIdNativeSQL(#Param("strings_id") long strings_id);
}
Solution 2, JPQL queries
Example how to define JPQL queries for your case. JPQL will be translated to SQL during execution.
#Repository
public interface MessageRepository extends JpaRepository<MassageEntity, Long> {
//find MessageEntities by String ID via JPQL
#Query("select message from MassageEntity message join message.strings string where string.id = :strings_id")
List<MassageEntity> findMassagesByStringIdJPQL(#Param("strings_id") long strings_id);
//find Message IDs by String ID via JPQL
#Query("select message.id from MassageEntity message join message.strings string where string.id = :strings_id")
List<Long> findMassagesIDByStringIdJPQL(#Param("strings_id") long strings_id);
}
Native query generated by Hibernate:
select
massageent0_.id as id1_3_,
massageent0_.string_text as string_t2_3_
from
massages massageent0_
inner join
string_massage strings1_
on massageent0_.id=strings1_.massage_id
inner join
string rstringsen2_
on strings1_.string_id=rstringsen2_.id
where
rstringsen2_.id=?
Solution 3, Spring auto-generated queries
Spring can auto-generate queries by repository method definition.
Example for your case:
#Repository
public interface MessageRepository extends JpaRepository<MassageEntity, Long> {
//find MessageEntities by String ID
List<MassageEntity> findByStrings_Id(#Param("id") long strings_id);
}
Entries which I used fro sulutions:
#Entity
#Table(name = "massages")
public class MassageEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "string_text")
private String string_text;
#ManyToMany(fetch = FetchType.LAZY,
cascade = {
CascadeType.PERSIST,
CascadeType.MERGE
})
#JoinTable(name = "string_massage",
joinColumns = { #JoinColumn(name = "massage_id") },
inverseJoinColumns = { #JoinColumn(name = "string_id") })
private Set<RstringsEntity> strings = new HashSet<>();
}
#Entity
#Table(name = "string")
public class RstringsEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "name")
private String name;
#Column(name = "code")
private String code;
#ManyToMany(fetch = FetchType.LAZY,
cascade = {
CascadeType.PERSIST,
CascadeType.MERGE
}, mappedBy = "strings")
#JsonIgnore
private Set<MassageEntity> massages = new HashSet<>();
}

Android room compiler error when trying to return a list of own objects in the DAO: incompatible types: <null> cannot be converted to int

I am using room for database operations. I have a class TableQuestion which holds a string and a id and I have a class TableAnswer which holds a string, an id plus the id of the question where it is refering to. QuizTask brings together the Question with all its answers. The query getQuestionsWithAnswer should return a QuizTask which wraps the question with all its answers. The error metioned in the title happens in auto-generated code of room.
The relevant part of the interface:
#android.arch.persistence.room.Dao
public interface dbDao {
#Transaction
#Query("SELECT table_question.question, table_answer.answer FROM table_question, table_answer WHERE table_question.id = table_answer.id_question")
LiveData<List<QuizTask>> getQuestionsWithAnswers();
}
Class TableQuestion:
#Entity(foreignKeys = #ForeignKey(entity = TableQuestionnaire.class,
parentColumns = "id",
childColumns = "id_questionnaire",
onDelete = CASCADE),
tableName = "table_question")
public class TableQuestion {
#PrimaryKey
public final int id;
#NonNull
public String question;
#NonNull
public int id_questionnaire;
public String subject;
public String category;
public String sub_category;
#Ignore
public String questionnaire;
public TableQuestion(int id, #NonNull String question, int id_questionnaire, String subject, String category, String sub_category) {
this.id = id;
this.question = question;
this.id_questionnaire = id_questionnaire;
this.questionnaire = null;
this.subject = subject;
this.category = category;
this.sub_category = sub_category;
}
public void setQuestionnaire(String questionnaire){
this.questionnaire = questionnaire;
}
}
Class TableAnswer:
#Entity(foreignKeys = #ForeignKey(entity = TableQuestion.class,
parentColumns = "id",
childColumns = "id_question",
onDelete = CASCADE),
tableName = "table_answer")
public class TableAnswer {
#PrimaryKey
public final int id;
#NonNull
public String answer;
#NonNull
public final int id_question;
public boolean rightAnswer;
public TableAnswer(int id, String answer, int id_question, boolean rightAnswer) {
this.id = id;
this.answer = answer;
this.id_question = id_question;
this.rightAnswer = rightAnswer;
}
}
Class QuizTask:
public class QuizTask {
#Embedded
private TableQuestion question;
#Relation(parentColumn = "id", entityColumn = "id_question")
private List<TableAnswer> answers;
public void setQuestion(TableQuestion question){ this.question = question; }
public TableQuestion getQuestion(){
return question;
}
public void setAnswers(List<TableAnswer> answers) { this.answers = answers; }
public List<TableAnswer> getAnswers() {
return answers;
}
}
AndroidStudio doesn't show any error upon compilation. When room auto-generates the code for getQuestionWithAnswers it shows a compiler error "incompatible types: cannot be converted to int". In the auto-generated dbDao_Impl.java is a row where a TableQuestion object is tried to create but with null for the id parameter. That's where the error occurs. What do I have to change?
I found the issue:
#Query("SELECT table_question.question, table_answer.answer FROM table_question, table_answer WHERE table_question.id = table_answer.id_question")
There is no id selected but used later on. That's why room tries to create objects with Id = null which is not possible. So simply change to:
#Query("SELECT * FROM table_question, table_answer WHERE table_question.id = table_answer.id_question")

JPA query to retrieve data using list of matching tuples as arguments [duplicate]

I have an Entity Class like this:
#Entity
#Table(name = "CUSTOMER")
class Customer{
#Id
#Column(name = "Id")
Long id;
#Column(name = "EMAIL_ID")
String emailId;
#Column(name = "MOBILE")
String mobile;
}
How to write findBy method for the below query using crudrepository spring data jpa?
select * from customer where (email, mobile) IN (("a#b.c","8971"), ("e#f.g", "8888"))
I'm expecting something like
List<Customer> findByEmailMobileIn(List<Tuple> tuples);
I want to get the list of customers from given pairs
I think this can be done with org.springframework.data.jpa.domain.Specification. You can pass a list of your tuples and proceed them this way (don't care that Tuple is not an entity, but you need to define this class):
public class CustomerSpecification implements Specification<Customer> {
// names of the fields in your Customer entity
private static final String CONST_EMAIL_ID = "emailId";
private static final String CONST_MOBILE = "mobile";
private List<MyTuple> tuples;
public ClaimSpecification(List<MyTuple> tuples) {
this.tuples = tuples;
}
#Override
public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
// will be connected with logical OR
List<Predicate> predicates = new ArrayList<>();
tuples.forEach(tuple -> {
List<Predicate> innerPredicates = new ArrayList<>();
if (tuple.getEmail() != null) {
innerPredicates.add(cb.equal(root
.<String>get(CONST_EMAIL_ID), tuple.getEmail()));
}
if (tuple.getMobile() != null) {
innerPredicates.add(cb.equal(root
.<String>get(CONST_MOBILE), tuple.getMobile()));
}
// these predicates match a tuple, hence joined with AND
predicates.add(andTogether(innerPredicates, cb));
});
return orTogether(predicates, cb);
}
private Predicate orTogether(List<Predicate> predicates, CriteriaBuilder cb) {
return cb.or(predicates.toArray(new Predicate[0]));
}
private Predicate andTogether(List<Predicate> predicates, CriteriaBuilder cb) {
return cb.and(predicates.toArray(new Predicate[0]));
}
}
Your repo is supposed to extend interface JpaSpecificationExecutor<Customer>.
Then construct a specification with a list of tuples and pass it to the method customerRepo.findAll(Specification<Customer>) - it returns a list of customers.
It is maybe cleaner using a projection :
#Entity
#Table(name = "CUSTOMER")
class CustomerQueryData {
#Id
#Column(name = "Id")
Long id;
#OneToOne
#JoinColumns(#JoinColumn(name = "emailId"), #JoinColumn(name = "mobile"))
Contact contact;
}
The Contact Entity :
#Entity
#Table(name = "CUSTOMER")
class Contact{
#Column(name = "EMAIL_ID")
String emailId;
#Column(name = "MOBILE")
String mobile;
}
After specifying the entities, the repo :
CustomerJpaProjection extends Repository<CustomerQueryData, Long>, QueryDslPredicateExecutor<CustomerQueryData> {
#Override
List<CustomerQueryData> findAll(Predicate predicate);
}
And the repo call :
ArrayList<Contact> contacts = new ArrayList<>();
contacts.add(new Contact("a#b.c","8971"));
contacts.add(new Contact("e#f.g", "8888"));
customerJpaProjection.findAll(QCustomerQueryData.customerQueryData.contact.in(contacts));
Not tested code.

Spring Data Rest Left Outer Join non-Entity POJO Null Entity Error

How do i accomplish getting the this interface method to work? i am using a MySQL DB if that matters...
public interface PersonRoleRepository extends CrudRepository<PersonRole,Long>{
//This causes null entity error from hibernate even though the SQL works outside hibernate
#Query(value="select * from Role r left outer join Person_Role pr on r.id = pr.role_id and pr.person_id = ? order by pr.expires_date desc", nativeQuery = true)
List<PersonRoleDto> getAllRolesAndPersonsStatusWithEachRole(int personId);
}
Here is the SQL query that returns what i want in SQL Workbench...
Select r.*, pr.*
from
Role r
left outer join person_role pr on r.id = pr.role_id and pr.person_id = ?
order by pr.expires_date desc;
Important MySQL database structure...
Table: Role
role_id bigint
name varchar
description varchar
...
Table: Person
person_id bigint
first_name varchar
last_name varchar
...
Table Person_Role_Link
person_role_id bigint
role_id bigint
person_id bigint
...
alter table person_Role_Link add constraint l1 foreign key (person_id) references Person(person_id)
alter table person_Role_Link add constraint l2 foreign key (role_id) references Role(role_id)
Here is the entity info...
#Entity
#Table(name="Role")
#EntityListeners(AuditEntityListener.class)
public class Role extends AbstractAuditEntity {
private static final long serialVersionUID= 44543543543543454543543L;
#id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name="role_id")
private long id;
#NotNull
private String fullName
...
#OneToMany(mappedBy="role",cascade=CascadeType.ALL)
private Set<PersonRole> personRoles = new HashSet<>();
...
}
#Entity
#Table(name="Person_Role_Link")
#EntityListeners(AuditEntityListener.class)
class PersonRole extends AbstractAuditEntry{
private static final long serialVersion = 54324243242432423L;
#id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name="person_role_id")
private long id;
#ManyToOne
#JoinColumn(name="person_id")
private Person person;
#ManyToOne
#JoinColumn(name="role_id")
private Role role;
...
}
#Entity
#Table(name="Person")
#EntityListeners(AuditEntityListener.class)
public class Person extends AbstractAuditEntity{
private ... serialVersionUID...;
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name="person_id")
private Long id;
...
#OneToMany(mappedBy="person", cascade=CascadeType.ALL)
private Set<PersonRole> personRoles = new HashSet<>();
...
}
Just for now i made a simple interface...
public interface PersonRoleDto {
String getFullName();
String getDescription();
//i want more attributes but for now i will just see if it works with the basics
}
Here is the latest HQL i tried...
public interface PersonRoleRepository extends CrudRepository<PersonRole,Long>{
//PersistentEntity must not be null
#Query("select r.fullName as fullName, r.description as description from Role r left join r.personRoles pr where pr.person = :person")
List<PersonRoleDto> findAllRolesWithPersonsStatus(#Param("person") Person person);
...
}
Whether I use HQL or native SQL i get a null entity error from hibernate. Also, the SQL generated from the HQL works without error but i still get a null entity error in code and, second, the SQL generated from HQL is slightly off which makes the results off. That's why i was trying so hard to get the native SQL to work.
The relationship is used to figure out how many people are in a role and at other times what roles a person has. This is a circular relationship, i'd say. I work on an Intranet so i had to hand type everything. If there are any problems seen in my code other than with the stated native query as stated then it is most likely because i had to hand type everything and not because the code is buggy. Everything else works so far but this one thing.
All help is appreciated.
UPDATE!!!!
I think this is the answer to my problem but when i try it i still get the error: PersistentEntity must not be null!
Here is how i tried to set it up...
//Added this to the top of PersonRole entity
#SqlResultSetMapping(
name="allRolesAndPersonsStatusWithEachRole"
classes={
#ConstructorResult(
targetClass=PersonRoleStatus.class,
columns={
#ColumnResult(name="full_name"),
#ColumnResult(name="description"),
...
}
)
}
)
#NamedNativeQuery(name="PersonRole.getAllRolesAndPersonsStatusWithEachRole",
query="Select r.*, pr.* from Role r Left Join person_role_link on r.role_id = pr.role_id and pr.person_id = :id", resultSetMapping="allRolesAndPersonsStatusWithEachRole")
Created my DTO like this...
public class RolePersonStatus {
private String fullName;
private String description;
private String ...
public RolePersonStatus(String fullName, String description, ...){
this.fullName = fullName;
this.description = description;
...
}
}
In my repository i just have:
//No annotation because it stated that the name of the method just needed to match the native query name?!?!?
List<RolePersonStatus> findAllRolesWithPersonStatus(#Param("id" Long id);
What am i missing???????
Try this way:
Entities
#Entity
#Table(name = "parents")
public class Parent {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
#OneToMany
private List<Child> children = new ArrayList<>();
//...
}
#Entity
#Table(name = "children")
public class Child {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
#ManyToOne(optional = false)
private Reference reference;
#OneToMany
private final List<Toy> toys = new ArrayList<>();
//...
}
#Entity
#Table(name = "references")
public class Reference {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String description;
//...
}
#Entity
#Table(name = "toys")
public class Toy {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
//...
}
DTO
public interface DemoDto {
String getParentName();
String getChildName();
String getToyName();
String getDescription();
}
Repository
public interface ParentRepo extends JpaRepository<Parent, Long> {
#Query("select " +
"p.name as parentName, " +
"c.name as childName, " +
"t.name as toyName, " +
"r.description as description " +
"from " +
"Parent p " +
"join p.children c " +
"join c.reference r " +
"join c.toys t " +
"where c.id = ?1 " +
"order by r.description desc")
List<DemoDto> getDto(Long childId);
}
Usage
#RunWith(SpringRunner.class)
#SpringBootTest
public class ParentRepoTest {
#Autowired
private ParentRepo parentRepo;
#Test
public void getDto() throws Exception {
List<DemoDto> dtos = parentRepo.getDto(3L);
dtos.forEach(System.out::println);
}
}
Result
{parentName=parent2, toyName=Toy7, childName=child3, description=Description1}
{parentName=parent2, toyName=Toy8, childName=child3, description=Description1}
{parentName=parent2, toyName=Toy9, childName=child3, description=Description1}
More info is here.
Working example.

JPQL query for left outer join over parent vs inherited object

Entities/Model:
#Inheritance(strategy=InheritanceType.JOINED)
public class UserAccount implements CommonUserAccount {
#Id
private Long id;
private String email;
#Embedded
private PersonalInfo personalInfo = new PersonalInfo(); // name/surname - regular stuff
#ElementCollection
#CollectionTable(name = "UserAccountTags", joinColumns = #JoinColumn(name = "accountId", nullable = false))
#Column(name = "tag")
//#Transient
private Set<String> tags = new HashSet<String>();
#ElementCollection
#CollectionTable(name = "UserAccountRoles", joinColumns = #JoinColumn(name = "accountId", nullable = false))
#Enumerated(EnumType.STRING)
#Column(name = "userRole")
private Set<UserAccountRole> userRoles = new HashSet<UserAccountRole>();
// regular getters/setters
}
#Entity
#Table
#PrimaryKeyJoinColumn(name = "useraccountid")
public class DemoUserAccount extends UserAccount implements CommonUserAccount {
#Column
private String passwordHash;
#Column
private Long failedLogins;
#Column
#Temporal(TemporalType.TIMESTAMP)
Date lockedAt;
// regular getters/setters
}
Question:
Is it possible to build query using JPQL (for JPA2.0) that would return DemoUserAccounts joined on parent table - UserAccounts? Doing this would assume I can filter on tags/user_roles as well. In general some records will not have DemoUserAccount specific fields filled in.
When you do a SELECT from DemoUserAccount, you already have the UserAccount fields available to do a query using them.
So, if you want to filter by email and failedLogins:
SELECT d FROM DemoUserAccount d WHERE d.email = 'you#you.com' AND d.failedLogins > 3