JPA Criteria: CriteriaBuilder over NestedObject field - jpa

Here my entity:
public class QdCF implements Diffable<QdCF> {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "QDCF_ID")
private Integer id;
// #Column(name = "QDCF_AMBIT")
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "QDCF_AMBIT")
private String ambit;
}
Now, I've changed ambit field to ManyToOne:
public class QdCF implements Diffable<QdCF> {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "QDCF_ID")
private Integer id;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "QDCF_AMBIT")
private Ambit ambit;
}
Above code works fine.
However, I've a custom JPA criteria like this:
public static Specification<QdCF> likeCodi(String codi) {
return (root, criteriaQuery, criteriaBuilder) -> {
return criteriaBuilder.like(root.get(QdCF_.ambit).as(String.class), "%" + codi + "%");
};
}
Above code doesn't works now since:
Caused by: org.hibernate.QueryException: Expression to CAST cannot be an entity : qdcf0_.QDCF_AMBIT
Any ideas about how to troubleshot it?

Related

JPA Repository Query on additional table #ManytoMany

I want to do select like this in my jpa spring repository
SELECT sicknes_id, count(symptomp_id) as ilosc FROM symptomp_sicknes where symptomp_id IN (1,2) group by sicknes_id Order by ilosc DESC;
My enitity
#Entity
#Table(name = "symptomp")
public class Symptomp {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "symptomp_id")
private Long symptomp_id;
#Column(name = "name")
private String name;
#Column(name = "description")
private String description;
#ManyToMany(cascade = {CascadeType.DETACH,CascadeType.MERGE,CascadeType.PERSIST,CascadeType.REFRESH}, fetch = FetchType.LAZY)
#JoinTable(name = "symptomp_sicknes",joinColumns = #JoinColumn(name = "symptomp_id"),inverseJoinColumns = #JoinColumn(name = "sicknes_id"))
private Set<Sicknes> sicknes = new HashSet<>();
#Entity
#Table(name = "sicknes")
public class Sicknes {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "sicknes_id")
private Long sicknes_id;
#Column(name = "name")
private String name;
#Column(name = "description")
private String description;
#ManyToOne(cascade = {CascadeType.DETACH,CascadeType.MERGE,CascadeType.PERSIST,CascadeType.REFRESH}, fetch = FetchType.LAZY)
#JoinColumn(name = "speciesId")
private Species species;
My Symptomp repository:
public interface SymptompRepository extends JpaRepository<Symptomp, Long> {
#Query("select p from Symptomp p where name like ?1%")
public List<Symptomp> findAllBySymptompName(String symptomp);
public Symptomp findByName(String symptomp);
public List<Symptomp> findByNameIn(List<String> list);
Integer countDistinctSymptompByName(String id);
}
How I can create this select in my JPA repository?
I try get value like in select but i got error mapping bean.
You can get query result as List<Object[]> using nativeQuery=true parameter
#Query("SELECT sicknes_id, count(symptomp_id) as ilosc FROM symptomp_sicknes where symptomp_id IN (1,2) group by sicknes_id Order by ilosc DESC", nativeQuery=true)
List<Object[]> getQueryResult();
Other option is to create dto class with appropriate constructor
public class QueryResultDto {
Long sicknesId;
Long count;
public QueryResultDto(Long sicknesId, Long count) {
this.sicknesId = sicknesId;
this.count = count;
}
}
Then using JPQL
#Query("select new yourproject.dtopath.QueryResultDto(...")
List<QueryResultDto> getQueryResult(#Param("symptompIds") List<Long> symptompIds);
If you want to avoid a native Query the best way is to create an Entity for that JoinTable. Then you can query it easily. Additional benefit if this is that if in future a requirement will pop up that you have to store additional attributes in that relation you will have the Entity already there to do that easily.

Project data from different tables to a model

I defined my model classes like below.
#Entity
#Table(name = "my_employee")
public class Employee {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
private String name;
#OneToMany(cascade = CascadeType.ALL)
#JoinTable(name = "emp_address_mapping", joinColumns = #JoinColumn(name = "emp_id"), inverseJoinColumns = #JoinColumn(name = "address_id"))
private List<Address> addresses = new ArrayList<Address>();
.......
.......
}
#Entity
#Table(name = "my_address")
public class Address {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
private String country;
.....
.....
}
public class EmployeeDetails {
private int empId;
private String name;
private String country;
......
......
}
How can I write a query using #Query annotation to populate all the EmployeeDetails.
public interface EmployeeRepository extends CrudRepository<Employee, Integer> {
#Query("SELECT new com.sample.app.model.EmployeeDetails......")
List<EmployeeDetails> getEmployeeDetails();
}
Create the constructor in EmployeeDetails
public EmployeeDetails(int id,String name,String country){
this.id=id;
this.name=name;
this.country=country;
}
Try this query
To get all employee details:
SELECT new com.sample.app.model.EmployeeDetails(e.id,e.name,a.country) from Employee e,Address a

JPA Entity mapping for join columns

I have three tables EmployeeDepartment, EmployeeGroup and EmpplyeeDetails Table. EmployeeDepartment table has primary key departmentId and a column groupId, EmployeeGroupTable has primary key groupid which should be generated from databse sequence GroupIdGenerator.
EmployeeDetails have two primary keys as groupid and employeeid. Groupid should be same as of the previous table
These values in all table should insert in one transaction.
Can you help me with correct JAP Entity mapping?
I already tried with different combination of Generated value and Sequence generator but not able to save the data into table.
#Entity
#Table(name="EMPLOYEE_DEPARTMENT")
public class EmployeeDepartment {
#Column(name = "DEPARTMENT_ID")
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator =
"departmentid-gen")
#Id
#NotNull
#SequenceGenerator(name = "departmentid-gen", sequenceName =
"DEAPARTMENT_ID_GENERATOR" )
private long departmentId;
#OneToOne(mappedBy = "employeeGroup")
private EmployeeGroup employeeGroup;
}
#Coulmn(name="GROUP_ID")
private long groupId;
#Entity
#Table(name="EMPLOYEE_GROUP")
public class EmployeeGroup {
#Column(name = "GROUP_ID")
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator =
"groupid-gen")
#Id
#NotNull
#SequenceGenerator(name = "groupid-gen", sequenceName =
"GROUIP_ID_GENERATOR" )
private long groupId;
#OneToMany(mappedBy = "employeeDetail")
private List<EmployeeDetail> employeeDetails;
#OneToOne
#JoinColumn(name = "DEPARTMENT_ID", insertable=false ,
updatable=false)
private EmployeeDepartment employeeDepatment;
}
#Entity
#Table(name = "EMPLOYEE_DETAIL")
#IdClass(EmployeeID.class)
public class EmployeeDetail {
#ManyToOne
#JoinColumn(name = "GROUP_ID", insertable=false , updatable=false)
private EmployeeGroup employeeGroup;
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator =
"groupid-gen")
#SequenceGenerator(name = "groupid-gen", sequenceName =
"GROUIP_ID_GENERATOR" )
#Column(name = "GROUP_ID")
#Nonnull
private Long groupId;
#Id
#Nonnull
#Column(name = "EMPLOYEE_ID")
private Long employeeId;
}
public class EmployeeId{
private Long groupId;
private Long employeeId;
public EmployeeId(final Long groupId, final Long employeeId) {
this.groupId = groupId;
this.employeeId = employeeId;
}
public EmployeeId() {
}
}
Expected result in these 3 tables should have proper values like.
Table EmployeeDepartment
DepartmentID GroupId
1 1
Table EmployeeGroup
GroupID
1
Table EmployeeDetail
GroupId EmployeeId
1 1
1 2
1 3
Actual results are below
Table EmployeeDepartment
DepartmentID GroupId
1 0
Table EmployeeGroup
GroupID
1
Table EmployeeDetail
GroupId EmployeeId
2 1
3 2
4 3
The important annotation is #MapsId("groupId")
Your mapping should be something like this:
#Entity
#Table(name="EMPLOYEE_DEPARTMENT")
public class EmployeeDepartment {
#Column(name = "DEPARTMENT_ID")
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator =
"departmentid-gen")
#Id
#NotNull
#SequenceGenerator(name = "departmentid-gen", sequenceName =
"DEAPARTMENT_ID_GENERATOR" )
private long departmentId;
#OneToOne(mappedBy = "employeeGroup")
private EmployeeGroup employeeGroup;
#Column(name="GROUP_ID")
private long groupId;
#Entity
#Table(name="EMPLOYEE_GROUP")
public class EmployeeGroup {
#Column(name = "GROUP_ID")
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator =
"groupid-gen")
#Id
#NotNull
#SequenceGenerator(name = "groupid-gen", sequenceName =
"GROUIP_ID_GENERATOR" )
private long groupId;
#OneToMany(mappedBy = "employeeDetail")
private List<EmployeeDetail> employeeDetails;
#OneToOne
#JoinColumn(name = "DEPARTMENT_ID", insertable=false, updatable=false)
private EmployeeDepartment employeeDepatment;
#Entity
#Table(name = "EMPLOYEE_DETAIL")
#IdClass(EmployeeID.class)
public class EmployeeDetail {
#MapsId("groupId")
#ManyToOne
#JoinColumn(name = "GROUP_ID", insertable=false , updatable=false)
private EmployeeGroup employeeGroup;
#Id
#Nonnull
#Column(name = "EMPLOYEE_ID")
private Long employeeId;
#Entity
#Table(name="EMPLOYEE_DEPARTMENT")
public class EmployeeDepartment {
#Column(name = "DEPARTMENT_ID")
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator =
"departmentid-gen")
#Id
#NotNull
#SequenceGenerator(name = "departmentid-gen", sequenceName =
"DEAPARTMENT_ID_GENERATOR" )
private Long departmentId;
#OneToOne(mappedBy = "employeeGroup")
private EmployeeGroup employeeGroup;
}
#Column(name="GROUP_ID")
private EmployeeGroup group;
#Entity
#Table(name="EMPLOYEE_GROUP")
public class EmployeeGroup {
#Column(name = "GROUP_ID")
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator =
"groupid-gen")
#Id
#NotNull
#SequenceGenerator(name = "groupid-gen", sequenceName =
"GROUIP_ID_GENERATOR" )
private Long groupId;
#OneToMany(mappedBy = "employeeDetail")
private List<EmployeeDetail> employeeDetails;
#OneToOne
#JoinColumn(name = "DEPARTMENT_ID", insertable=false ,
updatable=false)
private EmployeeDepartment employeeDepatment;
}
#Entity
#Table(name = "EMPLOYEE_DETAIL")
#IdClass(EmployeeID.class)
public class EmployeeDetail {
#Id
#ManyToOne
#JoinColumn(name = "GROUP_ID", insertable=false , updatable=false)
private EmployeeGroup employeeGroup;
#Id
#Nonnull
#Column(name = "EMPLOYEE_ID")
private Long employeeId;
You also need to change the EmployeeId accordingly:
public class EmployeeId{
private EmployeeGroup employeeGroup;
private Long employeeId;
public EmployeeId(final EmployeeGroup employeeGroup, final Long employeeId) {
this.employeeGroup= employeeGroup;
this.employeeId = employeeId;
}
However I haven't use composite keys this way before. If it doesn't work, then change EmployeeId to an embeddedId:
#Embeddable
public class EmployeeId implements Serializable{
#ManyToOne(fetch = FetchType.LAZY, optional = false)
private EmployeeGroup employeeGroup;
#Nonnull
#Column(name = "EMPLOYEE_ID")
private Long employeeId;
public EmployeeId(final EmployeeGroup employeeGroup, final Long employeeId) {
this.employeeGroup= employeeGroup;
this.employeeId = employeeId;
}
#Entity
#Table(name = "EMPLOYEE_DETAIL")
public class EmployeeDetail {
#EmbeddedId
private EmployeeId id;
#ManyToOne
#JoinColumn(name = "GROUP_ID", insertable=false , updatable=false)
private EmployeeGroup employeeGroup;
}
If it still doesn't work, then please attach the code snippet where you create the entities.

Reuse a composite key for a child + a new field

I use spring boot, with jpa (hibernate) and postgresql
I use composite key.
#Entity
#IdClass(SamplingsPK.class)
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
public class Samplings {
#Id
#GeneratedValue
private Integer id;
#Id
private int year;
#OneToMany(mappedBy = "sampling", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Samples> samples = new ArrayList<>();
...
}
public class SamplingsPK implements Serializable {
private Integer id;
private int year;
public SamplingsPK(Integer id, int year) {
this.id = id;
this.year=year;
}
private SamplingsPK(){
}
#PrePersist
public void prePersist() {
year = LocalDate.now().getYear();
}
}
#Entity
public class Samples {
#Id
#SequenceGenerator(name = "samples_id_seq", sequenceName = "samples_id_seq", allocationSize = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "samples_id_seq")
private Integer id;
private String sampleLetter;
#ManyToOne
#JoinColumns({
#JoinColumn(name = "sampling_id", referencedColumnName = "id"),
#JoinColumn(name = "sampling_year", referencedColumnName = "year")
})
private Samplings sampling;
}
That work fine
Instead of having an sequence in samples, I would like to have a composite key... SamplingsPK + sampleLetter.
Is it possible to do it, how to save a sample?
This is a "derived identity", so Samples could be mapped with an #IdClass like this:
#Entity
#IdClass(SamplesPK.class)
public class Samples {
#Id
#ManyToOne
#JoinColumns({
#JoinColumn(name = "sampling_id", referencedColumnName = "id"),
#JoinColumn(name = "sampling_year", referencedColumnName = "year")
})
private Samplings sampling;
#Id
private String sampleLetter;
}
public class SamplesPK {
SamplingsPK sampling; // matches name of attribute and type of Samplings PK
String sampleLetter; // matches name and type of attribute
}
Derived identities are discussed (with examples) in the JPA 2.2 spec in section 2.4.1.

