跳至主要內容

1.Spring框架

holic-x...大约 44 分钟框架Spring

[JAVA]-Spring框架

[TOC]

1.Spring框架简介

【1】Spring基础

🔖什么是Spring?

Spring是分层的JavaSE/JavaEE 一站式服务, 轻量级的开源框架

JavaEE程序在服务器被分为三层, web层[表现层] 业务逻辑层, 数据访问层[集成层,持久层]

  • web层 SpringMVC --表现层

  • 业务层 Sping的Bean的管理, AOP的管理,事务管理

  • 持久层 SpringJDBCTemplate ORM模块(整合其他的ORM比如MyBatis或者HIbernate)

🔖Spring的核心

IOC控制反转和AOP面向切面的编程

Spring官网open in new window

🔖Spring的由来

Spring的出现是为了取代EJB的臃肿、低效、脱离现实

Expert One-to-One J2EE Design and Development

Expert One-to-One J2EE Development without EJB

Rod Johnson

2002年

​ Expert One-to-One J2EE Design and Development分析JavaEE开发 使用的技巧 EJB

2004 年

​ Expert One-to-One J2EE Development without EJB 客观分析了JavaEE是需要什么,推出一个全新的框架,后来是Spring

📌Spring 框架的优点(重点)

方便解耦,简化开发

​ Spring就是一个大工厂,可以将所有对象创建和依赖关系维护,交给Spring管理

AOP编程的支持

​ Spring提供面向切面编程,可以方便的实现对程序进行权限拦截、运行监控等功能

声明式事务的支持

​ 只需要通过配置就可以完成对事务的管理,而无需手动编程

方便程序的测试

​ Spring对Junit4支持,可以通过注解方便的测试Spring程序

方便集成各种优秀框架

​ Spring不排斥各种优秀的开源框架,其内部提供了对各种优秀框架(如:Struts、Hibernate、MyBatis、Quartz等)的直接支持

降低JavaEE API的使用难度

​ Spring 对JavaEE开发中非常难用的一些API(JDBC、JavaMail、远程调用等),都提供了封装,使这些API应用难度大大降低

🔖Spring体系结构

【2】Spring入门程序

🔖基于eclipse的示例

1>a.新建maven工程

创建maven工程

​ 选择org.apache.maven.archetypes maven.archetypes-webapp 1.0, 等待文件导入完成,进入下一步的选择

​ 设置相关内容,完成maven工程创建

修改相关的配置

工程创建完毕可以看到工程出现如下内容,需要进行相关配置

  • 在pom.xml删除不必要的依赖,并加入以下内容:用以控制jdk版本
<build>
    <finalName>SpringBase</finalName>
    <plugins>  
        <plugin>  
            <groupId>org.apache.maven.plugins</groupId>  
            <artifactId>maven-compiler-plugin</artifactId>  
            <version>2.3.2</version>  
            <configuration>  
                <source>1.8</source>  
                <target>1.8</target>  
            </configuration>  
        </plugin>  
    </plugins>  
  </build>
  • 需要更改web.xml头文件的web版本

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
 http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" version="3.0">

  <display-name>Archetype Created Web Application</display-name>
</web-app>
  • 通过Navigator视图完成数据的修改

视图可以通过window –>show view 显示

更改如下内容:修改jdk版本、web版本

  • 工程上右键builder path:修改如下jar包,并将工程添加指定的服务器

​ 完成配置之后检查配置信息是否成功,右键工程选择properties->Project Facets,查看对应的jdk版本和web版本是否一致,如果不一致则要进行查阅、修改

​ 完成上述配置,工程右键 maven—update project 更新查看,出现如下内容,第一步操作完成

2>导入jar包

1.需要在pom.xml 文件添加如下数据,点击保存等待更新完成:

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<spring-version>5.0.8.RELEASE</spring-version>
		<logging-version>1.2</logging-version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-context</artifactId>
			<version>${spring-version}</version>
		</dependency>

		<dependency>
			<groupId>commons-logging</groupId>
			<artifactId>commons-logging</artifactId>
			<version>${logging-version}</version>
		</dependency>
	</dependencies>

​ 更新完成查看工程Maven Dependcies出现如下jar包,并且可以在指定的仓库中查询到相应的内容,说明jar包导入成功

​ 检查仓库中是否有如下内容:C:\Users\用户名.m2\repository\org\springframework

3>准备核心配置文件

新建applicationContext.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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
</beans>

4>编写程序进行测试

HelloServie接口:

public interface HelloServie{
	public void sayHello();
}

HelloServieImpl实现类:

public class HelloServieImpl implements HelloService{
	private String info;	
	public String getInfo() {
		return info;
	}
	public void setInfo(String info) {
		this.info = info;
	}
	@Override
	public void sayHello() {
		System.out.println("hello"+info);
	}
}

HelloServiceTest测试类:

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.guigu.spring.a_quickstart.HelloService;
import com.guigu.spring.a_quickstart.HelloServiceImpl;
public class HelloServiceTest {
	public static void main(String[] args) {
		/**
		 * 1.普通测试方式
		 */
		HelloServiceImpl helloServiceImpl = new HelloServiceImpl();
		helloServiceImpl.setInfo("杭州归谷普通测试......");
		helloServiceImpl.sayHello();
		/**
		 * 2.spring测试
		 * 需要在核心配置文件中先配置相关内容
		 * Spring中使用工厂 +反射 +配置文件的方式 实例化这个对象  
		 */
        ApplicationContext applicationContext =new ClassPathXmlApplicationContext("spring/applicationContext.xml");
        HelloService  service =(HelloService) applicationContext.getBean("helloService");
        service.sayHello();
	}
}

ApplicationContext.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"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
	<!--1.快速入门 ========================================================= -->
	<!-- - bean:就是定义一个具体的类 可以是一个对象 id 属性 就是一个合法的标识符 class 是当前类的全名称 -->
	<bean id="helloService" class="com.guigu.spring.a_quickstart.HelloServiceImpl">
		<property name="info" value="杭州归谷"></property>
	</bean>
	<!--1.end快速入门 ====================================================== -->
</beans>

2.IOC容器

【1】理解IOC容器和DI依赖注入

​ IOC容器 Inverser Of Control反转控制 就是把原来在程序中创建HelloService对象的控制权限交给Spring管理,简单的说 就是HelloService对象控制器被反转到Spring框架内

​ DI: 依赖注入 在Spring框架负责创建Bean对象的时候 动态的将数据注入到Bean的组件

【2】IOC容器装配Bean的方式(XML配置方式)

IOC容器装配Bean的方式就是实例化该对象的方式,Spring提供了三种装配Bean的方式

-使用类的构造函数进行初始化  (默认是无参的构造函数)
-使用静态工厂方法:利用factory-method=属性指定静态工厂方法
-使用实例化工厂方法 (工厂模式)
/**       
 * 通过3种方式获取Bean对象   
 *  a.根据获取Bean对象不同的方式定义相关内容
 *  	根据构造器获取
 *  	利用静态工厂方法获取
 *  	利用实例化工厂方法获取
 *  b.在applicationContext.xml中进行配置
 *  c.获取Bean对象进行测试
 *  	url : "spring/applicationContext.xml" 指定核心配置文件位置
 *  	ApplicationContext ac = new ClassPathXmlApplicationContext(url);
 *  	BeanClass bean = ac.getBean("id");
 */

a.构造函数初始化

定义Bean实体,提供无参构造函数

Bean.java:
/**       
 * 方式1:利用构造器获取   
 */
public class Bean {
	// 定义一个无参的构造方法,系统有默认的无参构造器
}

在核心配置文件applicationContext.xml中定义bean对象

<!-- 
	a.构造函数初始化 
		<bean id="" class=""></bean>
		id:实例对象名称
		class:对应类全名
-->
<bean id="bean" class="com.guigu.spring.b_instance.Bean"></bean>

编写代码获取指定对象进行测试

/**
 *  方式1:通过构造器获取Bean对象
 */
	@Test
	public void testGetBean1() {
		ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring/applicationContext.xml");
        Bean bean =(Bean) applicationContext.getBean("bean");
        System.out.println("方式1:"+bean);
	}

查看测试结果

b.使用静态工厂方法

定义Bean实体,工厂方法设计模式

ClientService.java:
/**
 * 方式2:利用静态工厂方法获取 此处类似于单例模式
 */
public class ClientService {
	// a.构造函数私有化
	private ClientService() {

	}

	// b.提供一个静态的私有的属于自己的全局变量进行初始化
	private static ClientService clientService = new ClientService();

	// c.通过提供公有方法返回该对象
	public static ClientService createInstance() {
		return clientService;
	}
}

在核心配置文件applicationContext.xml中定义bean对象

