Reputation: 83
I am trying to use @Transactional in a Spring MVC application to gain atomicity. The problem is that it seems like whenever an annotated function is called, it does not create a transaction. Here is my current set up:
Application 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:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd" >
<!-- Root Context: defines shared resources visible to all other web components -->
<tx:annotation-driven transaction-manager="txManager"/>
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close" >
<property name="driverClassName" value="org.postgresql.Driver" />
<property name="url" value="jdbc:postgresql://localhost:5432/qas" />
<property name="username" value="postgres" />
<property name="password" value="P0stgr3s" />
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate" >
<constructor-arg name="dataSource" ref="dataSource" />
</bean>
</beans>
Controller file:
package com.silverthorn.txtest;
import java.text.DateFormat;
import java.util.Date;
import java.util.Locale;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* Handles requests for the application home page.
*/
@Controller
public class HomeController {
private static final Logger logger = LoggerFactory.getLogger(HomeController.class);
@Autowired
private TagsDAO tagsDAO;
/**
* Simply selects the home view to render by returning its name.
*/
@RequestMapping(value = "/", method = RequestMethod.GET)
public String home(Locale locale, Model model) {
logger.info("Welcome home! the client locale is "+ locale.toString());
Date date = new Date();
DateFormat dateFormat = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG, locale);
String formattedDate = dateFormat.format(date);
model.addAttribute("serverTime", formattedDate );
return "home";
}
@RequestMapping(value="/tags", method=RequestMethod.GET)
public @ResponseBody ResponseDocument addTags(@RequestParam("tag") String[] tags) {
ResponseDocument doc = new ResponseDocument();
try {
tagsDAO.addTags(tags);
} catch ( RuntimeException e ) {
doc.setError("Could not add tags");
}
return doc;
}
}
and DAO file:
package com.silverthorn.txtest;
import java.util.HashMap;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Repository("tagsDAO")
public class TagsDAO {
@Autowired
private NamedParameterJdbcTemplate jdbcTemplate;
@Transactional(propagation = Propagation.REQUIRED)
public void addTags(String[] tags) {
Map<String, Object> params = new HashMap<String, Object>();
for(String tag : tags) {
params.put("tag", tag);
jdbcTemplate.update("INSERT INTO qas.tags (tag) VALUES (:tag)", params);
}
}
}
Here is an example of the output log when a transaction is supposed to fail:
DEBUG: org.springframework.jdbc.core.JdbcTemplate - Executing prepared SQL update
DEBUG: org.springframework.jdbc.core.JdbcTemplate - Executing prepared SQL statement [INSERT INTO qas.tags (tag) VALUES (?)]
DEBUG: org.springframework.jdbc.datasource.DataSourceUtils - Fetching JDBC Connection from DataSource
DEBUG: org.springframework.jdbc.core.JdbcTemplate - SQL update affected 1 rows
DEBUG: org.springframework.jdbc.datasource.DataSourceUtils - Returning JDBC Connection to DataSource
DEBUG: org.springframework.jdbc.core.JdbcTemplate - Executing prepared SQL update
DEBUG: org.springframework.jdbc.core.JdbcTemplate - Executing prepared SQL statement [INSERT INTO qas.tags (tag) VALUES (?)]
DEBUG: org.springframework.jdbc.datasource.DataSourceUtils - Fetching JDBC Connection from DataSource
DEBUG: org.springframework.jdbc.datasource.DataSourceUtils - Returning JDBC Connection to DataSource
INFO : org.springframework.beans.factory.xml.XmlBeanDefinitionReader - Loading XML bean definitions from class path resource [org/springframework/jdbc/support/sql-error-codes.xml]
INFO : org.springframework.jdbc.support.SQLErrorCodesFactory - SQLErrorCodes loaded: [DB2, Derby, H2, HSQL, Informix, MS-SQL, MySQL, Oracle, PostgreSQL, Sybase]
DEBUG: org.springframework.jdbc.support.SQLErrorCodesFactory - Looking up default SQLErrorCodes for DataSource [org.apache.commons.dbcp.BasicDataSource@704fb]
DEBUG: org.springframework.jdbc.datasource.DataSourceUtils - Fetching JDBC Connection from DataSource
DEBUG: org.springframework.jdbc.datasource.DataSourceUtils - Returning JDBC Connection to DataSource
DEBUG: org.springframework.jdbc.support.SQLErrorCodesFactory - Database product name cached for DataSource [org.apache.commons.dbcp.BasicDataSource@704fb]: name is 'PostgreSQL'
DEBUG: org.springframework.jdbc.support.SQLErrorCodesFactory - SQL error codes for 'PostgreSQL' found
DEBUG: org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator - Translating SQLException with SQL state '23505', error code '0', message [ERROR: duplicate key value violates unique constraint "tags_pkey"
Detail: Key (tag)=(hello) already exists.]; SQL was [INSERT INTO qas.tags (tag) VALUES (?)] for task [PreparedStatementCallback]
As you can see from the log file, it looks like there is no transaction being created. With no transaction being created, there is obviously no way to roll back the updates to the database. I have been banging my head against a wall on this for 2 days now, and have not been able to get any closer to getting the desired behavior. Any help I can get on this would be greatly appreciated.
Upvotes: 3
Views: 1806
Reputation: 49915
Just to make sure - if you don't have CGLIB libraries in your path that could be the issue. You will require CGLIB as your @Repository does not have an interface and Spring will create a CGLIB proxies for @Transactional annotated classes in that cases(which fails if CGLIB is not present). You may use either CGLIB or aspectj proxies.
Upvotes: 2