Strange behaviour when requesting on a ManyToMany relation with composite PrimaryKey - java

I am trying to create a ManyToMany relation between DocumentModels, with an additionnal information in the relation (dosIndex)
#Entity
#Table(name = "T_DOCUMENT_MODELS_DMO")
public class TDocumentModelsDmo extends fr.axigate.nx.frontend.server.common.entity.ValidityPeriodEntity implements Serializable
{
#Id
#SequenceGenerator(name = "T_DOCUMENT_MODELS_DMO_DMOID_GENERATOR", sequenceName = "T_DMO_ID_SEQ")
#GeneratedValue(strategy = GenerationType.AUTO, generator = "T_DOCUMENT_MODELS_DMO_DMOID_GENERATOR")
#Column(name = "DMO_ID", precision = 22)
private Long dmoId;
//Other unrelated members, no reference to TjDocumentSourcesDos
//constructors, getters and setters without annotations
}
#Entity
#Table(name = "TJ_DOCUMENT_SOURCES_DOS")
public class TjDocumentSourcesDos implements Serializable
{
#Column(name = "DOS_INDEX", nullable = false, precision = 22)
private long dosIndex; //the additionnal info on the relation
#EmbeddedId
private TjDocumentSourcesDosPK id = new TjDocumentSourcesDosPK();
#ManyToOne
#MapsId("dosParentId")
#JoinColumn(name = "DOS_PARENT_ID", nullable = false, insertable = false, updatable = false)
private TDocumentModelsDmo TDocumentModelsDmoParent;
#ManyToOne
#MapsId("dosSourceId")
#JoinColumn(name = "DOS_SOURCE_ID", nullable = false, insertable = false, updatable = false)
private TDocumentModelsDmo TDocumentModelsDmoSource;
//constructors, getters and setters without annotations
}
#Embeddable
public class TjDocumentSourcesDosPK implements Serializable
{
#Column(name = "DOS_PARENT_ID", nullable = false, precision = 22)
private Long dosParentId;
#Column(name = "DOS_SOURCE_ID", nullable = false, precision = 22)
private Long dosSourceId;
//constructors, getters and setters without annotations
//hashCode and equals implemented
}
I can insert datas in both tables, but when I try to request it using an entityManager, i get something strange :
Query query = entityManager.createQuery("SELECT dos.TDocumentModelsDmoSource FROM TDocumentModelsDmo AS dmo, TjDocumentSourcesDos as dos WHERE dmo.dmoId = :modelId AND dos.TDocumentModelsDmoParent = dmo");
query.setParameter("modelId", someData);
ArrayList<TjDocumentSourcesDos> dosList = (ArrayList<TjDocumentSourcesDos>) query.getResultList();
will work, while the following will throw an exception : QuerySyntaxException: dos.TDocumentModelsDmoSource is not mapped
Query query = entityManager.createQuery("SELECT sources FROM TDocumentModelsDmo AS dmo, TjDocumentSourcesDos as dos, dos.TDocumentModelsDmoSource AS sources WHERE dmo.dmoId = :modelId AND dos.TDocumentModelsDmoParent = dmo");
query.setParameter("modelId", someData);
ArrayList<TjDocumentSourcesDos> dosList = (ArrayList<TjDocumentSourcesDos>) query.getResultList();
This prevents me from doing more complicated requests where I would use my sources models in the WHERE condition.
I tried adding a referencedColumnName = "DMO_ID" in both my JoinColumn annotations, but I still get the same error

Related

OneToMany relationship does not store entity field

