Spring Transactional Commands

Published: 2019-08-04, Updated: 2019-03-11

Conceitos


@Transactional(isolation = Isolation.READ_COMMITTED)
void a(){b();}

@Transactional(isolation = Isolation.SERIALIZABLE)
void b(){}

Pegar conexão da transação atual

DataSourceUtils.getConnection(ApplicationContextProvider.context().getBean(DataSource.class))

Rodar query de banco na transação atual

ApplicationContextProvider.context().getBean(JdbcTemplate.class).queryForList("SELECT * FROM ATM_IMPORTED_FILE")

JDBCTemplate Compartilha a transação com o hibernate?

Sim, se voce fizer um update no hibernate e select no jdbc template eles vao usar a mesma transacao e só vai commitar no final Só não vai refletir entre o JDBCTemplate e o hibernate se o Hibernate rodar um HQL por exemplo e não rodar o flush

Verificar se a sincronizacao de transacao está atiada

Se a transação estiver marcada para rollback ou se a sincronizacao de transacao nao estiver ativa então o registro de sincronizacao vai estourar excecao, pode ser util verificar antes:

if(TransactionSynchronizationManager.isSynchronizationActive()){
	TransactionSynchronizationManager.registerSynchronization(....);
}
....

Deletar arquivo apenas se a transacao commitar com sucesso

@Transactional
public void createFile(File file){
	TransactionSynchronizationManager.registerSynchronization(new FileDeletioner(file));
	// filedao.persist()...
}
public class FileDeletioner extends TransactionSynchronizationAdapter {
  private final Logger logger = LoggerFactory.getLogger(getClass());
	private final File file;

	FileDeletioner(final File file) {
		this.file = file;
	}

	@Override
	public void afterCompletion(final int status) {
		logger.info("file={}, status={}", file, status);
		switch (status) {
		case STATUS_COMMITTED:
			logger.info("file={}, deleted={}", file, file.delete());
			break;
		}
	}
}

Para ter um rabbit transacional

Criar um rabbit template manual e setar set channel transacted, então provavelmente irá funcionar o controle transacional

Criando transacao manualmente

Manualmente comita ou da rollback nas transacoes

DefaultTransactionDefinition def = new DefaultTransactionDefinition();
// explicitly setting the transaction name is something that can only be done programmatically
def.setName("SomeTxName");
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);

TransactionStatus status = txManager.getTransaction(def);
try {
    // execute your business logic here
}
catch (MyException ex) {
    txManager.rollback(status);
    throw ex;
}
txManager.commit(status);

Automaticamente comita ou da rollback nas transacoes

@Autowired
private PlatformTransactionManager platformTransactionManager;

final TransactionTemplate transactionTemplate = new TransactionTemplate(platformTransactionManager);
transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_NOT_SUPPORTED);    
transactionTemplate.execute((TransactionCallback) status -> {
	final JobResult result = this.processBatchClosing(currentDate);
	bbSplitterService.splitLastBatches(currentDate);
	withdrawBatchSummaryService.createClosedAndNotSentBatchSummaries();
	return null;
});

Pegar a transação atual

TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();

Mostrar logs das transações

logging.level.org.springframework.transaction=TRACE
logging.level.org.springframework.jdbc=DEBUG

Configuração do pool de conexões

spring.datasource.tomcat.max-active=4
spring.datasource.tomcat.max-idle=2
spring.datasource.tomcat.min-idle=1
spring.datasource.tomcat.initial-size=1

POCS

Nesse caso ele vai reutilizar a transacao e só vai comitar quando sair do metodo update.

@Transactional(propagation = Propagation.REQUIRED)
public void update(){
	final TransactionTemplate template = new TransactionTemplate(txManager);
	template.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
	template.execute(status -> {...})
}

Vai suspender a transacao criada no metodo e vai criar uma nova para cada chamada ao .execute e ja vai comitar refletindo no banco

@Transactional(propagation = Propagation.REQUIRED)
public void update(){
	final TransactionTemplate template = new TransactionTemplate(txManager);
	template.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
	for(1 a 10) template.execute(status -> {...})
}

Por mais que seja required, como o execute está dentro de um for ele vai criar uma transação por chamada ao .execute já comitando quando sair do mesmo

@Transactional(propagation = Propagation.NEVER)
public void update(){
	final TransactionTemplate template = new TransactionTemplate(txManager);
	template.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
	for(1 a 10) template.execute(status -> {...})
}

Ignorando registros duplicados com o PostgreSQL sem abortar a transação

Baiscamente voce faria assim

@Transactional(propagation = Propagation.REQUIRED, noRollbackFor = DuplicateKeyException.class)
public void createCustomerWithoutFail(CustomerEntity customer) {
	customerDAO.create(customer);
}

E faria um for nele

@Transactional
public void createCustomersWithoutFail(List<CustomerEntity> customerEntities) {
	for(CustomerEntity customerEntity: customerEntities){
		customerService.createCustomerWithoutFail(customerEntity);

Porém o postgres não aceita que uma transação que recebeu DuplicateKeyException possa ser comitada, recebendo a seguinte exceção:

Caused by: org.postgresql.util.PSQLException: ERROR: current transaction is aborted, commands ignored until end of transaction block

Então existem duas opções:

Solução 1 - Solução transacional

@Transactional
public void createCustomersWithoutFail(List<CustomerEntity> customerEntities) {
	for(CustomerEntity customerEntity: customerEntities){
		try {
			TransactionTemplate transactionTemplate = new TransactionTemplate(txManager);
			transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_NESTED);
			transactionTemplate.execute(ts -> {
				customerService.createCustomerWithoutFail(customerEntity);
				return null;
			});
		} catch (final DuplicateKeyException e) {
			LOGGER.warn("status=duplicated, name={}, msg={}", customerEntity.getFirstName(), e.getMessage(), e);
		}
	}
}

Dessa forma com a NESTED se ela rececber a duplicated só ela será abortada e o pai ficará intacto mas se a transação pai levar exceção logo levará rollback então todas terão, diferente da requires_new

Solução 2 - Não funciona Dentro do createCustomerWithoutFail fazer o for chamando o DAO. Vai dar na mesma a transação já foi abortada no banco, voce não pode reutiliza-la

Solução 3 - Solução de banco

Simplemente faça um INSERT WHERE NOT EXISTS ou faça um select antes e veja se existe um registro correspodente

aqui o SQL do create do DAO

INSERT INTO tag ("key", "value")
SELECT 'key1', 'value1'
WHERE NOT EXISTS (
	SELECT id, "key", "value"
FROM node_tag
WHERE key = 'key1' AND value = 'value1'
) returning id, "key", "value"

Nao fazer rollback quando tiver ConstraintViolation

Usando Spring JDBC com PlatformTransactionManager de forma manual

Como voce ve no exemplo o transacional funciona mas tem uma limitação, uma vez que voce executa o TransactionTemplate ele ja comita em seguida, entao mesmo que voce use o required, nao vai conseguir reutilizar a transacao.


Spring and Queue Como Jogar Paladins

Comments