TL;DR
For OneToMany
maping between Note
and Tag
using third table Note_tag
, the save()
is not able to save Note
entity.
Background:
So I was workng on this NoteApp(Github repo location) which saves a Note with title, description, status and tag(tag being a String value). As a feature update, I thought to add multiple Tags for a Note. For this, I created a Tag table and a third association table of tag and note using faily straigt forward @JoinTable
annotation. This feature led me to above mentioned issue while saving the Note entity.
What I am using behind the screen:
Java 1.8, Hiberanate, SpringBoot
More details on tech stack at here
What I already have working:
Save()
Note
without Tag
s.
My Note.java:
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.OneToMany;
import javax.persistence.Table;
@Entity
@Table(name = "T_NOTE")
public class Note implements Serializable {
private static final long serialVersionUID = -9196483832589749249L;
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name="ID")
private Integer id;
@Column(name="TITLE")
private String title;
@Column(name="DESCRIPTION")
private String description;
@Column(name = "LAST_UPDATED_DATE")
private Date lastUpdatedDate;
@Column(name="STATUS")
private String status;
@OneToMany(cascade = CascadeType.ALL)
@JoinTable(name = "T_NOTE_TAG", joinColumns = { @JoinColumn(name="NOTE_ID", referencedColumnName = "ID") }, inverseJoinColumns = {
@JoinColumn(name = "TAG_ID", referencedColumnName = "TAG_ID") })
private List<Tag> tags = new ArrayList<Tag>();
/** getters setters omitted for brevity**/
}
My Tag.java:
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
@Entity
@Table(name = "T_TAG")
public class Tag implements Serializable {
private static final long serialVersionUID = -2685158076345675196L;
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name="TAG_ID")
private Integer tagId;
@Column(name="TAG_NAME")
private String tagName;
}
My NoteDto.java:
public class NoteDto {
private int id;
private String title;
private String description;
private List<TagDto> tags = new ArrayList<TagDto>();
private Date lastUpdatedDate;
private String status;
}
My TagDto.java:
public class TagDto {
private int tagId;
private String tagName;
}
My Table creation queries:
CREATE database if NOT EXISTS noteApp;
CREATE TABLE `T_NOTE` (
`id` int(11) unsigned NOT NULL auto_increment,
`description` varchar(20) NOT NULL DEFAULT '',
`header` varchar(20) NOT NULL,
`status` varchar(20) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `T_TAG` (
`tag_id` int unsigned not null auto_increment,
`tag_name` varchar(30) not null,
PRIMARY KEY(TAG_ID)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `T_NOTE_TAG` (
`note_tag_id` int unsigned not null auto_increment,
`note_id` int unsigned not null,
`tag_id` int unsigned not null,
CONSTRAINT note_tag_note foreign key (`note_id`) references T_NOTE(`id`),
CONSTRAINT note_tag_tag foreign key (`tag_id`) references T_TAG(`tag_id`),
CONSTRAINT note_tag_unique UNIQUE (`tag_id`, `note_id`),
PRIMARY KEY (`note_tag_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
use noteApp;
ALTER TABLE T_NOTE
Change header title varchar(1000) NOT NULL;
ALTER TABLE T_NOTE
ADD COLUMN last_updated_date timestamp AFTER status;
Error logs:
Hibernate: select next_val as id_val from hibernate_sequence for update
Hibernate: update hibernate_sequence set next_val= ? where next_val=?
test
Hibernate: insert into T_NOTE (DESCRIPTION, LAST_UPDATED_DATE, STATUS, TITLE, ID) values (?, ?, ?, ?, ?)
Hibernate: update T_TAG set TAG_NAME=? where TAG_ID=?
2020-04-11 18:02:40.647 ERROR 3614 --- [nio-8080-exec-1] o.h.i.ExceptionMapperStandardImpl : HHH000346: Error during managed flush [Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) : [com.sandeep.SpringBootDemo.model.Tag#0]]
org.springframework.orm.jpa.JpaOptimisticLockingFailureException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) : [com.sandeep.SpringBootDemo.model.Tag#0]; nested exception is javax.persistence.OptimisticLockException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) : [com.sandeep.SpringBootDemo.model.Tag#0]
at org.springframework.orm.jpa.EntityManagerFactoryUtils.convertJpaAccessExceptionIfPossible(EntityManagerFactoryUtils.java:396)
at org.springframework.orm.jpa.DefaultJpaDialect.translateExceptionIfPossible(DefaultJpaDialect.java:127)
at org.springframework.dao.support.DataAccessUtils.translateIfNecessary(DataAccessUtils.java:242)
at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:545)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:746)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:714)
at org.springframework.transaction.interceptor.TransactionAspectSupport.
The exception suggests me that Tag is being updated wrongly which raises question that why is it calling update in first place for Tag
whereas insert is correctly called for Note
?
I tried finding solutions before posting this question but couldn't find any.
Thanks in advance!