本篇博客知识点
利用SpringAOP技术完成事物,
事物具体需求如下:同时存储两条记录信息到两个表Person、User表当其中一个存储失败,另外一个也要回滚。
整个项目的包结构
项目的web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
<display-name></display-name>
<context-param>
<param-name>contextConfigLocation</param-name>
<!-- 知识点:将两个不同位置的XML文件加载到一起 -->
<param-value>
classpath:beans.xml,
/WEB-INF/applicationContext.xml
</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet>
<servlet-name>SaveServlet</servlet-name>
<servlet-class>cn.hncu.servlet.SaveServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>SaveServlet</servlet-name>
<url-pattern>/SaveServlet</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>
applicationContext.xml
<?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:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
">
<!-- 通过读取配置文件来连接数据库 -->
<context:property-placeholder location="WEB-INF/conf/jdbc.properties"/>
<bean id="dataSource" class="org.springframework.jdbc.datasource.SimpleDriverDataSource">
<property name="driverClass" value="${driver}"/>
<property name="url" value="${url}"></property>
<!--
坑: username是一个系统的内部变量名,我们的自定义变量会被它屏蔽,因此我们不要取这个变量名,否则读不出我们设的值
<property name="username" value="${username}"></property>
-->
<property name="username" value="${uName}"/>
<property name="password" value="${pwd}"/>
</bean>
</beans>
核心beans.xml Spring的核心容器
<?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:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<bean id="personDao" class="cn.hncu.dao.PersonDao">
<property name="dataSource" ref="dataSource"/>
</bean>
<bean id="UserDao" class="cn.hncu.dao.UserDao">
<property name="dataSource" ref="dataSource"></property>
</bean>
<bean id="service" class="cn.hncu.service.IServiceImp">
<property name="pDao" ref="personDao"/>
<property name="udao" ref="UserDao"/>
</bean>
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"></bean>
<bean id="advisor" class="org.springframework.aop.aspectj.AspectJExpressionPointcutAdvisor">
<property name="expression" value="execution(void cn.hncu.service.*.*(..) )"/>
<property name="advice">
<bean class="cn.hncu.service.TxAdvice"/>
</property>
</bean>
<bean id="advisor2" class="org.springframework.aop.aspectj.AspectJExpressionPointcutAdvisor">
<property name="expression" value="execution(* *..*.getConnection(..) )"/>
<property name="advice">
<bean class="cn.hncu.service.ConAdvice"/>
</property>
</bean>
</beans>
用来处理事务的通知—
package cn.hncu.service;
import java.sql.Connection;
import javax.sql.DataSource;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
public class TxAdvice implements MethodInterceptor,ApplicationContextAware{
private ApplicationContext ctx =null;
@Override//用来拿到Spring容器
public void setApplicationContext(ApplicationContext ctx)
throws BeansException {
this.ctx=ctx;
}
//通知---这个里面需要拿到dataSource,所以需要先拿到Spring的容器
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
DataSource dataSource = ctx.getBean(DataSource.class);
Connection con = dataSource.getConnection();
Object res =null;
try {
con.setAutoCommit(false);
//开启一个事务
res = invocation.proceed();//放行
con.commit();//提交
System.out.println("提交一个事务...");
} catch (Exception e) {
con.rollback();
System.out.println("事务回滚...");
}finally{
con.setAutoCommit(true);//关闭事务
con.close();
}
return res;
}
}
用来拦截getConnection的通知
package cn.hncu.service;
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 ConAdvice implements MethodInterceptor{
private ThreadLocal<Connection> t = new ThreadLocal<Connection>();
@Override
public Object invoke(MethodInvocation inc) throws Throwable {
Connection con = t.get();
if(con==null){
final Connection con0 = (Connection) inc.proceed();
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;
}
return method.invoke(con0, args);//放行
}
};
//生成代理后的对象
con = (Connection) Enhancer.create(Connection.class, callback);
t.set(con);
System.out.println("本地线程管理对象t中不存在,放入一个连接"+con);
}
System.out.println("从本地线程管理对象t中取出一个连接"+con);
return con;
}
}
Servlet
package cn.hncu.servlet;
import java.io.IOException;
import java.sql.SQLException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.context.ApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
import cn.hncu.domain.Person;
import cn.hncu.domain.User;
import cn.hncu.service.IService;
public class SaveServlet extends HttpServlet {
private IService service = null;
@Override
public void init() throws ServletException {
ApplicationContext ctx = WebApplicationContextUtils.getWebApplicationContext(getServletContext());
//获取web项目中的spring容器--- 再通过容器拿到Service
service = ctx.getBean("service", IService.class);
}
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doPost(request, response);
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
Person person = new Person("p001","Person1");
User user = new User("hncu", "1234");
try {
System.out.println(service);
service.save(person, user);
} catch (SQLException e) {
System.out.println("出錯--------");
e.printStackTrace();
}
}
}
总结:想要完成JDBC的事物,应该要在切点在service层,事物要成功的核心在于connection是要同一个,因此就在需要拦截getConnection方法。所以利用SpringAOP切面技术控制好这两个点就可以了,其他小知识点包括,通过读取配置文件链接数据库,EL表达式~ ,Servlet是由Tomcat服务器创建,我们就不能把他放到Bean 通过ref注入service,解决方法是Servlet初始化时 去拿Spring容器里面的service.