Spring @Transactional annotation is not working
Asked Answered
M

5

7

I'm trying to run entityManager.merge(myEntity) within the following method but it seems that the @Transactional annotation is ignored. The Hibernate configuration seems to be fine because I can successfully fetch data from the db but it's not possible to write to the db. I'm using Spring version 3.2.3. Why are the writing db operations not working?

my method that does not work

package  com.reflections.importer.bls;
...

@Service
class BlsGovImporter {

...

    @Transactional
    private void importSeries(String externalId) {
        // This works. The dao is using EntityManager too
        Series series = seriesDao.findByExternalId(externalId);

        series.getValues().addAll(fetchNewValues());

        // This does not work and no exception is thrown 
        entityManager.merge(series);
    }
Mufi answered 22/8, 2015 at 13:24 Comment(2)
In which package your class BlsGovImporter is located ? In "com.reflections" ?Loosejointed
It is located in the package com.reflections.importer.blsMufi
E
21

Because it is used on private method. Spring Docs:

Method visibility and @Transactional

When using proxies, you should apply the @Transactional annotation only to methods with public visibility. If you do annotate protected, private or package-visible methods with the @Transactional annotation, no error is raised, but the annotated method does not exhibit the configured transactional settings. Consider the use of AspectJ (see below) if you need to annotate non-public methods.

When its private, it is obviously called from within the same class. But Spring call needs to go through proxy in order to make it working. So the method will need to be called from another bean.

Other option is to annotate class with @Transactional.

Equities answered 22/8, 2015 at 14:33 Comment(4)
Even if it was public, if it is called from another method of same class, it would not use the proxy and the @Transactional would also be ignoredFendig
Making the method public has firstly not solved my problem. But putting additionally the @Transactional annotation to an other method, that is not called from the same class has finally solved my problem. Now I'm considering to use aspectjMufi
@SergeBallesta, you are correct, I updated my answer.Equities
There are several options to use @Transactional from the same class: 1. Configure Spring to use AspectJ instead of Spring AOP 2. Self-Autowire the bean and call the method via the injected reference See here for more infoMufi
P
3

luboskrnac actualy answered it exactly for me, but just adding this to Spring newbie who might be confused on using proxy or not.

Please refer to this page explaining the case where even if you call an @Transactional method within the same class, since you call it without the same class, it will not be called via proxy.

https://www.logicbig.com/tutorials/spring-framework/spring-data-access-with-jdbc/correct-use-of-declarative-transaction.html

Pilocarpine answered 28/10, 2019 at 6:59 Comment(0)
A
2

In my case, I had to add @EnableTransactionManagement (you can add it in any @Configuration class or on your @SpringBootApplication class).

Adiathermancy answered 21/2, 2023 at 14:31 Comment(0)
B
1

In my case, I had to manually set the transactionManager bean name to make it work:

@Transactional(transactionManager = "myTransactionManager")
Bugbee answered 13/10, 2022 at 13:31 Comment(0)
I
0

@Matheus Santz, you are correct!! When using multiple datasources, it is always a best practice to annotate the @Transactional with the TransactionManager reference.

TransactDataSourceConfig.java

package com.spsllc.config;

import javax.sql.DataSource;

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.TransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import com.zaxxer.hikari.HikariDataSource;

@Configuration
@EnableTransactionManagement
public class TransactDataSourceConfig {
    
    @Bean
    @ConfigurationProperties("spsllc.datasource.transactdb")
    public DataSourceProperties transactDataSourceProperties() {
        return new DataSourceProperties();
    }

    @Bean(name = "transactdbDS")
    @ConfigurationProperties("spsllc.datasource.transactdb.configuration")
    public DataSource transactDataSource() {
        return transactDataSourceProperties().initializeDataSourceBuilder().type(HikariDataSource.class).build();
    }

    @Bean
    public JdbcTemplate transactdbJdbcTemplate(@Qualifier("transactdbDS") DataSource dataSource) {
        return new JdbcTemplate(dataSource);
    }

    @Bean
    public NamedParameterJdbcTemplate transactdbNamedParameterJdbcTemplate(@Qualifier("transactdbDS") DataSource dataSource) {
        return new NamedParameterJdbcTemplate(dataSource);
    }

    @Bean(name = "transactTransactionManager")
    public TransactionManager transactTransactionManager() {
        return new DataSourceTransactionManager(transactDataSource());
    }
}

SearchLogRepository.java

package com.spsllc.repository.transactdb;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.namedparam.BeanPropertySqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

import com.spsllc.domain.SearchLogEntry;

import lombok.extern.slf4j.Slf4j;

@Repository
@Slf4j
public class SearchLogRepository {

    @Autowired
    @Qualifier("transactdbJdbcTemplate")
    JdbcTemplate jdbcTemplate;

    @Autowired
    @Qualifier("transactdbNamedParameterJdbcTemplate")
    NamedParameterJdbcTemplate nmParamjdbcTemplate;

    @Transactional(transactionManager = "transactTransactionManager")
    public void insert(List<SearchLogEntry> entries) {
        String sql = "INSERT INTO search_log(member_id, weekof_metadata, week_begin_date, " +
                "week_end_date, activity_date,type, name, contact_person, contact_type) " +
                "VALUES(:memberId, :weekofMetadata, :weekBeginDate, " +
                ":weekEndDate, :activityDate, :type, :name, :contactPerson, :contactType);";

        GeneratedKeyHolder generatedKeyHolder = new GeneratedKeyHolder();
        int index = 0;
        for (SearchLogEntry e : entries) {
            nmParamjdbcTemplate.update(sql, new BeanPropertySqlParameterSource(e), generatedKeyHolder,
                    new String[] { "search_log_id" });
            Integer id = generatedKeyHolder.getKey().intValue();
            e.setSearchLogId(id);
            log.info(e.toString());
            index++;
            /** uncomment to test the rollback
            if(index > 5)
                throw new RuntimeException("Testing @Transactional commit/rollback behavior");
             */
        }
    }
}

Indulge answered 18/6, 2023 at 14:8 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.