I created several similar threads about particular issues, but let me summarize the problem I'm facing more globally:
GOAL
Need to intercept all Insert/Update/Delete DB Transactions in an application written in Spring 4.1.5, Hibernate 4.3.8. The app uses all styles of Hibernate usage:
sessionFactory.getCurrentSession().saveOrUpdate(obj);
Query q = "update Obj..."; q.executeUpdate();
Query q = "update Obj..."; q.executeUpdate();
Criteria q = sessionFactory.getCurentSession().createCriteria(..); q.list();
Criteria q = sessionFactory.getCurentSession().createCriteria(..); q.list();
Possible Approaches
public class MyInterceptor extends EmptyInterceptor {..}
Override either (1) Transaction-level methods or (2) specific action methods.
Transaction-level:
@Override
public void afterTransactionBegin(Transaction tx) {
super.afterTransactionBegin(tx);
}
Specific action level :
@Override
public boolean onSave(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types) {
return super.onSave(entity, id, state, propertyNames, types);
}
@Override
public void onDelete(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types) {
super.onDelete(entity, id, state, propertyNames, types);
}
EventListener : Implement a PreUpdateEventListener
, PreInsertEventListener
and register it.
@Component public class HibernateSaveUpdateEventListener implements PreUpdateEventListener, PreInsertEventListener { @Override public boolean onPreInsert(PreInsertEvent arg0) { //... } @Override public boolean onPreUpdate(PreUpdateEvent arg0) { //... } }
Problems
EventListener
nor Interceptor
approach intercepts HQL executeUpdate() . But we have many instances of that in the app. Some other users have confirmed that:https://forum.hibernate.org/viewtopic.php?f=1&t=1012054
https://coderanch.com/t/669945/java/Hibernate-interceptor-working-hibernate-query
Seems like the issue is that EventListener/Interceptor
only work with a Session
. But HQL executeUpdate is on the Query
object, not the Session
.
afterTransactionBegin(Transaction tx)
either. The reason is, I can't distinguish Read-Only Select Transactions from Write Transactions. tx
doesn't tell me whether I'm in a readOnly=false
or readOnly=true
transaction. So the afterTransactionBegin
override will fire for all transactions, and I need to limit it non-Read-Only ones only (Insert, Update, Delete). @Transactional(readOnly = false) @Transactional(readOnly = true)
So is there a solution to comprehensively intercept Insert/Update/Delete for a Hibernate app that uses both object operations and HQL operations?
There's an easy way to check for Read-Only/non-Read-Only Transactions with a Spring-level transaction listener, and this will include outside SQL/HQL (non-entity) . On the Spring level, rather than Hibernate, there's a method called TransactionSynchronizationManager.isCurrentTransactionReadOnly()
that tells us if we're inside a read-only transaction we're intercepting. It can be called from the following implementation of TransactionSynchronization
which uses a pointcut to intercept all @Before
@Transactional
Spring processing.
@Component
@Aspect
public class TransactionSynchronizationAspect implements TransactionSynchronization {
@Before("@annotation(org.springframework.transaction.annotation.Transactional)")
public void registerTransactionSynchronization() {
boolean isReadOnly = TransactionSynchronizationManager.isCurrentTransactionReadOnly();
if (!isReadOnly) {
// ... logic goes here...
}
// Continue
TransactionSynchronizationManager.registerSynchronization(this);
}
@Override
public void afterCompletion(int status) {
// No custom code here
}
}
As for the Hibernate approach -- the original Hibernate Transaction Listener in the Original Post -- it doesn't distinguish between Read-Only/non-Read-Only, but we can add a tx:advice
to restrict the Listener to fire only on specified (mapped) Service method names, like so:
<aop:config>
<aop:advisor id="managerTx" advice-ref="txAdvice"
pointcut="execution(* com.app.*.*.service.*.*(..))"/>
</aop:config>
<tx:advice id="txAdvice" transaction-manager="customTransactionManager">
<tx:attributes>
<tx:method name="delete*" propagation="REQUIRED" isolation="READ_COMMITTED"/>
<tx:method name="update*" propagation="REQUIRED" isolation="READ_COMMITTED"/>
<tx:method name="save*" propagation="REQUIRED" isolation="READ_COMMITTED"/>
</tx:attributes>
</tx:advice>
This XML file can be imported with @ImportResource. With this strategy, all Write operations would be intercepted with the Listener, while Reads would always go through automatically (ie we don't need to do anything custom with them). This Hibernate Transaction Listener can be implemented as
public class HibernateTransactionInterceptor extends EmptyInterceptor {
@Override
public void afterTransactionBegin(Transaction tx) {
//...
}
}
I managed to intercept executeUpdate()
by overriding ASTQueryTranslatorFactory
with my own implementation:
You just need to set the hibernate property hibernate.query.factory_class
with a custom class.
<bean lazy-init="false" id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean" depends-on="dataSource">
...
<property name="jpaProperties">
<props>
<prop key="hibernate.query.factory_class">package.to.your.OwnASTQueryTranslatorFactory</prop>
...other properties
</props>
</property>
</bean>
public class OwnASTQueryTranslatorFactoryextends ASTQueryTranslatorFactory {
private static final Pattern INVALID_PATTERN = Pattern.compile("some regexp to prevent certain queries to be executed", Pattern.CASE_INSENSITIVE);
public static boolean isInvalidHql(String hql) {
return INVALID_PATTERN.matcher(hql).find()
}
@Override
public QueryTranslator createQueryTranslator(
String queryIdentifier,
String hql,
Map filters,
SessionFactoryImplementor factory,
EntityGraphQueryHint entityGraphQueryHint) {
if (isInvalidHql(hql)) {
/**
* My use-case was to prevent certain syntax for HQL to be executed
* here you can add w/e you want to do when the `createQuery` is executed.
*
* There's a lot more you can do if you also add a custom implementation
* of `QueryTranslatorImpl`
*
**/
throw new QueryException("Invalid HQL!", hql);
}
return super.createQueryTranslator(queryIdentifier, hql, filters, factory, entityGraphQueryHint);
}
}
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.