Hibernate entity join substitute value2 when value1 NULL - java

I am using Hibernate and trying to build the next logic in my entity for SELECT query. Creating a join column where if value of professor's name = NULL, then select value of teacher's name.
Code for Teacher table:
#Entity
#Table(name = "teacher")
public class Teacher {
#Id
#Column(name = "id_number)
private String id;
#OneToOne
#JoinColumn(name = "t_name")
private Professor name;
// Getters and Setters ...
}
Code for Professor table:
#Entity
#Table(name = "professor")
public class Professor{
#Id
#Column(name = "id_number)
private String id;
#Column(name = "p_name")
private String name;
// Getters and Setters ...
}
Working SQL query example:
select
t.id_number as "Identification Number",
isnull(p.p_name, t.t_name) as "Name"
from teacher t
left join professor p
on t.t_name = p.p_name
where id_number in (23, 24, 25, 26, 27)
What should I change in my entities to replicate logic of the SQL query above? Will really appreciate for any help provided.

I'm not sure if you can provide an annotation at field (name) level to achieve this. My guess is, if something like that is present then it might cause the update also to behave the same way. (override teacher's name with professor's)
Couple of other solutions:
Hibernate's Formula annotation:
Create another variable say actualName and provide Formula Annotation with Coalesce ( I used it before to return another field when one field was null).
#Formula("COALESCE(nullableField, backupField)")
I'm not sure if you can use a mapped entity in it, if not you've to make use of JoinColumnOrFormula annotation and write a query for this.
Create a getter for this new field actualName which will check if professor's name is present then return it. else return teacher's name. This will eliminate the need to write another query.
You could also modify the getter of name field in teacher class to return another field that you would want. NOTE: This will also cause your update operation on teacher's table to replace teacher's name with professor's if professor's name is present. Not Recommended at all

Related

Hibernate calculated collection #Formula

is it possible to have and Entity with a field calculated using Formula when the field is a Collection (let's say it's a Set)?
Here's the dummy example of what I'm trying to achive:
#Formula(value =
"SELECT NEW com.example.entity.Person(p.name, p.age) FROM Person p")
lateinit var people :Set<Person>
From the JavaDoc for #Formula:
Defines a formula (derived value) which is a SQL fragment ...
You have to think of the fragment you write as an replacement in the select statement:
SELECT (formulaValue) AS propertyName FROM ....
Everything you can write into formulaValue can be used in #Formula.
Your example is not a valid SQL fragment and as you can see, it is not possible to return more than one value from a #Formula.
But you could use #Subselect and a wrapper object instead:
#Entity
#Subselect("SELECT name, age FROM Person p")
public class PersonWrapper {
#Id
private String name;
private int age;
}
(in Java, as I'm not aware of the correct syntax of Kotlin)
If you really need the collection of these values in another entity, you need something to join on (which is missing in your example) and use that in an #OneToMany or #ManyToMany. Otherwise there is no use to have all these values in your entity, as all entities would have the same collection.

Spring JPA Repository findBy foreign key directly with POJO instead of data member of class

I have a Paper entity and Student entity defined as below
Paper.java
public class Paper extends BaseEntity
{
...
#ManyToOne
#JoinColumn(name = "student", nullable = false, referencedColumnName = "tp")
private Student student;
...
}
Student.java
public class Student extends BaseEntity
{
...
}
I create a PaperRepository for Paper class, I want to find Paper by Student, so a query method is created as this:
List<Paper> findByStudentOrderByDateSubmitted(Student student);
But this method returns an empty result. I have to specify the method to find by student id like below:
List<Paper> findByAndStudent_IdOrderByDateSubmitted(String id);
With this method, it works. Is it possible to find by foreign class, instead of the data member of the foreign class?
try below :
#ManyToOne
#JoinColumn(name = "STUDENT_ID", nullable = false, referencedColumnName = "tp")
private Student student;
At least it's possible using #Query.
#Query("select p from Paper p where p.student.id = :student.id order by p.student.dateSubmitted")
List<Paper> findByStudentOrderByDateSubmitted(#Param("student") Student student);
UDP
And do you mean it's possible or impossible, or "Is it possible to find by foreign class, instead of the data member of the foreign class?"? :)
Above example shows it's possible in your case.
Generally, yes it's possible and findByStudent(Student student) should work. But in some cases results of parser's algorithm could be ambigious and you can use underscore symbol to say where parser should split properties. My guess it's your case (if you could show full POJOs it might be helpfull and I can say more).
You could read about parser's algorithm here, for example, - https://docs.spring.io/spring-data/jpa/docs/1.11.6.RELEASE/reference/html/#repositories.query-methods.query-property-expressions.
There you also can find recommendation don't use underscore symbol as it breaks Java conventions, so sometimes #Query could be better.

partially mapping the result of a NamedNativeQuery to a class

I have an #Entity class Person, that has multiple fields and I would like to map the result of several #NamedNativeQuerys to the Person class however, the queries I am running do not return values for every field in the Person class. When I try to run a query I get the following errors:
[error] o.h.u.JDBCExceptionReporter - Invalid column name bar.
[error] play - Cannot invoke the action, eventually got an error: javax.persistence.PersistenceException: org.hibernate.exception.SQLGrammarException: could not execute query
My class is set up similar to this:
#NamedNativeQueries({
#NamedNativeQuery(
name = "getBirthdate",
//Returns values for {idnumber, name, birthdate}
query = "EXEC dbo.proc_get_birthdate :name",
resultClass = Person.class
),
#NamedNativeQuery(
name = "getBar",
//Returns values for {idnumber, name, bar}
query = "EXEC dbo.proc_get_bar :name",
resultClass = Person.class
)
})
#Entity
public class Person {
#Id
#Column(name = "idnumber")
private int idNumber;
#Column(name = "name")
private String name;
#Column(name = "birthdate")
private String birthdate;
#Column(name = "foo")
private String foo;
#Column(name = "bar")
private String bar;
//All appropriate getter and setter methods are implemented
}
I double checked and all the columns in the Person class do, in fact, exist in the table being queried.
My actual class is much larger and due to that and some security concerns, I do not want my queries to have to return EVERY field and am hoping that there is a simple way to just give them a value of null if a value isn't returned by the query. I tried to set each field to null in the declaration (example below) but that didn't work either.
#Column(name = "bar")
private String bar = null;
I would really rather not have to create a tailored class for every single query I need to run so if what I'm trying to do is possible, any help would be greatly appreciated. Thanks in advance.
I believe that you misunderstood the #NamedNativeQuery use. This annotation should be used with a SQL Query and not with a stored procedure. Looking at the exception SQLGrammarException: could not execute query it is possible to see that is a grammar exception, the query could not be parsed.
With JPA 2.1 you could use the annotation #NamedStoredProcedureQuery that is the one with support to stored procedures.

