JPA join multiple table one to many and one to one - spring

I have this 4 tables on Oracle DB
FACTORY_MASTER
FACTORY_DETAILS
LOT_MASTER
LOT_DETAILS
Relations
FACTORY_MASTER 1 - N FACTORY_DETAILS
LOT_MASTER 1 - N LOT_DETAILS
FACTORY_DETAILS 1 - 1 LOT_DETAILS
This is the current implementation of Model class
public class FactoryMaster {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "factory_master_gen")
#SequenceGenerator(name = "factory_master_gen", sequenceName = "seq_factory_master")
#Column(name = "FACTORY_MASTER_ID")
private Long id;
#OneToMany(cascade = CascadeType.ALL,
orphanRemoval = true,
fetch = FetchType.LAZY)
#JoinColumn(name = "FACTORY_MASTER_ID",
referencedColumnName = "FACTORY_MASTER_ID",
nullable = false)
private List<FactoryDetail> details;
}
public class FactoryDetail {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "factory_detail_gen")
#SequenceGenerator(name = "factory_detail_gen", sequenceName = "seq_factory_detail")
#Column(name = "FACTORY_DETAIL_ID")
private Long id;
// I exclude on purpose ManyToOne, because you cannot have FactoryDetail without a Master
#OneToOne(mappedBy = "factoryDetail")
private LotDetail lotDetail;
}
public class LotMaster {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "lot_master_gen")
#SequenceGenerator(name = "lot_master_gen", sequenceName = "seq_lot_master")
#Column(name = "LOT_MASTER_ID")
private Long id;
#OneToMany(cascade = CascadeType.ALL,
orphanRemoval = true,
fetch = FetchType.LAZY)
#JoinColumn(name = "LOT_MASTER_ID",
referencedColumnName = "LOT_MASTER_ID",
nullable = false)
private List<LotDetail> details;
}
public class LotDetail {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "lot_detail_gen")
#SequenceGenerator(name = "lot_detail_gen", sequenceName = "seq_lot_detail")
#Column(name = "LOT_DETAIL_ID")
private Long id;
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "FACTORY_DETAIL_ID",
referencedColumnName = "FACTORY_DETAIL_ID")
private FactoryDetail factoryDetail;
}
I need to extract the factory_master using lot_master_id
So the query would be
select distinct fm.*
from factory_master fm
join factory_details fd on fd.factory_master_id = fm.factory_master_id
join lot_details ld on fd.lot_details_id = ld.lot_details_id
join lot_master lt on ld.lot_master_id = lt.lot_master_id
where lt.lot_master_id = :ID
How can get the same result via JPQL valid?
Thank you

May be using a cross join like this would help:
select distinct fm
from
FactoryMaster fm
inner join fm.details fd
inner join fd.lotDetail ld1
,LotMaster lm
inner join lm.details ld2
where
ld2.id = ld1.id
and
lm.id = :lotMasterId

#Query(value = "select distinct fm.*from factory_master fm
join factory_details fd on fd.factory_master_id = fm.factory_master_id
join lot_details ld on fd.lot_details_id = ld.lot_details_id
join lot_master lt on ld.lot_master_id = lt.lot_master_id
where lt.lot_master_id =:#{#Id}", nativeQuery = true)
List<FactoryMaster > getFactoryMaster(#Param("Id")Long Id);
u can use like this

Related

How can I solve error 'Multiple representations of the same entity'?

I am new using JPA and I'm getting an error when trying to insert more than a value into the ParticipantesEntity table.
So ConcursoEntity has a OneToMany Relation to ParticipantesEntity.
ENTITY
------
public class ConcursoEntity {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "CONCURSO_ID_GEN")
#SequenceGenerator(name = "CONCURSO_ID_GEN", sequenceName = "CONCURSO_SEQ", initialValue = 1, allocationSize = 1)
#Column(name = "ID", unique = true, nullable = false)
private Long id;
#Column(name = "PREMIO")
private String premio;
#Column(name = "RUT")
private String rut;
#Column(name = "FECHA_ACTUALIZACION")
private Timestamp fechaActualizacion;
#Column(name = "DIRECCION")
private String direccion;
#Column(name = "COMUNA")
private String comuna;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval = true)
#JoinColumn(name = "ID", referencedColumnName = "id")
private List<ParticipantesEntity> pticipantes;
}
public class ParticipantesEntity {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "PARTICIPANTES_ID_GEN")
#SequenceGenerator(name = "PARTICIPANTES_ID_GEN", sequenceName = "PARTICIPANTES_SEQ", initialValue = 1, allocationSize = 1)
#Column(name = "ID", unique = false, nullable = false)
private Long id;
#Column(name = "RUT")
private String rut;
#Column(name = "TIPO_PARTICIPANTE")
private String tipoParticipante;
}
ERROR
org.springframework.dao.InvalidDataAccessApiUsageException: Multiple representations of the same entity [cl.bch.cloud.ms.grts.web.entities.ParticipantesEntity#1] are being merged. Detached: [ParticipantesEntity(id=1, rut=216956245, tipoParticipante=PROPIETARIO)]; Detached: [ParticipantesEntity(id=1, rut=156324865, tipoParticipante=AVAL)]; nested exception is java.lang.IllegalStateException: Multiple representations of the same entity [cl.bch.cloud.ms.grts.web.entities.ParticipantesEntity#1] are being merged. Detached: [ParticipantesEntity(id=1, rut=216956245, tipoParticipante=PROPIETARIO)]; Detached: [ParticipantesEntity(id=1, rut=156324865, tipoParticipante=AVAL)]
at org.springframework.orm.jpa.EntityManagerFactoryUtils.convertJpaAccessExceptionIfPossible(EntityManagerFactoryUtils.java:371)
at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:257)
REQUEST
{
"id":1,
"premio":"auto",
"rut":"162332112",
"direccion":"LOS JACINTOS 374",
"comuna":"VALPARAISO",
"participantes":[
{
"id": 1,
"rut":"156324865",
"tipoParticipante":"AVAL",
},
{
"id": 1,
"rut":"216956245",
"tipoParticipante":"PROPIETARIO",
}
]
}
I've tried adding a #ManyToOne relation in table ParticipantesEntity, but gives me the same error.