<!-- 
  	 b.使用静态工厂方法 
  	 	<bean id="" class="" factory-method=""></bean>
  	 	id:实例对象名称
  	 	class:对应类全名
  	 	factory-method:返回该实例对象的工厂方法名
-->
<bean id="clientService" class="com.guigu.spring.b_instance.ClientService" 				factory-method="createInstance"></bean>

编写代码获取指定对象进行测试

查看测试结果

image-20210503090512110

c.使用实例化工厂方法

定义Bean实体,实例化工厂方法设计模式

UserService.java:
/**
 * 方式3:实例化工厂方法
 * a.先创建一个实体类xxx
 * b.通过xxxFactory进行实例化并返回某个对象
 */
public class UserService {

}
UserServiceFactory.java:
public class UserServiceFactory {
	// 创建UserService 是通过UserServiceFactory 完成实例化
	private static UserService userService = new UserService();
	// 提供共有方法返回实例化对象
	public UserService createUserServiceInstance() {
		return userService;
	}
}

在核心配置文件applicationContext.xml中定义bean对象

<!-- 
  	 c.使用实例化工厂方法 
  	<bean id="xxxFactory" class="xxxFactory工厂类全名"></bean>
  		<bean id="实例对象名称" factory-bean="xxxFactory" factory-method="调用工厂方法名"></bean>
-->
  	 	<bean id="userServiceFactory" class="com.guigu.spring.b_instance.UserServiceFactory"></bean>
  	 	<bean id="userService" factory-bean="userServiceFactory" factory-method="createUserServiceInstance"></bean>

编写代码获取指定对象进行测试

查看测试结果

image-20210503090638021

【3】Bean的其他属性配置—作用域

定义Bean的时候可以指定以下方式

image-20210503090716666

singleton

prototype

image-20210503090746152

定义实体类product

/**       
 * 定义Product类,提供无参构造方法   
 */
public class Product {
	public Product() {
		System.out.println("product实例化......");
	}
}

核心配置文件进行Bean配置

<!-- a.单例模式配置:scope="singleton" -->
<bean id="product1" class="com.guigu.spring.c_scope.Product" scope="singleton"></bean>
<!-- b.多例模式配置:scope="prototype" -->
<bean id="product2" class="com.guigu.spring.c_scope.Product" scope="prototype"></bean>
<!-- c.还有其他作用域设置需根据实际项目需求进行选择,详细参考实际文档 -->

编写测试文件进行测试

public class ProductTest {
	@Test
	public void testSingletonScope() {
		/**
		 * 单例模式测试结果显示:
		 * product被实例化1次,且均为执行同一个对象(指向对象地址相同)
		 */
		ApplicationContext ac =new ClassPathXmlApplicationContext("spring/applicationContext.xml");
        Product p1 = (Product) ac.getBean("product1");
        Product p2 = (Product) ac.getBean("product1");
        System.out.println(p1);
        System.out.println(p2);
	}
	
	@Test
	public void testPrototypeScope() {
		/**
		 * 多例模式测试结果显示:
		 * product被实例化多次,且均指向不同的对象(指向对象的地址不同)
		 */
		ApplicationContext ac =new ClassPathXmlApplicationContext("spring/applicationContext.xml");
        Product p1 = (Product) ac.getBean("product2");
        Product p2 = (Product) ac.getBean("product2");
        System.out.println(p1);
        System.out.println(p2);
	}
}

Singleton测试结果:

image-20210503090907282

Prototype测试结果:

image-20210503090922038

【4】Bean属性依赖注入

Bean属性的注入分为构造器注入、setter方法注入

步骤:

1.定义实体对象
2.在核心配置文件中定义相关属性
3.编写测试文件进行测试

a.第一种构造器注入

实体类定义如下:
public class Car {
	// 定义Car类的两个属性
	private String carType ;
	private String carPrice;
	// 提供带参数的构造器
	public Car(String carType, String carPrice) {
		super();
		this.carType = carType;
		this.carPrice = carPrice;
	}
	@Override
	public String toString() {
		return "Car [carType=" + carType + ", carPrice=" + carPrice + "]";
	}
}
(1)使用构造函数的参数类型进行注入

​ 在核心配置文件中定义相关属性:

​ 编写测试代码:

​ 测试结果:

(2)构造函数的参数的索引、下标

​ 在核心配置文件中定义相关属性:

​ 编写测试代码:

​ 测试结果:

(3)构造函数参数的名称

​ 在核心配置文件中定义相关属性:

​ 编写测试代码:

​ 测试结果:

b.Setter方法注入

(1)Setter方法注入简单数据

创建Cellphone实体:

public class Cellphone {
	// 定义Cellphone的两个属性
	private String phoneName;
	private String phonePrice;
	// 提供setter方法
	public void setPhoneName(String phoneName) {
		this.phoneName = phoneName;
	}
	public void setPhonePrice(String phonePrice) {
		this.phonePrice = phonePrice;
	}
	@Override
	public String toString() {
		return "Cellphone [phoneName=" + phoneName + ", phonePrice=" + phonePrice + "]";
	}
}

核心文件applicationContext.xml配置:

代码测试:

测试结果:

(2)Setter方法注入复杂对象

在上述内容的基础上定义Person实体拥有Car、Cellphone属性:

public class Person {
	// 定义Person的3个属性
	private String name;
	private Car car;
	private Cellphone phone;
	// 提供setter方法
	public void setName(String name) {
		this.name = name;
	}
	public void setCar(Car car) {
		this.car = car;
	}
	public void setPhone(Cellphone phone) {
		this.phone = phone;
	}
	@Override
	public String toString() {
		return "Person [name=" + name + ", car=" + car + ", phone=" + phone + "]";
	}
}

核心文件applicationContext.xml配置:

代码测试:

测试结果:

(3)集合属性的注入

定义测试对象CollectionBean:

public class CollectionBean {
	// 定义CollectionBean的四个属性
	private List<String> list;
	private Set<Integer> set;
	private Map<String,String> map;
	private Properties properties;
	// 提供相应的setter方法
	public void setList(List<String> list) {
		this.list = list;
	}
	public void setSet(Set<Integer> set) {
		this.set = set;
	}
	public void setMap(Map<String, String> map) {
		this.map = map;
	}
	public void setProperties(Properties properties) {
		this.properties = properties;
	}
	@Override
	public String toString() {
		return "CollectionBean [list=" + list + ", set=" + set + ", map=" + map + ", properties=" + properties + "]";
	}
}

核心文件applicationContext.xml配置:

<bean id="collectionBean" class="com.guigu.spring.d_di.CollectionBean">
		<!-- 作为参考,此处注入的均为普通数据类型的集合元素,可以注入其他的bean元素 -->
		<!-- 
			a.list集合属性的注入 
			<property name="属性名称">
				<list>
					<value>注入值:普通数据</value>
				</list>
			</property>
		-->
		<property name="list">
			<list>
				<value>list项1</value>
				<value>list项2</value>
				<value>list项3</value>
			</list>
		</property>
		<!--  
			b.set集合属性的注入
			<property name="属性名称">
				<set>
					<value>注入值:普通数据</value>
				</set>
			</property>
		-->
		<property name="set">
			<set>
				<value>1</value>
				<value>2</value>
				<value>3</value>
			</set>
		</property>
		
		<!--  
			c.map集合属性的注入
			<property name="属性名称">
				<map>
					<entry key="key值" value="value值"></entry>
				</map>
			</property>
		-->
		<property name="map">
			<map>
				<entry key="username" value="noob"></entry>
				<entry key="password" value="000000"></entry>
			</map>
		</property>
		
		<!--  
			d.properties集合属性的注入
			<property name="属性名称">
				<props>
					<prop key="key值">value值</prop>
				</props>
			</property>
		-->	
		<property name="properties">
			<props>
				<prop key="company">杭州归谷</prop>
				<prop key="address">浙江杭州</prop>
			</props>
		</property>
</bean>

代码测试:

测试结果:

image-20210503092012441

image-20210503092014806

(4)P命名空间的使用

核心配置文件中引入P标签:

<?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:p="http://www.springframework.org/schema/p"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">

创建实体类Computer:

public class Computer {
	// 提供Computer的两个属性
	private String cname;
	private String cprice;
	// 提供setter方法
	public void setCname(String cname) {
		this.cname = cname;
	}
	public void setCprice(String cprice) {
		this.cprice = cprice;
	}
	@Override
	public String toString() {
		return "Computer [cname=" + cname + ", cprice=" + cprice + "]";
	}
}

核心配置文件中进行配置:

编写测试代码:

测试结果:

【5】多个xml配置文件

方式一 :在类中执行的时候添加多个文件(不推荐使用)

appliicationContext.xml定义如上述所示,appliicationContext2.xml定义如下

@Test
	public void testXML1() {
		ApplicationContext ac =new ClassPathXmlApplicationContext("spring/applicationContext.xml","spring/applicationContext2.xml");
		Person p1 = (Person) ac.getBean("p1");
		System.out.println(p1);
	}

