JPA GROUP BY entity - is this possible? - java

Is possible to select data in JPA with grouping by referenced entity?
I mean: I have two entities - insurance and referenced many-to-one vehicle. Insurance entity has validTill field (and vehicle field of course).
I'd like to select vehicle and it's latest insurance. The query below doesn't work:
SELECT DISTINCT v.vehicle,
max(v.validTill) as lastValidTill
FROM TraInsurance v
GROUP BY v.vehicle
ORDER BY lastValidTill
The query above fails with error:
ERROR: column "travehicle1_.id_brand" must appear in the GROUP BY clause or be used in an aggregate function
This is because JPA adds all fields from referenced vehicle to query and not to GROUP BY. Is here something I do wrong? Or maybe it's just not possible to do this?
EDIT:
TraInsurance entity
#Entity
#Table(name = "TRA_INSURANCES", schema="public")
#SequenceGenerator(name = "TRA_INSURANCES_SEQ", sequenceName = "TRA_INSURANCES_SEQ", allocationSize = 1)
public class TraInsurance implements EntityInt, Serializable {
private static final long serialVersionUID = 1L;
#Id
#Column(name = "id", nullable = false)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "TRA_INSURANCES_SEQ")
private Long id;
#NotNull
#ManyToOne
#JoinColumn(nullable = false, name = "id_vehicle")
private TraVehicle vehicle;
#NotNull
#Column(name = "valid_from", nullable = false)
private Date validFrom;
#Column(name = "valid_till", nullable = false)
private Date validTill;
#NotNull
#ManyToOne
#JoinColumn(nullable = false, name = "id_company")
private Company company;
#Column(name = "policy_no", nullable = true, length = 50)
private String policyNumber;
#Column(name = "rate", nullable = true, precision = 12, scale = 2)
private BigDecimal rate;
#Column(name = "discount_percent", nullable = true)
private Float discountPercent;
#Column(nullable = true)
private String description;
public TraInsurance() {}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public TraVehicle getVehicle() {
return vehicle;
}
public void setVehicle(TraVehicle vehicle) {
this.vehicle = vehicle;
}
public Date getValidFrom() {
return validFrom;
}
public void setValidFrom(Date validFrom) {
this.validFrom = validFrom;
}
public Date getValidTill() {
return validTill;
}
public void setValidTill(Date validTill) {
this.validTill = validTill;
}
public Company getCompany() {
return company;
}
public void setCompany(Company company) {
this.company = company;
}
public String getPolicyNumber() {
return policyNumber;
}
public void setPolicyNumber(String policyNumber) {
this.policyNumber = policyNumber;
}
public BigDecimal getRate() {
return rate;
}
public void setRate(BigDecimal rate) {
this.rate = rate;
}
public Float getDiscountPercent() {
return discountPercent;
}
public void setDiscountPercent(Float discountPercent) {
this.discountPercent = discountPercent;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((id == null) ? 0 : id.hashCode());
result = prime * result
+ ((validFrom == null) ? 0 : validFrom.hashCode());
result = prime * result + ((vehicle == null) ? 0 : vehicle.hashCode());
return result;
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (!(obj instanceof TraInsurance))
return false;
TraInsurance other = (TraInsurance) obj;
if (id == null) {
if (other.id != null)
return false;
} else if (!id.equals(other.id))
return false;
if (validFrom == null) {
if (other.validFrom != null)
return false;
} else if (!validFrom.equals(other.validFrom))
return false;
if (vehicle == null) {
if (other.vehicle != null)
return false;
} else if (!vehicle.equals(other.vehicle))
return false;
return true;
}
}

Please explicitly use JOIN in this use case:
SELECT ve, MAX(v.validTill)
FROM TraInsurance v JOIN v.vehicle ve
GROUP BY ve
ORDER BY MAX(v.validTill)

Apparently the JPA spec allows that but at least Hibernate's implementation does not support it (see HHH-2436 and HHH-1615).

If you pass an entity inside the GROUP BY, Hibernate automatically adds its id to the transformed SQL of the underlying DB. In addition, the values in the GROUP BY must exist in the SELECT clause. Thus, instead of select the whole object, you can select its id, then from those ids, you can retrieve the object again.
SELECT DISTINCT v.vehicle.id, max(v.validTill)
FROM TraInsurance v
GROUP BY v.vehicle.id
ORDER BY max(v.validTill)
If it takes time and requires DB hits to retrieve Vehicle objects from their ids, you can select all of Vehicle's attributes in the SELECT and put them in the GROUP BY. Then you can construct the Vehicle object from those attributes without accessing to DB.

