JPA: Get visible elements in a tree relantionship in same table - spring-boot

I have a hierarchical table of people in which I will have three levels:
Level 1: Grandfather: This always has the parent attribute to NULL
Level 2: Father: The parent attribute is Grandfather (Level 1)
Level 3: Child: The parent attribute is Father (Level 2)
The Postgres database table is as follows:
CREATE TABLE person(
cod_person varchar NOT NULL,
cod_parent varchar NULL,
visible bool NOT NULL,
CONSTRAINT s_person_pk PRIMARY KEY (cod_person)
);
And the JPA entity is as follows:
#Getter
#Entity
#EqualsAndHashCode(exclude = {"nodes"})
#NoArgsConstructor
#AllArgsConstructor
#Table(name = "person")
public class Person implements Serializable {
#Id
#Column(name = "cod_person")
private String id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "cod_parent")
private Person parent;
#OneToMany(mappedBy = "parent", fetch = FetchType.LAZY)
#Fetch(FetchMode.JOIN)
private Set<Person> childs = new HashSet<>();
#Column(name = "visible")
private Boolean isVisible;
}
Using JPA/JPQL (but never native query) and execute only one query I need to retrieve the visible elements. Based on these examples:
Case A:
cod_person
cod_parent
visible
1
null
true
2
1
true
3
2
true
4
2
true
5
1
true
6
5
true
7
5
true
In this case, I need retrieve this object:
Person 1:
parent: null
childs:
Person 2:
parent: Person 1
childs:
Person 3
Person 4
Person 5:
parent: Person 1
childs:
Person 6
Person 7
Case B:
cod_person
cod_parent
visible
1
null
true
2
1
true
3
2
true
4
2
true
5
1
true
6
5
false
7
5
false
In this case, I need retrieve this object:
Person 1:
parent: null
childs:
Person 2:
parent: Person 1
childs:
Person 3
Person 4
Person 5:
parent: Person 1
childs: EMPTY LIST
I have this query in JPQL:
#Repository
public interface PersonRepository extends JpaRepository<Person, String> {
#Query("SELECT DISTINCT granfather " +
"FROM Person granfather " +
"LEFT JOIN FETCH granfather.childs father " +
"LEFT JOIN FETCH father.childs child " +
"LEFT JOIN FETCH child.parent child_parent " + // To avoid new query to parent
"WHERE granfather.parent IS NULL " +
"AND granfather.isVisible = true " +
"AND father.isVisible = true " +
"AND child.isVisible = true ")
List<Person> findFamily();
}
With this query, case A works correctly, since all the Parents have children. The problem is in case B. When a Parent has no children, the query does not return this Parent and Person 5 is missing:
Person 1:
parent: null
childs:
Person 2:
parent: Person 1
childs:
Person 3
Person 4
I need to return this object since it is visible, with an empty child list.
How can I achieve this goal?

Related

Java 8 GroupBy and transform result to a list