方式二 :在一个总的文件中引入不同的配置文件,然后在代码中使用这个总的配置文件

applicationContext_all.xml:

// 调用总的文件以引入不同的配置文件
	@Test
	public void testXML2() {
		ApplicationContext ac =new ClassPathXmlApplicationContext("spring/applicationContext_all.xml");
		Person p2 = (Person) ac.getBean("p2");
		System.out.println(p2);
	}

【6】IOC容器装配Bean(注解的方式)

a.使用注解定义Bean

定义配置文件

引入context命名空间

配置Spring的自动扫描和要扫描的包

applicationContext.xml:
<!-- 配置注解bean自动扫描 -->
 	<context:annotation-config/>
 	<!-- 定义具体扫描的包 -->
 	<context:component-scan base-package="com.guigu.spring.a_beandefinition,com.guigu.spring.b_scope">
</context:component-scan>

需要在Bean对象中定义@Component 声明组件

测试代码

测试结果

image-20210503092818892

除了@Component外,Spring提供了3个功能基本和@Component等效的注解

@Repository 用于对DAO实现类进行标注

@Service 用于对Service实现类进行标注

@Controller 用于对Controller实现类进行标注

这三个注解是为了让标注类本身的用途清晰,Spring已对其增强

(1)@Repository测试

(2)@Service测试

(3)@Controller测试

(4)注解分析

@Autowired默认是根据类型自动注入

​ 通过@Autowired的required属性,设置一定要找到匹配的Bean

​ 使用@Qualifier指定注入Bean的名称

​ 使用Qualifier 指定Bean名称后,注解Bean必须指定相同名称

@Autowired(required=false)默认是true 如果注入不成功就会报错 False 空指针

​ 如果存在多个相同的类型注入就会报错,可以再使用类型的前提下再次明确使用哪个Bean对象就是使用

@Qualifier("userService") 明确指定使用哪个Bean对象

​ 使用@Resource可以优化上方两个注解:

@Resource(name="userService")

​ 等价于@Autowired(required=false)、@Qualifier("userService")

b.Bean的范围

在applicationContext.xml中声明要进行扫描的包

定义Car类进行作用范围测试

image-20210503093110080

编写测试文件

image-20210503093144021

测试结果分析

​ 如果指定作用范围为prototype多例模式显示如下内容

image-20210503093205975

​ 如果指定作用范围为singleton单例模式则显示如下内容

image-20210503093213739

c.注册Bean的注解

Spring3.0以JavaConfig为核心,提供使用Java类定义Bean信息的方法

  • @Configuration 指定POJO类为Spring提供Bean定义信息

  • @Bean 提供一个Bean定义信息

定义model对象Cellphone、Product

Cellphone.java:
public class Cellphone {
	private String cname;
	private String cprice;
	public String getCname() {
		return cname;
	}
	public void setCname(String cname) {
		this.cname = cname;
	}
	public String getCprice() {
		return cprice;
	}
	public void setCprice(String cprice) {
		this.cprice = cprice;
	}
	@Override
	public String toString() {
		return "Cellphone [cname=" + cname + ", cprice=" + cprice + "]";
	}
}
Product.java:
public class Product {
	private String pname;
	private String pprice;
	public String getPname() {
		return pname;
	}
	public void setPname(String pname) {
		this.pname = pname;
	}
	public String getPprice() {
		return pprice;
	}
	public void setPprice(String pprice) {
		this.pprice = pprice;
	}
	@Override
	public String toString() {
		return "Product [pname=" + pname + ", pprice=" + pprice + "]";
	}
}

定义POJO类定义Bean对象

BeanConfig.java:
/**       
 * Spring3.0以JavaConfig为核心,提供使用Java类定义Bean信息的方法
 * @Configuration 指定POJO类为Spring提供Bean定义信息
 * @Bean 提供一个Bean定义信息
 */
// 代表这个Bean对象是一个Bean的配置注解
@Configuration
public class BeanConfig {
	/**
	 * 方式1:@Bean
	 * 如果没有指定name属性,则表示方法名即为对应Bean的id
	 * 此处为initCellphone
	 */
	@Bean
	public Cellphone initCellphone() {
		Cellphone cp = new Cellphone();
		cp.setCname("诺基亚");
		cp.setCprice("5000");
		return cp;
	}
	/**
	 * 方式2:@Bean("xxx")
	 * 指定name属性表示该bean对象的id为指定的内容
	 */
	@Bean(name="product")
	public Product initProduct() {
		Product p = new Product();
		p.setPname("电冰箱");
		p.setPprice("10000");
		return p;
	}
}

编写测试类进行测试

image-20210503093350031

测试结果

image-20210503093401653

d.xml配置和注解配置综合案例

dao层、Service层定义

CustomerDAO .java:
public class CustomerDAO {

}
OrderDAO .java:
public class OrderDAO {

}
CustomerService.java:
public class CustomerService {
	// 1.使用注解的方式定义OrderDAO对象
	@Autowired(required=false)
	@Qualifier("orderDAO")
	private OrderDAO orderDAO;
	
	// 2.使用xml配置方式定义Customer对象,需根据需求提供相应的构造器或setter方法
	private CustomerDAO customerDAO;
	
	public void setCustomerDAO(CustomerDAO customerDAO) {
		this.customerDAO = customerDAO;
	}

	@Override
	public String toString() {
		return "CustomerService [orderDAO=" + orderDAO + ", customerDAO=" + customerDAO + "]";
	}
}

核心配置文件applicationContext.xml配置CustomerDAO

image-20210503093511603

编写测试文件

image-20210503093520456

测试结果

image-20210503093534637

3.AOP面向切面编程

【1】AOP的基础

什么是AOP

  • AOP Aspect Oriented Programing 面向切面编程

  • AOP采取横向抽取机制,取代了传统纵向继承体系重复性代码(性能监视、事务管理、安全检查、缓存)

  • Spring AOP使用纯Java实现,不需要专门的编译过程和类加载器,在运行期通过代理方式向目标类织入增强代码

  • AspectJ是一个基于Java语言的AOP框架,Spring2.0开始,Spring AOP引入对Aspect的支持,AspectJ扩展了Java语言,提供了一个专门的编译器,在编译时提供横向代码的织入

image-20210503093747635

AOP相关的术语

AOP术语说明
Joinpoint
(连接点)
所谓切入点是指我们要对哪些Joinpoint进行拦截的定义
Advice
(通知/增强)
所谓通知是指拦截到Joinpoint之后所要做的事情就是通知
通知分为前置通知,后置通知,异常通知,最终通知,环绕通知(切面要完成的功能)
Introduction
(引介)
引介是一种特殊的通知在不修改类代码的前提下, Introduction可以在运行期为类动态地添加一些方法或Field
Target
(目标对象)
代理的目标对象
Weaving
(织入)
是指把增强应用到目标对象来创建新的代理对象的过程
spring采用动态代理织入,而AspectJ采用编译期织入和类装载期织入
Proxy
(代理)
一个类被AOP织入增强后,就产生一个结果代理类
Aspect
(切面)
是切入点和通知(引介)的结合

image-20210503094210263

AOP的五类Advice

AOP联盟为通知Advice定义了org.aopalliance.aop.Interface.Advice

Spring按照通知Advice在目标类方法的连接点位置,可以分为5类

前置通知 org.springframework.aop.MethodBeforeAdvice

​ 在目标方法执行前实施增强

后置通知 org.springframework.aop.AfterReturningAdvice

​ 在目标方法执行后实施增强

环绕通知 org.aopalliance.intercept.MethodInterceptor

​ 在目标方法执行前后实施增强

异常抛出通知 org.springframework.aop.ThrowsAdvice

​ 在方法抛出异常后实施增强

引介通知 org.springframework.aop.IntroductionInterceptor

​ 在目标类中添加一些新的方法和属性

【2】Spring的AOP的应用

a.Spring的切面advisor

​ Advisor是切面 :就是对PointCut应用Advice (通常所说的Advisor指的是只有一个pointcut和一个Advice增强,这叫做简单的切面就是Advisor)

​ Aspect是切面 :可以有多个pointcut切点和多个advice增强

Advisor 切面分为以下几种情况:

​ Advisor :代表一般的切面,其中是包含一个切点pointcut和一个增强advice ,如果没有pointcut切点,此时advice增强本身就成为一个切面。没有切点的切面是对目标所有的方法都进行拦截

​ PointcutAdvisor: 代表具有切点的切面,可以指定拦截目标类中的哪些方法

​ IntroductionAdvisor:代表引介切面,针对引介通知使用切面。(了解)

1>不带有切点的切面 Advisor

不带有切点的切面是针对这个所有方法进行拦截

程序设计步骤:

