I am new to JPA 2.1 and started using only recently Named Entity Graphs. For my project I am mapping the following relation in JPA 2.1:
Order -> OrderDetail -> Product -> ProductLine
The question:
I want to instruct JPA to join and fetch properly all the needed data. So far this works flawlessly for Order -> OrderDetail -> Product but I have not managed so far to add a Sub-Sub Graph in order to go as deep as the ProductLine class. How do I make a subgraph of a subgraph ? Ex get the ProductLine of the Product ?
Here are my entities (getters and setters omitted):
Order
#Entity
#Table(name="ORDERS")
#NamedEntityGraph(
name = "graph.Order.details",
attributeNodes = {
#NamedAttributeNode(value = "details", subgraph = "graph.OrderDetail.product")
},
subgraphs = {
#NamedSubgraph(name = "graph.OrderDetail.product", attributeNodes = #NamedAttributeNode("product"))
}
)
public class Order implements Serializable{
#Id
#Column(name = "orderNumber")
private Long number;
#Column(name = "orderDate")
private Date date;
#OneToMany(mappedBy = "order")
private List<OrderDetail> details;
}
OrderDetail
#Entity
#Table(name = "orderdetails")
public class OrderDetail implements Serializable{
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "orderNumber")
#Id
private Order order;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "productCode", nullable = false)
#Id
private Product product;
#Column(name = "orderLineNumber")
private int lineNumber;
#Column(name = "quantityOrdered")
private int quantity;
Product
#Entity
#Table(name = "products")
class Product {
#Column(name = "productCode")
#Id
private String code;
#Column(name = "quantityInStock")
public int quantity;
#ManyToOne
#JoinColumn(name = "productLine")
private ProductLine line;
ProductLine
#Entity
#Table(name = "productlines")
public class ProductLine {
#Id
#Column(name = "productLine")
private String line;
#Column
private String textDescription;
The simple answer is that you cannot do this because, with the current JPA implementation, you would end up doing two separate queries and having to deal with the Cartesian Products. Some future version of JPA could be extended to include more levels of subgraphs, but as it stands today it does not. There is a JPA SPEC group that works on the next version of JPA. Feel free to submit your request/suggestion there.
Here on StackOverflow there is another reference to the same question.
You can create multi level entity graphs with dynamic entity graphs.
I am using jpa 2.2 and Hibernate 5.3.7 and i am able to create entity
graphs and fetch data upto 3 levels . I hope this will work for
next level too . Below is the code snippet . For more details and actual code you can checkout my github repo : https://github.com/vaneetkataria/Jpa-Hibernate/blob/master/jdbcToJpaMigration/src/test/java/com/katariasoft/technologies/jpaHibernate/entity/fetch/entitygraph/dynamic/MultiInstructorsDynamicEntityGrpahTests.java
Code snippet :
#SuppressWarnings("unchecked")
#Test
#Rollback(false)
public void fetchInstrctrsIdProofVehiclesStudentsTheirInstructorsVehiclesAndTheirDocuments() {
doInTransaction(() -> {
EntityGraph<Instructor> instructorGraph = em.createEntityGraph(Instructor.class);
instructorGraph.addAttributeNodes(Instructor_.idProof, Instructor_.vehicles);
Subgraph<Student> studentSubgraph = instructorGraph.addSubgraph(Instructor_.STUDENTS);
studentSubgraph.addAttributeNodes(Student_.instructors);
Subgraph<Vehicle> vehicleSubgraph = studentSubgraph.addSubgraph(Student_.VEHICLES);
vehicleSubgraph.addAttributeNodes(Vehicle_.documents);
TypedQuery<Instructor> query = em.createQuery("select i from Instructor i ", Instructor.class)
.setHint(EntityGraphUtils.FETCH_GRAPH, instructorGraph);
List<Instructor> instructors = query.getResultList();
if (Objects.nonNull(instructors))
instructors.forEach(instructor -> {
IdProof idProof = instructor.getIdProof();
Set<Vehicle> vehicles = instructor.getVehicles();
Set<Student> students = instructor.getStudents();
System.out.println(instructor);
System.out.println(idProof);
if (Objects.nonNull(vehicles))
vehicles.forEach(v -> System.out.println(v.getVehicleNumber()));
if (Objects.nonNull(students))
students.forEach(s -> System.out.println(s.getName()));
});
});
}
Every NamedAttributeNode can specify a subgraph.
#Entity
#Table(name="ORDERS")
#NamedEntityGraph(
name = "graph.Order.details",
attributeNodes = {
#NamedAttributeNode(value = "details", subgraph = "graph.OrderDetail.product")
},
subgraphs = {
#NamedSubgraph(name = "graph.OrderDetail.product", attributeNodes = #NamedAttributeNode(value = "product", subgraph = "graph.Product.productLine")),
#NamedSubgraph(name = "graph.Product.productLine", attributeNodes = #NamedAttributeNode("line"))
}
)
Related
I would like to use the EntityGraph Feature because of the known n+1 Problem. I have the following Entities structure:
#Entity
#Table(name = "customer")
public class Customer extends Person {
#Column(name = "foo")
public String foo;
#Column(name = "bar")
public String bar;
}
#Entity
#Table(name = "person")
public class Person {
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "car.id")
public Car car;
#Embedded
public Key key;
}
#Entity
#Table(name = "car")
public class Car {
#Column(name = "a")
public String a;
#Column(name = "b")
public String b;
}
#Embeddable
public class Key
{
#Column(name = "key_id")
public Long keyId;
#Column(name = "key_color")
public String keyColor;
}
Now I want to use a NamedEntityGraph. As far as I understand with "#NamedEntityGraph(name = "getCustomer", includeAllAttributes=true)" it should work but it doesnt.
The NamedEntityGraph call with
em.createQuery(criteriaQuery).setHint("javax.persistence.fetchgraph", em.getEntityGraph("getCustomer")).getResultList()
returns the amount of Customers in the database but all Attributes including car and the Embedded Attribute key is always null.
Do I have to use subgraphs? I tried to declare the NamedEntityGraph on Customer class also on Person class. It makes no difference.
EDIT:
After struggling a long time with this problem, i tried to break down it to the lowest level with these two entities
#Entity
#Table(name = "publication")
#NamedEntityGraph(name = "graph.Publication.articles",
attributeNodes = #NamedAttributeNode("articles"))
public class Publication {
#Id
private String publicationId;
private String name;
private String category;
#OneToMany(cascade = CascadeType.ALL)
#JoinColumn(name = "publicationId")
private List<Article> articles;
#Entity
#Table(name = "article")
public class Article {
#Id
private String articleId;
private String title;
private String publicationId;
}
If i create a query i can see further more than one query in the postgres log.
EntityGraph<?> entityGraph = em.getEntityGraph("graph.Publication.articles");
List resultList = em.createQuery("SELECT x FROM Publication x").setHint("javax.persistence.fetchgraph", entityGraph).getResultList();
Different queries for all publications
SELECT ARTICLEID, publicationId, TITLE FROM article WHERE (publicationId = $1) parameters: $1 = 'publication_1'
SELECT ARTICLEID, publicationId, TITLE FROM article WHERE (publicationId = $1) parameters: $1 = 'publication_2'
But I would only have expected one query with a join here.
Finally I found a solution for my problem. I refer to the edited part of my question.
I found this page which describes very well how to use batch query hints to improve performance.
http://java-persistence-performance.blogspot.com/2010/08/batch-fetching-optimizing-object-graph.html?m=1
For my example I don't need the entitygraph anymore. The query should created like this
List resultList = em.createQuery("SELECT x FROM Publication x").setHint("eclipselink.batch", "x.articles").getResultList();
I have the following entities
RegisteredProgram
#Data
#NoArgsConstructor
#Entity
#EntityListeners(RegisteredProgramAuditListener.class)
public class RegisteredProgram extends Auditable<String> {
#OneToMany(mappedBy = "registeredProgram", cascade = CascadeType.ALL)
#JsonBackReference
private List<Trainer> trainerList;
#OneToMany(mappedBy = "registeredProgram", cascade = CascadeType.ALL)
#JsonBackReference
private List<Official> officialList;
}
Trainer
#Data
#NoArgsConstructor
#EntityListeners(TrainerAuditListener.class)
#Entity
public class Trainer extends Auditable<String> {
#ManyToOne
#JoinColumn(name = "REGISTERED_PROGRAM_ID", nullable = false)
#JsonManagedReference
private RegisteredProgram registeredProgram;
#Type(type = "yes_no")
private Boolean isDeleted = false;
}
Official
#Data
#NoArgsConstructor
#EntityListeners(OfficialAuditListener.class)
#Entity
public class Official extends Auditable<String> {
#ManyToOne
#JoinColumn(name = "REGISTERED_PROGRAM_ID", nullable = false)
#JsonManagedReference
private RegisteredProgram registeredProgram;
#Type(type = "yes_no")
private Boolean isDeleted = false;
}
Basically I have entities with many to one relationship with RegisteredProgram, (Trainer-RegisteredProgram, Official-RegisteredProgram). Now I have a service which already achieves my requirement, to fetch a registered program by id and I should only include all the Trainer and Official with isDeleted false. See the service below:
Service
#Override
public RegisteredProgramRequestDto getRegisteredProgramDto(Long id) {
RegisteredProgram registeredProgram = registeredProgramRepository.getOne(id);
RegisteredProgramRequestDto registeredProgramRequestDto = programRegistrationMapper
.registeredProgramToRequestDto(registeredProgram);
registeredProgramRequestDto.setOfficialDtoList(
registeredProgramRequestDto.getOfficialDtoList()
.stream()
.filter(officialDto -> !officialDto.getIsDeleted())
.collect(Collectors.toList())
);
registeredProgramRequestDto.setTrainerDtoList(
registeredProgramRequestDto.getTrainerDtoList()
.stream()
.filter(trainerDto -> !trainerDto.getIsDeleted())
.collect(Collectors.toList())
);
return registeredProgramRequestDto;
}
Now, I tried to use #Query and #EntityGraph so I can be able to get the desired output using only a single query.
Repository
#Repository
public interface RegisteredProgramRepository extends JpaRepository<RegisteredProgram, Long>, QuerydslPredicateExecutor<RegisteredProgram> {
#Query("select rp from RegisteredProgram rp join rp.officialList rpos join rp.trainerList rpts where rp.id = :id and rpos.isDeleted = false and rpts.isDeleted = false")
#EntityGraph(attributePaths = {"officialList", "trainerList"}, type = EntityGraph.EntityGraphType.LOAD)
RegisteredProgram getByIdNotDeleted(#Param("id") Long id);
}
Updated Service
#Override
public RegisteredProgramRequestDto getRegisteredProgramDto(Long id) {
RegisteredProgram registeredProgram = registeredProgramRepository.getByIdNotDeleted(id);
return programRegistrationMapper
.registeredProgramToRequestDto(registeredProgram);
}
But after implementing it, i am encountering the error below:
org.hibernate.loader.MultipleBagFetchException: cannot simultaneously fetch multiple bags: [com.tesda8.region8.program.registration.model.entities.RegisteredProgram.officialList, com.tesda8.region8.program.registration.model.entities.RegisteredProgram.trainerList]
I already searched through stackoverflow and bumped into this but I still can't get my query to execute properly. Any ideas on how should I approach this?
The regular fix for solving MultipleBagFetchException is change List typed fields on Set typed, like this:
...
public class RegisteredProgram extends Auditable<String> {
#OneToMany(mappedBy = "registeredProgram", cascade = CascadeType.ALL)
#JsonBackReference
private Set<Trainer> trainerList = new HashSet<>();
#OneToMany(mappedBy = "registeredProgram", cascade = CascadeType.ALL)
#JsonBackReference
private Set<Official> officialList = new HashSet<>();
...
}
For more details see: https://thorben-janssen.com/hibernate-tips-how-to-avoid-hibernates-multiplebagfetchexception/
Note: Remember about equals and hashcode for Set data structure and avoiding Lombok & Hibernate pitfalls(https://thorben-janssen.com/lombok-hibernate-how-to-avoid-common-pitfalls/). Please pay attention for 'Avoid #Data' topic, because I see you are using that combination, that combination can produce unexpected behavior!
I was trying to use EntityGraph to load Entity.
I have Employee entity with all associations mapped as LAZY loading associations. At one use case, I want to fetch Employee with its single value associations eagerly, so I was trying to use EntityGraph for this.
[I know Eager associations can not be loaded as LAZY using EntityGraph, but in my case, I am trying to load LAZY association EAGERly using Entity graph, which I think should work]
Employee
#NamedEntityGraphs({
#NamedEntityGraph(name = "Employee.graph1", attributeNodes = {
#NamedAttributeNode(value = "firstName") ,
#NamedAttributeNode(value = "lastName"),
#NamedAttributeNode(value = "manager", subgraph = "managerSubGraph"),
#NamedAttributeNode(value = "department")
}, subgraphs = {
#NamedSubgraph(name = "managerSubGraph", attributeNodes = {
#NamedAttributeNode(value = "department"),
#NamedAttributeNode(value = "manager")}
)}
)}
)
public class Employee {
#Column(name = "id")
#Basic
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#Column(name = "first_name")
private String firstName;
#Column(name = "last_name")
private String lastName;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "department_id")
private Department department;
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "manager_id")
private Employee manager;
}
Department
#Entity
#Table(name = "department")
public class Department {
#Column(name = "id")
#Basic
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#Basic
#Column(name = "name")
private String name;
#OneToMany(fetch = FetchType.LAZY)
#JoinColumn(name = "department_id")
private Set<Employee> employees;
public Department(String name) {
this.name = name;
}
}
Caller
#Transactional(readOnly = true)
public void entityGraphDemo() {
final EntityGraph<?> entityGraph = em.getEntityGraph("Employee.graph1");
Map<String, Object> properties = new HashMap<>();
properties.put("javax.persistence.fetchgraph", entityGraph);
final Employee employee = em.find(Employee.class, 2, properties);
// all of these get loaded EAGERly with no issue
final PersistenceUnitUtil persistenceUnitUtil = entityManagerFactory.getPersistenceUnitUtil();
System.out.println("isLoaded(employee, \"department\") = " + persistenceUnitUtil.isLoaded(employee, "department"));
System.out.println("isLoaded(employee, \"manager\") = " + persistenceUnitUtil.isLoaded(employee, "manager"));
final Employee employeeManager = employee.getManager();
// these return false, means these nested associations Employee -> Manager -> Department, Employee -> Manager -> Manager are not loaded (which should be)
System.out.println("isLoaded(employeeManager, \"department\") = " + persistenceUnitUtil.isLoaded(employeeManager, "department"));
System.out.println("isLoaded(employeeManager, \"manager\") = " + persistenceUnitUtil.isLoaded(employeeManager, "manager"));
}
First level of entity graph get loaded correctly as expecred, but second level of graph is not getting loaded as expected.
Issue is, department and manager associations on employeeManager (Second level subgraph) are not getting loaded eagerly.
Last two Syso prints false. Data is available for both associations and when I call getters on employeeManager instance, hibernate execute two sql to load manager and department, so both associations seem to load lazily.
So, is there limitations on levels subgraph supported by hibernate? JPA spec does not define any such a limitation. Or am I missing something here?
I have an entity BlocRecord having a composite code BlocRecordId, and in its composite code there is an #Embedded (relation code ManyToOne) pointing to another entiy Record and want to Audit the entity BlocRecord.
The entity BlocRecord
#Entity
#Inheritance(strategy = InheritanceType.JOINED)
#Table(name = "blocRecord")
#Access(value = AccessType.FIELD)
#Audited
public class BlocRecord {
#EmbeddedId
private BlocRecordId blocRecordId = new BlocRecordId();
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumns({
#JoinColumn(name = "record_identifier_", referencedColumnName = "identifier_", unique = false, nullable = false),
#JoinColumn(name = "record_recordType_", referencedColumnName = "recordType_", unique = false, nullable = false)})
#MapsId("record")
private Record record;
...
}
The id class BlocRecordID
#Embeddable
public class BlocRecordId implements Serializable {
#Embedded
private RecordId record;
#Column(name = "source_")
String source ;
#Column(name = "messageType_")
String messageType ;
The entity Record
#Entity
#Inheritance(strategy = InheritanceType.JOINED)
#Table(name = "records")
#Access(value = AccessType.FIELD)
#Audited
public class Record {
#EmbeddedId
private RecordId recordId = new RecordId();
#OneToMany(targetEntity = BlocRecord.class, fetch = FetchType.LAZY, mappedBy = "record")
private Set<BlocRecord> blocRecord = new java.util.HashSet<>();
...
}
The idClass of the entity Record
#Embeddable
public class RecordId implements Serializable{
#Column(name = "identifier_")
String identifier ;
#Column(name = "recordType_")
String recordType ;
}
Hibernate-envers fails when trying to generate the metadata of the field record in the embeddable BlocRecordId, the The flowing exception is thrown
org.hibernate.MappingException: Type not supported: org.hibernate.type.ComponentType
at org.hibernate.envers.configuration.internal.metadata.IdMetadataGenerator.addIdProperties(IdMetadataGenerator.java:121)
at org.hibernate.envers.configuration.internal.metadata.IdMetadataGenerator.addId(IdMetadataGenerator.java:230)
at org.hibernate.envers.configuration.internal.metadata.AuditMetadataGenerator.generateFirstPass(AuditMetadataGenerator.java:642)
at org.hibernate.envers.configuration.internal.EntitiesConfigurator.configure(EntitiesConfigurator.java:95)
at org.hibernate.envers.boot.internal.EnversServiceImpl.doInitialize(EnversServiceImpl.java:154)
at org.hibernate.envers.boot.internal.EnversServiceImpl.initialize(EnversServiceImpl.java:118)
at org.hibernate.envers.boot.internal.AdditionalJaxbMappingProducerImpl.produceAdditionalMappings(AdditionalJaxbMappingProducerImpl.java:99)
at org.hibernate.boot.model.process.spi.MetadataBuildingProcess.complete(MetadataBuildingProcess.java:288)
at org.hibernate.boot.model.process.spi.MetadataBuildingProcess.build(MetadataBuildingProcess.java:83)
at org.hibernate.boot.internal.MetadataBuilderImpl.build(MetadataBuilderImpl.java:417)
at org.hibernate.boot.internal.MetadataBuilderImpl.build(MetadataBuilderImpl.java:86)
at org.hibernate.boot.MetadataSources.buildMetadata(MetadataSources.java:179)
Do you have any idea how to resolve the issue ?
Thanks
At the moment, Envers does not support the idea of nesting an embeddable inside an embeddable when we map the identifier columns like your example illustrates. The only valid mappings that Envers presently does support is if the attribute in the embeddable is a #ManyToOne or a #Basic type.
You can work around this problem but it involves being a bit more explicit and not using RecordId. What I mean is rewrite BlocRecordId to be the following:
#Embeddable
public class BlocRecordId implements Serializable {
#Column(name = "identifier_")
String identifier;
#Column(name = "recordType_")
String recordType;
#Column(name = "source_")
String source;
#Column(name = "messageType_")
String messageType;
#Transient
private RecordId recordId;
/** Helper method to assign the values from an existing RecordId */
public void setRecordId(RecordId recordId) {
this.identifier = recordId.getIdentifier();
this.recordType = recordId.getRecordType();
}
/** Helper method to get the RecordId, caching it to avoid multiple allocations */
public RecordId getRecordId() {
if ( recordId == null ) {
this.recordId = new RecordId( identifier, recordType );
}
return this.recordId;
}
}
I agree this is less than ideal but it does at least work around the current limitation of the code. I have gone added and added HHH-13361 as an open issue to support this. You're welcomed to contribute if you wish or I'll work in getting this supported for Envers 6.0.
I’m using JPA 2.0, Hibernate 4.1.0.Final, and MySQL 5.5.37. I have the following entities
#Entity
#Table(name = "user_subscription",
uniqueConstraints = { #UniqueConstraint(columnNames = { "USER_ID", “SUBSCRIPTION_ID" }) }
)
public class UserSubscription
{
#Id
#Column(name = "ID")
#GeneratedValue(generator = "uuid-strategy")
private String id;
#ManyToOne
#JoinColumn(name = "USER_ID", nullable = false, updatable = true)
private User user;
#ManyToOne
#JoinColumn(name = “SUBSCRIPTION_ID", nullable = false, updatable = true)
private Subscription subscription;
and
#Entity
#Table(name = "Subscription")
public class Subscription implements Serializable
{
#Id
#Column(name = "ID")
#GeneratedValue(generator = "uuid-strategy")
private String id;
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "PRODUCT_ID")
#NotNull
private Product product;
Without altering the entities, how do I construct a JPA CriteriaBuilder query in which I look for User entities that do not have a particular Subscription entity “A”, but have other subscription entities that match the same product as entity “A”? I have tried this to no avail …
public List<User> findUsersWithSubscriptions(Subscription Subscription)
{
final List<User> results = new ArrayList<User>();
final CriteriaBuilder builder = m_entityManager.getCriteriaBuilder();
final CriteriaQuery<UserSubscription> criteria = builder.createQuery(UserSubscription.class);
final Root<UserSubscription> root = criteria.from(UserSubscription.class);
Join<UserSubscription, Subscription> SubscriptionRoot = root.join(UserSubscription_.subscription);
criteria.select(root).where(builder.equal(root.get(UserSubscription_.Subscription).get(Subscription_.product),subscription.getProduct()),
builder.notEqual(root.get(UserSubscription_.subscription), subscription));
I thought if I could build a SetJoin from the user -> subscription entities, I could say something like “not.in”, but I’m not sure how to do that given the constraints.
Edit: This is the SQL produced by Vlad's post:
SELECT user1_.id AS id97_,
user1_.creator_id AS CREATOR15_97_,
user1_.dob AS DOB97_,
user1_.enabled AS ENABLED97_,
user1_.expiration AS EXPIRATION97_,
user1_.first_name AS first5_97_,
user1_.grade_id AS GRADE16_97_,
user1_.incorrect_logins AS INCORRECT6_97_,
user1_.last_name AS last7_97_,
user1_.middle_name AS middle8_97_,
user1_.organization_id AS organiz17_97_,
user1_.password AS password97_,
user1_.reset_state AS RESET10_97_,
user1_.salutation AS salutation97_,
user1_.temporary_password AS temporary12_97_,
user1_.url AS url97_,
user1_.user_demographic_info_id AS USER18_97_,
user1_.user_name AS user14_97_
FROM sb_user_subscription subscription0_
INNER JOIN sb_user user1_
ON subscription0_.user_id = user1_.id
INNER JOIN cb_subscription subscription2_
ON subscription0_.subscription_id = subscription2_.id
INNER JOIN sb_product product3_
ON subscription2_.product_id = product3_.id
AND product3_.id = ?
AND subscription2_.id <>?
Check this query:
final CriteriaBuilder builder = m_entityManager.getCriteriaBuilder();
final CriteriaQuery<User> criteria = builder.createQuery(User.class);
final Root<UserSubscription> root = criteria.from(UserSubscription.class);
Join<UserSubscription, User> userJoin = root.join(UserSubscription_.user);
Join<UserSubscription, Subscription> subscriptionJoin = root.join(UserSubscription_.subscription);
Join<Subscription, Product> productJoin = subscriptionJoin.join(Subscription_.product);
criteria
.select(userJoin)
.where(cb.and(
builder.equal(productJoin, subscription.getProduct()),
builder.notEqual(subscriptionJoin, subscription)
);
return entityManager.createQuery(criteria).getResultList();
The output query looks fine and it should select Users with a given subscription.product and with a different subscription than the product parent's one.
You could try it in your SQL console, but it looks fine and it validates the initial requirement:
that do not have a particular Subscription entity “A”, but have other
subscription entities that match the same product as entity “A”