select parent and count of all child class in JPA - java

I have following JPA entity structure.
#Entity
#Table(name = "PARENT_DETAILS")
class Parent{
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "parent_details_seq")
#SequenceGenerator(name = "parent_details_seq", sequenceName = "PARENT_DETAILS_SEQ", allocationSize = 1)
#Column(name = "PARENT_ID")
private long parentId;
#Column(name = "PARENT_NAME")
private String parentName;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "childPK.parent", cascade = CascadeType.ALL)
private Set<Child> child;
//setters and getters
}
#Entity
#Table(name = "CHILD_DETAILS")
public class Child {
private ChildPK childPK;
public void setProgramProcessesPK(ChildPK childPK) {
this.childPK = childPK;
}
#EmbeddedId
public ChildPK getChildPK() {
return childPK;
}
}
#Embeddable
public class ChildPK implements Serializable {
private static final long serialVersionUID = 1L;
private Parent parent;
private long childId;
#Column(name = "CHILDID")
public long getChildId() {
return childId;
}
public void setChildId(long childId) {
this.childId = childId;
}
#ManyToOne
#JoinColumn(name = "PARENT_ID", referencedColumnName = "PARENT_ID", nullable = false)
public ParentDetails getParent() {
return parent;
}
}
I want to write a JPA query which will return the PARENT_NAME and the count of all children for a given parent_id.
Tthe only solution I can think of is joining and writing a complex criteria query.
I cannot think of a way to get the result using a simple JPA query.
Is there an easier way to do this?

Have you tried SIZE? Something like "Select parent.parentName, Size(parent.child) from Parent parent" might work.

You can use JPA Named Query such as this:
private static class ParentChildsNumber {
public String parentName;
public Integer childsNum;
public ParentChildsNumber(String parentName, Integer childsNum) {
this.parentName = parentName;
this.childsNum = childsNum;
}
}
#NamedQuery(name="getParentChildsNumberQuery", query="SELECT NEW ParentChildsNumber(p.parentName, SIZE(p.child)) FROM Parent p WHERE p.parentId = :parentId GROUP BY p.parentId, p.parentName")
Use it in your code in the following way:
#PersistenceContext(unitName="YourPersistentUnit")
private EntityManager em;
em.createNamedQuery("getParentChildsNumberQuery", ParentChildsNumber.class).setParameter("parentId", parentId).getSingleResult();

Related

Hibernate joining tables with multiple primary keys

I have figured out how to join 2 tables with single primary key. But now I need to join 4 tables and some of these table with composite primary keys.
Here is my table picture
And I want to join them, so I generate classes for them:
// Record
#Getter
#Setter
#ToString
#AllArgsConstructor
#NoArgsConstructor
#Entity
#Table(name = "record")
public class Record implements java.io.Serializable{
private static final long serialVersionUID = 1L;
#Id
#Column(name = "student_id")
private Integer studentId;
#Id
#Column(name = "exam_id")
private Integer examId;
#Column(name = "total_score")
private Integer totalScore;
#Column(name = "student_score")
private Integer studentScore;
#Column(name = "submission_id")
private Integer submissionId;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "student_id")
private Student student;
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "submission_id")
private Submission submission;
}
// Submission
#Getter
#Setter
#ToString
#AllArgsConstructor
#NoArgsConstructor
#Entity
#Table(name = "submission")
public class Submission implements java.io.Serializable{
private static final long serialVersionUID = 1L;
#Id
#Column(name = "submission_id")
private Integer submissionId;
#Id
#Column(name = "question_id")
private Integer questionId;
#Column(name = "stu_answer")
private String stuAnswer;
#OneToOne(fetch = FetchType.LAZY, mappedBy = "submission")
private Record record;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "submission")
private Set<Question> question;
}
// Question
#Getter
#Setter
#ToString
#AllArgsConstructor
#NoArgsConstructor
#Entity
#Table(name = "question")
public class Question implements java.io.Serializable{
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue
#Column(name = "question_id")
private Integer questionId;
#Column(name = "content")
private String content;
#Column(name = "score")
private Integer score;
#Column(name = "is_delete")
private Integer isDelete;
#Column(name = "option_id")
private Integer optionId;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "submission_id")
private Submission submission;
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "optional_id")
private Optional optional;
}
// Optional
#Getter
#Setter
#ToString
#AllArgsConstructor
#NoArgsConstructor
#Entity
#Table(name = "optional")
public class Optional implements java.io.Serializable{
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue
#Column(name = "option_id")
private Integer optionId;
#Column(name = "content")
private String content;
#Column(name = "is_delete")
private Integer isDelete;
#Column(name = "answer")
private String answer;
#OneToOne(fetch = FetchType.LAZY, mappedBy = "optional")
private Question question;
}
// Final class to store information
public class RcdSubQuesOpt {
private Integer studentId;
private Integer examId;
private Integer questionId;
private String stuAnswer;
private String qContent;
private String oContent;
private String answer;
}
And this is code for JPA
#Override
public List<RcdSubQuesOpt> getRcdSubQuesOpt(int studentID, int examId) {
Session session = this.getSession();
List<RcdSubQuesOpt> results;
Transaction transaction = null;
try {
transaction = session.beginTransaction();
CriteriaBuilder criteriaBuilder = session.getCriteriaBuilder();
CriteriaQuery<RcdSubQuesOpt> criteriaQuery = criteriaBuilder.createQuery(RcdSubQuesOpt.class);
// Try to join tables
Root<Record> pRoot = criteriaQuery.from(Record.class);
pRoot.join("submission", JoinType.INNER);
pRoot.join("question", JoinType.INNER);
pRoot.join("optional", JoinType.INNER);
criteriaQuery.multiselect(
pRoot.get(columns in RcdSubQuesOpt Class......));
// Try to add constraints
Predicate predicate = pRoot.get("examId").in(Arrays.asList(1));
criteriaQuery.where(predicate);
// try to do queries
results = session.createQuery(criteriaQuery).getResultList();
transaction.commit();
} catch (Exception e) {
results = null;
if (transaction != null) {
transaction.rollback();
}
} finally {
session.close();
}
return results;
}
But hibernate throw error as following:
Enitial SessionFactory creation failedA Foreign key refering com.domain.Submission from com.domain.Record has the wrong number of column. should be 2
Exception in thread "main" java.lang.ExceptionInInitializerError
I think it's the composite primary keys problem. But solution I searched is not suitable to solve it. Anyone give me some advice? Thanks!
To reference a composite primary key, you need to use #JoinColumns (plural).
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumns({
#JoinColumn(name = "sub_submission_id", referencedColumnName = "submission_id"),
#JoinColumn(name = "sub_question_id", referencedColumnName = "question_id")
})
private Submission submission;
However, I must admit, I don't understand your model - especially why Submission has a composite PK with question_id. It looks that one Submission has many Questions, why to include question_id as part of Submission PK?
Perhaps, I'm missing something, because the diagram is not fully visible.