a.编写代理接口和实现类
b.编写增强(增强有五种形式可供选择)
c.在spring的核心配置文件中进行配置
d编写测试代码进行分析

a.编写代理接口和实现类

CustomerDAO.java:
public interface CustomerDAO {
	// 定义增删改查方法
	public void add();
	public void update();
	public void delete();
	public void find();
}
CustomerDAOImpl.java:
public class CustomerDAOImpl implements CustomerDAO{
	@Override
	public void add() {
		System.out.println("添加客户...");
	}

	@Override
	public void update() {
		System.out.println("修改客户...");
	}

	@Override
	public void delete() {
		System.out.println("删除客户...");		
	}

	@Override
	public void find() {
		System.out.println("查找客户...");		
	}
}

b.编写增强(增强有五种形式可供选择)

自定义增强:实现相应的增强接口

public class MyMethodBeforeAdvice implements MethodBeforeAdvice{
	@Override
	public void before(Method arg0, Object[] arg1, Object arg2) throws Throwable {
		System.out.println("模拟增强代码...在执行目标方法之前执行...");
	}
}

c.在spring的核心配置文件中进行配置

定义被代理对象:CustomerDAO实现类
<bean id="customerDAO" class="com.guigu.spring.a_advisor.CustomerDAOImpl"></bean>
定义增强:自定义增强类MyMethodBeforeAdvice
<bean id="myMethodBeforeAdvice" class="com.guigu.spring.a_advisor.MyMethodBeforeAdvice"></bean>
创建代理:org.springframework.aop.framework.ProxyFactoryBean,配置以下属性
<bean id="customerDAOProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
- 对应目标(target)
<property name="target" ref="customerDAO"></property>
- 针对接口代理还是类代理(proxyInterfaces)
<property name="proxyInterfaces" value="com.guigu.spring.a_advisor.CustomerDAO"></property>
- 增强(interceptorNames)
<property name="interceptorNames" value="myMethodBeforeAdvice"></property>
可以通过查看ProxyFactoryBean源码分析这几个属性
</bean>

image-20210503094918512

d.编写测试代码进行分析

使用spring自带的测试类,需要在pom.xml中增加spring-test测试包

image-20210503094952255

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="classpath:spring/applicationContext.xml")
public class AdvisorTest {
	
	@Autowired
	@Qualifier("customerDAOProxy")
	private CustomerDAO customerDAO;

	@Test
	public void test() {
		// 测试增删改查
		customerDAO.add();
		customerDAO.update();
		customerDAO.delete();
		customerDAO.find();
	}
}

测试结果如下所示:

image-20210503095012278

2>带有切点的切面PointcutAdvisor
a.编写代理类(没有接口的类)
b.编写增强(增强有五种形式可供选择)
c.在spring的核心配置文件中进行配置,此处需要指定切点
d编写测试代码进行分析

a.编写代理类(没有接口的类)

UserDAO.java:
public class UserDAO {
	
	// 添加增删改查方法
	public void add() {
		System.out.println("添加用户......");
	}
	
	public void update() {
		System.out.println("修改用户......");
	}
	
	public void delete() {
		System.out.println("删除用户......");
	}
	
	public void find() {
		System.out.println("查找用户......");
		
	}
}

b.编写增强(此处选择环绕增强进行测试)

MyMethodInterceptor.java:
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

public class MyMethodInterceptor implements MethodInterceptor{

	@Override
	public Object invoke(MethodInvocation invocation) throws Throwable {
		System.out.println("环绕增强......在执行目标方法之前执行......");
		// 放行到目标方法
		Object result = invocation.proceed();
		System.out.println("环绕增强......在执行目标方法之后执行......");
		return result;
	}
}

c.在spring的核心配置文件中进行配置,此处需要指定切点(指定对那个方法进行拦截)

