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

Não cheguei a testar com um banco só…

Inicialmente eu estava seguindo o guia de integração que é mostrado na apostila FJ-28 da Caelum, mas como precisei dos dois datasources, acabei me desviando.

teste isso por favor.

Beleza, vou testar e digo o resultado!

Obrigado pela atenção! =D

Lucas e mmaico,

Com um só transaction manager o rollback funciona tranquilamente.

applicationContext.xml:

[code]

<tx:annotation-driven /> 

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

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

<bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
    <property name="dataSource" ref="dataSource" />
    <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="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
	<property name="sessionFactory" ref="sessionFactory"/>
</bean>

[/code]

E na minha classe a única coisa que fiz foi anotar o método com @Transactional e forçar um nullpointer.

então é alguma configuração do atomikos que está errada… não sei se eu consigo ajudar pq nunca mexi com ele…

tenta ir adicionando ele aos poucos até ver onde começa a aparecer o erro

Felizmente tirei o Atomikos de questão. =)

Como nesta fase do projeto iremos apenas fazer inserções em um dos bancos (o outro será apenas consulta), tentei o seguinte:

[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://.../banco1" />
    <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://.../banco2" />
    <property name="username" value="postgres" />
    <property name="password" value="postgres" />
    <property name="initialSize" value="5" />
    <property name="maxActive" value="5" />
</bean>

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

<bean id="um" class="org.springframework.orm.hibernate3.HibernateTemplate">
	<property name="sessionFactory" ref="sessionFactoryUm"/>
</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="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="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
	<property name="sessionFactory" ref="sessionFactoryUm"/>
</bean>

[/code]

Mas não obtive sucesso, ele dá o commit de cara. D=

Marcus,

O que acontece se você trocar o seu hibernate template injetado no seu dao por uma session factory, assim:

@Autowired
	@Qualifier("um")
	protected  SessionFactory sessionFactory;

de depois você fazer um this.sessionFactory.getCurrentSession().save(objeto), o getCurrentSession sempre deve ser chamado, nunca o getSession().

Depois me fala o que aconteceu!

se vc usa desse jeito, só a sessionFactoryUm participa da transação, pq só ela tá no transactionManager.

Pra esse DAO sim, mas a idéia é ver se existe alguma outra transação sendo propagada!

Outra questão é verificar se os metodos de persistencia deste Hibernate Template esta usando o getCurrentSession e não o getSession

A ideia é que a apenas a sessionFactoryUm participe da transação por enquanto. Vou fazer o teste com a sessionFactory e dou um retorno.

Grato!

mmaico,

Ao tentar acessar o sessionFactory.getCurrentSession(), lança a seguinte exceção:

org.hibernate.HibernateException: No Hibernate Session bound to thread, and configuration does not allow creation of non-transactional one here

Rapaz, fiz um teste em outro Controller, só com um transactionManager e não funcionou. Será que o problema está no meu Controller?

ß) O Spring transactional usa o Spring AOP para gerenciar as transações. O Spring AOP tem uma restrição
forte: para poder decorar suas classes ele usa proxies da Cglib. Esses proxies precisamque sua classe tenha
um construtor sem argumentos, ou que as dependências da sua classe sejam sempre interfaces. E como
programar voltado a interfaces é uma boa prática, pois diminui o acoplamento de uma classe comas suas
dependências, vamos optar pela segunda solução.

Esse Controller problemático extende de uma classe que extende de outra. Seria algum problema nesse sentido? O Controller que funciona não extende de ninguém.

O problema não é a herança, fiz um teste no Controller que funciona, adicionei uma hierarquia e o funcionamento continunou correto. Estranho.

Uma dúvida, pq isso não funciona?

[code]class Classe {

public void metodo1() {
    try {
         metodo2();
    catch (Exception e) {
           //...
    }
}

@Transactional
public void metodo2() {
     //Processamento que lança exceção
}

}[/code]

O escopo da transação não deveria ser o correto funcionamento (ou não) do metodo2()? Pq capturar a exceção no metodo1() influencia no rollback? Se eu tirar o try/catch o rollback funciona.

Grato!

depende da exceção lançada… tenta colocar o transactional num @Component e chamá-lo do controller (de preferencia via interface)…

outra coisa: vc configurou o component-scan com o pacote correto?

Mas meus @Component são DAOs, não faria muito sentido os @Transactional nos DAOs.

O component-scan está correto.

Uma dúvida: Como farei para tratar as exceções se apenas é realizado o rollback quando eu lanço a exceção pro usuário?

claro que faz sentido ter @Transactional nos DAOs… principalmente nos métodos que modificam coisas…

se um método que é @Transactional chama outro que tb é, só uma transação é feita, pode usar sem medo…

faz o teste com o @Transactional no DAO (e não no controller)

Antes todos os @Transactional estavam nos DAOs, mas um método no DAO só realiza uma operação, por exemplo:

@Transactional public void save(Entity entity) { hibernateTemplate.save(entity); }

O @Transactional não faz mais sentido na camada superior?

@Transactional public void processamento() { operacao1(); operacao2(); operacao3(); }

Então dando erro, por exemplo, na operacao3(), ele daria rollback em tudo que fez anteriormente.

vc pode ter o transactional nas duas camadas, ele só considera a transação mais externa, por padrão. Colocando no dao vc garante que todos os saves rodam dentro de uma transaction.

Pessoal,

Para entender isso tem que conhecer como o Spring trabalha, e ele faz o seguinte:
Se estiver um @Trasaction no controller ele irá iniciar a transação no controller e propagar para o DAO, quando o metodo no controller finalizar é onde o Spring irá finalizar aquela transação.

 Se você colocar um @Transaction no DAO, neste caso não irá impactar em nada porque o perfil transacional default é required, nesse perfil ele usa a transação que já foi propagada do controller
 e não cria uma nova. Se você colocasse um @ Transaction com Required New no DAO aiii sim, iria ter 2 transações uma iniciada no controller e outra iniciada no DAO.


Resumo, só @Transaction no DAO não fará efeito algum nesse caso, fará quando um serviço sem transação o chamar como ele irá ver que não existe uma transação corrente ele irá iniciar uma nova.