Question

I am new to the spring framework and I have been trying to use transactions in my code. I am using @Transactional over my method and I have set the right tags in my beans.xml. It looks like the @transactional tag is being ignored. Any help is appreciated.

Also, I am using tomcat and I see no errors when it starts up.

Using maven as well.

Pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.abhi</groupId>
    <artifactId>quote</artifactId>
    <name>quote</name>
    <packaging>war</packaging>
    <version>1.0.0-BUILD-SNAPSHOT</version>
    <properties>
        <java-version>1.7</java-version>
        <org.springframework-version>3.2.3.RELEASE</org.springframework-version>
        <org.aspectj-version>1.6.10</org.aspectj-version>
        <org.slf4j-version>1.6.6</org.slf4j-version>
    </properties>
    <dependencies>
        <!-- Spring -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${org.springframework-version}</version>
            <exclusions>
                <!-- Exclude Commons Logging in favor of SLF4j -->
                <exclusion>
                    <groupId>commons-logging</groupId>
                    <artifactId>commons-logging</artifactId>
                 </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>${org.springframework-version}</version>
        </dependency>

        <!-- AspectJ -->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
            <version>${org.aspectj-version}</version>
        </dependency>   

        <!-- Logging -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>${org.slf4j-version}</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>jcl-over-slf4j</artifactId>
            <version>${org.slf4j-version}</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>${org.slf4j-version}</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.15</version>
            <exclusions>
                <exclusion>
                    <groupId>javax.mail</groupId>
                    <artifactId>mail</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>javax.jms</groupId>
                    <artifactId>jms</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>com.sun.jdmk</groupId>
                    <artifactId>jmxtools</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>com.sun.jmx</groupId>
                    <artifactId>jmxri</artifactId>
                </exclusion>
            </exclusions>
            <scope>runtime</scope>
        </dependency>

        <!-- @Inject -->
        <dependency>
            <groupId>javax.inject</groupId>
            <artifactId>javax.inject</artifactId>
            <version>1</version>
        </dependency>

        <!-- Servlet -->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>servlet-api</artifactId>
            <version>2.5</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>javax.servlet.jsp</groupId>
            <artifactId>jsp-api</artifactId>
            <version>2.1</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>jstl</artifactId>
            <version>1.2</version>
        </dependency>

        <!-- Test -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.7</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-core</artifactId>
            <version>3.1.4.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-web</artifactId>
            <version>3.1.4.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-config</artifactId>
            <version>3.1.4.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.26</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>3.2.4.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>javax.validation</groupId>
            <artifactId>validation-api</artifactId>
            <version>1.1.0.Final</version>
        </dependency>
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-validator</artifactId>
            <version>5.0.1.Final</version>
        </dependency>
        <dependency>
            <groupId>commons-dbcp</groupId>
            <artifactId>commons-dbcp</artifactId>
            <version>1.4</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-hibernate3</artifactId>
            <version>2.0.8</version>
        </dependency>
        <dependency>
            <groupId>javax.transaction</groupId>
            <artifactId>jta</artifactId>
            <version>1.1</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
            <version>3.2.4.RELEASE</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <artifactId>maven-eclipse-plugin</artifactId>
                <version>2.9</version>
                <configuration>
                    <additionalProjectnatures>
                        <projectnature>org.springframework.ide.eclipse.core.springnature</projectnature>
                    </additionalProjectnatures>
                    <additionalBuildcommands>
                        <buildcommand>org.springframework.ide.eclipse.core.springbuilder</buildcommand>
                    </additionalBuildcommands>
                    <downloadSources>true</downloadSources>
                    <downloadJavadocs>true</downloadJavadocs>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>2.5.1</version>
                <configuration>
                    <source>1.6</source>
                    <target>1.6</target>
                    <compilerArgument>-Xlint:all</compilerArgument>
                    <showWarnings>true</showWarnings>
                    <showDeprecation>true</showDeprecation>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>exec-maven-plugin</artifactId>
                <version>1.2.1</version>
                <configuration>
                    <mainClass>org.test.int1.Main</mainClass>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

