Spring(四)--------AOP、整合Mybatis、聲明式事務
11、AOP(重點)
11.1 什麼是AOP
- 總的來說:AOP是在不影響原始業務類的情況下,對代碼進行動态的增強
- AOP(Aspect Oriented Programming)意為:面向切面程式設計,通過預編譯方式和運作期動态代理實作程式功能的統一維護的一種技術。AOP是OOP的延續,是軟體開發中的一個熱點,也是Spring架構中的一個重要内容,是函數式程式設計的一種衍生範型。利用AOP可以對業務邏輯的各個部分進行隔離,進而使得業務邏輯各部分之間的耦合度降低,提高程式的可重用性,同時提高了開發的效率。
Spring(四)--------AOP、整合Mybatis、聲明式事務Spring(四)--------AOP、整合Mybatis、聲明式事務
11.2 AOP在Spring中的作用
- 提供聲明式事務;允許使用者自定義切面
以下名詞需要了解下:
- 橫切關注點:跨越應用程式多個子產品的方法或功能。即是,與我們業務邏輯無關的,但是我們需要關注的部分,就是橫切關注點。如日志 , 安全 , 緩存 , 事務等等 …
- 切面(ASPECT):橫切關注點 被子產品化 的特殊對象。即,它是一個類。
- 通知(Advice):切面必須要完成的工作。即,它是類中的一個方法。
- 目标(Target):被通知對象。
- 代理(Proxy):向目标對象應用通知之後建立的對象。
- 切入點(PointCut):切面通知 執行的 “地點”的定義。
- 連接配接點(JointPoint):與切入點比對的執行點。
5類Advice
SpringAOP中,通過Advice定義橫切邏輯,Spring中支援5種類型的Advice:
通知類型 | 連接配接點 | 實作接口 |
---|---|---|
前置通知 | 方法前 | org.springframework.aop.MethodBeforeAdvice |
後置通知 | 方法後 | org.springframework.aop.AfterReturningAdvice |
環繞通知 | 方法前後 | org.aopalliance.intercept.MethodInterceptor |
異常抛出通知 | 方法抛出異常 | org.springframework.aop.ThrowsAdvice |
引介通知 | 類中增加新的方法屬性 | org.springframework.aop.IntroductionInterceptor |
各類增強
-
前置增強:org.springframework.aop.BeforeAdvice代表前置增強,因為Spring隻支援
方法級的增強,是以MethodBeforeAdvice是目前可的的前置增強,表示在目标方法執行前實施增強,而BeforeAdvice是為了将來版本擴充需要而定義的;
-
後置增強:org.springframework.aop.AfterReturningAdvice代表後增強,表示在目标
方法執行後實施增強;
-
環繞增強:org.aopalliance.intercept.MethodInterceptor代表環繞增強,表示在目标
方法執行前後實施增強;
- 異常抛出增強:org.springframework.aop.ThrowsAdvice代表抛出異常增強,表示在目标方法抛出異常後實施增強;
-
引介增強:org.springframework.aop.IntroductionInterceptor代表引介增強,表示在
目标類中添加一些新的方法和屬性;
- 這些增強接口都有一些方法,通過實作這些接口方法,在接口方法中這義橫切邏輯,就可以将它們織入到目标類的方法的相應連接配接點的位置。
- 即 Aop 在 不改變原有代碼的情況下 , 去增加新的功能 .
限制
-
xmlns:aop="http://www.springframework.org/schema/aop"
-
http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd
11.3 使用Spring實作AOP
- 主要SpringAPI接口實作
【重點】使用AOP織入,需要導入一個依賴包
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
第一種方式:使用Spring的API接口
- 編寫UserService接口
public interface UserService {
public void add();
public void delete();
public void update();
public void query();
}
- 編寫UserServiceImpl實作類
public class UserServiceImpl implements UserService{
@Override
public void add() {
System.out.println("增加了一個使用者");
}
@Override
public void delete() {
System.out.println("删除了一個使用者");
}
@Override
public void update() {
System.out.println("更新了一個使用者");
}
@Override
public void query() {
System.out.println("查詢了一個使用者");
}
}
- 編寫前置增強類
public class Log implements MethodBeforeAdvice {
//method:要執行的目标對象的方法 args:被調用的方法的參數 target:目标對象
//類比動态代理ProxyInvocationHandler
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
//target.getClass().getName() 類名 method.getName()方法名字
System.out.println(target.getClass().getName()+"的"+method.getName()+"被執行了");
}
}
- 編寫後置增強類
public class AfterLog implements AfterReturningAdvice {
//returnValue:傳回值
//method:被調用的方法
//args:被調用的方法的對象的參數
//target:被調用的目标對象
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("執行了"+method.getName()+"方法,傳回結果為:"+returnValue);
}
}
- 編寫applicationContext.xml配置檔案(導入限制,實作aop切入)
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--注冊bean-->
<bean id="userService" class="com.zmt.service.UserServiceImpl"/>
<bean id="log" class="com.zmt.log.Log"/>
<bean id="afterLog" class="com.zmt.log.AfterLog"/>
<!--方式一:使用原生Spring API接口-->
<!--配置AOP:需要導入AOP的限制-->
<aop:config>
<!--切入點 expression:表達式(要執行的位置:修飾詞,傳回值,類名,方法名,參數),切入點可以有多個-->
<!--com.zmt.service.UserServiceImpl.*(..)
第一個*:傳回值類型任意
第二個*:代表類中所有方法 (..):代表任意數量參數-->
<aop:pointcut id="pointcut" expression="execution(* com.zmt.service.UserServiceImpl.*(..))"/>
<!--執行環繞增加-->
<!--advice-ref:advice引用了哪個類 pointcut-ref:插入到哪個切入點-->
<aop:advisor advice-ref="log" pointcut-ref="pointcut"/>
<aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
</aop:config>
</beans>
- 測試
public class MyTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
//動态代理,代理的是接口,不是實作類
UserService userService = context.getBean("userService", UserService.class);
userService.query();
}
}
- Spring的Aop就是将公共的業務 (日志 , 安全等) 和領域業務結合起來 , 當執行領域業務時 , 将會把公共業務加進來。實作公共業務的重複利用 . 領域業務更純粹 , 程式猿專注領域業務 , 其本質還是動态代理
第二種方式:自定義類來實作AOP
- 主要是切面定義
- 自定義切入類[增強類](相當于第一種方式的Log和afterLog)
public class DiyPointCut {
public void before(){
System.out.println("================方法執行前===============");
}
public void after(){
System.out.println("================方法執行後===============");
}
}
- 配置xml檔案
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--注冊bean-->
<bean id="userService" class="com.zmt.service.UserServiceImpl"/>
<bean id="log" class="com.zmt.log.Log"/>
<bean id="afterLog" class="com.zmt.log.AfterLog"/>
<!--方式二:自定義類-->
<bean id="diy" class="com.zmt.diy.DiyPointCut"/>
<aop:config>
<!--自定義切面 ref:切面引用的類(bean id)-->
<aop:aspect ref="diy">
<aop:pointcut id="point" expression="execution(* com.zmt.service.UserServiceImpl.*(..))"/>
<!--通知-->
<!--在前面執行什麼方法 method:執行的方法 pointcut-ref:插入到哪個切入點-->
<aop:before method="before" pointcut-ref="point"/>
<aop:after method="after" pointcut-ref="point"/>
</aop:aspect>
</aop:config>
</beans>
- 測試
public class MyTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
//動态代理,代理的是接口,不是實作類
UserService userService = context.getBean("userService", UserService.class);
userService.query();
}
}
第三種方式:使用注解實作
- 編寫一個注解實作的增強類
//方式三:使用注解方式實作AOP
@Aspect //标注這個類是一個切面
public class AnnotationPointCut {
@Before("execution(* com.zmt.service.UserServiceImpl.*(..))")
public void before(){
System.out.println("======在方法執行前======");
}
@After("execution(* com.zmt.service.UserServiceImpl.*(..))")
public void after(){
System.out.println("======在方法執行後======");
}
//在環繞增強中,我們可以給定一個參數,代表我們要擷取處理切入的點
@Around("execution(* com.zmt.service.UserServiceImpl.*(..))")
public void around(ProceedingJoinPoint jp) throws Throwable {
System.out.println("環繞前...");
//獲得簽名
Signature signature = jp.getSignature();
System.out.println("signature:"+signature);
//執行方法
Object proceed = jp.proceed();
System.out.println("環繞後...");
System.out.println(proceed);
}
}
- 編寫xml配置檔案
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--注冊bean-->
<bean id="userService" class="com.zmt.service.UserServiceImpl"/>
<bean id="log" class="com.zmt.log.Log"/>
<bean id="afterLog" class="com.zmt.log.AfterLog"/>
<!--方式三:使用注解實作-->
<bean id="annotationPointCut" class="com.zmt.diy.AnnotationPointCut"/>
<!--開啟注解支援-->
<aop:aspectj-autoproxy/>
</beans>
- 測試
public class MyTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
//動态代理,代理的是接口,不是實作類
UserService userService = context.getBean("userService", UserService.class);
userService.query();
}
}
// 環繞前...
// signature:void com.zmt.service.UserService.query()
// ======在方法執行前======
// 查詢了一個使用者
// ======在方法執行後======
// 環繞後...
// null
<aop:aspectj-autoproxy/>
說明:
- 通過aop命名空間的<aop:aspectj-autoproxy />聲明自動為spring容器中那些配置@aspectJ切面的bean建立代理,織入切面。當然,spring 在内部依舊采用AnnotationAwareAspectJAutoProxyCreator進行自動代理的建立工作,但具體實作的細節已經被<aop:aspectj-autoproxy />隐藏起來了
- <aop:aspectj-autoproxy />有一個proxy-target-class屬性,預設為false,表示使用jdk動态代理織入增強,當配為<aop:aspectj-autoproxy poxy-target-class=“true”/>時,表示使用CGLib動态代理技術織入增強。不過即使proxy-target-class設定為false,如果目标類沒有聲明接口,則spring将自動使用CGLib動态代理。
12、整合Mybatis
12.1 導入相關jar包
- junit
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
- mybatis
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.7</version>
</dependency>
- mysql資料庫
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.21</version>
</dependency>
- spring相關的
<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.9</version>
</dependency>
<!-- spring操作資料庫 -->
<!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.9</version>
</dependency>
- aop織入
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
- mybatis-spring【new知識點】
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis-spring -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.6</version>
</dependency>
- 編寫配置檔案
- 測試
12.2 回憶Mybatis
- 編寫實體類
public class User {
private int id;
private String name;
private String pwd;
public User() {
}
public User(int id, String name, String pwd) {
this.id = id;
this.name = name;
this.pwd = pwd;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPwd() {
return pwd;
}
public void setPwd(String pwd) {
this.pwd = pwd;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", pwd='" + pwd + '\'' +
'}';
}
}
- 編寫核心配置檔案
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<typeAliases>
<package name="com.zmt.pojo"/>
</typeAliases>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?userSSL=false&useUnicode=false&characterEncoding=UTF-8&serverTimezone=GMT%2B8"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper class="com.zmt.mapper.UserMapper"/>
</mappers>
</configuration>
- 編寫接口
public interface UserMapper {
public List<User> selectUser();
}
- 編寫Mapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zmt.mapper.UserMapper">
<select id="selectUser" resultType="User">
select * from user;
</select>
</mapper>
- 測試
public class MyTest {
@Test
public void test() throws IOException {
//--工具類
String resources = "mybatis-config.xml";
InputStream in = Resources.getResourceAsStream(resources);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(in);
SqlSession sqlSession = sqlSessionFactory.openSession(true);
//工具類--
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> userList = mapper.selectUser();
for (User user : userList) {
System.out.println(user);
}
sqlSession.close();
}
}
12.3 MyBatis-Spring學習
- 了解MyBatis-Spring:mybatis-spring
什麼是 MyBatis-Spring?
- MyBatis-Spring 會幫助你将 MyBatis 代碼無縫地整合到 Spring 中。它将允許 MyBatis 參與到 Spring 的事務管理之中,建立映射器 mapper 和
并注入到 bean 中,以及将 Mybatis 的異常轉換為 Spring 的SqlSession
。 最終,可以做到應用代碼不依賴于 MyBatis,Spring 或 MyBatis-Spring。DataAccessException
知識基礎
- 在開始使用 MyBatis-Spring 之前,你需要先熟悉 Spring 和 MyBatis 這兩個架構和有關它們的術語。
- MyBatis-Spring 需要以下版本:
MyBatis-Spring | MyBatis | Spring 架構 | Spring Batch | Java |
---|---|---|---|---|
2.0 | 3.5+ | 5.0+ | 4.0+ | Java 8+ |
1.3 | 3.4+ | 3.2.2+ | 2.1+ | Java 6+ |
- 如果使用 Maven 作為建構工具,僅需要在 pom.xml 中加入以下代碼即可:
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis-spring -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.6</version>
</dependency>
12.4 整合方式一編碼流程
- 編寫資料源配置
- sqlSessionFactory
- sqlSessionTemplate:是線程安全的
- 基礎的 MyBatis 用法中,是通過 SqlSessionFactoryBuilder 來建立 SqlSessionFactory 的。而在 MyBatis-Spring 中,則使用 SqlSessionFactoryBean 來建立。
- 在 MyBatis 中,你可以使用 SqlSessionFactory 來建立 SqlSession。一旦你獲得一個 session 之後,你可以使用它來執行映射了的語句,送出或復原連接配接,最後,當不再需要它的時候,你可以關閉 session。
- SqlSessionFactory有一個唯一的必要屬性:用于 JDBC 的 DataSource。這可以是任意的 DataSource 對象,它的配置方法和其它 Spring 資料庫連接配接是一樣的。
- 一個常用的屬性是 configLocation,它用來指定 MyBatis 的 XML 配置檔案路徑。它在需要修改 MyBatis 的基礎配置非常有用。通常,基礎配置指的是 < settings> 或 < typeAliases>元素。
- 編寫xml配置檔案(資料源配置,sqlSessionFactory,sqlSessionTemplate)
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- DataSource:使用spring的資料源替換Mybatis的配置 c3p0 dbcp druid
我們這裡使用spring提供的JDBC:org.springframework.jdbc.datasource
-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?userSSL=false&
useUnicode=false&characterEncoding=UTF-8&serverTimezone=GMT%2B8"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</bean>
<!--sqlSessionFactory-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<!--綁定Mybatis配置檔案-->
<property name="configLocation" value="classpath:mybatis-config.xml"/>
<property name="mapperLocations" value="classpath:com/zmt/mapper/UserMapper.xml"/>
</bean>
<!--SqlSessionTemplate就是我們使用的sqlSession -->
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<!--因為沒有set方法,是以隻能使用構造器注入sqlSessionFactory-->
<constructor-arg index="0" ref="sqlSessionFactory"/>
</bean>
</beans>
- 編寫接口
public interface UserMapper {
public List<User> selectUser();
}
- 編寫對應mapper.xml方法
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zmt.mapper.UserMapper">
<select id="selectUser" resultType="User">
select * from user;
</select>
</mapper>
- 給接口加對應實作類
public class UserMapperImpl implements UserMapper{
//在原來我們所有操作,都使用sqlSession來執行,現在都使用SqlSessionTemplate
private SqlSessionTemplate sqlSession;
public void setSqlSession(SqlSessionTemplate sqlSession) {
this.sqlSession = sqlSession;
}
@Override
public List<User> selectUser() {
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
return mapper.selectUser();
}
}
- 将自己寫的實作類注入到Spring中
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<import resource="spring-dao.xml"/>
<bean id="userMapper" class="com.zmt.mapper.UserMapperImpl">
<property name="sqlSession" ref="sqlSession"/>
</bean>
</beans>
- 測試
public class MyTest {
@Test
public void test() throws IOException {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserMapper userMapper = context.getBean("userMapper", UserMapper.class);
for (User user : userMapper.selectUser()) {
System.out.println(user);
}
}
}
12.5 整合方式二編碼流程
- mybatis-spring1.2.3版本以上才有這種方式
- 官方文檔截圖:
Spring(四)--------AOP、整合Mybatis、聲明式事務Spring(四)--------AOP、整合Mybatis、聲明式事務 - dao繼承Support類 , 直接利用 getSqlSession() 獲得 , 然後直接注入SqlSessionFactory 。比起方式1 , 不需要管理SqlSessionTemplate , 而且對事務的支援更加友好,可跟蹤源碼檢視
- 編寫UserMapperImpl2
import java.util.List;
public class UserMapperImpl2 extends SqlSessionDaoSupport implements UserMapper{
@Override
public List<User> selectUser() {
SqlSession sqlSession = getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
return mapper.selectUser();
}
}
- 修改配置檔案(spring-dao不需要修改)
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<import resource="spring-dao.xml"/>
<bean id="userMapper2" class="com.zmt.mapper.UserMapperImpl2">
<property name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>
</beans>
- 測試
public class MyTest {
@Test
public void test() throws IOException {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserMapper userMapper = context.getBean("userMapper2", UserMapper.class);
for (User user : userMapper.selectUser()) {
System.out.println(user);
}
}
}
小結
- 整合到spring以後可以完全不要mybatis的配置檔案,除了這些方式可以實作整合之外,我們還可以使用注解來實作,這個等我們後面學習SpringBoot的時候還會測試整合
13、聲明式事務
13.1 回顧事務
- 事務特點:把一組業務,當成一個業務來做,要麼都成功,要麼都失敗。
- 事務在項目開發中,十分重要,涉及到資料一緻性問題,不能馬虎
- 確定完整性和一緻性
事務ACID原則
- 原子性(atomicity):事務是原子性操作,由一系列動作組成,事務的原子性確定動作要麼全部完成,要麼完全不起作用
- 一緻性(consistency):一旦所有事務動作完成,事務就要被送出。資料和資源處于一種滿足業務規則的一緻性狀态中
- 隔離性(isolation):可能多個事務會同時處理相同的資料,是以每個事務都應該與其他事務隔離開來,防止資料損壞
- 持久性(durability):事務一旦完成,無論系統發生什麼錯誤,結果都不會受到影響。通常情況下,事務的結果被寫到持久化存儲器中
13.2 測試
- 編寫配置檔案,包括spring-dao.xml,mybatis-config.xml,applicationContext.xml
- 編寫User實體類
- 編寫UserMapper接口
public interface UserMapper {
public List<User> selectUser();
//添加一個使用者
public int addUser(User user);
//删除一個使用者
public int deleteUser(int id);
}
- 編寫UserMapper.xml
- 特意将sql語句中的delete打成deletes
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zmt.mapper.UserMapper">
<select id="selectUser" resultType="User">
select * from user;
</select>
<insert id="addUser" parameterType="user">
insert into user (id,name,pwd) values (#{id},#{name},#{pwd});
</insert>
<delete id="deleteUser" parameterType="int">
deletes from user where id = #{id}
</delete>
</mapper>
- 編寫接口的實作類:在重寫的select方法中,先增添一個使用者,再删除這個使用者(注意,删除sql語句是錯誤的)
public class UserMapperImpl extends SqlSessionDaoSupport implements UserMapper{
@Override
public List<User> selectUser() {
User user = new User(8,"小張","123456");
UserMapper mapper = getSqlSession().getMapper(UserMapper.class);
mapper.addUser(user);
mapper.deleteUser(8);
return mapper.selectUser();
}
@Override
public int addUser(User user) {
return getSqlSession().getMapper(UserMapper.class).addUser(user);
}
@Override
public int deleteUser(int id) {
return getSqlSession().getMapper(UserMapper.class).deleteUser(id);
}
}
- 測試
public class MyTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserMapper userMapper = context.getBean("userMapper", UserMapper.class);
for (User user : userMapper.selectUser()) {
System.out.println(user);
}
}
}
- 報錯,sql異常,其中delete寫錯了,但是插入語句運作成功
- 沒有進行事務的管理,我們想讓他們都成功才能運作成功,有一個失敗,就都失敗,我們需要事務!
- Spring給我們提供了事務管理,我們隻需要配置即可
13.3 Spring中的事務管理
- Spring在不同的事務管理API之上定義了一個抽象層,使得開發人員不必了解底層的事務管理API就可以使用Spring的事務管理機制。Spring支援程式設計式事務管理和聲明式的事務管理。
程式設計式事務
- 需要在代碼中,進行事務的管理。事務管理代碼嵌到業務方法中來控制事務的送出和復原
- 缺點:必須在每個事務操作業務邏輯中包含額外的事務管理代碼
聲明式事務
- AOP
- 将事務管理代碼從業務方法中分離出來,以聲明的方式來實作事務管理。
- 将事務管理作為橫切關注點,通過aop方法子產品化。Spring中通過Spring AOP架構支援聲明式事務管理。
事務管理器
- 寫入頭檔案限制:
xmlns:tx="http://www.springframework.org/schema/tx"
-
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
- 在配置檔案中配置事務的通知
<!--配置聲明式事務-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<!--結合AOP實作事務的織入-->
<!--配置事務通知: -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!--給那些方法配置事務 name:方法名 值為*時,代表所有方法-->
<!--配置事務的傳播特性 propagation:傳播特性(7種),不寫時預設為required-->
<tx:method name="add" propagation="REQUIRED"/>
<tx:method name="delete" propagation="REQUIRED"/>
<tx:method name="update" propagation="REQUIRED"/>
<tx:method name="search*" propagation="REQUIRED"/>
<tx:method name="query" read-only="true"/>
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
- 配置AOP織入
<!--切入事務-->
<aop:config>
<aop:pointcut id="txPoint" expression="execution(* com.zmt.mapper.*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPoint"/>
</aop:config>
- 進行測試:發現沒有增加新使用者
事務傳播七大特性
- requierd:如果目前沒有事務,就建立一個事務,如果已存在一個事務中,加入到這個事務中,這是最常見的選擇,也是預設選擇。
- supports:支援目前事務,如果沒有目前事務,就以非事務方法執行。
- mandatory:使用目前事務,如果沒有目前事務,就抛出異常。
- required_new:建立事務,如果目前存在事務,把目前事務挂起。
- not_supported:以非事務方式執行操作,如果目前存在事務,就把目前事務挂起。
- never:以非事務方式執行操作,如果目前事務存在則抛出異常。
- nested:如果目前存在事務,則在嵌套事務内執行。如果目前沒有事務,則執行與propagation_required類似的操作
思考
- 為什麼需要配置事務?
- 如果不配置事務,可能存在資料送出不一緻的情況
- 如果我們不在Spring中配置聲明式事務,我們就需要在代碼中手動配置事務
- 事務在項目開發過程中非常重要,涉及到資料的一緻性問題,不容馬虎