定义被代理对象:UserDAO类
<bean id="userDAO" class="com.guigu.spring.b_pointcutAdvisor.UserDAO"></bean>
定义增强:自定义增强类MyMethodInterceptor(环绕增强)
<bean id="myMethodInterceptor" class="com.guigu.spring.b_pointcutAdvisor.MyMethodInterceptor"></bean>
定义切点和切面:RegexpMethodPointcutAdvisor,并配置以下属性
<bean id="myAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
- 定义切点:patterns  即定义拦截哪写方法(此处拦截包含add、delete的方法
<property name="patterns" value=".*add.*,.*delete.*"></property>
- 定义增强:advice
<property name="advice" ref="myMethodInterceptor"></property>
</bean>
创建代理:org.springframework.aop.framework.ProxyFactoryBean,配置以下属性
<bean id="userDAOProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
- 对应目标(target)
<property name="target" ref="userDAO"></property>
- 针对接口代理还是类代理(proxyInterfaces)
<property name="proxyTargetClass" value="true"></property>
- 增强(interceptorNames)
<property name="interceptorNames" value="myAdvisor"></property>
可以通过查看ProxyFactoryBean源码分析这几个属性
</bean>

image-20210503095210933

d.编写测试代码进行分析

PointcutAdvisorTest.java:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring/applicationContext.xml")
public class PointcutAdvisorTest {

	@Autowired
	@Qualifier("userDAOProxy")
	private UserDAO userDAO;

	@Test
	public void test() {
		// 测试增删改查
		userDAO.add();
		userDAO.update();
		userDAO.delete();
		userDAO.find();
	}
}

测试结果:

image-20210503095248583

b.自动代理

​ 前面的案例中,每个代理都是通过ProxyFactoryBean织入切面代理,在实际开发中,非常多的Bean每个都配置ProxyFactoryBean开发维护量巨大

解决方案:自动创建代理

BeanNameAutoProxyCreator:根据Bean名称创建代理

DefaultAdvisorAutoProxyCreator:根据Advisor本身包含信息创建代理

AnnotationAwareAspectJAutoProxyCreator:基于Bean中的Aspect J 注解进行自动代理

1>BeanNameAutoProxyCreator
需求分析:针对不带有切点的切面实现自动代理 
a.定义代理接口和其实现类
b.编写增强
c.在核心配置文件applicationContext.xml中进行相关配置
d.编写测试代码

a.编写代理接口和其实现类

此处参考上述案例中提供的CustomerDAO、CustomerDAOImpl

CustomerDAO.java:
public interface CustomerDAO {
	// 定义增删改查方法
	public void add();
	public void update();
	public void delete();
	public void find();
}
CustomerDAOImpl.java:
public class CustomerDAOImpl implements CustomerDAO{
	@Override
	public void add() {
		System.out.println("添加客户...");
	}

	@Override
	public void update() {
		System.out.println("修改客户...");
	}

	@Override
	public void delete() {
		System.out.println("删除客户...");		
	}

	@Override
	public void find() {
		System.out.println("查找客户...");		
	}
}

b.编写增强

MyMethodInterceptor.java
public class MyMethodInterceptor implements MethodInterceptor{

	@Override
	public Object invoke(MethodInvocation invocation) throws Throwable {
		System.out.println("环绕增强......在执行目标方法之前执行......");
		// 放行到目标方法
		Object result = invocation.proceed();
		System.out.println("环绕增强......在执行目标方法之后执行......");
		return result;
	}
}

c.配置applicationContext2.xml

<!-- 方式1:使用BeanNameAutoProxyCreator实现自而动代理 -->
定义被代理对象:如果是针对接口则为对应实现类
<bean id="customerDAO" class="com.guigu.spring.c_autoProxy.CustomerDAOImpl"></bean>
定义增强
<bean id="myMethodInterceptor" class="com.guigu.spring.c_autoProxy.MyMethodInterceptor"></bean>
配置自动代理 :BeanNameAutoProxyCreator 配置两个属性
<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
- 配置bean对象名称
	<property name="beanNames" value="*DAO"></property>
- 配置增强
	<property name="interceptorNames" value="myMethodInterceptor"></property>
</bean>

image-20210503095609755

d.编写测试代码

AutoProxyTest.java:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring/applicationContext2.xml")
public class AutoProxyTest {

	@Autowired
	@Qualifier("customerDAO")
	private CustomerDAO customerDAO;

	@Test
	public void test() {
		// 测试增删改查
		customerDAO.add();
		customerDAO.update();
		customerDAO.delete();
		customerDAO.find();
	}
}

测试结果:

image-20210503095633423

2>DefaultAdvisorAutoProxyCreator
a.定义代理接口和其实现类、或直接是一个类
b.编写增强
c.在核心配置文件applicationContext.xml中进行相关配置
d.编写测试代码

a.定义代理类或代理接口(此处在上述自动代理的案例基础上添加内容进行测试)

OrderDAO.java:
public class OrderDAO {

	public void add() {
		System.out.println("添加订单......");
	}
	
	public void update() {
		System.out.println("修改订单......");
	}
	
	public void delete() {
		System.out.println("删除订单......");
	}
	
	public void find() {
		System.out.println("查找订单......");
	}
}

b.编写增强(引用上述案例的增强,此处不再赘述)

c.配置applicationContext3.xml

<!-- 方式1:使用DefaultAdvisorAutoProxyCreator实现自而动代理 -->
定义被代理对象:如果是针对接口则为对应实现类
<bean id="customerDAO" class="com.guigu.spring.c_autoProxy.CustomerDAOImpl"></bean>
<bean id="orderDAO" class="com.guigu.spring.c_autoProxy.OrderDAO"></bean>
定义增强
<bean id="myMethodInterceptor" class="com.guigu.spring.c_autoProxy.MyMethodInterceptor"></bean>
定义切点和切面(只针对OrderDAO的add方法和所有的delete方法)
<bean id="myAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
	<property name="patterns" value="com.guigu.spring.c_autoProxy.OrderDAO.add.*,.*delete.*"></property>
	<property name="advice" ref="myMethodInterceptor"></property>
</bean>
配置自动代理 :BeanNameAutoProxyCreator 配置两个属性
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"></bean>

d.编写测试代码

AutoProxyTest2.java:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring/applicationContext3.xml")
public class AutoProxyTest2 {

	@Autowired
	@Qualifier("orderDAO")
	private OrderDAO orderDAO;
	
	@Autowired
	@Qualifier("customerDAO")
	private CustomerDAO customerDAO;

	@Test
	public void test() {
		// 测试增删改查
		System.out.println("orderDAO测试......");
		orderDAO.add();
		orderDAO.update();
		orderDAO.delete();
		orderDAO.find();
		System.out.println("customerDAO测试......");
		customerDAO.add();
		customerDAO.update();
		customerDAO.delete();
		customerDAO.find();
	}
}

测试结果:

image-20210503095849322

c.AspectJ实现AOP

  • AspectJ是一个基于Java语言的AOP框架

  • Spring2.0以后新增了对AspectJ切点表达式支持

  • @AspectJ 是AspectJ1.5新增功能,通过JDK5注解技术,允许直接在Bean类中定义切面

  • 新版本Spring框架,建议使用AspectJ方式来开发AOP

  • 使用AspectJ 需要导入Spring AOP和 AspectJ相关jar包

spring-aop-4.2.0.RELEASE.jar
com.springsource.org.aopalliance-1.0.0.jar
spring-aspects-4.2.0.RELEASE.jar
com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar
1>基于@AspectJ的编程
开发步骤:
a.导入AspectJ相关的jar包(通过pom.xml进行操作)
b.编写被代理对象、定义切面
c.编写spring配置文件
d.编写代码进行测试

a.导入AspectJ相关的jar包(通过pom.xml进行操作)

<!-- springAOP:AspectJ相关开发jar包 -->
<dependency>
	<groupId>org.springframework</groupId>			
    <artifactId>spring-aspects</artifactId>
	<version>${spring-version}</version>
</dependency>

b.编写被代理对象、定义增强和切面

public class UserDAO {
	
	// 实现增删改查方法
	public void add() {
		System.out.println("添加用户......");
	}
	
	public void update() {
		System.out.println("修改用户......");
	}
	
	public void delete() {
		System.out.println("删除用户......");
	}
	
	public void find() {
		System.out.println("查找用户......");
	}

}
# 定义增强和切面:
使用AspectJ常见的注解定义增强和切面
通知类型:分为五类
- @Before 前置通知,相当于BeforeAdvice
- @AfterReturning 后置通知,相当于AfterReturningAdvice
- @Around 环绕通知,相当于MethodInterceptor
- @AfterThrowing抛出通知,相当于ThrowAdvice
- @After 最终final通知,不管是否异常,该通知都会执行
- @DeclareParents 引介通知,相当于IntroductionInterceptor (不要求掌握)
切面定义:@Aspect定义切点和增强
	定义切点(针对哪些方法进行增强)  使用切点表达式定义
	通过execution函数,可以定义切点的方法切入
	语法:execution(<访问修饰符>?<返回类型><方法名>(<参数>)<异常>)
	例如:
		匹配所有类public方法  execution(public * *(..))
		匹配指定包下所有类方法 execution(* cn.guigu.dao..*(..)) 不包含子包
		execution(* cn.guigu.dao..*(..))  ..*表示包、子孙包下所有类
		匹配指定类所有方法 execution(* cn.guigu.service.UserService.*(..))
		匹配实现特定接口所有类方法 execution(* cn.guigu.dao.GenericDAO+.*(..))
		匹配所有save开头的方法 execution(* save*(..))
MyAspect.java:
@Aspect // @Aspect定义增强和切面
public class MyAspect {
	
	@Before("execution(* com.guigu.spring.a_aspectj_annotation.UserDAO.add(..))")
	public void writeLog() {
		System.out.println("前置增强......记录日志");
	}
	
	@AfterReturning("execution(* com.guigu.spring.a_aspectj_annotation.UserDAO.delete(..))")
	public void doSth() {
		System.out.println("后置增强......执行操作后处理某些事物......");
	}
}

c.编写spring配置文件

​ 在核心配置文件applicationContext.xml中需要引入aspectJ相关的命名空间,并使用AspectJ实现自动代理

ApplicationContext.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:p="http://www.springframework.org/schema/p"
	xmlns:aop="http://www.springframework.org/schema/aop" 
	xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
	<!-- 使用AspectJ实现自动代理 -->
	<aop:aspectj-autoproxy/>
......其余代码
</beans>
使用<aop:aspect-autoproxy> 配置自动代理
底层实现原理就是AnnotationAwareAspectJAutoProxyCreator 基于Bean中的AspectJ 注解进行自动代理

image-20210503100406990

d.编写代码进行测试

AspectJTest.java:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="classpath:spring/applicationContext.xml")
public class AspectJTest {
	@Autowired
	@Qualifier("userDAO")
	private UserDAO userDAO;
	
	@Test
	public void test() {
		userDAO.add();
		userDAO.update();
		userDAO.delete();
		userDAO.find();
	}
}

测试结果:

image-20210503100431997

2>AspectJ通知的类型
注解说明
@Before 前置通知相当于BeforeAdvice
@AfterReturning 后置通知相当于AfterReturningAdvice
@Around 环绕通知相当于MethodInterceptor
@AfterThrowing抛出通知相当于ThrowAdvice
@After最终final通知,不管是否异常,该通知都会执行
@DeclareParents引介通知相当于IntroductionInterceptor

在上述案例的基础上添加增强进行测试(此处对UserDAO中所有的方法添加环绕增强)

MyAspect.java:

image-20210503100645852

测试结果:

image-20210503100653550

@PointCut 切点定义:

在通知上定义切点表达式,简化重复的切点表达式

把多次用到的切点定义完成,然后直接在增强中使用

image-20210503100705882

上述内容等价于下述定义

image-20210503100713011

3.基于 XML配置的AspectJ编程
开发步骤:
a.导入AspectJ相关的jar包(通过pom.xml进行操作)
b.编写被代理对象、定义切面
c.编写spring配置文件
d.编写代码进行测试

a.导入AspectJ相关的jar包(通过pom.xml进行操作),参考上述案例分析,此处不做赘述

b.编写被代理对象、定义切面

  • 定义被代理对象:ProductDAO.java
public class ProductDAO {
	// 添加增删改查方法
	public void add() {
		System.out.println("添加产品信息......");
	}
	public void update() {
		System.out.println("修改产品信息......");
	}
	public void delete() {
		System.out.println("删除产品信息......");
	}
	public void find() {
		System.out.println("查找产品信息......");
	}
}
  • 定义切面:
public class MyAspectXML{
	
	public void before() {
		System.out.println("前置增强......记录日志");
	}
	
	public void after() {
		System.out.println("后置增强......执行操作后处理某些事物......");
	}
	
	public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
		System.out.println("环绕增强......before......");
		// 放行资源
		Object res = proceedingJoinPoint.proceed();
		System.out.println("环绕增强......after......");
		return res;
	}
	
}

c.编写spring配置文件

定义被代理对象
<bean id="productDAO" class="com.guigu.spring.b_aspectj_xml.ProductDAO"></bean>
定义切面
<bean id="myAspectXML" class="com.guigu.spring.b_aspectj_xml.MyAspectXML"></bean>
使用xml详细配置aspectj的配置
<aop:config>
	<!-- 定义切面 -->
	<aop:aspect ref="myAspectXML">
		<!-- 定义切点 -->
		<aop:pointcut expression="切点(要拦截的方法)" id="pointcut1"/>
		<!-- 定义增强 -->
		<aop:before method="before" pointcut-ref="pointcut1"/>
	</aop:aspect>
</aop:config>

applicationContext2.xml:

image-20210503100911804

d.编写代码进行测试

