spring切面编程
Spring的切面编程概述
AOP
AOP(Aspect Oriented Programming
),即面向切面编程,可以说是OOP(Object Oriented Programming,面向对象编程)的补充和完善。OOP引入封装、继承、多态等概念来建立一种对象层次结构,用于模拟公共行为的一个集合。不过OOP允许开发者定义纵向的关系,但并不适合定义横向的关系,例如 日志功能
。日志代码往往横向地散布在所有对象层次中,这种散布在各处的与具体业务无关的代码被称为横切(cross cutting),在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。
用log4j这个日志框架来看一看这种“大量代码的重复”的情况:
1.导包
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.12</version>
</dependency>
2.log配置文件,放在src/main/resources目录中,命名:log4j.properties。
### set log levels,stdout控制台,D要保存磁盘上 ###
log4j.rootLogger = debug , stdout , D
### 输出到控制台,%n 换行 [%l]包全名,有时不用它很简略 ###
log4j.appender.stdout = org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target = System.out
log4j.appender.stdout.layout = org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern = %n %d %p [%l] %m %n
### 输出到日志文件 ,保存到当前根目录下###
log4j.appender.D = org.apache.log4j.DailyRollingFileAppender
log4j.appender.D.File = ./log.log
log4j.appender.D.Append = true
## 只输出DEBUG级别以上的日志!!!
log4j.appender.D.Threshold = DEBUG
log4j.appender.D.layout = org.apache.log4j.PatternLayout
log4j.appender.D.layout.ConversionPattern = %n %d %p [%l] %m %n
3.测试类,Logger类位置 org.apache.log4j.Logger
public class AopTest {
private static Logger logger= Logger.getLogger(AopTest.class);
public static void main(String[] args) {
add(9, 11);
div(10, 2);
sub(18, 9);
}
public static int add(int i,int j) {
//log4j,这个日志框架,日志信息有5个级别:fatal,error,warn,info,debug,这里debug信息不会显示出来
logger.info("add方法运算之前!");
int rs=i+j;
logger.info("add方法运算之后,结果:"+rs);
return rs;
}
public static int div(int i,int j) {
//log4j,这个日志框架,日志信息有5个级别:fatal,error,warn,info,debug,这里debug信息不会显示出来
logger.info("div方法运算之前!");
int rs=i/j;
logger.info("div方法运算之后,结果:"+rs);
return rs;
}
public static int sub(int i,int j) {
//log4j,这个日志框架,日志信息有5个级别:fatal,error,warn,info,debug,这里debug信息不会显示出来
logger.info("sub方法运算之前!");
int rs=i-j;
logger.info("sub方法运算之后,结果:"+rs);
return rs;
}
}
-------------------------------------------------------------------------
2025-05-24 13:27:37,179 INFO [cn.ybzy.springdemo.aop.AopTest.add(AopTest.java:16)] add方法运算之前!
2025-05-24 13:27:37,183 INFO [cn.ybzy.springdemo.aop.AopTest.add(AopTest.java:18)] add方法运算之后,结果:20
2025-05-24 13:27:37,183 INFO [cn.ybzy.springdemo.aop.AopTest.div(AopTest.java:24)] div方法运算之前!
2025-05-24 13:27:37,183 INFO [cn.ybzy.springdemo.aop.AopTest.div(AopTest.java:26)] div方法运算之后,结果:5
2025-05-24 13:27:37,183 INFO [cn.ybzy.springdemo.aop.AopTest.sub(AopTest.java:32)] sub方法运算之前!
2025-05-24 13:27:37,184 INFO [cn.ybzy.springdemo.aop.AopTest.sub(AopTest.java:34)] sub方法运算之后,结果:9
我看到产生了大量的得重信息,有些不是我们要的。
AOP技术恰恰相反,它利用一种称为"横切"的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为"Aspect",即切面。所谓"切面",简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。
使用"横切"技术,AOP把软件系统分为两个部分:核心关注点和横切关注点
。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处基本相似,比如权限认证、日志、事务。AOP的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。
Spring的AOP就可以实现核心关注点和横切关注点的分离
Spring中AOP代理由Spring的IOC容器负责生成、管理,其依赖关系也由IOC容器负责管理。因此,AOP代理可以直接使用容器中的其它bean实例作为目标,这种关系可由IOC容器的依赖注入提供。Spring创建代理的规则为:
1、默认使用Java动态代理来创建AOP代理,这样就可以为任何接口实例创建代理了
2、当需要代理的类不是代理接口的时候,Spring会切换为使用CGLIB代理,也可强制使用CGLIB(CGLIB是一个强大的、高性能的代码生成库)
AOP编程其实是很简单的事情,纵观AOP编程,程序员只需要参与三个部分:
1、定义普通业务组件
2、定义切入点,一个切入点可能横切多个业务组件
3、定义增强处理,增强处理就是在AOP框架为普通业务组件织入的处理动作
所以进行AOP编程的关键就是定义切入点和定义增强处理,一旦定义了合适的切入点和增强处理,AOP框架将自动生成AOP代理,即:代理对象的方法=增强处理+被代理对象的方法。(我自己直白的理解就是偷梁换柱,换上去的柱子是加了料的)
示例:简单的写了核心业务类与切面类
简要概述:::
核心为业务实现类AopTestImpl.java,切面类LoggerAsept.java,两个重要的类。切面类
,定义切入点方法,用@Before或@After等注解,让它切入到核心业务类
中那个方法有前面,或后面执行目志追踪方法,打印日志信息。
步骤1:添加包
<dependency>
<groupId>aopalliance</groupId>
<artifactId>aopalliance</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.0.6.RELEASE</version>
</dependency>
步骤2:写Spring的配置文件,把注解扫面打开,写接口和实现类,有main方法是测试类
spring配置文件zhujieBean.xml
<context:component-scan base-package="cn.ybzy.springdemo"></context:component-scan>
<!-- 让切面类的方法上的注解起作用,未被切入的类生产一个代理类(子类),代理类中加了切面类的方法,说白了,这个代理类,在原来的类上就会改一些东西 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
核心业务代码:AopTest.java接口,AopTestImpl.java接口实现类
----------------------------------------------------------AopTest.java核心业务接口------------------------------------------------------------------------------
public interface AopTest {
public int add(int i,int j);
public int sub(int i,int j);
public int div(int i,int j);
}
----------------------------------------------------------AopTestImpl.java核心业务实现类------------------------------------------------------------------------------
@Component("aopTest")
public class AopTestImpl implements AopTest{
@Override
public int add(int i,int j) {
int rs=i+j;
return rs;
}
@Override
public int div(int i,int j) {
int rs=i/j;
return rs;
}
@Override
public int sub(int i,int j) {
int rs=i-j;
return rs;
}
}
步骤3:定义切面类,LoggerAspect
/*
* 日志切面,用它切面到AopTestImpl三个方法,打印日志
* 1.LoggerAspect要让spring 管理,放入IOC容器中,实例化之
* 2.告诉spring,这个类不是普通类,是一个切面,就是横切关注点的集合,与核心业务代码无关的代码, 如,日志,权限,事务代码
* 切入点注解:@Before(修饰符 返回类型 包全名.类.方法/*(参数列表)) ,告诉spring,要将它下面的方法,切入到哪个类的那个(些)方法的前面去执行
*/
@Aspect
@Component
public class LoggerAspect {
private Logger logger=Logger.getLogger(this.getClass()); //org.apache.log4j.Logger
//@Before("execution(public int cn.ybzy.springdemo.aop.AopTestImpl.add(int,int))")
@Before("execution(public int cn.ybzy.springdemo.aop.AopTestImpl.*(int,int))")
public void beforeMethod(JoinPoint joinPoint) {
List<Object> args=Arrays.asList(joinPoint.getArgs());
logger.info(joinPoint.getSignature().getName()+"方法运算之前!执行日志程序,这个方法的参数:"+args);
}
}
测试
public class RunTest {
public static void main(String[] args) {
ApplicationContext ct=new ClassPathXmlApplicationContext("zhujieBean.xml");
AopTest aopTest = (AopTest) ct.getBean("aopTest");
System.out.println("add:"+aopTest.add(1, 1));
System.out.println("sub:"+aopTest.sub(7, 1));
System.out.println("div:"+aopTest.add(10, 2));
}
}
-----------------------------------------------------------------------------
2025-05-26 11:51:00,694 INFO [cn.ybzy.springdemo.aop.LoggerAspect.beforeMethod(LoggerAspect.java:27)] add方法运算之前!执行日志程序,这个方法的参数:[1, 1]
add:2
2025-05-26 11:51:00,696 INFO [cn.ybzy.springdemo.aop.LoggerAspect.beforeMethod(LoggerAspect.java:27)] sub方法运算之前!执行日志程序,这个方法的参数:[7, 1]
sub:6
2025-05-26 11:51:00,696 INFO [cn.ybzy.springdemo.aop.LoggerAspect.beforeMethod(LoggerAspect.java:27)] add方法运算之前!执行日志程序,这个方法的参数:[10, 2]
div:12
一些通知的注解说明
@Before和它注解的这个方法在AOP里叫:通知(advice),这个我们除了@Before这个叫前置通知(在切入目标方法执行之前执行)外,还有:
@After后置通知(在目标方法执行之后执行)
@AfterReturning返回通知(在目标方法返回结果之后执行)
@AfterThrowing异常通知(在目标方法跑出异常之后执行)
@Around环绕通知(围绕着方法执行)
@Aspect
@Component
public class LoggerAspect {
private Logger logger=Logger.getLogger(this.getClass()); //org.apache.log4j.Logger
//公共切入点表达式,提出来放到一个方法
@Pointcut("execution(* cn.ybzy.springdemo.aop.*.*(..))")
public void commonExecution() {
}
@Before("commonExecution()")
public void beforeMethod(JoinPoint joinPoint) {
List<Object> args=Arrays.asList(joinPoint.getArgs());
logger.info(joinPoint.getSignature().getName()+"方法运算之前!执行日志程序,这个方法的参数:"+args);
}
}
AOP实现后置通知的问题–返回与异常
后置通知: 在切入点的目标方法执行后(无论有异常抛出没的),都会执行这个通知方法!
如果想要在通知方法里访问到目标方法返回的结果,可以用返回通知,返回通知:是在目标方法执行之后没有异常,并且返回结果后才执行通知方法:
//公共切入点表达式,提出来放到一个方法
@Pointcut("execution(* cn.ybzy.springdemo.aop.*.*(..))")
public void commonExecution() {
}
@Before("commonExecution()")
public void beforeMethod(JoinPoint joinPoint) {
List<Object> args=Arrays.asList(joinPoint.getArgs());
logger.info(joinPoint.getSignature().getName()+"方法运算之前!执行日志程序,这个方法的参数:"+args);
}
@After("commonExecution()")
public void afterMethod(JoinPoint joinPoint) {
List<Object> args=Arrays.asList(joinPoint.getArgs());
logger.info(joinPoint.getSignature().getName()+"方法运算之后!执行日志程序,这个方法的参数:"+args);
}
//返回通知
@AfterReturning(value="commonExecution()",returning="rs") //有多个参数时,value不能省
public void returningMethod(JoinPoint joinPoint,Object rs) {
List<Object> args=Arrays.asList(joinPoint.getArgs());
logger.info(joinPoint.getSignature().getName()+"方法运算之后!执行日志程序,这个方法的参数:"+args+"运算结果:"+ rs);
}
-----------------------------------------------------------------------------
2025-05-26 12:25:30,393 INFO add方法运算之前!执行日志程序,这个方法的参数:[1, 1]
2025-05-26 12:25:30,396 INFO add方法运算之后!执行日志程序,这个方法的参数:[1, 1]
2025-05-26 12:25:30,396 INFO add方法运算之后!执行日志程序,这个方法的参数:[1, 1]运算结果:2
add:2
2025-05-26 12:25:30,396 INFO sub方法运算之前!执行日志程序,这个方法的参数:[7, 1]
2025-05-26 12:25:30,397 INFO sub方法运算之后!执行日志程序,这个方法的参数:[7, 1]
2025-05-26 12:25:30,397 INFO sub方法运算之后!执行日志程序,这个方法的参数:[7, 1]运算结果:6
sub:6
2025-05-26 12:25:30,397 INFO div方法运算之前!执行日志程序,这个方法的参数:[10, 2]
2025-05-26 12:25:30,397 INFO div方法运算之后!执行日志程序,这个方法的参数:[10, 2]
2025-05-26 12:25:30,397 INFO div方法运算之后!执行日志程序,这个方法的参数:[10, 2]运算结果:5
div:5
异常通知:当目标对象抛出异常的时候执行通知方法,添加一个div除法方法
//异常通知,
@AfterThrowing(value="commonExecution()",throwing ="ex")
public void throwwingMethod(JoinPoint joinPoint,Exception ex) {
List<Object> args = Arrays.asList(joinPoint.getArgs());
logger.info(joinPoint.getSignature().getName()+"方法运算之后!发生了异常执行日志程序,这个方法的参数:"+args+"异常信息:"+ ex);
}
测试:
public class RunTest {
public static void main(String[] args) {
ApplicationContext ct=new ClassPathXmlApplicationContext("zhujieBean.xml");
AopTest aopTest = (AopTest) ct.getBean("aopTest");
System.out.println("add:"+aopTest.add(1, 1));
System.out.println("sub:"+aopTest.sub(7, 1));
System.out.println("div:"+aopTest.div(10, 0)); //会发生除数为0的异常发生
}
}
-----------------------------------------------------------------------------
2025-05-26 13:26:39,757 INFO div方法运算之后!发生了异常执行日志程序,这个方法的参数:[10, 0]异常信息:java.lang.ArithmeticException: / by zero
Exception in thread "main" java.lang.ArithmeticException: / by zero
环绕通知:
就是把前面4中通知全给整合在一起,环绕目标方法的所有通知的意味。
使用它必须要求:
1、必须要带参数ProceedingJoinPoint类型的参数,这个参数可以直接调用原来的目标方法。
2、环绕通知方法必须有返回值,这个反正值就是目标方法的返回值。
最后说一个问题,当同一个目标方法有多个切面的时候,哪个切面先执行,取决于在切面类上的注解@order(值小的先执行)
基于xml配置文件的AOP使用
前面的例子中,配置切面类,AopTest类还是用注解,我们主要看有切面的通知方法在xml里该怎么配,方法上的(通知)注解删除干净,Componet注解不要删除,省得在xml配bean。
Spring使用JdbcTemplate访问数据库
要和数据库打交道了,首先在原来的基础上添加jar包,建一个测试数据库pring5,里边新建两个表users,authorities,user_authority:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.0.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>5.0.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.0.6.RELEASE</version>
</dependency>
然后在spring的配置文件里,配置数据源和JdbcTemplate,然后需要jdbc.properties文件,还有测试类:
<!-- 配置c3p0,ComboPooledDataSource对象,它指向一个数据源 -->
<context:property-placeholder location="classpath:jdbc.properties"/>
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="user" value="${jdbc.user}"></property>
<property name="password" value="${jdbc.password}"></property>
<property name="jdbcUrl" value="${jdbc.url}"></property>
<property name="driverClass" value="${jdbc.driverClass}"></property>
<property name="initialPoolSize" value="${jdbc.initialPoolSize}"></property>
<property name="acquireIncrement" value="${jbc.acquireIncrement}"></property>
<property name="minPoolSize" value="${jdbc.minPoolSize}"></property>
<property name="maxPoolSize" value="${jdbc.maxPoolSize}"></property>
<property name="maxStatements" value="${jdbc.maxStatements}"></property>
<property name="maxStatementsPerConnection" value="${jdbc.maxStatementsPerConnection}"></property>
</bean>
<!--配置创建JdbcTemplate bean对象,引用的包部包类,用不了注角解配bean-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
测试类
//测试数据库连接,此时我们不做AOP测试,让log4j不输出通知信息,只有错误时输出错误信息,改一下log4j.properties
// log4j.appender.stdout.Target = System.out下面加上log4j.appender.stdout.Threshold = ERROR
@Test
public void dataSourcetest() throws SQLException{
ApplicationContext ct = new ClassPathXmlApplicationContext("dataSource.xml");
DataSource dataSource = (DataSource)ct.getBean("dataSource");
//System.out.println("数据库连接:"+dataSource.getConnection());
//利用spring jdbc模块访问数据库
JdbcTemplate jdbcTemplate=(JdbcTemplate) ct.getBean("jdbcTemplate");
//增,批量增
String sql ="INSERT INTO `users`(`id`,`username`,`password`) VALUES(?,?,?)";
//jdbcTemplate.update(sql, 4,"admin","admin123");
List<Object[]> args = new ArrayList<>();
/*args.add(new Object[] {5,"aaa","111"});
args.add(new Object[] {6,"bbb","222"});
args.add(new Object[] {7,"ccc","333"});
args.add(new Object[] {8,"ddd","444"});
args.add(new Object[] {9,"eee","555"});
jdbcTemplate.batchUpdate(sql,args);*/
//删除
String sql2="DELETE FROM `users` WHERE id=?";
//jdbcTemplate.update(sql2,4);
//改
String sql3="UPDATE `users` SET `username`=?,`password`=? WHERE id=?";
//jdbcTemplate.update(sql3,"lijing","liu",4);
//查
//1.查询一条数据记录,封装成一个User对象
String sqlc="SELECT `id`,`username`,`password` FROM `users` WHERE id=?";
RowMapper<User> rowMapper=new BeanPropertyRowMapper<>(User.class);
User user=jdbcTemplate.queryForObject(sqlc,rowMapper,1);
System.out.println(user);
//2.查询多个记录
String sqls = "SELECT `id`,`username`,`password` FROM `users` WHERE id > ?";
RowMapper<User> rowMapper2=new BeanPropertyRowMapper<>(User.class);
List<User> users=jdbcTemplate.query(sqls,rowMapper2,0);
System.out.println(users);
//3.查询数据,返回的数据,是一个数值,或某一个列的值
String sqlo="SELECT COUNT(id) FROM `users`";
//String sqlo="SELECT `username` FROM `users` WHERE id=?";
int count=jdbcTemplate.queryForObject(sqlo,Integer.class);
//String username=jdbcTemplate.queryForObject(sqlo,String.class,3);
System.out.println(count); //System.out.println(username);
}
在实际项目开发中怎么使用jdbcTemplate:
UserDaoImpl.java
@Repository("userDao")
public class UserDaoImpl extends BaseDao<User> implements UserDao{
@Autowired //因为在dataSource.xml配置文件中,配了bean,这里自动装备即可
private JdbcTemplate jdbcTemplate;
@Override
public User get(int id) {
String sql ="SELECT `id`,`username`,`password` FROM `users` WHERE id=?";
RowMapper<User> rowMapper=new BeanPropertyRowMapper<>(User.class);
return jdbcTemplate.queryForObject(sql, rowMapper,id);
}
}
dataSouce.xml----因为用了注解,要加入寻包功能
<context:component-scan base-package="cn.ybzy.springdemo"></context:component-scan>
测试:
@Test
public void dataSourcetest() throws SQLException{
ApplicationContext ct = new ClassPathXmlApplicationContext("dataSource.xml");
DataSource dataSource = (DataSource)ct.getBean("dataSource");
//项目中,利用dao层来实现获取记录,userDao这里不能用@Autowired自动装备,因为它没有把成员属性值具体化
UserDao userDao=(UserDao)ct.getBean("userDao");
User user2=userDao.get(1);
System.out.println("项目应用中用的获取记录:"+user2);
}
具名参数
具名参数: 我们在Hibernate用过,就是sql语句中的占位符可以不使用?而使用具体的名字,格式(:nameValue)
讲解Spring的事务的买衣服例子
事务:
把一些列的sql语句,组合成一个整体,要么所有的sql都执行成功,要么一句sql都不能被执行,这就是事务。
我们要讲Spring中怎么用的声明试事务,通过网上买衣服例子来讲:
假设在数据库里的内部逻辑:
衣服的信息表:衣服的编号,单价,库存数
用户的信息表:用户的编号,用户名字,用户的余额
业务逻辑:
用户点击立即购买按钮,然后付款,在业务上发生:
1、通过点击的按钮获取到购买的衣服的id,用户购买的数量和用户id
2、通过衣服id获取到衣服的单价,计算出购买衣服的总金额
3、从衣服库存数中减去用户购买的数量
4、从用户的余额中扣除购买衣服的金额
接下来,我就用代码把这个业务逻辑表示出来:
Customer,CustomerDao,CustomerDaoImpl
public class Customer {
private int id;
private String username;
private double balance; //余额
//get,set,无参构造方法,toSring()
}
-------------------------------------------------------------------------------------------------------
/*
* 消费者对象,有以下方法
* 1.getBalance(int uid) 获取顾客余额
* 2.updateBalance(int uid) 更新顾客余额 :编号,够买需要的钱
*/
public interface CustomerDao {
public double getBalance(int uid);
public void updateBalance(int uid,double amount);
}
-------------------------------------------------------------------------------------------------------
@Repository("customerDao") //注解配bean,dataSource.xml配置文件打开会找它
public class CustomerDaoImpl implements CustomerDao{
@Autowired //在dataSource.xml中已配了bean,这里自动装备即可
private JdbcTemplate jdbcTemplate;
@Override
public double getBalance(int uid) {
String sql = "SELECT `balance` FROM `user` WHERE id=?";
return jdbcTemplate.queryForObject(sql,Double.class,uid);
}
//修改顾客余额,如果余额不够,挽出异常
@Override
public void updateBalance(int uid,double amount) {
if(getBalance(uid)<amount) {
throw new RuntimeException("顾客余额不足");
}
String sql="UPDATE `user` SET `balance`=`balance`-? WHERE id=?";
jdbcTemplate.update(sql,amount,uid);
}
}
Clothes,ClothesDao,ClothesDaoImpl,ClothesService,ClothesServiceImpl
public class Clothes {
private int id;
private double price;
private int inventory; //库存
//get,set,无参构造方法,toSring()
}
-------------------------------------------------------------------------------------------------------
/*
* 衣服类
* 1.获取单价
* 2.获取库存
* 3.修改库存,衣服编号,和卖买出去的数量
*/
public interface ClothesDao {
public double getPrice(int cid);
public int getInventory(int cid);
public void updateInventory(int cid,int amount);
}
-------------------------------------------------------------------------------------------------------
@Repository("customerDao") //注解配bean,dataSource.xml配置文件打开会找它
public class CustomerDaoImpl implements CustomerDao{
@Autowired //在dataSource.xml中已配了bean,这里自动装备即可
private JdbcTemplate jdbcTemplate;
@Override
public double getBalance(int uid) {
String sql = "SELECT `balance` FROM `user` WHERE id=?";
return jdbcTemplate.queryForObject(sql,Double.class,uid);
}
//修改顾客余额,如果余额不够,挽出异常
@Override
public void updateBalance(int uid,double amount) {
if(getBalance(uid)<amount) {
throw new RuntimeException("顾客余额不足");
}
String sql="UPDATE `user` SET `balance`=`balance`-? WHERE id=?";
jdbcTemplate.update(sql,amount,uid);
}
}
-------------------------------------------------------------------------------------------------------
public interface ClothesService {
/*
* 采购衣服
*/
public void purchaseClothes(int uid,int cid,int amount);
}
-------------------------------------------------------------------------------------------------------
@Service("clothesService")
public class ClothesServiceImpl implements ClothesService {
@Autowired
private ClothesDao clothesDao; //ClothesDaoImpl由注解配了bean,@Repository("clothesDao")以后,配置文件执行自动装配
@Autowired //ClothesDaoImpl由注解配了bean,@Repository("customerDao")以后,配置文件执行自动装配
private CustomerDao customerDao;
/*
* 事务处理方法:有两个模型类Customer,Clothes,当顾客购买衣服时,
* 会执行多条sql语句:查询单价,查询用户余额,更新余额,更新库存
* 业务:要么同时执行,要么都不执行
*/
@Override
public void purchaseClothes(int uid, int cid, int amount) {
double price=clothesDao.getPrice(cid);
double sumPrice = price*amount;
clothesDao.updateInventory(cid,amount);
customerDao.updateBalance(uid, sumPrice);
}
}
dataSource.xml-----加IOC容器可查询的类范围 ,注解配bean的类的范围
<context:component-scan base-package="cn.ybzy.springdemo"></context:component-scan>
其它需求,用以上例子中的,如数据库连接池的配置,JdbcTemplate的配置
测试
测试数据库事务处理:有两个模型类Customer,Clothes,当顾客购买衣服时,会执行多条sql语句:查询单价,查询用户余额,更新余额,更新库存
- 初始化时,用户余额300.00,衣服有两种:1,120/件,2,180/件
- 当我连续三次执行(三次买1号服purchaseClothes(1, 1, 1);)后,库存为0,但我的余额为60,说明只买了2件,而库存又为0说明出了3件,这就产生了事务异常了。
@Test
public void shiWutest() {
ApplicationContext ctx = new ClassPathXmlApplicationContext("dataSource.xml");
ClothesService clothesService=(ClothesService)ctx.getBean("clothesService");
clothesService.purchaseClothes(1, 1, 1);
}
改决上面产生的问题,就是把这些执行代码放在一个事务中
配置声明试的Spring事务
dataSource.xml
<!-- 声明式事务的配置,配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 注解实现事务,配置文件这里开启spring的事务注解功能,要加tx命名空间 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
ClothesServiceImpl.java
@Transactional //加入事务当中
@Override
public void purchaseClothes(int uid, int cid, int amount) {
double price=clothesDao.getPrice(cid);
double sumPrice = price*amount;
clothesDao.updateInventory(cid,amount);
customerDao.updateBalance(uid, sumPrice);
}
@Transactional
对@Transactional
这个注解,进一步说明:
@Transactional有属性,先看第一属性propagation(传播):
什么时候,用这个属性呢?当一个有事务的方法调用另一个也有事务的方法的时候,就要看情况设置这个属性了,这个属性还要注意一个问题
:如果这两个有事务的方法,在同一个Service类里,不管传播属性设置的值是哪个,都只能会是在同一个事务中来处理,Spring不会分开成两个事务来处理!
看例子:为了区分,我又增加一个Service层的类,批量购买
MultiClothesService.java,MultiClothesServiceImpl.java
public interface MultiClothesService {
public void multiPurchaseClothes(int uid,int[] cids,int[] amount);
}
------------------------------------------------------------------------
/*
* 为了测试购买多件衣服测试用,当然了,可以放在同 一个服务层类中用,
* 为什么我要放一个类中,是为测试事务传播的不同处传播
* 在同一个Service类里,不管传播属性设置的值是哪个,都只能会是在同一个事务中来处理,Spring不会分开成两个事务来处理!
*/
@Service("multiClothesService")
public class MultiClothesServiceImpl implements MultiClothesService{
@Autowired
private ClothesService clothesService;
@Transactional
@Override
public void multiPurchaseClothes(int uid, int[] cids, int[] amount) {
for(int i=0;i<cids.length;i++) {
clothesService.purchaseClothes(uid, cids[i], amount[i]);
}
}
}
ClothesServiceImpl.java中加事务传播属性
@Service("clothesService")
public class ClothesServiceImpl implements ClothesService {
@Autowired
private ClothesDao clothesDao; //ClothesDaoImpl由注解配了bean,@Repository("clothesDao")以后,配置文件执行自动装配
@Autowired //ClothesDaoImpl由注解配了bean,@Repository("customerDao")以后,配置文件执行自动装配
private CustomerDao customerDao;
/*
* 事务处理方法:有两个模型类Customer,Clothes,当顾客购买衣服时,
* 会执行多条sql语句:查询单价,查询用户余额,更新余额,更新库存
* 业务:要么同时执行,要么都不执行
*/
@Transactional(propagation=Propagation.REQUIRED) //加入事务当中,REQUIRED表示该方法的调用者也加入该事务中
@Override
public void purchaseClothes(int uid, int cid, int amount) {
double price=clothesDao.getPrice(cid);
double sumPrice = price*amount;
clothesDao.updateInventory(cid,amount);
customerDao.updateBalance(uid, sumPrice);
}
}
测试
@Test
public void shiWutest() {
ApplicationContext ctx = new ClassPathXmlApplicationContext("dataSource.xml");
ClothesService clothesService=(ClothesService)ctx.getBean("clothesService");
MultiClothesService multiClothesService=(MultiClothesService)ctx.getBean("multiClothesService");
//clothesService.purchaseClothes(1, 1, 1);
//批量购买,下面是两款衣服各买一件,初始化时,用户余额300.00,衣服有两种:1,120/件,2,180/件
//而由于加入同一个事务,所以余额不足时,一件也不买,库存也不减少增的
multiClothesService.multiPurchaseClothes(1, new int[] {1,2},new int[] {1,1});
}
事务的其他属性
说到事务,就要说事务的隔离级别属性:
事务还有回滚,这里也有回滚的控制属性:
rollbackFor可以指定对遇到什么异常回滚事务:默认是所有的运行时异常都要回滚,这个属性,知道就行,一般就取默认值,不修改!
指定事务是只读事务:
这个属性比较重要:一般情况下在实际项目中,我们对查询的方法都把事务设置为只读,对增、改、删的方法才设置为可写事务,原因是Spring底层可以给我们做优化,增加对数据库的访问效率!
下一个属性是timeout属性,单位是秒,设置事务的超时实际,超过这个超时时间就强制事务回滚,这样子做为了防止一个事务占用数据库链接的时间过长:
基于xml配置文件的Spring事务(非注解方式)
......
<!-- 注解实现事务,配置文件这里开启spring的事务注解功能,要加tx命名空间 -->
<!-- <tx:annotation-driven transaction-manager="transactionManager"/>-->
<!-- 非注解事务配置 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!-- 指定以get,find,select开头的方法为只读属性,其它所有方法可读写 -->
<tx:method name="get*" read-only="true"/>
<tx:method name="find*" read-only="true"/>
<tx:method name="select*" read-only="true"/>
<tx:method name="*" read-only="false"/>
</tx:attributes>
</tx:advice>
<!-- 配置事务切入点,前面学切面编程时,就是切面一般有:事务,通知 -->
<aop:config>
<aop:pointcut expression="execution(* cn.ybzy.springdemo.service.*.*(..))" id="txPointcut"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
</aop:config>
</beans>