Hibernate Criteria Join problem

I have a 2 classes that share a UUID and are uni-directionally mapped. I use the UUID to group related rows, and this group shares many details (this is just an example):
#Entity #Table
class Something {
#Id #Column("something_id")
private Long id;
private String uuid = UUID.randomUUID().toString();
#OneToMany
#JoinColumn("uuid")
private List<Detail> details = new LinkedList<Detail>();
}
#Entity #Table
class Detail {
#Id #Column("detail_id")
private Long id;
private String value;
private String uuid;
}
I'm attempting to use Criteria:
Criteria c = getSession().createCriteria(Something.class).createAlias("details", "detail").add(Restrictions.eq("detail.value", someValue));
This is all fine and dandy, but I'm not getting results because of the join:
inner join DETAIL d1_ on this_.SOMETHING_ID=d1_.UUID
Is it possible to specify:
inner join DETAIL d1 on this_.UUID=d1.UUID
I would have expected the join to use the #JoinColumn annotaiton to find the column to join on. I see that I can specify a join type, but I don't see a way to specify the actual column.
I would have expected the join to use the #JoinColumn annotation to find the column to join on. I see that I can specify a join type, but I don't see a way to specify the actual column.
The join is using the JoinColumn annotation since it's joining on d1_.UUID. However, because you didn't specify the referencedColumnName element, the foreign key is assumed to refer to the primary key of the referenced table (this_.SOMETHING_ID), hence the obtained result.
In other words, try this:
#OneToMany
#JoinColumn(name="uuid", referencedColumnName="uuid")
private List<Detail> details = new LinkedList<Detail>();
I'm not sure to understand the benefit but let's say it's another story.

Hibernate - #ElementCollection - Strange delete/insert behavior