How to use child table column in org.hibernate.annotations.Formula

I need to concat some columns from both parent and child table using #Formula
Here is the Entities
#Entity
#Table(name = "parent1")
public class Parent1 implements Serializable {
#Id
private BigInteger id;
#Column(name = "childId")
private BigInteger childId;
#Column(name = "col1")
private String col1;
#Column(name = "col2")
private String col2;
#Formula("CONCAT_WS(' ',Parent2.child_colm,col1,col2)")
private String combinedName;
#OneToOne
#JoinColumn(name = "childId")
private Parent2 parent2;
}
#Entity
#Table(name = "parent2")
public class Parent2 implements Serializable {
#Id
#Column(name = "childId")
private BigInteger childId;
#Column(name = "child_colm")
private String child_colm;
}
While giving like this it returns Unknown column 'Parent2.child_colm'
I would suggest you instead of using #Formula here just write the following method:
import javax.persistence.Transient;
#Entity
#Table(name = "parent1")
public class Parent1 implements Serializable {
#Transient
public String getCombinedName() {
return Stream.of(parent2.child_colm, col1, col2)
.filter(s -> s != null && !s.isEmpty())
.collect(Collectors.joining(" "));
}
}
The #Transient annotation is used to specify that a given entity attribute should not be persisted.

ModelMapper Set attribute of nested object