When saving my entities, child entities that work through the #OneToMany relationship are not saved to their tables. I can’t understand what’s the matter.
Employee:
#Entity
#Table(name = "EMPLOYEE", schema = PUBLIC)
public class Employee {
private String name;
private String lastname;
#OneToMany(mappedBy = "employee", cascade = CascadeType.ALL, orphanRemoval = true)
List<EmployeePhoneNumber> employeePhoneNumbers = new ArrayList<>();
}
EmployeePhoneNumber:
#Entity
#Table(name = "EMPLOYEE_PHONES", schema = PUBLIC)
public class EmployeePhoneNumber {
#Id
#SequenceGenerator(allocationSize = 1, name = "SEQ_EMPLOYEE_PHONES", schema = PUBLIC,
sequenceName = "EMPLOYEE_PHONES_ID_SEQ")
#GeneratedValue(generator = "SEQ_EMPLOYEE_PHONES", strategy = GenerationType.SEQUENCE)
#Column(name = "ID", unique = true, nullable = false)
private Long id;
#ManyToOne
#JoinColumn(name = "employee_id", referencedColumnName = "id",
nullable = false, insertable = false, updatable = false)
private Employee employee;
#Column(name = "PHONE_NUMBER", unique = true, nullable = false)
private String phoneNumber;
#Enumerated(EnumType.STRING)
#Column(name = "NUMBER_TYPE", nullable = false)
private PhoneNumberType phoneNumberType;
}
How I set those fields and then save the entity:
EmployeePhoneNumber workPhone = new EmployeePhoneNumber();
workPhone.setPhoneNumber(workPhone);
workPhone.setPhoneNumberType(PhoneNumberType.WORK_PHONE);
EmployeePhoneNumber mobilePhone = new EmployeePhoneNumber();
mobilePhone.setPhoneNumber(mobilePhone);
mobilePhone.setPhoneNumberType(PhoneNumberType.MOBILE_PHONE);
EmployeePhoneNumber corporatePhone = new EmployeePhoneNumber();
corporatePhone.setPhoneNumber(corporatePhoneNumber);
corporatePhone.setPhoneNumberType(PhoneNumberType.CORPORATE_PHONE);
List<EmployeePhoneNumber> employeePhoneNumbers = employee.getEmployeePhoneNumbers();
employeePhoneNumbers.add(workPhone);
employeePhoneNumbers.add(mobilePhone);
employeePhoneNumbers.add(corporatePhone);
employee.setEmployeePhoneNumbers(employeePhoneNumbers);
employeeRepository.save(employee);
Upon completion of the method, I do not have a single error, everything works out correctly, only the tables are not filled - why?
You must also set the Employee reference in EmployeePhoneNumber because Hibernate will use this to save it.
workPhone.setEmployee(employee);
mobilePhone.setEmployee(employee);
corporatePhone.setEmployee(employee);
The best solution would be to create an addEmployeePhoneNumber method on the Employee like this:
public void addEmployeePhoneNumber(EmployeePhoneNumber phoneNumber) {
phoneNumber.setEmployee(this);
employeePhoneNumbers.add(phoneNumber);
}
That way you will not forget to set both sides of the relationship.

Spring Boot: How to create an Entity with a composite key

I was creating my classes for a project using a chart for practice purposes until I stumbled upon this order_items:
I had no problem creating an Entity like Orders or Products because I knew that for Orders I just had to do something like:
#Entity
#Table(name = "orders")
public class Orders {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "order_id")
private Integer orderId;
// rest of the code...
}
And for for Products something like:
#Entity
#Table(name = "products")
public class Products {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "product_id")
private Integer productId;
// rest of the code...
}
But the table order_items has the variables order_id and item_id, does that count as a composite key? If that is the case, how should those variables look in my OrderItems class?
#Entity
#Table(name = "order_items")
public class OrderItems {
#Column(name = "order_id")
private Integer orderId;
#Column(name = "item_id")
private Integer itemId;
// rest of the code...
}
I've checked different questions and they mention using #IdClass or #EmbeddableId for composite keys, but I'd like to confirm first if that is what I should do in this situation, unless it's not the case, maybe there are more approaches.
I'd really appreciate opinions and/or any article related to this, thank your for your time.
As you mentioned you can use #EmbeddableId.
Here is example :
#Embeddable
#Data
#NoArgsConstructor
#AllArgsConstructor
#Builder(toBuilder = true)
public class OrderItemsPK implements Serializable {
private static final long serialVersionUID = 1L;
#Column(insertable = false, unique = false, updatable = false, nullable = false,name = "order_id")
private Long orderId;
#Column(insertable = false, unique = false, updatable = false, nullable = false,name = "products_id")
private Long productsId;
}
And the Order Items Class.
#Entity
public class OrderItems {
#EmbeddedId
private OrderItemsPK id;
#OneToOne
#JoinColumn(name = "products_id", nullable = false, unique = false, insertable = false, updatable = false, referencedColumnName = "products_id")
private Products products;
#OneToOne
#JoinColumn(name = "orders_id", nullable = false, unique = false, insertable = false, updatable = false, referencedColumnName = "orders_id")
private Order order;
private Long itemId;
}

