Need help learning how to join on two separate entities using jpa - java

A Java web project that uses Hibernate just fell into my lap. The original architect has left and I have almost no experience with JPA's Criteria API. So please forgive my ignorance. I'm looking for help with writing several joins using JPA's Criteria API.
I have the following entities and relationships. An ElectronicDevice contains a set of Components which contains a set of Signals.
I'm trying to write a query that returns all of the components and it's signals of a specific electronic device.
I can get the ElectronicDevice with the Component, but I'm unable to get the Signals. I have the following query written.
EntityManager em = getEm();
String electronicDeviceId="Some UUID";
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery cq = cb.createQuery(Component_.class);
Root<ElectronicDevice> i = cq.from(ElectronicDevice.class);
SetJoin<ElectronicDevice, Component> join = i.join(ElectronicDevice_.components);
cq.where(cb.equal(i.get(ElectronicDevice_.id), electronicDeviceId));
cq.multiselect(join);
TypedQuery q = em.createQuery(cq);
List<Component> resultList = q.getResultList();
My Entity Definitions are as follows
#Entity
public class ElectronicDevice implements Serializable {
#Id
#Column(length = 36)
private String id;
#XmlElement
#OneToMany
private Set<Component> components = new HashSet<Component>();
}
#Entity
public class Component extends ParametricObject implements Serializable{
#XmlElement
#OneToMany
private Set<Signal> signals = new HashSet<Signal>();
public Set<Signal> getComponents() {
return signals;
}
}
#Entity
public class Signal implements Serializable {
#Id
#Column(length = 36)
private String id;
}
public class ParametricObject {
#EmbeddedId
#XmlElement
private ParametricId id;
#XmlElement
private String name;
public ParametricId getId() {
return id;
}
public void setId(ParametricId id) {
this.id = id;
}
}
public class ParametricId {
#XmlElement
#ManyToOne
#XmlIDREF
private ElectronicDevice ed;
#XmlAttribute
#XmlID
#Column(length = 36)
private String internalId;
public ElectronicDevice getEd() {
return ed;
}
public void setEd(ElectronicDevice ed) {
this.ed = ed;
}
public String getInternalId() {
return internalId;
}
public void setInternalId(String internalId) {
this.internalId = internalId;
}
}

