Hibernate query on superclass property - java

First of all, please forgive my ignorance in both Java and Hibernate, I'm studying different ORM solutions and am not a Java programmer.
1) Is it possible to map the following class hierarchy to a database table, with Person.name and Employee.name pointing to different columns?
abstract public class Person {
private String name;
}
public class Employee extends Person {
private String name;
}
2) Supposing that the answer to 1) is Yes, is there a way to create a HQL or Criteria query which would ask Hibernate to return Employee objects, with a criterion on Person.name?
Something like that:
SELECT e FROM Employee e WHERE e.super.name = "test";

1) Yes. This can be accomplished via a MappedSuperclass, and annotating your columns
#MappedSuperclass
abstract public class Person {
#Column(name="PERSON_NAME")
private String name;
}
#Entity
public class Employee extends Person {
#Column(name="EMPLOYEE_NAME")
private String name;
}
2) No. Not without changing the attribute names of one of either Person or Employee. You could however query for all person objects with that name and cast the results to Employees. Another option would be to use Native SQL to query the column you want, again probably not ideal.
SELECT p FROM Person p WHERE p.name = "test";

Related

how to map native sql query to Entity

lets say I have the following Entities
#Entity
public class Person{
private int id;
private String firstName;
private String lastName;
#OneToOne
private B b;
...
}
#Entity
public class B{
private int id;
private String x;
private String y;
...
}
What I want to do is create a SELECT query from a join that returns columns from multiple tables. From the resultset I want to create Objects of Entity Person, process them and then persist them. Something like this
List<Person> list = entityManager.createNativeQuery("SELECT p.firstName, p.lastName, st.entityB FROM someTable st JOIN Person p ON p.lastName = st.lastName").getResultList()
In the statement "someTable" is an unmanaged class, so there is no Entity class for that but it has a column with a reference to the Entity B.
This statement doesnt work but in my current solution I am just returning List<Object[]>. From there I could create a Person but would need to resolve the B Entity because I only get scalar types. Is there a cleaner way somehow, so that I could directly get a List of type Person and also the B Entity is resolved?

Returning nested custom classes from Spring JPA, Hibernate

I have a Student class.
#Entity
public class Student {
private long id;
private String name;
private String department;
private String subDepartment;
private int marks;
//getters, setters and constructors.
}
I want to be able to get min, max and average marks grouped by department and then subDepartment.
My repository:
public class StudentRepository extends JpaRepository<Student,Long> {
#Query("Select s.department, s.subDepartment , min(marks), max(marks), avg(marks) from Student as s groupby s.department, s.subDepartment")
List<Object[]> getStatsByDepartmentAndSubDepartment()
}
Of course, this works but I want to avoid using List<Object[]> and instead get something like:
Map<String, Map<String,Stats>> class
Where first key is Department, second key is subDepartment and Stats class encapsulates min,max,average
Is there a way to do this with Spring JPA. ? I checked and nested classes are not possible.
How about Hibernate or any other solution? I am a newbie when it comes to Hibernate.
I am using Spring boot and can add any dependency needed. Any database is ok, even H2 for a start.
You cannot return a map.
You can convert the rows using a class DTO.
Class DTO with constructor
You can also create a class DTO:
class StudentDTO {
public StudentDTO(String dep, String subDep, Integer min, Integer max, Double avg ) {
this.stats = new Stats(min, max, avg);
...
}
}
And then call it in the select clause:
select new StudentDTO(s.department, s.subDepartment, avg(s.marks), ...) from ...
This will return a List<StudentDTO>.
As long as the constructor matches the select clause, it will work fine.
Interface DTO
You can also define DTO using interfaces, but I don't think it works if you have nested classes.
This will work though:
interface StudentViev {
String getDepartment();
String getSubdepartment();
Integer getMinMark();
Integer getMaxMark();
Double getAvgMark();
}
and now you can use it as type for the list:
#Query("Select s.department as department, s.subDepartment as subDepartment, min(marks) as minMark, max(marks) as maxMark, avg(marks) as avgMark from Student as s groupby s.department, s.subDepartment")
List<StudentView> getStatsByDepartmentAndSubDepartment()
Note that I've used aliases in the select cluase so that the column names returned match the getters in the interface

Search and Sort Problem in JPA with list of entity