beans.xml

<?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:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd">

    <context:component-scan base-package="com.abhi.quote.config"></context:component-scan>

    <context:property-placeholder
        location="classpath:com/abhi/quote/config/jdbc.properties" />



    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
        <property name="driverClassName" value="${jdbc.driver}"></property>
        <property name="url" value="${jdbc.url}"></property>
        <property name="password" value="${jdbc.password}"></property>
        <property name="username" value="${jdbc.username}"></property>
    </bean>

    <bean id="transactionManager"
        class="org.springframework.jdbc.datasource.DataSourceTransactionManager">

        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <tx:annotation-driven />

</beans>

web.xml

<?xml version="1.0" encoding="UTF-8"?>

<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5">
  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>
        /WEB-INF/spring/root-context.xml
        classpath:com/abhi/quote/beans/beans.xml
        classpath:com/abhi/quote/config/security-context.xml
        classpath:com/abhi/quote/config/service-context.xml
        classpath:com/abhi/quote/config/dao-context.xml        
    </param-value>
  </context-param>

  <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>

  <servlet>
    <servlet-name>appServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>/WEB-INF/spring/appServlet/servlet-context.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>appServlet</servlet-name>
    <url-pattern>/</url-pattern>
  </servlet-mapping>
  <filter>
    <display-name>springSecurityFilterChain</display-name>
    <filter-name>springSecurityFilterChain</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
  </filter>
  <filter-mapping>
    <filter-name>springSecurityFilterChain</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>

  <description>MySQL Quote</description>
  <resource-ref>
      <description>DB Connection</description>
      <res-ref-name>jdbc/quote</res-ref-name>
      <res-type>javax.sql.DataSource</res-type>
      <res-auth>Container</res-auth>
  </resource-ref>

</web-app>

doa-context.xml

<?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:jee="http://www.springframework.org/schema/jee"
xsi:schemaLocation="http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.2.xsd
    http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd">


<context:annotation-config></context:annotation-config>
<context:component-scan base-package="com.abhi.quote.dao"></context:component-scan>

<jee:jndi-lookup jndi-name="jdbc/quote" id="dataSource"
    expected-type="javax.sql.DataSource">
</jee:jndi-lookup>

</beans>

Code Snippet where transactional is used:

package com.abhi.quote.dao;

import javax.sql.DataSource;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.namedparam.BeanPropertySqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

@Component("usersDao")
public class UsersDao {

private NamedParameterJdbcTemplate jdbc;

    public UsersDao(){
    System.out.println("Loaded");
    }

    @Autowired
    public void setDataSource(DataSource jdbc){
        this.jdbc = new NamedParameterJdbcTemplate(jdbc);
    }

    @Transactional
    public boolean create(User user){

        DebugUtils.transactionRequired("UsersDao.create");
        BeanPropertySqlParameterSource userParams = new BeanPropertySqlParameterSource(user);

        jdbc.update("insert into users (username, password, email, enabled)" +
                " values (:username, :password, :email, :enabled)", userParams);

        return jdbc.update("insert into authorities (username, authority) values (:username, :authority)", userParams) == 1;
    }

}

Service layer code from where the create method is called

package com.abhi.quote.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.abhi.quote.dao.User;
import com.abhi.quote.dao.UsersDao;

@Service("userService")
public class UserService {

private UsersDao usersDao;

@Autowired
public void setUsersDao(UsersDao usersDao){
    this.usersDao = usersDao;
}

public void create(User user){
    usersDao.create(user);
}

}

DebugUtils class: I am using this class to check if transactions support was loaded. It says the support was not loaded

package com.abhi.quote.dao;

import java.lang.reflect.InvocationTargetException;

