2

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 + "]");
    }
}
}

4 Answers4

1

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

GreyBeardedGeek
  • 29,460
  • 2
  • 47
  • 67
1

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.

Luciano
  • 8,552
  • 5
  • 32
  • 56
1

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.

Sotirios Delimanolis
  • 274,122
  • 60
  • 696
  • 724
  • Is it safe to assume that because dao-context.xml has a component scan over userDao class(which contains @Transactional), I need to move my transactionManager to dao-context.xml? – Abhishek Shukla Ravishankara Oct 26 '13 at 18:47
  • Also, if I need transactions on two different data sources, I need two different transaction managers? Is that right? – Abhishek Shukla Ravishankara Oct 26 '13 at 18:48
  • @AbhishekShuklaRavishankara The beans that apply to the `tx:annotation-driven` have to be available in the context. For two different datasources, you would need two different transaction manager objects AFAIK. – Sotirios Delimanolis Oct 26 '13 at 18:49
  • So, for each xml file (a servlet specific) web application context is created which the root context (dispatcher servlet in this case) has access to. Is my understanding correct? – Abhishek Shukla Ravishankara Oct 27 '13 at 18:08
  • @AbhishekShuklaRavishankara Yes, the `ContextLoaderListener` typically loads this root application context, whose beans will be available to a servlet context loaded by the `DispatcherServlet`. – Sotirios Delimanolis Oct 27 '13 at 18:25
  • how is the servlet specific context (the context created by each xml) related to the root application context? – Abhishek Shukla Ravishankara Oct 27 '13 at 18:41
  • @AbhishekShuklaRavishankara The `ContextLoaderListener` creates the root context and puts it into the `ServletContext` provided by the `Servlet` container. When the `DispatcherServlet` is initialized, it has access to it and the beans inside it through the `ServletContext`. Your best bet is to go through some source code. – Sotirios Delimanolis Oct 27 '13 at 18:47
0

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.

Community
  • 1
  • 1
  • 1
    There is also another mistake you are mixing spring versions (3.2.3, 3.2.4 and 2.0.8). NEVER mix spring versions as that, eventually, will lead to strange errors! (spring-hibernate3 is replaced by spring-orm). – M. Deinum Oct 29 '13 at 08:56