论坛首页 Java版 Spring

spring 管理事务总结--包括如何正确地回滚事务

浏览 7379 次
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
时间:2004-12-27
spring是轻量级的解决方案,spring管理的bean和事务不依赖于j2ee的服务器。在spring中你可以把任何的java 类纳入spring的管理的bean,把任何类的函数纳入spring的事务管理,在applicationContext.xml文件中只要这样声明就可以:
[code:1]
<bean id="xxxService" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<property name="transactionManager"><ref local="transactionManager"/></property>
<property name="target"><ref local="applicationServiceTarget"/></property>
<property name="transactionAttributes">
<props>
<prop key="xxxMethod">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>
<bean id="xxxServiceTarget" class="com.nosqldb.service.spring.XXXServiceImpl">
</bean>
[/code:1]

以下是我对spring容器管理的事务的总结,对错否望指正。[list]

1.spring容器管理的事务是自动回滚的,也就是被纳入事务管理的类的函数执行完毕,事务自动提交。大家可能会想:假如函数调用发生异常,事务会自动回滚。不完全对!函数只有抛出派生自java.lang.RunTimeException这种类型的异常才能回滚事务(本人建议将其派生自org.springframework.dao.DataAccessException)。而且只能回滚对数据库的操作,而不能回滚其它类型的操作(比如,已经发送出去的email就不能“回滚”)。所以,要正确地回滚一个混和数据库操作和其它类型操作(例如发送Email操作)的事务,只能把其它类型的操作放在最后。例如把发送email的操作放在数据库操作的后面,发送email发生异常,数据库操作自动回滚--email的发送当然也不成功。假如用数据库操作的异常回滚一个email的发送,那是不可能的。还有对于email的发送异常应该进行这样的封装(注意以下语句:throw new SendEmailException):

[code:1]
public class MailServiceSpringImpl implements IMailService {
protected final Log logger = LogFactory.getLog(getClass());

private String smtpMailHost;

private String smtpMailUsername;

private String smtpMailPassword;

private String transport;

public void sendEmail(String from, String[] to, String subject, String text) {
Properties props = new Properties();
Session session;
Store store;
Transport t;
session = Session.getInstance(props, null);
props.put("mail.smtp.host", this.getSmtpMailHost());
props.put("mail.smtp.username", this.getSmtpMailUsername());
props.put("mail.smtp.password", this.getSmtpMailPassword());
Message msg = new MimeMessage(session);
try {
msg.setFrom(new InternetAddress(from));
msg.setSubject(subject);
msg.setSentDate(new Date());
msg.setText(text);
t = session.getTransport(this.getTransport());
t.connect();
if (to != null && to.length > 0) {
for (int i = 0; i < to.length; i++) {
Address address = new InternetAddress(to[i]);
msg.setRecipient(Message.RecipientType.TO, address);
t.send(msg);
}
}
t.close();
} catch (Exception e) {
logger.error("Send Email Exception--", e);
throw new SendEmailException();
}
}

public void sendEmail(String fromAddress, String toAddress, String subject,
String text) {
this.sendEmail(fromAddress, new String[] { toAddress }, subject, text);
}

public String getSmtpMailHost() {
return smtpMailHost;
}

public String getTransport() {
return transport;
}

public void setTransport(String transport) {
this.transport = transport;
}

public void setSmtpMailHost(String smtpMailHost) {
this.smtpMailHost = smtpMailHost;
}

public String getSmtpMailPassword() {
return smtpMailPassword;
}

public void setSmtpMailPassword(String smtpMailPassword) {
this.smtpMailPassword = smtpMailPassword;
}

public String getSmtpMailUsername() {
return smtpMailUsername;
}

public void setSmtpMailUsername(String smtpMailUsername) {
this.smtpMailUsername = smtpMailUsername;
}
}
[/code:1]
其中的SendEmailException派生自org.springframework.dao.DataAccessException,是用来回滚数据库操作的事务的。
2.org.springframework.dao.DataAccessException是RuntimeException,可以捕捉,也可以不捕捉;可以在函数后面的throws声明抛出这种类型的异常,也可以不声明。本人的建议:对这种类型的异常,可以声明,但不捕捉--这样函数调用可以一步运行到底,省去好多的try和catch。在controller层不用捕捉,除非你使用这些异常给controller传递信息从而控制页面的跳转。
3.对于不捕捉的异常,spring容器会自动捕捉(当然也会自动回滚事务),并自动地跳转到相应的异常页面,例如(异常处理也有优先级):
[code:1]
<bean id="exceptionResolver" class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionMappings">
<props>
<prop key="org.springframework.transaction.TransactionException">dataAccessException.jsp</prop>
<prop key="org.ggyy.service.exception.AuthorizationDenyException">authorizationException.jsp</prop>
<prop key="org.ggyy.service.exception.NotLoginException">notLoginException.jsp</prop>
<prop key="org.springframework.dao.DataAccessException">dataAccessException.jsp</prop>
<prop key="java.lang.Exception">systemException.jsp</prop>
</props>
</property>
</bean>
[/code:1]


使用这种方式给人的感觉就是:Service层和View层是直接通讯的,也就是Service层使用不同的异常类型控制jsp页面的跳转(当然只是异常跳转),而controller只是把程序一步运行到底,出了异常跳转到哪个页面也不知道。

[/list:u]
   