AspectJXMLTest.java:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="classpath:spring/applicationContext2.xml")
public class AspectJXMLTest {
	@Autowired
	@Qualifier("productDAO")
	private ProductDAO productDAO;
	
	@Test
	public void test() {
		productDAO.add();
		productDAO.update();
		productDAO.delete();
		productDAO.find();
	}
}

测试结果:

image-20210503100942011

4.Spring JDBCTemplate

Spring 提供了不同的持久化技术

Spring为各种支持的持久化技术,都提供了简单操作的模板和回调

JDBC->org.springframework.jdbc.core.JdbcTemplate

Hibernate->org.springframework.orm.hibernate3.HibernateTemplate

IBatis(MyBatis)->org.springframework.orm.ibatis.SqlMapClientTemplate

JPA->org.springframework.orm.jpa.JpaTemplate

【1】入门案例

a.导入相关的jar包

<properties>
	<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
	<spring-version>5.0.8.RELEASE</spring-version>
	<logging-version>1.2</logging-version>
	<mysql-version>5.1.6</mysql-version>
	<c3p0-version>0.9.5.2</c3p0-version>
	<dbcp-version>1.4</dbcp-version>
	<druid-version>1.1.10</druid-version>
</properties>         
<!-- 配置Spring内置数据库连接池 -->
	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-jdbc</artifactId>
		<version>${spring-version}</version>
	</dependency>
<!-- mysql相关jar包 -->
	<dependency>
		<groupId>mysql</groupId>
		<artifactId>mysql-connector-java</artifactId>
		<version>${mysql-version}</version>
	</dependency>
<!-- C3P0数据库连接池相关jar包 -->
	<dependency>
		<groupId>com.mchange</groupId>
		<artifactId>c3p0</artifactId>
		<version>${c3p0-version}</version>
	</dependency>
<!-- dbcp连接池相关jar包 -->
	<dependency>
		<groupId>commons-dbcp</groupId>
		<artifactId>commons-dbcp</artifactId>
		<version>${dbcp-version}</version>
	</dependency>
<!-- druid连接池相关jar包 -->
	<dependency>
		<groupId>com.alibaba</groupId>
		<artifactId>druid</artifactId>
		<version>${druid-version}</version>
	</dependency>
<!-- spring内置测试相关jar包 -->
	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-test</artifactId>
		<version>${spring-version}</version>
		<scope>test</scope>
	</dependency>

​ 第一次导入相关jar包,耐心等待加载完成,如果加载失败则需要彻底删除相关的内容后重新加载,否则无法正常使用,加载完成后有以下数据

image-20210503101152356

b.编写配置文件 (jdbc)

以下针对不同的数据库连接池进行测试,主要有4种连接池进行配置,可以通过xml文件直接配置,亦可通过外部文件进行配置(推荐使用)

基本步骤:

a.创建数据库连接池(在applicationContext.xml中进行配置)
b.创建JdbcTemplate对象
c.通过JdbcTemplate对象执行sql语句进行测试
(1)Spring内置的数据库连接池
数据库连接池配置:
<!-- 
	a.需要在pom.xml中配置相关内容,导入所需的jar包
	b.在核心配置文件applicationContext.xml中编写配置文件
		主要是配置JdbcTemplate对象的设置和相应的连接数据库的4个参数的设置
	c.在测试中获取JdbcTemplate对象执行sql语句
 -->
 	<!-- 配置JdbcTemplate对象 -->
 	<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
		<property name="dataSource" ref="dataSource"></property>
	</bean>	
	
	<!-- 配置相应的数据库连接池 -->
	<!-- a.使用Spring内置的数据库连接池 -->
	<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
   		<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
   		<property name="url" value="jdbc:mysql://localhost:3306/guigu"></property>
   		<property name="username" value="root"></property>
   		<property name="password" value="root"></property>
   </bean>
测试:
/**
 * 使用下述注解需要导入spring-test jar包
 * @RunWith(SpringJUnit4ClassRunner.class)
 * @ContextConfiguration(locations="url")
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="classpath:spring/applicationContext.xml")
public class JdbcTest {

	@Autowired
	@Qualifier("jdbcTemplate")
	private JdbcTemplate jdbcTemplate;

	@Test
	public void testJdbc() {
		System.out.println(jdbcTemplate.getDataSource());
		jdbcTemplate.execute("create table suser(sid int primary key,sname varchar(40))");
	}
}
(2)C3P0数据库连接池
C3P0数据池配置
<!-- 配置JdbcTemplate对象 -->
 	<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
		<property name="dataSource" ref="dataSource"></property>
	</bean>	
	
	<!-- b.使用C3P0数据库连接池 -->
	<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"  destroy-method="close">
   		<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
   		<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/guigu"></property>
   		<property name="user" value="root"></property>
   		<property name="password" value="root"></property>
   </bean>

​ 测试文件与上述相同,在测试的时候需要注释掉其他无关的数据库配置并删除指定数据库中已存在的表,避免造成错误

(3)DBCP数据库连接池
DBCP数据库连接池配置
<!-- 配置JdbcTemplate对象 -->
	<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
		<property name="dataSource" ref="dataSource"></property>
	</bean>

	<!-- c..使用DBCP数据库连接池 -->
	<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
		destroy-method="close">
		<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
		<property name="url" value="jdbc:mysql://localhost:3306/guigu"></property>
		<property name="username" value="root"></property>
		<property name="password" value="root"></property>
	</bean>

​ 测试文件与上述相同,在测试的时候需要注释掉其他无关的数据库配置并删除指定数据库中已存在的表,避免造成错误

(4)DRUID数据库连接池

​ DRUID是阿里巴巴开源平台上一个数据库连接池实现,它结合了C3P0、DBCP、PROXOOL等DB池的优点,同时加入了日志监控,可以很好的监控DB池连接和SQL的执行情况,可以说是针对监控而生的DB连接池(据说是目前最好的连接池,不知道速度有没有BoneCP快)。

​ 参考文档:https://blog.csdn.net/sdx1237/article/details/70305565

​ 和其它连接池一样DRUID的DataSource类为:com.alibaba.druid.pool.DruidDataSource,基本配置参数如下:

image-20210503101404391

image-20210503101411104

druid数据库连接池配置
<!-- 配置JdbcTemplate对象 -->
	<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
		<property name="dataSource" ref="dataSource"></property>
	</bean>

	<!-- d.使用druid数据库连接池 -->
	<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"  
			init-method="init" destroy-method="close"> 
		<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
		<property name="url" value="jdbc:mysql://localhost:3306/guigu"></property>
		<property name="username" value="root"></property>
		<property name="password" value="root"></property>
	</bean>

​ 测试文件与上述相同,在测试的时候需要注释掉其他无关的数据库配置并删除指定数据库中已存在的表,避免造成错误

(5)利用外部文件的形式

image-20210503101440139

druid数据库连接池配置:
applicationContext2.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:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">

<!-- 
	SpringJDBC测试:利用外部文件配置
	引入context标签
	xmlns:context="http://www.springframework.org/schema/context"
	http://www.springframework.org/schema/context
	http://www.springframework.org/schema/context/spring-context.xsd
-->
	
	<!-- 配置JdbcTemplate对象 -->
	<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
		<property name="dataSource" ref="dataSource"></property>
	</bean>

	 <!-- 引入配置文件 -->
	 <context:property-placeholder location="classpath:config/jdbc.properties"/>
	 <!-- 使用druid数据库连接池 -->
	 <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"  init-method="init" destroy-method="close"> 
 			<property name="driverClassName" value="${jdbc.driver}"></property>
	   		<property name="url" value="${jdbc.url}"></property>
	   		<property name="username" value="${jdbc.username}"></property>
	   		<property name="password" value="${jdbc.password}"></property>
     </bean>
</beans>

​ 在测试的时候与上述测试文件大同小异,在引入文件的时候是引入新建的applicationContext2.xml

image-20210503101500935

【2】CRUD操作

基本步骤:在删除创建的表格suer的基础上完成数据的增删改查

a.创建model实体类User(属性:id、name)
b.定义xxxDAO继承JdbcDaoSupport实现增删改查
c.在核心配置文件中进行相关配置(xxxDAO的注入和数据库连接池的配置)
d.定义测试文件完成基本的增删改查
代码分析:
User.java:
/**
 * model类:sid、sname
 */
public class User {
	private int sid;
	private String sname;
	public User() {
		super();
	}
	public User(int sid, String sname) {
		super();
		this.sid = sid;
		this.sname = sname;
	}
	public int getSid() {
		return sid;
	}
	public void setSid(int sid) {
		this.sid = sid;
	}
	public String getSname() {
		return sname;
	}
	public void setSname(String sname) {
		this.sname = sname;
	}
	@Override
	public String toString() {
		return "User [sid=" + sid + ", sname=" + sname + "]";
	}
}
UserDAO.java:
/**
 * 定义UserRowMapper将查询的数据封装为相应的对象 
 */
