If i have a @OneToMany relationship with @Cascade(CascadeType.SAVE_UPDATE) as follows
public class One {
private Integer id;
private List<Many> manyList = new ArrayList<Many>();
@Id
@GeneratedValue
public Integer getId() {
return this.id;
}
@OneToMany
@JoinColumn(name="ONE_ID", updateable=false, nullable=false)
@Cascade(CascadeType.SAVE_UPDATE)
public List<Many> getManyList() {
return this.manyList;
}
}
And Many class
public class Many {
private Integer id;
/**
* required no-arg constructor
*/
public Many() {}
public Many(Integer uniqueId) {
this.id = uniqueId
}
/**
* Without @GeneratedValue annotation
* Hibernate will use assigned Strategy
*/
@Id
public Integer getId() {
return this.id;
}
}
If i have The following scenario
One one = new One();
/**
* generateUniqueId method will Take care of assigning unique id for each Many instance
*/
one.getManyList().add(new Many(generateUniqueId()));
one.getManyList().add(new Many(generateUniqueId()));
one.getManyList().add(new Many(generateUniqueId()));
one.getManyList().add(new Many(generateUniqueId()));
And i call
sessionFactory.getCurrentSession().save(one);
Before going on
According to Transitive persistence Hibernate reference documentation, you can see
If a parent is passed to save(), update() or saveOrUpdate(), all children are passed to saveOrUpdate()
ok. Now Let's see what Java Persistence With Hibernate book Talks about saveOrUpdate method
Hibernate queries the MANY table for the given id, and if it is found, Hibernate updates the row. If it is not found, insertion of a new row is required and done.
Which can be translated according to
INSERT INTO ONE (ID) VALUES (?)
/**
* I have four Many instances added To One instance
* So four select-before-saving
*
* I DO NOT NEED select-before-saving
* Because i know i have a Fresh Transient instance
*/
SELECT * FROM MANY WHERE MANY.ID = ?
SELECT * FROM MANY WHERE MANY.ID = ?
SELECT * FROM MANY WHERE MANY.ID = ?
SELECT * FROM MANY WHERE MANY.ID = ?
INSERT INTO MANY (ID, ONE_ID) VALUES (?, ?)
INSERT INTO MANY (ID, ONE_ID) VALUES (?, ?)
INSERT INTO MANY (ID, ONE_ID) VALUES (?, ?)
INSERT INTO MANY (ID, ONE_ID) VALUES (?, ?)
Any workaround To avoid select-before-saving ??? Yes, You can either
So as a way to avoid select-before-saving default behavior when using this kind of cascading, i have improved my code by assigning a Hibernate Interceptor to a Hibernate Session whose Transaction is managed by Spring.
Here goes my repository
Before (Without any Hibernate Interceptor): It works fine!
@Repository
public class SomeEntityRepository extends AbstractRepository<SomeEntity, Integer> {
@Autowired
private SessionFactory sessionFactory;
@Override
public void add(SomeEntity instance) {
sessionFactory.getCurrentSession().save(instance);
}
}
After (With Hibernate Inteceptor): something goes wrong (No SQL query is performed - Neither INSERT Nor SELECT-BEFORE-SAVING)
@Repository
public class SomeEntityRepository extends AbstractRepository<SomeEntity, Integer> {
@Autowired
private SessionFactory sessionFactory;
@Override
public void add(SomeEntity instance) {
sessionFactory.openSession(new EmptyInterceptor() {
/**
* To avoid select-before-saving
*/
@Override
public Boolean isTransient(Object o) {
return true;
}
}).save(instance);
}
}
My question is: Why Spring does not persist my Entity and its relationships when using Hibernate Interceptor and what should i do as workaround to work fine ???
Spring maintains an association between the current session and the current transaction (see SessionFactoryUtils.java.) Since there is already a session associated for the current DAO method call, you have to use this Session, or take the plunge of getting involved with the murky details of associating the new session with the previous transaction context. It's probably possible, but with considerable risk, and is definitely not recommended. In hibernate, if you have a session already open, then it should be used.
Having said that, you may be able to get spring to create a new session for you and associate it with the current transaction context. Use SessionFactoryUtils.getNewSession(SessionFactory, Interceptor)
. If you use this rather than hibernate's sessionFactory, then this should keep the association with the transaction.
Initially, you can code this up directly in the DAO. When it's tried and tested and hopefully found to be working, you can then take steps to move the spring code out of your DAO, such as using AOP to add around advice to the add() methods that create and clean up new session.
Another alternative is to use a global Interceptor. Even though it's global, you can give it locally controllable behaviour. The TransientInterceptor contains a threadLocal<Boolean>
. This is the flag for the current thread to indicate if the interceptor should return true for isTransient
. You set it to true at the start of the add() method and clear it at the end. E.g.
class TransientInterceptor extends EntityInterceptor {
ThreadLocal<Boolean> transientFlag = new ThreadLocal<Boolean)();
public boolean isTransient() {
return transientFlag.get()==Boolean.TRUE;
}
static public setTransient(boolean b) {
transientFlag.set(b);
}
}
And then in your DAO:
@Override
public void add(SomeEntity instance) {
try {
TransientInterceptor.set(true);
sessionFactory.getCurrentSession().save(instance);
}
finally {
TransientInterceptor.set(false);
}
}
You can then setup TransientInterceptor as a global interceptor on the SessionFactory (e.g. LocalSessionFactoryBean
.) To make this less invasive, you could create an AOP around advice to apply this behaviour to all your DAO add methods, where appropriate.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With