Related

Hibernate error Not-null property references a transient value - transient instance must be saved before current operation

I have two related entities, one a main table and a column in the main table has a reference table.
The error is:
Caused by : javax.el.ELException: /jsf/submit.xhtml #20,76 listener="#{BankLocationMB.saveLocation}": org.springframework.dao.InvalidDataAccessApiUsageException: org.hibernate.TransientPropertyValueException:
Not-null property references a transient value - transient instance must be saved before current operation : bank.entity.BankLocation.bankFormat -> bank.entity.RefBankFormat;
nested exception is java.lang.IllegalStateException: org.hibernate.TransientPropertyValueException: Not-null property references a transient value - transient instance must be saved before current operation :
bank.entity.BankLocation.bankFormat -> bank.entity.RefBankFormat
#Entity
#Table(name = "BANK_LOCATION", schema = "OWNR")
public class BankLocation implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Basic(optional = false)
#NotNull
#Column(name = "BANK_LOCATION_ID")
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "BANK_LOCATION_ID_SEQ")
#SequenceGenerator(name = "BANK_LOCATION_ID_SEQ", sequenceName = "OWNR.BANK_LOCATION_ID_SEQ", allocationSize = 1)
private Long bankLocationId;
#Size(max = 32)
#Column(name = "BANK_NAME")
private String bankName;
#JoinColumn(name = "BANK_FORMAT_ID", referencedColumnName = "BANK_FORMAT_ID")
#ManyToOne(targetEntity=RefBankFormat.class, optional = false)
private RefBankFormat bankFormat;
public RefBankFormat getBankFormat() {
return bankFormat;
}
public void setBankFormat(RefBankFormat bankFormat) {
this.bankFormat = bankFormat;
}
#Override
public int hashCode() {
int hash = 0;
hash += (bankLocationId != null ? bankLocationId.hashCode() : 0);
return hash;
}
#Override
public boolean equals(Object object) {
// TODO: Warning - this method won't work in the case the id fields are not set
if (!(object instanceof BankLocation)) {
return false;
}
BankLocation other = (BankLocation) object;
if ((this.bankLocationId == null && other.bankLocationId != null) || (this.bankLocationId != null && !this.bankLocationId.equals(other.bankLocationId))) {
return false;
}
return true;
}
#Override
public String toString() {
return "bank.entity.BankLocation[ bankLocationId=" + bankLocationId + " ]";
}
}
Reference Table Entity
#Entity
#Table(name = "REF_BANK_FORMAT", schema = "OWNR")
public class RefBankFormat implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Basic(optional = false)
#NotNull
#Column(name = "BANK_FORMAT_ID")
private Integer bankFormatId;
#Basic(optional = false)
#NotNull
#Size(min = 1, max = 50)
#Column(name = "DISPLAY_NAME")
private String displayName;
#Size(max = 50)
#Column(name = "DESCRIPTION")
private String description;
public RefBankFormat() {
}
public RefBankFormat(Integer bankFormatId) {
this.bankFormatId = bankFormatId;
}
public RefBankFormat(Integer bankFormatId, String displayName) {
this.bankFormatId = bankFormatId;
this.displayName = displayName;
}
public Integer getbankFormatId() {
return bankFormatId;
}
public void setbankFormatId(Integer bankFormatId) {
this.bankFormatId = bankFormatId;
}
public String getDisplayName() {
return displayName;
}
public void setDisplayName(String displayName) {
this.displayName = displayName;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
#Override
public int hashCode() {
int hash = 0;
hash += (bankFormatId != null ? bankFormatId.hashCode() : 0);
return hash;
}
#Override
public boolean equals(Object object) {
// TODO: Warning - this method won't work in the case the id fields are not set
if (!(object instanceof RefBankFormat)) {
return false;
}
RefBankFormat other = (RefBankFormat) object;
if ((this.bankFormatId == null && other.bankFormatId != null) || (this.bankFormatId != null && !this.bankFormatId.equals(other.bankFormatId))) {
return false;
}
return true;
}
#Override
public String toString() {
return "bank.entity.RefBankFormat[ bankFormatId=" + bankFormatId + " ]";
}
}
Could anyone provide a fix where I'm going wrong?
#ManyToOne(targetEntity=RefBankFormat.class, optional = false)
private RefBankFormat bankFormat;
bankFormat is set mandatory(not null) so you first have to save this object in the database and then you are allowed to save BankLocation object.
OR
define a strategy for transitive persistence, a.k.a CascadeType.