Criteria API Specification - Filter records and return only the latest record for many to one mappings

I have two tables, Lead and LeadActivity. A lead can have many lead activities and mapping is done as #ManyToOne form LeadActivity to Lead.
Problem Statement -I want to to filter LeadActivity records such that, If there are more than one leadactivity records with same leadId, i should get only one record which is latest (have max primary key). Can anyone guide me on how to write specification or criteria API in such situations? I know this can be achieved through other ways but I have to use specification API. Below are the entity classes
Lead
#Entity
#Table(name = "customer_lead")
#Where(clause = ReusableFields.SOFT_DELETED_CLAUSE)
#Audited(withModifiedFlag = true)
#Data
public class Lead extends ReusableFields implements Serializable
{
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "lead_id", updatable = false, nullable = false)
Long leadId;
#Column(name = "name")
String customerName;
#Column(name = "primary_mobile")
String primaryMobile;
#Column(name = "secondary_mobile")
String secondaryMobile;
//more fields
}
Lead Activity
#Entity
#Table(name = "LeadActivity")
#Data
#Where(clause = ReusableFields.SOFT_DELETED_CLAUSE)
public class LeadActivity extends ReusableFields implements Serializable
{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "leadactivity_id", updatable = false, nullable = false)
Long leadActivityId;
#Column(name = "activity_date_time", nullable = false)
#NonNull
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm")
Date activityDateTime;
#Column(name = "title")
#NonNull
String title;
#OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name = "lead_id", nullable = false)
#JsonIgnoreProperties(
{ "hibernateLazyInitializer", "handler" })
#NotFound(action = NotFoundAction.IGNORE)
Lead lead;
//More fields
}
Expected Output - Suppose there are two records present with same leadId. I want to fetch only the latest among them based on their id. One with lower id should be ignored
Try this:
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<LeadActivity> cq = cb.createQuery(LeadActivity.class);
Root<LeadActivity> rootLeadActivity = cq.from(LeadActivity.class);
Join<LeadActivity,Lead> joinLead = rootLeadActivity.join(LeadActivity_.lead,JoinType.INNER);
/* If you dont use metamodel:
* Join<LeadActivity,Lead> joinLead = rootLeadActivity.join("lead",JoinType.INNER);
*/
// For performance, if you use JPA 2.1 set the leader id condition in the join
joinLead.on(cb.equal(joinLead.get(Lead_.leadId),LEAD_ID));
List<Predicate> predicatesList= new ArrayList<>();
/* if you use version 2.0 you will have to put it in the where
* predicatesList.add(cb.equal(joinLead.get(Lead_.leadId),LEAD_ID));
*/
Subquery<Long> sqMaxId = cq.subquery(Long.class);
Root<LeadActivity> sqRootActivity = sqMaxId.from(LeadActivity.class);
Join<LeadActivity,Lead> sqJoinLead = sqRootActivity.join(LeadActivity_.lead,JoinType.INNER);
sqMaxId.where(cb.equal(sqJoinLead.get(Lead_.leadId),joinLead.get(Lead_.leadId)));
sqMaxId.select(cb.max(sqRootActivity.get(LeadActivity_.leadActivityId)));
predicatesList.add(cb.equal(rootLeadActivity.get(LeadActivity_.leadActivityId),sqMaxId.getSelection()));
cq.where(predicatesList.toArray(new Predicate[predicatesList.size()]));
cq.multiselect(rootLeadActivity);
The result query:
select a.* from lead_activity a
inner join lead l on a.lead_id = l.lead_id and l.lead_id = LEAD_ID
where a.lead_activity_id =
(select max(lead_activity_id) from lead_activity where lead_id = LEAD_ID)

