Problema com rollback usando @Transactional (VRaptor+Spring+Hibernate+Atomikos+Postgres)

Prezados,

No projeto em que trabalho, tive a necessidade de configurar 2 DataSources diferentes, pois preciso acessar duas bancos de dados diferentes. Achei uma solução com o Atomikos e agora consigo obter os dois sem problemas. O problema agora está no controle de transação do Spring com o @Transactional.

Tenho meu Controller do VRaptor com um método salvar que preciso que seja transacionado, pois ele executa uma série de operações de inserção no banco de dados e se ocorrer algum erro, deve ser realizado Rollback. O problema é que o Rollback não acontece.

Meu applicationContext.xml:

[code]<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance” xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation=“
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-2.0.xsd”>

&lt;tx:annotation-driven /&gt;

&lt;bean id="dataSourceUm" class="com.atomikos.jdbc.AtomikosDataSourceBean"&gt;
	&lt;property name="uniqueResourceName"&gt;&lt;value&gt;dataSourceUm&lt;/value&gt;&lt;/property&gt;
	&lt;property name="xaDataSourceClassName"&gt;&lt;value&gt;org.postgresql.xa.PGXADataSource&lt;/value&gt;&lt;/property&gt;
	&lt;property name="xaProperties"&gt;
		&lt;props&gt;
			&lt;prop key="user"&gt;${db1.usuario}&lt;/prop&gt;
			&lt;prop key="password"&gt;${db1.senha}&lt;/prop&gt;
			&lt;prop key="databaseName"&gt;${db1.nomebanco}&lt;/prop&gt;
			&lt;prop key="serverName"&gt;${db1.url}&lt;/prop&gt;
			&lt;prop key="portNumber"&gt;${db1.porta}&lt;/prop&gt;
		&lt;/props&gt;
	&lt;/property&gt;
	&lt;property name="maxPoolSize" value="10" /&gt;
	&lt;property name="minPoolSize" value="5" /&gt;
	&lt;property name="maxIdleTime" value="1200" /&gt;
	&lt;property name="testQuery" value="select 1" /&gt;
	&lt;property name="reapTimeout" value="0" /&gt;
&lt;/bean&gt;

&lt;bean id="dataSourceDois" class="com.atomikos.jdbc.AtomikosDataSourceBean"&gt;
	&lt;property name="uniqueResourceName"&gt;&lt;value&gt;dataSourceDois&lt;/value&gt;&lt;/property&gt;
	&lt;property name="xaDataSourceClassName"&gt;&lt;value&gt;org.postgresql.xa.PGXADataSource&lt;/value&gt;&lt;/property&gt;
	&lt;property name="xaProperties"&gt;
		&lt;props&gt;
			&lt;prop key="user"&gt;${db2.usuario}&lt;/prop&gt;
			&lt;prop key="password"&gt;${db2.senha}&lt;/prop&gt;
			&lt;prop key="databaseName"&gt;${db2.nomebanco}&lt;/prop&gt;
			&lt;prop key="serverName"&gt;${db2.url}&lt;/prop&gt;
			&lt;prop key="portNumber"&gt;${db2.porta}&lt;/prop&gt;
		&lt;/props&gt;
	&lt;/property&gt;
	&lt;property name="maxPoolSize" value="10" /&gt;
	&lt;property name="minPoolSize" value="5" /&gt;
	&lt;property name="maxIdleTime" value="1200" /&gt;
	&lt;property name="testQuery" value="select 1" /&gt;
	&lt;property name="reapTimeout" value="0" /&gt;
&lt;/bean&gt;

&lt;bean id="sessionFactoryUm" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean"&gt;
		&lt;property name="dataSource"&gt;
				&lt;ref bean="dataSourceUm" /&gt;
		&lt;/property&gt;
		&lt;property name="annotatedClasses"&gt;
			&lt;list&gt;
				//Classes anotadas...
			&lt;/list&gt;
		&lt;/property&gt;
		&lt;property name="hibernateProperties"&gt;
			&lt;props&gt;
				&lt;prop key="hibernate.dialect"&gt;org.hibernate.dialect.PostgreSQLDialect&lt;/prop&gt;
				&lt;!-- atomikos --&gt;
				&lt;prop key="hibernate.current_session_context_class"&gt;jta&lt;/prop&gt;
				&lt;prop key="hibernate.transaction.factory_class"&gt;
					com.atomikos.icatch.jta.hibernate3.AtomikosJTATransactionFactory
				&lt;/prop&gt;
				&lt;prop key="hibernate.transaction.manager_lookup_class"&gt;
						com.atomikos.icatch.jta.hibernate3.TransactionManagerLookup
				&lt;/prop&gt;
			&lt;/props&gt;
		&lt;/property&gt;
