Understanding Hibernate Junction Table - java

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.

Related

Exception caused by one-to-many bidirectional relationship

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;
}
}

Map JPA Embedded entity class id to Embeddable entity class id

I have a class:
#Entity
public class A {
#Embedded
#AttributeOverride(name = "id", column = #Column(name = "b_id"))
private B b;
}
There is column b_id BIGINT NOT NULL in table A
#Embeddable
#Entity
public class B {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
}
we are getting error: Caused by: org.hibernate.MappingException: component property not found: id
Basically, we need to map B in A using id
Kindly help
I think the problem is with #Id in embedded class. We can not use in an embedded class. Try removing that? If you can remove it, try using #EmbeddedId if you just need an id field.
Try this
#Entity
public class A implements Serializable {
private static final long serialVersionUID = 9154946919235019012L;
#Embedded
#AttributeOverride(name = "id", column = #Column(name = "b_id"))
private B b;
public A() {
}
public A(B b) {
this.b = b;
}
public B getB() {
return b;
}
public void setB(B b) {
this.b = b;
}
}
And here is class B
#Embeddable
#Entity
public class B implements Serializable {
private static final long serialVersionUID = 5579181803793008928L;
#Id
#Column(nullable = false)
private Long id;
public B(Long id) {
this.id=id;
}
public B(){
}
public void setId(Long id) {
this.id = id;
}
public Long getId() {
return id;
}
}
You don't have getters and setters, or an additional constructor besides the implicit no arg one. You should have both a no-args constructor and getter and setter methods.

Java JPA Hibernate save not inserting referenced entities

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]

Hibernate #OneToOne is null

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.

error creating alias while using hibernate inheritance table per concrete class

I have a model where I implemented table per concrete class. So I have an abstract class having the common properties across multiple tables. and I have the following entities.
#Entity
#Inheritance(strategy=InheritanceType.TABLE_PER_CLASS)
public abstract class BaseForm{
#Id
#GeneratedValue(strategy = GenerationType.TABLE)
protected Long id;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id=id;
}
#Column(name = "submission_date")
protected Date submissionDate;
public Date getSubmissionDate() {
return submissionDate;
}
public void setSubmissionDate(Date submissionDate) {
this.submissionDate=submissionDate;
}
}
#Entity
#Table(name = "form_a")
public class FormA extends BaseForm{
#OneToMany(cascade = CascadeType.ALL, mappedBy = "formA", fetch = FetchType.LAZY)
#Cascade(value = org.hibernate.annotations.CascadeType.DELETE_ORPHAN)
#Fetch(FetchMode.SUBSELECT)
#OrderBy("id")
protected List<UserForm> userForms = new ArrayList<UserForm>();
}
#Entity
#Table(name = "form_b")
public class FormB extends BaseForm{
#OneToMany(cascade = CascadeType.ALL, mappedBy = "formB", fetch = FetchType.LAZY)
#Cascade(value = org.hibernate.annotations.CascadeType.DELETE_ORPHAN)
#Fetch(FetchMode.SUBSELECT)
#OrderBy("id")
protected List<UserForm> userForms = new ArrayList<UserForm>();
}
#Entity
#Table(name = "user_form")
public class UserForm {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
protected Long id;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
#ManyToOne(optional = true)
protected FormA formA;
#ManyToOne(optional = true)
protected FormB formB;
#ManyToOne(optional = true)
protected User user;
}
But whenever I try to use createAlias on the polymorphic query selecting all the forms joining userForms to return the user information for each form. It raises an exception.
2016-01-04 12:21:54,158 ERROR [org.hibernate.util.JDBCExceptionReporter] Not unique table/alias: 'userforms1_'
DetachedCriteria baseCR = DetachedCriteria.forClass(BaseForm.class);
baseCR.createAlias("userForms", "userForms");
);
baseCR.add(Restrictions.disjunction()
.add(Restrictions.isNotNull("userForms.formA"))
.add(Restrictions.isNotNull("userForms.formB"))
);
baseCR.setResultTransformer(CriteriaSpecification.DISTINCT_ROOT_ENTITY);
baseCR.setProjection(Projections.rowCount());
List results= ht.findByCriteria(baseCR);
Here is the generated hibernate query
SELECT COUNT(*) AS y0_
FROM
(SELECT id,
submissionDate,
1 AS clazz_
FROM form_a
UNION
SELECT id,
submissionDate,
2 AS clazz_
FROM form_b
) this_
INNER JOIN user_form userforms1_
ON this_.id=userforms1_.formA_id
INNER JOIN user_form userforms1_
ON this_.id=userforms1_.formB_id
WHERE (userforms1_.formA IS NOT NULL
OR userforms1_.formB IS NOT NULL)
Any idea what would be the problem. and how to solve it?
You are accessing subclass fields with a superclass alias, which is conceptually wrong (although works in some situations, but obviously not always).
You should consider moving userForms to the base class or transforming your query to something like this (JPQL, just translate it to an equivalent Criteria):
select bf from BaseForm bf
where bf.id in (select fa.id from FormA fa join fa.userForms)
or bf.id in (select fb.id from FormB fb join fb.userForms)

Categories

Resources