class UserRowMapper implements RowMapper<User> {
	@Override
	public User mapRow(ResultSet rs, int rowNum) throws SQLException {
		User user = new User();
		// 与数据库中定义的内容一一对应
		user.setSid(rs.getInt("sid"));
		user.setSname(rs.getString("sname"));
		return user;
	}
}
/**
 * 定义UserDAO继承JdbcDaoSupport,实现数据的增删改查
 */
public class UserDAO extends JdbcDaoSupport {

	// 添加用户信息
	public void addUser(User user) {
		String sql = "insert into suser values(?,?)";
		Object[] args = { user.getSid(), user.getSname() };
		this.getJdbcTemplate().update(sql, args);
	}

	// 修改用户信息
	public void updateUser(User user) {
		String sql = "update suser set sname=? where sid=?";
		Object[] args = { user.getSname(), user.getSid() };
		this.getJdbcTemplate().update(sql, args);
	}

	// 根据用户id删除用户信息
	public void deleteUserById(int sid) {
		String sql = "delete from suser where sid=?";
		this.getJdbcTemplate().update(sql, sid);

	}

	// 查找某个字段信息
	public String getSnameBySid(int sid) {
		String sql = "select sname from suser where sid=?";
		// queryForObject(sql,返回类型,参数值...)
		return this.getJdbcTemplate().queryForObject(sql, String.class, sid);
	}

	// 查找某个对象信息
	public User getSuserBySid(int sid) {
		String sql = "select * from suser where sid=?";
		// queryForObject(sql,返回类型,参数值...)
		return this.getJdbcTemplate().queryForObject(sql, new UserRowMapper(), sid);
	}

	// 查找某个集合信息
	public List<User> findAll() {
		String sql = "select * from suser";
		return this.getJdbcTemplate().query(sql, new UserRowMapper());
	}
}
数据库连接池配置文件:applicationContext3.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:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">

<!-- 
	SpringJDBC测试:利用外部文件配置
	引入context标签
	xmlns:context="http://www.springframework.org/schema/context"
	http://www.springframework.org/schema/context
	http://www.springframework.org/schema/context/spring-context.xsd
-->
	
	<!-- 配置JdbcTemplate对象 -->
	<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
		<property name="dataSource" ref="dataSource"></property>
	</bean>

	 <!-- 引入配置文件 -->
	 <context:property-placeholder location="classpath:config/jdbc.properties"/>
	 <!-- 使用druid数据库连接池 -->
	 <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"  init-method="init" destroy-method="close"> 
 			<property name="driverClassName" value="${jdbc.driver}"></property>
	   		<property name="url" value="${jdbc.url}"></property>
	   		<property name="username" value="${jdbc.username}"></property>
	   		<property name="password" value="${jdbc.password}"></property>
     </bean>
     <!-- 注入userDAO对象 -->
     <bean id="userDAO" class="com.guigu.spring.dao.UserDAO">
     	<property name="jdbcTemplate" ref="jdbcTemplate"></property>
     </bean>