SpringData CrudRepository applies automaticlly cascade type

In my project I have to connect to existing database and do logic which updates two tables.
My setup is following:
#Entity
#Table(name = "DOCUMENTCONTENT")
#Getter
public class DocumentContent {
#Id
#Column(name = "ID", insertable = false, updatable = false)
private Long id;
#OneToOne
#JoinColumn(name = "DOCUMENT_ID", insertable = false, updatable = false)
private Document document;
#Lob
#Column(name = "CONTENT")
#Setter
private byte[] content;
}
#Entity
#Table(name = "DOCUMENT")
#Getter
public class Document {
#Id
#Column(name = "ID", insertable = false, updatable = false)
private Long id;
#OneToOne(mappedBy = "document")
private DocumentContent documentContent;
#OneToMany(mappedBy = "document", fetch = EAGER)
private List<Attachment> attachments;
}
#Entity
#Table(name = "ATTACHMENT")
#Getter
public class Attachment {
#Id
#Column(name = "ID")
private Long id;
#ManyToOne
#JoinColumn(name = "DOCUMENT_ID", insertable = false, updatable = false)
private Document document;
#ManyToOne
#JoinColumn(name = "CONTRACT_ID",updatable = false, insertable = false)
private Contract contract;
}
#Entity
#Table(name = "CONTRACT")
#Getter
public class Contract {
#Id
#Column(name = "ID", insertable = false, updatable = false)
private Long id;
#Column(name = "STATUS")
#Setter
private String status;
#ManyToOne
#JoinColumn(name = "CUSTOMER_ID", insertable = false, updatable = false)
private Customer customer;
#OneToMany(mappedBy = "contract", fetch = EAGER)
private List<Attachment> attachments;
}
#Service
public class MyServiceImpl implements MyService {
#Autowired
private DocumentContentRepository documentContentRepository; // spring data Crud Repository
#Override
#Transactional
public void updateDocumentContent(SomeDto someDto) {
DocumentContent documentContent = documentContentRepository.findByDocumentId(someDto.getDocumentId());
documentContent.setContent(someDto.getBytes());
List<Contract> contracts = documentContent.getDocument().getAttachments()
.stream().map(Attachment::getContract).collect(toList());
contracts.forEach(contract -> contract.setStatus("SIGNED"));
documentContentRepository.save(documentContent);
}
}
When I fire method from above service I can notice those SQL in console output:
Hibernate: update documentcontent set content=? where id=?
Hibernate: update contract set status=? where id=?
I understand why jpa performed first update in documentcontent table, but I don't know why it did update in contract table aswell. As you can see I didn't use CascadeType.MERGE in any entity.
Can you explain me why this second update has been performed without declaring cascade type?
I doubt it has anything to do with Cascade at all, but with transactional write behind mechanism (more info). I believe you could also get rid of the line
documentContentRepository.save(documentContent);
since you are modifying two managed entities. At the end of the transaction hibernate persists all entities marked as modified by the dirty checking mechanism (more info).
You are getting 2nd query for the reason, you are modifying Status property of Contract.
JPA detect this change and try to update entity.
This is default CaseCadeType behaviour of #OneToMany
For further reading follow this link.