&lt;/bean&gt;

&lt;bean id="sessionFactoryDois" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean"&gt;
		&lt;property name="dataSource"&gt;
				&lt;ref bean="dataSourceDois" /&gt;
		&lt;/property&gt;
		&lt;property name="annotatedClasses"&gt;
			&lt;list&gt;
				//Classes anotadas...
			&lt;/list&gt;
		&lt;/property&gt;
		&lt;property name="hibernateProperties"&gt;
			&lt;props&gt;
				&lt;prop key="hibernate.dialect"&gt;org.hibernate.dialect.PostgreSQLDialect&lt;/prop&gt;
				&lt;!-- atomikos --&gt;
				&lt;prop key="hibernate.current_session_context_class"&gt;jta&lt;/prop&gt;
				&lt;prop key="hibernate.transaction.factory_class"&gt;
					com.atomikos.icatch.jta.hibernate3.AtomikosJTATransactionFactory
				&lt;/prop&gt;
				&lt;prop key="hibernate.transaction.manager_lookup_class"&gt;
						com.atomikos.icatch.jta.hibernate3.TransactionManagerLookup
				&lt;/prop&gt;
			&lt;/props&gt;
		&lt;/property&gt;
&lt;/bean&gt;

&lt;bean id="um" class="org.springframework.orm.hibernate3.HibernateTemplate"&gt;
	&lt;property name="sessionFactory" ref="sessionFactoryUm"/&gt;
&lt;/bean&gt;
 
&lt;bean id="dois" class="org.springframework.orm.hibernate3.HibernateTemplate"&gt;
		&lt;property name="sessionFactory" ref="sessionFactoryDois"/&gt;
&lt;/bean&gt;

&lt;bean id="AtomikosTransactionManager" class="com.atomikos.icatch.jta.UserTransactionManager" init-method="init" destroy-method="close"&gt;
    &lt;property name="forceShutdown" value="true" /&gt;
	&lt;!-- this prop is in seconds --&gt;
    &lt;property name="transactionTimeout" value="300"/&gt;
&lt;/bean&gt;
 
&lt;bean id="AtomikosUserTransaction" class="com.atomikos.icatch.jta.UserTransactionImp"&gt;
	&lt;!-- this prop is in seconds --&gt;
    &lt;property name="transactionTimeout" value="300" /&gt;
&lt;/bean&gt;
 
&lt;bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager"&gt;
    &lt;property name="transactionManager" ref="AtomikosTransactionManager" /&gt;
    &lt;property name="userTransaction" ref="AtomikosUserTransaction" /&gt;
    &lt;property name="transactionSynchronizationName" value="SYNCHRONIZATION_ON_ACTUAL_TRANSACTION" /&gt;
&lt;/bean&gt;

</beans>[/code]

Meu DAO:

[code]@Component
public class MeuDaoImpl implements MeuDao {

@Resource(name = &quot;um&quot;)
private HibernateTemplate hibernateTemplate;

public void salvar(Entidade entity) {
	hibernateTemplate.save(entity);
}

//...

}[/code]

Com essa estratégia, consigo obter um ou outro ( "um" ou "dois" ) HibernateTemplate definido no applicationContext.xml, neste caso estou obtendo o "um".

Meu Controller está anotado com @Resource do VRaptor. Meu Controller herda de outra classe (quer herda de outra) mas possui um construtor vazio. O método salvar:

[code]
@Autowired
private MeuDao meuDao;

@Post
@Transactional
public void salvar(Entity entity) throws Exception {
//Processamento fictício, apenas para testar o Rollback!
for (int i = 0; i < 2; i++) {
meuDao.salvar(entity);
}
throw new Exception("Forçando erro para testar o Rollback…");
}[/code]

Porém, o rollback não acontece, os dados são persistidos no banco.

Alguém sabe o que posso estar fazendo errado?

Grato pela atenção!

PS.: Tecnologias usadas: VRaptor + Spring + Hibernate + Atomikos + Postgres

Fiz um teste e algo estranho aconteceu. Retirei o @Transactional do método e coloquei na classe: @Transactional(rollbackFor = Exception.class)

Dos 3 registros inseridos, apenas 1 restou. Ele deu rollback em duas inserções, mas uma restou. Estranho.

Olá Marcus,