refer to the discussion at "Hibernate Criteria Join with 3 Tables". your query should looks likes this.
you must add ElectronicDevice with menyToOne annotation in the Component.(see the example at http://www.mkyong.com/hibernate/hibernate-one-to-many-relationship-example-annotation/.)
Criteria c = session.createCriteria(Component.class, "cmp");
c.createAlias("cmp.signals", "signal"); // this will do inner join
c.add(Restrictions.eq("cmp.electronicDevice", "xxxx"));
return c.list();

Related

Hibernate 5 and JPA: select table without his children but maintain persistence on save

I have two models: Ordine and DettaglioOrdine.
I would like that, when I save an object of type "Ordine", hibernate also save his child "DettaglioOrdine" (and this works).
But, if I do a select query, the query is very very slow because hibernate retrieve also DettaglioOrdine. So, I would like the "Ordine" object without "DettaglioOrdine" object.
"Ordine" model:
#Entity
#Table(name="ordini")
#NamedQuery(name="Ordine.findAll", query="SELECT o FROM Ordine o")
public class Ordine extends DataObject<Integer> {
private static final long serialVersionUID = 1L;
private Integer id;
[...]
private List<Dettagliordine> dettagliordine;
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(unique=true, nullable=false)
public Integer getId() {
return this.id;
}
public void setId(Integer id) {
this.id = id;
}
//bi-directional many-to-one association to Dettagliordine
#Column(insertable = true, updatable = true)
#OneToMany(cascade=CascadeType.ALL, fetch=FetchType.LAZY, mappedBy="ordine")
public List<Dettagliordine> getDettagliordine() {
return this.dettagliordine;
}
public void setDettagliordine(List<Dettagliordine> dettagliordine) {
this.dettagliordine = dettagliordine;
}
public Dettagliordine addDettagliordine(Dettagliordine dettaglioordine) {
dettaglioordine.setOrdine(this);
this.dettagliordine.add(dettaglioordine);
return dettaglioordine;
}
public Dettagliordine removeDettagliordine(Dettagliordine dettagliordine) {
dettagliordine.setOrdine(null);
this.dettagliordine.remove(dettagliordine);
return dettagliordine;
}
}
DettaglioOrdine Model:
#Entity
#Table(name="dettagliordine")
#NamedQuery(name="Dettagliordine.findAll", query="SELECT d FROM Dettagliordine d")
public class Dettagliordine extends DataObject<Integer> {
private static final long serialVersionUID = 1L;
private Integer id;
[...]
public Dettagliordine() {
}
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(unique=true, nullable=false)
public Integer getId() {
return this.id;
}
public void setId(Integer id) {
this.id = id;
}
[...]
//bi-directional many-to-one association to Ordini
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name="idOrdine", nullable=false)
public Ordine getOrdine() {
return this.ordine;
}
public void setOrdine(Ordine ordine) {
this.ordine = ordine;
}
}
And this is my query:
SessionFactory sessionFactory = getSessionFactory();
Session session = sessionFactory.getCurrentSession();
List<OrdineDTO> result = null;
try{
String hql = "select d.ordine from Dettagliordine d "
+ "group by d.ordine.id";
Query<Ordine> query = session.createQuery(hql,Ordine.class);
List<Ordine> res = query.getResultList();
result = new OrdineDMO().unmarshall(res);
}catch (DMOException e) {
e.printStackTrace();
}
It is not Hibernate. You have fetch=FetchType.LAZY for dettagliordine. Hibernate doesn't have to fetch the lazy association with the parent.
The problem can be here
result = new OrdineDMO().unmarshall(res);
if the code inside unmarshall() method touches dettagliordine or invoke a method, that differs from get, set methods (like toString() method), Hibernate will fetch dettagliordine association.
You can enable Hibernate logging and debug the code. You will see when fetching actually happens. Also keep in mind, if you debug persistent classes, the debugger can cause invoking of toString() method and the association will be fetched too.
Better to move this line outside session/#Transactional block of code.
result = new OrdineDMO().unmarshall(res);
You will have LazyInitializationExcepton, with any unwanted access to lazy associations.

querydsl how to return dto list?

i use querydsl, hibernate
i want select data by Dto in Dto list but not working
here is my code
#Data
#Entity
public class Team {
#Id
#GeneratedValue
private Long id;
private String name;
#OneToMany(mappedBy = "team")
private List<Member> members = new ArrayList<>();
}
#Entity
#Setter
public class Member {
#Id
#GeneratedValue
private Long id;
private String name;
#ManyToOne
#JoinColumn(name = "team_id")
private Team team;
}
#Setter
public class TeamDto {
private Long id;
private String name;
private List<MemberDto> members = new ArrayList<>();
}
#Setter
public class MemberDto {
private Long id;
private String name;
}
test
#BeforeEach
void setup() {
queryFactory = new JPAQueryFactory(em);
Team team = new Team();
team.setName("teamA");
em.persist(team);
Member member = new Member("memberA");
member.setTeam(team);
em.persist(member);
Member member2 = new Member("memberB");
member2.setTeam(team);
em.persist(member2);
em.flush();
em.clear();
}
#Test
void t1() {
TeamDto teamDto = queryFactory
.select(Projections.fields(
TeamDto.class,
team.id,
team.name,
Projections.fields(
MemberDto.class,
member.id,
member.name
).as("members")
))
.from(team)
.fetchOne();
System.out.println("teamDto = " + teamDto);
}
error log is = java.lang.IllegalArgumentException: com.blog.querydsltest.domain.dto.MemberDto is not compatible with java.util.List
what is problem?? is impossible bring data by List dto??
i try to change Projections.fields to bean, construct, ... but not working
how can i do ?
Multi level aggregations are currently not supported by QueryDSL. There are also no concrete plans to support it as of now.
For a DTO solution that can fetch associations with it, I recommend you to have a look at Blaze-Persistence Entity Views. With Entity Views the code for your DTO would look something like the following:
#EntityView(Team.class)
public interface TeamDto {
#IdMapping public Long getId();
#Mapping("name") public String getName();
#Mapping("members") public List<MemberDTO> getMembers();
}
If members is not an association on your TeamEntity, you can map it through a #MappingCorrelated binding.
Disclaimer: I am a contributor for Hibernate, QueryDSL and Blaze-Persistence.

