All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.github.harishb2k.easy.database.mysql.transaction.TransactionInterceptor Maven / Gradle / Ivy

package io.github.harishb2k.easy.database.mysql.transaction;

import com.google.inject.Provider;
import io.gitbub.harishb2k.easy.helper.ApplicationContext;
import io.github.harishb2k.easy.database.mysql.DataSourceFactory;
import io.github.harishb2k.easy.database.mysql.transaction.TransactionContext.Context;
import lombok.extern.slf4j.Slf4j;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import org.springframework.transaction.support.DefaultTransactionStatus;

import javax.sql.DataSource;
import java.util.Map;
import java.util.Objects;
import java.util.StringTokenizer;
import java.util.concurrent.ConcurrentHashMap;

@Slf4j
public class TransactionInterceptor implements MethodInterceptor {
    private final Map transactionManagerMap;
    private final int defaultTimeout;
    private final Provider transactionManagerResolver;

    public TransactionInterceptor(int defaultTimeout, Provider transactionManagerResolver) {
        this.transactionManagerResolver = transactionManagerResolver;
        this.transactionManagerMap = new ConcurrentHashMap<>();
        this.defaultTimeout = defaultTimeout;
    }

    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        Transactional transactional = invocation.getMethod().getAnnotation(Transactional.class);
        if (transactional == null) {
            log.trace("Execute method without transaction - method is not marked with Transactional");
            return invocation.proceed();
        }

        // Process labels provided in Transactional
        TransactionContext.getInstance().clear();
        resolveTransactionManagerByName(transactional);

        // Keep a clone copy of context. This is needed because we can have recursive call of  Transactional methods
        // Those methods will update the context
        Context context = TransactionContext.getInstance().getContext().cloneContext();

        // Make sure we initialized transaction manager if required
        DataSourceTransactionManager transactionManager = ensureDataSourceTransactionManager();

        // Do not do anything if we do not have transaction manager
        if (transactionManager == null) {
            log.trace("Execute method without transaction - method is marked with Transactional, but we do not have transaction manager with name={}", context.getDatasourceName());
            return invocation.proceed();
        }

        DefaultTransactionDefinition definition = new DefaultTransactionDefinition(transactional.propagation().value());
        definition.setName(transactional.value());
        definition.setTimeout(transactional.timeout() > 0 ? transactional.timeout() : 10);
        DefaultTransactionStatus transactionStatus = (DefaultTransactionStatus) transactionManager.getTransaction(definition);

        // Set the user provided name - it is useful in debugging
        resolveName(transactional, definition);

        Object result;
        try {
            result = invocation.proceed();
            log.trace("Execute with transaction (commit) - result={}", result);
            transactionManager.commit(transactionStatus);
            return result;
        } catch (Exception e) {
            log.trace("Execute with transaction failed (rollback) - exception={}", e.getMessage(), e);
            transactionManager.rollback(transactionStatus);
            throw e;
        } finally {
            TransactionContext.getInstance().clear();
        }
    }

    /**
     * @param transactional - instance of @{@link Transactional} from the method which was invoked
     */
    private void resolveTransactionManagerByName(Transactional transactional) {
        String transactionManagerToUse = transactionManagerResolver.get().resolveTransactionManager(transactional);
        Context context = TransactionContext.getInstance().getContext();
        context.setDatasourceName(transactionManagerToUse);
    }

    /**
     * Helper method to set the name for this transaction for debugging.
     */
    private void resolveName(Transactional transactional, DefaultTransactionDefinition definition) {
        if (transactional.label().length == 0) return;
        for (String label : transactional.label()) {
            StringTokenizer st = new StringTokenizer(label, "=");
            String token = st.nextToken();
            if (Objects.equals("name", token)) {
                definition.setName(st.nextToken());
            }
        }
    }

    /**
     * Make sure we have a instance of @{@link DataSourceTransactionManager} with us to handle this transaction. If not
     * then make a new instance.
     */
    private synchronized DataSourceTransactionManager ensureDataSourceTransactionManager() {
        Context context = TransactionContext.getInstance().getContext();
        String dataSourceName = context.getDatasourceName();

        if (transactionManagerMap.containsKey(dataSourceName)) {
            return transactionManagerMap.get(dataSourceName);
        }

        DataSourceFactory dataSourceFactory = ApplicationContext.getInstance(DataSourceFactory.class);
        DataSource dataSource = dataSourceFactory.getDataSource(dataSourceName);
        if (dataSource instanceof TransactionAwareDataSourceProxy) {
            DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(dataSource);
            transactionManager.setDefaultTimeout(defaultTimeout);
            transactionManagerMap.put(dataSourceName, transactionManager);
            log.info("Created new dataSourceManager for dataSource={}", dataSourceName);
        }
        return transactionManagerMap.get(dataSourceName);
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy