事务
如果只代理到上面这里,写con.close方法其实会出问题的。
当然,本例很简单,servlet只请求了一个service中的一个方法,这样写没什么问题,
但是,假如我有多个service和一个service有多个方法,需要被一个用户请求servlet时同时调用时,这个连接就不能被关闭了。
因为Spring容器的事务机制的实质是对传统JDBC的封装,也即是Spring事务管理无论是对单数据库实例还是分布式数据库实例,要实现事务管理,那么必须保证在一个事务过程获得Connetion对象是同一个。
假如是servlet调用多个service或service中多个方法,需要实现的是同一个事务,我们可以:在service中写一个综合方法,在其中调用其它方法,然后给综合方法设置代理,因为这个综合方法在这里就是一个业务
,多个service,原理一样。
AOP拦截getConnection()方法,cglib工具进行动态代理Connection
然后再拦截Connection的close方法!
package cn.hncu.utils; import java.lang.reflect.Method; import java.sql.Connection; import net.sf.cglib.proxy.Callback; import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodProxy; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; public class CloseAdvice implements MethodInterceptor{ private ThreadLocal<Object> t = new ThreadLocal<Object>(); @Override public Object invoke(MethodInvocation invocation) throws Throwable { Object obj = t.get(); if(obj==null){ final Object con = invocation.proceed();//返回原型对象Connection //通过cglib工具进行动态代理 Callback callback = new net.sf.cglib.proxy.MethodInterceptor() { @Override public Object intercept(Object proxiedObj, Method method, Object[] args, MethodProxy proxy) throws Throwable { if(method.getName().equals("close")){ return null; } //con为原型Connection对象 return method.invoke(con, args); } }; //obj为cglib工具代理后的Connection对象 obj=Enhancer.create(Connection.class, callback); t.set(obj); } return obj; } }
在applicationContext.xml中配置拦截getConnection()
<bean id="conClose" class="org.springframework.aop.aspectj.AspectJExpressionPointcutAdvisor"> <property name="expression" value="execution( * *..*.*.getConnection() )"></property> <property name="advice"> <bean id="advice" class="cn.hncu.utils.CloseAdvice"></bean> </property> </bean>
接下来就是要用到AOP了,拦截事务。
拦截service层的。
拦截事务的切面配置:
<!-- 自动代理 --> <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"></bean> <!-- 事务 切面=切点+通知 --> <bean id="tx" class="org.springframework.aop.aspectj.AspectJExpressionPointcutAdvisor"> <!-- 拦截cn.hncu包下的,方法名最后为Service的任意返回值任意参数的方法 --> <property name="expression" value="execution (* cn.hncu..*Service.*(..) )"> </property> <property name="advice"> <bean class="cn.hncu.utils.TxAdvice"></bean> </property> </bean>
DAO层的实现类代码:
stud的实现类:
package cn.hncu.stud.dao; import java.sql.SQLException; import java.util.UUID; import javax.sql.DataSource; import org.apache.commons.dbutils.QueryRunner; import cn.hncu.stud.domain.Book; import cn.hncu.stud.domain.Stud; public class StudDaoJdbc implements StudDAO{ private DataSource dataSource = null;//依赖注入 public DataSource getDataSource() { return dataSource; } public void setDataSource(DataSource dataSource) { this.dataSource = dataSource; } @Override public void saveStud(Stud stud) throws SQLException { String uuid = UUID.randomUUID().toString().replaceAll("-", ""); stud.setS_id(uuid); QueryRunner run = new QueryRunner(dataSource); run.update("insert into stud(s_id,s_name) values(?,?)", stud.getS_id(),stud.getS_name()); } }
book的实现类
package cn.hncu.stud.dao; import java.sql.SQLException; import javax.sql.DataSource; import org.apache.commons.dbutils.QueryRunner; import cn.hncu.stud.domain.Book; public class BookDaoJdbc implements BookDAO{ private DataSource dataSource = null;//依赖注入 public DataSource getDataSource() { return dataSource; } public void setDataSource(DataSource dataSource) { this.dataSource = dataSource; } @Override public void saveBook(Book book) throws SQLException { QueryRunner run = new QueryRunner(dataSource); run.update("insert into book(b_name) values(?)", book.getB_name()); } }
测试:
打开页面输入:
点按钮提交:
service:cn.hncu.stud.service.SaveServiceImpl@4adeee3d这个输出是我在servlet中测试一个错误的时候的输出。
再看数据库的数据:
然后,我们来测试下,事务回滚情况:
因为service层是:
@Override public void saveStudAndBook(Stud stud, Book book) throws SQLException { studDao.saveStud(stud); bookDao.saveBook(book); }
后调用bookDao的,所以,我们让saveBook挂了,改一下saveBook的方法中sql语句为:
这样,后面Book的存储肯定是出问题的。
再来测试:
点添加。
可以看到事务回滚了,但是看这里没用,我们去看下stud和book表有没有存储。当然book表肯定是不会被存储的,去看stud表就可以了:
可以看到,李四这个用户并没有被保存,证明事务起作用了。