Spring事务管理 | 使用ProxyFactoryBean/Transaction Interceptor
控制层:
import java.math.BigDecimal; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.prospring.ticket.service.PaymentService; import org.springframework.stereotype.Controller; 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; @Controller @RequestMapping("/payment") public class PaymentController { @Autowired private PaymentService paymentService; @RequestMapping(method = RequestMethod.GET, value = "/transfer") @ResponseBody public String tranfer( @RequestParam(value = "sourceAccount", required =true
) String sourceAccount,
@RequestParam(value = "targetAccount", required =
true
) String targetAccount,
@RequestParam(value = "money", required =
true
) BigDecimal money) throws Exception { paymentService.transfer(sourceAccount, targetAccount, money); return "{rs_code:0}"; } }
业务逻辑层:
import java.math.BigDecimal; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.prospring.ticket.aop.interceptor.DebugInterceptor; import org.springframework.prospring.ticket.dao.PaymentDao; import org.springframework.prospring.ticket.domain.Account; public class PaymentServiceImpl implements PaymentService { private PaymentDao paymentDao; @Override public void transfer(String sourceAccountNumber, String targetAccountNumber, BigDecimal money) { Account sourceAccount = paymentDao.getAccount(sourceAccountNumber); Account targetAccount = paymentDao.getAccount(targetAccountNumber); paymentDao.updateAccount(sourceAccountNumber, sourceAccount.getBalance().subtract(money)); paymentDao.updateAccount(targetAccountNumber, targetAccount.getBalance().add(money)); } /** * @param paymentDao the paymentDao to set */ public void setPaymentDao(PaymentDao paymentDao) { this.paymentDao = paymentDao; } }
Dao层:
import java.math.BigDecimal; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import org.springframework.dao.DataAccessException; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.PreparedStatementCallback; import org.springframework.jdbc.core.RowMapper; import org.springframework.jdbc.core.support.JdbcDaoSupport; import org.springframework.prospring.ticket.domain.Account; public class PaymentDao { private JdbcTemplate jdbcTemplate; public Account getAccount(String account) { return (Account) this.jdbcTemplate.query("select id, name, balance from account where id = ?", new Object[]{account},new RowMapper(){ @Override public Object mapRow(ResultSet rs, int rowNum) throws SQLException { Account account = new Account(); account.setId(rs.getString("id")); account.setName(rs.getString("name")); account.setBalance(rs.getBigDecimal("balance")); return account; } }).get(0); } public void updateAccount(final String account, final BigDecimal money) { this.jdbcTemplate.execute("update account set balance = ? where id = ?", new PreparedStatementCallback() { @Override public Object doInPreparedStatement(PreparedStatement ps) throws SQLException, DataAccessException { ps.setBigDecimal(1, money); ps.setString(2, account); ps.executeUpdate(); return null; } }); } /** * @param jdbcTemplate the jdbcTemplate to set */ public void setJdbcTemplate(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; } }
配置文件1: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_3_0.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0"> <display-name>bookstore</display-name> <context-param> <param-name>contextConfigLocation</param-name> <param-value> classpath*:spring/*.xml </param-value> </context-param> <context-param> <param-name>log4jConfigLocation</param-name> <param-value>classpath:conf/log4j.properties</param-value> </context-param> <context-param> <param-name>webAppRootKey</param-name> <param-value>bookstore.root</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <listener> <listener-class>org.springframework.web.util.Log4jConfigListener</listener-class> </listener> <servlet> <servlet-name>bookstore</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>bookstore</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> <!-- session --> <session-config> <session-timeout>60</session-timeout> </session-config> <welcome-file-list> <welcome-file>index.jsp</welcome-file> </welcome-file-list> <error-page> <error-code>403</error-code> <location>/error.html</location> </error-page> </web-app>
配置文件2:applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"> <beans> <!--从配置文件读取配置信息 --> <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE" /> <property name="ignoreResourceNotFound" value="true" /> <property name="locations"> <list> <value>classpath*:conf/jdbc.properties</value> </list> </property> </bean> <!-- The DBCP DataSource --> <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName"> <value>${jdbc.driverClassName}</value> </property> <property name="url"> <value>${jdbc.url}</value> </property> <property name="username"> <value>${jdbc.username}</value> </property> <property name="password"> <value>${jdbc.password}</value> </property> </bean> <!-- 配置JdbcTemplate --> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSource"></property> </bean> <!-- The DAO class --> <bean id="paymentDao" class="org.springframework.prospring.ticket.dao.PaymentDao"> <property name="jdbcTemplate"> <ref local="jdbcTemplate" /> </property> </bean> <!-- The transactionmanager to use for regular non JTA datasource --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource"> <ref local="dataSource" /> </property> </bean> <!-- Business Object --> <bean id="paymentServiceTarget" class="org.springframework.prospring.ticket.service.PaymentServiceImpl"> <property name="paymentDao"> <ref local="paymentDao" /> </property> </bean> <!-- TransactionInterceptor --> <bean id="transactionInterceptor" class="org.springframework.transaction.interceptor.TransactionInterceptor"> <property name="transactionManager"> <ref bean="transactionManager" /> </property> <property name="transactionAttributeSource"> <value> org.springframework.prospring.ticket.service.PaymentService.transfer=PROPAGATION_REQUIRED,ISOLATION_SERIALIZABLE,timeout_30,-Exception </value> </property> </bean> <!-- Transactional proxy for the primary business object --> <bean id="paymentService" class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="target"> <ref local="paymentServiceTarget" /> </property> <property name="proxyInterfaces"> <value>org.springframework.prospring.ticket.service.PaymentService </value> </property> <property name="interceptorNames"> <list> <value>transactionInterceptor</value> </list> </property> </bean> </beans>
配置文件3:jdbc.properties
# Mysql驱动 jdbc.driverClassName=com.mysql.jdbc.Driver # Mysql主机、端口、数据库名 jdbc.url=jdbc:mysql://localhost:6688/spring?useUnicode=true&characterEncoding=UTF-8 # Mysql用户名 jdbc.username=admin # Mysql密码 jdbc.password=admin
配置文件4:log4j.properties
log4j.rootLogger=INFO,CONSOLE,ROLLING_FILE
#log4j.rootLogger=ERROR,CONSOLE,ROLLING_FILE
log4j.debug=true
###################
# Console Appender
###################
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
log4j.appender.Threshold=INFO
log4j.appender.CONSOLE.Target=System.out
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.ConversionPattern= [%p] %d %c - %m%n
########################
# Rolling File
########################
log4j.appender.ROLLING_FILE=org.apache.log4j.RollingFileAppender
log4j.appender.ROLLING_FILE.Threshold=INFO
log4j.appender.ROLLING_FILE.File=${bookstore.root}/logs/bookstore.log
log4j.appender.ROLLING_FILE.Append=true
log4j.appender.ROLLING_FILE.MaxFileSize=5000KB
log4j.appender.ROLLING_FILE.MaxBackupIndex=100
log4j.appender.ROLLING_FILE.layout=org.apache.log4j.PatternLayout
log4j.appender.ROLLING_FILE.layout.ConversionPattern=%d{yyyy-M-d HH:mm:ss}%x[%5p](%F:%L) %m%n