I'm implementing a #ManyToOne bidirectional relationship with a join table using hibernate, but when I'm persisting some data, the hibernate claims that the record in relationship table is being inserted twice, violating the unique constraint, as the error message below shows:
ERROR: org.hibernate.engine.jdbc.spi.SqlExceptionHelper - ERROR: duplicate key value violates unique constraint "tillage_sample_pkey"
Detail: Key (id_tillage, id_sample)=(82, 110) already exists.
I have the following tables:
Tillage (id, some other data) (One Tillage can have many samples)
Sample (id, some other data) (One Sample can have one Tillage only)
tillage_sample (id_tillage, id_sample) PK (id_tillage, id_sample)
When I create a Tillage object, I fill with a Sample. In the Sample Object, I point with the Tillage object, creating a "double binding".
I guess that this "double bind" is causing the trouble, as Tillage/Sample relationship is persisted by hibernate when is saving the Tillage and repeating the step when it tries to persist the Tillage inside the Sample (which is the same tillage object).
Here Goes my code, to help you to understand my issue:
Tillage.java
#Entity
#Table(name = "tillage")
public class Tillage implements Serializable {
private static final long serialVersionUID = 3605331584324240290L;
#Id
#GeneratedValue(generator = "tillage_id_seq", strategy = GenerationType.SEQUENCE)
#SequenceGenerator(name = "tillage_id_seq", sequenceName = "tillage_id_seq", allocationSize = 1)
private Integer id;
#Column(name = "name")
private String name;
// Other simple attributes
#ManyToOne
#JoinColumn(name = "id_farm")
#JsonBackReference
private Farm farm;
// This relation is the problematic one
#OneToMany(cascade=CascadeType.ALL, fetch = FetchType.EAGER)
#JoinTable(name = "tillage_sample",
joinColumns = { #JoinColumn(name = "id_tillage") },
inverseJoinColumns = {#JoinColumn(name = "id_sample") })
private List<Sample> sampleList;
// Although similar, this one is doing OK
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(name = "tillage_owner",
joinColumns = { #JoinColumn(name = "id_tillage") },
inverseJoinColumns = {#JoinColumn(name = "id_owner") })
private List<Owner> ownerList;
// getters & setters
}
Sample.java
#Entity
#Table(name = "sample")
public class Sample implements Serializable {
private static final long serialVersionUID = 7064809078222302493L;
#Id
#GeneratedValue(generator = "sample_id_seq", strategy = GenerationType.SEQUENCE)
#SequenceGenerator(name = "sample_id_seq", sequenceName = "sample_id_seq", allocationSize = 1)
private Integer id;
#Column(name = "name")
private String name;
// Other simple attributes
// This completes the relation Tillage-Sample
#ManyToOne(cascade=CascadeType.ALL, fetch = FetchType.EAGER)
#JoinTable(name = "tillage_sample",
joinColumns = { #JoinColumn(name = "id_sample") },
inverseJoinColumns = {#JoinColumn(name = "id_tillage") })
private Tillage tillage = new Tillage();
#OneToMany(cascade=CascadeType.ALL, fetch=FetchType.EAGER)
#JoinTable(name = "sample_sample_item",
joinColumns = { #JoinColumn(name = "id_sample") },
inverseJoinColumns = {#JoinColumn(name = "id_sample_item") })
private List<SampleItem> sampleItemList;
// Getters and Setters
}
SomeService.java
...
#Override
public Tillage toTillage(TillageDTO dto) {
Tillage tillage = new Tillage();
tillage.setName(dto.getNameTillage());
// Fill the samples of the tillage
for(ArrSample sample : dto.getSamples().getArrSample()){
Sample s = new Sample();
s.setName(sample.getName());
// Setting the tillage in the Sample object
s.setTillage(tillage);
// Fill the items of the sample
for(Array arr : sample.getAreas().getArray()){
SampleItem si = new SampleItem();
si.setProduction(Double.parseDouble(arr.getProduction()));
// Double binding between sample and sampleItem
si.setSample(s);
s.getSampleItemList().add(si);
}
// Adding a sample to Tillage
tillage.getSampleList().add(s);
}
return tillage;
}
public void save(TillageDTO dto){
Tillage t = this.toTillage(dto);
// The error occurs when we persist the data
// The entityManager is Autowired by Spring and works in other places
entityManager.persist(tillage);
}
That's not a bidirectional OneToMany. That's too separate unidirectional associations using the same join table.
In a bidirectional association, one side must be the inverse of the other side. For a OneToMany, the One side must be the inverse side:
#OneToMany(mappedBy = "tillage", cascade=CascadeType.ALL, fetch = FetchType.EAGER)
private List<Sample> sampleList;
Related
I have two entities connected with many-to-many relationship. For example:
#Entity
public class Account {
#Id
private Long id;
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(
name = "account_games",
joinColumns = {#JoinColumn(name="account_id")},
inverseJoinColumns = {#JoinColumn(name="game_id")}
)
private Set<Game> games = new HashSet<>();
}
#Entity
public class Game {
#Id
private Long id;
#ManyToMany(mappedBy = "games", fetch = FetchType.LAZY)
List<Account> accounts = new ArrayList<>();
}
So, there is a table account_games(account_id, game_id) in mysql describing entities many-to-many relations.
I don't want to have Game entity anymore. Is there a way to get rid of Game and leave gameId relation only? So, I'd like to have code something like that:
#Entity
public class Account {
#Id
private Long id;
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(
name = "account_games",
joinColumns = {#JoinColumn(name="account_id")},
inverseJoinColumns = {#JoinColumn(name="game_id")}
)
private Set<Long> gameIds = new HashSet<>();
}
without making changes in database.
I've tried different configuration on javax.persistance annotations, but none worked
You can use #ElementCollection and #CollectionTable to achieve that.
#Entity
public class Account {
#Id
private Long id;
#ElementCollection(fetch = FetchType.LAZY)
#CollectionTable(name = "account_games", joinColumns = #JoinColumn(name = "account_id"))
#Column(name = "game_id", nullable = false)
private Set<Long> gameIds = new HashSet<>();
}
You may have to change the query on how to filter data using gameId. Element Collection Query
I am currently trying to extract a set of entities from a table with a composite key, but I only want to perform the join with id_a to get a set and not both(id_a and id_b) which would yield a single result.
This is not the original code, but it is an example of what I am trying to achieve.
#Entity
public class ItemA {
#EmbeddedId
private ItemId itemId;
#OneToMany
private Set<ItemB> itemsB = new HashSet<>();
}
#Embeddable
public class ItemID implements Serializable {
private Integer itemIDA;
private Integer itemIDB;
}
#Entity
public class ItemB {
#Id
private Integer itemIDA;
}
I have tried a couple of ways, mainly annotating the #OneToMany with #JoinColumn and also with #JoinTable
#OneToMany
#JoinTable(
name = "itemB",
joinColumns = { #JoinColumn(name = "itemIDA") },
inverseJoinColumns = { #JoinColumn(name = "itemIDA") }
)
private Set<ItemB> detalleUsuarios = new HashSet<>();
also I tried maybe trying to get inside of the ItemId class
#OneToMany
#JoinTable(
name = "itemB",
joinColumns = { #JoinColumn(name = "itemID.itemIDA") },
inverseJoinColumns = { #JoinColumn(name = "itemIDA") }
)
private Set<ItemB> detalleUsuarios = new HashSet<>();
But I get the following error
A Foreign key refering com.example.ItemA from com.example.ItemB has the wrong number of column. should be 2
I've got two entities mapped with #ManyToMany annotation
FIRST
Pielegniarka:
#Data
#Entity
#Table(name = "pielegniarka")
public class Pielegniarka {
#Id
#SequenceGenerator(name = "seq2", sequenceName = "pielegniarka_id_pielegniarki", allocationSize = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "seq2")
#Column(name = "id_pielegniarki", nullable = false, unique = true)
private int id_pielegniarki;
#Column(name = "imie")
private String imie;
#Column(name = "nazwisko")
private String nazwisko;
#Column(name = "placa")
private int placa;
#ManyToMany(fetch = FetchType.LAZY, cascade = {CascadeType.DETACH,CascadeType.MERGE,
CascadeType.PERSIST,CascadeType.REFRESH})
#JoinTable(
name = "pielegniarki_sale",
joinColumns = #JoinColumn(name = "id_pielegniarki"),
inverseJoinColumns = #JoinColumn(name = "nr_sali")
)
private List<Sala> sale;
public Pielegniarka() {
}
public Pielegniarka(String imie, String nazwisko, int placa, List<Sala> sale) {
this.imie = imie;
this.nazwisko = nazwisko;
this.placa = placa;
this.sale = sale;
}
public void addSala(Sala sala){
if(sale == null){
sale = new ArrayList<>();
}
sale.add(sala);
}
public void removeSala(Sala sala){
sale.remove(sala);
sala.getPielegniarki().remove(this);
}
}
SECOND
Sala:
#Data
#Entity
#Table
public class Sala {
#Id
#SequenceGenerator(name = "seq3", sequenceName = "sala_nr_sali_seq", allocationSize = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "seq3")
#Column(name = "nr_sali", nullable = false, unique = true)
private int nr_sali;
#Column(name = "pojemnosc")
private int pojemnosc;
#Column(name = "oddzial")
private String oddzial;
#JsonBackReference
#ManyToMany(fetch = FetchType.LAZY, cascade = {CascadeType.DETACH,CascadeType.MERGE,
CascadeType.PERSIST,CascadeType.REFRESH})
#JoinTable(
name = "pielegniarki_sale",
joinColumns = #JoinColumn(name = "nr_sali"),
inverseJoinColumns = #JoinColumn(name = "id_pielegniarki")
)
private List<Pielegniarka> pielegniarki;
public Sala() {
}
public Sala(int pojemnosc, String oddzial, List<Pielegniarka> pielegniarki) {
this.pojemnosc = pojemnosc;
this.oddzial = oddzial;
this.pielegniarki = pielegniarki;
}
public void addPielegniarka(Pielegniarka pielegniarka){
if(pielegniarki == null){
pielegniarki = new ArrayList<>();
}
pielegniarki.add(pielegniarka);
}
public void removePielegniarka(Pielegniarka pielegniarka){
pielegniarki.remove(pielegniarka);
pielegniarka.getSale().remove(this);
}
}
I also have a method in my SalaDAO (and similar method in PielegniarkaDAO) which adds Pielegniarka to list in Sala class and then inserts id of that Sala and id of newly added Pielegniarko to Join Table in my Oracle Database (because of #ManyToMany annotation)
#Override
public void saveSalaWithIdPielegniarki(int idPielegniarki, int nr_sali) {
Pielegniarka pielegniarka = entityManager.find(Pielegniarka.class, idPielegniarki);
Sala sala = entityManager.find(Sala.class, nr_sali);
if (pielegniarka != null && sala != null) {
for (Pielegniarka salPiel : sala.getPielegniarki()) {
if (salPiel.getId_pielegniarki() == idPielegniarki) {
return;
}
}
pielegniarka.addSala(sala);
sala.addPielegniarka(pielegniarka);
}
}
Here is a fragment of SalaController which shows the method doing this. In-service classes I used exactly the same methods as in DAO. It is only a wrapper for DAO classes.
#RestController
#RequestMapping("/sala")
public class SalaController {
#PostMapping("/{nr_sali}/pielegniarka/{idPielegniarki}")
public void saveSalaWithIdPielegniarki(#PathVariable int idPielegniarki,
#PathVariable int nr_sali) {
salaService.saveSalaWithIdPielegniarki(idPielegniarki,nr_sali);
}
}
When I try to access this endpoint for example /sala/4/pielegniarka/5 which should add Pielegniarka with id 5 to the list of sala with id 4, Hibernate inserts record with id's 4,5 twice into pielegniarki_sale JOIN TABLE in my Database. What can cause this error?
Oracle is throwing "ORA-00001: unique constraint violated" because of that.
Here is the picture of Spring Boot logs that show double insert. And entities relationships image
Unique constraint violated error
MER
You have two times defined a ManyToMany relationship with the same realtion table.
You have to make one of the relations the owning and one the inverse side.
The owning side is the side in the realtionship from where the intermidate realtion table is maintainted (insert, update, delete) and the other side, the so called inverse side, is the side where nothing happens in terms of insert, update and delete.
The inserves side is defined when you use the mappedBy attribute that points to the owning side attribute.
For example you could have the owning side like this:
#ManyToMany(fetch = FetchType.LAZY, cascade = {CascadeType.DETACH,CascadeType.MERGE,
CascadeType.PERSIST,CascadeType.REFRESH})
#JoinTable(
name = "pielegniarki_sale",
joinColumns = #JoinColumn(name = "nr_sali"),
inverseJoinColumns = #JoinColumn(name = "id_pielegniarki")
)
private List<Pielegniarka> pielegniarki;
And then the inverse side like this:
#ManyToMany(fetch = FetchType.LAZY, cascade = {CascadeType.DETACH,CascadeType.MERGE,
CascadeType.PERSIST,CascadeType.REFRESH}, mappedBy = "pielegniarki")
private List<Pielegniarka> pielegniarki;
If you don't do this and let it like your mapping then Hibernate tries to insert two time the same record to the relation table.
I have 3 entities User, Order, Item with a mapping like this:
#Entity
public class Item {
#Id
#Access(AccessType.PROPERTY)
private long id; // item id predefined
//..getters and setters
}
#Entity
public class Client {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
//...getters and setters
}
#Entity
public class Order {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(nullable = false)
#Access(AccessType.PROPERTY)
private Long id;
#ManyToOne(optional = false, fetch = FetchType.LAZY, cascade = {
CascadeType.PERSIST, CascadeType.MERGE,
CascadeType.REFRESH, CascadeType.DETACH
})// without removing
//#JoinColumn(name = "client_id") - works fine
#JoinTable(name = "client_cr_order_cr_item",
joinColumns = #JoinColumn(name = "order_id"),
inverseJoinColumns = #JoinColumn(name = "client_id"))
private Client client;
#NotEmpty
#OneToMany(fetch = FetchType.LAZY, cascade = {
CascadeType.PERSIST, CascadeType.MERGE,
CascadeType.REFRESH, CascadeType.DETACH
})// without removing
#JoinTable(name = "client_cr_order_cr_item",
joinColumns = #JoinColumn(name = "order_id"),
inverseJoinColumns = #JoinColumn(name = "item_id"))
private List<Item> items;
//...getters and setters
}
When I persist order it fails with error:
ERROR: null value in column "item_id" violates not-null constraint
it generate query like this:
insert into client_cr_order_cr_item (client_id, order_id) values ()
i.e. it do not fill out segment_id field by some reason I don't know. But I refer to client not thru 3th table but using FK #JoinColumn(name = "client_id") , then it generate correct query:
insert into order_cr_item (order_id, item_id) values ()
Please, could you explain this behavior? Why mapping of client affect items? Is there any hint to make hibernate persists items to 3th table?
I am trying to create a new User(entity1) - it has reference to a Group (entity2) via a link table Member (entity3)
A user has a Set of groups as a class variable.
When i create my user object i want to say this user will be a member of group n (there are pre defined users that are linked to by id (1,2,3,4,5,6...) each group has some associated data in the table.
Whenever I create my user object as follows;
User user = new User();
user.setActive(1);
user.setCrby("me");
user.setUsername("username");
user.setCrdate("2016-06-20 12:42:53.610");
user.setCrwsref("...");
user.setModby("...");
user.setModdate("2016-06-20 12:42:53.610");
user.setModswref("..");
user.setBackground("Y");
user.setPassword("password");
user.setFullName("me");
Group group = new Group();
group.setId(1);
Group group2 = new Group();
group2.setId(2);
Set<Group> sets = new HashSet<Group>();
sets.add(group);
sets.add(group2);
user.setGroups(sets);
userDao.addUser(user);
I keep getting errors telling me that certain columns cannot be null. What I actually want to happen here is not to be doing an insert in to the group table but associating a user to a line in the group table. Is there a particular way I can prevent the columns in the group table being modified? I think I need to modify the mappings between the link table - this is how much pojos link right now
User
#Entity
#Table(name = "user")
public class User
{
#Id
#GeneratedValue
#Column(name = "id")
private int id;
#Column(name = "username")
private String username;
#Column(name = "password")
private String password;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinTable(name = "zmember", joinColumns = {#JoinColumn(name = "username")}, inverseJoinColumns = {#JoinColumn(name = "id")})
private Set<Group> groups = new HashSet<Group>(0);
Member link table
#Entity
#Table(name = "member")
public class Member implements Serializable
{
#Id
#GeneratedValue
#Column(name = "id")
private int id;
#Id
#Column(name = "sgpid")
private int sgpid;
#Column(name = "username")
private String memberUsername;
Group
#Entity
#Table(name = "group")
public class Group
{
#Id
#GeneratedValue
#Column(name = "id")
private int id;
What is happening is there is no association to the link Member table so ideally should User have a set of member objects rather than a set of groups?
Thanks - this was quite hard to explain so sorry if it is hard to understand
This is a typical case for the #ManyToMany annotation. See for example:
https://dzone.com/tutorials/java/hibernate/hibernate-example/hibernate-mapping-many-to-many-using-annotations-1.html
The relationship from User to Group is essentially ManyToMany. You could model this is using the #ManyToMany annotation however one drawback with this approach is you cannot save additional information about the group in the join table such as 'date_joined'.
See: https://en.wikibooks.org/wiki/Java_Persistence/ManyToMany#ManyToMany
Using this approach you would not need the Join entity Member and the relationship on User would look like:
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinTable(name = "zmember", joinColumns = {#JoinColumn(name = "member_id", referencedColumnName = "id")}, inverseJoinColumns = {#JoinColumn(name = "group_id", referencedColumnName = "id")})
private Set<Group> groups = new HashSet<Group>(0);
The alternative to using #ManyToMany is to use a Join entity Member(ship) as you have done. This would allow you to save additional data about the relationship (by defining additional field mappings in the Join entity).
In this case the mappings would look like:
User:
public class User{
#OneToMany(mappedBy = "user", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private Set<Membership> memberships = new HashSet<Membership>(0);
//if required, you can 'hide' the join entity from client code by
//encapsulating add remove operations etc.
public void addToGroup(Group group){
Membership membershup = new Membership();
membership.setUser(this);
membership.setGroup(group);
memberships.add(membership);
)
public Set<Groupp> getGroups(){
//iterate memberships and build collection of groups
}
}
Membership:
public class Membership{
#Id
#GeneratedValue
#Column(name = "id")
private int id;
#ManyToOne
#JoinColumn(name = "user_id")
private Member member;
#ManyToOne
#JoinColumn(name = "group_id")
private Group group;
}
Group:
#Entity
#Table(name = "group")
public class Group
{
#Id
#GeneratedValue
#Column(name = "id")
private int id;
#OneToMany(mappedBy = "group", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private Set<Membership> memberships = new HashSet<Membership>(0);
}