Hello I have to group by multiple fields and make a summary by one of these files, thereafter I have to work with this result and make some more transformations my problem is that I'm getting a complex structure after the grouping and it's not easy to work with this items. This is my code:
Map<String, Map<String, Map<LocalDateTime, Map<String, Double>>>> processed = null;
processed = itemsToProcess
.stream()
.collect(groupingBy(entity::getId,
groupingBy(entity::getType,
groupingBy(entity::getCreateDate,
groupingBy(entity::getCode,
summingDouble(entity::getPay))))));
The objective of this grouping is the summary of the pays but thereafter I I have to do some transformations with this processed structure, my doubt if is there is a way to transform this in a simple list in order to make more easy this task?
My input is basically a list of:
List<Person> itemsToProcess= new ArrayList<>();
#JsonInclude
public class Person extends Entity<Person > {
private static final long serialVersionUID = 1L;
/* File line content */
private String id;
private String type;
private LocalDateTime createDate;
private String code;
private double pay;
private String city
private String company
}
The output that I'm looking for is the summary of the pay field grouped by {id,type,createDate,pay}
example if I have the next values
Id type createdDAte Code pay more fields....
1 0 today BC 500
1 0 today BC 600
2 0 today BC 600
2 0 today BC 300
3 0 today BC 300
The result must be:
Id type createdDAte Code pay more fields....
1 0 today BC 1100
2 0 today BC 900
3 0 today BC 300
You can use Collectors.toMap to Map by that four properties and merging the same group person objects and creating a new one using the constructor.
List<Person> processed =
new ArrayList<>(
itemsToProcess
.stream()
.collect(Collectors.toMap(
i -> Arrays.asList(i.getId(), i.getType(), i.getCreateDate(), i.getCode()),
i -> i,
(a, b) -> new Person(a.getId(), a.getType(), a.getCreateDate(), a.getCode(),
a.getPay() + b.getPay())))
.values());
Output:
Person [id=2, type=0, createDate=2020-08-18T12:26:15.616034800, code=BC, pay=900.0]
Person [id=3, type=0, createDate=2020-08-18T12:26:15.616034800, code=BC, pay=300.0]
Person [id=1, type=0, createDate=2020-08-18T12:26:15.616034800, code=BC, pay=1100.0]
Demo here
Quick way is to group by a map of the key fields:
groupingBy(e -> Map.<String, Object>of(
"id", e.getId(),
"type", e.getType(),
"createDate", e.getCreateDate(),
"code", e.getCode(),
"pay", e.getPay()
), identity())
Map's equals() method works as you would hope it does.

How to query a collection that can be null in JPQL?

Say I have an entity like this
class Parent {
#OneToMany(mappedBy = "par")
Set<Child> children
String stuff;
}
class Child {
#ManyToOne
#JoinColumn(name="par_id", nullable=false)
private Parent par;
String value;
}
I want to have a query like this:
Select DISTINCT par from Parent par LEFT JOIN par.children chi
WHERE
( par.stuff = :stuff or (:stuff is null))
AND ((chi is not empty) and chi.value = :value))
But this gives me back parents that have empty children.
I need to select all Parent that have a set of non empty children AND also children matching value = x
You can try using inner join to make sure children exists:
select distinct par from Parent par join par.children chi where chi.value = :value
You can use exists operator:
select distinct par from Parent par
where exists
(select chi
from Child chi
where chi.value = :value and chi.parent = par)

Hibernate Envers revision number order

I used Envers to audit my entities. The DB used is Oracle 11g. For the revision number of the revision entity, I used a oracle sequence. But since the oracle sequence does not guarantee monotonic increasing of each call to nextval, In my revision table, I got something like this,
We can see that the rev number is not monotonically increasing. In the audit table of my entity, I have this:
By chaining the records together with the rev number to form a timeline, I got :
24 --> 1302 --> 1303 --> 1355 --> 1304 --> 1356 --> 1357 --> 1305 -->1358 --> null
In fact, I have a cluster of 2 servers. They both can persist data into the DB. I suspect that this has relation to the problem of order in revnumber. Because of this problem. Query like MyEntity anEntity = auditReader.find(MyEntity.class, id, revNum) doesn't work because of
org.hibernate.envers.query.impl.AbstractAuditQuery.getSingleResult(AbstractAuditQuery.java:117) . I checked the SQL generated by Hibernate, in the where clause
where
myentity.rev<=?
and myentity.revtype<>?
and myentity.id=?
and (
myentity.revend>?
or myentity.revend is null
)
so for the rev number 1356, several audit records are retrieved, for example
1356 --> 1357 and 1305 -->1358 because rev num 1356 is just between the two ends.
How can I solve this problem? I mean to make the rev number monotonically increasing one transaction after another.
UPDATE
My revision entity
#Entity
#RevisionEntity(CustomRevisionListener.class)
#Table(name = "REV_TABLE")
#SequenceGenerator(name = "GENERIC_GENERATOR", sequenceName = "SQ_REVISION_ID")
public class Revision {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator ="GENERIC_GENERATOR")
#Column(nullable = false, name = REV)
#RevisionNumber
private long rev;
}
You probably want to take a look at how the revision number sequence is defined inside the SequenceIdRevisionEntity class. We essentially define it as follows:
#Id
#GeneratedValue(generator = "RevisionNumberSequenceGenerator")
#GenericGenerator(
name = "RevisionNumberSequenceGenerator",
strategy = "org.hibernate.envers.enhanced.OrderedSequenceGenerator",
parameters = {
#Parameter(name = "table_name", value = "REVISION_GENERATOR"),
#Parameter(name = "sequence_name", value = "REVISION_GENERATOR"),
#Parameter(name = "initial_value", value = "1"),
#Parameter(name = "increment_size", value = "1")
}
)
#RevisionNumber
private int id;
The key is specifying that the initial_value and increment_size are defined as 1 to avoid the hi-lo gaps you're noticing with your existing sequence definition.

