JPA Criteria API join on 3 tables and some null elements - jpa

I have one parent entity that has two child entities as attributes.
I want to select all elements from the parent entity that have EITHER a childOne with a given parameter as personal attribute OR childTwo with that same given parameter as personal attribute.
Here are my three classes simplified:
The Parent Object:
#Entity
public class ParentObject {
#Id
private int id;
private int fkChildOne;
private int fkChildTwo;
#ManyToOne
#JoinColumn(name = "fk_child_one_id", referencedColumnName =
"child_one_id")
private ChildOne childOne;
#ManyToOne
#JoinColumn(name = "fk_child_one_id", referencedColumnName =
"child_one_id")
private ChildTwo childTwo;
// getters and setters
}
The Child One Object:
#Entity
public class ChildOne {
#Id
private int childOneId;
private String nameChildOne;
#OneToMany
#JoinColumn(name = "fk_child_one_id")
private List<ParentObject> parents;
// getters and setters
}
The Child Two Object:
#Entity
public class ChildTwo {
#Id
private int childOneId;
private String nameChildTwo;
#OneToMany
#JoinColumn(name = "fk_child_two_id")
private List<ParentObject> parents;
// getters and setters
}
The Specs Class:
public static Specification<ParentObject> checkName(String name) {
return Specifications.where(
(root, query, builder) -> {
final Join<ParentObject, ChildOne> joinchildOne =
root.join("childOne");
final Join<ParentObject, ChildTwo > joinchildTwo =
root.join("childTwo");
return builder.or(
builder.equal(joinchildOne .get("nameChildOne"), name),
builder.equal(joinchildTwo .get("nameChildTwo"), name)
);
}
);
}
When this spec is called in my service, I get no results. However, if I comment out one of the two joins and the corresponding Predicate in my builder.or method, then I get some results but they obviously don't match what I'm looking for, which is to select every ParentObject that have either ChildOne with that parameter or ChildTwo with that paramater.
Any clue what's wrong with the code ?

Finally got the solution : to fetch all the corresponding results, I had to add the type of the join which would be left join, since I wanted to fetch all ParentObjects regardless of owning childOne or ChildTwo objects.
final Join<ParentObject, ChildOne> joinchildOne =
root.join("childOne", JoinType.LEFT);
final Join<ParentObject, ChildTwo > joinchildTwo =
root.join("childTwo", JoinType.LEFT);

