JPA EntityGraph is not working; Always loading related collections - java

I am trying to get named entity graphs to work properly. Basically, I have a Customer who can have zero or more Addresses. When I query to get a list of Customers, I want the customer's fields, but not the associated Addresses. When I query for a particular Customer, I want its fields and all of the associated Addresses. So, I've been trying to use named entity graphs because they seem to address this situation. I created one to get basic information, and another that gets everything. Unfortunately, when I use either graph, I still always get everything. I am sure that I am making a simple mistake, but I cannot seem to find it. I'd appreciate your help to figure out my error. The relevant code follows...
Thank you for your time and advise,
Mike
/////////////////////////////////////////////////////////
// ENTITIES
/////////////////////////////////////////////////////////
#Entity
#Table(name = "customers")
#XmlRootElement
#NamedQueries({
#NamedQuery(name = "Customer.findAll", query = "SELECT c FROM Customer c")})
#NamedEntityGraphs({
#NamedEntityGraph(
name="previewCustomerEntityGraph",
attributeNodes={
#NamedAttributeNode("id"),
#NamedAttributeNode("displayAs"),
#NamedAttributeNode("rowVersion")
}
),
#NamedEntityGraph(
name="fullCustomerEntityGraph",
attributeNodes={
#NamedAttributeNode("id"),
#NamedAttributeNode("displayAs"),
#NamedAttributeNode("rowVersion"),
#NamedAttributeNode(value = "addressCollection", subgraph = "addressCollection")
},
subgraphs = #NamedSubgraph(
name = "addressCollection",
attributeNodes = {
#NamedAttributeNode("id"),
#NamedAttributeNode("displayAs"),
#NamedAttributeNode("rowVersion")
}
)
)
})
public class Customer implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#Column(name = "Id")
#XmlAttribute(required=true)
private Long id;
#Basic(optional = false)
#NotNull
#Size(min = 1, max = 256)
#Column(name = "DisplayAs")
#XmlElement(required=true, nillable=false)
private String displayAs;
#Basic(optional = true)
#Size(max = 256)
#Column(name = "Name")
#XmlElement(required=true, nillable=true)
private String name;
#Basic(optional = false)
#NotNull
#XmlElement(required=true, nillable=false)
#Column(name = "RowVersion")
#Version
private Timestamp rowVersion;
#OneToMany(mappedBy = "customerId",cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Collection<Address> addressCollection;
//#XmlTransient
public Collection<Address> getAddressCollection() {
return addressCollection;
}
public void setAddressCollection(Collection<Address> addressCollection) {
this.addressCollection = addressCollection;
}
// Object methods have been removed to save space
}
#Entity
#Table(name = "addresses")
#XmlRootElement
#NamedQueries({
#NamedQuery(name = "Address.findAll", query = "SELECT a FROM Address a")})
public class Address implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#XmlAttribute(required=true)
#Column(name = "Id")
private Long id;
#Basic(optional = false)
#NotNull
#Column(name = "RowVersion")
#Temporal(TemporalType.TIMESTAMP)
#XmlElement(required=true, nillable=true)
private Date rowVersion;
#Basic(optional = false)
#NotNull
#Size(min = 1, max = 256)
#Column(name = "DisplayAs")
#XmlElement(required=true, nillable=true)
private String displayAs;
#JoinColumn(name = "CustomerId", referencedColumnName = "Id")
#ManyToOne
private Customer customerId;
// Object methods have been removed to save space
}
/////////////////////////////////////////////////////////
// QUERIES
/////////////////////////////////////////////////////////
protected T find(Object id) {
// Filter to only return entities belonging to the tenant.
EntityGraph eg = getEntityManager().getEntityGraph("fullCustomerEntityGraph");
javax.persistence.criteria.CriteriaBuilder cb = getEntityManager().getCriteriaBuilder();
javax.persistence.criteria.CriteriaQuery<T> cq = cb.createQuery(entityClass);
javax.persistence.criteria.Root<T> from = cq.from(entityClass);
javax.persistence.criteria.CriteriaQuery<T> select = cq.select(from);
cq.where(cb.and(
cb.equal(from.<T>get("id"), id)
));
javax.persistence.TypedQuery<T> tq = getEntityManager().createQuery(select);
tq.setHint("javax.persistence.fetchgraph", eg);
return getSingleResultOrNull(tq);
}
protected List<T> findAll() {
// Filter to only return entities belonging to the tenant.
EntityGraph eg = getEntityManager().getEntityGraph("previewCustomerEntityGraph");
javax.persistence.criteria.CriteriaBuilder cb = getEntityManager().getCriteriaBuilder();
javax.persistence.criteria.CriteriaQuery<T> cq = cb.createQuery(entityClass);
javax.persistence.criteria.Root<T> from = cq.from(entityClass);
javax.persistence.criteria.CriteriaQuery<T> select = cq.select(from);
javax.persistence.TypedQuery<T> tq = getEntityManager().createQuery(select);
tq.setHint("javax.persistence.fetchgraph", eg);
return tq.getResultList();
}
protected T getSingleResultOrNull(javax.persistence.TypedQuery<T> query) {
query.setMaxResults(1);
List<T> list = query.getResultList();
if (list.isEmpty()) {
return null;
}
return list.get(0);
}