#Entity
public class Person {
#ElementCollection
#CollectionTable(name = "PERSON_LOCATIONS", joinColumns = #JoinColumn(name = "PERSON_ID"))
private List<Location> locations;
[...]
}
#Embeddable
public class Location {
[...]
}
Given the following class structure, when I try to add a new location to the list of Person's Locations, it always results in the following SQL queries:
DELETE FROM PERSON_LOCATIONS WHERE PERSON_ID = :idOfPerson
And
A lotsa' inserts into the PERSON_LOCATIONS table
Hibernate (3.5.x / JPA 2) deletes all associated records for the given Person and re-inserts all previous records, plus the new one.
I had the idea that the equals/hashcode method on Location would solve the problem, but it didn't change anything.
Any hints are appreciated!
The problem is somehow explained in the page about ElementCollection of the JPA wikibook:
Primary keys in CollectionTable
The JPA 2.0 specification does not
provide a way to define the Id in the
Embeddable. However, to delete or
update a element of the
ElementCollection mapping, some unique
key is normally required. Otherwise,
on every update the JPA provider would
need to delete everything from the
CollectionTable for the Entity, and
then insert the values back. So, the
JPA provider will most likely assume
that the combination of all of the
fields in the Embeddable are unique,
in combination with the foreign key
(JoinColunm(s)). This however could be
inefficient, or just not feasible if
the Embeddable is big, or complex.
And this is exactly (the part in bold) what happens here (Hibernate doesn't generate a primary key for the collection table and has no way to detect what element of the collection changed and will delete the old content from the table to insert the new content).
However, if you define an #OrderColumn (to specify a column used to maintain the persistent order of a list - which would make sense since you're using a List), Hibernate will create a primary key (made of the order column and the join column) and will be able to update the collection table without deleting the whole content.
Something like this (if you want to use the default column name):
#Entity
public class Person {
...
#ElementCollection
#CollectionTable(name = "PERSON_LOCATIONS", joinColumns = #JoinColumn(name = "PERSON_ID"))
#OrderColumn
private List<Location> locations;
...
}
References
JPA 2.0 Specification
Section 11.1.12 "ElementCollection Annotation"
Section 11.1.39 "OrderColumn Annotation"
JPA Wikibook
Java Persistence/ElementCollection
In addition to Pascal's answer, you have to also set at least one column as NOT NULL:
#Embeddable
public class Location {
#Column(name = "path", nullable = false)
private String path;
#Column(name = "parent", nullable = false)
private String parent;
public Location() {
}
public Location(String path, String parent) {
this.path = path;
this.parent= parent;
}
public String getPath() {
return path;
}
public String getParent() {
return parent;
}
}
This requirement is documented in AbstractPersistentCollection:
Workaround for situations like HHH-7072. If the collection element is a component that consists entirely
of nullable properties, we currently have to forcefully recreate the entire collection. See the use
of hasNotNullableColumns in the AbstractCollectionPersister constructor for more info. In order to delete
row-by-row, that would require SQL like "WHERE ( COL = ? OR ( COL is null AND ? is null ) )", rather than
the current "WHERE COL = ?" (fails for null for most DBs). Note that
the param would have to be bound twice. Until we eventually add "parameter bind points" concepts to the
AST in ORM 5+, handling this type of condition is either extremely difficult or impossible. Forcing
recreation isn't ideal, but not really any other option in ORM 4.
We discovered that entities we were defining as our ElementCollection types did not have an equals or hashcode method defined and had nullable fields. We provided those (via #lombok for what it's worth) on the entity type and it allowed hibernate (v 5.2.14) to identify that the collection was or was not dirty.
Additionally, this error manifested for us because we were within a service method that was marked with the annotation #Transaction(readonly = true). Since hibernate would attempt to clear the related element collection and insert it all over again, the transaction would fail when being flushed and things were breaking with this very difficult to trace message:
HHH000346: Error during managed flush [Batch update returned unexpected row count from update [0]; actual row count: 0; expected: 1]
Here is an example of our entity model that had the error
#Entity
public class Entity1 {
#ElementCollection #Default private Set<Entity2> relatedEntity2s = Sets.newHashSet();
}
public class Entity2 {
private UUID someUUID;
}
Changing it to this
#Entity
public class Entity1 {
#ElementCollection #Default private Set<Entity2> relatedEntity2s = Sets.newHashSet();
}
#EqualsAndHashCode
public class Entity2 {
#Column(nullable = false)
private UUID someUUID;
}
Fixed our issue. Good luck.
I had the same issue but wanted to map a list of enums: List<EnumType>.
I got it working like this:
#ElementCollection
#CollectionTable(
name = "enum_table",
joinColumns = #JoinColumn(name = "some_id")
)
#OrderColumn
#Enumerated(EnumType.STRING)
private List<EnumType> enumTypeList = new ArrayList<>();
public void setEnumList(List<EnumType> newEnumList) {
this.enumTypeList.clear();
this.enumTypeList.addAll(newEnumList);
}
The issue with me was that the List object was always replaced using the default setter and therefore hibernate treated it as a completely "new" object although the enums did not change.

Categories

Resources