How to map results of a Hibernate Query to a DTO object?

I have the following 3 Hibernate Entities within my Java Project:
CompanyStatus
#Entity(name = "company_status")
#Table(name = "company_status")
public class CompanyStatus implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#JsonProperty
#Column(name = "company_status_id")
private Integer companyStatusId;
#JsonProperty
#Column(name = "company_status_label")
private String companyStatusLabel;
}
Employee Status
#Entity(name = "employee_status")
#Table(name = "employee_status")
public class EmployeeStatus implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#JsonProperty
#Column(name = "employee_status_id")
private Integer employeeStatusId;
#JsonProperty
#Column(name = "employee_status_name")
private String employeeStatusName;
// many other fields
}
CompanyStatusEmployeeStatus (Entity linking the 2 entities- one to one relationship)
#Entity(name = "company_status_employee_status")
#Table(name = "company_status_employee_status")
public class CompanyStatusEmployeeStatus implements Serializable {
// int(20)
#Id
#JsonProperty
#Column(name = "company_status_id")
private Integer companyStatusId;
// int(20)
#JsonProperty
#Column(name = "employee_status_id")
private Integer employeeStatusId;
}
I only want to return the necessary fields in my JSON response to the front end , so In order to do so I have created a smaller CompanyStatusDTO object that also has an EmployeeStatusDTO list nested
CompanyStatusDTO
public class CompanyStatusDTO {
#JsonProperty
private Integer companyStatusId;
#JsonProperty
private String companyStatusLabel;
#JsonProperty
private List <EmployeeStatusDTO> employeeStatusDTOs;
}
EmployeeStatusDTO
public class EmployeeStatusDTO {
#JsonProperty
private Integer employeeStatusId;
#JsonProperty
private String employeeStatusName;
}
However, I am relatively new to using Hibernate - is there a way that I can create a query that will map results directly from my MySQL DB to my CompanyStatusDTOobject?
If so, how can do I this?
you can directly map query result to you desired DTO using NativeQuery (datatype must match)
String q = "select ... from table"; // your sql query
Query query = getEntityManager().createNativeQuery(q, "EmployeeStatusDTO");
EmployeeStatusDTO data = (EmployeeStatusDTO) query.getSingleResult();
return data;
This is a perfect use case for Blaze-Persistence Entity Views.
I created the library to allow easy mapping between JPA models and custom interface or abstract class defined models, something like Spring Data Projections on steroids. The idea is that you define your target structure(domain model) the way you like and map attributes(getters) via JPQL expressions to the entity model.
If you adapt the CompanyStatus and CompanyStatusEmployeeStatus entities a bit and add the following:
public class CompanyStatus implements Serializable {
//...
#OneToMany(mappedBy = "companyStatus")
private Set<CompanyStatusEmployeeStatus> employeeStatuses;
}
public class CompanyStatusEmployeeStatus implements Serializable {
//...
#JsonProperty
#ManyToOne(fetch = LAZY)
#JoinColumn(name = "company_status_id", insertable = false, updatable = false)
private CompanyStatus companyStatus;
#JsonProperty
#ManyToOne(fetch = LAZY)
#JoinColumn(name = "employee_status_id", insertable = false, updatable = false)
private EmployeeStatus employeeStatus;
}
Your model could look like the following:
#EntityView(CompanyStatus.class)
public interface CompanyStatusDTO {
#IdMapping
Integer getCompanyStatusId();
String getCompanyStatusLabel();
#Mapping("employeeStatuses.employeeStatus")
List<EmployeeStatusDTO> getEmployeeStatusDTOs();
}
#EntityView(EmployeeStatus.class)
public interface EmployeeStatusDTO {
#IdMapping
Integer getEmployeeStatusId();
String getEmployeeStatusName();
}
Querying is a matter of applying the entity view to a query, the simplest being just a query by id.
CompanyStatusDTO c = entityViewManager.find(entityManager, CompanyStatusDTO.class, id);
The Spring Data integration allows you to use it almost like Spring Data Projections: https://persistence.blazebit.com/documentation/entity-view/manual/en_US/index.html#spring-data-features
You can try this:
public class Dao{
private SessionFactory sessionFactory;
public Dao(SessionFactory sessionFactory) {
this.sessionFactory = sessionFactory;
}
public <T> T save(final T o){
return (T) sessionFactory.getCurrentSession().save(o);
}
public void delete(final Object object){
sessionFactory.getCurrentSession().delete(object);
}
public <T> T get(final Class<T> type, final Long id){
return (T) sessionFactory.getCurrentSession().get(type, id);
}
public <T> List<T> getAll(final Class<T> type) {
final Session session = sessionFactory.getCurrentSession();
final Criteria crit = session.createCriteria(type);
return crit.list();
}
// and so on, you should get the idea
and you can then access like so in the service layer:
private Dao dao;
#Transactional(readOnly = true)
public List<MyEntity> getAll() {
return dao.getAll(MyEntity.class);
}

Many to Many relation with extra column query Hibernate

I did a #ManyToMany relationship in Hibernate with an extra column successfully, as follows.
Activity.class
#Entity
#Table(name = "Activity")
public class Activity {
#Id
#GeneratedValue
private int actId;
#OneToMany(fetch = FetchType.EAGER, mappedBy = "pk.activity",
cascade = { CascadeType.ALL, CascadeType.REMOVE })
private Set<ActivityRepairMap> activityRepairMaps = new HashSet<ActivityRepairMap>();
#NotEmpty
private String actTurno;
#NotEmpty
private String actTexto;
private String actFhc;
public Activity() {
}
// Getters and Setters
}
Repair.class
#Entity
#Table(name = "Repair2")
public class Repair {
#Id
#GeneratedValue
private int repId;
#OneToMany(fetch = FetchType.EAGER, mappedBy = "pk.repair")
private Set<ActivityRepairMap> activityRepairMaps = new HashSet<ActivityRepairMap>();
#NotEmpty(message=Constants.EMPTY_FIELD)
private String repNombre;
private Integer repCant;
public Repair() {
}
// Getters and Setters
}
ActivityRepairMap.class
#Entity
#Table(name="ActivityRepairMap")
#AssociationOverrides({
#AssociationOverride(name="pk.activity", joinColumns=#JoinColumn(name="actId")),
#AssociationOverride(name="pk.repair", joinColumns=#JoinColumn(name="repId"))
})
public class ActivityRepairMap {
private ActivityRepairId pk = new ActivityRepairId();
private Integer actRepCant;
#EmbeddedId
public ActivityRepairId getPk() {
return pk;
}
public void setPk(ActivityRepairId pk) {
this.pk = pk;
}
#Transient
public Activity getActivity() {
return getPk().getActivity();
}
public void setActivity(Activity activity) {
getPk().setActivity(activity);
}
#Transient
public Repair getRepair() {
return getPk().getRepair();
}
public void setRepair(Repair repair) {
getPk().setRepair(repair);
}
#Column(name="actRepCant")
public Integer getActRepCant() {
return actRepCant;
}
public void setActRepCant(Integer actRepCant) {
this.actRepCant = actRepCant;
}
public ActivityRepairMap (){
}
// hashCode and equals methods
}
ActivityRepairId
#Embeddable
public class ActivityRepairId implements Serializable{
private static final long serialVersionUID = -776429030880521951L;
private Activity activity;
private Repair repair;
#ManyToOne
public Activity getActivity() {
return activity;
}
public void setActivity(Activity activity) {
this.activity = activity;
}
#ManyToOne
public Repair getRepair() {
return repair;
}
public void setRepair(Repair repair) {
this.repair = repair;
}
// hashCode and equals method
}
My problem is that I can't query all the repairs used in a specific activity.
I've already checked in MySQL Workbench that the data stored in the DB is correct.
I would appreciate if anyone can explain me either using HQL or Criteria how can I achieve this.
Thanks a lot.
In SQL this should be:
SELECT
r.*
FROM
repair r
LEFT JOIN
activity_repair ar
ON
ar.repair_id = r.id
WHERE
ar.activity_id = ?
Now it's still possible that a single activity is connected with two repairs, and though you might get some repairs twice in the result list. You could simple use a SELECT DISTINCT r.* to work around this, or work with a subquery.
In JPQL the query should be bascially the same as the SQL from above.
SELECT
r
FROM
Repair r
WHERE
r.activityRepairMaps.pk.activity = ?
If you need a JOIN:
SELECT
r
FROM
Repair r
JOIN
ActivityRepairMap arm
WHERE
arm.pk.activity = ?
Maybe you need to use #MapsId within your ActivityRepairMaps class. (I haven't done JPQL for a while now)
As a far as I remember, you should NOT use Entities within your #EmbeddedId classes, but instead use the raw #Id type of the corresponding classes. Instead of Repair and Activity, you should use int and int.

