We have a problem with fetching by Id of a subselected entity
Here the structure
public class A {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
//Other class members;
}
public class B {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#ValueRestriction("NOTNULL")
#ManyToOne
#JoinColumn(name = "C_ID")
private C c;
#ValueRestriction("NOTNULL")
#JsonIgnore
#ManyToOne
#JoinColumn(name = "A_ID")
private A a;
//Other class members;
}
public class C {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#JsonIgnore
#OneToMany(mappedBy = "c")
#Fetch (FetchMode.SELECT)
private List<B> bs;
#ValueRestriction("NOTNULL")
#JsonIgnore
#ManyToOne
#JoinColumn(name = "A_ID")
private A a;
//Other class members;
}
So when we fetch over hibernate
em().find(B.class, id);
the Hibernate query also fetches the columns of A in the C entity.
This lead in a bigger Entitystructure to an
target lists can have at most 1664 entries
(This is a simple demonstration)
In our case we need the references of Entity A in all of these sub Entities
How can we prevent hibernate to fetch the same object multiple times if it is the ame.
In our cases A will always be the SAME in Entity B and Entity C. The Case that Entity B has a different A Entity than in the C Entity is in our structure not possible.
The problem is that #ManyToOne and #OneToOne by default do eager fetching. Switch to lazy fetching #ManyToOne(fetch = LAZY) to avoid this.
Related
I work with an embedded H2 database in which I use the #OneToMany relationship to relate an entity instance (product) to multiple instances of the other entities (suppliers); it's useful when I have specific suppliers for a particular product.
However now, I want to associate all the suppliers with every single product; I don't want to generate in my supplier table different supplier records for each product, instead I want to have only 5 records (5 suppliers) in my supplier table which are associated to every single product, it few words I want to achieve something like "one to all", is it possible to do it using JPA annotations?
Product entity
#Entity
public class Product {
#Id
private String productCode;
#OneToMany
#JoinColumn(name = "supplier_id", referencedColumnName = "productCode")
private List<Supplier> suppliers;
}
Supplier entity
#Entity
public class Supplier {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private Long id;
private String name;
}
Unidirectional #OneToMany association:
#Entity
public class Product {
#Id
// #Column(name = "id") maybe
// #GeneratedValue maybe
private String productCode;
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true) // according to your need
private List<Supplier> suppliers;
...
}
And,
#Entity
public class Supplier {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
...
}
#ManyToOne association:
#Entity
public class Product {
#Id
// #Column(name = "id") maybe
// #GeneratedValue maybe
private String productCode;
...
}
And,
#Entity
public class Supplier {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#ManyToOne
#JoinColumn(name = "product_id", foreignKey = #ForeignKey(name = "PRODUCT_ID_FK"))
private Product product;
private String name;
...
}
Let's say I have the following entities and associations:
Entity A:
#Entity
public class A {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#OneToMany(mappedBy = "a", cascade = CascadeType.ALL, orphanRemoval = true)
private List<B> b;
}
Entity B:
#Entity
public class B {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "a_id")
private A a;
#OneToMany(mappedBy = "b", cascade = CascadeType.ALL, orphanRemoval = true)
private List<C> c;
}
Entity C:
#Entity
public class C {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "b_id")
private B b;
}
Using the .findAll() method of the CrudRepository for entity A, it will return each A with its associated B's in a list. Also, each B will have each of its associated C's in a list.
My question is: If I in some cases only want to load all A's with their B's, but I don't want the C's in the B's, would that be possible? Could I create a custom query to do that, or is there another way? I hope it is clear what I want to achieve.
I think your problem is mappedBy values in one side of OneToMany relationships.
mappedBy value must be the the name of variable in the other side. So in your cases, you can do this:
In Entity A: change mappedBy = "citizen" to mappedBy = "a"
In Entity B: change mappedBy = "citizen" to mappedBy = "b"
I know Entity a,b,c is just an example, but you should follow above pattern when designing your models relationships.
Suppose, we have two entities, first one:
#Entity
#Table(name = "entitya")
public class EntityA {
#Id
#Column(name = "id")
private Long id;
#Column(name = "name")
private Long name;
#OneToMany(mappedBy = "parent", fetch = FetchType.LAZY)
private Set<EntityB> childEntities;
}
and the second:
#Entity
#Table(name = "entityb")
public class EntityB {
#Id
#Column(name = "id")
private Long id;
#Column(name = "name")
private String name;
#Column(name = "master")
private Boolean master;
#ManyToOne
#JoinColumn(name = "parent")
private EntityA parent;
}
So far, so good. However underlying database tables and constrains enforce that for any entityA there can be only one EntityB with boolean field master set to true. I can extract it by adding following method to entityA:
public entityB getMasterChild() {
for(entityB ent : childEntities) {
if(ent.isMaster()) {
return ent;
}
}
}
The question is, can I create #OneToOne relationship in EntityA that can express that rule, so that entityA can have additional masterChild member of type entityB?
If I understood you correctly you want to create/define a relationship between two entities based on a value of some entity's property. The think is that relationship between entities is defined on entities count (how many entities can has the other entity) and not on some entity's property value.
However
If you really want to use #OneToOne mapping for masterChild I would recommend creating a separate table/entity for it. Once this is done, you can include this new MasterChild entity into EntityA and annotate it with #OneToOne.
Here is new MasterChild entity
#Entity
public class MasterChild extends EntityB{
#Id
#Column(name = "id")
private Long id;
}
Note that I have removed 'master' from EntityB as it is no longer needed
#Entity
#Table(name = "entityb")
public class EntityB {
#Id
#Column(name = "id")
private Long id;
#Column(name = "name")
private String name;
#ManyToOne
#JoinColumn(name = "parent")
private EntityA parent;
}
And here is modified EntityA
#Entity
#Table(name = "entitya")
public class EntityA {
#Id
#Column(name = "id")
private Long id;
#Column(name = "name")
private Long name;
#OneToOne
private MasterChild master;
#OneToMany(mappedBy = "parent", fetch = FetchType.LAZY)
private Set<EntityB> childEntities;
}
i try to write a JPQL query, which deletes all entities which are within a collection of another entity.
(Example code, without getter/setter and annotations)
class Aa implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String value;
}
#Entity
class A implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#JoinColumn(nullable = false)
#OneToOne
private Aa aa;
#OneToMany(fetch = FetchType.EAGER, cascade = {CascadeType.ALL})
private List<B> data;
}
#Entity
class B implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String value;
}
I have tried the following:
DELETE FROM B b WHERE b.id IN(SELECT k.id FROM A a JOIN a.data k WHERE a.id = :id)
But its end in a foreign key violation exception.
Another approach was
DELETE FROM B b WHERE EXISTS(SELECT a FROM A a WHERE a.id = :id)
but its end in a foreign key violation too.
However if i execute a sql query on the database directly, like
DELETE FROM B WHERE id = <a id number here>
then no error occurs...
EntityManager.remove() is not an option because i want to delete a huge amout of data.
I'm thankfully for every answer and help.
Can't add comments since I don't have enough reputation.
You skipped the annotations, which are kind of important, because it would help to know which side, parent, child or maybe something in between them, is in control of the relationship. For example, if parent side is controlling, in some cases it is enough to empty the List on parent side and persist to remove its children. So more code would be helpful.
EDIT 1:
If you are using JPA 2.0 you could add orphanRemoval="true" to your #OneToMany. Then just get the parent from persistence, do parent.getData().clear(), and then do EntityManager.merge(parent) OR make children aware of relationship. You are trying to remove children that aren't aware of their parent so I suggest doing so through parent, or making them aware.
Okay, with the help of Syfors' suggestions i have change the relationship between A and B:
class Aa implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String value;
}
#Entity
class A implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#JoinColumn(nullable = false)
#OneToOne
private Aa aa;
#OneToMany(fetch = FetchType.EAGER, cascade = {CascadeType.ALL}, mappedBy = "owner")
private List<B> data;
}
#Entity
class B implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#OneToOne
private A owner;
private String value;
}
I have also found:
[...]Normally it is best to define the ManyToOne back reference in Java[...]
Source: http://en.wikibooks.org/wiki/Java_Persistence/OneToMany
Now JPA doesn't generate a thrid table for the m:1 relationship and now my JPQL query works fine:
DELETE FROM B b WHERE b.id IN(SELECT k.id FROM A a JOIN a.data k WHERE a.id = :id)
I have two tables which have Many-to-Many relations which have a JoinTable USER_SERVICES as below.
#Entity
public class User implements Serializable {
#NotNull
#Column(unique=true)
private String username;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private Long id;
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(
name = "USER_SERVICES",
joinColumns = {#JoinColumn(name = "id", referencedColumnName = "id")},
inverseJoinColumns = {#JoinColumn(name = "", referencedColumnName = "name")})
private Set<Services> services;
// Getters and Setters
}
#Entity
public class Services implements Serializable {
#NotNull
#GeneratedValue(strategy = GenerationType.AUTO)
#Id
private Long serviceId;
#NotNull
#Column(unique=true)
private String name;
//Getters and Setters
}
The above code creates a table USER_SERVICES, but I also want to have a Many-to-Many relation on the table USER_SERVICES with another table RATINGS which would result in another table USER_SERVICES_RATINGS. how can I define this relation with Hibernate/JPA annotations?
Bi-Directional Many to Many using user managed join table object (Relatively common)
Commonly used when you want to store extra information on the join object such as the date the relationship was created.
public class Foo{
private UUID fooId;
#OneToMany(mappedBy = "bar", cascade=CascadeType.ALL)
private List<FooBar> bars;
}
public class Bar{
private UUID barId;
#OneToMany(mappedBy = "foo", cascade=CascadeType.ALL)
private List<FooBar> foos;
}
#Entity
#Table(name="FOO_BAR")
public class FooBar{
private UUID fooBarId;
#ManyToOne
#JoinColumn(name = "fooId")
private Foo foo;
#ManyToOne
#JoinColumn(name = "barId")
private Bar bar;
//You can store other objects/fields on this table here.
}
You need to create an explicit UserServices entity and setup the relationship to the Ratings entity per your needs.
Remember that in hibernate you model relationships between entities (i.e. your java objects), not db tables.