Cascading save Entity objects with Foreign Key as a part of composite Primary Key

I would like to persist object of my QuestionCompletion class with all child elements. One of these childs has a composite primary key. And as a part of this primary key I have also foreign key to another entity. As a result I am getting this error:
Exception caught during request processing: javax.ejb.EJBTransactionRolledbackException:
could not set a field value by reflection setter of com.example.model.domain.QuestionCompletionAnswerPK.questionCompletionId
javax.ejb.EJBTransactionRolledbackException: could not set a field value by reflection
setter of com.example.model.domain.QuestionCompletionAnswerPK.questionCompletionId
And the last "caused by" is of course NullPointerException:
Caused by: java.lang.NullPointerException
This is part of my code. The last line causes error.
QuestionCompletion questionCompletion = new QuestionCompletion();
List<QuestionCompletionAnswer> answers = new ArrayList<QuestionCompletionAnswer>();
for (;;) { // loop isn't important; it's loop for answers
ExtendedQuestion extendedQuestion = new ExtendedQuestion();
extendedQuestion.setId(extendedQuestionId); //extendedQuestionId is known to me in that place
for (;;) { // loop isn't important; it's loop for question answers
//questionCompletion and extendedQuestion are popualted here
QuestionCompletionAnswer questionCompletionAnswer = new QuestionCompletionAnswer();
questionCompletionAnswer.setQuestionCompletion(questionCompletion);
questionCompletionAnswer.setExtendedQuestion(extendedQuestion);
answers.add(questionCompletionAnswer);
}
}
questionCompletion.setAnswers(answers);
questionCompletionService.saveOrMerge(questionCompletion);
This is my basic entity class I would like to persist with all its childs elements. I have realized that List<QuestionCompletionAnswer> causes problems. I have used cascade = CascadeType.ALL to allow to persist childs elements also.
#javax.persistence.Entity
#org.hibernate.annotations.Entity(dynamicUpdate = true)
#Table(name = "question_completion")
public class QuestionCompletion implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "question_completion_gen")
#SequenceGenerator(name = "question_completion_gen", sequenceName = "question_completion_id_seq")
#Column(name = "question_completion_id")
private Long id;
#OneToMany(cascade = CascadeType.ALL)
#JoinColumn(name = "extended_question_id")
protected List<QuestionCompletionAnswer> answers;
}
This is my class - Primary Key for the QuestionCompletionAnswer class.
#Embeddable
public class QuestionCompletionAnswerPK implements Serializable {
#Column(name = "question_completion_id")
protected Long questionCompletionId;
#Column(name = "extended_question_id")
protected Long extendedQuestionId;
}
And this is class which uses my EmbeddedId. Attribues questionCompletionId and questionCompletionId are the foreign key for some another entities so I have placed below also whole objects of these entities with #MapsId annotation.
#javax.persistence.Entity
#org.hibernate.annotations.Entity(dynamicUpdate = true)
#Table(name = "extended_question_answer")
public class QuestionCompletionAnswer implements Serializable {
#EmbeddedId
private QuestionCompletionAnswerPK id;
#ManyToOne
#MapsId(value = "questionCompletionId")
#JoinColumn(name = "question_completion_id", nullable = false, insertable = false, updatable = false)
protected QuestionCompletion questionCompletion;
#ManyToOne
#MapsId(value = "extendedQuestionId")
#JoinColumn(name = "extended_question_id", nullable = false, insertable = false, updatable = false)
protected ExtendedQuestion extendedQuestion;
}
Could you tell me if my annotations are correct? Maybe I have mixed up few approaches. Or I can't in that case persist my basic object with all of its child elements.
EDIT
Now my mapping looks like:
#javax.persistence.Entity
#org.hibernate.annotations.Entity(dynamicUpdate = true)
#Table(name = "question_completion")
public class QuestionCompletion implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "question_completion_gen")
#SequenceGenerator(name = "question_completion_gen", sequenceName = "question_completion_id_seq")
#Column(name = "question_completion_id")
private Long id;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "questionCompletion")
protected List<QuestionCompletionAnswer> answers;
}
Code of the QuestionCompletionAnswerPK class is the same.
#javax.persistence.Entity
#org.hibernate.annotations.Entity(dynamicUpdate = true)
#Table(name = "extended_question_answer")
public class QuestionCompletionAnswer implements Serializable {
#EmbeddedId
private QuestionCompletionAnswerPK id;
#ManyToOne(cascade = CascadeType.PERSIST)
#MapsId(value = "questionCompletionId")
#JoinColumn(name = "question_completion_id", nullable = false)
protected QuestionCompletion questionCompletion;
#ManyToOne
#MapsId(value = "extendedQuestionId")
#JoinColumn(name = "extended_question_id", nullable = false)
protected ExtendedQuestion extendedQuestion;
}
With that mapping I am still getting the same exception.
EDIT #2
However when I have changed QuestionCompletionAnswer class in this way:
#javax.persistence.Entity
#org.hibernate.annotations.Entity(dynamicUpdate = true)
#Table(name = "extended_question_answer")
public class QuestionCompletionAnswer implements Serializable {
#EmbeddedId
private QuestionCompletionAnswerPK id;
#ManyToOne(cascade = CascadeType.PERSIST)
#JoinColumn(name = "question_completion_id", nullable = false, insertable = false, updatable = false)
protected QuestionCompletion questionCompletion;
#ManyToOne
#JoinColumn(name = "extended_question_id", nullable = false, insertable = false, updatable = false)
protected ExtendedQuestion extendedQuestion;
}
I am getting that exception:
Caused by: org.hibernate.id.IdentifierGenerationException: null id generated
for:class com.example.model.domain.QuestionCompletionAnswer
Edit 1 and 2 are still not right. You need mapsid specified on the relationship or you must set the field in the embedded id with a value yourself. And when you use mapsid, you shouldn't have the join column marked insertable=false or jpa can't insert a value for you. The last problem I see is that the new question is not persisted so it doesn't get an id assigned that the answer can reference - you need to explicitly persist the new question in the for loop or mark the relationship in the answer to it to cascade persist.
Why are you mixing jpa and hibernate annotations ? jpa annotations alone should be enough.
Try with the following mapping (I removed the class QuestionCompletionAnswerPK) :
QuestionCompletion.java
#Entity
#Table(name = "question_completion")
public class QuestionCompletion {
#Id
#SequenceGenerator(name = "question_completion_gen", sequenceName = "question_completion_id_seq")
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "question_completion_gen")
#Column(name = "question_completion_id")
private Long id;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "questionCompletion")
protected List<QuestionCompletionAnswer> answers;
}
QuestionCompletionAnswer.java
#Entity
#Table(name = "extended_question_answer")
public class QuestionCompletionAnswer implements Serializable {
#Id
#ManyToOne
#JoinColumn(name = "question_completion_fk")
private QuestionCompletion questionCompletion;
#Id
#ManyToOne
#JoinColumn(name = "extended_question_fk")
private ExtendedQuestion extendedQuestion;
}
With this mapping, I run the following test case and it worked :
QuestionCompletion questionCompletion = new QuestionCompletion();
ExtendedQuestion extendedQuestion = new ExtendedQuestion();
QuestionCompletionAnswer answer = new QuestionCompletionAnswer();
answer.setQuestionCompletion(questionCompletion);
answer.setExtendedQuestion(extendedQuestion);
questionCompletion.setAnswers(Collections.singletonList(answer));
...
session.save(extendedQuestion);
session.save(questionCompletion);

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