Returning complex objects from Spring Data #Query

I'm writing a Questionnaire application (Java, Spring Boot, SQL) and I have a working query for returning the count of each answer in the database for specified questionnaire:
#Query(value = "SELECT new org.project.domain.AnswerCount(a.value, count(a)) FROM "
+ "Answer a WHERE a.questionnaire = :questionnaire GROUP BY a.value")
List<AnswerCount> findAnswerCountByQuestionnair(#Param("questionnaire") Questionnaire questionnaire);
Now what I would like to do is to group these AnswerCounts by what question they are answers to and store that in a list of QuestionResponseData objects. I could do it in Java code by some stream grouping methods, but I would prefer to do it directly in the query for speed.
Is that even possible, and what would be the best way to do that?
Here are the relevant parts of the models:
public class AnswerCount {
private String answer;
private long count;
}
.
public class QuestionResponseData {
private String question;
private String type;
private List<AnswerCount> answers;
}
.
/**
* A Answer.
*/
#Entity
#Table(name = "answer")
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
#Document(indexName = "answer")
public class Answer implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
#SequenceGenerator(name = "sequenceGenerator")
private Long id;
#NotNull
#Column(name = "jhi_value", nullable = false)
private String value;
#ManyToOne
private Question question;
#ManyToOne
private Respondant respondant;
#ManyToOne
private Questionnaire questionnaire;
}
.
/**
* A Question.
*/
#Entity
#Table(name = "question")
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
#Document(indexName = "question")
public class Question implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
#SequenceGenerator(name = "sequenceGenerator")
private Long id;
#NotNull
#Column(name = "text", nullable = false)
private String text;
#Enumerated(EnumType.STRING)
#Column(name = "jhi_type")
private QuestionType type;
#OneToMany(mappedBy = "question")
#JsonIgnore
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
private Set<Answer> answers = new HashSet<>();
#ManyToMany(mappedBy = "questions")
#JsonIgnore
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
private Set<Questionnaire> questionnaires = new HashSet<>();
}
I'm thinking something similar to this:
#Query(value = "SELECT new QuestionResponseData(q.text, q.type, answers) FROM "
+ "(SELECT new org.project.domain.AnswerCount(a.value, count(a)) as answerCount FROM "
+ "Answer a WHERE a.questionnaire = :questionnaire GROUP BY a.value") answers, "
+ "Question q WHERE answers.answerCount.question = q "
+ "GROUP BY answerCount.question")
but that obviously doesn't work...
Is it possible?