AOP
AOP
概述
AOP (Aspect Oriented Programing)
AOP 采用了横向抽取
机制,取代了纵向继承
体系中重复性的代码( 性能监视、 事务管理、 安全检查、 缓存)
Spring AOP 使用纯 Java 实现, 不需要专门的编译过程和类加载器, 在运行期通过代理方式向目标类织入增强代码
SpringAOP思想
Spring的AOP代理
- JDK 动态代理:对实现了接口的类生成代理(缺陷就是这个类必须先实现某个接口)
- CGLib 代理机制:对类生成代理(对没有实现接口的类也可以代理)
AOP 的术语
- ==
Joinpoint
==(连接点):所谓连接点是指那些被拦截到的点。 在 spring 中,这些点指的是方法,因为 spring 只支持方法类型的连接点.( 即那些方法可以被拦截) - ==
Pointcut
==(切入点):所谓切入点是指我们要对哪些 Joinpoint 进行拦截的定义.( 实际拦截的方法) - ==
Advice
==(通知/增强):所谓通知是指拦截到Joinpoint之后所要做的事情就是通知.通知分为前置通知,后置通知,异常通知,最终通知,环绕通知(切面要完成的功能) - ==
Introduction
==(引介):引介是一种特殊的通知在不修改类代码的前提下, Introduction 可以在运行期为类动态地添加一些方法或 Field. - ==
Target
==(目标对象):即代理的目标对象 - ==
Weaving
==(织入):是指把增强应用到目标对象来创建新的代理对象的过程.spring 采用动态代理织入, 而 AspectJ 采用编译期织入和类装载期织入 - ==
Proxy
==(代理):一个类被 AOP 织入增强后, 就产生一个结果代理类 - ==
Aspect
==(切面):是切入点和通知(引介)的结合
AspectJ 表达式
语法:execution(表达式)
execution(<访问修饰符>?<返回类型><方法名>(<参数>)<异常>)
[*代表方法的返回值为任意类型] [*代表所有方法] [..代表任意参数类型]
execution(* cn.itcast.spring3.demo1.dao.*(..))
---只检索当前包execution(* cn.itcast.spring3.demo1.dao..*(..))
---检索包及当前包的子包.execution(* cn.itcast.dao.GenericDAO+.*(..))
---检索 GenericDAO 及子类- 匹配所有类 public 方法
execution(public * *(..))
- 匹配指定包下所有类方法
execution(* cn.itcast.dao.*(..))
不包含子包 execution(* cn.itcast.dao..*(..))
..*
表示包、 子孙包下所有类- 匹配指定类所有方法
execution(*cn.itcast.service.UserService.*(..)
) - 匹配实现特定接口所有类方法
execution(*cn.itcast.dao.GenericDAO+.*(..))
- 匹配所有 save 开头的方法
execution(* save*(..))
- 全通配方式:
* *..*.*(..)
- 注:通常情况下,我们都是对业务层的方法进行增强,所以切入点表达式都是切到业务层实现类。
execution(* com.itheima.service.impl.*.*(..))
基于注解
1.引入jar包
- pom.xml
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
</dependency>
<!--解析切入点表达式-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
2.编写增强的类
- UserDao
@Repository(value = "userDao") //注解的方式,spring管理对象的创建
public class UserDao {
public void add(){
System.out.println("添加用户");
}
public void addInfo(){
System.out.println("添加用户信息");
}
public void update(){
System.out.println("更新用户");
}
public void delete(){
System.out.println("删除用户");
}
public void find(){
System.out.println("查找用户");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
3.使用AspectJ注解形式
- MyAspectJ
@Component // 注解的方式,spring管理对象的创建
@Aspect // 用来定义且切面
public class MyAspectJ {
/**
* 前置通知
* 对UserDao里面的以add方法进行增强
* @param joinPoint
*/
@Before("execution(* club.krislin.Dao.UserDao.add*(..))")
public void before(JoinPoint joinPoint){
//打印的是切点信息
System.out.println(joinPoint);
System.out.println("前置增强");
}
/**
* 后置通知
* 对update开头的方法进行增强
* @param returnValue
*/
@AfterReturning(value = "execution(* club.krislin.Dao.UserDao.update*(..))",returning = "returnValue") // 接受返回值,方法的返回值为任意类型
public void after(Object returnValue){
System.out.println("后置通知");
System.out.println("方法的返回值为:"+returnValue);
}
/**
* 环绕通知增强
* 对以find开头的方法进行增强
* @param proceedingJoinPoint
* @return
* @throws Throwable
*/
@Around(value = "execution(* club.krislin.Dao.UserDao.find*(..))")
public Object arount(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("环绕通知增强-----前");
// 执行目标方法
Object obj = proceedingJoinPoint.proceed();
System.out.println("环绕通知增强-----后");
return obj;
}
/**
* 异常通知增强
* 对以find开头的方法进行增强
* @param e
*/
@AfterThrowing(value = "execution(* club.krislin.Dao.UserDao.find(..))",throwing = "e")
public void afterThrowing(Throwable e){
System.out.println("出现异常"+e.getMessage());
}
@After(value = "execution(* club.krislin.Dao.UserDao.find(..))")
public void after(){
System.out.println("最终通知");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
4.配置
<!--自动生成代理底层就是 AnnotationAwareAspectJAutoProxyCreator-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
<context:component-scan base-package="club.krislin"></context:component-scan>
2
3
4
5.测试
- UserDaoTest
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class UserDaoTest {
@Resource(name = "userDao")
private UserDao userDao;
@Test
public void testUserDao(){
userDao.add();
userDao.addInfo();
userDao.delete();
userDao.find();
userDao.update();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
注解说明
==@Aspect
==
表明当前类是一个切面类
==@Before
==
作用:
把当前方法看成是前置通知。
属性: value:用于指定切入点表达式,还可以指定切入点表达式的引用。
==@AfterReturning
==
作用
把当前方法看成是后置通知。相当于 AfterReturningAdvice
属性:
value:用于指定切入点表达式,还可以指定切入点表达式的引用
==@Around==
作用
把当前方法看成是环绕通知。相当于MethodInterceptor
属性:
value:用于指定切入点表达式,还可以指定切入点表达式的引用
==@AfterThrowing==
作用
把当前方法看成是异常通知。相当于 ThrowAdvice
属性:
value:用于指定切入点表达式,还可以指定切入点表达式的引用
==@After==
作用
把当前方法看成是最终通知,不管是否异常, 该通知都会执行
属性:
value:用于指定切入点表达式,还可以指定切入点表达式的引用
==@Pointcut
==
作用
指定切入点表达式
属性
value:指定表达式的内容
基于xml
1.编写被增强的类
- UserDao
public class UserDao {
public void add(){
System.out.println("添加用户");
}
public void addInfo(){
System.out.println("添加用户信息");
}
public void update(){
System.out.println("更新用户");
}
public void delete(){
System.out.println("删除用户");
}
public void find(){
System.out.println("查找用户");
//int i = 1;
//int n = i/0;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2.编写增强的类
- MyAspectJ
public class MyAspectJ {
/**
* 前置通知
* @param joinPoint
*/
public void before(JoinPoint joinPoint){
//打印的是切点信息
System.out.println(joinPoint);
System.out.println("前置增强");
}
/**
* 后置通知
* @param returnValue
*/
public void after(Object returnValue){
System.out.println("后置通知");
System.out.println("方法的返回值为:"+returnValue);
}
/**
* 环绕通知增强
* @param proceedingJoinPoint
* @return
* @throws Throwable
*/
public Object arount(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("环绕通知增强-----前");
// 执行目标方法
Object obj = proceedingJoinPoint.proceed();
System.out.println("环绕通知增强-----后");
return obj;
}
/**
* 异常通知增强
* @param e
*/
public void afterThrowing(Throwable e){
System.out.println("出现异常"+e.getMessage());
}
/**
* 最终通知增强
*/
public void after(){
System.out.println("最终通知");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
3配置applicationContext.xml
<!--定义被增强的类-->
<bean name="userDao" class="club.krislin.Dao.UserDao"></bean>
<!--定义切面类-->
<bean name="myAspectJ" class="club.krislin.MyAspectJ"></bean>
<!--定义aop配置-->
<aop:config>
<!--定义哪些方法上使用增强-->
<aop:pointcut expression="execution(* club.krislin.Dao.UserDao.add*(..))" id="myPointcut"/>
<aop:pointcut expression="execution(* club.krislin.Dao.UserDao.add(..))" id="myPointcut1"/>
<aop:aspect ref="myAspectJ">
<!--在add开头的方法上采用前置通知-->
<aop:before method="before" pointcut-ref="myPointcut"/>
</aop:aspect>
<aop:aspect ref="myAspectJ">
<!--后置通知-->
<aop:after-returning method="after" pointcut-ref="myPointcut" returning="returnValue"/>
</aop:aspect>
<aop:aspect ref="myAspectJ">
<!--环绕通知-->
<aop:around method="arount" pointcut-ref="myPointcut"/>
</aop:aspect>
<aop:aspect ref="myAspectJ">
<!--异常通知-->
<aop:after-throwing method="afterThrowing" pointcut-ref="myPointcut" throwing="e"/>
</aop:aspect>
<aop:aspect ref="myAspectJ">
<!--最终通知-->
<aop:after method="after" pointcut-ref="myPointcut1"/>
</aop:aspect>
</aop:config>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
4.测试
- UsetDaoTest
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class UserDaoTest {
@Resource(name = "userDao")
private UserDao userDao;
@Test
public void testUserDao(){
userDao.add();
userDao.addInfo();
userDao.delete();
userDao.update();
userDao.find();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
xml方式的AOP配置步骤
1.配置被增强的类和通知(即增强方法)
2.使用aop:config
声明aop配置
==aop:config
== 用于声明开始aop的配置
<aop:config>
<!-- 配置的代码都写在此处 -->
</aop:config>
2
3
3.使用aop:aspect
配置切面
==aop:aspect
== 用于配置切面
属性: id:给切面提供一个唯一的表示
ref:引用配置好的通知类bean的id
<aop:aspect id="txAdvice" ref="txManager">
<!--配置通知的类型要写在此处-->
</aop:aspect>
2
3
4.使用aop:pointcut
配置切入点表达式
==aop:pointcut
== 用于配置切入点表达式.就是指定对哪些类的哪些方法进行增强
属性: expression:由于定义切入表达式
id:用于给切入点表达式提供唯一的标识
<aop:pointcut expression="execution(* club.krislin.Dao.UserDao.add*(..))" id="myPointcut"/>
5.使用aop:xxx
配置对应的通知类型
==aop:before
== 用于配置前置通知.指定增强的方法在切入点方法之前执行
属性: method:用于指定通知类中的增强方法名称
pointcut-ref:用于指定切入点表达式的引用
pointcut:用于指定切入点表达式
执行时间: 切入点方法执行之前
<aop:before method="before" pointcut-ref="myPointcut"/>
==aop:after-returning
== 用于配置后置通知
属性: method:用于指定通知类中的增强方法名称
pointcut-ref:用于指定切入点表达式的引用
pointcut:用于指定切入点表达式
returning:后置通知返回值
执行时间:切入点方法正常执行之后,它和异常通知只能有一个执行
<aop:after-returning method="after" pointcut-ref="myPointcut" returning="returnValue"/>
==aop:after-throwing
== 用于配置异常通知
属性: method:用于指定通知类中的增强方法名称
pointcut-ref:用于指定切入点表达式的引用
pointcut:用于指定切入点表达式
throwing:指定抛出的异常
执行时间:切入点方法执行异常后执行,它和后置通知只能有一个执行
<aop:after-throwing method="afterThrowing" pointcut-ref="myPointcut" throwing="e"/>
==aop:after
==: 用于配置最终通知
属性: method:用于指定通知类中的增强方法名称
pointcut-ref:用于指定切入点表达式的引用
pointcut:用于指定切入点表达式
执行时间:无论切入点方法执行时是否异常,它都会在后面执行
<aop:after method="after" pointcut-ref="myPointcut1"/>
==aop:around
== 用于配置环绕通知
- 属性: method:用于指定通知类中的增强方法名称 pointcut-ref:用于指定切入点表达式的引用 pointcut:用于指定切入点表达式
- 说明: 它是 spring 框架为我们提供的一种可以在代码中手动控制增强代码什么时候执行的方式。
- 注意: 通常情况下,环绕通知都是独立使用的
<aop:around method="arount" pointcut-ref="myPointcut"/>
package cn.edu.buaa.utils;
import org.aspectj.lang.ProceedingJoinPoint;
/**
* 用于记录日志的工具类,它里面提供了公共的代码
*/
public class Logger {
/**
* 前置通知
*/
public void beforePrintLog(){
System.out.println("前置通知Logger类中的beforePrintLog方法开始记录日志了。。。");
}
/**
* 后置通知
*/
public void afterReturningPrintLog(){
System.out.println("后置通知Logger类中的afterReturningPrintLog方法开始记录日志了。。。");
}
/**
* 异常通知
*/
public void afterThrowingPrintLog(){
System.out.println("异常通知Logger类中的afterThrowingPrintLog方法开始记录日志了。。。");
}
/**
* 最终通知
*/
public void afterPrintLog(){
System.out.println("最终通知Logger类中的afterPrintLog方法开始记录日志了。。。");
}
/**
* 环绕通知
* 问题:
* 当我们配置了环绕通知之后,切入点方法没有执行,而通知方法执行了。
* 分析:
* 通过对比动态代理中的环绕通知代码,发现动态代理的环绕通知有明确的切入点方法调用,而我们的代码中没有。
* 解决:
* Spring框架为我们提供了一个接口:ProceedingJoinPoint。该接口有一个方法proceed(),此方法就相当于明确调用切入点方法。
* 该接口可以作为环绕通知的方法参数,在程序执行时,spring框架会为我们提供该接口的实现类供我们使用。
*
* spring中的环绕通知:
* 它是spring框架为我们提供的一种可以在代码中手动控制增强方法何时执行的方式。
*/
public Object aroundPringLog(ProceedingJoinPoint pjp){
Object rtValue = null;
try{
Object[] args = pjp.getArgs();//得到方法执行所需的参数
System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。前置");
rtValue = pjp.proceed(args);//明确调用业务层方法(切入点方法)
System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。后置");
return rtValue;
}catch (Throwable t){
System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。异常");
throw new RuntimeException(t);
}finally {
System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。最终");
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
Advisor 和 Aspect 的区别?
- 都叫切面
- Advisor:Spring 传统意义上的切面:支持一个切点和一个通知的组合.
- Aspect:可以支持多个切点和多个通知的组合.
JdbcTemplate
pom.xml
<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>
<!-- mysql connector版本号-->
<mysql.version>5.1.47</mysql.version>
<!-- spring版本号 -->
<spring.version>5.1.5.RELEASE</spring.version>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5.4</version>
</dependency>
<!-- https://mvnrepository.com/artifact/commons-dbcp/commons-dbcp -->
<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
<version>1.4</version>
</dependency>
</dependencies>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
配置数据源
<!--加载配置文件jdbc.properties-->
<context:property-placeholder location="classpath:jdbc.properties"/>
2
Spring中默认的数据源
<!--spring内置数据源-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="${jdbc.driverClass}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
2
3
4
5
6
7
C3P0数据源
<!--c3p0数据源-->
<bean id="dataSource1" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driverClass}"/>
<property name="jdbcUrl" value="${jdbc.url}"/>
<property name="user" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
2
3
4
5
6
7
DBCP数据源
<!--DBCP数据源-->
<bean id="dataSource2" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="${jdbc.driverClass}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
2
3
4
5
6
7
JdbcTemplate的增删改查
配置数据库的操作模板 JdbcTemplate
<!-- 配置一个数据库的操作模板: JdbcTemplate -->
<!--spring的默认数据源-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!--注入数据源-->
<property name="dataSource" ref="dataSource"/>
</bean>
2
3
4
5
6
获取对象
// 1.加载spring配置
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
// 2.根据id获取bean对象
JdbcTemplate jdbcTemplate = (JdbcTemplate) ac.getBean("jdbcTemplate");
// 3.执行操作
2
3
4
5
保存操作
// 3.1 保存操作
jdbcTemplate.update("insert into account(name,money) values ('ffff',77777.0)");
2
修改操作
// 3.2 修改操作
jdbcTemplate.update("update account set money=money-? where id=?",300,11);
2
删除操作
// 3.3 删除操作
jdbcTemplate.update("delete account where id=?",11);
2
查询所有操作
// 3.4 查询所用操作
List<Account> accounts = jdbcTemplate.query("select * from account where money>?",new AccountRowMapper(),500);
//spring提供的封装好的
List<Account> accounts = jt.query("select * from account where money > ?",new BeanPropertyRowMapper<Account>(Account.class),1000f);
for (Account account:accounts) {
System.out.println(account);
}
2
3
4
5
6
7
class AccountRowMapper implements RowMapper<Account>{
@Override
public Account mapRow(ResultSet resultSet, int i) throws SQLException {
Account account = new Account();
account.setId(resultSet.getInt("id"));
account.setName(resultSet.getString("name"));
account.setMoney(resultSet.getFloat("money"));
return account;
}
}
2
3
4
5
6
7
8
9
10
查询一个操作
// 3.5 查询一个操作
List<Account> account = jdbcTemplate.query("select * from account where id=?",new AccountRowMapper(),23);
System.out.println(account.isEmpty()?"没有结果":account.get(0));
2
3
查询一行一列操作
// 3.6 查询返回一行一列操作
// 查询返回一行一列:使用聚合函数,在不使用 group by 字句时,都是返回一行一列。最常用的就是分页中获取总记录条数
int count = jdbcTemplate.queryForObject("select count(*) from account where money>?",Integer.class,500);
System.out.println(count);
2
3
4
在dao中使用JdbcTemplate
实体类
- Account
public class Account {
public int id;
public String name;
public float money;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public float getMoney() {
return money;
}
public void setMoney(float money) {
this.money = money;
}
@Override
public String toString() {
return "Account{" +
"id=" + id +
", name='" + name + '\'' +
", money=" + money +
'}';
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
账户接口
- IAccountDao
public interface IAccountDao {
/**
* 根据id查询账户信息
* @param id
* @return
*/
Account findAccountById(Integer id);
/**
* 根据name查询账户信息
* @param name
* @return
*/
Account findAccountByName(String name);
/**
* 更新账户信息
* @param account
*/
void updateAccount(Account account);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
第一种方式:在dao中定义JdbcTemplate
账户持久层实现类
- AccountDaoImpl
public class AccountDaoImpl implements IAccountDao {
JdbcTemplate jdbcTemplate;
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
/**
* 根据id查询账户信息
*
* @param id
* @return
*/
@Override
public Account findAccountById(Integer id) {
List<Account> list = jdbcTemplate.query("select * from account where id=?",new AccountRowMapper(),id);
return list.isEmpty()?null:list.get(0);
}
/**
* 根据name查询账户信息
*
* @param name
* @return
*/
@Override
public Account findAccountByName(String name) {
List<Account> list = jdbcTemplate.query("select * from account where name=?",new AccountRowMapper(),name);
if (list.isEmpty()){
return null;
}
if (list.size()>1){
throw new RuntimeException("结果集不唯一");
}
return list.get(0);
}
/**
* 更新账户信息
*
* @param account
*/
@Override
public void updateAccount(Account account) {
jdbcTemplate.update("update account set money=?,name=? where id=?",account.getMoney(),account.getName(),account.getId());
}
}
class AccountRowMapper implements RowMapper<Account> {
@Override
public Account mapRow(ResultSet resultSet, int i) throws SQLException {
Account account = new Account();
account.setId(resultSet.getInt("id"));
account.setName(resultSet.getString("name"));
account.setMoney(resultSet.getFloat("money"));
return account;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
- 配置
<!--配置dao-->
<bean id="accountDao" class="club.krislin.dao.impl.AccountDaoImpl">
<!--注入JdbcTemplate-->
<property name="jdbcTemplate" ref="jdbcTemplate"/>
</bean>
2
3
4
5
- 测试
// 获取dao
@Resource(name = "accountDao")
private IAccountDao accountDao;
@Test
public void testDao(){
Account account = accountDao.findAccountById(11);
System.out.println(account);
Account account1 = accountDao.findAccountByName("krislin");
System.out.println(account1);
Account account2 = new Account();
account2.setId(11);
account2.setName("wwww");
account2.setMoney(2344f);
accountDao.updateAccount(account2);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
第二种方式:让dao继承JdbcDaoSupport
JdbcDaoSupport 是 spring 框架为我们提供的一个类,该类中定义了一个 JdbcTemplate 对象,我们可以 直接获取使用,但是要想创建该对象,需要为其提供一个数据源,具体源码如下:
public abstract class JdbcDaoSupport extends DaoSupport {
//定义对象
private JdbcTemplate jdbcTemplate;
//set 方法注入数据源,判断是否注入了,注入了就创建 JdbcTemplate
public final void setDataSource(DataSource dataSource) {
if (this.jdbcTemplate == null || dataSource != this.jdbcTemplate.getDataSource())
{ //如果提供了数据源就创建 JdbcTemplate
this.jdbcTemplate = createJdbcTemplate(dataSource);
initTemplateConfig();
}
}
//使用数据源创建 JdcbTemplate
protected JdbcTemplate createJdbcTemplate(DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
//当然,我们也可以通过注入 JdbcTemplate 对象
public final void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
initTemplateConfig();
}
//使用 getJdbcTmeplate 方法获取操作模板对象
public final JdbcTemplate getJdbcTemplate() {
return this.jdbcTemplate;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
账户持久层实现类
- AccountDaoImpl2
public class AccountDaoImpl2 extends JdbcDaoSupport implements IAccountDao{
/**
* 根据id查询账户信息
*
* @param id
* @return
*/
@Override
public Account findAccountById(Integer id) {
List<Account> list = getJdbcTemplate().query("select * from account where id=?",new AccountRowMapper(),id);
return list.isEmpty()?null:list.get(0);
}
/**
* 根据name查询账户信息
*
* @param name
* @return
*/
@Override
public Account findAccountByName(String name) {
List<Account> list = getJdbcTemplate().query("select * from account where name=?",new AccountRowMapper(),name);
if (list.isEmpty()){
return null;
}
if (list.size()>1){
throw new RuntimeException("结果集不知一个");
}
return list.get(0);
}
/**
* 更新账户信息
*
* @param account
*/
@Override
public void updateAccount(Account account) {
getJdbcTemplate().update("update account set name=?,money=? where id=?",account.getName(),account.getMoney(),account.getId());
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
- 配置
<!--配置dao2-->
<bean id="accountDao2" class="club.krislin.dao.impl.AccountDaoImpl2">
<!--注入dataSource-->
<property name="dataSource" ref="dataSource"/>
</bean>
2
3
4
5
- 测试
// 获取dao2
@Resource(name = "accountDao2")
IAccountDao accountDao2;
@Test
public void testDao2(){
Account account = accountDao2.findAccountById(11);
System.out.println(account);
Account account1 = accountDao2.findAccountByName("krislin");
System.out.println(account1);
Account account2 = new Account();
account2.setId(11);
account2.setName("fff");
account2.setMoney(2344f);
accountDao2.updateAccount(account2);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
两版 Dao 有什么区别呢?
- 第一种在 Dao 类中定义 JdbcTemplate 的方式,适用于所有配置方式( xml 和注解都可以)。
- 第二种让 Dao 继承 JdbcDaoSupport 的方式,只能用于基于 XML 的方式,注解用不了。
Spring的事务管理
事务
事务:是逻辑上一组操作, 要么全都成功, 要么全都失败.
事务特性: ACID:
- 原子性:事务不可分割
- 一致性:事务执行的前后, 数据完整性保持一致.
- 隔离性:一个事务执行的时候, 不应该受到其他事务的打扰
- 持久性:一旦结束, 数据就永久的保存到数据库.
如果不考虑隔离性:
- 脏读:一个事务读到另一个事务未提交数据
- 不可重复读:一个事务读到另一个事务已经提交数据( update) 导致一个事务多次查询结果不一致
- 虚读:一个事务读到另一个事务已经提交数据( insert) 导致一个事务多次查询结果不一致
事务的隔离级别: 未提交读:以上情况都有可能发生。 已提交读:避免脏读, 但不可重复读, 虚读是有可能发生。 可重复读:避免脏读, 不可重复读, 但是虚读有可能发生。 串行的:避免以上所有情况.
Spring的事务管理机制
Spring事务管理高层抽象主要包括3个接口,Spring的事务主要是由他们共同完成的:
PlatformTransactionManager
:事务管理器—主要用于平台相关事务的管理TransactionDefinition
:事务定义信息(隔离、传播、超时、只读)—通过配置如何进行事务管理。TransactionStatus
:事务具体运行状态—事务管理过程中,每个时间点事务的状态信息。
PlatformTransactionManager事务管理器
该接口提供三个方法:
- commit:提交事务
- rollback:回滚事务
- getTransaction:获取事务状态
Spring为不同的持久化框架提供了不同PlatformTransactionManager接口实现:
DataSourceTransactionManager
针对JdbcTemplate
、MyBatis
事务控制 ,使用Connection
(连接)进行事务控制 : 开启事务connection.setAutoCommit(false);
提交事务connection.commit();
回滚事务connection.rollback();
HibernateTransactionManage
针对Hibernate
框架进行事务管理, 使用Session
的Transaction
相关操作进行事务控制 : 开启事务session.beginTransaction();
提交事务session.getTransaction().commit();
回滚事务session.getTransaction().rollback();
事务管理器的选择? 用户根据选择和使用的持久层技术,来选择对应的事务管理器。
TransactionDefinition事务定义信息
该接口主要提供的方法:
getIsolationLevel
:隔离级别获取getPropagationBehavio
r:传播行为获取getTimeout
:获取超时时间isReadOnly
是否只读(保存、更新、删除—对数据进行操作-变成可读写的,查询-设置这个属性为true,只能读不能写)withDefaults()
Return an unmodifiable TransactionDefinition with defaults. For customization purposes, use the modifiable DefaultTransactionDefinition instead. Since: 5.2
1
2
3
4
5这些事务的定义信息,都可以在配置文件中配置和定制。
IsolationLevel事务的隔离级别
事务四大特性 ACID ---隔离性引发问题 ---- 解决事务的隔离问题 隔离级别 Mysql 默认隔离级别 REPEATABLE_READ Oracle 默认隔离级别 READ_COMMITTED
事务的传播行为PropagationBehavior
什么是事务的传播行为? 有什么作用? 事务传播行为用于解决两个被事务管理的方法互相调用问题
表现层:责任是管理页面跳转,页面数据获取和传递 业务层:管理逻辑、封装数据 --- 一个方法可能会调用多个dao,放在一个事务中,只要有一次操作失败,整体回滚,保证业务的完整性。 持久层:主要是和数据库打交道
业务层两个方法面临的事务问题:有些时候需要处于同一个事务,有些时候不能在同一个事务 !
事务的传播行为的7种类型:
主要分为三大类:
PROPAGATION_REQUIRED(默认值)
、PROPAGATION_SUPPORTS
、PROPAGATION_MANDATORY
支持当前事务, A调用B,如果A事务存在,A和B处于同一个事务 。 事务默认传播行为 REQUIRED。PROPAGATION_REQUIRES_NEW
、PROPAGATION_NOT_SUPPORTED
、PROPAGATION_NEVER
不会支持原来的事务 ,A调用B, 如果A事务存在, B肯定不会和A处于同一个事务。 常用的事务传播行为:PROPAGATION_REQUIRES_NEWPROPAGATION_NESTED
嵌套事务 ,只对DataSourceTransactionManager有效 ,底层使用JDBC的SavePoint机制,允许在同一个事务设置保存点,回滚保存点
嵌套事务的示例:
Connection conn=null;
try{
conn. setAutoCommit(false);
Statement stmt=conn.createStatement();
stmt.executeUpdate("update person set name='888' where id=1");
Savepoint savepoint=conn.setSavepoint();
try{
conn.createStatement().executelypdate("update person set name=222'where sid=2");
} catch(Exception ex){
conn.rollback(savepoint);
}
stmt.executeUpdate("delete from person where id=9");
conn.commit();
stmt.close();
} catch(Exception e){
conn.rollback();
} finally{
try{
if(null!=conn && lconn. isClosed0) conn.close();
} catch (SQLException e){e. printstackTrace()}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[面试题] REQUIRED、REQUIRES_NEW、NESTED 区分 REQUIRED 一个事务 REQUIRES_NEW 两个事务 NESTED 一个事务,事务可以设置保存点,回滚到保存点 ,选择提交或者回滚
TransactionStatus 事务状态
Oracle事务的结束: 1.手动commit 2.正常关闭或者create table等DDL语言—自动提交—commit 3.死机断电----自动回滚,然后自动提交。 事务的结束必须通过commit ,rollback 作用标记为回滚。
try {
操作
} catch (){
rollback
} finally {
commit
}
2
3
4
5
6
7
【三个事务超级接口对象之间的关系】
用户管理事务,需要先配置事务管理方案TransactionDefinition
、
管理事务通过TransactionManager
完成,TransactionManager
根据 TransactionDefinition
进行事务管理,在事务运行过程中,每个时间点都可以通过获取TransactionStatus
了解事务运行状态!
Spring事务管理两种方式
编程式的事务管理 通过TransactionTemplate手动管理事务 在实际应用中很少使用,原因是要修改原来的代码,加入事务管理代码(侵入性 )
使用XML配置声明式事务 Spring的声明式事务是通过AOP实现的(环绕通知) 开发中经常使用(代码侵入性最小)--推荐使用!
声明式事务管理案例-转账(xml、注解)
实体类
- Account
public class Account {
public int id;
public String name;
public float money;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public float getMoney() {
return money;
}
public void setMoney(float money) {
this.money = money;
}
@Override
public String toString() {
return "Account{" +
"id=" + id +
", name='" + name + '\'' +
", money=" + money +
'}';
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
编写dao和service
编写dao
- IAccountDao
public interface IAccountDao {
/**
* 转出的方法
* @param from :转出的人
* @param money :要转账金额
*/
void out(String from,Double money);
/**
* 转入的方法
* @param to :转入的人
* @param money :要转账金额
*/
void in(String to,Double money);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
- AccountDaoImpl
public class AccountDaoImpl extends JdbcDaoSupport implements IAccountDao {
/**
* 转出的方法
*
* @param from :转出的人
* @param money :要转账金额
*/
@Override
public void out(String from, Double money) {
String sql="update account set money=money-? where name=?";
super.getJdbcTemplate().update(sql,money,from);
}
/**
* 转入的方法
*
* @param to :转入的人
* @param money :要转账金额
*/
@Override
public void in(String to, Double money) {
String sql="update account set money=money+? where name=?";
super.getJdbcTemplate().update(sql,money,to);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
编写service
- IAccountService
public interface IAccountService {
/**
* 转账的方法
* @param from:从哪转出
* @param to:转入的人
* @param money:转账金额
*/
void transfer(String from,String to,Double money);
}
2
3
4
5
6
7
8
9
- AccountServiceImpl
public class AccountServiceImpl implements IAccountService {
private TransactionTemplate transactionTemplate;
/**
* 为了注入transactionTemplate
* @param transactionTemplate
*/
public void setTransactionTemplate(TransactionTemplate transactionTemplate) {
this.transactionTemplate = transactionTemplate;
}
private AccountDaoImpl accountDao;
/**
* 为了注入dao
* @param accountDao
*/
public void setAccountDao(AccountDaoImpl accountDao) {
this.accountDao = accountDao;
}
/**
* 转账的方法
*
* @param from :转出的人
* @param to :转入的人
* @param money :转账金额
*/
@Override
public void transfer(String from, String to, Double money) {
// 从A转入B,A的数量减少
accountDao.out(from,money);
// int i=1/0;
// B转入,B的数量增加
accountDao.in(to, money);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
配置步骤
第一步:配置事务管理器并注入数据源
<!--配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--注入数据源-->
<property name="dataSource" ref="dataSource"/>
</bean>
2
3
4
5
第二步:配置事务模板类
<!--事务管理的模板-->
<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
<!--name必须这样:因为要注入-->
<property name="transactionManager" ref="transactionManager"/>
</bean>
2
3
4
5
第三步:在业务层注入模板类:(模板类管理事务)
<!--配置业务层-->
<bean id="accountService" class="club.krislin.dao.impl.AccountServiceImpl">
<!--service 依赖 dao 层 -->
<property name="accountDao" ref="accountDao"/>
<!--在业务层注入事务的管理模板-->
<property name="transactionTemplate" ref="transactionTemplate"/>
</bean>
2
3
4
5
6
7
第四步:在业务层代码上使用模板
/**
* 转账的方法
*
* @param from :转出的人
* @param to :转入的人
* @param money :转账金额
*/
@Override
public void transfer(String from, String to, Double money) {
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
// 从A转入B,A的数量减少
accountDao.out(from,money);
int i=1/0;
// B转入,B的数量增加
accountDao.in(to, money);
}
});
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?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"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 配置业务层-->
<bean id="accountService" class="cn.edu.buaa.service.impl.AccountServiceImpl">
<property name="accountDao" ref="accountDao"></property>
</bean>
<!-- 配置账户的持久层-->
<bean id="accountDao" class="cn.edu.buaa.dao.impl.AccountDaoImpl">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置数据源-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://localhost:3306/exercise"></property>
<property name="username" value="root"></property>
<property name="password" value="1024"></property>
</bean>
<!-- spring中基于XML的声明式事务控制配置步骤
1、配置事务管理器
2、配置事务的通知
此时我们需要导入事务的约束 tx名称空间和约束,同时也需要aop的
使用tx:advice标签配置事务通知
属性:
id:给事务通知起一个唯一标识
transaction-manager:给事务通知提供一个事务管理器引用
3、配置AOP中的通用切入点表达式
4、建立事务通知和切入点表达式的对应关系
5、配置事务的属性
是在事务的通知tx:advice标签的内部
-->
<!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置事务的通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<!-- 配置事务的属性
isolation:用于指定事务的隔离级别。默认值是DEFAULT,表示使用数据库的默认隔离级别。
propagation:用于指定事务的传播行为。默认值是REQUIRED,表示一定会有事务,增删改的选择。查询方法可以选择SUPPORTS。
read-only:用于指定事务是否只读。只有查询方法才能设置为true。默认值是false,表示读写。
timeout:用于指定事务的超时时间,默认值是-1,表示永不超时。如果指定了数值,以秒为单位。
rollback-for:用于指定一个异常,当产生该异常时,事务回滚,产生其他异常时,事务不回滚。没有默认值。表示任何异常都回滚。
no-rollback-for:用于指定一个异常,当产生该异常时,事务不回滚,产生其他异常时事务回滚。没有默认值。表示任何异常都回滚。
-->
<tx:attributes>
<tx:method name="*" propagation="REQUIRED" read-only="false"/>
<tx:method name="find*" propagation="SUPPORTS" read-only="true"></tx:method>
</tx:attributes>
</tx:advice>
<!-- 配置aop-->
<aop:config>
<!-- 配置切入点表达式-->
<aop:pointcut id="pt1" expression="execution(* cn.edu.buaa.service.impl.*.*(..))"></aop:pointcut>
<!--建立切入点表达式和事务通知的对应关系 -->
<aop:advisor advice-ref="txAdvice" pointcut-ref="pt1"></aop:advisor>
</aop:config>
</beans>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76