É o seguinte, pelo que estou vendo esta tendo um problema de indentificação da transaction a ser usada pelo proxy do Spring.
Quando você adiciona a um método o @Transaction, você diz ao Spring que neste momento ele deve criar uma transação, assim
quando ele chamar o metodo do seu DAO essa transação já terá sido criada.
Para você informar ao proxy do Spring que ao iniciar o metodo savar no serviço ele deve usar uma ou outra sessionFactory você tem
que dizer o seguinte:

     @Transactional(value = "txManagerUm")

Para você identificar suas session você tem que adicionar um qualifier que ficaria assim:

      &lt;bean id="um" class="org.springframework.orm.hibernate3.HibernateTemplate"&gt;  
           &lt;property name="sessionFactory" ref="sessionFactoryUm"/&gt;  
            &lt;qualifier value="txManagerUm"/&gt;
    &lt;/bean&gt;  

Desta forma você diz ao Spring o que ele tem que usar na propagação!

abraço!

mmaico,

Obrigado por responder!

Cara, tentei da forma como você falou, mas continuo sem conseguir dar o rollback. Todos os registros continuam sendo salvos…

Tentei também colocar o hibernate.connection.autocommit=false mas também não deu resultado.

Grato pela atenção!

Marcus,
Se isso não resolveu eu acredito que quem deve estar comitando essa transação é essa api que você adicionou, dê uma olhada nela e vê como ela controla as trasações.
Um teste rapido que você pode fazer é comentar o AtomikosTransactionManager e as linhas :

                    &lt;prop key="hibernate.transaction.factory_class"&gt;  
                        com.atomikos.icatch.jta.hibernate3.AtomikosJTATransactionFactory  
                    &lt;/prop&gt;  
                    &lt;prop key="hibernate.transaction.manager_lookup_class"&gt;  
                            com.atomikos.icatch.jta.hibernate3.TransactionManagerLookup  
                    &lt;/prop&gt;  

Adicione ou altere o seu annotation driven para <tx:annotation-driven /> <- não especifica o trasaction manager.

Ai tenta novamente faz os testes. Eu usava desta forma em um projeto que tinha 3 base de dados(isso mesmo 3 :frowning: )

abraço!

Tentei também da seguinte forma:

Adicionei:

<tx:annotation-driven transaction-manager="transactionManager1"/> <tx:annotation-driven transaction-manager="transactionManager2"/>

E

[code]



<bean id="transactionManager2" class="org.springframework.transaction.jta.JtaTransactionManager">
    <property name="transactionManager" ref="AtomikosTransactionManager" />
    <property name="userTransaction" ref="AtomikosUserTransaction" />
    <property name="transactionSynchronizationName" value="SYNCHRONIZATION_ON_ACTUAL_TRANSACTION" />
</bean>[/code]

Ficando com:

[code]

<tx:annotation-driven transaction-manager="transactionManager1"/>
<tx:annotation-driven transaction-manager="transactionManager2"/>

<bean id="dataSourceUm" class="com.atomikos.jdbc.AtomikosDataSourceBean">
	<property name="uniqueResourceName"><value>dataSourceUm</value></property>
	<property name="xaDataSourceClassName"><value>org.postgresql.xa.PGXADataSource</value></property>
	<property name="xaProperties">
		<props>
			//props...
		</props>
	</property>
	<property name="maxPoolSize" value="10" />
	<property name="minPoolSize" value="5" />
	<property name="maxIdleTime" value="1200" />
	<property name="testQuery" value="select 1" />
	<property name="reapTimeout" value="0" />
</bean>

<bean id="dataSourceDois" class="com.atomikos.jdbc.AtomikosDataSourceBean">
	<property name="uniqueResourceName"><value>dataSourceDois</value></property>
	<property name="xaDataSourceClassName"><value>org.postgresql.xa.PGXADataSource</value></property>
	<property name="xaProperties">
		<props>
			//props...
		</props>
	</property>
	<property name="maxPoolSize" value="10" />
	<property name="minPoolSize" value="5" />
	<property name="maxIdleTime" value="1200" />
	<property name="testQuery" value="select 1" />
	<property name="reapTimeout" value="0" />
</bean>

<bean id="sessionFactoryUm" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
		<property name="dataSource">
				<ref bean="dataSourceUm" />
		</property>
		<property name="annotatedClasses">
			<list>
				//...
			</list>
		</property>
		<property name="hibernateProperties">
			<props>
				<prop key="hibernate.dialect">org.hibernate.dialect.PostgreSQLDialect</prop>
				<!-- atomikos -->
				<prop key="hibernate.current_session_context_class">jta</prop>
				<prop key="hibernate.transaction.factory_class">
					com.atomikos.icatch.jta.hibernate3.AtomikosJTATransactionFactory
				</prop>
				<prop key="hibernate.transaction.manager_lookup_class">
						com.atomikos.icatch.jta.hibernate3.TransactionManagerLookup
				</prop>
			</props>
		</property>