JPA Criteria Query - How to implement Join on two tables to get desired result in single Query

I have 2 classes mapped with db Tables.
Composite Primary Key Class :
#Embeddable
public class Pk implements Serializable, Cloneable {
#Column(name = "dataId")
private String dataId;
#Column(name = "occurrenceTime")
private Timestamp occurrenceTime;
public String getDataId() {
return dataId;
}
public Pk setDataId(String dataId) {
this.dataId = dataId;
return this;
}
public Timestamp getOccurrenceTime() {
return occurrenceTime;
}
public Pk setOccurrenceTime(Timestamp occurrenceTime) {
this.occurrenceTime = occurrenceTime;
return this;
}
#Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Pk pk = (Pk) o;
return Objects.equals(getDataId(), pk.getDataId()) &&
Objects.equals(getOccurrenceTime(), pk.getOccurrenceTime());
}
#Override
public int hashCode() {
return Objects.hash(getDataId(), getOccurrenceTime());
}
}
1 : LoadProfile
#Entity
#Table(name = "energy")
public class LoadProfile implements Serializable, Cloneable {
public LoadProfile() {
}
#EmbeddedId
private Pk pk;
#Column(name = "RECEIVE_TIME")
private Timestamp reportingTime;
#Column(name = "DATA1")
private Double DATA1;
#OneToOne
#JoinColumns({
#JoinColumn(name = "dataId", insertable = false, updatable = false, referencedColumnName = "dataId"),
#JoinColumn(name = "occurrenceTime", insertable = false, updatable = false, referencedColumnName = "occurrenceTime")
})
private ForwardPower forwardPower;
public Pk getPk() {
return pk;
}
public LoadProfile setPk(Pk pk) {
this.pk = pk;
return this;
}
public Timestamp getReportingTime() {
return reportingTime;
}
public LoadProfile setReportingTime(Timestamp reportingTime) {
this.reportingTime = reportingTime;
return this;
}
public Double getDATA1() {
return DATA1;
}
public LoadProfile setDATA1(Double DATA1) {
this.DATA1 = DATA1;
return this;
}
public ForwardPower getForwardPower() {
return forwardPower;
}
public LoadProfile setForwardPower(
ForwardPower forwardPower) {
this.forwardPower = forwardPower;
return this;
}
#Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
LoadProfile that = (LoadProfile) o;
return Objects.equals(getPk(), that.getPk());
}
#Override
public int hashCode() {
return Objects.hash(getPk());
}
}
2 : ForwardPower
#Entity
#Table(name = "forward_power")
public class ForwardPower implements Serializable, Cloneable {
public ForwardPower() {
}
#EmbeddedId
private Pk pk;
#Column(name = "RECEIVE_TIME")
private Timestamp reportingTime;
#Column(name = "DATA2")
private Double DATA2;
public Pk getPk() {
return pk;
}
public ForwardPower setPk(Pk pk) {
this.pk = pk;
return this;
}
public Timestamp getReportingTime() {
return reportingTime;
}
public ForwardPower setReportingTime(Timestamp reportingTime) {
this.reportingTime = reportingTime;
return this;
}
public Double getDATA2() {
return DATA2;
}
public ForwardPower setDATA2(Double DATA2) {
this.DATA2= DATA2;
return this;
}
#Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
ForwardPower that = (ForwardPower) o;
return Objects.equals(getPk(), that.getPk());
}
#Override
public int hashCode() {
return Objects.hash(getPk());
}
}
I want to execute a query
Select * From energy e
Left join forward_power fp
on fp.dataId== e.dataId and fp.occurrenceTime == e.occurrenceTime
where e.occurrenceTime >= '2017-12-28 00:00:00'
and e.occurrenceTime <= '2018-01-02 00:00:00'
Limit 1000;
I wrote a equivalent Query in java using JPA criteria Query
CriteriaBuilder cb = session.getCriteriaBuilder();
CriteriaQuery<LoadProfile> cq = cb.createQuery(LoadProfile.class);
Root<LoadProfile> loadProfileRoot = cq.from(LoadProfile.class);
Join<LoadProfile, ForwardPower> join = loadProfileRoot.join(LoadProfile_.forwardPower);
List<Predicate> conditions = new ArrayList();
conditions.add(cb.equal(loadProfileRoot.get(LoadProfile_.pk).get(Pk_.dataId), join.get(
ForwardPower_.pk).get(Pk_.dataId)));
conditions.add(cb.equal(loadProfileRoot.get(LoadProfile_.pk).get(Pk_.occurrenceTime),
join.get(ForwardPower_.pk).get(Pk_.occurrenceTime)));
conditions.add(
cb.greaterThanOrEqualTo(loadProfileRoot.get(LoadProfile_.pk).get(Pk_.occurrenceTime),
config.getDataStartTime()));
conditions.add(
cb.lessThanOrEqualTo(loadProfileRoot.get(LoadProfile_.pk).get(Pk_.occurrenceTime),
config.getDataEndTime()));
cq.select(loadProfileRoot);
cq.where(conditions.toArray(new Predicate[]{}));
Query query = session.createQuery(cq);
List list = query.setFirstResult(0).setMaxResults(1000).getResultList();
I set the Option hibernate.show_sql = true.
Now that query gives me exact 1000 desired result.
when i see the hibernate query which is generated by ORM by above code.
ORM create 1 query for energy table and 1000 queries for forwardpower table which cause performance issue and query take too much time aproximately 55 - 60 seconds for fetching 1000 records.
How i can create a criteria Query so that ORM generate exactly 1 query for that code?
Thanks in advance.
You can add a fetch type eager instructions on your relation, and the ForwardPower will be load with LoadProfile with any LoadProfile.find
#OneToOne(fetch=FetchType.EAGER)
#JoinColumns({
#JoinColumn(name = "dataId", insertable = false, updatable = false, referencedColumnName = "dataId"),
#JoinColumn(name = "occurrenceTime", insertable = false, updatable = false, referencedColumnName = "occurrenceTime")
})
private ForwardPower forwardPower;
Or you can add the fetch instruction in your query.
I'm not familiar with it but it's probably something like that
//instead of loadProfileRoot.join(LoadProfile_.forwardPower)
Join<LoadProfile, ForwardPower> join = (Join<LoadProfile, ForwardPower>) loadProfileRoot.fetch(LoadProfile_.forwardPower);
See JPA 2 Criteria Fetch Path Navigation for more information about fetch with CriteriaBuilder.

