課代表筆記(本篇文章的重點):
-
本篇文章從代理模式出發,介紹靜態代理模式和動態代理模式,瞭解AOP的底層的實現
接下來詳細介紹AOP思想
和Spring中AOP的開發流程
-
Spring框架和MyBatis框架整合
,讓開發的效率更高
兩者都是企業面試筆試最常問到的知識點,或者換一句話說,這是企業招人最低的要求
-
最後講Spring的
註解開發
,省去了配置實體類和注入實體類煩雜的流程 -
Spring集成JUnit,
讓測試寫更少的代碼
前文推薦
:
Spring基礎入門到Spring IOC介紹(本篇文章竟然將這麼多Spring的底層實現,面試官又得問了)
在講SpringAOP之前,先介紹一下代理設計模式,能更好的理解AOP的思想和AOP的底層實現
代理設計模式
概念
將核心功能與輔助功能(事務.日誌.性能監控代碼)分離,達到核心業務功能更純粹.輔助業務可複用
功能分離 |
---|
靜態代理模式
通過代理類的對象,爲原始類的對象(目標類的對象添加輔助功能),更容易更換代理實現類,利於維護
靜態代理 |
---|
結構 |
---|
- 代理類和原始類中要保證實現功能一致,因此讓他們實現同一接口
接口
package per.leiyu.entiry;
/**
* @author 雷雨
* @date 2020/6/19 21:15
*/
public interface FangdongService {
public void zufang();
}
房東
package per.leiyu.entiry;
/**
* @author 雷雨
* @date 2020/6/19 21:15
*/
public class FangdongServiceImpl implements FangdongService{
@Override
public void zufang() {
//核心功能
System.out.println("籤合同");
System.out.println("收房租");
}
}
代理
package per.leiyu.entiry;
/**
* @author 雷雨
* @date 2020/6/19 21:17
*/
public class FangdongServiceProxy implements FangdongService {
private FangdongService fangdongService = new FangdongServiceImpl();
@Override
public void zufang() {
//輔助功能呢
System.out.println("發佈租房信息");
System.out.println("帶租客看房");
//核心功能
//核心功能仍然由原始類完成
fangdongService.zufang();
}
}
靜態代理的特點
- 的確解決了原始業務類中核心功能和輔助功能的耦合
- 但是在
代理類中又出現了核心功能和輔助功能的耦合
因爲在靜態代理中我們明確的創建了代理對象,那麼就增加了代理類的維護成本,我們想要的是既能將輔助功能分離,還不需要我們自己創建代理類對象,那麼就使用動態代理.在程序運行過程中,通過反射機制,動態的爲我們生成一個類來解決原始類中的問題
動態代理設計模式
動態創建代理類的對象,爲原始類的對象添加輔助功能
JDK動態代理實現(基於接口)
package per.leiyu.factoryTest;
import org.junit.Test;
import per.leiyu.entiry.FangdongService;
import per.leiyu.entiry.FangdongServiceImpl;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* @author 雷雨
* @date 2020/6/19 21:38
*/
public class TestProxyByjdk {
@Test
public void test1(){
//目標
FangdongService fangdongService = new FangdongServiceImpl();
//額外功能
InvocationHandler hi= new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//輔助功能呢
System.out.println("發佈租房信息");
System.out.println("帶租客看房");
//核心功能
//核心功能仍然由原始類完成
fangdongService.zufang();
return null;
}
};
//動態生成代理類
//因爲這個代理是目標接口的實現類(和靜態代理一樣,要保證目標類和代理的功能統一),因此用接口接收代理實現類沒有問題
FangdongService proxy = (FangdongService)Proxy.newProxyInstance(TestProxyByjdk.class.getClassLoader(), fangdongService.getClass().getInterfaces(), hi);
proxy.zufang();
}
}
動態代理-基於接口 |
---|
CGLIB動態代理
@Test
public void test2(){
//目標
FangdongService fangdongService = new FangdongServiceImpl();
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(FangdongServiceImpl.class);
enhancer.setCallback(new org.springframework.cglib.proxy.InvocationHandler() {
@Override
public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
//輔助功能呢
System.out.println("發佈租房信息");
System.out.println("帶租客看房");
//核心功能
//核心功能仍然由原始類完成
fangdongService.zufang();
return null;
}
});
//動態創建代理類
FangdongServiceImpl proxy = (FangdongServiceImpl)enhancer.create();
proxy.zufang();
}
動態代理-基於繼承 |
---|
- 也就是把目標類當做了父類,其子類肯定繼承了父類的核心功能,子類再實現一些額外的功能
面向切面編程
前面講到動態代理模式的確能夠把類之間的耦合去掉,但是爲每一個類都編寫這麼大量的代碼費事費力,在Spring中封裝了動態代理的功能,併爲我們提供了友好的方法.
概念
AOP(Aspect Oriented Programming),即面向切面編程,利用一種稱爲"橫切"的技術,刨開封裝的對象內部,並將那些影響了多個類的公共行爲封裝到一個可重用模塊,並將其命名爲"Aspect",即切面,簡單說就是那些與業務無關,卻爲業務模塊所共同調用的邏輯或責任封裝起來,便於減少系統的重複代碼,降低模塊之間的耦合度,並有利於未來的可操作性和可維護性
AOP開發術語
- 連接點(Joinpoint):連接點是程序類中客觀存在的方法,可被Spring攔截並切入內容
- 切入點(Ponintcut):被Spring切入連接點
- 通知.增強(Advice):可以爲切入點添加額外的功能,分爲前置通知.後置通知.異常通知.環繞通知等
- 目標對象(Target):代理的目標對象
- 引介(Introduction):一種特殊的增強,可在運行期爲類動態添加Field和Method
- 織入(Weaving):把通知應用到具體的類,進而創建新的代理類的過程
- 代理(Proxy):被AOP織入通知後,產生的結果類
- 切面(Aspect):由切點和通知組成,將橫切邏輯織入切面所指定的連接點中
作用
Spring的AOP編程既是通過動態代理類爲原始類的方法添加輔助功能
環境搭建
引入AOP相關依賴
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.1.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>4.1.4.RELEASE</version>
</dependency>
開發流程
定義原始類
package per.leiyu.service.userServiceImpl;
import per.leiyu.service.Userservice;
import java.util.List;
/**
* @author 雷雨
* @date 2020/6/20 8:31
*/
public class UserserviceImpl implements Userservice {
@Override
public Integer queryUserByid() {
System.out.println("核心功能" +"查詢用戶通過id值");
return 1;
}
@Override
public void queryUserByName(String name) {
System.out.println("核心功能查詢用戶通過用戶名");
}
@Override
public List queryUserByNameAndPassword(String name, String password) {
System.out.println("核心功能查詢用戶通過用戶名和密碼");
return null;
}
}
package per.leiyu.service;
import java.util.List;
/**
* @author 雷雨
* @date 2020/6/20 8:29
*/
public interface Userservice {
Integer queryUserByid();
public void queryUserByName(String name);
List queryUserByNameAndPassword(String name, String password);
}
定義通知類(實現MethodxxxAdvice)
package per.leiyu;
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;
/**
* @author 雷雨
* @date 2020/6/20 8:34
*/
public class myAdvice implements MethodBeforeAdvice {
@Override
public void before(Method method, Object[] objects, Object o) throws Throwable {
System.out.println("輔助功能1");
System.out.println("輔助功能2");
}
}
定義bean標籤
<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="userService" class="per.leiyu.service.userServiceImpl.UserserviceImpl"></bean>
<bean id="before" class="per.leiyu.myAdvice"></bean>
<!--編織 -->
<aop:config>
<!--定義切入點 修飾符 返回值 包.類 方法名 參數表-->
<aop:pointcut id="pc_leiyu" expression="execution(* queryUserByid())"/>
<!-- 組裝
advice-ref:通知
pointcut-ref:切入點
-->
<aop:advisor advice-ref="before" pointcut-ref="pc_leiyu"/>
</aop:config>
</beans>
- 注意配置頭中添加了aop的配置
定義切入點
package service;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import per.leiyu.service.Userservice;
/**
* @author 雷雨
* @date 2020/6/20 8:36
*/
public class MyserviceAdvice {
@Test
public void test1(){
ApplicationContext context = new ClassPathXmlApplicationContext("/spring-context.xml");
Userservice proxy = (Userservice)context.getBean("userService");
System.out.println(proxy.getClass());
proxy.queryUserByid();
}
}
運行結果
如果運行出來有輔助功能說明我們的動態代理實現成功了
運行結果 |
---|
- 注意:通過Spring AOP實現的動態代理獲得的代理類可以用原始類接口接收(原因和JDK實現動態代理的原因相同)
AOP中的各種通知類
定義通知類,達到通知的效果
- 前置通知:MethodBeforeAdvice
- 後置通知:AfterAdvice
- 後置通知:AfterReturningAdvice 有異常不執行,方法會因異常而結束,無返回值
- 異常通知:ThrowsAdvice
- 環繞通知:MethodInterceptor
切入點表達式
根據表達式通配切入點
修飾符 返回值 包.類 方法名 參數列表
<!-- 匹配參數-->
<aop:pointcut id="myPointCut" expression="execution(* *(per.leiyu.service.UserserviceImpl))"/>
<!--匹配方法名(無參) -->
<aop:pointcut id="myPointCut" expression="execution(* save())"/>
<!--匹配方法名(任意參數)-->
<aop:pointcut id="myPointCut" expression="execution(* save(..))"/>
<!--匹配返回值類型 -->
<aop:pointcut id="myPointCut" expression="execution(per.leiyu.service.UserServiceImpl *(..))"/>
<!-- 匹配類名 -->
<aop:pointcut id="myPointCut" expression="execution(* per.leiyu.service.*(..))"/>
<!-- 匹配包名 -->
<aop:pointcut id="myPointCut" expression="execution(* perleiyu.*.*(..))"/>
<!-- 匹配包名以及子包名-->
<aop:pointcut id="myPointCut" expression="execution(* per.leiyu..*.*(..))"/>
Spring AOP中使用JDK和CGlib選擇
在Spring中實現動態代理的功能來解耦合,可以使用兩種方式JDK的方法和CGlib的方式
那麼Spring底層是如何選擇代理模式的?
基本規則:目標業務類如果有接口則用JDK代理,沒有接口則使用CGlib代理
看看java中的源碼 |
---|
後處理器
Spring中定義了很多後處理器
每個bean在創建完成之前,都會有一個後處理過程,即再加工,對bean做出相應的改變和調整.
Spring-AOP中,就有一個專門的後處理器,負責通過原始業務組件(Service),再加工得到一個代理組件
自定義一個後處理器
自定義後處理器瞭解後處理器的運行
實體類
package per.leiyu.entity;
import javax.print.attribute.standard.PrinterURI;
/**
* @author 雷雨
* @date 2020/6/20 12:04
*/
public class User {
private Integer id;
public User() {
System.out.println("User的構造方法");
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
System.out.println("User的set");
}
public void initUser(){
System.out.println("初始化方法");
}
}
後處理類
需要繼承BeanPostProcessor接口
package per.leiyu.entity;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
/**
* @author 雷雨
* @date 2020/6/20 12:03
*
* 定義後處理器:
* 作用在bean創建之後,對bean進行再加工
*/
public class MyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object o, String s) throws BeansException {
System.out.println("後處理器1");
System.out.println("後處理器1"+o+s);
return null;
}
@Override
public Object postProcessAfterInitialization(Object o, String s) throws BeansException {
System.out.println("後處理器2");
System.out.println("後處理器2"+o+s);
return null;
}
}
Spring配置
<!-- 後處理器-->
<bean id="user1" class="per.leiyu.entity.User" init-method="initUser">
<property name="id" value="1"></property>
</bean>
<bean id="beanPostProcessor" class="per.leiyu.entity.MyBeanPostProcessor"></bean>
測試類
package service;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @author 雷雨
* @date 2020/6/20 12:08
*/
public class TestMyBeanPostProcessor {
@Test
public void test1(){
ApplicationContext context = new ClassPathXmlApplicationContext("/spring-context.xml");
}
}
結果 |
---|
在Spring中集成了很多後處理器,不需要我們自己來編寫代碼
常用處理器 |
---|
由此可得:我們的動態代理是在
目標Bean的後處理過程中生成的
完成的Bean聲明週期
構造
>>注入屬性 滿足依賴
>>後處理器前置過程
>>初始化
>>後處理器後置過程
>>返回
>>銷燬
Spring和MyBatis整合
配置數據源
將數據源配置到項目中
引入jdbc.properties配置文件
#jdbc.properties
jdbc.driver =com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis?useUnicode=true
jdbc.username=root
jdbc.passward=123456
#jdbc.xxx的jdbc是標識名,可以修改,是爲了我們方便記憶和書寫,你如果願意,也可以修改爲aaa.xxx
整合Spring配置文件和properties配置文件
創建一個Spring的配置文件,然後把MyBatis的配置可以直接寫入,這樣就不用再單獨給MyBatis建立配置文件
<?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:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:property-placeholder location="jdbc.properties"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<!--使用$佔位符索引到jdbc.properties配置文件中的具體內容-->
<property name="driver" value="${jdbc.driver}" />
<!-- 轉義字符& 方法在下面標籤中有具體演示-->
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.passward}" />
</bean>
</beans>
Druid監控中心
<!-- web.xml-->
<servlet>
<servlet-name>DruidStatView</servlet-name>
<servlet-class>com.alibaba.druid.support.http.StatViewServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>DruidStatView</servlet-name>
<url-pattern>/druid/*</url-pattern>
</servlet-mapping>
測試監控中心
配置tomcat,並訪問protocol://ip:port/project/druid/index.html
整合MyBatis
將SqlSessionFactory.DAO.Service配置到項目中
導入依賴
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>4.2.4.RELEASE</version>
</dependency>
<!-- MyBatis-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.3.0</version>
</dependency>
<!--Spring整合MyBatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.2.3</version>
</dependency>
<!-- Mysql驅動-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.32</version>
</dependency>
配置SqlSessionFactory
首先這個是一個FactoryBean,而FactoryBean是用來創建複雜對象的,那麼SqlSessionFactory就是爲了創建SqlSessionFactory的複雜的對象
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- 注入連接池-->
<property name="dataSource" ref="dataSource"></property>
<property name="mapperLocations">
<list>
<value>classpath:per/leiyu/dao/*.xml</value>
</list>
</property>
<!-- 爲 dao-mapper文件中的實體 定義缺省包路徑-->
<!-- 如:<select id="queryAll" resultType="User"> 中User類可以不定義包-->
<property name="typeAliasesPackage" value="per.leiyu.entity"></property>
</bean>
創建一個UserDao
爲了測試我們的MyBatis的整合
package per.leiyu.dao;
import per.leiyu.entity.User;
import java.util.List;
/**
* @author 雷雨
* @date 2020/6/20 16:54
*/
public interface UserDao {
List<User> queryUsers();
}
創建實體類
package per.leiyu.entity;
import java.util.Date;
/**
* @author 雷雨
* @date 2020/6/20 16:55
*/
public class User {
private Integer id;
private String username;
private String passward;
private Boolean gender;
private Date registTime;
public User() {
super();
}
public User(Integer id, String username, String passward, Boolean gender, Date registTime) {
this.id = id;
this.username = username;
this.passward = passward;
this.gender = gender;
this.registTime = registTime;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", passward='" + passward + '\'' +
", gender=" + gender +
", registTime=" + registTime +
'}';
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassward() {
return passward;
}
public void setPassward(String passward) {
this.passward = passward;
}
public Boolean getGender() {
return gender;
}
public void setGender(Boolean gender) {
this.gender = gender;
}
public Date getRegistTime() {
return registTime;
}
public void setRegistTime(Date registTime) {
this.registTime = registTime;
}
}
配置對象關係映射
這裏是基於UserDao接口
寫sql語句
這裏的配置方式和MyBatis中完全相同
<?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">
<!-- 指明該xml文件時用來描述那個dao接口的-->
<mapper namespace="per.leiyu.dao.UserDao">
<select id="queryUsers" resultType="User">
select id,username,passward,gender,regist_time
from t_user
</select>
</mapper>
創建測試類
package per.leiyu;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import per.leiyu.dao.UserDao;
import per.leiyu.entity.User;
import java.util.List;
/**
* @author 雷雨
* @date 2020/6/20 17:08
*/
public class TestSpringMyBatis {
@Test
public void test(){
ApplicationContext context = new ClassPathXmlApplicationContext("/spring-context.xml");
SqlSessionFactory sqlSessionFactory =(SqlSessionFactory) context.getBean("sqlSessionFactory");
SqlSession sqlSession = sqlSessionFactory.openSession();
UserDao mapper = sqlSession.getMapper(UserDao.class);
List<User> users = mapper.queryUsers();
for (User user : users) {
System.out.println(user);
}
}
}
成功讀取到了數據庫中數據 |
---|
給出文件結構 |
---|
配置MapperScannerConfigurer
管理DAO實現類的創建,並創建DAO對象,存入工廠管理
- 掃描所有DAO接口,去構建DAO實現
- 將DAO實現存入工廠管理
- DAO實現對象在工廠中的id是:
首字母小寫的-接口的類名
,例如:UserDAO==>userDAO,OrderDAO==>orderDAO
- 意思就是如果你的Dao接口是UserDAO,那麼它在工廠中的bean對象就是userDAO,雖然這個bean我們看不到,但是可以正常的使用,是Spring動態爲我們創建的
<!-- mapperScannerConfigurer-->
<bean id="mapperScannerConfigurer2" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!--dao接口所在的包,如果有多個包,可以用逗號或分號分隔
<property name="basePackage" value="dao接口所在的包,多個包用逗號分隔"></property>
-->
<property name="basePackage" value="per.leiyu.dao"></property>
<!-- 如果工廠中只有一個SqlSessionFactory的bean,此配置可省略-->
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property>
</bean>
測試Spirng爲我們動態的生成了Dao接口的實現類對象
那麼我們能夠使用這個動態創建出來的對象調用Dao接口的方法就代表Spirng爲我們動態的生成了Dao接口的實現類對象
@Test
public void testMapperScannerConfigurer(){
ApplicationContext context = new ClassPathXmlApplicationContext("/spring-context.xml");
//這個bean對象在Spring的配置文件中沒有註冊,這是我們根據規則推測出來的
UserDao userDao=(UserDao) context.getBean("userDao");
List<User> users = userDao.queryUsers();
for (User user : users) {
System.out.println(user);
}
}
也能正常的打印我們查詢的結果 |
---|
配置service
創建接口和實現類
都是基於我們之前的UserDao(我們做的就是關於UserDao的service實現)
package per.leiyu.service;
import per.leiyu.entity.User;
import java.util.List;
/**
* @author 雷雨
* @date 2020/6/20 20:49
*/
public interface UserService {
public List<User> queryUser();
}
package per.leiyu.service;
import per.leiyu.dao.UserDao;
import per.leiyu.entity.User;
import java.util.List;
/**
*
* @author 雷雨
* @date 2020/6/20 20:50
*/
public class UserServiceImpl implements UserService {
//這裏需要用到UserDao,我們使用了Spring IOC的思想
private UserDao userDao;
public UserDao getUserDao() {
return userDao;
}
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
@Override
public List<User> queryUser() {
return userDao.queryUsers();
}
}
將service註冊進Spring
<!-- UserService-->
<bean id="userService" class="per.leiyu.service.UserServiceImpl">
<property name="userDao" ref="userDao"></property>
</bean>
創建測試
@Test
public void testUserService(){
ApplicationContext context = new ClassPathXmlApplicationContext("/spring-context.xml");
UserService userService =(UserService) context.getBean("userService");
List<User> users = userService.queryUser();
for (User user : users) {
System.out.println(user);
}
}
service成功打印數據庫數據 |
---|
事務
配置DataSourceTransactionManager
事務管理器,其中持有DataSource,可以控制事務功能(commit,rollback)等
<!--DataSourceTransactionManager -->
<!-- 引入一個事務管理器,其中依賴DataSource,藉以獲得連接,進而控制事務邏輯-->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
- 注意:DataSourceTransactionManager和SqlSessionFactoryBean要注入同一個DataSource的Bean,否則事務控制失敗
配置事務通知
基於事務管理器,進一步定製,生成一個額外功能:Advice
此Advice可以切入任何需要事務的方法,通過事務管理器爲方法控制事務.
我們之前講過額外功能,而事務就是不屬於我們業務的額外功能,而且事務有一個特點:
在覈心功能之前開啓,在覈心功能執行之後提交
----因此是一個環繞通知
Spring中爲我們定製了管理事務的通知的標籤
- 爲了不同方法都寫一個事務屬性,這樣做的好處是,當執行不同的方法時,可以切換到不同的事務屬性執行-----控制事務更加精密
事務屬性
1.隔離級別
isolation
隔離級別當多事務併發時,事務之間是有隔離級別的,隔離級別越高,說明共享的數據越少,併發的效率就越低,但是對應着安全性就越高
隔離級別 | 解釋 |
---|---|
default | (默認值)(採用數據庫的默認的設置)(建議) |
read-uncommited | 讀未提交 |
read-commited | 讀提交(Oracle數據庫默認的隔離級別) |
repeatable-read | 可重複度(Mysql數據庫默認的隔離級別) |
serialized-read | 序列化讀 |
隔離級別由低到高:
read-uncommited
–<read-commited
–<repeatable-read
–<serialized-read
- 安全性:級別越高,多事務併發時,越安全.因爲共享的數據越來越少,事務間批次干擾減少
- 併發性:級別越高,多事務併發時,併發越差.因爲共享的數據越來越少,事務間阻塞情況增多
事務併發時的安全問題:
- 髒讀:讀髒數據,一個事務讀取到另一個事務還未提交的數據
- 大於等於read-commited 可防止
- 不可重複讀:一個事務內多次讀取一行數據的相同內容,其結果不一致(主要針對更新中發生的,事務A讀取一行數據,事務B也讀取這行數據並更新了數據,事務A重新讀取這行數據的時候發現這一行內容前後不一致)
- 大於等於 repeatable-read可防止
- 幻影讀:一個書屋內多次讀取一張表中的相同內容,其結果不一致(主要指的是行數不一致)(主要針對的是刪除和添加中發生的,事務A讀取這個表的數據,事務B也讀取這個表數據並刪除(或者添加)了部分數據,事務A重新讀取這個表數據的時候發現這一格表的內容(行數都不相同)前後不一致)
- serialized-read 可防止
2.傳播行爲
propagation
傳播行爲
事務嵌套:事務A(Service A)中要調用事務B(Service B),那麼如果事務B發生了錯誤會回滾,但是由於原子的隔離性(事務A和事務B不屬於同一個原子),那麼可能事務A的操作就沒能得到回滾
假如事務A是一個查詢操作,本身不存在對數據的操作,那麼不會發生錯誤
假如事務A是一個增刪改的操作,那麼A不回滾,就會造成錯誤
所以我們要討論事務之間的傳播性
當涉及到事務嵌套(Service調用Service)中,可能會出現問題
- SUPPORTS = 不存在外部事務,則不開啓新事物;存在外部事務,則合併到外部事務中.
適合查詢
- REQUIRD = 不存在外部事務,則開啓新事務;存在外部事務,則合併到外部事務中.(默認值)
適合增刪改
3.讀寫性
readonly
讀寫性
- true:只讀,可提高查詢效率(適合查詢)
- false:可讀可寫.(默認值)(適合增刪改)
4.事務超時
timeout
:事務超時時間
當前事務所需操作的數據被其他事務佔用,則等待.
- 100:自定義等待時間爲100(秒)
- -1:由數據指定等待時間,默認值.(建議)
5.事務回滾
roolback
回滾屬性
- 如果事務中拋出RuntimeException,則自動回滾
- 如果事務中拋出CheckException(非運行時異常Exception),不會自動回滾,而是默認提交事務
- 處理方案:將CheckException轉換成RuntimeException上拋,或設置rollback-for=“exception”
編織
將剛纔配置爲事務通知編織到我們我們的Service的實現類中
註解開發
聲明bean
用於替換自建類型組件的<bean…>標籤;可以更快速的聲明bean
@Service
業務類專用
@Repository
dao實現類專用
@Controller
web層專用
@Component
通用
@Scope
用戶控制bean的創建模式(默認爲單例創建模式,可以根據需要設置爲多例的創建模式)
//@Service說明 此類是一個業務類,需要將此類納入工廠,等價替換掉<bean class="xxx.UserServiceImpl">
//@Service默認beanId= 首字母小寫的類名"userServiceImpl"
//@Service("userService") 自定義beanId爲userService
@Service//聲明bean,且id是userServiceImpl
@Scope("singleton")//聲明創建模式,默認爲單例模式;@Scope("prototype")即可設置爲多例模式
public class UserServiceImpl implements UserService{
...
}
注入
用於完成bean中屬性值的注入
@Autowired
基於類型自動注入
@Resource
基於名稱自動注入
@Qualifier("userDao")
限定要自動注入的bean的id,一般和@Autowired聯用
@Value
注入簡單類型數據(jdk8種+String)
@Service
public class UserService implements UserService{
@Autowired //注入類型爲UserDAO的bean
@Qualifier("userDAO2")//如果有多個類型爲UserDAO的bean,可以用此註解在其中選在其中一個
private UserDAO userDAO;
@Value("leiyu")//注入String
private String name;
...
}
- 因爲僅僅通過類型注入的話,可以會有多個類型都相同,但是注入的bean只能是一個,所以需要再控制bean的id
事務控制
用於控制事務切入
@Transactional
工廠配置中的<tx:advice…和<aop:config…可以省略
//類中的每個方法都切入事務(有自己的事務控制的方法除外)
@Transactional(isolation=Isolation.READ_COMMITED,proagation=Propagation.REQUIRED,readonly=false,rollbackFor=Exception.class,timeout=-1)
public class UserServiceImpl implements UserService{
...
//該方法自己的事務控制,僅對該方法有效
//如果即在類上加了事務控制,又在方法上加了事務控制,那麼以方法上的爲準
@Transactional(propagation=Propagation.SUPPORTS)
public List<User> queryAll(){
return userDao.queryAll();
}
public void save(User user){
userDao.save(user);
}
}
註解所需的配置
<!-- 告知Spring,那些包中 有被註解的類.方法 屬性-->
<!-- 只有告知了Spring被註解的地方,Spring才能去找到這些類並去幫我們創建對象 -->
<!-- <context:component-scan base-package="per.leiyu"></context:component> -->
<context:component-scan base-package="per.leiyu"></context:component>
<!--告知Spring,@Transactional在定製事務時,是基於txManager=DataSourceTransactionManager -->
<!-- 事務的管理是需要我們的事務管理器的,事務管理器的功能在上面有說明 -->
<tx:annotation-driven transaction-manager="txManager">
AOP開發
註解使用
package per.leiyu;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import java.util.Properties;
/**
* @author 雷雨
* @date 2020/6/21 16:08
*/
@Aspect //聲明此類是一個切面類:會包含切入點(pointcut)和通知(advice)
@Component //聲明組件 進入工廠
public class MyAspect {
//定義切入點
// 這裏爲了定義切入點,因爲基於聲明的Spring中切入點定義是有一個id值的,我們這的方法名就相當於id值
@Pointcut("execution(* per.leiyu.service.UserServiceImpl.*(..))")
public void pc(){}
@Before("pc()")
public void mybefore(JoinPoint a){
System.out.println("target"+a.getTarget()); //獲取當前的目標是那個(方法)
System.out.println("args:"+a.getArgs()); //獲取當前目標方法的參數
System.out.println("method's name"+a.getSignature().getName()); //當前調用這個方法的名稱是
System.out.println("before");
}
@AfterReturning(value="pc()",returning = "ret")//後置通知
public void myAfterReturning(JoinPoint a, Object ret){
System.out.println("after"+ret);
}
public void myInterceptor(ProceedingJoinPoint p) throws Throwable{ //環繞通知
System.out.println("interceptor1");
Object ret = p.proceed(); //調用核心功能
System.out.println("interceptor2");
}
@AfterThrowing(value = "pc()",throwing = "ex")
public void myThrows(JoinPoint jp,Exception ex){ //異常通知
System.out.println("throws");
System.out.println("====="+ex.getMessage());
}
}
給AOP註解添加Spring配置才能使用
<!-- 添加如下配置,啓動AOP註解-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
Spring集成JUnit
導入依賴
<!--Junit單元測試 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>4.2.4.RELEASE</version>
</dependency>
編碼
可以免去工廠的創建過程
可以直接將要測試的組件注入到測試類
// 測試啓動 啓動Spring工廠 並且當前測試類也會被工廠啓動
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:spring-context.xml")
public class TestSpringMyBatis {
//這兩個註解是將屬性UserService屬性注入
@Autowired
@Qualifier("userService")
private UserService userService;
@Test
public void testUserService2(){
List<User> users = userService.queryUser();
for (User user : users) {
System.out.println(user);
}
}
}
測試結果 |
---|
我是雷雨,一個
普本科
的學生,主要專注於Java後端和大數據開發
如果這篇文章有幫助到你,希望你給我一個
大大的贊
如果有什麼問題,希望你能留言
和我一起研究
,學習靠自覺,分享靠自願