0

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

  1. Save Entity A

    aRepository.saveAndFlush(a);

  2. 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); });

Romil Patel
  • 12,879
  • 7
  • 47
  • 76
  • You need to be careful here. Table `a_b` (or whatever its real name is) is a _junction_ table which if I recall correctly is auto generated by Hibernate to accommodate your many to one relationship. I'm not sure you should be issuing deletes directly against this table; from the ORM point of view, it doesn't really exist. – Tim Biegeleisen Jan 02 '20 at 07:26
  • Have you tried @OneToMany(cascade = CascadeType.ALL) ? – Vivek Jan 02 '20 at 07:27
  • Thanks @TimBiegeleisen, Does hibernate generates *junction table* even after adding *mappedBy = "a"*? As per answer, I have linked it should not create it after adding the owning side. I have added the delete call for more details. Can you please share more details about it? – Romil Patel Jan 02 '20 at 08:21
  • Unfortunately my expertise more or less stops with the comment I left above. For starters, check your underlying database to see what Hibernate is doing with it. Ideally you should _not_ have to delete from the junction table in Hibernate, as I mentioned above. Instead, if you delete the correct side of the relationship, Hibernate should be able to use cascading deletion to clean everything up from all tables. – Tim Biegeleisen Jan 02 '20 at 08:22
  • Please edit your question and give it a better title. _"Understanding Hibernate ORM"_ is too general and too vague, and doesn't tell us anything about your actual question. – Mark Rotteveel Jan 02 '20 at 08:23
  • @Vivek, I have tried after your comment and it gives the *ERROR: relation "a_b" does not exist.* without *mappedBy*. – Romil Patel Jan 02 '20 at 08:24
  • Can you post the method which saves A and B entities? – user06062019 Jan 03 '20 at 10:52
  • @user06062019, `aRepository.saveAndFlush(a);` and `bRepository.save(b)` – Romil Patel Jan 03 '20 at 11:01
  • 1 if you are doing this,creating A entity and saving it and then associating this created A entity to B and saving.In this case the reference of A entity is saved from ManyToOne side,which essentially means that hibernate will populate the foreign_key column that is generated by mapping ManyToOne on b's entity's attribute. 2 If you try to associate B's in the A entity by setting the list and then saving the B (via cascade) it will populate the join table.In short,since A entity doesnt have any way of storing reference of multiple B's ,hibernate is using join_table. – user06062019 Jan 03 '20 at 11:55
  • This might help you https://vladmihalcea.com/the-best-way-to-map-a-onetomany-association-with-jpa-and-hibernate/ – user06062019 Jan 03 '20 at 11:57
  • Thanks @user06062019, First I save entity A and then request has been made to third party API based on a response I have a forEach where I set the entity A for saving entity B. I have updated the question with insert code and sequence – Romil Patel Jan 03 '20 at 13:59
  • b.setA(aRepository.findById(aId).get());bRepository.save(b).This will not create an entry into the join_table since the foreign key is stored in the B table itself.What do you want to achieve? A bidirection oneToMany relation? – user06062019 Jan 03 '20 at 14:04
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/205306/discussion-between-patel-romil-and-user06062019). – Romil Patel Jan 03 '20 at 16:35

1 Answers1

1

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.

user06062019
  • 681
  • 4
  • 9