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
...
}
Related
This question already has answers here:
Foreign key constraint failure when trying to insert because of key change
(2 answers)
Closed 3 years ago.
I am trying to set up a system very similar to the one shown here:
https://vladmihalcea.com/the-best-way-to-use-the-manytomany-annotation-with-jpa-and-hibernate/
The PostgreSQL schema is set up the same way with the content class shown below, mapping to the tag class in a table with a content_id and a tag_id which have foreign key constraints on their respective tables. The issue I am having is when attempting to persist a new content object I am checking if the tags of the object exist and if they do I am adding them using the addTag method and then persisting the object. Otherwise I create them and persist the object. The POST method for doing this is also shown below. The repository successfully finds the tags since they are already persisted but I get the following error when I attempt to then persist the content:
org.postgresql.util.PSQLException: ERROR: insert or update on table
"content_tag" violates foreign key constraint "tag_id_fkey"
Detail: Key (tag_id)=(11) is not present in table "tag".
I stepped through the code and when the tags are added to the content using the addTag method it shows that their ids match the tags that are already in the database so I don't understand why when I persist the content it is a different id. Does anyone know how I can prevent this from happening and have the persisting of content work?
#Entity(name = "Content")
#Table(name = "content")
#TypeDef( name= "StringJsonObject", typeClass = StringJsonUserType.class)
public class Content implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Type(type = "StringJsonObject")
#Column(name = "text")
private String text;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
#ManyToMany(cascade = {
CascadeType.MERGE
})
#JoinTable(name = "content_tag",
joinColumns = #JoinColumn(name = "tag_id"),
inverseJoinColumns = #JoinColumn(name="content_id")
)
private Set<Tag> tags = new HashSet<>();
public Set<Tag> getTags() {
return tags;
}
public void setTags(Set<Tag> tags) {
this.tags = tags;
}
public void addTag(Tag tag) {
tags.add(tag);
tag.getContents().add(this);
}
public void removeTag(Tag tag) {
tags.remove(tag);
tag.getContents().remove(this);
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Content)) return false;
return id != null && id.equals(((Content) o).getId());
}
#Override
public int hashCode() {
return 31;
}
}
#Entity(name = "Tag")
#Table(name = "tag")
public class Tag implements Serializable {
#Id
#GeneratedValue
private Long id;
#Column(name = "name")
private String name;
#ManyToMany(mappedBy = "tags")
private Set<Content> contents = new HashSet<>();
public Tag() {}
public Tag(String name) {
this.name = name;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Set<Content> getContents() {
return contents;
}
public void setContents(Set<Content> contents) {
this.contents = contents;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Tag tag = (Tag) o;
return Objects.equals(name, tag.name);
}
#Override
public int hashCode() {
return Objects.hash(name);
}
}
#PostMapping(value = "/content", consumes = { MediaType.APPLICATION_JSON_VALUE },
produces = { MediaType.APPLICATION_JSON_VALUE })
public ContentJSON createContent(#RequestBody(required = false) final String payload) {
if (StringUtils.isEmpty(payload)) {
throw new IllegalArgumentException(ServiceErrorCode.INVALID_REQUEST_BODY);
}
final ContentRequest request = convertPayloadToRequest(payload, ContentRequest.class);
final Content content = new Content();
content.setText(request.getContent().getText().toString());
for (final String tag : request.getContent().getTags()) {
final List<Tag> current = tagRepository.findByName(tag);
if (current.isEmpty()) {
final Tag newTag = new Tag(tag);
tagRepository.save(newTag);
content.addTag(newTag);
} else {
content.addTag(current.get(0));
}
}
final Content response = contentRepository.save(content);
Set<String> tagNames = new HashSet<>();
for (final Tag tag : content.getTags()) {
tagNames.add(tag.getName());
}
return new ContentJSON(response, tagNames);
}
The issue was the annotation below.
#JoinTable(name = "content_tag",
joinColumns = #JoinColumn(name = "content_id"),
inverseJoinColumns = #JoinColumn(name="tag_id")
)
the joinColumns and inverseJoinColumns were reversed
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;
}
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.
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.
So here is short description of my problem. I have two tables, first contains Person details:
#Entity
#Converter(name = "uuidConverter", converterClass = UUIDConverter.class)
public class Person {
private UUID mId;
private String mLogin;
#Id
#UuidGenerator(name = "uuid")
#GeneratedValue(generator = "uuid")
#Column(name = "id", nullable = false)
#Convert("uuidConverter")
public UUID getId() {
return mId;
}
#Column(name = "login", nullable = false)
public String getLogin() {
return mLogin;
}
public Person setId(UUID id) {
mId = id;
return this;
}
public Person setLogin(String login) {
mLogin = login;
return this;
}
}
Second table contains multiple person preferences:
#IdClass(PersonPreference.Pk.class)
#Table(name = "person_preference")
#Entity
#Converter(name = "uuidConverter", converterClass = UUIDConverter.class)
public class PersonPreference {
private Person mPerson;
private String mComponentUid;
private String mComponentProperties;
#SuppressWarnings("UnusedDeclaration")
static class Pk implements Serializable {
private String mComponentUid;
private UUID mPerson;
public String getComponentUid() {
return mComponentUid;
}
public void setComponentUid(String componentUid) {
mComponentUid = componentUid;
}
#Convert("uuidConverter")
public UUID getPerson() {
return mPerson;
}
public void setPerson(UUID person) {
mPerson = person;
}
#Override
public int hashCode() {
return Objects.hashCode(mComponentUid, mPerson);
}
#Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final Pk other = (Pk) obj;
return Objects.equal(this.mComponentUid, other.mComponentUid) && Objects.equal(this.mPerson, other.mPerson);
}
}
#Id
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "person_id", nullable = false)
public Person getPerson() {
return mPerson;
}
#Id
#Column(name = "component_uid", nullable = false)
public String getComponentUid() {
return mComponentUid;
}
#Column(name = "component_properties", nullable = false)
public String getComponentProperties() {
return mComponentProperties;
}
public PersonPreference setPerson(Person person) {
mPerson = person;
return this;
}
public PersonPreference setComponentUid(String componentUid) {
mComponentUid = componentUid;
return this;
}
public PersonPreference setComponentProperties(String componentProperties) {
mComponentProperties = componentProperties;
return this;
}
#Override
public int hashCode() {
return Objects.hashCode(mPerson, mComponentUid, mComponentProperties);
}
#Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final PersonPreference other = (PersonPreference) obj;
return Objects.equal(this.mPerson, other.mPerson)
&& Objects.equal(this.mComponentUid, other.mComponentUid)
&& Objects.equal(this.mComponentProperties, other.mComponentProperties);
}
}
I also wrote a simple test:
Person person = new Person()
.setLogin("PersonPreferencePersistenceTestLogin");
PersonPreference personPreference = new PersonPreference()
.setPerson(person)
.setComponentUid("4028808C3AA49ABB013AA49ABB2B0000")
.setComponentProperties("{123}");
mPersonPreferenceService.save(personPreference);
Optional<PersonPreference> newPersonPreference = mPersonPreferenceService.getByPersonAndComponentUid(
person,
"4028808C3AA49ABB013AA49ABB2B0000"
);
Assert.assertEquals(personPreference.getComponentProperties(), newPersonPreference.get().getComponentProperties());
We used previously Hibernate as JPA provider and that code works perfectly. Unfortunately, after switching JPA provider to Eclipselink, cascade persisting dosen't work correctly. From log I found out:
--INSERT INTO PERSON (id, login) VALUES (?, ?)
bind => [f2ce518c-8f37-4fac-bf5b-c8225d228b28, PersonPreferencePersistenceTestLogin]
--INSERT INTO person_preference (component_uid, component_properties, person_id) VALUES (?, ?, ?)
bind => [4028808C3AA49ABB013AA49ABB2B0000, {123}, f2ce518c-8f37-4fac-bf5b-c8225d228b28]
--SELECT component_uid, component_properties, person_id FROM person_preference WHERE ((person_id = ?) AND (component_uid = ?))
bind => [null, 4028808C3AA49ABB013AA49ABB2B0000]
But person.getId() returns null value. I don't know why? Other mappings works fine with cascade persisting, but that fails in Eclipselink.