Search code examples
javaspringhibernatespring-mvcspring-transactions

Spring MVC transaction manager defined in root context doesn't open transactions in dao defined in child context


I have faced a real proglem and solved it, but couldn't figure out what has been happened.

I defined transactionManager and sessionFactory bean in root context and my dao class with @Transactional methods in dispatcher context. And that's all. When I was trying to use getCurrentSession() in dao, I was getting "could not obtain a current session".

But, as I can remember, dispatcher context is aware about root context and has access to all beans in root context.

Can somebody explain me, why do not transactions open before @Transactional method if transactionManager and sessionFactory were defined in root context and class with @Transactional in child context?

Database config class

@Configuration
@EnableTransactionManagement
public class DatabaseConfig {
    @Bean
    public LocalSessionFactoryBean sessionFactory() throws IOException {
        LocalSessionFactoryBean sessionFactoryBean = new LocalSessionFactoryBean();
        sessionFactoryBean.setDataSource(getDatabaseDataSource());
        sessionFactoryBean.setPackagesToScan("com.varguss.domain");

        Properties properties = new Properties();
        properties.setProperty("hibernate.dialect", "org.hibernate.dialect.MySQL57Dialect");
        properties.setProperty("hibernate.show_sql", "true");
        properties.setProperty("hibernate.hbm2ddl.auto", "update");
        properties.setProperty("hibernate.connection.useUnicode", "true");
        properties.setProperty("hibernate.connection.characterEncoding", "utf8");
        properties.setProperty("hibernate.connection.charSet", "utf8");

        sessionFactoryBean.setHibernateProperties(properties);

        return sessionFactoryBean;
    }

    @Bean
    @Autowired
    public HibernateTransactionManager getTransactionManager(SessionFactory sessionFactory) {
        return new HibernateTransactionManager(sessionFactory);
    }

    @Bean(name = "dataSource", destroyMethod = "close")
    public BasicDataSource getDatabaseDataSource() throws IOException {
        BasicDataSource databaseDataSource = new BasicDataSource();

        Properties properties = new Properties();

        ClassPathResource propertiesFileResource = new ClassPathResource("database.properties");
        properties.load(propertiesFileResource.getInputStream());

        databaseDataSource.setDriverClassName(properties.getProperty("driverClassName"));
        databaseDataSource.setUrl(properties.getProperty("url"));
        databaseDataSource.setUsername(properties.getProperty("username"));
        databaseDataSource.setPassword(properties.getProperty("password"));

        return databaseDataSource;
    }
}

DAO class

@Repository
@Transactional
public class DbComputerPartDAO implements ComputerPartDAO {
    private SessionFactory sessionFactory;
    private Strategy strategy;

    @Autowired
    public void setSessionFactory(SessionFactory sessionFactory) {
        this.sessionFactory = sessionFactory;
        strategy = StrategyFactory.getStrategy(StrategyType.ALL, sessionFactory);
    }

    @Override
    @Transactional(readOnly = true)
    public List<ComputerPart> allParts() {
        return sessionFactory.getCurrentSession().createQuery("FROM ComputerPart part ORDER BY part.count DESC", ComputerPart.class).getResultList();
    }

    @Override
    @Transactional(readOnly = true)
    public ComputerPart part(Long id) {
        return sessionFactory.getCurrentSession().find(ComputerPart.class, id);
    }

    @Override
    public void save(String name, boolean isImportant, Long count) {
        sessionFactory.getCurrentSession().saveOrUpdate(new ComputerPart(name, isImportant, count));
    }

    @Override
    public void remove(Long id) {
        ComputerPart computerPart = part(id);

        if (computerPart != null)
            sessionFactory.getCurrentSession().delete(computerPart);
    }

    @Override
    @Transactional(readOnly = true)
    public List<ComputerPart> byImportance(boolean isImportant) {
        return sessionFactory.getCurrentSession().createQuery("FROM ComputerPart part WHERE part.isImportant ORDER BY part.count DESC", ComputerPart.class).getResultList();
    }

    @Override
    public void updateImportance(Long id, boolean isImportant) {
        ComputerPart computerPart = part(id);

        if (computerPart != null)
            computerPart.setImportant(isImportant);
    }

    @Override
    public void updateName(Long id, String name) {
        ComputerPart computerPart = part(id);

        if (computerPart != null)
            computerPart.setName(name);
    }

    @Override
    public void updateCount(Long id, Long count) {
        ComputerPart computerPart = part(id);

        if (computerPart != null)
            computerPart.setCount(count);
    }

    @Override
    @Transactional(readOnly = true)
    public List<ComputerPart> page(int pageNumber) {
        return strategy.page(pageNumber);
    }

    @Override
    @Transactional(readOnly = true)
    public List<ComputerPart> parts() {
        return strategy.parts();
    }

    @Override
    @Transactional(readOnly = true)
    public Integer lastPageNumber() {
        return strategy.lastPageNumber();
    }

    @Override
    @Transactional(readOnly = true)
    public List<ComputerPart> search(String partOfName) {
        return strategy.search(partOfName);
    }

    @Override
    public void changeStrategy(StrategyType strategyType) {
        this.strategy = StrategyFactory.getStrategy(strategyType, sessionFactory);
    }
}

Root context

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <!-- Root Context: defines shared resources visible to all other web components -->
    <context:annotation-config/>


    <bean class="com.varguss.config.DatabaseConfig"/>
</beans>

Child context

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/mvc"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xmlns:beans="http://www.springframework.org/schema/beans"
             xmlns:context="http://www.springframework.org/schema/context"
             xmlns:p="http://www.springframework.org/schema/p"
             xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd
            http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
            http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">

    <!-- DispatcherServlet Context: defines this servlet's request-processing infrastructure -->

    <!-- Enables the Spring MVC @Controller programming model -->
    <annotation-driven />

    <!-- Handles HTTP GET requests for /resources/** by efficiently serving up static resources in the ${webappRoot}/resources directory -->
    <resources mapping="/resources/**" location="/resources/" />

    <!-- Resolves views selected for rendering by @Controllers to .jsp resources in the /resources/views/ directory -->
    <beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" p:prefix="/views/" p:suffix=".jsp" />

    <context:component-scan base-package="com.varguss.dao" />
    <context:component-scan base-package="com.varguss.controller" />
</beans:beans>

Solution

  • When using hierarchical application context (a parent and child) the child can see the beans from the parent. So it can detect the EntityManagerFactory and the PlatformTransactionManager.

    However when using things like AOP that only applies to beans in the same application context as the AOP is defined in. So AOP defined in the parent context only applies to beans in the parent context, not to beans in the child contexts.

    So in your case the @EnableTransactionManagement is in the parent context but in there there aren't any beans with @Transactional, those are in the child context. So either create an @Configuration which enables transactions there or use <tx:annotation-driven /> in your XML configuration.