Queries in EJB QL Collection Member Comparison

I need to write the following sql query, in EJBQL.
SELECT * FROM teacher t left join student_class sc on t.id_teacher = p.id_student_class where t.login = 01325 and sc.id_student_class = 3;
In the class bean Teacher, there is the following relantionship:
#Column(name = "login")
private String mLogin;
#OneToMany(mappedBy = "mTeacher", fetch = FetchType.LAZY)
private List<StudentClass> mStudentClassList ;
I know how start it:
SELECT mTeacher FROM Teacher mTeacher INNER JOIN mTeacher.mStudentClassList mStudentClassList WHERE mTeacher.mLogin = 01325 AND mStudentClassList
... ???
Can anyone help me?

LINQ update on joined collections

How do I use LINQ to update objects in one list from objects in a second list? My question is very similar to LINQ In Line Property Update During Join except that, in my case, the second list is smaller than the parent list.
In other words, I want to update those members of the master collection that have corresponding updates in a second collection. Otherwise I want the master object left unaltered. The technique in the article referenced above seems to result in an inner join of the two collections.
Thanks
The answer in the other article is fine for your problem too, as what you really want is an inner join. It's important to note that the inner join is only used to perform the function, it's not modifying the list (i.e. items that don't meet the inner join are left untouched in the list).
For completeness here's the solution I would use:
List<Person> people = new List<Person>();
people.Add( new Person{ Name = "Timothy", Rating = 2 } );
people.Add( new Person{ Name = "Joe", Rating = 3 } );
people.Add( new Person{ Name = "Dave", Rating = 4 } );
List<Person> updatedPeople = new List<Person>();
updatedPeople.Add( new Person { Name = "Timothy", Rating = 1 } );
updatedPeople.Add( new Person { Name = "Dave", Rating = 2 } );
ShowPeople( "Full list (before changes)", people );
Func<Person, Person, Person> updateRating =
( personToUpdate, personWithChanges ) =>
{
personToUpdate.Rating = personWithChanges.Rating;
return personToUpdate;
};
var updates = from p in people
join up in updatedPeople
on p.Name equals up.Name
select updateRating( p, up );
var appliedChanges = updates.ToList();
ShowPeople( "Full list (after changes)", people );
ShowPeople( "People that were edited", updatedPeople );
ShowPeople( "Changes applied", appliedChanges );
Here's the output I get:
Full list (before changes)
-----
Name: Timothy, Rating: 2
Name: Joe, Rating: 3
Name: Dave, Rating: 4
Full list (after changes)
-----
Name: Timothy, Rating: 1
Name: Joe, Rating: 3
Name: Dave, Rating: 2
People that were edited
-----
Name: Timothy, Rating: 1
Name: Dave, Rating: 2
Changes applied
-----
Name: Timothy, Rating: 1
Name: Dave, Rating: 2

Resources