Great, now you have to choose if you need to join or fetch.To optimize the query and the memory, you should establish the relations as Lazy (#ManyToMany (fetch = FetchType.LAZY)), so you will only bring the objects that you demand.
The main difference is that Join defines the crossing of tables in a variable and allows you to use it, to extract certain fields in the select clause, for example, on the other hand, fetch makes it feed all the objects of that property. On your example,
a select from parent with join of children (once the relation is set to lazy) would only bring initialized objects of type parent, however if you perform a fetch, it would bring the parent and child objects initialized.
Another modification I would make is to change the type of the identifier to non-primitive, so that it accepts null values, necessary for insertion using sequences

Related

Spring Data Repository: What is the managed type?

For my intention the JavaDoc for org.springframework.data.repository.Repository is kind of imprecise on the generic parameter <T>. It says:
#param the domain type the repository manages
Okay.
Suppose we have these entities:
#Entity
#Table
public class Parent {
#Id
private Long id;
#Getter
#OrderBy
#OneToMany(mappedBy = "parent")
private Set<Children> children;
}
#Entity
#Table
public class Children {
#Id
private Long id;
#OneToOne
#JoinColumn(name = "parent_id", nullable = false)
private Parent parent;
}
If all children for a parent are queried which repository is the correct one?
public interface ParentRepository extends Repository<Parent, Long> {
// IntelliJ Warning: 'Children' domain type or valid projection interface expected here
#Query("SELECT children FROM Parent WHERE id = ?1")
List<Children> getChildrenByParentId(Long parentId);
}
public interface ChildrenRepository extends Repository<Children, Long> {
#Query("SELECT children FROM Parent WHERE id = ?1")
List<Children> getChildrenByParentId(Long parentId);
}
What about this additional query?
#Query("SELECT parent.children FROM Parent parent LEFT JOIN parent.children children WHERE parent.id = ?1 ORDER BY children.id")
List<Children> getOrderedChildrenByParentId(Long parentId);
As it is working no matter which repository is used my interest is more why ParentRepository or ChildrenRepository is the correct one.
After skimming through the documentation: https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#reference the ParentRepository seems more correct - although the IntelliJ warning.
If you create an interface:
public interface ParentRepository extends Repository<Parent, Long>
The parameter Parent is used to generate the methods like findAll findOne etc. to return the correct type and the parameter Long is used as the primary key type.
If you do:
#Query("SELECT parent.children FROM Parent parent LEFT JOIN parent.children children WHERE parent.id = ?1 ORDER BY children.id")
List<Children> getOrderedChildrenByParentId(Long parentId);
You are absolutely free what parameter types and what return type you want to use. So it doesn't matter where you put the method with the #Query annotation.

Spring Data JPA Projection nested list projection interface

I have a question about usage of nested list projection interface. I have two entity (Parent and child) (they have Unidirectional association)
Parent =>
#Table(name = "parent")
#Entity
public class ParentEntity {
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private String name;
// other fields........
}
Child =>
#Table(name = "child")
#Entity
public class ChildEntity {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#NonNull
private String name;
#NonNull
#ManyToOne(fetch = FetchType.LAZY)
private ParentEntity parent;
// other fields........
}
I have two projection interface for select specific columns.
ParentProjection =>
public interface ParentProjection {
String getName();
Set<ChildProjection> getChild();
}
ChildProjection =>
public interface ChildProjection {
String getId();
String getName();
}
I want to take list of ParentProjection which includes with list of ChildProjection.
Repository query like that =>
#Query("select p.name as name, c as child from ParentEntity p left join ChildEntity as c on p.id = c.parent.id")
List<ParentProjection> getParentProjectionList();
This query works, but it selects all columns of ChildEntity, and map only id, name propeties to ChildProjection. (generated query selects all columns, but i want to select only id and name columns)
How can i select only id and name columns (select specific columns for nested list projection interface) and map to ChildProjection fields (using with #Query) ?
Note: I don't need to use class type projection.
You need to add the OneToMany relation to ParentEntity and annotate with Lazy.
Hope it helps (i have tried this).

EclipseLink Batch Fetch Hint Not Working For Two Fields

I'm using QueryHints in Spring Data JPA to use EclipseLink Batch Fetch with a type of IN. Ultimately, I need to use this around 30 fields but it doesn't seem to work right for 2 fields. Field A has a ManyToOne relationship and Field B has a ManyToMany. Based on the results of the initial query, I would expect the batch hint to generate an IN clause with 2 ids for Field A and 12 for Field B. This works fine when the hint is turned on for one field at a time. When it is enabled for both fields, the hint only applies to whichever field is the last hint in the list of QueryHints. I've tried EAGER and LAZY fetch on the fields as a shot in the dark, but it had not impact.
Is there a limitation with mixing batch fetch hints based on the relationship type? Is there something different going on? The EclipseLink documentation isn't very detailed for this feature.
EDIT: It seems it doesn't matter what fields I enable it only, it only works for one at at time. Here is sample code for two entities. The BaseEntity defines the PK id generation.
#Entity
#Table(name = "MainEntity")
public class MainEntity extends BaseEntity implements Cloneable {
...
#ManyToMany(fetch=FetchType.LAZY, cascade=CascadeType.PERSIST)
#JoinTable(
name="EntityBMapping",
joinColumns={#JoinColumn(name="mainId", referencedColumnName="id")},
inverseJoinColumns={#JoinColumn(name="bId", referencedColumnName="id")})
#JsonIgnore
private Set<EntityB> bSet = new HashSet<>();
#ManyToMany(fetch=FetchType.LAZY, cascade=CascadeType.ALL)
#JoinTable(
name="EntityAMapping",
joinColumns={#JoinColumn(name="mainId", referencedColumnName="id")},
inverseJoinColumns={#JoinColumn(name="aId", referencedColumnName="id")})
#JsonIgnore
#OrderColumn(name="order_index", columnDefinition="SMALLINT")
private List<EntityA> aList = new ArrayList<>();
...
}
#Entity
#Cache(type=CacheType.FULL)
#Table(name = "EntityA")
public class EntityA extends BaseEntity {
#Column(name = "name", columnDefinition = "VARCHAR(100)")
private String name;
#ManyToMany(mappedBy = "entityASet", fetch=FetchType.LAZY)
#JsonIgnore
private Set<MainEntity> mainEntityList = new HashSet<>();
}
#Entity
#Cache(type=CacheType.FULL)
#Table(name = "EntityB")
public class EntityB extends BaseEntity {
#Column(name = "name", columnDefinition = "VARCHAR(100)")
private String name;
#ManyToMany(mappedBy = "entityBSet", cascade=CascadeType.ALL)
#JsonIgnore
private Set<MainEntity> mainEntityList = new HashSet<>();
}
The repository query:
#QueryHints(value = {
#QueryHint(name = org.eclipse.persistence.config.QueryHints.BATCH_TYPE, value = "IN"),
#QueryHint(name = org.eclipse.persistence.config.QueryHints.BATCH_SIZE, value = "250"),
#QueryHint(name = org.eclipse.persistence.config.QueryHints.BATCH, value = "o.aList")},
#QueryHint(name = org.eclipse.persistence.config.QueryHints.BATCH, value = "o.bSet")},
forCounting = false)
List<MainEntity> findAll(Specification spec);
Generated queries:
SELECT id, STATUS, user_id FROM MainEntity WHERE ((STATUS = ?) OR ((STATUS = ?) AND (user_id = ?)))--bind => [ONESTAT, TWOSTAT, myuser]
..
SELECT t1.id, t1.name, t0.order_index FROM EntityAMapping t0, EntityA t1 WHERE ((t0.mainId = ?) AND (t1.id = t0.aId))--bind => [125e17d2-9327-4c6b-a65d-9d0bd8c040ac]
...
SELECT t1.id, t1.name, t0.mainId FROM EntityBMapping t0, EntityB t1 WHERE ((t1.id = t0.bId) AND (t0.mainId IN (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)))--bind => [125e17d2-9327-4c6b-a65d-9d0bd8c040ac, 1c07a3a9-7028-48ba-abe8-2296d58ebd57, 235bb4f2-d724-4237-b73b-725db2b9ca9f, 264f64b3-c355-4476-8530-11d2037b1f3c, 2d9a7044-73b3-491d-b5f1-d5b95cbb1fab, 31621c93-2b0b-4162-9e42-32705b7ba712, 39b33b19-c333-4523-a5a7-4ba0108fe9de, 40ba7706-4023-4b7e-9bd5-1641c5ed6498, 52eed760-9eaf-4f6a-a36f-076b3eae9297, 71797f0c-5528-4588-a82c-5e1d4d9c2a66, 89eda2ef-80ff-4f54-9e6a-cf69211dfa61, 930ba300-52fa-481c-a0ae-bd491e7dc631, 96dfadf9-2490-4584-b0d4-26757262266d, ae079d02-b0b5-4b85-8e6f-d3ff663afd6e, b2974160-33e8-4faf-ad06-902a8a0beb04, b86742d8-0368-4dde-8d17-231368796504, caeb79ce-2819-4295-948b-210514376f60, cafe838f-0993-4441-8b99-e012bbd4c5ee, da378482-27f9-40b7-990b-89778adc4a7e, e4d7d6b9-2b8f-40ab-95c1-33c6c98ec2ee, e557acf4-df01-4e66-9d5e-84742c99870d, ef55a83c-2f4c-47b9-99bb-6fa2f5c19a76, ef55a83c-2f4c-47b9-99bb-6fa2f5c19a77]
...
SELECT t1.id, t1.name, t0.order_index FROM EntityAMapping t0, EntityA t1 WHERE ((t0.mainId = ?) AND (t1.id = t0.aId))--bind => [1c07a3a9-7028-48ba-abe8-2296d58ebd57]
As Chris mentioned, Named Queries are the best work around for this issue. The other option is to use a custom repository and call setHint on the EntityManager yourself for each hint specified (plenty of examples out there for creating custom repos in Spring Data JPA). You could attempt to override findOne(...) and protected <S extends T> TypedQuery<S> getQuery(Specification<S> spec, Class<S> domainClass, Sort sort) on SimpleJpaRepository to try and create a generic way to properly set the hints but you'll likely want to check that you don't duplicate hint setting on getQuery(...) as you'll still want to call super() for that and then apply your additional hints before returning the query. I'm not sure what the behavior would be if you applied a duplicate hint. Save yourself the trouble and use Named Queries is my advice.

get #ElementCollection entries with Querydsl

I have an #Entity class which holds an #ElementCollection:
#Entity
public class Skill extends JpaEntity {
#ElementCollection(targetClass = SkillName.class)
#CollectionTable(joinColumns = #JoinColumn(name = "SKILL_ID"))
private Set<SkillName> names = new HashSet<>();
...
Those elements are defined in a nested #Embeddable class without ID:
#Embeddable
#Immutable
#Table(uniqueConstraints = #UniqueConstraint(columnNames = "NAME"))
public static class SkillName extends ValueObject {
private boolean selectable;
#Column(unique = true, nullable = false)
#Size(max = 64)
#NotEmpty
private String name;
...
I try to get some specific elements of that element-collection via Querydsl:
QSkill skill = QSkill.skill;
QSkill_SkillName skillName = QSkill_SkillName.skillName;
List<SkillName> foundSkillNames = from(skill)
.innerJoin(skill.names, skillName).where(...)
.list(skillName);
This gives me a MySQLSyntaxErrorException: Unknown column 'names1_.id' in 'field list' since the resulting query looks like:
select names1_.id as col_0_0_ from Skill skill0_ inner join Skill_names names1_ on ...
which is obviously wrong since SkillName has no id
If I replace .list(skillName) with .list(skillName.name) everything works fine, but I get a list of Strings instead of a list of SkillNames.
So the question is:
What can I do to get a list of #Embeddables of an #ElementCollection via Querydsl?
since you are looking for Embeddable objects inside an entity, you might navigate from the entity to the requested Embeddable (in your case "SkillName") - therefor your query should be changed to list(skill) - the entity:
List<Skill> list =
from(skill).innerJoin(skill.names, skillName).
where(skillName.name.like(str)).
list(skill);
for (Skill skill : list) {
// do something with
Set<SkillNames> skillNames = skill.getNames();
}
HTH
You cannot project Embeddable instances directly, but alternatively you can use
Projections.bean(SkillName.class, ...) to populate them or
Projections.tuple(...) to get the skillName properties as a Tuple instance

Single Table Inheritance Query

i have an existing table for TransactionLogs which is either links to a External or to a InternalType. the id's corresponding to the cash adjustment & game transaction are stored in a single column called transaction id and a separate column called type indicates which table is it linked to
Because of the nature of the existing table, i mapped it in a single table inheritance:
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name = "TYPE", discriminatorType = DiscriminatorType.INTEGER)
public class TransLog implements Serializable {
#Id
#GeneratedValue
private Long id;
private Integer type;
// getters and setters
}
#Entity
public class InternalAdjustmentTransLog extends TransLog {
#ManyToOne
#JoinColumn(name = "TransID", nullable = false)
private InternalAdjustmentRecord internalAdjustmentRecord;
// getters and setters
}
#Entity
public class ExternalTransLog extends TransLog {
#ManyToOne
#JoinColumn(name = "TransID", nullable = false)
private ExternalAdjustmentRecord externalAdjustmentRecord;
}
each of these two subclasses has their subclasses with defined descriminator values..
With the setup given above, there are instances that i need to get a unified data of both
internal and external records. What is the best way to accomplish this? at first i thought it would be enough to use the TransLog as the root class for the query (i'm using jpa criteria). however, i need to get TransId (which are defined in the subclasses and points to 2 different objects of no relationship).
Thanks.
You can make abstract method in TransLog that returns what you need and implement it in both subclasses.