HIbernate - JPA: foreign key not set in ManyToOne relationship

I know this might be a common problem but so far I couldn't find any solution.
I have the following 2 entities: Mission and Representation. Both entities have composed primary keys, respectively:
MissionId [idImpXml, code, categorie]
RepresentationId [idImpXml, codeRepresentation]
There's a one-to-many relationship between Representation and Mission, meaning that each mission has a single representation and each representation can be used in different missions.
I use JPA repositories to persist the entities in my database:
public interface RepresentationDao extends JpaRepository <Representation, RepresentationId>
public interface MissionDao extends JpaRepository <Mission, MissionId>
I need to save first a set of representations, and THEN a set of missions.
The representations work correctly, but when I try to save a mission the foreign key to the representation remains empty.
This is my code:
// here representations are already persisted (in a separate transaction)..
RepresentationId representationId = new RepresentationId(idImpXml, codeRepresentation);
Representation representation = representationDao.findOne(representationId);
// representation is retrieved correctly
MissionId missionId = new MissionId(idImpXml, code, categorie);
Mission mission = new Mission(missionId, representation, label);
// I try to save the mission
missionDao.saveAndFlush(mission);
// THE FOREIGN KEY THAT SHOULD REFERENCE THE REPRESENTATION (CODE_REPRESENTATION) REMAINS NULL
Mission
#Entity
#Table(name = "MISSION", schema = "dbo", catalog ="TEST")
public class Mission implements java.io.Serializable {
private MissionId id;
private Representation representation;
private String label;
public Mission() {
}
public Mission(MissionId id, Representation representation, String label) {
this.id = id;
this.representation = representation;
this.label = label;
}
#EmbeddedId
#AttributeOverrides({
#AttributeOverride(name = "idImpXml", column = #Column(name = "ID_IMP_XML", nullable = false, precision = 38, scale = 0)),
#AttributeOverride(name = "code", column = #Column(name = "CODE", nullable = false)),
#AttributeOverride(name = "categorie", column = #Column(name = "CATEGORIE", nullable = false)) })
public MissionId getId() {
return this.id;
}
public void setId(MissionId id) {
this.id = id;
}
#MapsId("ID_IMP_XML")
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumns(
value={ #JoinColumn(name = "ID_IMP_XML", referencedColumnName = "ID_IMP_XML", updatable=false),
#JoinColumn(name = "CODE_REPRESENTATION", referencedColumnName = "CODE_REPRESENTATION", insertable=true, updatable=true)
})
public Representation getRepresentation() {
return this.representation;
}
public void setRepresentation(Representation representation) {
this.representation = representation;
}
#Column(name = "LABEL", nullable = false)
public String getLabel() {
return this.label;
}
public void setLabel(String label) {
this.label = label;
}
}
MissionId
#Embeddable
public class MissionId implements java.io.Serializable {
private Long idImpXml;
private String code;
private int categorie;
public MissionId() {
}
public MissionId(Long idImpXml, String code, int categorie) {
this.idImpXml = idImpXml;
this.code = code;
this.categorie = categorie;
}
#Column(name = "ID_IMP_XML", nullable = false, precision = 38, scale = 0)
public Long getIdImpXml() {
return this.idImpXml;
}
public void setIdImpXml(Long idImpXml) {
this.idImpXml = idImpXml;
}
#Column(name = "CODE", nullable = false)
public String getCode() {
return this.code;
}
public void setCode(String code) {
this.code = code;
}
#Column(name = "CATEGORIE", nullable = false)
public int getCategorie() {
return this.categorie;
}
public void setCategorie(int categorie) {
this.categorie = categorie;
}
public boolean equals(Object other) {
if ((this == other))
return true;
if ((other == null))
return false;
if (!(other instanceof MissionId))
return false;
MissionId castOther = (MissionId) other;
return ((this.getIdImpXml() == castOther.getIdImpXml()) || (this.getIdImpXml() != null
&& castOther.getIdImpXml() != null && this.getIdImpXml().equals(castOther.getIdImpXml())))
&& ((this.getCode() == castOther.getCode()) || (this.getCode() != null && castOther.getCode() != null
&& this.getCode().equals(castOther.getCode())))
&& (this.getCategorie() == castOther.getCategorie());
}
public int hashCode() {
int result = 17;
result = 37 * result + (getIdImpXml() == null ? 0 : this.getIdImpXml().hashCode());
result = 37 * result + (getCode() == null ? 0 : this.getCode().hashCode());
result = 37 * result + this.getCategorie();
return result;
}
}
Representation
#Entity
#Table(name = "REPRESENTATION", schema = "dbo", catalog ="TEST")
public class Representation implements Serializable {
private RepresentationId id;
private Long colorPattern;
private String typePattern;
private Integer thickness;
public Representation() {
}
public Representation(RepresentationId id) {
this.id = id;
}
public Representation(RepresentationId id, Long colorPattern, String typePattern,
Integer thickness, Long codeCouleurPattern2, String typePattern2, Integer epaisseurPattern2) {
this.id = id;
this.colorPattern = colorPattern;
this.typePattern = typePattern;
this.thickness = thickness;
}
#EmbeddedId
#AttributeOverrides({
#AttributeOverride(name = "idImpXml", column = #Column(name = "ID_IMP_XML", nullable = false, precision = 38, scale = 0)),
#AttributeOverride(name = "codeRepresentation", column = #Column(name = "CODE_REPRESENTATION", nullable = false)) })
public RepresentationId getId() {
return this.id;
}
public void setId(RepresentationId id) {
this.id = id;
}
#Column(name = "COLOR_PATTERN")
public Long getColorPattern() {
return this.colorPattern;
}
public void setColorPattern(Long colorPattern) {
this.colorPattern = colorPattern;
}
#Column(name = "TYPE_PATTERN")
public String getTypePattern() {
return this.typePattern;
}
public void setTypePattern(String typePattern) {
this.typePattern = typePattern;
}
#Column(name = "THICKNESS")
public Integer getThickness() {
return this.thickness;
}
public void setThickness(Integer thickness) {
this.thickness = thickness;
}
}
RepresentationId
#Embeddable
public class RepresentationId implements java.io.Serializable {
private Long idImpXml;
private int codeRepresentation;
public RepresentationId() {
}
public RepresentationId(Long idImpXml, int codeRepresentation) {
this.idImpXml = idImpXml;
this.codeRepresentation = codeRepresentation;
}
#Column(name = "ID_IMP_XML", nullable = false, precision = 38, scale = 0)
public Long getIdImpXml() {
return this.idImpXml;
}
public void setIdImpXml(Long idImpXml) {
this.idImpXml = idImpXml;
}
#Column(name = "CODE_REPRESENTATION", nullable = false)
public int getCodeRepresentation() {
return this.codeRepresentation;
}
public void setCodeRepresentation(int codeRepresentation) {
this.codeRepresentation = codeRepresentation;
}
public boolean equals(Object other) {
if ((this == other))
return true;
if ((other == null))
return false;
if (!(other instanceof RepresentationId))
return false;
RepresentationId castOther = (RepresentationId) other;
return ((this.getIdImpXml() == castOther.getIdImpXml()) || (this.getIdImpXml() != null
&& castOther.getIdImpXml() != null && this.getIdImpXml().equals(castOther.getIdImpXml())))
&& (this.getCodeRepresentation() == castOther.getCodeRepresentation());
}
public int hashCode() {
int result = 17;
result = 37 * result + (getIdImpXml() == null ? 0 : this.getIdImpXml().hashCode());
result = 37 * result + this.getCodeRepresentation();
return result;
}
}
try to override column definition by adding another field in your object and set the insertable and updatable properties to false in the #JoinColum, like this:
#MapsId("ID_IMP_XML")
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumns(
value={ #JoinColumn(name = "ID_IMP_XML", referencedColumnName = "ID_IMP_XML", insertable=false, updatable=false),
#JoinColumn(name = "CODE_REPRESENTATION", referencedColumnName = "CODE_REPRESENTATION", insertable=false, updatable=false)
})
public Representation getRepresentation() {
return this.representation;
}
private Integer representationCode;
#Column(name = "CODE_REPRESENTATION")
public Integer getRepresentationCode() {
return this.representationCode;
}

