I have an entity with lazy relations like this:
#Getter
#Entity
#Table(name = "entity")
#SequenceGenerator(name = "seq_entity", sequenceName = "seq_entity", allocationSize = 1)
#DynamicUpdate
public class Entity {
#Id
#Column(name = "id_entity")
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "seq_entity")
private Long id;
#Setter
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "id_relation1")
private Relation1 relation1;
#Setter
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "id_relation2")
private Relation2 relation2;
// ...
}
When I make a query to load the other relations I have to specify them like:
return jpaQuery()
.select(qEntity)
.distinct()
.from(qEntity)
.innerJoin(qEntity.relation1).fetchJoin()
.leftJoin(qEntity.relation2).fetchJoin()
.fetch();
But I want to load them without specify into left joins and inner joins for one query...
There is a way to load all in eager mode for one query? Is it possible to desactivate the FetchType.LAZY for one query?
I am thinking in something like
return jpaQuery()
.select(qEntity)
.distinct()
.from(qEntity)
.fetchEager();
You can use entity graph for that. It will be something like this:
EntityGraph<Post> entityGraph = entityManager.createEntityGraph(YourEntity.class);
entityGraph.addAttributeNodes("relation1");
entityGraph.addAttributeNodes("relation2");
And in query
TypedQuery<Post> typedQuery = entityManager.createQuery(criteriaQuery);
typedQuery.setHint("javax.persistence.loadgraph", entityGraph);
Post post = typedQuery.getSingleResult();
This can be enclosed with named entity graph (over entity)
#NamedEntityGraph(
name = "entity-with-all-relations",
attributeNodes = {
#NamedAttributeNode("relation1"),
#NamedAttributeNode("relation2"),
},
thus reused many times. To do that, you use EntityManager#getEntityGraph
Related
I have yet another #OneToMany question. In this case, I'm trying to model a person having a list of excluded people they shouldn't be able to send items to. This is a Spring Boot app using JPA.
In the code below, the exclusions list populates properly but the excludedBy List does not. Because of this, I believe that is causing the deletion of a Person that is excluded by another person to fail because the Exclusion in excludedBy is not mapped on the object properly.
#Entity
#Table(name = "person")
public class Person {
#Id
#GeneratedValue
#Column(nullable = false)
Long id;
...
#OneToMany(mappedBy = "sender", cascade = { CascadeType.ALL })
List<Exclusion> exclusions = new ArrayList<>();
//This is not getting populated
#JsonIgnore
#OneToMany(mappedBy = "receiver", cascade = { CascadeType.ALL })
List<Exclusion> excludedBy = new ArrayList<>();
...
}
#Entity
#Table(name = "exclusions")
public class Exclusion {
#Id
#GeneratedValue
#Column(nullable = false)
Long id;
#ManyToOne
#JsonIgnore
Person sender;
#ManyToOne
Person receiver;
...
}
I would expect that this would have mapped the bidirectional relationship properly and as such the excludedBy List would be populated as well.
Any wisdom on this matter would be great!
1 - An #Id is by default not nullable, not required:
#Column(nullable = false)
2 - There is no need for an #Id in this class. Both sides of the exclusion are together unique. Not needed:
#Id
#GeneratedValue
Long id;
3 - An "Exclusion" requires both an excludedBy and an excluded, give them names that match and they are your #Id. It is a 2 way ManyToMany relationship.
#Entity
#Table(name = "exclusions")
public class Exclusion {
#Id
#ManyToMany // An ID so not optional, so no need for (optional = false)
Person excludedBy;
#Id
#ManyToMany // An ID so not optional, so no need for (optional = false)
Person excluded;
}
Entity Exclusion always knows both sides of the story.
#ManyToMany(mappedBy = "excludedBy", cascade = { CascadeType.ALL })
List<Exclusion> excluded = new ArrayList<>();
#ManyToMany(mappedBy = "excluded", cascade = { CascadeType.ALL })
List<Exclusion> excludedBy = new ArrayList<>();
Tip: JSON DTOs shouldn't be defined in your JPA DTOs, otherwise you can't change your internal data model independently of your external API model.
I had this problem in the past. Your key problem ist that your ORM Mapper hibernate does not know which of your database entries need to be assinged to exclusions and which are assiged to excludedBy. You need a discriminator and add the constraint in your select. I would propose a solution that looks something like this:
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name = "PRIMARY_KEX_IN_EXCLUSION_TABLE", referencedColumnName = "id")
#Where(clause = "is_excluded_by = 0")
private Set<Exclusion> exclusions;
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name = "PRIMARY_KEX_IN_EXCLUSION_TABLE", referencedColumnName = "id")
#Where(clause = "is_excluded_by = 1")
private Set<Exclusion> excludedBy;
the value isExcludedBy needs to be a database column, part of your Entity and set in your code manually.
I think you also need to use Set instead of List when having multiple collections in one Entity. https://vladmihalcea.com/spring-data-jpa-multiplebagfetchexception/
I have a problem using elasticsearch with hibernate search 6. Let's assume we have this setup :
#Entity
#Table(name = "entityA")
#Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
#Indexed(index = "entityA")
public class EntityA {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
#SequenceGenerator(name = "sequenceGenerator")
#GenericField
private Long id;
#Column(name = "name")
#KeywordField
private String name;
#OneToOne
#JoinColumn(unique = true)
#Cascade(value = {CascadeType.MERGE, CascadeType.PERSIST, CascadeType.SAVE_UPDATE})
#IndexedEmbedded
#IndexingDependency(reindexOnUpdate = ReindexOnUpdate.SHALLOW)
private EntityB entityB;
}
#Entity
#Table(name = "entityB")
#Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class EntityB {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
#SequenceGenerator(name = "sequenceGenerator")
#GenericField
private Long id;
#Column(name = "name")
#KeywordField
private String name;
#OneToOne(cascade = {}, fetch = FetchType.EAGER, targetEntity = EntityA.class)
#JoinColumn(name = "id", nullable = false)
#IndexingDependency(reindexOnUpdate = ReindexOnUpdate.DEFAULT)
private EntityA entityA
}
When I first persist EntityA, that being the entity that is indexed, the EntityB is persisted in the elasticsearch index as a child of EntityA. This is ok. The problem appears when I directly edit EntityB and make changes to it, this changes are not propagated to the elasticsearch index. Is something that i am missing?
UPDATE 1
After #yrodiere answers, i made this changes :
#OneToOne
#JoinColumn(unique = true)
#Cascade(value = {CascadeType.MERGE, CascadeType.PERSIST,
CascadeType.SAVE_UPDATE})
#IndexedEmbedded
#AssociationInverseSide(inversePath = #ObjectPath(
#PropertyValue(
propertyName = "entitya" ) ))
private EntityB entityB;
The problem still persist. If i do something like this :
EntityB b = entityBRepository.findById(5051L).get();
b.setProperty("3333");
entityBRepository.save(b);
Regards.
The problem appears when I directly edit EntityB and make changes to it, this changes are not propagated to the elasticsearch index.
You explicitly instructed Hibernate Search to behave exactly that way:
#IndexingDependency(reindexOnUpdate = ReindexOnUpdate.SHALLOW)
private EntityB entityB;
reindexOnUpdate = ReindexOnUpdate.SHALLOW means "reindex EntityA when the entityB property of EntityA changes, but not when a property of EntityB itself (e.g. its name) changes".
See this section of the reference documentation.
I'm guessing you added that to get rid of an exception telling you that Hibernate Search was unable to find the inverse side of the association EntityA.entityB. In your case, it seems you should rather tell Hibernate Search what the inverse side of that association is. Either add a mappedBy to one side of the association (Warning: this will change your DB schema), or use #AssociationInverseSide (see this section of the documentation).
I am getting the above error when trying to run a custom query. I understand that with hibernate, you need to map to the entity names (not the column names). However, in the case of a #OneToMany, I don't have the column in the child. Let me explain with a simple example (I've removed all other columns and methods):
#Query("SELECT ch.randomColumnHere FROM Parent pa INNER JOIN Child ch ON pa.id = ch.parent_id")
Parent.class
#Entity(name="Parent")
#Table(name="parent")
#Builder(toBuilder = true)
#AllArgsConstructor(access = AccessLevel.PACKAGE)
#NoArgsConstructor
#Setter
#Getter
public class Parent {
#Id
#SequenceGenerator(name = "parent_id_seq", sequenceName = "parent_id_seq", allocationSize = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "parent_id_seq")
private Long id;
#OneToMany (cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "parent_id", nullable = false, updatable = false)
private List<Child> children;
}
Child.class
#Entity(name="Child")
#Table(name="child")
#Builder(toBuilder = true)
#AllArgsConstructor(access = AccessLevel.PACKAGE)
#NoArgsConstructor
#Setter
#Getter
public class Child {
#Id
#SequenceGenerator(name = "child_id_seq", sequenceName = "child_id_seq", allocationSize = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "child_id_seq")
private Long id;
#Column(name="randomcolumnhere")
private Double randomColumnHere;
}
I get the following exception:
Caused by: org.hibernate.hql.internal.ast.QuerySyntaxException: could not resolve property: parent_id
I understand that there is no field called parent_id in the Child entity. How can I get around this? Since the only 'reference' to the parent_id column is the #JoinColumn of the Parent class.
Any suggestions?
Add relationship in child class as follow and change query accordingly.
#ManyToOne
private Parent p;
#Query("SELECT ch.randomColumnHere FROM Parent pa INNER JOIN Child ch ON pa.id = ch.p.id")
HQL queries use entities and their associations. The fact that the association uses a join table or not is not important for HQL: you navigate through associations and Hibernate does the appropriate translation to SQL.
SELECT ch.randomColumnHere FROM Parent pa INNER JOIN pa.children;
I'm using QueryDSL JPA, and want to do a join between two tables. I found many similar questions here already, but my case is different from all of them by one little detail. Here is a simplified version of my classes:
#Entity(name = "TABLE_A")
public class TableA {
#Id
#Column(name = "ID_A", nullable = false)
private Long idA;
}
#Entity(name = "TABLE_B")
public class TableB {
#Id
#Column(name = "ID_B", nullable = false)
private Long idB;
}
#Entity(name = "TABLE_C")
public class TableC {
#Id
#Column(name = "ID_C", nullable = false)
private Long idC;
#JoinColumn(name = "ID_A", referencedColumnName = "ID_A")
#ManyToOne
private TableA tableA;
#JoinColumn(name = "ID_B", referencedColumnName = "ID_B")
#ManyToOne
private TableB tableB;
}
Now what I want to do is join Tables A, C and B, to find the Bs which are linked to A. I know this seems like a useless step between, why not add a relation from A to B directly. In my case this is needed, these are just example classes to illustrate.
I tried this:
QTbTableA tableA = QTbTableA.tableA;
QTbTableB tableC = QTbTableC.tableC;
JPAQuery query = new JPAQuery(entityManager).from(tableA);
query.leftJoin(tableA, tableC.tableA);
The join throws an Exception because tableC.tableA is not a root path, only a property. But how do I join these tables correctly then?
Thanks in advance!
If you want to keep your current impl, you could start from TableC and then join the other tables:
query.from(tableC)
.innerJoin(tableC.tableA, tableA)
.innerJoin(tableC.tableB, tableB)
.where(tableA.idA.eq(myId)
.list(tableB);
We have the following 2 classes
public class StagingConcept implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO, generator = "sequence")
#SequenceGenerator(name = "sequence", sequenceName = "stg_concept_seq")
#Column(name = "id")
private long id;
#Column(name = "concept_id", nullable = false, length = 18)
private String conceptId;
#OneToMany(mappedBy = "concept", fetch = FetchType.LAZY,
cascade = { CascadeType.ALL })
private Set<StagingConceptDescription> descriptions;
// rest of the class
}
public class StagingConceptDescription {
#Id
#GeneratedValue(strategy = GenerationType.AUTO, generator = "sequence")
#SequenceGenerator(name = "sequence", sequenceName = "stg_concept_desc_seq")
#Column(name = "id")
private long id;
#ManyToOne
#JoinColumn(name = "concept_id", referencedColumnName = "concept_id")
#ForeignKey(name = "stg_concept_desc_fk1")
private StagingConcept concept;
// rest of the class
}
Some of the details, such as other class properties and entity annotations, have been omitted to keep the example precise. Please let me know if you need more details. Yes, the FK from StagingConceptDescription to StagingConcept is a non-PK Foreign Key.
When I create a Criteria:
"from " + StagingConcept.class.getCanonicalName()
I get all the StagingConcept entities from the DB in one single query. But I need to get the descriptions for each StagingConcept. For that, I write a query:
"from " + StagingConcept.class.getCanonicalName() + " join fetch descriptions"
The resulting SQL looks like:
select stagingcon0_.id as col_0_0_,
descriptio1_.id as id178_1_,
stagingcon0_.is_active as is2_149_0_,
stagingcon0_.brand_restriction_status as brand3_149_0_,
stagingcon0_.concept_id as concept4_149_0_,
stagingcon0_.container_type as container5_149_0_,
stagingcon0_.controlled_drug_status as controlled6_149_0_,
stagingcon0_.effective_date as effective7_149_0_,
stagingcon0_.form as form149_0_,
stagingcon0_.is_multi_component as is9_149_0_,
stagingcon0_.namespace as namespace149_0_,
stagingcon0_.preferred_term as preferred11_149_0_,
stagingcon0_.source as source149_0_,
stagingcon0_.source_version as source13_149_0_,
stagingcon0_.subsidy_status as subsidy14_149_0_,
stagingcon0_.type as type149_0_,
stagingcon0_.unit_of_use_size as unit16_149_0_,
stagingcon0_.unit_of_use_size_unit as unit17_149_0_,
descriptio1_.is_active as is2_178_1_,
descriptio1_.concept_id as concept6_178_1_,
descriptio1_.is_preferred as is3_178_1_,
descriptio1_.term as term178_1_,
descriptio1_.type as type178_1_,
descriptio1_.concept_id as concept6_149_0__,
descriptio1_.id as id0__
from stg_concept stagingcon0_
inner join stg_concept_description descriptio1_ on stagingcon0_.concept_id=descriptio1_.concept_id
It does fetch all the StagingConcepts and their descriptions, albeit in a slightly larger result set in that SQL.
All looks fine up until here. But then it goes and tries to find a staging concept for each and every description again. So if I have 30000 Staging Concepts and 60000 descriptions, it will send another 60000 queries to fetch the staging concept for every description. This looks a little nasty and takes up huge amount of time, enough to run past the transaction timeout.
To attempt to resolve this issue, I change the StagingConceptDescription to
public class StagingConceptDescription {
#Id
#GeneratedValue(strategy = GenerationType.AUTO, generator = "sequence")
#SequenceGenerator(name = "sequence", sequenceName = "stg_concept_desc_seq")
#Column(name = "id")
private long id;
#ManyToOne(fetch = FetchType.LAZY, optional=false)
#JoinColumn(name = "concept_id", referencedColumnName = "concept_id")
#ForeignKey(name = "stg_concept_desc_fk1")
private StagingConcept concept;
// rest of the class
}
So, the ManyToOne relationship is now set to LAZY, explicitly. And also, the relationship states that the concept is not optional, in an attempt to indicate that the relationship is non-optional. By setting that, I meant to tell hibernate that it should be OK to create a proxy object, if needed, because the other end of the relationship is always going to be present. But none of this had any effect. Not too sure if this would work.
I have also tried the #Fetch annotations, etc. None of them work. Even setting it to #LazyToOne(LazyToOneOption.PROXY) didn't have any effect.
Based off https://stackoverflow.com/a/29863982/5464931, you can do another join fetch.
e.g.
"from " + StagingConcept.class.getCanonicalName() + " join fetch descriptions join fetch descriptions.concept"
But this isn't the best solution because it still queries the entity/parent/concept again which is unnecessary.