Recently I created a project and in my models I have ManyToMany back references.
My model is like below:
#Entity
public class A {
#ManyToMany(mappedBy = "parents", fetch = FetchType.EAGER)
private Set<A> children = new HashSet<>();
#ManyToMany
#JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
#JoinTable(
name = "link_a_recursion",
joinColumns = #JoinColumn(name = "child_id", referencedColumnName = "id"),
inverseJoinColumns = #JoinColumn(name = "parent_id", referencedColumnName = "id")
)
private Set<A> parents = new HashSet<>();
//I removed the rest ( setter, getter and other fields )
}
When I fetch this model and I want to load children it throws StackOverFlowException error ( Recursive exception )
I want to know is there any way to say to hibernate just load one level of associate and don't go deep.
For clarify:
A.children[0].children[0].children should be null to stop recursion
I want to load the first children not all the children inside of the other children
Edited:
I add another entity:
#Entity
public class B {
#OneToMany(mappedBy = "b")
private Set<A> entities = new HashSet<>();
//setter, getter
}
and add below to A entity:
#ManyToOne
private B b;
then I changed below:
#ManyToMany(mappedBy = "parents", fetch = FetchType.EAGER)
to
#ManyToMany(mappedBy = "parents", fetch = FetchType.LAZY)
and in my BService my findOne function is like below:
#Transactional(readOnly = true)
public B findOne(Long id) {
B b = repository.findOne(id);
for (A a: b.getEntities()) {
a.getChildren().size();
}
return b;
}
but again I'm getting error :(
Try lazy fetch instead
#ManyToMany(mappedBy = "parents", fetch = FetchType.LAZY)
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 have 4 entities:
Profile which has a relation with companyContract:
#OneToMany(fetch = FetchType.EAGER, mappedBy = "profile", cascade = { CascadeType.ALL })
#Fetch(value = FetchMode.SUBSELECT)
#Cascade({ org.hibernate.annotations.CascadeType.SAVE_UPDATE, org.hibernate.annotations.CascadeType.DELETE_ORPHAN })
private List<CompanyContract> companyContracts;
CompanyContract which has a relation with timesheet:
#OneToMany(mappedBy = "companyContract", cascade = { CascadeType.ALL },orphanRemoval = true, fetch = FetchType.EAGER)
#Fetch(FetchMode.SUBSELECT)
#Cascade({ org.hibernate.annotations.CascadeType.SAVE_UPDATE, org.hibernate.annotations.CascadeType.DELETE_ORPHAN })
private List<Timesheet> timesheets;
#ManyToOne
#JoinColumn(name = "IDPROFILE")
private Profile profile;
Timesheet which has a relation with invoice:
#OneToMany(mappedBy = "timesheet", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#Fetch(value = FetchMode.SUBSELECT)
#Cascade({ org.hibernate.annotations.CascadeType.SAVE_UPDATE, org.hibernate.annotations.CascadeType.DELETE_ORPHAN })
private List<Invoice> invoices;
#ManyToOne
#JoinColumn(name = "IDCONTRACT")
private CompanyContract companyContract;
Invoice:
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "ID_TIMESHEET")
private Timesheet timesheet;
So As you can see here, I'm using org.hibernate.annotations.CascadeType.DELETE_ORPHAN so I can delete the children of a parent.
If I execute this:
Profile p = companyContract.getProfile();
p.getCompanyContracts().remove(companyContract);
companyContract.setProfile(null);
profileService.update(p);
---> The order of removal should be:
Remove invoices --> Timesheets --> CompanyContract, No ?
And instead I'm getting this error:
org.hibernate.exception.ConstraintViolationException: Column 'IDCONTRACT' cannot be null
And I've checked, this error happens after profileService.updateProfile(p);
The problem seems to be that the column IDCONTRACTin the table that holds Timesheets has a NOT NULL restriction. Remove it and try again.
If you're autogenerating the schema, try adding #Basic(optional = true) to Timesheet.companyContract:
#Basic(optional = true)
#ManyToOne
#JoinColumn(name = "IDCONTRACT")
private CompanyContract companyContract;
This is working completely fine. I see all associated child entities getting successfully deleted.
Check the below code.
Use orphanRemoval = true like below instead of deprecated way
org.hibernate.annotations.CascadeType.DELETE_ORPHAN , which you are using.
#OneToMany(mappedBy = "companyContract", cascade = { CascadeType.ALL },orphanRemoval = true, fetch = FetchType.EAGER)
Find code below
public void check() {
System.out.println("Start check() in DummyDAOImpl");
Session session = sessionFactory.openSession();
CompanyContract companyContract = session.get(CompanyContract.class, 2);
Profile p = companyContract.getProfile();
p.getCompanyContracts().remove(companyContract);
companyContract.setProfile(null);
session.update(p);
session.flush();
session.close();
System.out.println("Executed check() in DummyDAOImpl");
}
I have a Parent class Seller , it have onetomany mappings to all many child classes. It also has some properties as name,id , email . In edit sequence I am fetching this object displays it on jsp , bind it through Spring MVC form , takes input from user and then save it as it is to db using session.saveorupdate(seller).
But while doing so my previous mappings with all the child classes are getting deleted. Only the mapping from join tables are getting deleted. If there is a two way mapping using mappedBy , it is not deleted.
All the mapping are lazy fetch.
Scenario is :
Step 1: Register seller . Add email ,name values.
Step 2 : Create child object in different journeys , create List of A ,Band add to existing seller object.
Step 3: Change name of seller , editing the seller and after saving all the previous mapping are gone.
Seller.java
#Entity
#Table(name = "Seller")
public class Seller {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
#Column
private String name;
#Column
private String address;
#Column
private String email;
#OneToOne(cascade = CascadeType.ALL)
#JoinTable(name = "seller_roles", joinColumns = { #JoinColumn(name = "seller_id", referencedColumnName = "id") }, inverseJoinColumns = { #JoinColumn(name = "role_id", referencedColumnName = "id") })
private Role role;
#OneToMany(mappedBy = "seller", cascade = CascadeType.ALL)
private List<A> a = new ArrayList<A>();
#OneToMany(cascade = CascadeType.ALL)
private List<B> b = new ArrayList<B>();
#OneToMany(mappedBy = "seller", cascade = CascadeType.ALL)
private List<C> c = new ArrayList<C>();
#OneToMany(cascade = CascadeType.ALL)
private List<D> d = new ArrayList<D>();
#OneToMany(cascade = CascadeType.ALL)
private List<E> e = new ArrayList<E>();
Saving seller in SellerDaoImpl as :
Session session = sessionFactory.openSession();
session.beginTransaction();
session.saveOrUpdate(seller);
session.getTransaction().commit();
session.close();
Please help.
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;