I recently had the need to map a one-to-one entity from an embbeded entity:
#Entity
public class A {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Embedded
private B b;
//getters and setters
}
#Embeddable
public class B {
#OneToOne(mappedBy="a", cascade = CascadeType.ALL, orphanRemoval = true)
private C c;
//getters and setters
}
#Entity
public class C {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#OneToOne
#JoinColumn(name="a_id")
private A a;
//other fields, getters and setters
}
This mapping works correctly when we create, update the information for entity c and delete a (and consequently deletes c).
The problem is when we try to remove C through an update, what really happens is that hibernate updates entity C and sets the a_id field to null. This causes objects C not attached to any entity A.
My solution was to duplicate the information of the relation one to one in the entity A
#Entity
public class A {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Embedded
private B b;
#OneToOne(mappedBy="a", cascade = CascadeType.ALL, orphanRemoval = true)
private C c;
public void setB(final Optional<B> b) {
b.ifPresentOrElse(newB -> {
newB.getC().ifPresent(c -> {
c.setA(this);
this.b = b;
}, () -> {
this.c = null;
this.b = null;
});
}
// other getters and setters
}
Is there any way to not duplicate the information of entity C in A and maintain the correct behavior?
Related
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.
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.
I have the following 2 classes:
#Entity
#Table(name = "TableA")
public class EntityA
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "Id")
private final Integer id = null;
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "BId")
private EntityB b;
public EntityA(EntityB b)
{
this.b = b;
}
}
#Entity
#Table(name = "TableB")
public class EntityB
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "Id")
private final Integer id = null;
#OneToOne(mappedBy = "b")
private final EntityA a = null;
}
When I do
session.save(new EntityA(new EntityB());
the database only inserts a record in TableA and leaves the column that references TableB as NULL
If I first insert b, then a, it works, but it should work with a single call too.
Other questions/answers mention that the annotations are not correct, but I see no difference between mine and the provided solutions.
I also tried adding the CascadeType.PERSIST on both #OneToOne annotations, but that didnt work either.
In jpa, default Cascade setting is NONE ... thus the entities in relationships (B in your case) is not inserted (persisted) by default ... You need to annotate the relationship with
#OneToOne(fetch = FetchType.LAZY,cascade = CascadeType.PERSIST)
First of all you must delete final keyword from your entities.
Try this one:
#Entity
#Table(name = "TableA")
class EntityA {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "Id")
private Integer id;
#OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST)
#JoinColumn(name = "BId")
private EntityB b;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public EntityB getB() {
return b;
}
public void setB(EntityB b) {
this.b = b;
}
public EntityA(EntityB b) {
this.b = b;
b.setA(this);
}
}
#Entity
#Table(name = "TableB")
class EntityB {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "Id")
private Integer id;
#OneToOne(mappedBy = "b")
private EntityA a;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public EntityA getA() {
return a;
}
public void setA(EntityA a) {
this.a = a;
}
}
I am using spring boot, hibernate and H2 database
#SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
ApplicationContext app = SpringApplication.run(DemoApplication.class, args);
ServiceCascade bean = app.getBean(ServiceCascade.class);
bean.save();
}
}
#Service
class ServiceCascade {
#PersistenceContext
private EntityManager entityManager;
#Transactional
public void save() {
EntityA entityA = new EntityA(new EntityB());
entityManager.persist(entityA);
}
}
The following logs show that the two entities are inserted correctly
org.hibernate.SQL : insert into tableb (id) values (null)
org.hibernate.SQL : insert into tablea (id, bid) values (null, ?)
o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [INTEGER] - [1]
In my application I have the following model relationship.
public class EntityA {
(...)
#OneToMany(fetch = FetchType.EAGER, mappedBy = "a")
#Fetch(FetchMode.SELECT)
private List<EntityB> listOfBs;
(...) // and getters and setters
}
public class EntityB {
(...)
#OneToOne
#JoinColumn(name = "idA")
private EntityA a;
#OneToMany(fetch = FetchType.EAGER, mappedBy = "b")
#Fetch(FetchMode.SELECT)
private List<EntityC> listOfCs;
(...) // and getters and setters
}
public class EntityC {
(...)
#ManyToOne
#JoinColumn(name = "idB")
private EntityB b;
#OneToMany(fetch = FetchType.EAGER, mappedBy = "c")
#Fetch(FetchMode.SELECT)
private List<EntityD> listOfDs;
(...) // and getters and setters
}
public class EntityD {
(...)
#ManyToOne
#JoinColumn(name = "idC")
private EntityC c;
#OneToMany(fetch = FetchType.EAGER, mappedBy = "d")
#Fetch(FetchMode.SELECT)
private List<EntityE> listOfEs;
(...) // and getters and setters
}
public class EntityE {
(...)
#ManyToOne
#JoinColumn(name = "idD")
private EntityD d;
private Date dateA;
private Date dateB;
private Float float;
private String stringX;
(...) // and getters and setters
}
The idea here is:
each EntityA can have one or more EntityBs that are associated
from a DualListBox element;
each EntityB can be assigned to only
one EntityA;
each EntityB can have one or more EntityCs. When
the EntityC is created, a List<EntityD> is created, one EntityD
is created and added to listOfDs and set into EntityC. Also, a
List<EntityE> is created, one EntityE is created and added to
listOfEs and set into EntityD.
I hope I managed to make myself clear on the business rules above.
The problem is the removal of an EntityA and its cascading effect.
On my EntityAServiceThe code I'm trying is:
public removeA(String idA) {
A a = service.findAById(idA);
for (EntityB b : a.getListOfBs()) {
for (EntityC c : b.getListOfDs()) {
for (EntityD d : c.getListOfDs()) {
for (EntityE e : d.getListOfEs()) {
service.removeE(e);
}
service.removeD(d);
}
service.removeC(c);
}
b.setA(null);
}
a.setListOfBs(null);
update(a);
remove(a);
}
When I try to remove an entityA, I the entityBs are disassociated from entityA, but all entityCs associated to a EntityB are kept. Same goes, respectively, to entityDs and entityEs.
I have been at this for a while now but still can't see the problem. What's going on and how to fix?
This problem is probably due to my ignorance when it comes to what JPA can and cannot do, so hopefully someone will be able to enlighten me. In short, the removal of an entity from a collection does not get propagated to the in-memory instance of its grandparent. Following is an example scenario.
We have three entities (A, B, C).
#Entity
public class C {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
...
#ManyToOne(fetch = FetchType.EAGER, cascade = {CascadeType.PERSIST, CascadeType.MERGE})
#JoinColumn(name="B_ID")
private B b;
...
}
And,
#Entity
public class B {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String name;
#OneToMany(mappedBy = "b", fetch = FetchType.EAGER, cascade = {CascadeType.REMOVE, CascadeType.MERGE})
private Set<C> cs;
#ManyToOne(fetch = FetchType.EAGER, cascade = {CascadeType.PERSIST, CascadeType.MERGE})
#JoinColumn(name = "A_ID")
private A a;
...
}
And,
#Entity
public class A {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#OneToMany(mappedBy = "tenant", fetch = FetchType.EAGER, cascade ={CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REMOVE})
private Set<B> bs;
...
}
Next, we have a stateless session bean for modifying instances of B.
#Stateless
public class BServiceBean implements BService {
#PersistenceContext(unitName = Constants.PU)
EntityManager em;
#Override
public B updateB(B b) {
return em.merge(b);
}
#Override
public B removeC(int bId, int cId) throws IllegalArgumentException {
B b = em.find(B.class, bId);
if (null == b) {
throw new IllegalArgumentException("No b with id: " + bId + " exists.");
}
C c = em.find(C.class, cId);
if (null == c) {
throw new IllegalArgumentException("No c with id: " + cId + " exists.");
}
b.getCs().remove(c);
em.remove(c);
return em.merge(b);
}
}
We modify a bunch of these entities in a servlet via a container-injected BService instance.
// created and persisted an A with three Bs, one of which has three Cs.
A a = ...;
b2.setName(changedName);
b2 = bService.updateB(b2); // this change is reflected in a
...
b2 = bService.removeC(b2.getId(), c1.getId()); // this change is not reflected in a
// but it is in the db
a = aService.findAById(a.getId); // this instance of a has a b2 without a c1
Why does the removal of an entity from the collection in B not cascade upon merge to A, even though the change of a basic field in B does cascade to A on merge of B? And is there anything I can do to cause JPA to cascade the entity removal upwards to A?
You have cascade only persist and merge You should also need cascade the removal. Simply add following type to cascade add, update and remove.
cascade =CascadeType.ALL