时间:2004-12-27
那么如果只是抛出不捕捉,那程序怎么知道,事务是成功完成了呢?还是失败了被回滚了呢?

对这一点,尤其是声明性事务,如何在程序中判断事务的结果呢?
   
0 请登录后投票
时间:2004-12-27
程序(一般指controller)运行结束,没发生异常,就自动地提交一个声明的事务。出现异常(是指派生自DataAccessException)除了自动回滚一个事务外,还自动跳转到相应的出错页面(事务运行的结果是不言自明的),出错页面由一个spring 的bean解析:
[code:1]
<bean id="exceptionResolver" class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionMappings">
<props>
<prop key="org.springframework.transaction.TransactionException">dataAccessException.jsp</prop>
<prop key="org.ggyy.service.exception.AuthorizationDenyException">authorizationException.jsp</prop>
<prop key="org.ggyy.service.exception.NotLoginException">notLoginException.jsp</prop>
<prop key="org.springframework.dao.DataAccessException">dataAccessException.jsp</prop>
<prop key="java.lang.Exception">systemException.jsp</prop>
</props>
</property>
</bean>
[/code:1]
比如,org.ggyy.service.exception.NotLoginException和NotLoginException.jsp相对应,NotLoginException.jsp代码如下:
[code:1]
<%@ page contentType="text/html; charset=gb2312"%>
<%@ include file="includes.jsp"%>
<HEAD>

</HEAD>
<BODY>
<TABLE width="780" height=300 border="0" cellspacing="0" cellpadding="0" >
<%
Exception ex = (Exception) request.getAttribute("exception");
%>
<H2>没登陆: <%= ex.getMessage() %></H2>
<p>
<%
ex.printStackTrace(new java.io.PrintWriter(out));
%>
<P>
<BR>


[/code:1]
在这个jsp里面,你能取得spring容器抛出的exception也就是你在Service层抛出的那个exception,你可以将其转换为你所抛出的那个异常,然后想打印什么样的信息都可以。

对于这类的异常(DataAccessException),spring 也不建议捕捉,spring的XXXDaoSupport还把大量的异常封装到DataAccessException,还向我们表明:不写try 和catch和finally是很酷的编程方式。例如:[code:1]
public void store(Entity entity) throws DataAccessException {
this.getHibernateTemplate().saveOrUpdate(entity);
}
[/code:1]
假如使用HibernateSession的openSession,beginTransaction,endTransaction等等,try和catch和finally肯定要写的。
   
0 请登录后投票
时间:2004-12-29
大愚弱智 写道
函数只有抛出派生自org.springframework.dao.DataAccessException这种类型的异常才能回滚事务。


非也非也,理解错误。建议自定义异常做个测试,一试便知。
   
0 请登录后投票
时间:2004-12-30
我敢用一生的运气担保:
[code:1]
public class ManagerServiceImpl implements IManagerService {
private ICatDao catDao;
/**
* 一个声明式事务
*/
public void testTransaction()throws Exception {
Cat cat=new Cat();
this.getCatDao().store(cat);
throw new Exception();
}

public ICatDao getCatDao() {
return catDao;
}

public void setCatDao(ICatDao catDao) {
this.catDao = catDao;
}
}
[/code:1]
[code:1]
public class ManagerServiceTest extends TestCase {
public void testAll(){
IManagerService service = (IManagerService)ServiceFactory.getApplicationContext().getBean("managerService");
try {
service.testTransaction();
} catch (Exception e) {
e.printStackTrace();
}

}

}
[/code:1]
[code:1]
<bean id="managerService" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<property name="transactionManager"><ref local="transactionManager"/></property>
<property name="target"><ref local="managerServiceTarget"/></property>
<property name="transactionAttributes">
<props>
<prop key="testTransaction">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>
[/code:1]
结果数据库还是增加了一条记录,证明事务没有回滚。
   
0 请登录后投票
时间:2004-12-30
试试看RuntimeException

http://www.springframework.org/docs/api/org/springframework/transaction/interceptor/RuleBasedTransactionAttribute.html

TransactionAttribute implementation that works out whether a given exception should cause transaction rollback by applying a number of rollback rules, both positive and negative. If no rules are relevant to the exception, it behaves like DefaultTransactionAttribute (rolling back on runtime exceptions).

btw, 一生的运气......
   
0 请登录后投票
时间:2004-12-30
大愚弱智 写道
我敢用一生的运气担保:

这句话应该理解为:我只担保我贴出来的这个程序不会回滚事务^_^。

看了一下spirng的源代码,DataAccessException源自RuntimeException。RuntimeException和Exception有什么不同我不了解,惭愧!
   
0 请登录后投票
时间:2004-12-30
大愚弱智 写道
大愚弱智 写道
我敢用一生的运气担保:

这句话应该理解为:我只担保我贴出来的这个程序不会回滚事务^_^。


狡猾狡猾滴,那么把你最开始的文章修改正确吧。
   
0 请登录后投票
时间:2005-01-30
ReadOnly落井下石啊...
   
0 请登录后投票
时间:2005-01-31
Readonly 写道
大愚弱智 写道
大愚弱智 写道
我敢用一生的运气担保:

这句话应该理解为:我只担保我贴出来的这个程序不会回滚事务^_^。


狡猾狡猾滴,那么把你最开始的文章修改正确吧。


怕了你,我改就是了。
   
0 请登录后投票
论坛首页 Java版 Spring

跳转论坛:
JavaEye推荐