</beans>
测试代码:
/**
 * 使用下述注解需要导入spring-test jar包 
 * @RunWith(SpringJUnit4ClassRunner.class) 
 * @ContextConfiguration(locations="url")
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring/applicationContext3.xml")
public class UserCRUDTest {

	@Autowired
	@Qualifier("userDAO")
	private UserDAO userDAO;

	@Test
	public void testAdd() {
		User user = new User(1, "李四");
		userDAO.addUser(user);
	}
	
	@Test
	public void testUpdate() {
		User user = new User(2, "小傻子");
		userDAO.updateUser(user);
	}
	
	@Test
	public void testDelete() {
		userDAO.deleteUserById(1);
	}
	
	@Test
	public void testFind() {
		System.out.println(userDAO.getSnameBySid(2));
		System.out.println(userDAO.getSuserBySid(2));
		System.out.println(userDAO.findAll());
	}
}

5.Spring事务

【1】Spring事务的概述

Spring事务管理高层抽象主要包括3个接口

接口说明
PlatformTransactionManager事务管理器
TransactionDefinition事务定义信息(隔离、传播、超时、只读)
TransactionStatus事务具体运行状态

image-20210503101726877

PlatformTransactionManager

​ 事务管理器:不同的持久层使用不同的事务管理器

image-20210503101757519

TransactionDefinition

​ TransactionDefinition进行事务的定义(配置信息和xml进行定义),包括以下情况 (隔离级别、传播行为、超时、只读)

(1)事务隔离级别

​ 与之前所学习的数据库事务相关内容大同小异,需重点掌握

​ 事务的四大特性:ACID 原子性、一致性、隔离性、持久性

​ 由于隔离性导致的四大问题脏读、不可重复读、虚读(幻读)、丢失更新

image-20210503101834848

​ 为了解决上述四大问题提供了四种隔离级别:

image-20210503101844534

(2)传播行为

传播行为不是JDBC定义的规范,传播行为是针对实际开发中的问题定义的

image-20210504194148277

为了解决多个业务层调用事务之间的关系处理Spring定义了七种传播行为

image-20210503101913153

简单说明:(重点掌握标注的3个内容,其余简单记忆)

PROPAGATION_REQUIRED支持当前事务,如果当前事务不存在则创建一个新的事务

举例:删除客户同时删除订单处于同一个事务中,如果删除订单失败,删除客户信息也要回滚。

PROPAGATION_SUPPORTS支持当前事务,如果不存在则不使用事务

PROPAGATION_MANDATORY支持当前事务,如果不存在则抛出异常

​ 说明:必须有事务的存在

PROPAGATION_REQUIRES_NEW如果存在事务则挂起当前事务,创建一个新的事务

​ 举例:日常生活中生成订单的同时,系统会自动发送邮件通知客户,通知邮件会创建一个新的事务,如果邮件发送失败,不影响订单的生成。

PROPAGATION_NOT_SUPPORTED:以非事务的方式运行,如果存在事务则取消事务。

PROPAGATION_NEVER :以非事务的方式运行,如果存在事务则抛出异常

PROPAGATION_NESTED如果当前事务存在则嵌套事务执行

​ 举例:删除客户的时候删除订单,删除客户后可以设置savepoint, 然后执行删除订单,删除客户和删除订单都在同一个事务内,删除订单失败后,事务回滚到savepoint,剩余的是继续提交还是回滚均由用户自行决定。

【2】Spring事务的管理

a.基于配置文件的声明式事务

Spring事务管理分为两种形式

  • 编程式事务 (了解)

    在代码中使用TransactionTemplate手动进行管理事务 (实际开发中几乎没有使用)

  • 声明式事务 (重点掌握)

    在使用数据库连接测试需要导入相关的jar包,pom.xml配置如下

<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<spring-version>5.0.8.RELEASE</spring-version>
		<logging-version>1.2</logging-version>
		<mysql-version>5.1.6</mysql-version>
		<druid-version>1.1.10</druid-version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-context</artifactId>
			<version>${spring-version}</version>
		</dependency>

		<dependency>
			<groupId>commons-logging</groupId>
			<artifactId>commons-logging</artifactId>
			<version>${logging-version}</version>
		</dependency>

		<!-- mysql相关jar包 -->
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>${mysql-version}</version>
		</dependency>
		
		<!-- 配置Spring内置数据库连接池 -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-jdbc</artifactId>
			<version>${spring-version}</version>
		</dependency>

		<!-- druid连接池相关jar包 -->
		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>druid</artifactId>
			<version>${druid-version}</version>
		</dependency>
		
		<!-- spring内置测试相关jar包 -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-test</artifactId>
			<version>${spring-version}</version>
			<scope>test</scope>
		</dependency>
		
	</dependencies>

​ applicationContext.xml:此处参考使用druid数据库连接池进行测试

	<!-- 配置JdbcTemplate对象 -->
	<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
		<property name="dataSource" ref="dataSource"></property>
	</bean>

	<!-- 使用druid数据库连接池 -->
	<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
		init-method="init" destroy-method="close">
		<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
		<property name="url" value="jdbc:mysql://localhost:3306/guigu"></property>
		<property name="username" value="root"></property>
		<property name="password" value="root"></property>
	</bean>
(1)创建用户表
新建account表,简单实现转账小程序
create table account(
	id int primary key auto_increment,
	name varchar(30) not null,
	money double
);
分别插入数据进行测试
insert into account values(1,'aaa',1000 );
insert into account values(2,'bbb',1000 );
(2)编写转账程序

xxxDao、xxxDaoImpl、xxxService、xxxServiceImpl编写

AccountDAO.java:

public interface AccountDAO {
	// 定义两个方法用于实现转账小程序
	public void outMoney(String outAccount,double money);
	public void inMoney(String inAccount,double money);
}

AccountDAOImpl.java:

public class AccountDAOImpl extends JdbcDaoSupport implements AccountDAO{
	@Override
	public void outMoney(String outAccount, double money) {
		String sql = "update account set money=money-? where name=?";
		Object[] args = {money,outAccount};
		this.getJdbcTemplate().update(sql, args);
	}
	@Override
	public void inMoney(String inAccount, double money) {
		String sql = "update account set money=money+? where name=?";
		Object[] args = {money,inAccount};
		this.getJdbcTemplate().update(sql,args);
	}
}

AccountService.java:

public interface AccountService {
	public void transfer(String outAccount,String inAccount,double money);
}

AccountServiceImpl.java:

public class AccountServiceImpl implements AccountService{
	private AccountDAO accountDAO;
	
	public void setAccountDAO(AccountDAO accountDAO) {
		this.accountDAO = accountDAO;
	}
	@Override
	public void transfer(String outAccount, String inAccount, double money) {
		accountDAO.outMoney(outAccount, money);
		int x=1/0;
		accountDAO.inMoney(inAccount, money);
	}
}
(3)编写配置文件
applicationContext.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"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">

	<!-- 配置JdbcTemplate对象 -->
	<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
		<property name="dataSource" ref="dataSource"></property>
	</bean>

	<!-- 使用druid数据库连接池 -->
	<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
		init-method="init" destroy-method="close">
		<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
		<property name="url" value="jdbc:mysql://localhost:3306/guigu"></property>
		<property name="username" value="root"></property>
		<property name="password" value="root"></property>
	</bean>

	<!-- a.定义事务管理器 -->
	<bean id="transactionManager"
		class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<property name="dataSource" ref="dataSource"></property>
	</bean>

	<!-- b.定义DAO层 :对应的实现类定义 -->
	<bean id="accountDAO" class="com.guigu.spring.a_xmlTransaction.AccountDAOImpl">
		<property name="dataSource" ref="dataSource"></property>
	</bean>

	<!-- c.定义Service层:对应的实现类定义 -->
	<bean id="accountService" class="com.guigu.spring.a_xmlTransaction.AccountServiceImpl">
		<property name="accountDAO" ref="accountDAO"></property>
	</bean>

	<!-- d.为AccountService创建代理,完成事务的增强TransactionProxyFactoryBean -->
	<bean id="accountServiceProxy" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
		<!-- 被代理对象 -->
		<property name="target" ref="accountService"></property>
		<!-- 针对的是接口还是对象 -->
		<property name="proxyInterfaces"
			value="com.guigu.spring.a_xmlTransaction.AccountService"></property>
		<!-- 增强:针对事务进行增强 -->
		<property name="transactionManager" ref="transactionManager"></property>
		<property name="transactionAttributes">
			<props>
				<!-- 针对插入的方法定义的传播行为是required -->
				<prop key="insert*">PROPAGATION_REQUIRED</prop>
				<!-- 针对转账方法定义的传播行为是 -->
				<prop key="transfer">ISOLATION_READ_COMMITTED,PROPAGATION_REQUIRED</prop>
				<!-- 针对其他方法定义的传播行为是required,并且设置为只读 -->
				<prop key="*">PROPAGATION_REQUIRED,readOnly</prop>
			</props>
		</property>
	</bean>

</beans>

定义四种隔离级别的属性

​ ISOLATION_READ_UNCOMMITTED:这是事务最低的隔离级别,它充许令外一个事务可以看到这个事务未提交的数据。这种隔离级别会产生脏读,不可重复读和幻像读

​ ISOLATION_READ_COMMITTED:保证一个事务修改的数据提交后才能被另外一个事务读取。另外一个事务不能读取该事务未提交的数据

​ ISOLATION_REPEATABLE_READ:这种事务隔离级别可以防止脏读,不可重复读。但是可能出现幻像读。它除了保证一个事务不能读取另一个事务未提交的数据外,还保证了避免下面的情况产生(不可重复读)

​ ISOLATION_SERIALIZABLE:这是花费最高代价但是最可靠的事务隔离级别。事务被处理为顺序执行。除了防止脏读,不可重复读外,还避免了幻像读

​ 注:隔离级别可以认为是4个(上面4个对应JDBC的隔离级别),也可认为是5个(还有一个default对应ISOLATION_DEFAULT是默认的隔离级别,遵循传播属性)

定定义事务传播行为

​ 事务传播行为就是多个事务方法相互调用时,事务如何在这些方法间传播。spring支持7种事务传播行为:

​ propagation_requierd:如果当前没有事务,就新建一个事务,如果已存在一个事务中,加入到这个事务中,这是最常见的选择

​ propagation_supports:支持当前事务,如果没有当前事务,就以非事务方法执行

​ propagation_mandatory:使用当前事务,如果没有当前事务,就抛出异常

​ propagation_required_new:新建事务,如果当前存在事务,把当前事务挂起

​ propagation_not_supported:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起

​ propagation_never:以非事务方式执行操作,如果当前事务存在则抛出异常

​ propagation_nested:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与propagation_required类似的操作

(4)编写测试文件
AccountTest.java:
/**
 * 转账小程序测试
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring/applicationContext.xml")
public class AccountTest {

	@Autowired
	@Qualifier("accountServiceProxy")
	AccountService accountService;

	@Test
	public void testTransfer() {
		accountService.transfer("张三", "李四", 400);
	}
}

正常转账测试显示结果正常完成转账:

image-20210503102511773

如果在转账过程中出现异常则事务发生回滚:

image-20210503102521645

数据库中的数据不会发生改变

b.基于注解的声明式事务

​ 基本的内容与上述大同小异,只是在定义变量的时候采用注解的方式实现,此处简单进行说明

(1)编写转账程序
AccountDAOpublic interface AccountDAO {
	// 定义两个方法用于实现转账小程序
	public void outMoney(String outAccount,double money);
	public void inMoney(String inAccount,double money);
}
AccountDAOImplpublic class AccountDAOImpl extends JdbcDaoSupport implements AccountDAO{
	@Override
	public void outMoney(String outAccount, double money) {
		String sql = "update account set money=money-? where name=?";
		Object[] args = {money,outAccount};
		this.getJdbcTemplate().update(sql, args);
	}
	@Override
	public void inMoney(String inAccount, double money) {
		String sql = "update account set money=money+? where name=?";
		Object[] args = {money,inAccount};
		this.getJdbcTemplate().update(sql,args);
	}
}
AccountServicepublic interface AccountService {
	public void transfer(String outAccount,String inAccount,double money);
}
AccountServiceImpl@Service("accountService")
@Transactional // 开启事务:声明式事务注解的使用
public class AccountServiceImpl implements AccountService{
	
	@Autowired
	private AccountDAO accountDAO;
	
	public void setAccountDAO(AccountDAO accountDAO) {
		this.accountDAO = accountDAO;
	}

	@Override
	public void transfer(String outAccount, String inAccount, double money) {
		accountDAO.outMoney(outAccount, money);
		int x=1/0;
		accountDAO.inMoney(inAccount, money);
	}
}
(2)编写配置文件
<?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"
	xmlns:tx="http://www.springframework.org/schema/tx"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">

	<!-- 配置JdbcTemplate对象 -->
	<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
		<property name="dataSource" ref="dataSource"></property>
	</bean>

	<!-- 使用druid数据库连接池 -->
	<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
		init-method="init" destroy-method="close">
		<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
		<property name="url" value="jdbc:mysql://localhost:3306/guigu"></property>
		<property name="username" value="root"></property>
		<property name="password" value="root"></property>
	</bean>

	<!-- a.定义事务管理器 -->
	<bean id="transactionManager"
		class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<property name="dataSource" ref="dataSource"></property>
	</bean>

	<!-- b.定义DAO层 :对应的实现类定义 -->
	<bean id="accountDAO" class="com.guigu.spring.b_annotation.AccountDAOImpl">
		<property name="dataSource" ref="dataSource"></property>
	</bean>

	<!-- c.自动扫描包 (需要引入context标签)-->
	<context:component-scan base-package="com.guigu.spring.b_annotation"></context:component-scan>
  
	<!-- d.基于注解的声明式事务管理(需要引入tx标签) -->
	<tx:annotation-driven transaction-manager="transactionManager"/>

</beans>
(3)编写测试文件
/**
 * 转账小程序测试
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring/applicationContext2.xml")
public class AccountTest {

	@Autowired
	@Qualifier("accountService")
	AccountService accountService;

	@Test
	public void testTransfer() {
		accountService.transfer("张三", "李四", 200);
	}
}

测试结果与上述相同

(4)详细配置事务的相关属性

⚡@Transactional(指定事务的隔离级别、传播行为、是否只读)

image-20210503102744332

评论
  • 按正序
  • 按倒序
  • 按热度
Powered by Waline v3.1.3