Spring 學習記錄
- 1. Spring和SpringFrameWork
- 1.1 廣義的Spring
- 2.1 狹義的Spring
- 2.3 SpringFrameWork / Spring框架圖
- 2. Spring IOC容器(即上圖中的Core Container)
- 2.1 相關概念 (IOC DI 容器 組件)
- 2.2 Spring IOC容器的作用
- 2.3 Spring IOC容器接口和具體實現類
- 3. Spring IOC 實踐
- 3.1 IOC / DI 一般步驟
- 3.2 基于 XML配置 方法
- 3.2.1 組件信息聲明配置 (IOC) 和 依賴注入配置 (DI)
- 3.2.2 IOC容器創建和使用
- 3.2.3 組件周期方法配置
- 3.2.4 FactoryBean的使用
- 3.3 基于 注解配置 方法
- 3.3.1 Spring提供的常見注解
- 3.3.2 注解中BeanName的問題
- 3.3.2 Autowired 和 Resourc 注解
- 3.4 基于 配置類 方法
- 3.5 三種方法總結
- 4. Spring AOP
- 4.1 AOP是什么
- 4.2 為什么引入AOP
- 4.3 主要應用場景
- 4.4 Spring AOP 基于注解實現
- 4.4.1 Spring AOP 底層技術組成
- 4.3.2 通知增強的類型
- 4.4.3 切點表達式
- 4.4.4 代碼示例
- 5.Spring-tx
- 5.1什么是Spring-tx?為什么需要Spring-tx?
- 5.2 代碼示例
- 5.3 事務屬性
1. Spring和SpringFrameWork
通常情況下,我們所說的Spring是狹義概念的Spring,即SpringFrameWork框架。
1.1 廣義的Spring
廣義上的 Spring 泛指以 Spring Framework 為基礎的 Spring 技術棧。
經過十多年的發展,Spring 已經不再是一個單純的應用框架,而是逐漸發展成為一個由多個不同子項目(模塊)組成的成熟技術,例如 Spring Framework、Spring MVC、SpringBoot、Spring Cloud、Spring Data、Spring Security 等,其中 Spring Framework 是其他子項目的基礎。
2.1 狹義的Spring
狹義的 Spring 特指 Spring Framework,通常我們將它稱為 Spring 框架。
Spring Framework(Spring框架)是一個開源的應用程序框架,由SpringSource公司開發,最初是為了解決企業級開發中各種常見問題而創建的。它提供了很多功能,例如:依賴注入(Dependency Injection)、面向切面編程(AOP)、聲明式事務管理(TX)等。其主要目標是使企業級應用程序的開發變得更加簡單和快速,并且Spring框架被廣泛應用于Java企業開發領域。
2.3 SpringFrameWork / Spring框架圖
2. Spring IOC容器(即上圖中的Core Container)
2.1 相關概念 (IOC DI 容器 組件)
- IOC:Inversion of Control(控制反轉)
主要是針對對象的創建和調用控制而言的。當應用程序需要使用一個對象時,不再是應用程序直接創建該對象,而是由 IoC 容器來創建和管理,即控制權由應用程序轉移到 IoC 容器中,也就是“反轉”了控制權 。這種方式基本上是通過依賴查找的方式來實現的,即 IoC 容器維護著構成應用程序的對象,并負責創建這些對象。 - DI: Dependency Injection(依賴注入)
組件之間傳遞依賴關系的過程中,將依賴關系在容器內部進行處理,這樣就不必在應用程序代碼中硬編碼對象之間的依賴關系,實現了對象之間的解耦合。例如在A類內部需要引用B類,不需要A類的內部創建B類的對象,而是在容器內完成。在 Spring 中,DI 是通過 XML 配置文件或注解的方式實現的。它提供了三種形式的依賴注入:構造函數注入、Setter 方法注入和接口注入 - IOC容器:IOC是是一種思想而不是某種技術 簡而言之即對象不再由應用程序創建,而是由另外的獨立的模塊來創建,我們把這個獨立的模塊稱為容器,由于應用了IOC思想,所以叫做IOC容器。Spring框架應用了IOC思想將對象的創建放在了獨立的模塊中,稱為Spring IOC容器。
- 組件:簡而言之,組件是對象。但是對象不一定是組件
2.2 Spring IOC容器的作用
Spring 框架中的IOC容器負責創建組件、管理組件的依賴關系、存儲組件,銷毀組件。減少編碼壓力,讓程序員更加專注進行業務編寫。
容器通過讀取配置元數據來獲取有關要實例化、配置和組裝組件的指令。配置元數據以 XML、Java 注解或 Java 代碼形式表現。它允許表達組成應用程序的組件以及這些組件之間豐富的相互依賴關系。
配置元數據的方式:
- XML配置方式:是Spring框架最早的配置方式之一,通過在XML文件中定義Bean及其依賴關系、Bean的作用域等信息,讓Spring IoC容器來管理Bean之間的依賴關系。該方式從Spring框架的第一版開始提供支持。
- 注解方式:從Spring 2.5版本開始提供支持,可以通過在Bean類上使用注解來代替XML配置文件中的配置信息。通過在Bean類上加上相應的注解(如@Component, @Service, @Autowired等),將Bean注冊到Spring IoC容器中,這樣Spring IoC容器就可以管理這些Bean之間的依賴關系。
- Java配置類方式:從Spring 3.0版本開始提供支持,通過Java類來定義Bean、Bean之間的依賴關系和配置信息,從而代替XML配置文件的方式。Java配置類是一種使用Java編寫配置信息的方式,通過@Configuration、@Bean等注解來實現Bean和依賴關系的配置。
2.3 Spring IOC容器接口和具體實現類
- 接口:BeanFactory接口提供了配置框架和基本功能,ApplicationContext接口繼承自BeanFactory接口,添加了更多特定于企業的功能。
- 實現類:以下實現類都繼承了某些父類,這些父類實現了ApplicatiContext接口。
例如:ClassPathXmlApplicationContext的類繼承關系如下:引用自該文章
3. Spring IOC 實踐
3.1 IOC / DI 一般步驟
元數據配置——容器讀取配置并實例化——獲取對應的組件
元數據配置:此步驟只是進行配置而不進行具體的實例化
容器讀取配置并實例化組件:此步驟容器會對配置讀取進而實例化組件
獲取對應的組件:此步驟是獲取所需要的組件
3.2 基于 XML配置 方法
3.2.1 組件信息聲明配置 (IOC) 和 依賴注入配置 (DI)
- 基于無參構造函數
public class HappyComponent {//默認包含無參數構造函數public void doWork() {System.out.println("HappyComponent.doWork");}
}
...
<bean id="happyComponent" class="com.local.ioc_01.HappyComponent"/>
- 基于有參的構造函數
如果是基本類型,就用value。如果是引用類型,就用ref。所ref的類型要在配置文件中已經配置過,并且ref的是對應類型的id。
public class UserDao {
}public class UserService {private UserDao userDao; private int age; private String name;public UserService(int age , String name ,UserDao userDao) {this.userDao = userDao;this.age = age;this.name = name;}
}
<bean id="userDao" class="com.local.ioc_01.UserDao"/>
<bean id="userService" class="com.local.ioc_01.UserService"><constructor-arg name="name" value="zzz"/><constructor-arg name="age" value="12"/><constructor-arg name="userDao" ref="userDao"/>
</bean>
- 基于靜態工廠方法(創建對象的方法是靜態方法)
注:工廠方法——在方法內部創建對象,外部訪問的時候只需要訪問方法就可以,類似在“工廠”內部對對象進行創建,而使用者不用關心在工廠內部發生了什么。
factory-method: 指定靜態工廠方法,注意,該方法必須是static方法。
public class ClientService {public static ClientService createInstance() {return new ClientService();}
}
<bean id="clientService" class="com.local.ioc_01.ClientService" factory-method="createInstance"/>
- 基于實例工廠方法(創建對象的方法是非靜態方法)
factory-bean屬性:指定當前容器中工廠Bean 的名稱。
factory-method: 指定實例工廠方法名。注意,實例方法必須是非static的
public class DefaultServiceLocator {public ClientService createClientServiceInstance() {return new ClientService();}
}
<bean id="serviceLocator" class="com.local.ioc_01.DefaultServiceLocator"/>
<bean id="clientService2" factory-bean="serviceLocator" factory-method="createClientServiceInstance"/>
- 基于Setter方法依賴注入
public class MovieFinder {
}public class SimpleMovieLister {private MovieFinder movieFinder;private String movieName;public void setMovieFinder(MovieFinder movieFinder) {this.movieFinder = movieFinder;}public void setMovieName(String movieName){this.movieName = movieName;}
}
<bean id="movieFinder" class="com.local.ioc_02.MovieFinder"/>
<bean id="movieLister" class="com.local.ioc_02.SimpleMovieLister"><property name="movieFinder" ref="movieFinder"/><property name="movieName" value="電影"/>
</bean>
3.2.2 IOC容器創建和使用
# spring-ioc-01.xml是要讀取的配置文件
# getBean()中填寫要創建的組件對應的id和對應的反射ClassPathXmlApplicationContext Context = new ClassPathXmlApplicationContext("spring-ioc-01.xml");
UserService userService = Context.getBean("userService", UserService.class);
3.2.3 組件周期方法配置
可以在組件類中定義方法,然后當IOC容器實例化和銷毀組件對象的時候進行調用。這兩個方法我們成為生命周期方法。
類似于Servlet的init/destroy方法,我們可以在周期方法完成初始化和釋放資源等工作。
方法命名隨意,但是要求方法必須是 public void 無形參列表
public class BeanOne {public void init(){System.out.println("init....");}public void destroy(){System.out.println("destroy....");}
}
<bean id="bean" class="com.local.ioc_03.BeanOne" init-method="init" destroy-method="destroy"/>
ClassPathXmlApplicationContext Context = new ClassPathXmlApplicationContext("spring-ioc-03.xml");
BeanOne bean = Context.getBean("bean", BeanOne.class);
Context.close();
//init....
//destroy....
3.2.4 FactoryBean的使用
FactoryBean 是接口。類實例化該接口后可以將創建復雜對象的過程存儲在FactoryBean 的getObject方法中。
待定…
在這里插入代碼片
3.3 基于 注解配置 方法
和 XML 配置文件一樣,注解本身并不能執行,注解本身僅僅只是做一個標記,具體的功能是框架檢測到注解標記的位置,然后針對這個位置按照注解標記的功能來執行具體操作。
3.3.1 Spring提供的常見注解
@Controller、@Service、@Repository這三個注解只是在@Component注解的基礎上起了三個新的名字,在語法層面沒有區別。但是為了代碼的可讀性,不要隨意亂起。
- 使用注解的組件
@Controller
public class UserController {@Autowiredprivate UserService userService;}@Service
public class UserService {@Autowiredprivate UserDao userDao;
}@Repository
public class UserDao {
}
- xml配置文件進行掃描
基本掃描
<context:component-scan base-package="com.atguigu.components"/>排除掃描
<context:component-scan base-package="com.atguigu.components"><!-- context:exclude-filter標簽:指定排除規則 --><!-- type屬性:指定根據什么來進行排除,annotation取值表示根據注解來排除 --><!-- expression屬性:指定排除規則的表達式,對于注解來說指定全類名即可 --><context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>指定掃描
<!-- use-default-filters屬性:取值false表示關閉默認掃描規則 -->
<context:component-scan base-package="com.atguigu.ioc.components" use-default-filters="false"><!-- context:include-filter標簽:指定在原有掃描規則的基礎上追加的規則 --><context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
- 獲取對象
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("annotation01.xml");
UserController bean = context.getBean(UserController.class);
3.3.2 注解中BeanName的問題
使用 XML 方式管理 bean 的時候,每個 bean 都有一個唯一標識——id 屬性的值,便于在其他地方引用。現在使用注解后,每個組件仍然應該有一個唯一標識。
默認情況:類名首字母小寫就是 bean 的 id。例如:SoldierController 類對應的 bean 的 id 就是 soldierController。
還可以使用value屬性指定:
@Controller(value = "tianDog")
public class SoldierController {
}
3.3.2 Autowired 和 Resourc 注解
- AutoWired注解
在成員變量上直接標記@Autowired注解即可。容器創建對象的時候會自動尋找對應的組件進行注入。注入流程如下:
@Controller(value = "tianDog")
public class SoldierController {@Autowired@Qualifier(value = "maomiService222")// 根據面向接口編程思想,使用接口類型引入Service組件private ISoldierService soldierService;//依賴注入的時候,去尋找value值對應的注解
- Resource注解
@Resource注解默認根據 Bean名稱裝配,未指定name時,使用屬性名作為name通過name找不到的話會自動啟動通過類型裝配。
@Autowired注解默認根據類型裝配,如果想根據名稱裝配,需要配合@Qualifier注解一起用。
@Autowired注解是Spring提供的注解,@Resource注解是JDK擴展包中的。【高于JDK11或低于JDK8需要引入以下依賴】
<dependency><groupId>jakarta.annotation</groupId><artifactId>jakarta.annotation-api</artifactId><version>2.1.1</version>
</dependency>
@Controller
public class UserController {/*** 1. 如果沒有指定name,先根據屬性名查找IoC中組件xxxService* 2. 如果沒有指定name,并且屬性名沒有對應的組件,會根據屬性類型查找* 3. 可以指定name名稱查找! @Resource(name='test') == @Autowired + @Qualifier(value='test')*/@Resourceprivate UserService UserService;
}
3.4 基于 配置類 方法
完全注解開發:Spring 完全注解配置(Fully Annotation-based Configuration)是指通過 Java配置類 代碼來配置 Spring 應用程序,使用注解來替代原本在 XML 配置文件中的配置。相對于 XML 配置,完全注解配置具有更強的類型安全性和更好的可讀性
- 配置類:使用 @Configuration 注解將一個普通的類標記為 Spring 的配置類
@Configuration
@ComponentScan(basePackages = {"com.local"})
// 上述代替了使用xml文件配置
public class configuration {
}
- IOC容器創建對象
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(configuration.class);
UserController bean = context.getBean(UserController.class);
3.5 三種方法總結
-
XML方式配置
-
XML+注解方式配置
-
完全注解方式配置
4. Spring AOP
4.1 AOP是什么
AOP:Aspect Oriented Programming(面向切面編程)面向切面編程是一種思維,它利用一種稱為"橫切"的技術,剖解開封裝的對象內部,并將那些影響了多個類的公共行為封裝到一個可重用模塊,并將其命名為"Aspect",即切面。所謂"切面",簡單說就是那些與業務無關,卻為業務模塊所共同調用的邏輯或責任封裝起來,便于減少系統的重復代碼,降低模塊之間的耦合度,并有利于未來的可操作性和可維護性。
4.2 為什么引入AOP
某種程度AOP上完善和解決OOP的非核心代碼冗余和不方便統一維護問題。OOP引入封裝、繼承、多態等概念來建立一種對象層次結構,用于模擬公共行為的一個集合。不過OOP允許開發者定義縱向的關系,但并不適合定義橫向的關系,例如日志功能。日志代碼往往橫向地散布在所有對象層次中,而與它對應的對象的核心功能毫無關系對于其他類型的代碼,如安全性、異常處理和透明的持續性也都是如此,這種散布在各處的無關的代碼被稱為橫切(cross cutting),在OOP設計中,它導致了大量代碼的重復,而不利于各個模塊的重用。
4.3 主要應用場景
- 日志記錄:在系統中記錄日志是非常重要的,可以使用AOP來實現日志記錄的功能,可以在方法執行前、執行后或異常拋出時記錄日志。
- 事務處理:在數據庫操作中使用事務可以保證數據的一致性,可以使用AOP來實現事務處理的功能,可以在方法開始前開啟事務,在方法執行完畢后提交或回滾事務。
- 安全控制:在系統中包含某些需要安全控制的操作,如登錄、修改密碼、授權等,可以使用AOP來實現安全控制的功能。可以在方法執行前進行權限判斷,如果用戶沒有權限,則拋出異常或轉向到錯誤頁面,以防止未經授權的訪問。
- 性能監控:在系統運行過程中,有時需要對某些方法的性能進行監控,以找到系統的瓶頸并進行優化。可以使用AOP來實現性能監控的功能,可以在方法執行前記錄時間戳,在方法執行完畢后計算方法執行時間并輸出到日志中。
- 異常處理:系統中可能出現各種異常情況,如空指針異常、數據庫連接異常等,可以使用AOP來實現異常處理的功能,在方法執行過程中,如果出現異常,則進行異常處理(如記錄日志、發送郵件等)。
- 緩存控制:在系統中有些數據可以緩存起來以提高訪問速度,可以使用AOP來實現緩存控制的功能,可以在方法執行前查詢緩存中是否有數據,如果有則返回,否則執行方法并將方法返回值存入緩存中。
- 動態代理:AOP的實現方式之一是通過動態代理,可以代理某個類的所有方法,用于實現各種功能。
4.4 Spring AOP 基于注解實現
4.4.1 Spring AOP 底層技術組成
- 動態代理(InvocationHandler):JDK原生的實現方式,需要被代理的目標類必須實現接口。因為這個技術要求代理對象和目標對象實現同樣的接口(兄弟兩個拜把子模式)。
- cglib:通過繼承被代理的目標類(認干爹模式)實現代理,所以不需要目標類實現接口。
- AspectJ:早期的AOP實現的框架,SpringAOP借用了AspectJ中的AOP注解。
4.3.2 通知增強的類型
即表示將切面中的方法切入到核心方法中的哪里去。
- 前置通知:在被代理的目標方法前執行 @Before
- 返回通知:在被代理的目標方法成功結束后執行 @After
- 異常通知:在被代理的目標方法異常結束后執行 @AfterThrowing
- 后置通知:在被代理的目標方法最終結束后執行 @AfterReturning
- 環繞通知:使用try...catch...finally結構圍繞整個被代理的目標方法,包括上面四種通知對應的所有位置 @Around
4.4.3 切點表達式
- 什么是切點表達式
在準備好切面后,需要確定將切面切入到哪里,即切入到核心方法的哪里,切點表達式即指定說明了切入的點。 - 切點表達式語法
- 第一位:execution( ) 固定開頭
- 第二位:方法訪問修飾符
public private 直接描述對應修飾符即可
- 第三位:方法返回值
int String void 直接描述返回值類型
注意:特殊情況 不考慮 訪問修飾符和返回值execution(* * ) 這是錯誤語法execution(*) == 你只要考慮返回值 或者 不考慮訪問修飾符 相當于全部不考慮了
- 第四位:指定包的地址
固定的包: com.atguigu.api | service | dao單層的任意命名: com.atguigu.* = com.atguigu.api com.atguigu.dao * = 任意一層的任意命名任意層任意命名: com.. = com.atguigu.api.erdaye com.a.a.a.a.a.a.a ..任意層,任意命名 用在包上!注意: ..不能用作包開頭 public int .. 錯誤語法 com..找到任何包下: *..
- 第五位:指定類名稱
固定名稱: UserService
任意類名: *
部分任意: com..service.impl.*Impl
任意包任意類: *..*
- 第六位:指定方法名稱
語法和類名一致
任意訪問修飾符,任意類的任意方法: * *..*.*
- 第七位:方法參數
第七位: 方法的參數描述具體值: (String,int) != (int,String) 沒有參數 ()模糊值: 任意參數 有 或者 沒有 (..) ..任意參數的意識部分具體和模糊:第一個參數是字符串的方法 (String..)最后一個參數是字符串 (..String)字符串開頭,int結尾 (String..int)包含int類型(..int..)
- 切點表達式重用
Q:為什么要對切點表達式重用?
A:當多個切面表達式相同時,后期進行修改維護較麻煩,例如以下:前2個和后2個的切面表達式相同,因此將切點表達式提取到同一個類中,方便重用。
@Before(value = "execution(public int *..Calculator.sub(int,int))")
..........
@AfterReturning(value = "execution(public int *..Calculator.sub(int,int))")
.........
@AfterThrowing(value = "execution(* *..*Service.*(..))")
.........
@After(value="execution(* *..*Service.*(..))")
......
Q:如何重用?如何重用后引用?
// 重用
@Component
public class PointCut {@Pointcut(value = "execution(public int *..Calculator.sub(int,int))")public void GlobalPointCut(){}@Pointcut(value = "execution(* *..*Service.*(..))")public void SecondPointCut(){}
}//引用
@Before(value = "GlobalPointCut") //value中是方法名
public void printLogBeforeCoreOperation() {}
4.4.4 代碼示例
//定義計算接口
public interface Calculator {int add(int i, int j);int sub(int i, int j);int mul(int i, int j);int div(int i, int j);
}//接口的核心代碼實現類(只實現核心代碼而忽略一些打印輸出和異常處理的代碼)
@Component
public class CalculatorPureImpl implements Calculator { @Overridepublic int add(int i, int j) {return i + j;} @Overridepublic int sub(int i, int j) {return i - j;}@Overridepublic int mul(int i, int j) {return i * j;}@Overridepublic int div(int i, int j) {return i / j;}
}//重用切面表達式
@Component
public class MyPointCut {@Pointcut(value = "execution(* service.impl.*.*(..))")public void pointCutOne(){}
}//切面
@Component
@Aspect
public class LogAdvice {@Before("PointCut.MyPointCut.pointCutOne()")public void start(){System.out.println("start");}@After("PointCut.MyPointCut.pointCutOne()")public void end(){System.out.println("end");}@AfterThrowing("PointCut.MyPointCut.pointCutOne()")public void error(){System.out.println("error");}
}//配置類掃描和開啟aspectj注解
@Configuration
@ComponentScan({"service","advice","config"}) //掃描
@EnableAspectJAutoProxy //開啟aspectj注解
public class JavaConfig {
}//測試
@SpringJUnitConfig(value = JavaConfig.class)
public class SpringAopTest {@Autowiredprivate Calculator calculator;@Testpublic void test1(){int res=calculator.add(1,1);System.out.println(res);}
}
//輸出
start
end
2
5.Spring-tx
5.1什么是Spring-tx?為什么需要Spring-tx?
- Spring-tx是Spring框架支持以聲明性的方式管理事務,而不是編程式的方式。將事務的控制和業務邏輯分離開來,提高代碼的可讀性和可維護性
try {// 開啟事務:關閉事務的自動提交conn.setAutoCommit(false);// 核心操作// 業務代碼// 提交事務conn.commit();
}catch(Exception e){ // 回滾事務conn.rollBack();
}finally{ // 釋放數據庫連接conn.close();
}
- 編程式事務:手動編寫程序來管理事務,即通過編寫代碼的方式直接控制事務的提交和回滾。
- 聲明式事務:使用注解或 XML 配置的方式來控制事務的提交和回滾。開發者只需要添加配置即可, 具體事務的實現由第三方框架實現,避免我們直接進行事務操作。
5.2 代碼示例
//jdbc.properties
atguigu.url=jdbc:mysql://localhost:3306/fruitdb
atguigu.driver=com.mysql.cj.jdbc.Driver
atguigu.username=root
atguigu.password=123456//javaconfig
@Configuration
@ComponentScan({"Dao","Service"})
@PropertySource("classpath:jdbc.properties")
@EnableTransactionManagement //開啟事務注解的支持
public class JavaConfig {@Value("${atguigu.driver}")private String driver;@Value("${atguigu.url}")private String url;@Value("${atguigu.username}")private String username;@Value("${atguigu.password}")private String password;//druid連接池@Beanpublic DataSource dataSource(){DruidDataSource dataSource = new DruidDataSource();dataSource.setDriverClassName(driver);dataSource.setUrl(url);dataSource.setUsername(username);dataSource.setPassword(password);return dataSource;}@Bean//jdbcTemplatepublic JdbcTemplate jdbcTemplate(DataSource dataSource){JdbcTemplate jdbcTemplate = new JdbcTemplate();jdbcTemplate.setDataSource(dataSource);return jdbcTemplate;}//使用事務管理器 //事務管理器需要數據庫連接信息datasource@Beanpublic DataSourceTransactionManager transactionManager(DataSource dataSource){return new DataSourceTransactionManager(dataSource);}
}//FruitDao
@Repository
public class FruitDao {@Autowiredprivate JdbcTemplate jdbcTemplate;public void updatePriceByName(String name,Integer price){String sql = "update t_fruit set price = ? where fname = ? ;";int rows = jdbcTemplate.update(sql, price,name);}public void updateRemarkByName(String name,String remark){String sql = "update t_fruit set remark= ? where fname = ? ;";jdbcTemplate.update(sql,remark,name);}
}//FruitService
@Service
public class FruitService {@Autowiredprivate FruitDao fruitDao;@Transactional //添加事務注解public void changeInfo(){fruitDao.updatePriceByName("蘋果",30);int i=1/0; //這里會報錯 那么整個事務將會回滾,2次修改信息都將失敗 如果沒有事務 那么第一次將修改成功而第二次失敗fruitDao.updateRemarkByName("蘋果","ok");}
}
上述代碼在FruitService的方法中添加了Transactionnal注解,該注解可以作用與類和方法上,作用于類上說明對類內的方法都生效,作用于方法則只對方法生效。
5.3 事務屬性
- 只讀
在Transactionnal注解中設置屬性readOnly屬性為True,默認值為False
@Transactional(readOnly = true)
- 超時時間
程序運行過程中因為某些原因卡住占用資源,設置超時時間,事務運行的時間超過超過設置的超時時間則回滾,釋放資源。通過timeout屬性設置。默認值是-1,即無限。
@Transactional(timeout = 3)
- 事務異常
關于異常的分類可以參考此文章-異常分類總結
默認只針對運行時異常回滾,編譯時異常不回滾
rollbackForClassName:指定哪些異常才會回滾,默認是 RuntimeException and Error 異常方可回滾!
noRollbackForClassName:指定哪些異常不會回滾, 默認沒有指定,如果指定,應該在rollbackFor的范圍內!
public class FruitService {@Autowiredprivate FruitDao fruitDao;@Transactional(noRollbackFor = ArithmeticException.class) //添加事務注解 發生該異常時不回滾public void changeInfo(){fruitDao.updatePriceByName("蘋果",30);int i=1/0; //這里會報錯 但是設置為不回滾 所以第一條修改成功,但是第二條修改失敗fruitDao.updateRemarkByName("蘋果","ok");}
}
- 隔離級別
數據庫事務的隔離級別是指在多個事務并發執行時,數據庫系統為了保證數據一致性所遵循的規定。常見的隔離級別包括:
- 讀未提交(Read Uncommitted):事務可以讀取未被提交的數據,容易產生臟讀、不可重復讀和幻讀等問題。實現簡單但不太安全,一般不用。
- 讀已提交(Read Committed):事務只能讀取已經提交的數據,可以避免臟讀問題,但可能引發不可重復讀和幻讀。
- 可重復讀(Repeatable Read):在一個事務中,相同的查詢將返回相同的結果集,不管其他事務對數據做了什么修改。可以避免臟讀和不可重復讀,但仍有幻讀的問題。
- 串行化(Serializable):最高的隔離級別,完全禁止了并發,只允許一個事務執行完畢之后才能執行另一個事務。可以避免以上所有問題,但效率較低,不適用于高并發場景。
@Transactional(isolation = Isolation.REPEATABLE_READ)
- 事務傳播
@Transactional 注解通過 propagation 屬性設置事務的傳播行為。它的默認值是
Propagation propagation() default Propagation.REQUIRED;
//jdbc.properties
atguigu.url=jdbc:mysql://localhost:3306/fruitdb
atguigu.driver=com.mysql.cj.jdbc.Driver
atguigu.username=root
atguigu.password=123456//javaconfig
@Configuration
@ComponentScan({"Dao","Service"})
@PropertySource("classpath:jdbc.properties")
@EnableTransactionManagement //開啟事務注解的支持
public class JavaConfig {@Value("${atguigu.driver}")private String driver;@Value("${atguigu.url}")private String url;@Value("${atguigu.username}")private String username;@Value("${atguigu.password}")private String password;//druid連接池@Beanpublic DataSource dataSource(){DruidDataSource dataSource = new DruidDataSource();dataSource.setDriverClassName(driver);dataSource.setUrl(url);dataSource.setUsername(username);dataSource.setPassword(password);return dataSource;}@Bean//jdbcTemplatepublic JdbcTemplate jdbcTemplate(DataSource dataSource){JdbcTemplate jdbcTemplate = new JdbcTemplate();jdbcTemplate.setDataSource(dataSource);return jdbcTemplate;}//使用事務管理器 //事務管理器需要數據庫連接信息datasource@Beanpublic DataSourceTransactionManager transactionManager(DataSource dataSource){return new DataSourceTransactionManager(dataSource);}
}//FruitDao
@Repository
public class FruitDao {@Autowiredprivate JdbcTemplate jdbcTemplate;public void updatePriceByName(String name,Integer price){String sql = "update t_fruit set price = ? where fname = ? ;";int rows = jdbcTemplate.update(sql, price,name);}public void updateRemarkByName(String name,String remark){String sql = "update t_fruit set remark= ? where fname = ? ;";jdbcTemplate.update(sql,remark,name);}
}//FruitService
@Service
public class FruitService {@Autowiredprivate FruitDao fruitDao;@Transactional(noRollbackFor = ArithmeticException.class,propagation = Propagation.REQUIRES_NEW) //添加事務注解 發生該異常時不回滾 并且修改默認傳播public void changePrice(){fruitDao.updatePriceByName("香蕉",50);int i=1/0; //這里會報錯 但是設置了不回滾}@Transactionalpublic void changeRemark(){fruitDao.updateRemarkByName("蘋果","good");}
}//TopService 整合的Service
@Service
public class TopService {@Autowiredprivate FruitService fruitService;@Transactionalpublic void changeInfo(){fruitService.changePrice();fruitService.changeRemark();}
}//測試
@Test
public void test(){topService.changeInfo();
}結果:父事務默認的傳播行為,changePrice方法中修改了默認的傳播方法,并且遇到運行錯誤時不回滾。
那么changePrice方法則忽略父方法中的傳播行為,獨立創建事務。
結果是成功修改了price而沒有修改remark。