</bean>

<bean id="sessionFactoryDois" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
		<property name="dataSource">
				<ref bean="dataSourceDois" />
		</property>
		<property name="annotatedClasses">
			<list>
				//...
			</list>
		</property>
		<property name="hibernateProperties">
			<props>
				<prop key="hibernate.dialect">org.hibernate.dialect.PostgreSQLDialect</prop>
				<!-- atomikos -->
				<prop key="hibernate.current_session_context_class">jta</prop>
				<prop key="hibernate.transaction.factory_class">
					com.atomikos.icatch.jta.hibernate3.AtomikosJTATransactionFactory
				</prop>
				<prop key="hibernate.transaction.manager_lookup_class">
						com.atomikos.icatch.jta.hibernate3.TransactionManagerLookup
				</prop>
			</props>
		</property>
</bean>

<bean id="um" class="org.springframework.orm.hibernate3.HibernateTemplate">
	<property name="sessionFactory" ref="sessionFactoryUm"/>
	<qualifier value="transactionManager1"/>
</bean>
 
<bean id="dois" class="org.springframework.orm.hibernate3.HibernateTemplate">
		<property name="sessionFactory" ref="sessionFactoryDois"/>
		<qualifier value="transactionManager2"/>
</bean>

<bean id="AtomikosTransactionManager" class="com.atomikos.icatch.jta.UserTransactionManager" init-method="init" destroy-method="close">
    <property name="forceShutdown" value="true" />
	<!-- this prop is in seconds -->
    <property name="transactionTimeout" value="300"/>
</bean>
 
<bean id="AtomikosUserTransaction" class="com.atomikos.icatch.jta.UserTransactionImp">
	<!-- this prop is in seconds -->
    <property name="transactionTimeout" value="300" />
</bean>
 
<bean id="transactionManager1" class="org.springframework.transaction.jta.JtaTransactionManager">
    <property name="transactionManager" ref="AtomikosTransactionManager" />
    <property name="userTransaction" ref="AtomikosUserTransaction" />
    <property name="transactionSynchronizationName" value="SYNCHRONIZATION_ON_ACTUAL_TRANSACTION" />
</bean>

<bean id="transactionManager2" class="org.springframework.transaction.jta.JtaTransactionManager">
    <property name="transactionManager" ref="AtomikosTransactionManager" />
    <property name="userTransaction" ref="AtomikosUserTransaction" />
    <property name="transactionSynchronizationName" value="SYNCHRONIZATION_ON_ACTUAL_TRANSACTION" />
</bean>

[/code]

Adicionei também os qualifiers.

O Método:

[code]@Post
@Transactional("transactionManager1")
public void adicionar(Material material)  throws Exception {  
		for (int i = 0; i < 2; i++) {
			materialDao.salvar(material);
		}
                    throw new Exception("Forçando erro para testar o Rollback...");   
}[/code]

mmaico,

Vou tentar e dou o retorno.

Obrigado!

Achei um tópico no fórum da Atomikos que fala sobre isso:

http://fogbugz.atomikos.com/default.asp?community.6.2117.7

Porém não foi dada nenhuma solução. Tenso.

Outra possibilidade é configurar o JTA direto no seu web container, isso se estiver usando JBoss, Websphere, WebLogic e cia.

Infelizmente preciso usar o TomCat, por isso o Atomikos. =(

mmaico,

Pesquisando mais, percebi que não há necessidade de utilizar o Atomikos, pois ele seria necessário apenas se eu quisesse realizar operações em bancos diferentes na mesma transação. Simplifiquei um pouco minha vida. =)

Só que ocorre um problema estranho, se eu inserir 10 registros na base e forçar o rollback, ele continua com 1 registro inserido, dando rollback em 9. Se eu inserir 5 e forçar o rollback, ele continua com 1 salvo e dá rollback como se fosse uma regra de X - 1 rollbacks.

Fiz um teste usando o Change Value do Eclipse. Adicionei 3 entidades com nomes: ENTIDADE1, ENTIDADE2, ENTIDADE3 (modificando os nomes na hora de inserir). Na quarta, forcei o erro. Ele inseriu a ENTIDADE3.

Tem alguma ideia do que pode ser?

Minha classe Controller está anotada com:

Meu método está anotado com:

Meu novo applicationContext.xml:

[code]

<tx:annotation-driven /> 