Is there a way configure model mapper to automatically map parent id to parent ids in nested child object?
Parent entity
#Entity
#Table(name = "parent")
public class ParentEntity {
#Id
#Column(name = "id")
private Long id;
#Basic
#Column(name = "name")
private String name;
#OneToMany(cascade = CascadeType.ALL)
#JoinColumn(name = "parent_id", referencedColumnName = "id")
private List<ChildEntity> child;
Child entity
#Entity
#Table(name = "child")
public class ChildEntity {
#Id
#Column(name = "id")
private Long id;
#Basic
#Column(name = "parent_id")
private Long parentId;
#Basic
#Column(name = "name")
private String name;
Parent DTO
public class ParentDto {
private Long id;
private String name;
private List<ChildDto> children;
Child DTO
public class ChildDto {
private Long id;
private Long parentId;
private String name;
Currently Im using model mapper and it conversion works for the most part, and it creates a ParentEntity object with a list of ChildEntity object. Now Im looking for a way to populate the "parentId" field in each ChildEntity with modelmapper. Thanks in advance!
/*
{
id: 1,
name: "dad",
children: [
{
id:10,
name: "child",
},
{
id:20,
name: "child2",
}
]
}
*/
modelMapper.map(parentDto, ParentEntity.class)
The challenge is that when ChildDto is mapped you must have access to the ParentDto that has the List children in which the ChildDto to be mapped is added. Otherwise the mapper does not know about that id.
Luckily you can access ParentDto with a little help from a org.modelmapper.Converter. Implement this interface like:
public Converter<ChildDto, ChildEntity> converter = new Converter<>() {
private final ModelMapper mm = new ModelMapper();
#Override
public ChildEntity convert(MappingContext<ChildDto, ChildEntity> context) {
// map it first normally
ChildEntity ce = mm.map(context.getSource(), ChildEntity.class);
try {
// 1st parent is the List 2nfd is the ParentDto
ParentDto parentDto = (ParentDto)context.getParent().getParent().getSource();
ce.setParentId(parentDto.getId());
} catch (Exception e) {
// well, maybe it was not ParentDto that expected to be the grandparent.
}
return ce;
}
};
Then use it like:
ModelMapper mm = new ModelMapper();
mm.addConverter(converter);
and ChildDto that has no parentId should still be mapped to ChildEntity with parentId.

Spring Data : issue with OneToMany, child not saved properly

I am having trouble dealing with #OneToMany relationship.
Here is my code :
#Entity
#Table(name = "type_mouvement")
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class TypeMouvement implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#OneToMany(mappedBy="typeMouvement", fetch = FetchType.EAGER,cascade = CascadeType.PERSIST)
private List<CompteTypeMouvement> comptes;
...
}
#Entity
#Table(name = "type_mouvement_comptes")
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class CompteTypeMouvement implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String numCompte;
#ManyToOne
private TypeMouvement typeMouvement;
...
}
How I use these entities :
TypeMouvement typeMouvementFromDB = typeMouvementRepository.findOne(new Long(1));
CompteTypeMouvement compte = new CompteTypeMouvement();
compte.setNumCompte("123");
compte.setTypeMouvement(typeMouvementFromDB);
typeMouvementFromDB.getComptes().add(compte);
typeMouvementRepository.save(typeMouvementFromDB);
The result I get :
I thought I would get :
Why are the properties of CompteTypeMouvement not filled when I save TypeMouvement?
You are trying to invoke save passing an already existing entity:
TypeMouvement typeMouvementFromDB = typeMouvementRepository.findOne(new Long(1));
typeMouvementRepository.save(typeMouvementFromDB);
The relationship has a persist cascade type only though:
#OneToMany(mappedBy="typeMouvement", fetch = FetchType.EAGER,cascade = CascadeType.PERSIST)
private List<CompteTypeMouvement> comptes;
The save impl is a follows (spring-data-jpa-1.11.3):
public <S extends T> S save(S entity) {
if (entityInformation.isNew(entity)) {
em.persist(entity);
return entity;
} else {
return em.merge(entity);
}
}
Which means that a merge instead of a persist will be invoked.
This if you add merge to cascade it should work:
#OneToMany(mappedBy="typeMouvement", fetch = FetchType.EAGER,
cascade = {CascadeType.PERSIST, CascadeType.MERGE})
private List<CompteTypeMouvement> comptes;

JPA / Hibernate delete entity from association table

I have the following hibernate mappings:
Class Folders:
#Entity
#Table(name = "FOLDER")
public class Folders implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "folder_seq")
#Column(name = "ID_FOL")
private Long id;
#Column(name = "NOFOLDER")
private String noFolder;
#LazyCollection(LazyCollectionOption.FALSE)
#OneToMany(cascade = CascadeType.ALL)
#JoinTable(name = "M_DOMAIN_DN", joinColumns = {
#JoinColumn(name = "FK_FOLDER", referencedColumnName = "ID_FOL") }, inverseJoinColumns = {
#JoinColumn(name = "FK_DOMAIN", referencedColumnName = "ID_DOM") })
private List<Domain> domain = new ArrayList<Domain>();
/** GETTER + SETTER **/
}
Class Domain :
#Entity
#Table(name = "DOMAIN")
public class Domain {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "domain_seq")
#Column(name = "ID_DOM")
private Long id;
#Column(name = "CODE")
private String code;
#Column(name = "LABEL")
private String label;
public Long getId() {
/** GETTER + SETTER **/
}
Method that deletes an entity of type Folder:
public void deleteFolder(Long id) {
try {
entityManager.remove(getFolderById(id));
entityManager.flush();
} catch (PersistenceException e) {
}
}
I tried to delete the entity Folder but is not deleted in the database. Deleting manually in the database works fine, looking for help trying to solve this issue.

Categories

Resources