重构JavaEE三层结构
面向接口编程
使用者只看接口,不管实现类,实现类交给容器工厂去创建
使用properties或者xml配置文件指定类的全路径,防止修改源代码
IOC/DI
控制反转是一种设计思想,就是将原本在程序中手动创建对象的控制权,交由Spring框架来管理
将对象之间的相互依赖关系交给IoC容器来管理,并由IoC容器完成对象的注入
IoC容器就像是一个工厂一样,当我们需要创建一个对象的时候,只需要配置好配置文件/注解即可,完全不用考虑对象是如何被创建出来的
控制反转 (Inversion Of Control)和依赖注入(Dependency Injection)是同一行为的两种表达,只是描述的角度不同而已
控制反转的描述: IoC/DI容器反过来控制应用程序,控制应用程序所需要的外部资源
依赖注入的描述: 应用程序依赖IoC/DI容器,依赖它注入所需要的外部资源
具体的讲:当某个角色需要另外一个角色协助的时候,在传统的程序设计过程中,通常由调用者来创建被调用者的实例。但在 spring 中 创建被调用者的工作不再由调用者来完成,因此称为控制反转。创建被调用者的工作由 spring 来完成,然后注入调用者,因此也称为依赖注入。 IOC 侧重于原理,DI 侧重于实现。
依赖倒置原则/面向接口编程:高层模块不应该依赖低层模块,二者都应该依赖其抽象;抽象不应该依赖细节;细节应该依赖抽象。
Spring容器中的Bean
创建Bean的三种方式
1、使用构造器创建实例
根据是否采用构造注入来选择指定的构造器创建对象
2、使用静态工厂方法创建
<bean…/>需要指定以下属性
-
class:该属性的值为静态工厂类的全类名
-
factory-method:该属性指定静态工厂方法来生产Bean实例
-
如果静态工厂方法需要参数,则使用<constructor-arg…/>元素指定静态工厂方法的参数
3、使用实例工厂方法创建
<bean…/>需要指定以下属性
-
factory-bean:工厂Bean的id
-
factory-method:指定实例工厂的工厂方法
-
如果工厂方法需要参数,则使用<constructor-arg…/>元素指定工厂方法的参数
依赖注入方式
-
构造器注入
配置<constructor-arg…/>时可指定一个index属性,用于指定该参数值将作为第几个构造参数值,type属性指定value值要转化为哪种数据类型
<bean id="role" class="com.hzb.model.Role"> <constructor-arg name="name" value=" 张三 " index="0"></constructor-arg> <constructor-arg name="age" value="18" index="1"></constructor-arg> <constructor-arg name="birthday" ref="now" index="2"></constructor-arg> </bean>
-
使用set方法注入
<bean id="role" class="com.hzb.model.Role"> <property name="name" value="test"></property> <property name="age" value="21"></property> <property name="birthday" ref="now"></property> </bean>
Bean的作用域
bean 默认是单例的,可以使用 scope 配置作用域
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
spring支持6种作用域,常用的是:singleton,prototype(每次获取都创建一个新的 bean)
XML配置
设置普通属性值:<property value.../>
配置合作者Bean:<property ref.../>
使用自动装配注入合作者Bean
- 指定<bean…/>的autowire属性:no、byName、byType、constructor、autodetect
装配集合
<!-- 给数组注入数据 -->
<property name="myArray">
<array>
<value>AAA</value>
<value>BBB</value>
<value>CCC</value>
</array>
</property>
<!-- 注入 list 集合数据 -->
<property name="myList">
<list>
<value>AAA</value>
<value>BBB</value>
<value>CCC</value>
</list>
</property>
<!-- 注入 set 集合数据 -->
<property name="mySet">
<set>
<value>AAA</value>
<value>BBB</value>
<value>CCC</value>
</set>
</property>
<!-- 注入 map 数据 -->
<property name="myMap">
<map>
<entry key="testA" value="aaa"></entry>
<entry key="testB">
<value>bbb</value>
</entry>
</map>
</property>
<!-- 注入 properties 数据 -->
<property name="myProps">
<props>
<prop key="testA">aaa</prop>
<prop key="testB">bbb</prop>
</props>
</property>
基于XML Schema的简化配置方式
1、使用p:命名空间(用于简化set方式注入)
<bean id="accountService"
class="com.hzb.service.impl.AccountServiceImpl4"
p:name="test" p:age="21" p:birthday-ref="now"/>
2、使用c:命名空间(用于简化构造注入)
c:构造器参数名="值"或c:构造器参数名-ref=“其他Bean的id”
c:_N=“值”(N代表第几个构造器参数)
<bean class="spring.bean.Source" id="source" c_0="apple" c_1="big" c_2="little"/>
3、使用util:命名空间
<util:list id="list">
<ref bean="role1"/>
<ref bean="role2"/>
</util:list>
<util:map id="map">
<entry key="" value-ref=""/>
<entry key="" value-ref=""/>
</util:map>
xml和注解混合使用
@ImportResource
在使用注解的配置类上,可以使用@ImportResource引入Spring的配置文件,让配置文件里面的内容生效
抽象Bean与子Bean
抽象Bean的<bean…/>指定abstract=“true”
子Bean的<bean…/>的parent属性指定父Bean的id
容器中的工厂Bean
实现FactoryBean接口
使用FactoryBean向容器中注册bean
- 当配置文件中标签的class属性配置的实现类是FactoryBean时,通过 getBean()方法返回的不是FactoryBean本身,而是FactoryBean#getObject()方法所返回的对象,相当于FactoryBean#getObject()代理了getBean()方法
- 通过boolean isSingleton()方法的返回值来决定FactoryBean创建的bean实例的作用域是singleton还是prototype
- 如果要从容器中获取FactoryBean对象本身,只需要在获取工厂Bean本身时,在id前面加上&符号即可
获取Bean本身的id
实现BeanNameAware接口,Spring容器调用该接口中的方法时,把部署该Bean的id属性作为参数传入该方法
强制初始化Bean
指定<bean…/>的depends-on属性,该属性可以在初始化该bean之前,强制初始化指定的bean
协调作用域不同步的Bean
Spring容器在初始化singleton作用域的Bean之前,先创建被依赖的prototype bean,然后才初始化singleton bean,并将prototype bean注入singleton bean,这会导致以后无论何时通过singleton bean去访问prototype bean时,得到的永远是最初那个prototype bean
解决办法:
-
将调用者Bean的实现类定义为抽象类,并定义一个抽象方法来获取被依赖的Bean
-
在<bean…/>中添加<lookuo-method name=“获取被依赖的Bean的抽象方法名” bean=“prototype bean的id”…/>子元素让Spring为调用者Bean的实现类实现指定的抽象方法
容器中Bean的生命周期
初始化——>依赖注入——>setBeanName方法(接口BeanNameAware)——>setBeanFactory方法(接口BeanFactoryAware)——>
setApplicationContext(接口ApplicationContextAware)——>
postProcessBeforeInitialization方法(BeanPostProcessor的预初始化方法,注意:它是对所有Bean而言的)——>
afterPropertiesSet方法(接口InitializingBean)——>自定义初始化方法(除了XML配置init-method外,也可以用注解@PostConstruct标注方法)——>
postProcessorAfterInitialization方法(BeanPostProcessor的后初始化方法,注意:它是对所有Bean而言的)——>
生存期——>destroy方法(接口DisposableBean)——>自定义销毁方法(除了XML配置destroy-method外,也可以用注解@PreDestroy标注方法)
public class JuiceMaker implements BeanNameAware, BeanFactoryAware,ApplicationContextAware, InitializingBean, DisposableBean {
private String shop;
private Source source;
public String getShop() {
return shop;
}
public void setShop(String shop) {
this.shop = shop;
}
public Source getSource() {
return source;
}
public void setSource(Source source) {
this.source = source;
}
public String makeJuice(){
return "这是由"+shop+"提供的"+source.toString();
}
@PostConstruct
public void init(){
System.out.println(this.getClass().getSimpleName()+"执行自定义init()");
}
@PreDestroy
public void myDestroy(){
System.out.println(this.getClass().getSimpleName()+"执行自定义destroy()");
}
private BeanFactory beanFactory;
private ApplicationContext applicationContext;
private String beanName;
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
System.out.println(beanFactory);
System.out.println(this.getClass().getSimpleName()+"执行setBeanFactory(BeanFactory beanFactory) ");
}
@Override
public void setBeanName(String name) {
this.beanName = name;
System.out.println(beanName);
System.out.println(this.getClass().getSimpleName()+"执行setBeanName ");
}
@Override
public void destroy() throws Exception {
System.out.println(this.getClass().getSimpleName()+"执行destroy() ");
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println(this.getClass().getSimpleName()+"执行afterPropertiesSet()");
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
System.out.println(applicationContext);
System.out.println(this.getClass().getSimpleName()+"执行setApplicationContext");
}
}
bean后置处理器
public class BeanPostProcessorImpl implements BeanPostProcessor {
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println(bean.getClass().getSimpleName()+"结束实例化");
return bean;
}
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println(bean.getClass().getSimpleName()+"开始实例化");
return bean;
}
}
配置
<context:component-scan base-package="spring.bean"></context:component-scan>
<bean class="spring.bean.JuiceMaker" id="juiceMaker" init-method="init" destroy-method="myDestroy">
<property name="source" ref="source"/>
<property name="shop" value="喜茶"/>
</bean>
<bean class="spring.bean.Source" id="source">
<property name="fruit" value="apple"></property>
<property name="size" value="big"></property>
<property name="sugar" value="little"></property>
</bean>
<bean class="spring.bean.BeanProcessorImpl"></bean>
Spring容器
BeanFactory
BeanFactory 只是对 IOC 容器最基本行为作了定义,而不关心 Bean 是怎样定义和加载的。 定义了一些 getBean、containsBean、isSingleton、isPrototype 等基本容器方法
ApplicationContext
和 BeanFactory 类似,它可以加载配置文件中定义的 bean,将所有的 bean 集中在一起,当有请求的时候分配 bean。 另外,它增加了企业所需要的功能:
-
当系统创建ApplicationContext容器时,默认会预初始化所有的singleton Bean(可以为<bean…/>元素指定lazy-init="true"阻止容器预初始化Bean)
-
继承了 MessageSource 接口,提供国际化支持(当程序创建ApplicationContext容器时,Spring自动查找配置文件中名为messageSource的bean实例)
-
资源访问
-
事件机制(Spring的事件框架有两个重要成员:ApplicationEvent、ApplicationListener)
Spring IoC容器的初始化和依赖注入
Bean的定义分为三步
-
Resource定位:根据配置进行资源定位
-
BeanDefinition的载入:将定位到的信息保存到BeanDefinition中
-
BeanDefinition的注册:将BeanDefinition的信息发布到容器中
Spring AOP
AOP能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,提高程序的可重用性,降低模块间的耦合度,并有利于未来的可扩展性和可维护性
Spring AOP就是基于动态代理的,如果要代理的对象实现了某个接口,那么Spring AOP会使用JDK Proxy去创建代理对象,而对于没有实现接口的对象,Spring AOP会使用 Cglib 生成一个被代理对象的子类来作为代理
相关术语
通知
通知描述了切面要完成的工作以及何时执行
- 前置通知(Before):在目标方法调用前调用通知功能;
- 后置通知(After):在目标方法调用之后调用通知功能,不关心方法的返回结果;
- 返回通知(AfterReturning):在目标方法成功执行之后调用通知功能;
- 异常通知(AfterThrowing):在目标方法抛出异常后调用通知功能;
- 环绕通知(Around):通知包裹了目标方法,在目标方法调用之前和之后执行自定义的行为。
连接点
通知功能被应用的时机(如调用接口的时候)
切点
切点定义了通知功能被应用的范围
切面
切面是通知和切点的结合,定义了何时、何地应用通知功能。
引入
在无需修改现有类的情况下,向现有的类添加新方法或属性。
织入
把切面应用到目标对象并创建新的代理对象的过程。
应用场景
权限校验、日志记录、性能监控、事务控制
基于注解的方式
创建切面:@Aspect
定义切入点:@Pointcut
execution(方法修饰符 返回类型 方法所属的包.类名.方法名称(方法参数))
//com.macro.mall.tiny.controller包中所有类的public方法都应用切面里的通知
execution(public * com.macro.mall.tiny.controller.*.*(..))
//com.macro.mall.tiny.service包及其子包下所有类中的所有方法都应用切面里的通知
execution(* com.macro.mall.tiny.service..*.*(..))
//com.macro.mall.tiny.service.PmsBrandService类中的所有方法都应用切面里的通知
execution(* com.macro.mall.tiny.service.PmsBrandService.*(..))
通知类型
- @Before
- @After
- @AfterReturning
- returning
- @AfterThrowing
- throwing
- @Around:增强处理方法的第一个参数必须是ProceedingJoinPoint类型,在增强方法内调用该参数的proceed()方法才会执行目标方法
启用AspectJ框架的自动代理,自动生成动态代理对象
- 注解类上添加
@EnableAspectJAutoProxy
或者在 spring 配置文件中添加<aop:aspectj-autoproxy />
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AspectJAutoProxyRegistrar.class) //引入AspectJAutoProxyRegister.class对象
public @interface EnableAspectJAutoProxy {
//true——使用CGLIB基于类创建代理;false——使用java接口创建代理
boolean proxyTargetClass() default false;
//是否通过aop框架暴露该代理对象,aopContext能够访问.
boolean exposeProxy() default false;
}
在切面中引入其他类的方法得到更好的实现:@DeclareParents
- value:连接点所处的类
- defaultImpl:引入接口的实现类
配置多个切面,可以使用@Order注解指定执行顺序
示例代码
//选择连接点
public interface RoleService {
void printRole(int i);
}
@Service
public class RoleServiceImpl implements RoleService {
public void printRole(int i) {
if(i == 0) throw new RuntimeException();
System.out.println("role service....");
}
}
//创建切面
@Aspect
public class RoleAspect {
@Pointcut("execution(* com.hzb.service.impl.*.*(..))")
public void print(){
}
@Before("print()")
public void before(){
System.out.println("before.........");
}
@After("print()")
public void after(){
System.out.println("after...........");
}
@AfterReturning("print()")
public void afterReturning(){
System.out.println("afterReturning.........");
}
@AfterThrowing("print()")
public void afterThrowing(){
System.out.println("afterThrowing............");
}
@Around("print()")
public void around(ProceedingJoinPoint joinPoint){
System.out.println("around before.......");
try {
joinPoint.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
System.out.println("around after..........");
}
}
@Configuration
@EnableAspectJAutoProxy
@ComponentScan("com.hzb.service")
public class AopConfig {
@Bean
public RoleAspect roleAspect(){
return new RoleAspect();
}
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(AopConfig.class);
RoleService service = context.getBean(RoleService.class);
service.printRole(1);
System.out.println("###########");
service.printRole(0);
}
}
运行结果:
around before.......
before.........
role service....
afterReturning.........
after...........
around after..........
###########
around before.......
before.........
afterThrowing............
after...........
around after..........
//引入其他类的方法得到更好的实现
public interface RoleVerifier {
boolean verify(boolean boo);
}
public class RoleVerifierImpl implements RoleVerifier {
public boolean verify(boolean boo) {
return boo;
}
}
@Aspect
public class RoleAspect {
@DeclareParents(value = "com.hzb.service.impl.RoleServiceImpl+",defaultImpl = RoleVerifierImpl.class)
public RoleVerifier roleVerifier;
...
}
@Configuration
@EnableAspectJAutoProxy
@ComponentScan("com.hzb.service")
public class AopConfig {
@Bean
public RoleAspect roleAspect(){
return new RoleAspect();
}
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(AopConfig.class);
RoleService service = context.getBean(RoleService.class);
RoleVerifier roleVerifier = (RoleVerifier)service;
if(roleVerifier.verify(false)){
service.printRole(1);
}else{
System.out.println("验证失败");
}
}
}
搭建一个AOP切面步骤
- 将切面类和业务逻辑组件(目标方法所在类)都加入到容器中,并且要告诉Spring哪个类是切面类(标注了@Aspect注解的那个类)
- 在切面类上的每个通知方法上标注通知注解,告诉Spring何时何地运行,当然最主要的是要写好切入点表达式,这个切入点表达式可以参照官方文档来写
- 开启基于注解的AOP模式,即加上@EnableAspectJAutoProxy注解,这是最关键的一点
基于XML的方式
- 通知类用 bean 标签配置起来
- 使用 aop:config 声明 aop 配置
- 使用 aop:pointcut 配置切入点表达式
- 使用 aop:aspect 配置切面
- 使用 aop:xxx 配置对应的通知类型
配置实例
<bean class="com.hzb.aop.RoleAspect" id="roleAspect"/>
<aop:config>
<aop:aspect ref="roleAspect">
<!-- 引入 -->
<aop:declare-parents types-matching="com.hzb.service.impl.RoleServiceImpl"
implement-interface="com.hzb.service.RoleVerifier"
default-impl="com.hzb.service.impl.RoleVerifierImpl"/>
<aop:pointcut id="print" expression="execution(* com.hzb.service.impl.*.*(..))"/>
<aop:before method="before" pointcut-ref="print"/>
<aop:after method="after" pointcut-ref="print"/>
<aop:after-returning method="afterReturning" pointcut-ref="print"/>
<aop:after-throwing method="afterThrowing" pointcut-ref="print"/>
<aop:around method="around" pointcut-ref="print"/>
</aop:aspect>
</aop:config>
访问目标方法的参数
增强方法的第一个参数定义为JoinPoint类型,JoinPoint里包含了如下几个常用的方法
-
getArgs():返回执行目标方法的参数
-
getSignature():返回被增强的方法的相关信息
-
getTarget():返回目标对象
-
getThis():返回生成的代理对象
事务管理
XML配置
<!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置事务的通知引用事务管理器 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!--
指定方法名称:是业务核心方法
read-only:是否是只读事务。默认 false,不只读。
isolation:指定事务的隔离级别。默认值是使用数据库的默认隔离级别。
propagation:指定事务的传播行为。
timeout:指定超时时间。默认值为:-1。永不超时。
rollback-for:用于指定一个异常,当执行产生该异常时,事务回滚。产生其他异常,事务不回滚。没有默认值,任何异常都回滚。
no-rollback-for:用于指定一个异常,当产生该异常时,事务不回滚,产生其他异常时,事务回滚。没有默认值,任何异常都回滚。
-->
<tx:method name="*" read-only="false" propagation="REQUIRED"/>
<tx:method name="find*" read-only="true" propagation="SUPPORTS"/>
</tx:attributes>
</tx:advice>
<!-- 配置切入点表达式和事务通知的对应关系 -->
<aop:config>
<aop:pointcut expression="execution(* com.itheima.service.impl..*.*(..))" id="pt1"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="pt1"/>
</aop:config>
注解式事务
事务是逻辑上的一组操作,要么都执行,要么都不执行。
1、配置事务管理器并注入数据源
2、开启 spring 对注解事务的支持
方式一:配置文件
<tx:annotation-driven transaction-manager="transactionManager"/>
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
方式二:java配置方式
@Configuration
@EnableTransactionManagement
@ImportResource(locations = {"classpath:applicationContext.xml"})
public class TransactionConfig implements TransactionManagementConfigurer {
@Autowired
private DataSource dataSource;
@Bean(name = "transactionManager")
public TransactionManager annotationDrivenTransactionManager() {
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
transactionManager.setDataSource(dataSource);
return transactionManager;
}
}
3、在业务层使用@Transactional 注解
属性名 | 说明 |
---|---|
value | 当在配置文件中有多个 TransactionManager , 可以用该属性指定选择哪个事务管理器 |
propagation | 事务的传播行为,默认为REQUIRED |
isolation | 事务的隔离级别,默认为DEFAULT |
timeout | 事务的超时时间,默认值为-1 |
read-only | 指定事务是否为只读事务,默认值为false;为了省略那些不需要事务的方法,比如读取数据,可以设置为true |
rollback-for | 用于指定能够触发事务回滚的异常类型,如果有多个异常类型需要指定,各类型之间可以通过逗号分隔 |
no-rollback-for | 抛出no-rollback-for指定的异常类型,不回滚事务 |
隔离级别
脏读:允许一个事务去读取另一个事务中未提交的数据
读/写提交:一个事务只能读取另一个事务已经提交的数据,不可重复读
可重复读:解决读/写提交存在的不可重读问题,可重复读是针对数据库同一条记录而言的
序列化: 最高的隔离级别,完全服从 ACID 的隔离级别
脏读 | 不可重读 | 幻读 | |
---|---|---|---|
脏读 | √ | √ | √ |
读/写提交 | × | √ | √ |
可重复读 | × | × | √ |
序列化 | × | × | × |
TransactionDefinition 接口中定义了五个表示隔离级别的常量
- TransactionDefinition.ISOLATION_DEFAULT: 使用后端数据库默认的隔离级别,Mysql 默认采用的 REPEATABLE_READ隔离级别 Oracle 默认采用的 READ_COMMITTED隔离级别.
- TransactionDefinition.ISOLATION_READ_UNCOMMITTED: 最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读
- TransactionDefinition.ISOLATION_READ_COMMITTED: 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生
- TransactionDefinition.ISOLATION_REPEATABLE_READ: 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。
- TransactionDefinition.ISOLATION_SERIALIZABLE: 最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。
传播行为
一个方法调用另一个方法时,可以对事务的特性进行传播配置,称为传播行为
支持当前事务的情况:
- TransactionDefinition.PROPAGATION_REQUIRED: 如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
- TransactionDefinition.PROPAGATION_SUPPORTS: 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
- TransactionDefinition.PROPAGATION_MANDATORY: 如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。(mandatory:强制性)
不支持当前事务的情况:
- TransactionDefinition.PROPAGATION_REQUIRES_NEW: 创建一个新的事务,如果当前存在事务,则把当前事务挂起。
- TransactionDefinition.PROPAGATION_NOT_SUPPORTED: 以非事务方式运行,如果当前存在事务,则把当前事务挂起。
- TransactionDefinition.PROPAGATION_NEVER: 以非事务方式运行,如果当前存在事务,则抛出异常。
其他情况:
- TransactionDefinition.PROPAGATION_NESTED: 如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED。
注意的问题
-
Spring AOP的@Transactional只能应用到public方法才有效
-
Spring的AOP的@Transactional的自调用失效问题:一个类的方法去调用自身的另一个方法,事务注解没有起作用
原因是@Transactional的实现原理是AOP,而AOP的实现原理是动态代理,自己调用自己的过程过程不存在代理对象的调用- 解决方法:对于自调用失效问题,使用两个类或者从容器中获取当前类的代理对象再去调用方法
-
错误使用Service:分开两次调用方法,每次调用方法时都会启用一个事务,方法完成后会释放该事务,这样前后两次调用是在两个不同的事务中完成的,这就可能产生数据不一致的问题
-
过长时间占用事务:在方法中进行一些与数据库事务无关的操作,而只有等到方法完成后,才会释放数据库事务资源,所以对于这种时间长并且不需要使用事务资源的操作,要将它放在一个没有事务的环境中运行,防止占用事务资源
-
错误捕捉异常:比如我们在一个事务中有两个操作,我们在代码中加入了try…catch…语句,当其中一个操作成功,另一个操作抛出异常时,由于catch语句对异常进行了处理,Spring在数据库事务所约定的流程中得不到任何异常信息,此时Spring就会提交事务,这样就导致了数据出现错误。因此在需要大量异常处理的代码中,我们要时刻记住Spring和我们的约定流程,在catch中把异常抛出,这样Spring才会捕捉到这个异常,并进行事务回滚
参考
- https://snailclimb.gitee.io/javaguide
- https://liayun.blog.csdn.net/article/details/115053350