The serializer is calling getAddressCollection.
Note that #XmlTransient is commented on address list. Place this annotation back and try again.
In this case auto serialization will allways avoid the list. If you need to return the full graph somewhere else I recommend writing your own serialization logic or create a Value Object to hold exactly what you need and let serializer do the job.

Related

Duplicate parent and child data in jpql (JPA)

I have a Production class and ProductionDetail entity class where Id of Production table is a foreignKey as production_id in ProductionDetail entity class so my both entity class with mapping has given bellow
Production Entity Class:
#Entity
#Table(name = "tbl_production")
#XmlRootElement
public class TblProduction implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Basic(optional = false)
#NotNull
#Size(min = 1, max = 45)
#Column(name = "ID")
private String id;
#Column(name = "PRODUCTION_DATE")
#Temporal(TemporalType.DATE)
private Date productionDate;
#Column(name = "START_DATETIME")
#Temporal(TemporalType.TIMESTAMP)
private Date startDatetime;
#Column(name = "END_DATETIME")
#Temporal(TemporalType.TIMESTAMP)
private Date endDatetime;
#Size(max = 45)
#Column(name = "MACHINE_UUID")
private String machineUuid;
**Relation with Production Details Table**
#OneToMany(mappedBy = "production")
#XmlElement(name = "productionDetails")
private List<TblProductionDetail> productionDetailList;
#PrimaryKeyJoinColumn(name = "MACHINE_UUID", referencedColumnName = "UUID")
#ManyToOne(fetch = FetchType.LAZY)
private MstMachine mstMachine;
#XmlTransient
public MstMachine getMstMachine() {
return this.mstMachine;
}
}
Production Details Entity Class:
#Entity
#Table(name = "tbl_production_detail")
#XmlRootElement
public class TblProductionDetail implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Basic(optional = false)
#NotNull
#Size(min = 1, max = 45)
#Column(name = "ID")
private String id;
#Size(max = 45)
#Column(name = "COMPONENT_ID")
private String componentId;
#Size(max = 45)
#Column(name = "PRODUCTION_ID")
private String productionId;
**Relation with Production Class**
#ManyToOne
#JoinColumn(name = "PRODUCTION_ID", referencedColumnName = "ID", insertable = false,
updatable = false)
private TblProduction production;
#Transient
public String componentCode;
#Transient
public String componentName;
#PrimaryKeyJoinColumn(name = "COMPONENT_ID", referencedColumnName = "ID")
#ManyToOne(fetch = FetchType.LAZY)
private MstComponent mstComponent;
#XmlTransient
public MstComponent getMstComponent() {
return this.mstComponent;
}
public void setMstComponent(MstComponent mstComponent) {
this.mstComponent = mstComponent;
}
}
ParentList Class:
public class TblProductionList {
private List<TblProduction> productionList;
public TblProductionList() {
productionList = new ArrayList<>();
}
public List<TblProduction> getTblProductions() {
return productionList;
}
public void setTblProductions(List<TblProduction> tblProductionList) {
this.productionList = tblProductionList;
}
}
BusinessLogic(DAO Class):
public TblProductionList getJson() {
TblProductionList response = new TblProductionList();
StringBuilder retrieveQuery = new StringBuilder();
retrieveQuery.append(" SELECT prod FROM TblProduction prod ");
retrieveQuery.append(" JOIN FETCH prod.productionDetailList ");
retrieveQuery.append(" WHERE prod.endDatetime IS NULL ");
retrieveQuery.append(" AND prod.machineUuid IS NOT NULL ");
retrieveQuery.append(" AND NOT EXISTS (SELECT tpt FROM
TblProductionThset tpt WHERE prod.id = tpt.productionId) ");
retrieveQuery.append(" AND EXISTS (SELECT mmfd FROM
MstMachineFileDef mmfd WHERE prod.machineUuid = mmfd.machineUuid
AND mmfd.hasThreshold = 1) ");
retrieveQuery.append(" ORDER BY prod.id ");
Query query =
entityManager.createQuery(retrieveQuery.toString());
List thresholdList = query.getResultList();
response.setTblProductions(thresholdList);
return response;
}
According to the database I am getting expected master child data like below
After designing this entity class I am expecting that I will get 3 master records where each record has 2 detail records. But I am getting 6 duplicate master records with 12 child records. Can anyone suggest to me please where is my code became wrong and why this situation raised? please check the JSON data that I am getting from API.
change your array list to hash set then records are not duplicate.

