论坛首页 Java企业应用论坛

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

浏览 19787 次
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
   发表时间:2004-12-27  
spring是轻量级的解决方案,spring管理的bean和事务不依赖于j2ee的服务器。在spring中你可以把任何的java 类纳入spring的管理的bean,把任何类的函数纳入spring的事务管理,在applicationContext.xml文件中只要这样声明就可以:
   <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> 


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

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

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;
    }
}

其中的SendEmailException派生自org.springframework.dao.DataAccessException,是用来回滚数据库操作的事务的。
   2.org.springframework.dao.DataAccessException是RuntimeException,可以捕捉,也可以不捕捉;可以在函数后面的throws声明抛出这种类型的异常,也可以不声明。本人的建议:对这种类型的异常,可以声明,但不捕捉--这样函数调用可以一步运行到底,省去好多的try和catch。在controller层不用捕捉,除非你使用这些异常给controller传递信息从而控制页面的跳转。
3.对于不捕捉的异常,spring容器会自动捕捉(当然也会自动回滚事务),并自动地跳转到相应的异常页面,例如(异常处理也有优先级):
	<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>



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

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

对这一点,尤其是声明性事务,如何在程序中判断事务的结果呢?
0 请登录后投票
   发表时间:2004-12-27  
程序(一般指controller)运行结束,没发生异常,就自动地提交一个声明的事务。出现异常(是指派生自DataAccessException)除了自动回滚一个事务外,还自动跳转到相应的出错页面(事务运行的结果是不言自明的),出错页面由一个spring 的bean解析:
	<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>

比如,org.ggyy.service.exception.NotLoginException和NotLoginException.jsp相对应,NotLoginException.jsp代码如下:
<%@ 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>
					


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

对于这类的异常(DataAccessException),spring 也不建议捕捉,spring的XXXDaoSupport还把大量的异常封装到DataAccessException,还向我们表明:不写try 和catch和finally是很酷的编程方式。例如:
 public void store(Entity entity); throws DataAccessException {
        this.getHibernateTemplate();.saveOrUpdate(entity);;
    }

假如使用HibernateSession的openSession,beginTransaction,endTransaction等等,try和catch和finally肯定要写的。
0 请登录后投票
   发表时间:2004-12-29  
大愚弱智 写道
函数只有抛出派生自org.springframework.dao.DataAccessException这种类型的异常才能回滚事务。


非也非也,理解错误。建议自定义异常做个测试,一试便知。
0 请登录后投票
   发表时间:2004-12-30  
我敢用一生的运气担保:
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;
    }
}

public class ManagerServiceTest extends TestCase {
    public void testAll();{
        IManagerService service = (IManagerService);ServiceFactory.getApplicationContext();.getBean("managerService");;
            try {
                service.testTransaction();;
            } catch (Exception e); {
                e.printStackTrace();;
            }
       
    }

}

 <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>

结果数据库还是增加了一条记录,证明事务没有回滚。
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企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics