I am trying to create two entities which have many-to-many relation between them. First entity is Person with PID as primary key, second is Serie with SID as primary key. In database there is a table TJV_5_SERIE_2_PERSON, which represents many to many relationship between these entities.
tables in database
The problem is when I retrieve any entity, Collection annotated with #ManyToMany is always empty. So I assume I've messed up something in my code that explains why my many-to-many relation doesn't work.
I retrieve these two entities by generating (in Netbeans 9.0) 'Restful Web Services from Entity classes'. This way I can use these services to retrieve all attributes succesfully, except Collection with #ManyToMany annotation is always empty.
Any idea why it is not woking appreciated. It is first time trying this, so pardon me for any dumm mistakes.
Person class:
#Entity
#Table(name = "TJV_5_PERSON")
#XmlRootElement
public class Person implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "PID")
private Integer id;
#Column(name = "PNAME")
private String name;
#ManyToMany()
#JoinTable(
name = "TJV_5_SERIE_2_PERSON",
joinColumns = #JoinColumn(name = "PID", referencedColumnName = "PID"),
inverseJoinColumns = #JoinColumn(name = "SID", referencedColumnName = "SID")
)
// always empty
private Collection<Serie> favourites = new ArrayList<Serie>();
public Person() {
}
public Person(Integer id, String name) {
this.id = id;
this.name = name;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
#XmlTransient
public Collection<Serie> getFavourites() {
return favourites;
}
public void setFavourites(Collection<Serie> favourites) {
this.favourites = favourites;
}
#Override
public int hashCode() {
int hash = 5;
hash = 31 * hash + Objects.hashCode(this.id);
return hash;
}
#Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final Person other = (Person) obj;
if (!Objects.equals(this.id, other.id)) {
return false;
}
return true;
}
#Override
public String toString() {
return "Person{" + "id=" + id + ", name=" + name + ", favourites=" + favourites + '}';
}
}
Serie class:
#Entity
#Table(name = "TJV_5_SERIE")
#XmlRootElement
public class Serie implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "SID")
private Integer id;
#Column(name = "STITLE")
private String title;
// always empty
#ManyToMany(mappedBy = "favourites")
private Collection<Person> fans = new ArrayList<Person>();
public Serie() {
}
public Serie(Integer id, String title) {
this.id = id;
this.title = title;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
#XmlTransient
public Collection<Person> getFans() {
return fans;
}
public void setFans(Collection<Person> fans) {
this.fans = fans;
}
#Override
public int hashCode() {
int hash = 3;
hash = 67 * hash + Objects.hashCode(this.id);
return hash;
}
#Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final Serie other = (Serie) obj;
if (!Objects.equals(this.id, other.id)) {
return false;
}
return true;
}
#Override
public String toString() {
return "Serie{" + "id=" + id + ", title=" + title + ", fans=" + fans + '}';
}
}
I am not 100% sure, but you may not retrieving any results beacuse of #XMLTransiet annotation above the Serie.class method
#XmlTransient
public Collection<Person> getFans() {
return fans;
}
Try to look in documentation https://docs.oracle.com/javaee/6/api/javax/xml/bind/annotation/XmlTransient.html or in connected posts Hide an entity variable from xml message - #XmlTransient not working
The other issue is cascading data between two corresponding #ManyToMany tables. It means that you have intersection and the data appears in this table automatically when you use some type of cascade but you need send a POST request. It means in your service class layer you can create a method responsible for creating Person and assign a Serie to this Person object which is a foreign key. The article about cascading is here :) https://vladmihalcea.com/a-beginners-guide-to-jpa-and-hibernate-cascade-types/
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 am trying to persist one object which has inside list.
I had to annotate the entity Item with #JsonManagedReference
and ItemProperty with #JsonBackReference, to avoid infinite loop - break the cycle.
And for getting items with item properties is fine. The problem is now when I try to persist the new Item with list of item properties, then only the Item is persisted, without any ItemProperties. Any one know why's that? Has the #JsonBackReference/ManagedReference annotations something with it?
CODE:
import com.fasterxml.jackson.annotation.JsonManagedReference;
import javax.persistence.*;
import java.util.List;
import java.util.Objects;
#Entity
#Table(name = "item")
public class Item {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private int id;
#Enumerated(EnumType.STRING)
#Column(name = "type")
private ItemType itemType;
#OneToMany(mappedBy = "item")
// #JsonManagedReference is the forward part of reference which gets serialized normally.
#JsonManagedReference
private List<ItemProperty> itemProperties;
public Item() {
}
public Item(ItemType itemType, List<ItemProperty> itemProperties) {
this.itemType = itemType;
this.itemProperties = itemProperties;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public ItemType getItemType() {
return itemType;
}
public void setItemType(ItemType itemType) {
this.itemType = itemType;
}
public List<ItemProperty> getItemProperties() {
return itemProperties;
}
public void setItemProperties(List<ItemProperty> itemProperties) {
this.itemProperties = itemProperties;
}
#Override
public String toString() {
return "Item{" +
"id=" + id +
", itemType=" + itemType +
", itemProperties=" + itemProperties +
'}';
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Item item = (Item) o;
return id == item.id &&
itemType == item.itemType &&
Objects.equals(itemProperties, item.itemProperties);
}
#Override
public int hashCode() {
return Objects.hash(id, itemType, itemProperties);
}
}
ITEM PROPERTY:
import com.fasterxml.jackson.annotation.JsonBackReference;
import javax.persistence.*;
import java.util.Objects;
#Entity
#Table(name = "item_properties")
public class ItemProperty {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private int id;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "item_id")
#JsonBackReference
private Item item;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "item_property_definition_id")
private ItemPropertyDefinition itemPropertyDefinition;
#Column(name = "value")
private String value;
public ItemProperty(){}
public ItemProperty(Item item, ItemPropertyDefinition itemPropertyDefinition, String value) {
this.item = item;
this.itemPropertyDefinition = itemPropertyDefinition;
this.value = value;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public Item getItem() {
return item;
}
public void setItem(Item item) {
this.item = item;
}
public ItemPropertyDefinition getItemPropertyDefinition() {
return itemPropertyDefinition;
}
public void setItemPropertyDefinition(ItemPropertyDefinition itemPropertyDefinition) {
this.itemPropertyDefinition = itemPropertyDefinition;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
#Override
public String toString() {
return "ItemProperty{" +
"id=" + id +
", item=" + item +
", itemPropertyDefinition=" + itemPropertyDefinition +
", value='" + value + '\'' +
'}';
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ItemProperty that = (ItemProperty) o;
return id == that.id &&
Objects.equals(item, that.item) &&
Objects.equals(itemPropertyDefinition, that.itemPropertyDefinition) &&
Objects.equals(value, that.value);
}
#Override
public int hashCode() {
return Objects.hash(id, item, itemPropertyDefinition, value);
}
}
IN REST CONTROLLER:
#PostMapping("/items")
Item addItem(#RequestBody Item item) {
item.setId(0);
return this.itemService.addItem(item);
}
Thanks in advance for hints.
Have a nice day and happy coding!
You haven´t declared the cascade flag in the #OneToMany. By default no operation on an item entity is cascaded to the ItemProperty list. So take a look into the CascadeType enum and set the operations you want to be cascaded to the itemsproperty list. For more information on CascadeTypes look here.
Example:
#OneToMany(cascade = CascadeType.ALL, mappedBy = "item", orphanRemoval = true)
// #JsonManagedReference is the forward part of reference which gets serialized normally.
#JsonManagedReference
private List<ItemProperty> itemProperties;
If you wonder what´s the orphanRemoval good for take a look at this question.
The C.Weber answer was right but also #Transactional annotation was missing.
How do I have to update the following classes to get the one-to-many relationship between parent and child working?
Parent:
#Entity
public class Parent {
#Embeddedid
private ParentId id;
#OneToMany
private List<Child> children;
public Parent() {
}
public Parent(final ParentId id) {
this.id = id;
}
#Override
public String toString() {
return "Parent [id=" + this.id + ", children=" + this.children + "]";
}
// (Getters and setters)
}
ParentId:
#Embeddable
public class ParentId implements Serializable {
private Integer key1;
private Integer key2;
public ParentId() {
}
public ParentId(final Integer key1, final Integer key2) {
this.key1 = key1;
this.key2 = key2;
}
#Override
public boolean equals(final Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final ParentId other = (ParentId) obj;
if (this.key1 != other.key1) {
return false;
}
if (this.key2 != other.key2) {
return false;
}
return true;
}
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + this.key1;
result = prime * result + this.key2;
return result;
}
#Override
public String toString() {
return "ParentId [key1=" + this.key1 + ", key2=" + this.key2 + "]";
}
// (Getters for key1 and key2)
}
Child:
#Entity
public class Child {
private Integer id;
#ManyToOne
private Parent parent;
public Child() {
}
public Child(final Integer id, final Parent parent) {
this.id = id;
this.parent = parent;
}
#Override
public String toString() {
return "Child [id=" + this.id + ", parent.getId()=" + this.parent.getId() + "]";
}
// (Getters and setters)
}
Because we fought a bit with this problem in our current project, I will post our solution as an answer to this question.
Parent:
#Entity
public class Parent {
#Embeddedid
private ParentId id;
/*******************************************************/
/*******************************************************/
/*******************************************************/
// #formatter:off
#OneToMany(
cascade = { CascadeType.MERGE, CascadeType.PERSIST },
fetch = FetchType.EAGER
)
#JoinColumns(
{
#JoinColumn(name = "parent_key1"),
#JoinColumn(name = "parent_key2")
}
)
// #formatter:on
/*******************************************************/
/*******************************************************/
/*******************************************************/
private List<Child> children;
public Parent() {
}
public Parent(final ParentId id) {
this.id = id;
}
#Override
public String toString() {
return "Parent [id=" + this.id + ", children=" + this.children + "]";
}
// (Getters and setters)
}
Child:
#Entity
public class Child {
private Integer id;
#ManyToOne
/*******************************************************************************/
/*******************************************************************************/
/*******************************************************************************/
// #formatter:off
#JoinColumns(
{
#JoinColumn(name = "parent_key1", insertable = false, updatable = false),
#JoinColumn(name = "parent_key2", insertable = false, updatable = false)
}
)
// #formatter:on
/*******************************************************************************/
/*******************************************************************************/
/*******************************************************************************/
private Parent parent;
public Child() {
}
public Child(final Integer id, final Parent parent) {
this.id = id;
this.parent = parent;
}
#Override
public String toString() {
return "Child [id=" + this.id + ", parent.getId()=" + this.parent.getId() + "]";
}
// (Getters and setters)
}
i have the following RESTfull method :
#RequestMapping(value = "/budgetLines",
method = RequestMethod.POST,
produces = MediaType.APPLICATION_JSON_VALUE)
#Timed
public void create(#RequestBody BudgetLine budgetLine) {
System.out.println("Before Persisting in the repository " + budgetLine);
budgetLineRepository.save(budgetLine);
}
I'am consuming this method inside a web application, i checked using the network analysis tool (in the web developper tool of chrome) that the object sended is valid (all attribute except the id were set with a valid value), but then the object passed to the repository contains only null attributes.
here is an example body :
{
"Name":"testLabel",
"Label":"testName",
"AnnualBudget":9000
}
the class BudgetLine is defined as follows:
#Entity
#Table(name = "T_BUDGETLINE")
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class BudgetLine implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(name = "label")
private String Label;
#Column(name = "name")
private String Name;
#Column(name = "annual_budget", precision=10, scale=2)
private BigDecimal AnnualBudget;
#OneToMany(mappedBy = "budgetLine")
#JsonIgnore
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
private Set<Report> reportss = new HashSet<>();
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getLabel() {
return Label;
}
public void setLabel(String Label) {
this.Label = Label;
}
public String getName() {
return Name;
}
public void setName(String Name) {
this.Name = Name;
}
public BigDecimal getAnnualBudget() {
return AnnualBudget;
}
public void setAnnualBudget(BigDecimal AnnualBudget) {
this.AnnualBudget = AnnualBudget;
}
public Set<Report> getReportss() {
return reportss;
}
public void setReportss(Set<Report> Reports) {
this.reportss = Reports;
}
#Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
BudgetLine budgetLine = (BudgetLine) o;
if (id != null ? !id.equals(budgetLine.id) : budgetLine.id != null) return false;
return true;
}
#Override
public int hashCode() {
return (int) (id ^ (id >>> 32));
}
#Override
public String toString() {
return "BudgetLine{" +
"id=" + id +
", Label='" + Label + "'" +
", Name='" + Name + "'" +
", AnnualBudget='" + AnnualBudget + "'" +
'}';
}
public BudgetLine() {
}
}
Try with first letter in lowercase for parameters
{
"name":"testLabel",
"label":"testName",
"annualBudget":9000
}
Spring relies heavily on standard Java naming conventions, so I suggest you also follow them. In your example, you should name your class fields with lowercased first letter.
Hi Could any one please help me to resolve this exception.Here I have entity class called Person and the thisclass having the instance variable has name and petnames, here petnames I have taken as simple value type that data type is collection and have mapped by using jpa annotation, Please look at my following code and corresponding exception that is generating . And I am facing this exception from so many days.I am struggling to resolve this problem I don not understand where I am going wrong.
Person Class
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
#ElementCollection
#CollectionTable(name = "petname")
// #Column(name = "ColumnName")
private Set<String> petname = new HashSet<String>();
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 void setPetname(Set<String> petname) {
this.petname = petname;
}
public Set<String> getPetname() {
return petname;
}
public boolean addPets(String p) {
return petname.add(p);
}
#Override
public int hashCode() {
int hash = 0;
hash += (id != null ? id.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 Person)) {
return false;
}
Person other = (Person) object;
if ((this.id == null && other.id != null) || (this.id != null && !this.id.equals(other.id))) {
return false;
}
return true;
}
#Override
public String toString() {
return "com.myapp.struts.Person[ id=" + id + " ]";
}
Excaption
Initial SessionFactory creation failed.org.hibernate.MappingException: Could not determine type for: java.util.Set, for columns: [org.hibernate.mapping.Column(petname)]
Exception in thread "main" java.lang.ExceptionInInitializerError
at com.myapp.struts.NewHibernateUtil.<clinit>(NewHibernateUtil.java:28)
at com.myapp.struts.Test.main(Test.java:16)
Caused by: org.hibernate.MappingException: Could not determine type for: java.util.Set, for columns: [org.hibernate.mapping.Column(petname)]
at org.hibernate.mapping.SimpleValue.getType(SimpleValue.java:266)
aJava Result: 1
#CollectionOfElements
#JoinTable(name = "PET", joinColumns = { #JoinColumn(name = "person_id") })
#Column(name = "petname")
private Set<String> petname = new HashSet<String>();