I noticed that when I have a ManyToMany relationship and @Version field on an entity then when I try to add some other entitys that have relationship with this entity hibernate triggers an update on the entity and updates the version.
This could be what I want but what if I dont want to update the version every time a relationship changes. Is there any way to achieve this when using @Version?
I have a small dummy example:
@Entity
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@EntityListeners(AuditingEntityListener.class)
public class Shop {
@NotNull
@LastModifiedDate
@Column(name = "last_updated")
private Instant lastUpdated;
@NotNull
@Version
@Column(name="version")
private Long version;
@Id
@SequenceGenerator(name = "shop_id_seq", sequenceName = "shop_id_seq", allocationSize = 1)
@GeneratedValue(
generator = "shop_id_seq",
strategy = GenerationType.SEQUENCE
)
private Long id;
private String name;
@ManyToOne()
private Location location;
@OneToMany(mappedBy = "shop")
private List<ShopProduct> shopProducts;
@ManyToMany()
@JoinTable(
name="shop_worker",
joinColumns = @JoinColumn(name = "shop_id"),
inverseJoinColumns = @JoinColumn(name = "worker_id")
)
private Set<Worker> workers = new HashSet<>();
public void addWorker(Worker worker) {
workers.add(worker);
worker.getShops().add(this);
}
public void removeWorker(Worker worker) {
workers.remove(worker);
worker.getShops().remove(this);
}
}
@Entity
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@EntityListeners(AuditingEntityListener.class)
public class Worker {
@NotNull
@LastModifiedDate
@Column(name = "last_updated")
private Instant lastUpdated;
@Id
@SequenceGenerator(name = "worker_id_seq", sequenceName = "worker_id_seq", allocationSize = 1)
@GeneratedValue(
generator = "worker_id_seq",
strategy = GenerationType.SEQUENCE
)
private Long id;
private String name;
@ManyToMany(mappedBy = "workers")
public Set<Shop> shops = new HashSet<>();
public void addShop(Shop shop) {
shops.add(shop);
shop.getWorkers().add(this);
}
public void removeShop(Shop shop) {
shops.remove(shop);
shop.getWorkers().remove(this);
}
}
@Test
@Transactional
void testAddingShopToWorker(){
System.out.println("TEST SETUP");
var shop = new Shop();
shop.setName("Shop1");
em.persist(shop);
em.flush();
em.clear();
System.out.println("TEST BEGINS");
Shop myShop = em.find(Shop.class, 1L);
var worker = new Worker();
worker.setName("John");
worker.addShop(myShop);
workerRepository.save(worker);
em.flush(); // hibernate triggers update on shop even though its not needed
}
from logs: Hibernate: update shop set last_updated=?, location_id=?, name=?, version=? where id=? and version=?
Note: When you comment out the version field then hibernate wont trigger the update. It just makes an insert to the intermediery table like expected.
This is required by 3.4.2 of the JPA specification (2.1 that I'm looking at):
"All non-relationship fields and properties and all relationships owned by the entity are included in version checks[35]."
and
"[35] This includes owned relationships maintained in join tables."
If you want to avoid this, switch the owning side of the relationship so Workers (which doesn't have optimistic locking) owns it.
Alternatively, you'll need to use native Hibernate API to bypass versions: https://stackoverflow.com/questions/33972564/is-it-possible-to-turn-off-hibernate-version-increment-for-particular-update#:~:text=Hibernate%20Optimistic%20Locking%20can%20be%20bypassed%20using%20hibernate,%2F%2FDetaching%20to%20prevent%20hibernate%20to%20spot%20dirty%20fields.