JPA Repository - Getting duplicates in List caused by table joins

I am having a very difficult situation and yet the situation is very complex and hard to find similar case in stackoverflow.
I have the following entities
Store
#Data
#Entity
#Table(name = "store")
public class Store implements IModel {
#Id
#EqualsAndHashCode.Exclude
#GeneratedValue(strategy = GenerationType.AUTO)
private UUID id;
#Enumerated(EnumType.STRING)
#Column(name = "storestatus", nullable = false)
private StoreStatus storeStatus = StoreStatus.UNKNOWN;
#OneToOne
#JoinColumn(name = "storetypecode_id")
private StoreTypeCode storeTypeCode;
#OneToOne(cascade = CascadeType.ALL, mappedBy = "store")
private Address address;
#Setter(AccessLevel.NONE)
#EqualsAndHashCode.Exclude
#OneToMany(fetch = FetchType.EAGER, mappedBy = "store")
private Set<StoreTranslation> storeTranslationList = new HashSet<>();
public Store() {
}
StoreTypeCode
#Data
#Entity
#Table(name = "storetypecode")
public class StoreTypeCode implements IModel {
#Id
#EqualsAndHashCode.Exclude
#GeneratedValue(strategy = GenerationType.AUTO)
private UUID id;
#Column(name = "displaysort", nullable = false)
private Integer displaySort = 999;
#Setter(AccessLevel.NONE)
#EqualsAndHashCode.Exclude
#OneToMany(fetch = FetchType.EAGER, mappedBy = "storeTypeCode")
private Set<StoreTypeCodeTranslation> storeTypeCodeTranslationList = new HashSet<>();
public StoreTypeCode() {
}
}
And StoreCategory
#Data
#Entity
#Table(name = "storeitemcategory")
public class StoreItemCategory implements IModel {
#Id
#EqualsAndHashCode.Exclude
#GeneratedValue(strategy = GenerationType.AUTO)
private UUID id;
#ManyToOne
#JoinColumn(name = "store_id")
private Store store;
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(
name = "storeitemcategory_storeitem",
joinColumns = {#JoinColumn(name = "storeitemcategory_id", referencedColumnName = "id")},
inverseJoinColumns = {#JoinColumn(name = "storeitem_id", referencedColumnName = "id")})
private List<StoreItem> storeItems = new ArrayList<>();
#Setter(AccessLevel.NONE)
#EqualsAndHashCode.Exclude
#OneToMany(fetch = FetchType.EAGER, mappedBy = "storeItemCategory")
private Set<StoreItemCategoryTranslation> storeItemCategoryTranslationList = new HashSet<>();
public StoreItemCategory() {
}
public void addStoreItem(StoreItem storeItem) {
this.storeItems.add(storeItem);
}
}
With the above relationship, here is what I have.
Store A with storeTypeCode ("Cafe") and storeItemCategory ("Iced drinks")
StoreTypeCode has two translations 1) for English, 2) for Chinese.
Whenever I add an item to storeItems in StoreItemCategory, I get duplicates in the list. (And multiple duplicate records are inserted to 'storeitemcategory_storeitem' table.)
StoreItemCategory sic = storeItemCategoryRepository.findById(storeItemCategoryid).get();
sic.addStoreItem(new StoreItem(...));
sic = storeItemCategoryRepository.save(sic);
I suspect this has something to do with the way tables are joined for translations because when I run a query created from Spring for getting StoreItemCategory, I get multiple records of StoreItemCategory (one for English and one for Chinese from StoreTypeCode).
select
*
from
storeitemcategory storeitemc0_
left outer join
store store1_
on storeitemc0_.store_id=store1_.id
left outer join
storetranslation storetrans2_
on store1_.id=storetrans2_.store_id
left outer join
storetypecode storetypec3_
on store1_.storetypecode_id=storetypec3_.id
left outer join
storetypecodetranslation storetypec4_
on storetypec3_.id=storetypec4_.storetypecode_id
left outer join
address address5_
on store1_.id=address5_.store_id
left outer join
storeitemcategorytranslation storeitemc6_
on storeitemc0_.id=storeitemc6_.storeitemcategory_id
left outer join
storeitemcategory_storeitem storeitems7_
on storeitemc0_.id=storeitems7_.storeitemcategory_id
left outer join
storeitem storeitem8_
on storeitems7_.storeitem_id=storeitem8_.id
left outer join
store store9_
on storeitem8_.store_id=store9_.id
left outer join
storeitemtranslation storeitemt10_
on storeitem8_.id=storeitemt10_.storeitem_id
where
storeitemc0_.id=?
All my tables will have translations tables and I am not sure how to get-around with this without using set.
Does anyone have similar experience?

Left Join Fetch Behaving Like Inner Join

I have a one-to-many relationship between routes and stops. In order to maintain an audit trail, my Stop entities have a "historic" boolean.
When fetching a route, I want to ignore historic stops, and so I constructed this query:
#Query("select r from Route r " +
"left join fetch r.schedules schedule " +
"left join fetch r.stops stop " +
"where r.routeId = :routeId and stop.historic = false ")
Optional<Route> findByIdLoadStops(#Param("routeId") int routeId);
This works fine when the route has non-historic stops and no stops, but when the route only has a historic stop (which shouldn't happen but I want to be able to at least handle it), it returns an empty optional as though an inner join has been performed.
When logging the JPA query created by hibernate, I can see that the query uses a left outer join.
What have I done incorrectly?
Route and Stop entities:
#Table(name = "route")
#Entity
public class Route {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "routeId", columnDefinition = "SMALLINT(5) UNSIGNED")
private int routeId;
#Column(name = "locked")
private boolean locked = false;
#OneToMany(mappedBy = "route",
cascade = CascadeType.ALL,
fetch = FetchType.LAZY)
#OrderBy("stopTime asc")
private SortedSet<Stop> stops = new TreeSet<>();
public Route() {
}
}
#Table(name = "stop", uniqueConstraints = {
#UniqueConstraint(columnNames = {"stopTime", "routeId"}),
#UniqueConstraint(columnNames = {"stopName", "routeId"})})
#Entity
public class Stop implements Comparable<Stop> {
#Id
#Column(name = "stopId")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int stopId;
#Column(name = "routeId",
columnDefinition = "SMALLINT(5)",
updatable = false, insertable = false)
private int routeId;
#ManyToOne(cascade = CascadeType.MERGE,
fetch = FetchType.LAZY)
#JoinColumn(name = "routeId")
private Route route;
#Column(name = "stopTime")
private LocalTime stopTime;
#Column(name = "stopName")
private String stopName;
#JoinColumn(name = "originalId", referencedColumnName = "stopId")
#ManyToOne(fetch = FetchType.LAZY)
private Stop originalStop = this;
#Column(name = "historic")
private boolean historic = false;
public Stop() {
}
}