Lets say I have two Entity, First One
//Annotations skipped
public class Person {
List<Name> names;
//getter,setter
}
public class Name {
String name;
//getter, setter
}
I want to search and sort according to name with JPA(Hibernate)
I write a query like;
SELECT entity FROM person entity LEFT JOIN entity.names as names
WHERE names.name like :keyword ORDER BY names.name
When there is one name in person there is no problem. Bu if more than one name exists JPA returns a Person for each name. If I use Distict this time, hibernate warns me to distinct must have order by part.
How can I solve this problem
I recommend that you get all your persons in a list say List<Person> persons and then sort the list by using Collections.sort(persons, customComparator); where your Comparator implementation could be like below:
class CustomPersonComparator implements Comparator<Person>{
#Override
public int compare(Person o1, Person o2) {
return 0;//TODO for you: perform your names comparision here and return int comparison result accordingly.
}
}
You can read about impl of comparator here

Select only some columns from a table

Is there a way to select only some columns from a table using jpa?
My tables are huge and I am not allowed to map all the columns in my entities. I tried to create an entity (as a side note, I don't have PKs in my tables):
#Entity
#Table(name = "SuperCat")
#Getter
#Setter
public class Cat{
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
#Column(name="nameCat")
private String name;
}
and then in my repository to
public interface CatRepository extends
CrudRepository<Cat, Long> {
#Query(
"SELECT name FROM Cat")
Page<Cat> getAlCats(Pageable pageable);
This is only a simple example, but the idea is the same. I have searched a lot and I found projections, but there you need to map the whole table, then I found native queries, but still doesn't apply. I know I can return an Object and the other solution is to use query with NEW and create my own object (no #entity, like a pojo). But is there a way that I can do this using jpa, to be able to use repository and services, if I am creating my own pojo then i will create a #transactional class put the queries (with NEW) there and this is it. I don't like this approach and I don't think that the jpa does't allow you to select only some columns, but I didn't find a proper way.
Maybe you will ask what is the result if I am doing like this:
I get this error: "Cannot create TypedQuery for query with more than one return using requested result type [java.lang.Long]"
(For new queries, I am talking about : http://www.java2s.com/Tutorials/Java/JPA/4800__JPA_Query_new_Object.htm maybe I was not clear)
You can do the same by using below approach.
Just create a constructor in entity class with all the required parameters and then in jpa query use new operator in query like below.
String query = "SELECT NEW com.dt.es.CustomObject(p.uniquePID) FROM PatientRegistration AS p";
TypedQuery<CustomObject> typedQuery = entityManager().createQuery(query , CustomObject.class);
List<CustomObject> results = typedQuery.getResultList();
return results;
And CustomObject class should look like below with the constructor.
public class CustomObject {
private String uniquePID;
public CustomObject(String uniquePID) {
super();
this.uniquePID = uniquePID;
}
public String getUniquePID() {
return uniquePID;
}
public void setUniquePID(String uniquePID) {
this.uniquePID = uniquePID;
}
}
spring-data-jpa projection not need to map the whole table, just select the necessary fileds :
// define the dto interface
public interface CatDto {
String getName();
// other necessary fields
...
}
#Query(value = "select c.name as name, ... from Cat as c ...)
Page<CatDto> getAllCats(Pageable pageable);
By this way, CatDto is an interface and it only includes some fileds part of the whole table. Its fields name need to match the select field's alias name.

Crudrepository / Join single field from other table to entity as readonly

Bear with me for any mistakes I make, as this is my first question here.
I have a database with two tables, one table called: PERSON
with the following entity:
#Entity
class Person {
#Id
private String guid;
private String firstName;
private String organisationGuid;
...
...
}
And one table called: ORGANISATION
with the following entity:
#Entity
class Organisation {
#Id
private String guid;
private String name;
...
...
}
As you can see, every Person belongs to an Organisation.
And now I need to list all my persons, with the name of the organisation. I do not want the full Organisation-entity on the Person-entity, rather just the name. Like so:
[{
"guid": "xxx",
"firstName": "Name",
"organisationGuid": "yyy",
"organisationName": "Name of yyy"
}]
How can I accomplish this in the easiest way possible?
Things I have already tried:
1) Adding property to Person and modyfing select-statement
#Entity
class Person {
#Id
private String guid;
private String firstName;
private String organisationGuid;
private String organisationName;
...
...
}
--
#Repository
public interface PersonRepository extends CrudRepository<Person, String> {
#Query(nativeQuery = true, value = "select p.*, o.name as organisation_name from person p left join organisation o on p.organisation_guid = o.guid")
List<Person> findAll();
}
Result: This works fine when using findAll but as soon as I try to save a Person I get an error stating that column ORGANISATION_NAME does not exist.
2) OK, makes sense, so I tried to put #Transient on the field in the entity
#Entity
class Person {
...
#Transient
private String organisationName;
...
...
}
Result: Now it works to save the entity, but I never get the organisationName (as it is marked as Transient).
3) Well damn, then I try to use the annotation #ReadOnlyProperty
#Entity
class Person {
...
#ReadOnlyProperty
private String organisationName;
...
...
}
Result: Same error as in (1). I can findAll but as soon as I try to save a person entity hibernate reports that the column does not exist (because in order to save an item, hibernate first needs to select it, and this particular select does NOT use my own custom select I created in the repository).
4) So then I created a class called PersonOrganisation (with #Table(name="organisation")) with a #ManyToOne-relation from Person to PersonOrganisation, where PersonOrganisation is an entity with just two fields, guid and name.
Result: Error. I can findAll but as soon as I try to save a person entity hibernate reports that the organisationGuid does not match a PersonOrganisation in the database (as it seems that PersonOrganisation is not an Organisation the way Hibernate sees it).
Many things that can be improved here:
1) Add a relationship in the Person relating to the Organization:
#Entity
class Person {
#Id
private String guid;
private String firstName;
#ManyToOne
#JoinColumn(name = "organisationGuid")
private Organisation organisation;
2) create a Result Class which would be holding the projection results:
package com.mypkg;
#Entity
class PersonOrganization {
private String guid;
private String firstName;
private String organisationGuid;
private String organisationName;
public PersonOrganization(String guid, String firstName
, String organisationGuid, String organisationName){
// set the fields
}
}
3) Change the query (dont use native.. its not necessary):
#Repository
public interface PersonRepository extends CrudRepository<Person, String> {
#Query("select NEW com.mypkg.PersonOrganization(p.guid as guid ...
, o.name as organisationName)
from person p left join p.organisation o")
List<PersonOrganization> findPersonWithOrganization();
}
Remeber to add aliases to each result column to match the consturctor of the PersonOrganization class.
I started using the solution from Maciej above, but ran into problems when I didn't want to create an extra "projection" entity with the same fields as the original entity (there were 20 additional fields the on Person entity).
So I actually found another solution which I am very happy with.
1) I started out with adding a #ManyToOne in Person (like Maciej suggested)
#Entity
public class Person {
#Id
private String guid;
private String firstName;
#ManyToOne
private Organisation organisation;
...
...
}
2) I also added a custom serializer to the get-method for the Organisation on the Person entity:
#JsonSerialize(using = OrganisationLightSerializer.class)
public Organisation getOrganisation() {
return organisation;
}
The custom serializer is super simple:
public class OrganisationLightSerializer extends JsonSerializer<Organisation> {
#Override
public void serialize(Organisation organisation, JsonGenerator jsonGenerator, SerializerProvider serializerProvider)
throws IOException, JsonProcessingException {
jsonGenerator.writeStartObject();
jsonGenerator.writeStringField("name", organisation.getName());
jsonGenerator.writeEndObject();
}
}
3) Then I changed all find-queries in my repository and added join fetch, and with this I stopped hibernate from using (at least) two SQL-queries when fetching a list of Persons:
#Repository
public interface PersonRepository extends CrudRepository<Person, String> {
#Override
#Query("select p from Person p join fetch p.organisation o")
List<Person> findAll();
}
Result: I did not get the JSON-structure exactly the way I wanted it, but I managed to get just the pertinent information from each Organisation on each Person. The resulting JSON when fetching persons looks like so (as you can see I skipped the guid of the Organisation in the end, but it can easily be added again by just changing the custom serializer):
[{
"guid": "xxx",
"firstName": "Name",
"organisation": { name: "Name of yyy"}
}]
Disclaimer: Now I realize that my response here is not an exact answer to my own query, as I stated I wanted the JSON as a flat structure, but having an Organisation object in the JSON, which only contains the name of the Organisation is almost as good a solution. Should I edit/change/comment the question so that it reflects the actual answer here, or is this minor change an "acceptable" deviation of the requirements.

Categories