How to use In clause in JPQL query with subquery

I have a entity classes as below
ExportMO.java
#Entity
#Table(name = "DATAEXPORTTEMPLATE")
#NamedQueries({
#NamedQuery(name = "ExportTemplateMO.getTemplatesByUser", query = "SELECT DISTINCT template FROM ExportTemplateMO template " +
"join template.fields exportFields WHERE template.eId IN (SELECT S1.dcid FROM SchoolMO S1 join S1.fields S2 WHERE S2.dcid = :userDCID)")
})
public class ExportMO implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Column(name = "DATATEMPLATEID", unique = true, nullable = false)
private Long templateId;
#Column(name = "EID", nullable = true)
private Long eId;
#OneToMany(fetch=FetchType.EAGER, mappedBy="templateMO", cascade={CascadeType.ALL}, orphanRemoval=true)
private Set<ExportFieldMO> fields;
}
SchoolMO.java
#Entity
#Table(name = "SCHOOLSTAFF")
public class SchoolMO implements Serializable {
#Id
#Column(name = "DCID", unique = true, nullable = false)
private Long dcid;
#Column(name = "ID", unique = true)
private Long id;
#Column(name = "USERS_DCID", unique = false, nullable = false)
#JoinColumn(name="USERS_DCID",referencedColumnName="USERS_DCID",nullable=false)
private Long users_dcid;
#OneToMany(fetch=FetchType.EAGER, mappedBy="users_dcid", cascade={CascadeType.ALL}, orphanRemoval=true)
private Set<SchoolStaffLeanMO> fields;
}
Can someone help me how to use this using the IN clause for subquery.
If i try to use the same '=' instead of 'IN' i am able to get the results.
Thanks in advance.

Deleting entity, which is element of collection and has collection of another entities in Hibernate