queryDSL entity's list contains all

So I have really been struggling to figure this out but there doesn't seem to be good documentation on how to do this. I have an entity RepairMan with a list of Skill entities. I need a query that can return a List<RepairMan> whose list of skills contain all the skill id's. Here is what the entities look like:
#Entity
#Table(name = "REPAIRMAN")
public class RepairMan implements Serializable
{
private static final long serialVersionUID = 8151638047721448259L;
#SequenceGenerator(name="REPAIRMAN_SEQ", sequenceName="REPAIRMAN_SEQ", allocationSize=1, initialValue=100)
#Id #GeneratedValue(generator="REPAIRMAN_SEQ")
private Long id;
#OneToMany
#JoinTable(name="REPAIRMAN_SKILLS", joinColumns=#JoinColumn(name="REPAIRMAN_ID"), inverseJoinColumns=#JoinColumn(name="SKILL_ID"))
private List<Skill> skills;
....
}
#Entity
#Table(name = "SKILL")
public abstract class Skill implements Serializable
{
private static final long serialVersionUID = -5272849377636005084L;
#SequenceGenerator(name="SKILL_SEQ_GEN", sequenceName="SKILL_SEQ", allocationSize=1, initialValue=100)
#Id
#GeneratedValue(generator="SKILL_SEQ_GEN", strategy=GenerationType.SEQUENCE)
private Long id;
#Column(name="NAME")
private String name;
#Column(name="DESCRIPTION")
private String description;
...
}
And here's the desired signature and what I have been able to work out in my mind:
public class RepairManRepositoryImpl extends QueryDslRepositorySupport implements RepairManRepositoryCustom
{
public CompanyInspectorRepositoryImpl()
{
super(RepairMan.class);
}
#Override
public List<RepairMan> getRepairMenByRequiredSkills(List<Long> skillIds)
{
PathBuilder<RepairMan> repairManPath = new PathBuilder<>(RepairMan.class, "repairman");
PathBuilder repairManSkillsPath = repairManPath.get("skills"); // probably wrong
BooleanBuilder hasAllSkills = new BooleanBuilder();
for (Long skillId : skillIds)
{
hasAllSkills.and(repairManSkillsPath.getNumber("id", Long.class).eq(skillId));
}
JPAQuery query = new JPAQuery(getEntityManager())
.from(repairManPath)
//need to join the repairManSkills somehow
.where(hasAllSkills);
return query.list(repairManPath);
}
}
I know this doesn't exactly work, plus I understand it would be easier to use the Qclasses but for compatibility reasons I can't do Qclasses.

Categories