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]
Related
I have a two entities relationship, I believe that I did all fine, but when I try to run my application, Payara is launching this error:
Caused by: Exception [EclipseLink-7154] (Eclipse Persistence Services - 2.7.4.payara-p2): org.eclipse.persistence.exceptions.ValidationException
Exception Description: The attribute [retenciones] in entity class [class komp.model.ChequePropio] has a mappedBy value of [chequepropio] which does not exist in its owning entity class [class komp.model.RetencionChp]. If the owning entity class is a #MappedSuperclass, this is invalid, and your attribute should reference the correct subclass.
Here one of entities causing the problem:
#Entity
#Table(name = "chequepropio", uniqueConstraints = {
#UniqueConstraint(name = "ukchequepropio", columnNames = {"idbanco", "idempresa", "idtipo", "numero"})})
public class ChequePropio implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#OneToMany(fetch = FetchType.LAZY,mappedBy = "chequepropio")
private List<RetencionChp> retenciones;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
}
The other entities:
#Entity
#Table(name = "retencionchp")
public class RetencionChp implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "idcheque", insertable = true, updatable = true)
private ChequePropio chequePropio;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public ChequePropio getChequePropio() {
return chequePropio;
}
public void setChequePropio(ChequePropio chequePropio) {
this.chequePropio = chequePropio;
}
}
I can't see any error in the relationship, somebody could say me what is wrong?
Thanks in advance!
Fernando
The error is self-explanatory:
The attribute [retenciones] in entity class [class
komp.model.ChequePropio] has a mappedBy value of [chequepropio] which
does not exist in its owning entity class
Mind the keyword chequepropio. You don't have a chequepropio property in RetencionChp. You have a chequePropio instead. Mind the uppercase P. The following should solve it (mappedBy = "chequePropio")).
#Entity
#Table(name = "chequepropio", uniqueConstraints = {
#UniqueConstraint(name = "ukchequepropio", columnNames = {"idbanco", "idempresa", "idtipo", "numero"})})
public class ChequePropio implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#OneToMany(fetch = FetchType.LAZY,mappedBy = "chequePropio")
private List<RetencionChp> retenciones;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
}
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?
I have two entities with one to many relationships as below. Everything works fine except delete action. On deleting, I was getting ERROR: relation "a_b" does not exist. For that, I found the solution here.
According to an answer, there was an issue with the relationship and hibernate treats relationships as separate uni-directional relationships and it will create the third table a_b and tracks both sides of the relationship independently. To resolve the issue I had added mappedBy = "a".
Question is
Why does hibernate fires delete query for table a_b while it does not insert into a_b at the time new record creation?
Log on insert
Hibernate: insert into a...
Hibernate: insert into b...
Hibernate: insert into b...
Hibernate: insert into b...
**Why insert into a_b... is not generated/inserted?**
Log on delete
Hibernate: select a0_.id as id1_11_, from a a0_ where (a0_.id in (?))?
Hibernate: delete from b where a_id in (?)
Hibernate: delete from a_b where (a_id) in (select id from a where id in (?))
**Why delete from a_b if nothing is inserted into a_b**
12:19:50.432 [XNIO-1 task-20] WARN o.h.e.jdbc.spi.SqlExceptionHelper - SQL Error: 0, SQLState: 42P01
12:19:50.433 [XNIO-1 task-20] ERROR o.h.e.jdbc.spi.SqlExceptionHelper - ERROR: relation "a_b" does not exist
with cause = 'org.hibernate.exception.SQLGrammarException: could not execute statement' and exception = 'could not execute statement; SQL [n/a]; nested exception is org.hibernate.exception.SQLGrammarException: could not execute statement'
Entity A
#Entity
#Table(name = "a")
public class A extends AbstractAuditingEntity implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
#SequenceGenerator(name = "sequenceGenerator")
private Long id;
#OneToMany
private List<B> b;
.....
}
Entity B
#Entity
#Table(name = "b")
public class B extends AbstractAuditingEntity implements Serializable{
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
#SequenceGenerator(name = "sequenceGenerator")
private Long id;
#ManyToOne
private A a;
.....
}
AServiceImpl
#Override
public int delete(List<Long> ids) {
...
bRepository.deleteWithIds(ids);
aRepository.deleteWithIds(ids);
}
BRepository
#Transactional
#Modifying
#Query("delete from b x where x.a.id in :ids")
void deleteLogsWithIds(#Param("ids") List<Long> ids);
ARepository
#Modifying
#Transactional
#Query("delete from a x where x.id in :ids")
void deleteJobWithIds(#Param("ids") List<Long> ids);
Current Code
Entity A
#Entity
#Table(name = "a")
public class A extends AbstractAuditingEntity implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
#SequenceGenerator(name = "sequenceGenerator")
private Long id;
#OneToMany(mappedBy = "a")
private List<B> b;
.....
}
Entity B
#Entity
#Table(name = "b")
public class B extends AbstractAuditingEntity implements Serializable{
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
#SequenceGenerator(name = "sequenceGenerator")
private Long id;
#ManyToOne
private A a;
.....
}
EDIT: Insert sequence
Save Entity A
aRepository.saveAndFlush(a);
Make a call to third party API and based on response set Entity A
for saving Entity B
x.forEach(b-> {
b.setA(aRepository.findById(aId).get());
bRepository.save(b);
});
There can be many scenarios to consider
If you are using a uni-directional oneToMany mapping it will require a join table to save the relationship.Since, a single A entity is associated with multiple B entities and due to its unidirectional nature it does not has a mapping column in B table.enter code here
#Entity
#Table(name = "A")
public class A {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "ID")
private int id;
private String stateName;
//This is uni-directional since we donot have a corresponding reference to A in B entity
#OneToMany(cascade = CascadeType.ALL)
List<B> bs = new ArrayList<>();
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public List<B> getBs() {
return bs;
}
public void setBs(List<B> bs) {
this.bs = bs;
}
public String getStateName() {
return stateName;
}
public void setStateName(String stateName) {
this.stateName = stateName;
}
}
#Entity
#Table(name="B")
public class B {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name="ID")
private int id;
private String districtName;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getDistrictName() {
return districtName;
}
public void setDistrictName(String districtName) {
this.districtName = districtName;
}
}
In the above case its uni-directional oneToMany and it will require a join-table.
If you save your entity like this
enter code here
A a= new A();
B b=new B();
B b1=new B();
List<B> bs=new ArrayList<>();
bs.add(b);
bs.add(b1);
aRepository.save(a);
This will save the relationship mapping in join table.
Case 2:- Now if you add the following in the B entity class it will create a foreign-key column to A table. This will be again a unidirection ManyToOne mapping.
enter code here
#ManyToOne()
A a;
If you the following
enter code here
A a =new A();
B b =new B();
b.setA(a);
B b1=new B();
b1.setA(a);
bRepository.save(b);
bRepository.save(b1);
This will not save the relationship in the join table instead it will use the foreign-key which is present in the table B column named A_ID.
Case 3 :- Bidirectional oneToMany
enter code here
#Entity
#Table(name = "A")
public class A {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "ID")
private int id;
private String stateName;
#OneToMany(mappedBy = "a", cascade = CascadeType.ALL)
List<B> bs = new ArrayList<>();
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public List<B> getBs() {
return bs;
}
public void setBs(List<B> bs) {
this.bs = bs;
}
public void addB(B b) {
b.setA(this);
bs.add(b);
}
public void removeB(B b) {
b.setA(null);
bs.remove(b);
}
public String getStateName() {
return stateName;
}
public void setStateName(String stateName) {
this.stateName = stateName;
}
}
#Entity
#Table(name = "B")
public class B {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "ID")
private int id;
private String districtName;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "A_ID")
A a;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public A getA() {
return a;
}
public void setA(A a) {
this.a = a;
}
public String getDistrictName() {
return districtName;
}
public void setDistrictName(String districtName) {
this.districtName = districtName;
}
}
The above entity mapping is bi-directional oneToMany and doesn't uses the join-table.
Whenever I try to get the joined object it is returning null. Sample code below:
Table A
id
name
b_id
Table B
id
name
Coding
#Entity
#Table(name = "A")
public class A {
private Integer id;
#OneToOne
#JoinColumn(name = "b_id", nullable = false)
private B b;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
public Integer getId() {
return id;
}
public B getb() {
return b;
}
}
#Entity
#Table(name = "B")
public class B {
private Integer id;
private String name;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
public Integer getId() {
return id;
}
public String getname() {
return name;
}
}
When I use getA(id).getB() function it returns null. When I use getA it returns a valid object and not null.
You have to move your #Id and #GeneratedValue(strategy = GenerationType.IDENTITY)
and placed above your id variable like this.
#Entity
#Table(name = "A")
public class A{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#OneToOne
#JoinColumn(name = "b_id", nullable = false)
private B b;
public Integer getId() {
return id;
}
public B getb() {
return b;
}
}
Hope this will help.
May be you should give the FetchType as Eager in the joinColumn annotation to get object B also loaded while a is loaded.
You could consider use more annotation properties, for example:
#OneToOne(cascade = CascadeType.ALL)
#JoinColum(name = "b_id" referencedColumnName = "id", nullable = false)
private B b;
#Entity
#Table(name = "B")
public class B{
**#Column(name="b_id", nullable=false)**
private Integer id;
private String name;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
public Integer getId() {
return id;
}
public String getname() {
return name;
}
}
Use #Column(name="b_id", nullable=false)
I'm trying to get Hibernate #OneToOne annotation working with 2 classes, Hito and Portada. Portada table has the foreign key of Hito, an int attribute called hito.
My entities looks like this:
Hito:
#Entity
#Table(name = "hito")
public class Hito implements Serializable {
//...other attributes
private Portada portada;
//...getters and setters from other attributes
#OneToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER, mappedBy = "hito")
public Portada getPortada(){ return portada;}
public void setPortada(Portada portada){ this.portada = portada;}
}
Portada:
#Entity
#Table(name = "portada")
public class Portada {
//...other attributes
private Hito hito;
//...getters and setters from other attributes
#OneToOne(fetch = FetchType.EAGER, optional = false)
#JoinColumn(name = "hito")
public Hito getHito() {return hito;}
public void setHito(Hito hito) {this.hito = hito;}
}
When I call hito.getPortada(), I expect a Portada object, but it returns null.
Any suggestions?
I tried to reproduce your problem with code:
#MappedSuperclass
public abstract class BaseEntity {
#Id #GeneratedValue
private Long id;
#Version
private long version;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public long getVersion() {
return version;
}
public void setVersion(long version) {
this.version = version;
}
}
#Entity
#Table(name = "portada")
public class Portada extends BaseEntity {
//...other attributes
#OneToOne(fetch = FetchType.EAGER, optional = false)
#JoinColumn(name = "hito")
private Hito hito;
//...getters and setters from other attributes
public Hito getHito() {return hito;}
public void setHito(Hito hito) {this.hito = hito;}
}
#Entity
#Table(name = "hito")
public class Hito extends BaseEntity implements Serializable {
#OneToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER, mappedBy = "hito")
private Portada portada;
public Portada getPortada(){ return portada;}
public void setPortada(Portada portada){ this.portada = portada;}
}
// app:
Portada p = new Portada();
Hito h = new Hito();
p.setHito(h);
h.setPortada(p);
entityManager.persist(h);
entityManager.flush();
entityManager.clear();
Hito h2 = entityManager.find(Hito.class, h.getId());
System.out.println(h2.getPortada().toString());
tx.commit();
The last find generated sql:
select
hito0_.id as id1_4_0_,
hito0_.version as version2_4_0_,
portada1_.id as id1_7_1_,
portada1_.version as version2_7_1_,
portada1_.hito as hito3_7_1_
from
hito hito0_
left outer join
portada portada1_
on hito0_.id=portada1_.hito
where
hito0_.id=?
Everything worked for me...
EDIT: Only difference is that I like to put mapping attributes on fields instead of properties but it doesn't matter in this problem. Please check if you add both of your classes to persistance.xml or hibernate config.