Many to Many relationships using Spring Boot, Jackson and Hibernate

I'm working on a rest project using Spring Boot and Hibernate and am currently trying to figure out how to handle my json-serialization.
The schema shown in the ERD above is mapped by Hibernate and works fine.
The problem arises when I make a get request to a controller. My understanding is that Spring now tries to serialize the object-chain using Jackson. Because both the parent and child objects have one another as an attribute, we find ourselves hitting an infinite recursion loop.
Now I've looked into #JsonIgnore, #JsonView, #JsonManagedReference and #JsonBackReference but these only seem to work for one-to-many relationships.
What I'm looking for is a situation where when I for instance make a GET request to /users/{id}, I get the user object including all it's relationship attributes (let's call it the full object), but the relationship attributes themselves don't show their relationship-attributes (minimized objects). This works fine with the annotations mentioned above, but how do I make this work the other way as well?
Desired response for: /users/{id}
{ // full user object
id: 1,
username: 'foo',
// password can be JsonIgnored because of obvious reasons
role: { // minimized role object
id: 1,
name: 'bar'
// NO USERS LIST
}
area: { //minimized area object
id: 2,
name: 'some val'
// NO USERS LIST
// NO TABLES LIST
}
}
Desired response for /userrole/{id}
{ // full role object
id: 1,
name: 'waiter'
users: [
{ // minmized user object
id: 1,
username: 'foo'
// password can be JsonIgnored because of obvious reasons
// NO ROLE OBJECT
// NO AREA OBJECT
},
{ // minmized user object
id: 1,
username: 'foo'
// password can be JsonIgnored because of obvious reasons
// NO ROLE OBJECT
// NO AREA OBJECT
}
]
}
In general: I'd like a full object when the request is made to the entity directly and a minimized object when requested indirectly.
Any Ideas? I hope my explanation is clear enough.
UPDATE
The Area, User and UserRole POJO's as requested in the comment sections.
User
#Entity
#Table(name = "users", schema = "public", catalog = "PocketOrder")
public class User {
private int id;
private String username;
private String psswrd;
private List<Area> areas;
private UserRole Role;
#Id
#Column(name = "id", nullable = false)
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
#Basic
#Column(name = "username", nullable = false, length = 20)
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
#Basic
#JsonIgnore
#Column(name = "psswrd", nullable = true, length = 40)
public String getPsswrd() {
return psswrd;
}
public void setPsswrd(String psswrd) {
this.psswrd = psswrd;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
if (id != user.id) return false;
if (username != null ? !username.equals(user.username) : user.username != null) return false;
if (psswrd != null ? !psswrd.equals(user.psswrd) : user.psswrd != null) return false;
return true;
}
#Override
public int hashCode() {
int result = id;
result = 31 * result + (username != null ? username.hashCode() : 0);
result = 31 * result + (psswrd != null ? psswrd.hashCode() : 0);
return result;
}
#ManyToMany(mappedBy = "users")
public List<Area> getAreas() {
return areas;
}
public void setAreas(List<Area> areas) {
this.areas = areas;
}
#ManyToOne
#JoinColumn(name = "role_fk", referencedColumnName = "id", nullable = false)
public UserRole getRole() {
return Role;
}
public void setRole(UserRole role) {
Role = role;
}
}
UserRole
#Entity
#javax.persistence.Table(name = "userroles", schema = "public", catalog = "PocketOrder")
public class UserRole {
private int id;
private String name;
private List<User> users;
#Id
#Column(name = "id", nullable = false)
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
#Basic
#Column(name = "name", nullable = false, length = 20)
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
UserRole userRole = (UserRole) o;
if (id != userRole.id) return false;
if (name != null ? !name.equals(userRole.name) : userRole.name != null) return false;
return true;
}
#Override
public int hashCode() {
int result = id;
result = 31 * result + (name != null ? name.hashCode() : 0);
return result;
}
#OneToMany(mappedBy = "role")
public List<User> getUsers() {
return users;
}
public void setUsers(List<User> users) {
users = users;
}
}
Area
#Entity
#javax.persistence.Table(name = "areas", schema = "public", catalog = "PocketOrder")
public class Area {
private int id;
private String name;
private List<User> users;
private List<Table> tables;
#Id
#Column(name = "id", nullable = false)
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
#Basic
#Column(name = "name", nullable = false, length = 20)
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Area area = (Area) o;
if (id != area.id) return false;
if (name != null ? !name.equals(area.name) : area.name != null) return false;
return true;
}
#Override
public int hashCode() {
int result = id;
result = 31 * result + (name != null ? name.hashCode() : 0);
return result;
}
#ManyToMany
#JoinTable(name = "areas_users", catalog = "PocketOrder", schema = "public", joinColumns = #JoinColumn(name = "area_fk", referencedColumnName = "id", nullable = false), inverseJoinColumns = #JoinColumn(name = "user_fk", referencedColumnName = "id", nullable = false))
public List<User> getUsers() {
return users;
}
public void setUsers(List<User> users) {
this.users = users;
}
#OneToMany(mappedBy = "area")
public List<Table> getTables() {
return tables;
}
public void setTables(List<Table> tables) {
this.tables = tables;
}
}
Try use #JsonSerialize on specific points:
For sample:
1 - Map your field
#JsonSerialize(using = ExampleSampleSerializer.class)
#ManyToOne
private Example example;
2 - Create custom jackson serializer (Here you can control the serialization)
public class ExampleSampleSerializer extends JsonSerializer<Example> {
#Override
public void serialize(Example value, JsonGenerator jsonGenerator, SerializerProvider serializers) throws IOException, JsonProcessingException {
jsonGenerator.writeStartObject();
jsonGenerator.writeFieldName("first");
jsonGenerator.writeNumber(value.getFirstValue());
jsonGenerator.writeFieldName("second");
jsonGenerator.writeNumber(value.getSecondValue());
jsonGenerator.writeFieldName("third");
jsonGenerator.writeNumber(value.getAnyAnotherClass().getThirdValue());
jsonGenerator.writeEndObject();
}
}
The way I worked around this on a Many-to-Many relationship is by using
#JsonIgnore
On one of the Entities.
For example we have Person and Child entities. One Person can have many children and vice-versa.
On Person we have :
public class Person
{
//Other fields ommited
#ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST)
#JoinTable(name = "person_child",
joinColumns = {
#JoinColumn(name = "person_id", referencedColumnName = "id", nullable = false,
updatable = false)
},
inverseJoinColumns = {
#JoinColumn(name = "child_id", referencedColumnName = "id", nullable =
false, updatable = false)
})
private Set<Child> children = new HashSet<>() ;
}
And on Child we have :
public class Child
{
#JsonIgnore
#ManyToMany(mappedBy = "children", fetch = FetchType.LAZY)
private Set<Person> people = new HashSet<>() ;
}
Now when we get a Person, we also get all his connected children. But when we get a Child then we don't get all People because we have #JsonIgnore annotation on it.
This fixes the Infinite Recursion problem, and raises this one.
My workaround was by writing a query to get me all the People connected to a specific child_id.
Below you may see my code:
public interface PersonDAO extends JpaRepository<Person, Long>
{
#Query(value = "SELECT * " +
" FROM person p INNER JOIN person_child j " +
"ON p.id = j.person_id WHERE j.child_id = ?1 ", nativeQuery = true)
public List<Person> getPeopleViaChildId(long id);
}
And i use it whenever I want to get all People from a child.