How to create Predicate BooleanExpression for many to many relations in QueryDSL

How can inner join be done on Many to Many relations using Predicate BooleanExpression?
I have 2 entities
public class A {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
#ManyToMany(fetch = FetchType.LAZY,
cascade = { CascadeType.DETACH, CascadeType.MERGE,
CascadeType.REFRESH, CascadeType.PERSIST})
#JoinTable(name = "a_b_maps",
joinColumns = #JoinColumn(name = "a_id", nullable =
false,referencedColumnName = "id"),
inverseJoinColumns = #JoinColumn(name = "b_id", nullable = false,
referencedColumnName = "id")
)
private Set<B> listOfB = new HashSet<B>();
}
public class B {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
#ManyToMany(fetch = FetchType.LAZY,
cascade = { CascadeType.DETACH, CascadeType.MERGE,
CascadeType.REFRESH, CascadeType.PERSIST})
#JoinTable(name = "a_b_maps",
joinColumns = #JoinColumn(name = "b_id", nullable =
false,referencedColumnName = "id"),
inverseJoinColumns = #JoinColumn(name = "a_id", nullable = false,
referencedColumnName = "id")
)
private Set<A> listOfA = new HashSet<A>();
}
A Base repo
#NoRepositoryBean
public interface BaseRepository<E, I extends Serializable>
extends JpaRepository<E, I> {
}
And a repository class for A
public interface Arepo extends BaseRepository<A, Integer>,
QueryDslPredicateExecutor<A> {
Page<A> findAll(Predicate predicate, Pageable pageRequest);
}
Now I want to use A Repo with Predicate query. I need to form a predicate where I can load A based on some given Bs
I tried
QA a = QA.a;
QB b = QB.b;
BooleanExpression boolQuery = null;
JPQLQuery<A> query = new JPAQuery<A>();
query.from(a).innerJoin(a.listOfB, b)
.where(b.id.in(someList));
Now I am able to form a JPQLQuery, but the repository expects a Predicate. How can I get Predicate from the JPQLQuery??
Or, how can the inner join be achieved using Predicate?
I am able to create a Predicate with the help of answer given here
https://stackoverflow.com/a/23092294/1969412.
SO, instead of using JPQLQuery, I am directly using
a.listOfB.any()
.id.in(list);
This is working like a charm.

Resources