Spring 整合 jdbc 及其 crud 操作**
一.代理模式
设计原则:
代理类 与 委托类 具有相似的行为(共同)
代理类增强委托类的行为
分为两种: 静态代理 和 动态代理
1.静态代理
为某个对象提供一个代理, 代理角色固定, 以控制对这个对象的访问。
代理类和委托类有共同的父类或父接口, 这样在任何使用委托类对象的地方都
可以用代理对象替代。 代理类负责请求的预处理、 过滤、
将请求分派给委托类处理、 以及委托类执行完请求后的后续处理。
因为静态代理对于代理的角色是固定的, 如 dao 层 20 个 dao 类,
如果要对方法的访问权限进行代理, 此时需要创建 20 个静态代理角色,
引起类爆炸,无法满足生产上的需要, 于是就催生了动态代理的思想。
1)创建项目
2)创建接口Marry.java
/**
* 代理接口
*/
public interface Marry {
public void toMarry();
}
3)创建代理类Company,持有目标对象的引用
/**
* 婚庆公司类
*/
public class Company implements Marry {
private You target;// 目标对象
public Company(You target) {
this.target = target;
}
public void before(){
System.out.println("布置婚礼现场...");
}
public void after(){
System.out.println("整理,以及送亲朋好友...");
}
@Override
public void toMarry() {
before();
target.toMarry();
after();
}
}
4)创建委托类You
/**
* 被代理对象委托类
*/
public class You implements Marry{
@Override
public void toMarry() {
//System.out.println("布置婚礼现场...");
System.out.println("结婚...");
//System.out.println("整理,以及送亲朋好友...");
}
}
5)测试类MarryTest测试静态代理实现
/**
* 测试代理
*/
public class MarryTest {
@Test
public void toMarry() throws Exception {
You you = new You();
Company company = new Company(you);
company.toMarry();
}
}
2.动态代理
相比于静态代理, 动态代理在创建代理对象上更加的灵活, 它会根据需 要通过反射机制在程序运行期动态的为目标对象创建代理对象, 代理的行为可以 代理多个方法, 即满足生产需要的同时又达到代码通用的目的。
动态代理的两种实现方式
2.1.jdk 实现动态代理。
对于 jdk 动态代理实现方式比较复杂, 回调方式实现 底层原理参考:
http://rejoy.iteye.com/blog/1627405
原理: 基于接口实现
局限: 很多类没有实现接口, 那么jdk就无法实现代理
1)package---JdkHandler类
/**
* jdk 动态代理
*
*/
public class JdkHandler implements InvocationHandler{
// 目标类
private Object target;
public JdkHandler(Object target) {
this.target = target;
}
/**
* 程序运行期动态创建代理角色
* @return
*/
public Object getProxy(){
/**
* 获取代理对象
* 1.类加载器
* 2.目标类 实现的接口 class
* 3.当前类
* @return
*/
return Proxy.newProxyInstance(
this.getClass().getClassLoader(),
target.getClass().getInterfaces(),
this
);
}
public void before(){
System.out.println("婚礼现场紧张布置中......");
}
public void after(){
System.out.println("恭喜您成功进入人生第二阶段.....");
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
before();//增强真实角色行为
Object result= method.invoke(target, args);// 执行真实角色方法
after();//增强真实角色行为
return result;
}
}
2)测试动态代理JdkHandlerTest
public class JdkHandlerTest {
@Test
public void invoke() throws Throwable {
You you = new You();
JdkHandler jdkHandler = new JdkHandler(you);
Object proxy = jdkHandler.getProxy();// 创建代理角色
jdkHandler.invoke(proxy, You.class.getMethod("toMarry"),null);
}
}
2.2.cglib 动态代理实现
code generator library , 操作字节码。 与 jdk 提供的代理区
别, Proxy: 委托类必须有接口, 制作过程比较快, 执行慢; cglib:
委托类
可以没有接口, 继承的思维来实现相似性, 制作代理过程比较慢, 执行快。
主要解决没有接口类的代理实现。
原理: 基于继承思想
作用: 针对没有实现接口的类,进行代理
1)引入pom.xml依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.shsxt</groupId>
<artifactId>spring08</artifactId>
<version>1.0-SNAPSHOT</version>
<name>spring08</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!-- cglib -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib-nodep</artifactId>
<version>2.2</version>
</dependency>
</dependencies>
<build>
</build>
</project>
2)创建cglib-CglibInterceptor类
package com.shsxt.cglib;
import java.lang.reflect.Method;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
public class CglibInterceptor implements MethodInterceptor {
private Object target;
public CglibInterceptor(Object target) {
this.target = target;
}
// 运行期动态创建代理类
public Object getProxy(){
Enhancer enhancer=new Enhancer();
//设置父类 class
enhancer.setSuperclass(target.getClass());
enhancer.setCallback(this);
return enhancer.create();
}
public void before(){
System.out.println("婚礼现场紧张布置中......");
}
@Override
public Object intercept(Object arg0, Method arg1, Object[] arg2,
MethodProxy arg3) throws Throwable {
before();//增强真实角色行为
Object result= arg3.invoke(target, arg2);
after();//增强真实角色行为
return result;
}
public void after(){
System.out.println("恭喜您成功进入人生第二阶段.....");
}
}
3)测试CglibInterceptorTest类
public class CglibInterceptorTest {
@Test
public void intercept() throws Exception {
You you = new You();
CglibInterceptor cglibInterceptor = new CglibInterceptor(you);
You proxy = (You) cglibInterceptor.getProxy();
proxy.toMarry();
}
/*
合并写法
*/
@Test
public void intercept02() throws Exception {
You proxy = (You) new CglibInterceptor(new You()).getProxy();
proxy.toMarry();
}
}
总结:
与jdk提供的代理区别,
Proxy:委托类必须有接口,制作过程比较快,执行慢;
cglib:委托类可以没有接口,继承的思维来实现相似性,制作代理过程比较慢,执行快。主要解决没有接口类的代理实现
3. 日志处理带来的问题?
我们有一个 Pay(接口) 然后两个实现类 DollarPay 和 RmbPay,
都需要重写pay()方法, 这时我们需要对 pay 方法进行性能监控,
日志的添加等等怎么做?
1.最容易想到的方法
对每个字符方法均做日志代码的编写处理形如下面方式:
缺点: 代码重复太多,
添加的日志代码耦合度太高(如果需要更改日志记录代码
功能需求,类中方法需要全部改动,工程量浩大。。。)
2.使用装饰器模式 /代理模式改进解决方案
装饰器模式:动态地给一个对象添加一些额外的职责。
代理模式:以上刚讲过。
于是得出以下结构:
仔细考虑过后发现虽然对原有内部代码没有进行改动,**对于每个类做日志处理,
并引用目标类,但是如果待添加日志的业务类的数量很多,此时手动为每个业务类实
现一个装饰器或创建对应的代理类,同时代码的耦合度也加大,需求一旦改变,改动
的工程量也是可想而知的。
**有没有更好的解决方案,只要写一次代码,对想要添加日志记录的地方能够实现
代码的复用,达到松耦合的同时,又能够完美完成功能?
答案是肯定的,存在这样的技术,aop 已经对其提供了完美的实现!
二.AOP
面向切面编程Aspect Oriented Programing
1.Aop 是什么?
Aspect Oriented Programing 面向切面编程,相比较 oop 面向对象编程来
说,Aop 关注的不再是程序代码中某个类,某些方法,而 aop
考虑的更多的是一种面到面
的切入
即层与层之间的一种切入,所以称之为切面。联想大家吃的汉堡(中间夹肉)。
那么 aop 是怎么做到拦截整个面的功能呢?考虑中级学到的 servlet
urlpattern
/* 的配置 ,实际上也是 aop 的实现。
2.Aop 能做什么?
AOP 主要应用于日志记录,性能统计,安全控制,事务处理等方面,实现公共功能
性的重复使用。
3.Aop 带来的好处\
- 降低模块与模块之间的耦合度,提高业务代码的聚合度。(高内聚低耦合)\
- 提高了代码的复用性。\
- 提高系统的扩展性。
4、 Aop 基本概念
4.1.Joinpoint(连接点)
被拦截到的每个点, spring 中指被拦截到的每一个方法, spring
aop 一个连接点即代表一个方法的执行。
4.2. Pointcut(切入点)
对连接点进行拦截的定义(匹配规则定义 规定拦截哪些方法, 对哪些方
法进行处理) , spring 这块有专门的表达式语言定义。
4.3.Advice(通知)
拦截到每一个连接点即(每一个方法) 后所要做的操作
i. 前置通知 (前置增强) --before() 执行方法前通知
ii.返回通知(返回增强)--afterReturn 方法正常结束返回后的通知
iii.异常抛出通知(异常抛出增强)--afetrThrow()
iv.最终通知---after 无论方法是否发生异常,均会执行该通知。
v.环绕通知---around 包围一个连接点(join point)的通知,如方法调用。
这是最强大的一种通知类型。
环绕通知可以在方法调用前后完成自定义的行为。它也
会选择是否继续执行连接点或直接返回它们自己的返回值或抛出异常来结束执行。
4.4.Aspect(切面)
切入点与通知的结合, 决定了切面的定义, 切入点定义了要拦截哪些类的
哪些方法, 通知则定义了拦截过方法后要做什么,
切面则是横切关注点的抽象,
与类相似, 类是对物体特征的抽象, 切面则是横切关注点抽象。
4.5.Target(目标对象)
被代理的目标对象
4.6.Weave(织入)
将切面应用到目标对象并生成代理对象的这个过程即为织入。
4.7.Introduction(引入)
在不修改原有应用程序代码的情况下, 在程序运行期为类动态添加方法或
者字段的过程称为引入\
5、 使用 Aop 解决日志处理问题
Aop 配置有两种方式 注解方式 与 xml 方式\
5.1注解方式解决日志处理问题,实现代理
1). jar 包坐标引入
<!-- 引入aop依赖-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.9</version>
</dependency>
2). spring.xml 配置, 添加命名空间, 配置 aop 代理
<!-- 开启包扫描 -->
<context:component-scan base-package="com.shsxt"/>
<!-- 开启aop代理 -->
<aop:aspectj-autoproxy/>
3. 编写业务方法 UserService
@Service
public class UserService {
public void addUser(){
// int i = 1/0;
System.out.println("UserService addUser...");
}
public void queryUser(){
System.out.println("UserService queryUser...");
}
}
4. 编写 aop 实现类LogCut.java
/**
* 通过注解配置aop
*/
@Aspect
@Component
public class LogCut {
// 定义切入点
// 拦截规则 匹配包及子包的任意类任意方法
@Pointcut("execution (* com.shsxt.service..*.*(..))")
public void cut() {
}
//当拦截到service里方法时触发通知
@Before("cut()")
public void before() {
System.out.println("before");
}
//都会执行
@After("cut()")
public void after() {
System.out.println("after");
}
//报错后无法执行
@AfterReturning("cut()")
public void afterReturning() {
System.out.println("afterReturning");
}
//抛异常才执行
@AfterThrowing(pointcut = "cut()", throwing = "e")
public void afterThrowing(Exception e) {
System.out.println("AfterThrowing");
System.out.println(e);
}
//环绕通知 ,发生异常后around-after不会执行
@Around("cut()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("around -- before");
Object result = pjp.proceed();
System.out.println("around -- after");
return result;
}
}
5.测试UserServiceTest.java
public class UserServiceTest {
@Test
public void addUser() throws Exception {
ApplicationContext context =
new ClassPathXmlApplicationContext("spring.xml");
UserService userService = (UserService) context.getBean("userService");
userService.addUser();
}
正常代理结果显示
报错后测试结果,aroud-after不会执行
5.2xml 配置实现
1). 声明 aop 代理,配置spring.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:component-scan base-package="com.shsxt"/>
<!-- 开启aop代理 -->
<aop:aspectj-autoproxy/>
<!-- 通过xml配置aop -->
<aop:config>
<aop:aspect ref="logCut2">
<aop:pointcut id="cut" expression="execution (* com.shsxt.service..*.*(..))"/>
<aop:before pointcut-ref="cut" method="before"/>
<aop:after pointcut-ref="cut" method="after"/>
<aop:after-returning pointcut-ref="cut" method="afterReturning"/>
<aop:after-throwing pointcut-ref="cut" method="afterThrowing" throwing="e"/>
<aop:around pointcut-ref="cut" method="around"/>
</aop:aspect>
</aop:config>
</beans>
2). 定义 bean----LogCut2.java
/**
* 通过xml配置aop
*/
@Component
public class LogCut2 {
public void cut() {
}
public void before() {
System.out.println("before==2");
}
public void after() {
System.out.println("after==2");
}
public void afterReturning() {
System.out.println("afterReturning==2");
}
public void afterThrowing(Exception e) {
System.out.println("afterThrowing==2");
System.out.println(e);
}
public Object around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("around -- before==2");
Object result = pjp.proceed();
System.out.println("around -- after==2");
return result;
}
}
3). 测试UserServiceTest测试aop
@Test
public void queryUser() throws Exception {
ApplicationContext context =
new ClassPathXmlApplicationContext("spring.xml");
UserService userService = (UserService) context.getBean("userService");
userService.queryUser();
}
测试结果,在原有基础上多了一组aop配置
**5.3 Aop 匹配方法规则表达式语言(简要了解)
**Aop 切入点表达式简介
执行任意公共方法:
execution(public *(..))
执行任意的 set 方法
execution(* set*(..))
执行 com.xyz.service 包下任意类的任意方法
execution(* com.xyz.service.*.*(..))
执行 com.xyz.service 包 以及子包下任意类的任意方法
execution(* com.xyz.service..*.*(..))
三. Spring Aop 面试中常见问题
1.代理模式实现三要素是什么?
2.jdk 动态代理与 cglib 动态代理区别是什么?
3.Spring AOP 两种实现机制是什么?
动态代理 jdk cglib
4.什么是 aop,谈谈你对 aop 的理解。
5.aop 通知类型有哪些?
总结\
- 代理模式实现三要素
i.接口定义
ii.目标对象 与代理对象必须实现统一接口
iii.代理对象持有目标对象的引用 增强目标对象行为\ - 代理模式实现分类以及对应区别
静态代理:手动为目标对象制作代理对象,即在程序编译阶段完成代理对象的创建
动态代理:在程序运行期动态创建目标对象对应代理对象。
jdk 动态代理:被代理目标对象必须实现某一或某一组接口 实现方式 通过回
调创建代理对象。
cglib 动态代理:被代理目标对象可以不必实现接口,继承的方式实现。
动态代理相比较静态代理,提高开发效率,可以批量化创建代理,提高代码复用率。\
- Aop 理解
i. 面向切面,相比 oop 关注的是代码中的层 或面
ii. 解耦,提高系统扩展性
iii. 提高代码复用\ - Aop 关键词\
- 连接点:每一个方法\
- 切入点:匹配的方法集合\
- 切面:连接点与切入点的集合决定了切面,横切关注点的抽象\
- 通知:几种通知\
- 目标对象:被代理对象\
- 织入:程序运行期将切面应用到目标对象 并生成代理对象的过程\
- 引入:在不修改原始代码情况下,在程序运行期为程序动态引入方法或字段的
过程
四、 spring 整合 jdbc
1坐标添加
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.shsxt</groupId>
<artifactId>spring10-jdbc</artifactId>
<version>1.0-SNAPSHOT</version>
<name>spring10-jdbc</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!-- spring 框架坐标依赖添加 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.3.2.RELEASE</version>
</dependency>
<!-- aop -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.9</version>
</dependency>
<!-- mysql 驱动包 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.39</version>
</dependency>
<!-- c3p0 连接池 -->
<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.1.2</version>
</dependency>
<!-- spring jdbc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>4.3.2.RELEASE</version>
</dependency>
<!-- spring事物 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>4.3.2.RELEASE</version>
</dependency>
</dependencies>
<build>
</build>
</project>
2.配置文件db.properties
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/spring_jdbc?useUnicode=true&characterEncoding=utf8
jdbc.user=root
jdbc.password=shsxt
***\
3) bean.xml 配置修改,加载 properties 文件配置
<?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"
xmlns:tx="http://www.springframework.org/schema/tx"
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
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<!-- 开启包扫描 -->
<context:component-scan base-package="com.shsxt"/>
<!-- 开启aop代理 -->
<aop:aspectj-autoproxy/>
<!-- 加载properties 配置文件 -->
<context:property-placeholder location="db.properties" />
<!-- 配置c3p0 数据源 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driver}"></property>
<property name="jdbcUrl" value="${jdbc.url}"></property>
<property name="user" value="${jdbc.user}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<!-- jdbcTemplate 配置 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 事务管理器定义 -->
<bean id="txManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置通知 -->
<!--
tx:method的属性:
* name 是必须的,表示与事务属性关联的方法名(业务方法名),对切入点进行细化。通配符(*)可以用来指定一批关联到相同的事务属性的方法。
如:'get*'、'handle*'、'on*Event'等等.
propagation 不是必须的 ,默认值是REQUIRED
表示事务传播行为, 包括REQUIRED,SUPPORTS,MANDATORY,REQUIRES_NEW,NOT_SUPPORTED,NEVER,NESTED
isolation 不是必须的 默认值DEFAULT
表示事务隔离级别(数据库的隔离级别)
timeout 不是必须的 默认值-1(永不超时)
表示事务超时的时间(以秒为单位)
read-only 不是必须的 默认值false不是只读的
表示事务是否只读
rollback-for 不是必须的
表示将被触发进行回滚的 Exception(s);以逗号分开。
如:'com.foo.MyBusinessException,ServletException'
no-rollback-for 不是必须的
表示不被触发进行回滚的 Exception(s);以逗号分开。
如:'com.foo.MyBusinessException,ServletException'
任何 RuntimeException 将触发事务回滚
-->
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<!--匹配以save开头的service方法均加入事物-->
<tx:method name="save*" propagation="REQUIRED" />
<!--匹配以del开头的service方法均加入事物-->
<tx:method name="del*" propagation="REQUIRED" />
<!--匹配以update开头的service方法均加入事物-->
<tx:method name="update*" propagation="REQUIRED" />
<!--匹配以query开头的service方法事物为只读模式-->
<tx:method name="query*" read-only="true" />
</tx:attributes>
</tx:advice>
<!-- aop 切面定义 -->
<aop:config>
<aop:pointcut expression="execution( * com.shsxt.service..*.*(..) )" id="cut" />
<aop:advisor advice-ref="txAdvice" pointcut-ref="cut" />
</aop:config>
<!-- 开启注解配置 -->
<tx:annotation-driven transaction-manager="txManager"/>
</beans>
4 .配置数据源(见上)
由于建立数据库连接是一个非常耗时耗资源的行为, 所以通过连接池预
先同数据库建立一些连接, 放在内存中, 应用程序需要建立数据库连接时直接
到连接池中申请一个就行, 用完后再放回去。
C3P0 与 dbcp 二选一即可
DBCP(DataBase connection pool),数据库连接池。 是 apache 上的一个 java
连接
池项目, 也是 tomcat 使用的连接池组件。 单独使用 dbcp 需要 2 个包:
commons
dbcp.jar,commons-pool.jar dbcp,没有自动回收空闲连接的功能.
C3P0 是一个开源的 JDBC 连接池, 它实现了数据源, 支持 JDBC3 规范和
JDBC2
的标准扩展。 目前使用它的开源项目有 Hibernate, Spring 等。 c3p0
有自动回收空闲连接功能
C3P0 数据源配置
5.模板类配置(见上)
Spring 把 JDBC 中重复的操作建立成了一个模板类:
org.springframework.jdbc.core.JdbcTemplate ,配置文件中加入
6.创建数据库 spring_jdbc 并创建测试表
account,创建SpringJdbcTest测试连接数据库
public class SpringJdbcTest {
private JdbcTemplate template;
private AccountService accountService;
@Before
public void init(){
ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
template = (JdbcTemplate) context.getBean("jdbcTemplate");
accountService = (AccountService) context.getBean("accountService");
}
@Test
public void test01(){
String sql = "select count(*) from account";
Integer total = template.queryForObject(sql, Integer.class);
System.out.println("total: "+total);
}
7.测试整合结果
通过 junit 测试 jdbcTemplate bean 是否获取到
五. 转账操作模拟
1.pom.xml配置与spring.xml见上例
2. AccountDao 方法实现
对于转账涉及到双方账户以及对应转账金额,所以对应 dao
有入账和出账两个方法。
@Repository
public class AccountDao {
@Autowired
private JdbcTemplate template;
/**
* 出账
* @param id
* @param money
*/
public void outMoney(Integer id, Double money){
String sql = "update account set money=money-? where id=?";
template.update(sql,money,id);
}
/**
* 入账
* @param id
* @param money
*/
public void inMoney(Integer id, Double money){
String sql = "update account set money=money+? where id=?";
template.update(sql,money,id);
}
}
3.Service 层转账接口方法定义AccountService层
对于要处理的 service
层转账业务,可以在业务方法中处理两个操作:入账与出账,业
务方法定义如下
@Service
public class AccountService {
@Autowired
private AccountDao accountDao;
/**
* 转账
* @param outId
* @param inId
* @param money
*/
@Transactional(propagation = Propagation.REQUIRED)
public void zhuanzhang(Integer outId, Integer inId, Double money){
// try {
//
// } catch (Exception e) {
// e.printStackTrace();
// //TODO rollback
// }
// spring中只要报错就自动回滚
try {
accountDao.outMoney(outId, money);
int i = 1/0;
accountDao.inMoney(inId, money);
} catch (Exception e) {
e.printStackTrace();
// 如果需要try ... catch 自己手动抛出一个异常
// 抛出的异常必须的RuntimeException
throw new RuntimeException();
}
}
public void updateAccountMoney(Integer outId, Integer inId, Double money){
accountDao.outMoney(outId, money);
int i = 1/0;
accountDao.inMoney(inId, money);
}
}
4.转账业务实现 SpringJdbcTest模拟转账
public class SpringJdbcTest {
private JdbcTemplate template;
private AccountService accountService;
@Before
public void init(){
ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
template = (JdbcTemplate) context.getBean("jdbcTemplate");
accountService = (AccountService) context.getBean("accountService");
}
@Test
public void test01(){
String sql = "select count(*) from account";
Integer total = template.queryForObject(sql, Integer.class);
System.out.println("total: "+total);
}
@Test
public void zhuanzhang(){
accountService.zhuanzhang(8,9,100.00);
}
@Test
public void updateAccountMoney(){
accountService.updateAccountMoney(8,9,100.00);
}
}
六、 Spring 事务
6.1 事物四大特性(即: ACID)
原子性(Atomicity):共生死, 要么全部成功, 要么全部失败!
一致性(Consistency):事务在执行前后,
数据库中数据要保持一致性状态。
(如转账的过程 账户操作后数据必须保持一致)
隔离性(Isolation):事务与事务之间的执行应当是相互隔离互不影响的。
(多
个角色对统一记录进行操作必须保证没有任何干扰) , 当然没有影响是不可能
的, 为了让影响级别降到最低, 通过隔离级别加以限制:\
- READ_UNCOMMITTED\
- READ_COMMITTED\
- REPEATABLE_READ\
- SERIALIZABLE
持久性(Durability):事务提交完毕后,
数据库中的数据的改变是永久的。
6.2 Spring 事物核心接口
Spring
事务管理的实现有许多细节,如果对整个接口框架有个大体了解会非常有利
于我们理解事务,下面通过讲解 Spring 的事务接口来了解 Spring
实现事务的具体策略。
Spring 并不直接管理事务, 而是提供了多种事务管理器, 他们将事务管
理的职责委托给 Hibernate 或者 JTA
等持久化机制所提供的相关平台框架的事务来实现。
Spring 事务管理器的接口是
org.springframework.transaction.PlatformTransactionManager, 通过这个
接口, Spring 为各个平台如 JDBC、 Hibernate
等都提供了对应的事务管理器,
但是具体的实现就是各个平台自己的事情了。 此接口的内容如下:
Public interface PlatformTransactionManager(){
// 由 TransactionDefinition 得到 TransactionStatus 对象
TransactionStatus getTransaction(TransactionDefinition definition) throws
TransactionException;
// 提交
Void commit(TransactionStatus status) throws TransactionException;
// 回滚
Void rollback(TransactionStatus status) throws TransactionException;
}
这里可知具体的具体的事务管理机制对 Spring
来说是透明的,它并不关心那些,那
些是对应各个平台需要关心的,所以 Spring
事务管理的一个优点就是为不同的事务 API 提供一致的编程模型,如
JTA、JDBC、Hibernate、JPA。下面分别介绍各个平台框架实现事务管理的机制。
6.3 JDBC 事务
如果应用程序中直接使用 JDBC 来进行持久化, 此时使用
DataSourceTransactionManager 来处理事务边界。 为了使用
DataSourceTransactionManager, 需要使用如下的 XML
将其装配到应用程序的上下文定义中:
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
实际上, DataSourceTransactionManager 是通过调用 java.sql.Connection
来管理事务, 而后者是通过 DataSource 获取到的。 通过调用连接的
commit()方法来提交事务, 同
样, 事务失败则通过调用 rollback()方法进行回滚。
6.4 Hibernate 事务
如果应用程序的持久化是通过 Hibernate 实习的, 那么你需要使用
HibernateTransactionManager。 对于 Hibernate3, 需要在 Spring
上下文定义中添加如下的<bean>声明:
<bean id="transactionManager"
class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
sessionFactory 属性需要装配一个 Hibernate 的 session 工厂,
HibernateTransactionManager 的实现细节是它将事务管理的职责委托给
org.hibernate.Transaction 对象, 而后者是从 Hibernate Session
中获取到的。 当事务成功完成时, HibernateTransactionManager 将会调用
Transaction 对象的 commit()方法, 反之, 将会调用 rollback()方法。
6.5 Java 持久化 API 事务(JPA)
Hibernate 多年来一直是 Java 持久化标准, 但是现在 Java 持久化 API
作为真正的
Java 持久化标准进入大家的视野。 如果你计划使用 JPA 的话, 那你需要使用
Spring 的JpaTransactionManager 来处理事务。 你需要在 Spring 中这样配置
JpaTransactionManager:
<bean id="transactionManager"
class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
JpaTransactionManager 只需要装配一个 JPA 实体管理工厂
(javax.persistence.EntityManagerFactory 接口的任意实现) 。
JpaTransactionManager将与由工厂所产生的 JPA EntityManager
合作来构建事务。
6.6 Java 原生 API 事务
如果应用程序没有使用以上所述的事务管理,
或者是跨越了多个事务管理源(比如两个或者是多个不同的数据源) ,
此时需要使用 JtaTransactionManager:
<bean id="transactionManager"
class="org.springframework.transaction.jta.JtaTransactionManager">
<property name="transactionManagerName"
value="java:/TransactionManager" />
</bean>
JtaTransactionManager 将事务管理的责任委托给
javax.transaction.UserTransaction 和
javax.transaction.TransactionManager 对象, 其中事务成功完成通过
UserTransaction.commit()方法提交, 事务失败通过
UserTransaction.rollback()方法回滚。
6.7 Spring Jdbc 事务管理配置
通过 jdbc 持久化事物,对于事物配置实现由两种方式即:Xml 配置,注解配置
***6.7.1Xml 事物配置声明
*1).修改 xml 命名空间
xmlns:tx=[http://www.springframework.org/schema/tx](http://www.springframework.org/schema/tx)
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
2.aop 代理
<aop:aspectj-autoproxy />
3.配置事物管理器
<!-- 事务管理器定义 -->
<bean id="txManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
4.配置事物相关通知
一般来说增删改方法 propagation=Required,对于查询方法使用
read-only=**"true"
**示例如下
5.配置 aop(切入点、 通知)
<!-- aop 切面定义 -->
<aop:config>
<aop:pointcut expression="execution( *
com.shsxt.service..*.*(..) )"
id="cut" />
<aop:advisor advice-ref="txAdvice" pointcut-ref="cut" />
</aop:config>
6.全部配置spring.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"
xmlns:tx="http://www.springframework.org/schema/tx"
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
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<!-- 开启包扫描 -->
<context:component-scan base-package="com.shsxt"/>
<!-- 开启aop代理 -->
<aop:aspectj-autoproxy/>
<!-- 加载properties 配置文件 -->
<context:property-placeholder location="db.properties" />
<!-- 配置c3p0 数据源 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driver}"></property>
<property name="jdbcUrl" value="${jdbc.url}"></property>
<property name="user" value="${jdbc.user}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<!-- jdbcTemplate 配置 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 事务管理器定义 -->
<bean id="txManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置通知 -->
<!--
tx:method的属性:
* name 是必须的,表示与事务属性关联的方法名(业务方法名),对切入点进行细化。通配符(*)可以用来指定一批关联到相同的事务属性的方法。
如:'get*'、'handle*'、'on*Event'等等.
propagation 不是必须的 ,默认值是REQUIRED
表示事务传播行为, 包括REQUIRED,SUPPORTS,MANDATORY,REQUIRES_NEW,NOT_SUPPORTED,NEVER,NESTED
isolation 不是必须的 默认值DEFAULT
表示事务隔离级别(数据库的隔离级别)
timeout 不是必须的 默认值-1(永不超时)
表示事务超时的时间(以秒为单位)
read-only 不是必须的 默认值false不是只读的
表示事务是否只读
rollback-for 不是必须的
表示将被触发进行回滚的 Exception(s);以逗号分开。
如:'com.foo.MyBusinessException,ServletException'
no-rollback-for 不是必须的
表示不被触发进行回滚的 Exception(s);以逗号分开。
如:'com.foo.MyBusinessException,ServletException'
任何 RuntimeException 将触发事务回滚
-->
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<!--匹配以save开头的service方法均加入事物-->
<tx:method name="save*" propagation="REQUIRED" />
<!--匹配以del开头的service方法均加入事物-->
<tx:method name="del*" propagation="REQUIRED" />
<!--匹配以update开头的service方法均加入事物-->
<tx:method name="update*" propagation="REQUIRED" />
<!--匹配以query开头的service方法事物为只读模式-->
<tx:method name="query*" read-only="true" />
</tx:attributes>
</tx:advice>
<!-- aop 切面定义 -->
<aop:config>
<aop:pointcut expression="execution( * com.shsxt.service..*.*(..) )" id="cut" />
<aop:advisor advice-ref="txAdvice" pointcut-ref="cut" />
</aop:config>
<!-- 开启注解配置 -->
<tx:annotation-driven transaction-manager="txManager"/>
</beans>
7通过上述转账accoutService中updateAccountMoney测试JDBC事务配置成功
6.7.2Spring 事务管理注解方式
1.配置事物管理器
<!-- spring 注解式事务声明 -->
<!-- 事务管理器定义 -->
<bean id="txManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
2.配置注解支持
<tx:annotation-driven transaction-manager="txManager"/>
3.Service 方法上在需要添加事务的方法上加入事物注解
@Override
@Transactional(propagation=Propagation.REQUIRED)
public void saveUser(String userName,String userPwd){
User user1=new User();
user1.setUserName(userName);
user1.setUserPwd(userPwd);
userDao.saveUser(user1);
userDao.delUserById(2);
}
备注:默认 spring 事务只在发生未被捕获的 runtimeexcetpion 时才回滚。
spring aop 异常捕获原理:被拦截的方法需显式抛出异常,并不能经任何处理,
这样 aop 代理才能捕获到方法的异常,才能进行回滚,默认情况下 aop
只捕获runtimeexception 的异常,但可以通过配置来捕获特定的异常并回滚
换句话说在 service 的方法中不使用 try catch 或者在 catch 中最后加上
throw new
RunTimeexcetpion(),这样程序异常时才能被 aop 捕获进而回滚