class DebugUtils {

private static final boolean transactionDebugging = true;
private static final boolean verboseTransactionDebugging = true;

public static void showTransactionStatus(String message) {
    System.out.println(((transactionActive()) ? "[+] " : "[-] ") + message);
}

// Some guidance from: http://java.dzone.com/articles/monitoring-declarative-transac?page=0,1
public static boolean transactionActive() {
    try {
        ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
        Class tsmClass = contextClassLoader.loadClass("org.springframework.transaction.support.TransactionSynchronizationManager");
        Boolean isActive = (Boolean) tsmClass.getMethod("isActualTransactionActive", null).invoke(null, null);

        return isActive;
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    } catch (IllegalArgumentException e) {
        e.printStackTrace();
    } catch (SecurityException e) {
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    } catch (InvocationTargetException e) {
        e.printStackTrace();
    } catch (NoSuchMethodException e) {
        e.printStackTrace();
    }

    // If we got here it means there was an exception
    throw new IllegalStateException("ServerUtils.transactionActive was unable to complete properly");
}

public static void transactionRequired(String message) {
    // Are we debugging transactions?
    if (!transactionDebugging) {
        // No, just return
        return;
    }

    // Are we doing verbose transaction debugging?
    if (verboseTransactionDebugging) {
        // Yes, show the status before we get to the possibility of throwing an exception
        showTransactionStatus(message);
    }

    // Is there a transaction active?
    if (!transactionActive()) {
        // No, throw an exception
        throw new IllegalStateException("Transaction required but not active [" + message + "]");
    }
}
}
Was it helpful?

Solution 4

In addition to what @GreyBeardedGeek, @Luciano and @Sotirios said, I had a committed a grave mistake.

I used STS to create a web project for me.

So, the dispatcher was pointed to read all the beans from a base package "com.abhi.quote" where my controller was present.

As I continued from there, I created packages named "com.abhi.quote.dao", "com.abhi.quote.service" etc and put these in the contextLoaderListner.

So, both the root context and dispatcher context (servlet context) were reading the beans because all other packages were subfolders of the the base package.

I had tx annotation driven tag defined in dao-context.xml which was read only by the root context.

When the application was running, the beans in the servlet package were being used as they were the beans present in the child (servlet context) and the transaction annotation was not being honored.

These post will help you better understand:

Why DispatcherServlet creates another application context?

Spring XML file configuration hierarchy help/explanation

Declaring Spring Bean in Parent Context vs Child Context

Hope this helps.

OTHER TIPS

Quite a lot depends on exactly how the second insert fails.

By default, Spring will only do rollback on the transaction if a RuntimeException is thrown from the method that is marked with @Transactional.

If anything else happens (either a checked Exception is thrown, or the method completes successfully), then the transaction will be committed.

I believe that SQLExceptions are Exceptions, not RuntimeExceptions, so you would either have to catch the SQLException and throw a RuntimeException, or re-configure the Transactional annotation (e.g. @Transactional(rollbackFor=Exception.class) )

See, for instance, http://simplespringtutorial.com/springDeclarativeTransactions.html

In addition to what GreyBeardedGeek said, the DAO you are accesing is a class, instead of an interface. So you should set

<tx:annotation-driven proxy-target-class="true" />

and add CGLIB to your classpath, else the transactional annotations won't do any effect.

You should move this

<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
    <property name="driverClassName" value="${jdbc.driver}"></property>
    <property name="url" value="${jdbc.url}"></property>
    <property name="password" value="${jdbc.password}"></property>
    <property name="username" value="${jdbc.username}"></property>
</bean>

<bean id="transactionManager"
    class="org.springframework.jdbc.datasource.DataSourceTransactionManager">

    <property name="dataSource" ref="dataSource"></property>
</bean>

<tx:annotation-driven />

to doa-context.xml and remove the jndi lookup data source (or give each DataSource an id. The TransactionManager can only apply transaction management to the referenced DataSource. So choose the one you want.

This is necessary because when you specify multiple contexts in the contextConfigLocation context-param, each context is refreshed individually and then they are merged. Therefore the tx:annotation-driven in one context file doesn't apply to the other context files.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top