2.2.10 bean的作用域
(1)概念 在Spring中可以通过配置bean标签的scope属性来指定bean的作用域范围,各取值含义参加下表:
注意:如果是在WebApplicationContext环境下还会有另外两个作用域(但不常用):
2.2.11 bean的生命周期
(1)具体的生命周期过程
①实例化(bean对象的创建,调用无参构造器)
②依赖注入,给bean对象设置属性
③bean对象初始化之前操作(后置处理器的postProcessBeforeInitialization)
④初始化,需要通过bean的init-method属性指定初始化方法
⑤bean对象初始化之后操作(后置处理器的postProcessAfterInitialization)
⑥IOC容器关闭时销毁,需要通过bean的destroy-method属性指定销毁的方法
(2)配置bean
<!-- 使用init-method属性指定初始化方法 --> <!-- 使用destroy-method属性指定销毁方法 --> <bean class="com.atguigu.bean.User" scope="prototype" init-method="initMethod" destroy-method="destroyMethod"> <property name="id" value="1001"></property> <property name="username" value="admin"></property> <property name="password" value="123456"></property> <property name="age" value="23"></property> </bean>
(3)bean的后置处理器
bean的后置处理器会在生命周期的初始化前后添加额外的操作,需要实现BeanPostProcessor接口,
且配置到IOC容器中,需要注意的是,bean后置处理器不是单独针对某一个bean生效,而是针对IOC容
器中所有bean都会执行
(4)创建bean的后置处理器:
public class MyBeanProcessor implements BeanPostProcessor { @Override // 此方法在bean的生命周期初始化 之前 执行 public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { System.out.println("☆☆☆" + beanName + " = " + bean); return bean; } @Override // 此方法在bean的生命周期初始化 之后 执行 public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { System.out.println("★★★" + beanName + " = " + bean); return bean; } }
(5)在IOC容器中配置后置处理器
<!-- bean的后置处理器要放入IOC容器才能生效 --> <bean id="myBeanProcessor" class="com.atguigu.spring.process.MyBeanProcessor"/>
2.2.12 FactoryBean
(1)简介
FactoryBean是Spring提供的一种整合第三方框架的常用机制。和普通的bean不同,配置一个
FactoryBean类型的bean,在获取bean的时候得到的并不是class属性中配置的这个类的对象,而是
getObject()方法的返回值。通过这种机制,Spring可以帮我们把复杂组件创建的详细过程和繁琐细节都
屏蔽起来,只把最简洁的使用界面展示给我们。将来我们整合Mybatis时,Spring就是通过FactoryBean
机制来帮我们创建SqlSessionFactory对象的。
FactoryBean是一个接口,需要创建一个类去实现该接口,其中有三个方法:
①getObject():提供一个对象交给IOC容器管理
②getObjectType():设置所提供的对象的类型
③isSingleton():所提供的对象是否单例
当把FactoryBean的实现类配置为bean时,会把当前类中getObject()所返回的对象交给IOC容器管理
(2)创建类UserFactoryBean
public class UserFactoryBean implements FactoryBean<User> { @Override public User getObject() throws Exception { return new User(); } @Override public Class<?> getObjectType() { return User.class; } }
(3)配置bean
<bean id="user" class="com.atguigu.bean.UserFactoryBean"></bean>
(4)测试
@Test public void testUserFactoryBean(){ //获取IOC容器 ApplicationContext ac = new ClassPathXmlApplicationContext("springfactorybean.xml"); User user = (User) ac.getBean("user"); System.out.println(user); }
2.2.13 基于xml的自动装配
自动装配:根据指定的策略,在IOC容器中匹配某一个bean,自动为指定的bean中所依赖的类类型或接口类型
属性赋值
(1)场景模拟
①创建类UserController
public class UserController { private UserService userService; public void setUserService(UserService userService) { this.userService = userService; } public void saveUser(){ userService.saveUser(); } }
②创建接口UserService
public interface UserService { void saveUser(); } // 创建类UserServiceImpl实现接口UserService public class UserServiceImpl implements UserService { private UserDao userDao; public void setUserDao(UserDao userDao) { this.userDao = userDao; } @Override public void saveUser() { userDao.saveUser(); } }
③创建接口UserDao
public interface UserDao { void saveUser(); } // 创建类UserDaoImpl实现接口UserDao public class UserDaoImpl implements UserDao { @Override public void saveUser() { System.out.println("保存成功"); } }
(2)配置bean
可以通过bean标签中的autowire属性设置自动装配的策略
自动装配的策略:
①no,default:都表示不装配,即bean中的属性不会自动匹配某个bean,为属性赋值,此时属性使用默认值
②byType:根据要赋值的属性的类型,在IOC容器中匹配某个bean,为属性赋值
注意:
a>若通过类型没有找到任何一个类型匹配的bean,此时不装配,属性使用默认值
b>若通过类型找到了多个类型匹配的bean,此时会抛出异常:NoUniqueBeanDefinitionException
<bean id="userController" class="com.atguigu.autowire.xml.controller.UserController" autowire="byType"> </bean> <bean id="userService" class="com.atguigu.autowire.xml.service.impl.UserServiceImpl" autowire="byType"> </bean> <bean id="userDao" class="com.atguigu.autowire.xml.dao.impl.UserDaoImpl"></bean>
③byName
byName:将自动装配的属性的属性名,作为bean的id在IOC容器中匹配相对应的bean进行赋值
<bean id="userController" class="com.atguigu.autowire.xml.controller.UserController" autowire="byName"> </bean> <bean id="userService" class="com.atguigu.autowire.xml.service.impl.UserServiceImpl" autowire="byName"> </bean> <bean id="userServiceImpl" class="com.atguigu.autowire.xml.service.impl.UserServiceImpl" autowire="byName"> </bean> <bean id="userDao" class="com.atguigu.autowire.xml.dao.impl.UserDaoImpl"></bean> <bean id="userDaoImpl" class="com.atguigu.autowire.xml.dao.impl.UserDaoImpl"> </bean>
(3)测试
@Test public void testAutoWireByXML(){ ApplicationContext ac = new ClassPathXmlApplicationContext("autowirexml.xml"); UserController userController = ac.getBean(UserController.class); userController.saveUser(); }
2.3 基于注解管理bean
2.3.1 标记与扫描
(1)注解
①和 XML 配置文件一样,注解本身并不能执行,注解本身仅仅只是做一个标记,具体的功能是框架检测
到注解标记的位置,然后针对这个位置按照注解标记的功能来执行具体操作。
②本质上:所有一切的操作都是Java代码来完成的,XML和注解只是告诉框架中的Java代码如何执行。
③举例:元旦联欢会要布置教室,蓝色的地方贴上元旦快乐四个字,红色的地方贴上拉花,黄色的地方贴
上气球。班长做了所有标记,同学们来完成具体工作。墙上的标记相当于我们在代码中使用的注解,后面
同学们做的工作,相当于框架的具体操作。
(2)扫描组件
Spring 为了知道程序员在哪些地方标记了什么注解,就需要通过扫描的方式,来进行检测。然后根据注
解进行后续操作。
①情况一:最基本的扫描方式
<context:component-scan base-package="com.atguigu"></context:component-scan>
②情况二:指定要排除的组件
<context:component-scan base-package="com.atguigu"> <!-- context:exclude-filter标签:排除扫描 --> <!-- type:设置排除或包含的依据 type="annotation",根据注解排除,expression中设置要排除的注解的全类名 type="assignable",根据类型排除,expression中设置要排除的类型的全类名 --> <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/> <!--<context:exclude-filter type="assignable" expression="com.atguigu.controller.UserController"/>--> </context:component-scan>
③情况三:仅扫描指定组件
<context:component-scan base-package="com.atguigu" use-default-filters="false"> <!-- context:include-filter标签:包含扫描 --> <!-- use-default-filters属性:取值false表示关闭默认扫描规则 --> <!-- 此时必须设置use-default-filters="false",因为默认规则即扫描指定包下所有类 --> <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/> <!--<context:include-filter type="assignable" expression="com.atguigu.controller.UserController"/>--> </context:component-scan>
(3)标识组件的常用注解
①@Component:将类标识为普通组件
②@Controller:将类标识为控制层组件
③@Service:将类标识为业务层组件
④@Repository:将类标识为持久层组件
通过查看源码我们得知,@Controller、@Service、@Repository这三个注解只是在@Component注解
的基础上起了三个新的名字。
对于Spring使用IOC容器管理这些组件来说没有区别。所以@Controller、@Service、@Repository这
三个注解只是给开发人员看的,让我们能够便于分辨组件的作用。
注意:虽然它们本质上一样,但是为了代码的可读性,为了程序结构严谨我们肯定不能随便胡乱标记。
(4)组件所对应的bean的id
在我们使用XML方式管理bean的时候,每个bean都有一个唯一标识,便于在其他地方引用。现在使用
注解后,每个组件仍然应该有一个唯一标识。
①默认情况
类名首字母小写就是bean的id。例如:UserController类对应的bean的id就是userController。
②自定义bean的id
可通过标识组件的注解的value属性设置自定义的bean的id
@Service("userService")//默认为userServiceImpl public class UserServiceImpl implementsUserService {}
2.3.2 基于注解的自动装配
(1)@Autowired注解
在成员变量上直接标记@Autowired注解即可完成自动装配,不需要提供setXxx()方法。以后我们在项
目中的正式用法就是这样。
@Controller public class UserController { @Autowired private UserService userService; public void saveUser(){ userService.saveUser(); } }
注意:@Autowired注解也可以标记在构造器和set方法上
①@Autowired注解标记在构造器上
@Controller public class UserController { private UserService userService; @Autowired public UserController(UserService userService){ this.userService = userService; } public void saveUser(){ userService.saveUser(); } }
②@Autowired注解标记在set方法上
@Controller public class UserController { private UserService userService; @Autowired public void setUserService(UserService userService){ this.userService = userService; } }
(2)@Autowired工作流程
补充:如果和所需类型匹配的bean不止一个
①没有@Qualifier注解:根据@Autowired标记位置成员变量的变量名作为bean的id进行匹配
能够找到:执行装配;找不到:装配失败
②使用@Qualifier注解:根据@Qualifier注解中指定的名称作为bean的id进行匹配。
能够找到:执行装配;找不到:装配失败
@Controller public class UserController { @Autowired @Qualifier("userServiceImpl") private UserService userService; public void saveUser(){ userService.saveUser(); } }
3.AOP
3.1 AOP概念及相关术语
3.1.1 概述
AOP,即面向切面编程,它是面向对象编程的一种补充和完善,它通过预编译方式和运行期动态代理的方式实现在不修改源代码的情况下给程序动态统一添加额外功能。它将公共逻辑(比如事务管理、日志、缓存等)封装成切面,跟业务代码进行分离。
3.1.2 相关术语
(1)横切关注点
①从每个方法中抽取出来的同一类非核心业务。在同一个项目中,我们可以使用多个横切关注点对相关方
法进行多个不同方面的增强。
②这个概念不是语法层面天然存在的,而是根据附加功能的逻辑上的需要:有十个附加功能,就有十个横
切关注点。
(2)通知
每一个横切关注点上要做的事情都需要写一个方法来实现,这样的方法就叫通知方法。
①前置通知:在被代理的目标方法前执行
②返回通知:在被代理的目标方法返回值之后执行
③异常通知:在被代理的目标方法的catch子句中执行
④后置通知:在被代理的目标方法的finally子句中执行
⑤环绕通知:使用try…catch…finally结构围绕整个被代理的目标方法,包括上面四种通知对应的所有位置
(3)切面
封装通知方法的类
(4)目标
被代理的目标对象。
(5)代理
向目标对象应用通知之后创建的代理对象。
(6)连接点
①这也是一个纯逻辑概念,不是语法定义的。
②把方法排成一排,每一个横切位置看成x轴方向,把方法从上到下执行的顺序看成y轴,x轴和y轴的交叉点
就是连接点。就是我们抽取横切关注点的位置。
(7)切入点
①定位连接点的方式。
②每个类的方法中都包含多个连接点,所以连接点是类中客观存在的事物(从逻辑上来说)。
③如果把连接点看作数据库中的记录,那么切入点就是查询记录的 SQL 语句。
④Spring 的 AOP 技术可以通过切入点定位到特定的连接点。
⑤切点通过 org.springframework.aop.Pointcut 接口进行描述,它使用类和方法作为连接点的查询条件。
3.3.3、作用
(1)简化代码:把方法中固定位置的重复的代码抽取出来,让被抽取的方法更专注于自己的核心功能,
提高内聚性。
(2)代码增强:把特定的功能封装到切面类中,看哪里有需要,就往上套,被套用了切面逻辑的方法就
被切面给增强了。
3.4 基于注解的AOP
3.4.1 技术说明
(1)动态代理(InvocationHandler):JDK原生的实现方式,需要被代理的目标类必须实现接口。因
为这个技术要求代理对象和目标对象实现同样的接口(兄弟两个拜把子模式)。
(2)cglib:通过继承被代理的目标类(认干爹模式)实现代理,所以不需要目标类实现接口。
(3)AspectJ:本质上是静态代理,将代理逻辑“织入”被代理的目标类编译得到的字节码文件,所以最
终效果是动态的。weaver就是织入器。Spring只是借用了AspectJ中的注解。
3.4.2 准备工作
(1)添加依赖(在IOC所需依赖基础上再加入下面依赖即可)
<!-- spring-aspects会帮我们传递过来aspectjweaver --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>5.3.1</version> </dependency>
(2)准备被代理的目标资源
①接口:
public interface Calculator { int add(int i, int j); int div(int i, int j); }
②实现类:
@Component public class CalculatorPureImpl implements Calculator { @Override public int add(int i, int j) { int result = i + j; System.out.println("方法内部 result = " + result); return result; } @Override public int div(int i, int j) { int result = i / j; System.out.println("方法内部 result = " + result); return result; } }
3.4.3 创建切面类并配置
@Aspect//表示该类是一个切面类 @Component// 保证这个切面类能够放入IOC容器 //在切面中,需要通过指定的注解将方法标识为通知方法 public class LogAspect { // 第一个*表示任意的访问修饰符和返回值类型 // 第二个*表示类中任意的方法 // .. 表示任意的参数列表 @Before("execution(* com.zqg.spring.CalculatorImpl.*(int,int))") public void beforeAdviceMethod(JoinPoint joinPoint) { Signature signature = joinPoint.getSignature();//获取连接点所对应方法的签名信息 String args = Arrays.toString(joinPoint.getArgs());//获取连接点所对应方法的参数 System.out.println("前置通知" + signature.getName() + args); }
在spring的配置文件中配置:
<!-- AOP的主要事项: 切面类和目标类都需要交给IOC容器管理 切面类必须通过@Aspect注解标识为一个切面 --> <context:component-scan base-package="com.zqg.spring"></context:component-scan> <!-- 开启基于注解的AOP --> <aop:aspectj-autoproxy/>
3.4.4 各种通知
①前置通知:使用@Before注解标识,在被代理的目标方法前执行
②返回通知:使用@AfterReturning注解标识,在被代理的目标方法成功结束后执行(寿终正寝)
③异常通知:使用@AfterThrowing注解标识,在被代理的目标方法异常结束后执行(死于非命)
④后置通知:使用@After注解标识,在被代理的目标方法最终结束后执行(盖棺定论)
⑤环绕通知:使用@Around注解标识,使用try…catch…finally结构围绕整个被代理的目标方法,包
括上面四种通知对应的所有位置
(1)各种通知的执行顺序:
1)Spring版本5.3.x以前:
①前置通知
②目标操作
③后置通知
④返回通知或异常通知
2)Spring版本5.3.x以后:
①前置通知
②目标操作
③返回通知或异常通知
④后置通知
3.4.5 切入点表达式语法
(1)用*号代替“权限修饰符”和“返回值”部分表示“权限修饰符”和“返回值”不限 (2)在包名的部分,一个“*”号只能代表包的层次结构中的一层,表示这一层是任意的。 例如:*.Hello匹配com.Hello,不匹配com.spring.Hello (3)在包名的部分,使用“*..”表示包名任意、包的层次深度任意 (4)在类名的部分,类名部分整体用*号代替,表示类名任意 (5)在类名的部分,可以使用*号代替类名的一部分 例如:*Service匹配所有名称以Service结尾的类或接口 (6)在方法名部分,可以使用*号表示方法名任意 (7)在方法名部分,可以使用*号代替方法名的一部分 例如:*Operation匹配所有方法名以Operation结尾的方法 (8)在方法参数列表部分,使用(..)表示参数列表任意 (9)在方法参数列表部分,使用(int,..)表示参数列表以一个int类型的参数开头 (10)在方法参数列表部分,基本数据类型和对应的包装类型是不一样的 切入点表达式中使用 int 和实际方法中 Integer 是不匹配的 (11)在方法返回值部分,如果想要明确指定一个返回值类型,那么必须同时写明权限修饰符 例如:execution(public int ..Service.*(.., int)) 正确 例如:execution(* int ..Service.*(.., int)) 错误
3.4.6 重用切入点表达式
(1)声明
@Pointcut("execution(* com.aop.annotation.*.*(..))") public void pointCut(){}
(2)在同一个切面中使用
@Before("pointCut()") public void beforeMethod(JoinPoint joinPoint){ String methodName = joinPoint.getSignature().getName(); String args = Arrays.toString(joinPoint.getArgs()); System.out.println("Logger-->前置通知,方法名:"+methodName+",参数:"+args); }
(3)在不同切面中使用
@Before("com.spring.aop.CommonPointCut.pointCut()") public void beforeMethod(JoinPoint joinPoint){ String methodName = joinPoint.getSignature().getName(); String args = Arrays.toString(joinPoint.getArgs()); System.out.println("Logger-->前置通知,方法名:"+methodName+",参数:"+args); }
3.4.7 获取通知的相关信息
(1)获取连接点信息
获取连接点信息可以在通知方法的参数位置设置JoinPoint类型的形参
@Before("execution(public int com.spring.aop.annotation.CalculatorImpl.*(..))") public void beforeMethod(JoinPoint joinPoint){ //获取连接点的签名信息 String methodName = joinPoint.getSignature().getName(); //获取目标方法到的实参信息 String args = Arrays.toString(joinPoint.getArgs()); System.out.println("Logger-->前置通知,方法名:"+methodName+",参数:"+args); }
(2)获取目标方法的返回值
@AfterReturning中的属性returning,用来将通知方法的某个形参,接收目标方法的返回值
@AfterReturning(value = "execution(* com.atguigu.aop.annotation.CalculatorImpl.* (..))", returning = "result") public void afterReturningMethod(JoinPoint joinPoint, Object result){ String methodName = joinPoint.getSignature().getName(); System.out.println("Logger-->返回通知,方法名:"+methodName+",结果:"+result); }
(3)获取目标方法的异常
@AfterThrowing中的属性throwing,用来将通知方法的某个形参,接收目标方法的异常 @AfterThrowing(value = "execution(* com.spring.aop.annotation.CalculatorImpl.* (..))", throwing = "ex") public void afterThrowingMethod(JoinPoint joinPoint, Throwable ex){ String methodName = joinPoint.getSignature().getName(); System.out.println("Logger-->异常通知,方法名:"+methodName+",异常:"+ex); }
3.4.8 环绕通知
@Around("execution(* com.spring.aop.annotation.CalculatorImpl.*(..))") public Object aroundMethod(ProceedingJoinPoint joinPoint){ String methodName = joinPoint.getSignature().getName(); String args = Arrays.toString(joinPoint.getArgs()); Object result = null; try { System.out.println("环绕通知-->前置通知位置"); //表示目标对象方法的执行,目标方法的返回值一定要返回给外界调用者 result = joinPoint.proceed(); System.out.println("环绕通知-->返回通知位置"); } catch (Throwable throwable) { throwable.printStackTrace(); System.out.println("环绕通知-->异常通知位置"); } finally { System.out.println("环绕通知-->后置通知位置"); } return result; }
3.4.9 切面的优先级
(1)相同目标方法上同时存在多个切面时,切面的优先级控制切面的内外嵌套顺序。
①优先级高的切面:外面
②优先级低的切面:里面
(2)使用@Order注解可以控制切面的优先级:
①@Order(较小的数):优先级高
②@Order(较大的数):优先级低
3.5 基于XML的AOP(了解)
<context:component-scan base-package="com.atguigu.aop.xml"></context:componentscan> <aop:config> <!--配置切面类--> <aop:aspect ref="loggerAspect"> <aop:pointcut id="pointCut" expression="execution(*com.spring.aop.xml.CalculatorImpl.*(..))"/> <aop:before method="beforeMethod" pointcut-ref="pointCut"></aop:before> <aop:after method="afterMethod" pointcut-ref="pointCut"></aop:after> <aop:after-returning method="afterReturningMethod" returning="result" pointcut-ref="pointCut"></aop:after-returning> <aop:after-throwing method="afterThrowingMethod" throwing="ex" pointcutref="pointCut"> </aop:after-throwing> <aop:around method="aroundMethod" pointcut-ref="pointCut"></aop:around> </aop:aspect> <aop:aspect ref="validateAspect" order="1"> <aop:before method="validateBeforeMethod" pointcut-ref="pointCut"></aop:before> </aop:aspect> </aop:config>
4 声明式事务
4.2 声明式事务概念
4.2.1 编程式事务
(2)事务功能的相关操作全部通过自己编写代码来实现
Connection conn = ...; try { // 开启事务:关闭事务的自动提交 conn.setAutoCommit(false); // 核心操作 // 提交事务 conn.commit(); }catch(Exception e){ // 回滚事务 conn.rollBack(); }finally{ // 释放数据库连接 conn.close(); }
(2)编程式的实现方式存在缺陷:
①细节没有被屏蔽:具体操作过程中,所有细节都需要程序员自己来完成,比较繁琐。
②代码复用性不高:如果没有有效抽取出来,每次实现功能都需要自己编写代码,代码就没有得到复用。
4.2.2、声明式事务
(1)既然事务控制的代码有规律可循,代码的结构基本是确定的,所以框架就可以将固定模式的代码抽取出
来,进行相关的封装。封装起来后,我们只需要在配置文件中进行简单的配置即可完成操作。
①好处1:提高开发效率
②好处2:消除了冗余的代码
③好处3:框架会综合考虑相关领域中在实际开发环境下有可能遇到的各种问题,进行了健壮性、性能等各个
方面的优化
(2)所以,我们可以总结下面两个概念:
①编程式:自己写代码实现功能
②声明式:通过配置让框架实现功能
4.3 基于注解的声明式事务
4.3.1 加入事务
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> </bean> <!-- 开启事务的注解驱动: 通过注解@Transactional所标识的方法或标识的类中所有的方法,都会被事务管理器管理事务 --> <!-- transaction-manager属性的默认值是transactionManager,如果事务管理器bean的id正好就 是这个默认值,则可以省略这个属性 --> <tx:annotation-driven transaction-manager="transactionManager" />
注意:导入的名称空间需要 tx 结尾的那个
4.3.1 @Transactional注解标识的位置
(1)@Transactional标识在方法上,只会影响该方法
(2)@Transactional标识的类上,会影响类中所有的方法
4.3.2 事务属性:只读
(1)介绍
对一个查询操作来说,如果我们把它设置成只读,就能够明确告诉数据库,这个操作不涉及写操作。这
样数据库就能够针对查询操作来进行优化。
(2)使用方式
@Transactional(readOnly = true) public void buyBook(Integer bookId, Integer userId) { //查询图书的价格 Integer price = bookDao.getPriceByBookId(bookId); //更新图书的库存 bookDao.updateStock(bookId); //更新用户的余额 bookDao.updateBalance(userId, price); }
(3)注意
对增删改操作设置只读会抛出下面异常:
Caused by: java.sql.SQLException: Connection is read-only. Queries leading to data modification
are not allowed
4.3.3 事务属性:超时
(1)介绍
事务在执行过程中,有可能因为遇到某些问题,导致程序卡住,从而长时间占用数据库资源。而长时间
占用资源,大概率是因为程序运行出现了问题(可能是Java程序或MySQL数据库或网络连接等等)。
此时这个很可能出问题的程序应该被回滚,撤销它已做的操作,事务结束,把资源让出来,让其他正常
程序可以执行。
(2)使用方式
@Transactional(timeout = 3) public void buyBook(Integer bookId, Integer userId) { try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); } //查询图书的价格 Integer price = bookDao.getPriceByBookId(bookId); //更新图书的库存 bookDao.updateStock(bookId); //更新用户的余额 bookDao.updateBalance(userId, price); }
4.3.4 事务属性:回滚策略
(1)介绍
声明式事务默认只针对运行时异常回滚,编译时异常不回滚。可以通过@Transactional中相关属性设置回滚策略
①rollbackFor属性:需要设置一个Class类型的对象
②rollbackForClassName属性:需要设置一个字符串类型的全类名
③noRollbackFor属性:需要设置一个Class类型的对象
④rollbackFor属性:需要设置一个字符串类型的全类名
(2)使用方式
@Transactional(noRollbackFor = ArithmeticException.class) public void buyBook(Integer bookId, Integer userId) { //查询图书的价格 Integer price = bookDao.getPriceByBookId(bookId); //更新图书的库存 bookDao.updateStock(bookId); //更新用户的余额 bookDao.updateBalance(userId, price); System.out.println(1/0); }
(3)观察结果
虽然购买图书功能中出现了数学运算异常(ArithmeticException),但是我们设置的回滚策略是,当
出现ArithmeticException不发生回滚,因此购买图书的操作正常执行
4.4.5 事务属性:事务隔离级别
数据库系统必须具有隔离并发运行各个事务的能力,使它们不会相互影响,避免各种并发问题。一个事
务与其他事务隔离的程度称为隔离级别。SQL标准中规定了多种事务隔离级别,不同隔离级别对应不同
的干扰程度,隔离级别越高,数据一致性就越好,但并发性越弱。
@Transactional(isolation = Isolation.DEFAULT)//使用数据库默认的隔离级别 @Transactional(isolation = Isolation.READ_UNCOMMITTED)//读未提交 @Transactional(isolation = Isolation.READ_COMMITTED)//读已提交 @Transactional(isolation = Isolation.REPEATABLE_READ)//可重复读 @Transactional(isolation = Isolation.SERIALIZABLE)//串行化