spring-切面编程-JDBC-事务

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语句:查询单价,查询用户余额,更新余额,更新库存

  1. 初始化时,用户余额300.00,衣服有两种:1,120/件,2,180/件
  2. 当我连续三次执行(三次买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>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

qq_33406021

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值