I have entity Ad:
#Entity
#NamedQueries({
#NamedQuery(name = "getAllAds",
query = "from Ad"),
#NamedQuery(name = "deleteById",
query = "delete from Ad where id = :id")
})
#FieldMatch(first = "initLanguage",second = "resultLanguage", message = "Languages must be different")
#Table(name = "AD_TEST")
public class Ad implements Serializable{
/**
* Version of this class in production
*/
private static final long serialVersionUID = 1L;
#Id
#SequenceGenerator(name = "standard", initialValue = 1)
#GeneratedValue(generator = "standard", strategy =GenerationType.SEQUENCE)
#Column(name = "AD_ID")
private long id;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "CLIENT",nullable = false)
private Client client;
#NotBlank
#Column(name = "AD_NAME", nullable = false)
private String name;
#NotBlank
#Column(name = "AD_DESC",nullable = false,length = 1000)
#Lob
#Size(min = 0, max = 1000)
private String description;
#Column(name = "AD_COUNTRY", nullable = false)
private String country;
#Column(name = "AD_CITY", nullable = false)
private String city;
#NotNull
/**
* Добавить проверку валидности даты
*/
#DateTimeFormat(iso = ISO.DATE,pattern = "dd.MM.yyyy")
#Column(name = "AD_END_DATE",nullable = false)
private LocalDate endDate;
#NotNull
#Column(name = "AD_INIT_LANGUAGE",nullable = false)
#Enumerated(EnumType.STRING)
private Language initLanguage;
#NotNull
#Column(name = "AD_RESULT_LANGUAGE",nullable = false)
#Enumerated(EnumType.STRING)
private Language resultLanguage;
#NotNull
#Column(name = "AD_TRANSLATE_TYPE",nullable = false)
#Enumerated(EnumType.STRING)
private TranslateType translateType;
#Lob
#Column(name = "AD_FILE")
private byte[] file;
#NotNull
#Column(name = "AD_COST",nullable = false,precision = 2)
private double cost;
#NotNull
#Column(name = "AD_CURRENCY",nullable = false)
#Enumerated(EnumType.STRING)
private Currency currency;
#Column(name = "AD_CREATING_DATE",nullable = false)
private LocalDateTime creationDateTime;
#Column(name = "AD_STATUS",nullable = false)
#Enumerated(EnumType.STRING)
private AdStatus status;
#OnDelete(action = OnDeleteAction.CASCADE)
#OneToMany(fetch = FetchType.EAGER,orphanRemoval = true,mappedBy = "ad")
#Cascade(CascadeType.ALL)
private List<ResponsedAd> responsedAds = new ArrayList<>();
public Ad(){}
/*Getters and setters, hashCode and equals*/
Ad is element of collection in Client:
#Entity
#NamedQueries({
#NamedQuery(name = "clientByEmail",
query = "from Client client where client.email = :email")
})
#Table(name = "CLIENT_TEST")
#PrimaryKeyJoinColumn(name= "client_id")
public class Client extends User{
/**
* Version of this class in production
*/
private static final long serialVersionUID = 1L;
#OneToMany(fetch = FetchType.EAGER,orphanRemoval = true,mappedBy = "client")
#Cascade(CascadeType.ALL)
public List<Ad> ads = new ArrayList<>();
#OneToMany(fetch = FetchType.EAGER,orphanRemoval = true,mappedBy = "client")
#Cascade(CascadeType.ALL)
private List<ResponsedAd> responsedAds = new ArrayList<>();
public Client(){}
Ad.class and Client.class have collection of ResponseAd objects:
#Entity
#Table(name = "RESPONSED_AD_TEST")
#NamedQueries({
#NamedQuery(name = "responsedAdsByAd",query="from ResponsedAd where ad = :ad")
})
#Component
public class ResponsedAd {
#Id
#SequenceGenerator(name = "standard", initialValue = 1)
#GeneratedValue(generator = "standard", strategy =GenerationType.SEQUENCE)
private long id;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "AD",nullable = false)
private Ad ad;
#Column(nullable = false)
private LocalDateTime dateTimeOfResponse;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "CLIENT")
private Client client;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "TRANSLATOR",nullable = false)
private Translator translator;
#Column(nullable = false)
#Enumerated(EnumType.STRING)
private ResponsedAdStatus status;
public ResponsedAd(){}
I want that after deleting Ad object, all ResponseAd objects from collection were deleted too.
For this I have:
public void deleteById(long id){
Ad ad = get(id);
CopyOnWriteArrayList<ResponsedAd> list = new CopyOnWriteArrayList<>(ad.getResponsedAds());
list.forEach(rad->{
Translator translator = rad.getTranslator();
translator.removeResponsedAd(rad);
ad.removeResponsedAd(rad);
Client client = rad.getClient();
client.removeResponsedAd(rad);
});
Client client = ad.getClient();
client.removeAd(ad);
}
Say me please, how can I do this right? I have very a lot of relationships and when I try do this, I get error
HTTP Status 500 - Request processing failed; nested exception is
org.springframework.dao.InvalidDataAccessApiUsageException:
deleted object would be re-saved by cascade (remove deleted object from associations):
[ua.translate.model.ad.ResponsedAd#102]; nested exception is org.hibernate.ObjectDeletedException:
deleted object would be re-saved by cascade (remove deleted object from associations): [ua.translate.model.ad.ResponsedAd#102]
First of all,
You don't need to use #NotNull if you have nullable = false already declared in #Column(nullable = false).
Second,
What you're trying to do is doing operations in Cascade. Add this cascade = CascadeType.ALL or maybe cascade = CascadeType.REMOVE to your #ManyToOne tags and it should work.
Use this as a reference: JPA #ManyToOne with CascadeType.ALL
My Client object: Client client = rad.getClient(); have two responsedAd object in collection with the same id, therefore even after deleting this responsedAd object: client.removeResponsedAd(rad); client object has one more. And now main question is why this client have two objects with the same id.

JPA related entities persist

I've designed some tables for my application where I've placed something like that:
product productversion
--- ---
productId productVersionId
productVersionId (ref) productId (ref)
name length
height
where we have relation one to many. I just need some base product with parameters which can change and versioned parameters so I can watch it's history. Now... I have problem with Persisting those objects. I've tried multiple ways, this is oldest one:
Product p = new Product();
p.name = "some name";
Productversion pv = new Productversion();
pv.length = 10;
pv.height = 20;
p.productversionId = pv;
But while trying to persist this object I've getting error that productId cannot be null and it refers to field productId inside Productversion object. How can I do this?
[update]
Some more information about my problem:
public class Product implements Serializable {
#JoinColumn(name = "productversionId", referencedColumnName = "productversionId")
#ManyToOne(optional = true, cascade = CascadeType.PERSIST)
private Productversion productversionId;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "productId")
private Collection<Productversion> productversionCollection;
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#Column(name = "ProductId")
private Integer productId;
#Basic(optional = false)
#NotNull
#Size(min = 1, max = 255)
#Column(name = "Name")
private String name = null;
// well I believe construct and getter/setter not important now
And second Entity:
public class Productversion implements Serializable {
#OneToMany(cascade = CascadeType.ALL, mappedBy = "productversionId")
private Collection<Productiondata> productiondataCollection;
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#Column(name = "productversionId")
private Integer productversionId;
#Basic(optional = false)
#NotNull
#Column(name = "parameter")
private float weight;
And the way I create new object (just have no idea how can I do this):
public Product getNewProduct() {
if (newProduct == null) {
this.newProduct = new Product();
Productversion pv = new Productversion();
pv.setProductId(newProduct);
newProduct.setProductversionId(pv);
}
return newProduct;
}
And the way I'm trying to persist object:
public Boolean addEntry(Product productData) {
Boolean status = false;
DbConnector dc = new DbConnector();
EntityManager em = dc.getEntityManager();
try {
EntityTransaction et = em.getTransaction();
et.begin();
em.persist(productData);
et.commit();
status = true;
} finally {
em.close();
}
return status;
}
Seems like you missing on the set for the id field in your Product object.
Product p = new Product();
p.id = 1; <<<<<<-------------------
p.name = "some name";

Creating a Hibernate Criteria that can filter by referenced entity

I have an entity (PersonQuestionsEntity) that has a PersonEntity and QuestionEntity as its primary key. I use a composite key to reflect this relationship.
Now, I want to create a Criteria object that can do the following: Find all PersonQuestion entities for a given questionId and a person's age.
Here is my attempt at creating a Criteria for this:
Session session = getHibernateTemplate().getSessionFactory().openSession();
Criteria criteria = session.createCriteria(PersonQuestionsEntity.class);
criteria.add(Restrictions.eq("question.questionId", "87"));
criteria = criteria.createCriteria("person");
criteria.add(Restrictions.eq("age", 23));
criteria.setMaxResults(100);
List l = criteria.list();
The problem is that I get this error:
Caused by: java.sql.SQLException: ORA-00904: "PERSONENTI1_"."AGE": invalid identifier
In the generated SQL, it seems the person is referenced as PERSONENTI4, not PERSONENTI1. If I copy the SQL and run it with PERSONENTIT4 instead of PERSONENTI4, it works (sort of -- it seems to be doing a cartesian join of some sort).
Any clues as to what I might be doing wrong? I'm very new to using Hibernate.
PersonQuestionsEntity
#Entity
#IdClass(com.anonymous.model.PersonQuestionsKey.class)
#Table(name = "PERSON_QUESTIONS")
public class PersonQuestionsEntity implements Serializable
{
private static final long serialVersionUID = -8254277382097937813L;
#Id
#ManyToOne
#JoinColumn(name = "USER_NAME", nullable = false)
private PersonEntity person;
#Id
#ManyToOne
#JoinColumn(name = "QUESTION_ID", nullable = false)
private QuestionEntity question;
#Column(name = "THEIR_ANSWER")
private int theirAnswer;
}
PersonEntity
#Entity
#Table(name = "PERSON")
public class PersonEntity implements Serializable
{
private static final long serialVersionUID = -1699435979266209440L;
#Id
#Column(name = "USER_NAME", length = 20, nullable = false)
private String userName;
#Column(name = "USER_NAME_REAL", length = 20, nullable = false)
private String userNameReal;
#Column(name = "AGE", nullable = false)
private int age;
}
PersonQuestionsKey
#Embeddable
public class PersonQuestionsKey implements Serializable
{
private static final long serialVersionUID = -264160855961369405L;
#Id
#ManyToOne
#JoinColumn(name = "USER_NAME", nullable = false)
private PersonEntity person;
#Id
#ManyToOne
#JoinColumn(name = "QUESTION_ID", nullable = false)
private QuestionEntity question;
}
First of all, you don't really need the inner criteria, simply use:
Criteria criteria = session.createCriteria(PersonQuestionsEntity.class);
criteria.add(Restrictions.eq("question.questionId", "87"));
criteria.add(Restrictions.eq("person.age", 23));
criteria.setMaxResults(100);
List l = criteria.list();
Second (regarding the join type), in such cases, I usually go with HQL that produces inner joins. The HQL might look as follows:
from PersonQeustionEntity where question.questionId = :questionId
and person.age = :age
in the result Query object you can set the parameters questionId and age to your desired inputs.

Categories

Resources