org.springframework.kafka.transaction.KafkaTransactionManager Maven / Gradle / Ivy
/*
* Copyright 2017-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.kafka.transaction;
import org.springframework.kafka.core.DefaultKafkaProducerFactory;
import org.springframework.kafka.core.KafkaResourceHolder;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.kafka.core.ProducerFactory;
import org.springframework.kafka.core.ProducerFactoryUtils;
import org.springframework.transaction.CannotCreateTransactionException;
import org.springframework.transaction.InvalidIsolationLevelException;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.support.AbstractPlatformTransactionManager;
import org.springframework.transaction.support.DefaultTransactionStatus;
import org.springframework.transaction.support.ResourceTransactionManager;
import org.springframework.transaction.support.SmartTransactionObject;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.util.Assert;
/**
* {@link org.springframework.transaction.PlatformTransactionManager} implementation for a
* single Kafka {@link ProducerFactory}. Binds a Kafka producer from the specified
* ProducerFactory to the thread, potentially allowing for one thread-bound producer per
* ProducerFactory.
*
*
* This local strategy is an alternative to executing Kafka operations within, and
* synchronized with, external transactions. This strategy is not able to provide
* XA transactions, for example in order to share transactions between messaging and
* database access.
*
*
* Application code is required to retrieve the transactional Kafka resources via
* {@link ProducerFactoryUtils#getTransactionalResourceHolder(ProducerFactory)}.
* Spring's {@link KafkaTemplate} will auto detect a thread-bound Producer and
* automatically participate in it.
*
*
* The use of {@link DefaultKafkaProducerFactory} as a target for this transaction
* manager is strongly recommended. Because it caches producers for reuse.
*
*
* Transaction synchronization is turned off by default, as this manager might be used
* alongside a datastore-based Spring transaction manager such as the JDBC
* org.springframework.jdbc.datasource.DataSourceTransactionManager, which has stronger
* needs for synchronization.
*
* @param the key type.
* @param the value type.
*
* @author Gary Russell
*/
@SuppressWarnings("serial")
public class KafkaTransactionManager extends AbstractPlatformTransactionManager
implements ResourceTransactionManager, KafkaAwareTransactionManager {
private final ProducerFactory producerFactory;
/**
* Create a new KafkaTransactionManager, given a ConnectionFactory.
* Transaction synchronization is turned off by default, as this manager might be used alongside a datastore-based
* Spring transaction manager like DataSourceTransactionManager, which has stronger needs for synchronization. Only
* one manager is allowed to drive synchronization at any point of time.
* @param producerFactory the ProducerFactory to use
*/
public KafkaTransactionManager(ProducerFactory producerFactory) {
Assert.notNull(producerFactory, "The 'ProducerFactory' cannot be null");
Assert.isTrue(producerFactory.transactionCapable(), "The 'ProducerFactory' must support transactions");
setTransactionSynchronization(SYNCHRONIZATION_NEVER);
this.producerFactory = producerFactory;
}
/**
* Get the producer factory.
* @return the producerFactory
*/
@Override
public ProducerFactory getProducerFactory() {
return this.producerFactory;
}
@Override
public Object getResourceFactory() {
return getProducerFactory();
}
@SuppressWarnings("unchecked")
@Override
protected Object doGetTransaction() {
KafkaTransactionObject txObject = new KafkaTransactionObject();
txObject.setResourceHolder((KafkaResourceHolder) TransactionSynchronizationManager
.getResource(getProducerFactory()));
return txObject;
}
@Override
protected boolean isExistingTransaction(Object transaction) {
@SuppressWarnings("unchecked")
KafkaTransactionObject txObject = (KafkaTransactionObject) transaction;
return (txObject.getResourceHolder() != null);
}
@Override
protected void doBegin(Object transaction, TransactionDefinition definition) {
if (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT) {
throw new InvalidIsolationLevelException("Apache Kafka does not support an isolation level concept");
}
@SuppressWarnings("unchecked")
KafkaTransactionObject txObject = (KafkaTransactionObject) transaction;
KafkaResourceHolder resourceHolder = null;
try {
resourceHolder = ProducerFactoryUtils.getTransactionalResourceHolder(getProducerFactory());
if (logger.isDebugEnabled()) {
logger.debug("Created Kafka transaction on producer [" + resourceHolder.getProducer() + "]");
}
txObject.setResourceHolder(resourceHolder);
txObject.getResourceHolder().setSynchronizedWithTransaction(true);
int timeout = determineTimeout(definition);
if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
txObject.getResourceHolder().setTimeoutInSeconds(timeout);
}
}
catch (Exception ex) {
if (resourceHolder != null) {
ProducerFactoryUtils.releaseResources(resourceHolder);
}
throw new CannotCreateTransactionException("Could not create Kafka transaction", ex);
}
}
@Override
protected Object doSuspend(Object transaction) {
@SuppressWarnings("unchecked")
KafkaTransactionObject txObject = (KafkaTransactionObject) transaction;
txObject.setResourceHolder(null);
return TransactionSynchronizationManager.unbindResource(getProducerFactory());
}
@Override
protected void doResume(Object transaction, Object suspendedResources) {
@SuppressWarnings("unchecked")
KafkaResourceHolder producerHolder = (KafkaResourceHolder) suspendedResources;
TransactionSynchronizationManager.bindResource(getProducerFactory(), producerHolder);
}
@Override
protected void doCommit(DefaultTransactionStatus status) {
@SuppressWarnings("unchecked")
KafkaTransactionObject txObject = (KafkaTransactionObject) status.getTransaction();
KafkaResourceHolder resourceHolder = txObject.getResourceHolder();
resourceHolder.commit();
}
@Override
protected void doRollback(DefaultTransactionStatus status) {
@SuppressWarnings("unchecked")
KafkaTransactionObject txObject = (KafkaTransactionObject) status.getTransaction();
KafkaResourceHolder resourceHolder = txObject.getResourceHolder();
resourceHolder.rollback();
}
@Override
protected void doSetRollbackOnly(DefaultTransactionStatus status) {
@SuppressWarnings("unchecked")
KafkaTransactionObject txObject = (KafkaTransactionObject) status.getTransaction();
txObject.getResourceHolder().setRollbackOnly();
}
@Override
protected void doCleanupAfterCompletion(Object transaction) {
@SuppressWarnings("unchecked")
KafkaTransactionObject txObject = (KafkaTransactionObject) transaction;
TransactionSynchronizationManager.unbindResource(getProducerFactory());
txObject.getResourceHolder().close();
txObject.getResourceHolder().clear();
}
/**
* Kafka transaction object, representing a KafkaResourceHolder. Used as transaction object by
* KafkaTransactionManager.
* @see KafkaResourceHolder
*/
private static class KafkaTransactionObject implements SmartTransactionObject {
private KafkaResourceHolder resourceHolder;
KafkaTransactionObject() {
super();
}
public void setResourceHolder(KafkaResourceHolder resourceHolder) {
this.resourceHolder = resourceHolder;
}
public KafkaResourceHolder getResourceHolder() {
return this.resourceHolder;
}
@Override
public boolean isRollbackOnly() {
return this.resourceHolder.isRollbackOnly();
}
@Override
public void flush() {
// no-op
}
}
}