How to comprehensively intercept Insert/Update/Delete DB Transactions in a Hibernate 4 application
Asked Answered
A

2

6

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:

  • Object-based through Session, e.g. sessionFactory.getCurrentSession().saveOrUpdate(obj);
  • HQL executeUpdate, e.g. Query q = "update Obj ..."; q.executeUpdate();
  • Criteria API, e.g. Criteria q = sessionFactory.getCurentSession().createCriteria(..); q.list();

Possible Approaches

  • Interceptor: Implement an Interceptor.

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

  • Neither the 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.

  • Can't use the Transaction-level Interceptor approach of 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?

Alfano answered 16/11, 2018 at 16:50 Comment(1)
Using StatementInspector and sql parser might be another possible approachHinshaw
A
1

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
    }
}
Alfano answered 4/8, 2022 at 18:4 Comment(0)
C
1

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 OwnASTQueryTranslatorFactory extends 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);
    }
}
Cardholder answered 20/9, 2022 at 22:19 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.