Foreign Key Problems with Hibernate (Bug?)

at our current project we are experiencing some difficulties. I recently changed some Hibernate Beans (our Article Bean and some underlying stuff) and I ran some tests and everything looked fine. Now my teammate is having exceptions with this message:
Foreign key (FK_09fd525ae6654c059394d22cc15:ARTBILDER [artikel_fk,servdat_fk])) must have same number of columns as the referenced primary key (ARTIKEL [AUTOIN_FIX])
The annotations are definitely correct. I had the same problem and decided to setup the project on my computer from scratch and the problems were gone. What can be the reason for these problems?
We are working on a legacy database and are only mapping our objects to the database and not generating the database with hibernate. And we are using HibernateSearch for full-text search (maybe this is related, because the first time this occured was after I added the HibernateSearch Annotations).
We are using a Firebird 2.5 instance.
EDIT:
here is the property the error is coming from:
The ID Class:
#Embeddable
public class ID implements Serializable {
private static final long serialVersionUID = 1810044174631580834L;
private Long autoin;
private Integer serverId;
public ID() {
}
public ID(Long autoin, Integer serverId) {
this.autoin = autoin;
this.serverId = serverId;
}
#Column(name = "autoin_fix")
public Long getAutoin() {
return this.autoin;
}
#Column(name = "servdat_fk")
public Integer getServerId() {
return this.serverId;
}
public void setAutoin(Long autoin) {
this.autoin = autoin;
}
public void setServerId(Integer serverId) {
this.serverId = serverId;
}
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result
+ ((this.autoin == null) ? 0 : this.autoin.hashCode());
result = prime * result
+ ((this.serverId == null) ? 0 : this.serverId.hashCode());
return result;
}
#Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (this.getClass() != obj.getClass()) {
return false;
}
ID other = (ID) obj;
if (this.autoin == null) {
if (other.autoin != null) {
return false;
}
} else if (!this.autoin.equals(other.autoin)) {
return false;
}
if (this.serverId == null) {
if (other.serverId != null) {
return false;
}
} else if (!this.serverId.equals(other.serverId)) {
return false;
}
return true;
}
#Override
public String toString() {
return new StringBuilder().append("ID [").append(this.autoin)
.append("_").append(this.serverId).append("]").toString();
}
}
The Article class:
#Indexed
#Entity
#Table(name = "ARTIKEL")
public class Article {
private ID id;
private List<Picture> pictures;
...
#DocumentId
#EmbeddedId
#FieldBridge(impl = IDBridge.class)
public ID getId() {
return id;
}
#OneToMany
#JoinColumns({
#JoinColumn(name = "artikel_fk", referencedColumnName = "autoin_fix"),
#JoinColumn(name = "servdat_fk", referencedColumnName = "servdat_fk") })
#IndexedEmbedded
public List<Picture> getPictures() {
return pictures;
}
}
The Picture class:
#Entity
#Table(name = "ARTBILDER")
public class Picture extends BasePicture {
...
protected ID id;
#EmbeddedId
#FieldBridge(impl = IDBridge.class)
#Field(store = Store.YES, index = Index.YES)
public ID getId() {
return id;
}
...
}
EDIT2: I may have a clue where this comes from, please standby.
EDIT3: Nope, not the error.
EDIT4: Here is the DDL:
CREATE TABLE ARTIKEL
(
AUTOIN_FIX NUM10_0 DEFAULT 0,
SERVDAT_FK NUM10_0 DEFAULT 0,
...
PRIMARY KEY (AUTOIN_FIX,SERVDAT_FK)
);
CREATE TABLE ARTBILDER
(
AUTOIN_FIX NUM10_0 DEFAULT 0,
ARTIKEL_FK NUM10_0 DEFAULT 0,
SERVDAT_FK NUM10_0 DEFAULT 0,
...
PRIMARY KEY (AUTOIN_FIX,SERVDAT_FK)
);
Here is full link and description
OneToMany(fetch = FetchType.LAZY)
#JoinTable(name = "DATA_VALUE", joinColumns = {
#JoinColumn(name = "DATA_ID"),
}, inverseJoinColumns = {
#JoinColumn(name = "COLUMN_NM")
})
List<DataValue> dataValueList;
OR more Descriptive
#Entity
public class Parent implements Serializable {
#Id
public ParentPk id;
public int age;
#OneToMany(cascade=CascadeType.ALL)
#JoinColumns ({
#JoinColumn(name="parentCivility", referencedColumnName = "isMale"),
#JoinColumn(name="parentLastName", referencedColumnName = "lastName"),
#JoinColumn(name="parentFirstName", referencedColumnName = "firstName")
})
public Set<Child> children; //unidirectional
...
}

Categories

Resources