<bean id="dataSourceUm" class="org.apache.commons.dbcp.BasicDataSource">
    <property name="driverClassName" value="org.postgresql.Driver" />
    <property name="url" value="jdbc:postgresql://.../base1" />
    <property name="username" value="postgres" />
    <property name="password" value="postgres" />
    <property name="initialSize" value="5" />
    <property name="maxActive" value="5" />
</bean>

<bean id="dataSourceDois" class="org.apache.commons.dbcp.BasicDataSource">
    <property name="driverClassName" value="org.postgresql.Driver" />
    <property name="url" value="jdbc:postgresql://.../base2" />
    <property name="username" value="postgres" />
    <property name="password" value="postgres" />
    <property name="initialSize" value="5" />
    <property name="maxActive" value="5" />
</bean>

<bean id="sessionFactoryUm" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
    <property name="dataSource" ref="dataSourceUm" />
    <property name="annotatedClasses">
        <list>
        		...
        </list>
    </property>
    <property name="hibernateProperties">
        <props>
            <prop key="hibernate.dialect">org.hibernate.dialect.PostgreSQLDialect</prop>
            <prop key="hibernate.show_sql">true</prop>
        </props>
    </property>
</bean>

<bean id="sessionFactoryDois" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
    <property name="dataSource" ref="dataSourceDois" />
    <property name="annotatedClasses">
        <list>
				...
        </list>
    </property>
    <property name="hibernateProperties">
        <props>
            <prop key="hibernate.dialect">org.hibernate.dialect.PostgreSQLDialect</prop>
            <prop key="hibernate.show_sql">true</prop>
        </props>
    </property>
</bean>

<bean id="txManagerUm" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
	<property name="sessionFactory" ref="sessionFactoryUm"/>
</bean>

<bean id="txManagerDois" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
	<property name="sessionFactory" ref="sessionFactoryDois"/>
</bean>

<bean id="sispes" class="org.springframework.orm.hibernate3.HibernateTemplate">
	<property name="sessionFactory" ref="sessionFactoryUm"/>
	<qualifier value="txManagerUm"/>
</bean>
 
<bean id="sss" class="org.springframework.orm.hibernate3.HibernateTemplate">
	<property name="sessionFactory" ref="sessionFactoryDois"/>
	<qualifier value="txManagerDois"/>
</bean>

[/code]

Mais uma vez grato pela atenção!

Marcus,
Você esta certo, quando se fala de JTA se pensa em remoção do controle trasacional do banco e controle do mesmo na aplicação para situações que você citou.

Esse comportamento que você mencionou é muito estranhooo!!!

No seu DAO tem alguma anotação de transação?

Se tiver um @Trasacional na classe ou no metodo ele irá pegar a transação criada no seu controller, se ele tiver um requires_new ai ele irá criar uma nova transação e o seu rollback
no controller não fará efeito nele.

abraço!

esse comportamento de salvar X vezes e só manter 1 é por causa do seu código:

for (int i = 0; i < 2; i++) {  
    meuDao.salvar(entity);  
}  

vc tá salvando o mesmo objeto, dentro da mesma transação… o hibernate vai considerar como um save só…

vc teria que testar com vários objetos diferentes…

desconfio que as transações estão desabilitadas no banco… se vc não usa o Atomikos funciona?

Opaaa, nesse caso não deveria salvar nenhuma vez, porque tem uma exception no final do metodo, então ele deveria fazer o rollback de tudo :slight_smile:

sim, supondo que o método está participando da transação…

dúvida: como vc está pegando a Session/EntityManager?

Lucas, pego da seguinte forma:

[code]@Component
public class MeuDaoImpl implements MeuDao {

@Resource(name = "um")  
private HibernateTemplate hibernateTemplate;  
  
public void salvar(Entidade entity) {  
    hibernateTemplate.save(entity);  
}  
  
//...  

} [/code]

Estou usando o HibernateTemplate, que referencia o:

<bean id="um" class="org.springframework.orm.hibernate3.HibernateTemplate"> <property name="sessionFactory" ref="sessionFactoryUm"/> </bean>

Teria algum problema com esta abordagem?

Lucas, mas quando não lanço a exceção, ele salva todas as vezes os N objetos, mesmo sendo o mesmo.

depende… precisa ver se a operação está de fato participando da mesma transação que o @Transactional… senão estiver está certo não fazer o rollback…

to achando que a configuração do TransactionManager não está feita corretamente.

Detalhe: uso Tomcat.

o tomcat não deveria influenciar nisso, vc tá usando o spring…

o mesmo código, mas com um banco só, sem o Atomikos , funciona?