赞
踩
Spring官网链接:官网
Spring的五大功能
功能模块 | 功能介绍 |
---|---|
Core Container | 核心容器,在 Spring 环境下使用任何功能都必须基于 IOC 容器。 |
AOP&Aspects | 面向切面编程 |
Testing | 提供了对 junit 或 TestNG 测试框架的整合。 |
Data Access/Integration | 提供了对数据访问/集成的功能。 |
Spring MVC | 提供了面向Web应用程序的集成功能。 |
Spring的核心就是控制反转(IOC)和依赖注入(DI)及面向切面编程(AOP),之后会给大家介绍这些概念。
①Servlet的生命周期
名称 | 时机 | 次数 |
---|---|---|
创建对象 | 默认情况:接收到第一次请求修改启动顺序后:Web应用启动过程中 | 一次 |
初始化 | 创建对象之后 | 一次 |
处理请求 | 接收到请求 | 多次 |
销毁 | Web应用卸载之前 | 一次 |
②Filter生命周期
名称 | 时机 | 次数 |
---|---|---|
创建对象 | Web应用启动时 | 一次 |
初始化 | 创建对象之后 | 一次 |
拦截请求 | 接收到匹配的请求 | 多次 |
销毁 | Web应用卸载之前 | 一次 |
IOC概念:Inversion of Control,中文意思是控制反转。它是软件设计模式中的一种设计原则和思想。
① 获取资源的传统方式
在应用程序中的组件需要获取资源时,传统的方式是组件主动的从容器中获取所需要的资源,因此开发人员需要知道在具体容器中特定资源的获取方式。
②控制反转的方式获取资源
将你设计好的对象交给容器控制,而不是显式的用代码进行对象的创建。
把创建和查找依赖对象的控制权交给IOC容器,由IOC容器进行注入、组合对象。
这也就是说容器主动的将资源推送给需要的组件,而开发人员是不需要知道容器是如何创建资源的,只需要提供接收资源的方式。
③使用IOC的好处
④DI
DI:Dependency Injection,中文意思是依赖注入。他也是IOC的另外一个说法。就是组件之间依赖关系是由容器在运行期决定。组件的创建都由容器来创建,再由容器来决定各组件之间的依赖关系。各个组件不再自己决定依赖的其他对象。换句话说,就是组件运行所依赖的对象只能是注入进来的对象,注入哪个对象,我就依赖哪个!
由Spring的IOC容器来创建类的对象。
步骤:
①创建一个Maven Module
②在pom.xml中添加依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.10</version>
</dependency>
spring-context的依赖包
③创建组件类
public class Computer {
public void work(){
System.out.println("==========电脑启动=======");
}
}
④创建Spring的配置文件
注意:这个spring配置文件的名字可以自定义
⑤编写spring.xml文件
bean标签:通过配置bean标签告诉IOC容器需要创建对象的组件是什么。
name属性:bean的唯一标识。
id属性:bean的唯一标识。
class属性:组件类的全类名。
<?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">
<bean name="computer" class="com.zhang.Computer">
</bean>
</beans>
⑥测试加载
public class Test {
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("/spring.xml");
Computer computer = applicationContext.getBean("computer", Computer.class);
computer.work();
}
}
有两种方式获取bean。
注意:name属性和id属性的值必须是唯一的,不能重复。
在spring.xml中bean上不管是name属性还是id属性,只需要在getBean时写上他们的值就可以获取到了。
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("/spring.xml");
// 如果不命名第二个参数,获取的就是Object对象,需要强转为Computer类
// Computer computer = (Computer)applicationContext.getBean("computer");
Computer computer = applicationContext.getBean("computer", Computer.class);
computer.work();
注意: 如果通过类型获取,在xml文件中只能有一个该类型的bean;如果有多个该类型的bean,则会报 org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type ‘com.zhang.Computer’ available: expected single matching bean but found 2: computer,computer1 这个错误。
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("/spring.xml");
Computer computer = applicationContext.getBean(Computer.class);
computer.work();
实体类如下:
public class CPU { private String name; public CPU(){} public CPU(String name) { this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } } public class Computer { private String name; private CPU cpu; public Computer(){} public Computer(String name, CPU cpu) { this.name = name; this.cpu = cpu; } public CPU getCpu() { return cpu; } public void setCpu(CPU cpu) { this.cpu = cpu; } public String getName() { return name; } public void setName(String name) { this.name = name; } public void work(){ System.out.println("==========电脑启动======="); } }
property 标签:通过实体类的setXxx() 方法给实体类对象设置属性;
name属性:指定属性名(这个属性名是getXxx()、setXxx()方法定义的,和成员变量无关,与其方法的参数有关);
value属性:指定属性值;
ref属性:通过bean的id引用另一个bean。
<bean id="cpu" class="com.zhang.pojo.CPU">
<property name="name" value="Intel"/>
</bean>
<bean id="computer" class="com.zhang.pojo.Computer">
<property name="name" value="惠普"/>
<!-- 这个ref注入时,idea有提示,并且ref只能匹配setXxx()方法中参数的相对应的实体类类型-->
<property name="cpu" ref="cpu"/>
</bean>
注意:ref和value属性的区别:当我们使用value属性时,Spring会直接给该属性赋值,Spring不会把这个属性作为一个bean的id,也不会去找相应的bean来赋值;而ref属性会找自己相对应的bean(类型),即引用这个对象。
导入命名空间
注意:命名空间你可以先写p:,然后使用Alt+Enter,idea帮你自动导入命名空间。
<?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">
</beans>
如上面使用 property 标签注入的方式,我们可以改成 <p:prop> 命名空间注入的形式。
注意: p命名空间后边跟着属性名相当于注入值,而后边跟着属性名-ref相当于注入实体类。
<bean id="cpu" class="com.zhang.pojo.CPU" p:name="Intel"/>
<bean id="computer" class="com.zhang.pojo.Computer" p:name="惠普" p:cpu-ref="cpu"/>
index属性:指定参数所在位置的索引(从0开始)
<bean id="cpu" class="com.zhang.pojo.CPU">
<constructor-arg index="0" value="Inter"/>
</bean>
<bean id="computer" class="com.zhang.pojo.Computer">
<constructor-arg index="0" value="惠普"/>
<constructor-arg index="1" ref="cpu"/>
</bean>
name属性:指定属性名;
value属性:指定的属性值;
ref属性:通过bean的id引用另一个bean。
<bean id="cpu" class="com.zhang.pojo.CPU">
<constructor-arg name="name" value="Inter"/>
</bean>
<bean id="computer" class="com.zhang.pojo.Computer">
<!--这个name属性可以省略-->
<constructor-arg name="name" value="惠普"/>
<constructor-arg name="cpu" ref="cpu"/>
</bean>
使用bean标签的autowire属性设置自动装配效果;
若autowire属性的值为byType,则表示根据类型进行装配,注意:上下文默认情况下最多只有一个类型,如果有多个类型,通过设置primary=“true” ,则此bean就是优先注入的;如果给其中多余的bean设置autowire-candidate=“false” 属性,则就不会作为自动装配的候选。
<bean id="cpu1" class="com.zhang.pojo.CPU" autowire-candidate="false">
<property name="name" value="Intel"/>
</bean>
<bean id="cpu2" class="com.zhang.pojo.CPU" primary="true">
<property name="name" value="AMD"/>
</bean>
<bean id="computer" class="com.zhang.pojo.Computer" autowire="byType">
</bean>
若autowire属性的值为byName,则表示根据bean的id进行匹配。而bean的id是根据需要装配组件的属性的属性名来确定的;
<bean id="cpu" class="com.zhang.pojo.CPU">
<property name="name" value="AMD"/>
</bean>
<bean id="computer" class="com.zhang.pojo.Computer" autowire="byName">
</bean>
若autowire属性的值为constructor,则表示通过构造器来匹配。
首先匹配对应参数,名字,类型。
如果有多个类型参数时,优先名字匹配,如果名字匹配成功则正常注入,否则不执行有参构造方法,不能注入
<bean id="cpu1" class="com.zhang.pojo.CPU">
<property name="name" value="Intel"/>
</bean>
<bean id="cpu" class="com.zhang.pojo.CPU">
<property name="name" value="AMD"/>
</bean>
<bean id="computer" class="com.zhang.pojo.Computer" autowire="constructor">
</bean>
<bean id="computer" class="com.zhang.pojo.Computer">
<property name="name" value="惠普"/>
<property name="cpu">
<!-- 在一个 bean 中再声明一个 bean 就是内部 bean -->
<!-- 内部 bean 可以直接用于给属性赋值,可以省略 id 和 name属性 -->
<bean class="com.zhang.pojo.CPU">
<property name="name" value="Intel"/>
</bean>
</property>
</bean>
实体类如下
public class CollectionBean { private List<String> list; private Set<String> set; private String[] arr; private Map<String,String> map; private Properties prop; public CollectionBean(){} public CollectionBean(List<String> list,Set<String> set){ this.list = list; this.set = set; } public List<String> getList() { return list; } public void setList(List<String> list) { this.list = list; } public Set<String> getSet() { return set; } public void setSet(Set<String> set) { this.set = set; } public String[] getArr() { return arr; } public void setArr(String[] arr) { this.arr = arr; } public Map<String, String> getMap() { return map; } public void setMap(Map<String, String> map) { this.map = map; } public Properties getProp() { return prop; } public void setProp(Properties prop) { this.prop = prop; } }
给List、Set、数组、Map、Properties等这些特殊类型进行注入。
<bean id="collectionBean" class="com.zhang.pojo.CollectionBean"> <!-- list标签:准备一组集合类型的数据,给集合属性赋值 --> <property name="list"> <list> <value>张三</value> <value>李四</value> <value>王麻子</value> </list> </property> <!-- 使用array标签给数组属性赋值 --> <property name="arr"> <array> <value>张三</value> <value>李四</value> <value>王麻子</value> </array> </property> <!-- 使用map标签给Map类型的属性赋值 --> <property name="map"> <map> <entry key="张三" value="123"/> <entry key="李四" value="456"/> <entry key="王五" value="789"/> </map> </property> <!-- 使用set标签给集合属性赋值,只是附带了去重功能 --> <property name="set"> <set> <value>张三</value> <value>李四</value> <value>王麻子</value> </set> </property> <!-- 可以使用props标签 --> <property name="prop"> <props> <prop key="张三">123</prop> <prop key="李四">456</prop> <prop key="王五">789</prop> </props> </property> </bean>
null标签:将一个属性值明确设置为null。
<bean id="bean" class="com.zhang.pojo.CollectionBean">
<constructor-arg index="0">
<!-- null标签:将一个属性值明确设置为null -->
<null/>
</constructor-arg>
<constructor-arg index="1">
<!-- null标签:将一个属性值明确设置为null -->
<null/>
</constructor-arg>
</bean>
注意: 小于号在XML文档中用来定义标签的开始,不能随便使用,如果需要使用的话,需要使用XML实体来代替或者使用CDATA节。
CDATA中的C代表Character,是文本、字符的含义,CDATA就表示纯文本数据。XML解析器看到CDATA节就知道这里是纯文本,就不会当作XML标签或属性来解析,因此CDATA节中写什么符号都随意。
实体类如下:
public class Expression {
private String expression;
public String getExpression() {
return expression;
}
public void setExpression(String expression) {
this.expression = expression;
}
}
<bean id="expression" class="com.zhang.pojo.Expression">
<property name="expression">
<value><![CDATA[ a < b]]></value>
</property>
</bean>
Spring 级联属性是当两个bean 关联时,从一个bean 给 另一个bean 赋值。
第一种写法:
<bean id="cpu" class="com.zhang.pojo.CPU">
</bean>
<bean id="computer" class="com.zhang.pojo.Computer">
<property name="name" value="惠普"/>
<property name="cpu" ref="cpu"/>
<!-- 对于Computer来说,cpu的name属性就是级联属性 -->
<property name="cpu.name" value="Inter"/>
</bean>
第二种写法:这种写法之前也提到过。
<bean id="cpu" class="com.zhang.pojo.CPU">
<property name="name" value="Inter"/>
</bean>
<bean id="computer" class="com.zhang.pojo.Computer">
<property name="name" value="惠普"/>
<property name="cpu" ref="cpu"/>
</bean>
在pom.xml中加入依赖
<!-- MySQL驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.26</version>
</dependency>
<!-- 数据源 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.8</version>
</dependency>
创建外部属性文件
jdbc.url=jdbc:mysql://localhost:3306/testdb
jdbc.driver=com.mysql.jdbc.Driver
jdbc.user=root
jdbc.password=123456
在spring.xml文件中引入外部文件,并且使用外部文件。
在这之前,先引入命名空间。
xmlns:context="http://www.springframework.org/schema/context"
<context:property-placeholder location="classpath:jdbc.properties"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="url" value="${jdbc.url}"/>
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="username" value="${jdbc.user}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
测试
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("/spring.xml");
DataSource dataSource = applicationContext.getBean("dataSource", DataSource.class);
try {
System.out.println(dataSource.getConnection());
} catch (SQLException throwables) {
throwables.printStackTrace();
}
可以通过继承来实现代码的复用,属性注入的复用
创建两个实体类:Person和Student
public class Person { private String address; private String sex; private String school; public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } public String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; } public String getSchool() { return school; } public void setSchool(String school) { this.school = school; } } public class Student { // 本身和Person 没有继承关系 private String address; private String sex; private String school; private String name; public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } public String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; } public String getSchool() { return school; } public void setSchool(String school) { this.school = school; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
在spring.xml文件中配置
<!-- 模板,不希望被实例化,设置为抽象的-->
<bean id="person" class="com.zhang.pojo.Person" abstract="true">
<property name="address" value="北京"/>
<property name="school" value="北京邮电大学"/>
<property name="sex" value="男"/>
</bean>
<!-- 使用上面bean 中的属性,可以parent="person" 继承过来-->
<bean id="student" class="com.zhang.pojo.Student" parent="person">
<property name="name" value="张三"/>
</bean>
创建a.xml文件
在a.xml文件中编写bean
<bean id="cpu" class="com.zhang.pojo.CPU">
<property name="name" value="Inter"/>
</bean>
在spring.xml文件引入a.xml文件,并且使用a.xml文件中的bean。
<import resource="a.xml"/>
<bean class="com.zhang.pojo.Computer">
<property name="cpu" ref="cpu"/>
</bean>
String[] files = {"a.xml","spring.xml"};
ApplicationContext context = new ClassPathXmlApplicationContext(files);
<bean name="computer" class="com.zhang.pojo.Computer">
<property name="cpu">
<!--parent:去父容器中查找-->
<ref parent="cpu"/>
</property>
</bean>
在Spring中可以通过配置bean标签的scope属性来指定bean的作用域范围,各取值含义参加下表:
取值 | 含义 | 创建对象的时机 |
---|---|---|
singleton | 在IOC容器中,这个bean的对象始终为单实例,不管多少次getBean,只实例化一次 | IOC容器初始化时 |
prototype | 这个bean在IOC容器中有多个实例,每次getBean时,都会实例化 | 获取bean时 |
lazy-init="false" : 立即加载,创建上下文时,立即构造bean 对象
lazy-init="true" : 延时加载,在使用时,获取bean 时,才去实例加载
bean的生命周期清单
bean的初始化方法和销毁方法
public void init(){
System.out.println("初始化方法");
}
public void destroy(){
System.out.println("销毁方法");
}
使用init-method属性指定初始化方法
使用destroy-method属性指定销毁方法
<bean id="cpu" class="com.zhang.pojo.CPU" init-method="init" destroy-method="destroy">
<property name="name" value="Inter"/>
</bean>
bean的后置处理器
① 创建后置处理器
// 声明一个自定义的bean后置处理器 // 注意:bean后置处理器不是单独针对某一个bean生效,而是针对IOC容器中所有bean都会执行 public class BeanProcessor implements BeanPostProcessor { @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { System.out.println("==== " + beanName + " = " + bean); return null; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { System.out.println("--- " + beanName + " = " + bean); return null; } }
②将bean的后置处理器放入IOC容器
因为bean的后置处理器要放入IOC容器才能生效。
<bean id="beanProcessor" class="com.zhang.BeanProcessor"/>
注解的概念:和XML文件一样,注解本身并不能执行,注解本身仅仅只是做一个标记,具体的功能是框架检测到注解标记的位置,然后针对这个位置按照注解标记的功能来执行具体操作。
但是追根本源:所有一切的操作都是由Java代码来完成的,XML和注解只是告诉框架中的Java代码如何执行。
注意: 我们使用注解后,每一个组件必须保证有一个唯一的标识。
扫描的概念:Spring要通过扫描包,才能知道开发人员在哪标记了注解,然后通过注解进行后面的操作。
使用@Component注解标记普通组件
import org.springframework.stereotype.Component;
@Component
public class CommonComponent {
}
使用@Repository注解标记持久化层组件
import org.springframework.stereotype.Repository;
@Repository
public class UserDaoImpl implements UserDao {
}
使用@Service注解标记业务逻辑组件
import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl implements UserService {
}
使用@Controller注解标记控制层组件
import org.springframework.stereotype.Controller;
@Controller
public class UserController {
}
上面四个组件之间的对比:
我们可以点进去查看源码,我们会发现:@Controller、@Service、@Repository这三个注解只是在@Component注解的基础上起了三个新的名字。对于Spring的IOC容器来说,这三个组件本质上没有什么区别,而他们的的作用主要是来提高代码的可读性。
最基本扫描的配置
<!-- 配置自动扫描的包 -->
<context:component-scan base-package="com.zhang"/>
在配置扫描包的基础上指定匹配模式
<!-- resource-pattern属性:配置要扫描资源的正则表达式的 -->
<context:component-scan base-package="com.zhang" resource-pattern="**/User*.class"/>
在配置扫描包的基础上指定不需要扫描的包
<context:component-scan base-package="com.zhang">
<!--
context:exclude-filter标签:指定一些不需要扫描的包
type属性:指定根据什么来进行去除。例如:annotation取值是根据注解来排除。
expression属性:指定全类名,表示过滤器的表达式
-->
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
在配置扫描包的基础上指定其他需要的规则
<!-- 仅扫描 = 关闭默认规则 + 追加规则 -->
<!-- use-default-filters属性:取值false表示关闭默认扫描规则 -->
<context:component-scan base-package="com.zhang" use-default-filters="false">
<!-- context:include-filter标签: 在配置扫描包的基础上添加其他需要的规则 -->
<context:include-filter type="annotation" expression="org.springframework.stereotype.Repository"/>
</context:component-scan>
注意:被组件标记后,默认情况下获取bean,就是类名的首字母小写。
如:UserServiceImpl类对应的bean的id或name属性就是 userServiceImpl。
当然,我们也可以自定义bean的名字。如果当注解中只有一个属性时,其value属性的属性名可以省略不写。
// @Service("service")
@Service(value = "service")
public class UserServiceImpl implements UserService {
}
在成员变量上使用@Autowired注解来完成自动装配,不在需要setXxx()方法了。
注意:自动装配前必须要扫描所用到的所有组件,将其装入IOC容器。
在controller层装配Service
@Controller("controller")
public class UserController {
@Autowired
private UserService userService;
public void add(){
userService.add();
}
}
在service层装配Dao
@Service(value = "useService")
public class UserServiceImpl implements UserService {
@Autowired
private UserDao userDao;
@Override
public void add() {
userDao.insert();
}
}
注意:@Autowired注解还可以标记在构造器和setXxx()方法上。
注意: @Qualifier 注解和 @Autowired注解要搭配使用,可以只有@Autowired注解,但是不能只有 @Qualifier 注解。
① 首先到IOC容器中查找所需要的组件类型
如果能找到唯一的bean,则进行装配;
如果找不到这个bean,则装配失败;
如果有多个相同类型的bean的话
若有 @Qualifier 注解:则需要根据 @Qualifier 注解中指定的名称作为bean的id或name进行匹配
如果找到,则匹配成功;找不到,则匹配失败。
若没有 @Qualifier 注解:则需要按照被加了@Autowired注解的属性的名字进行匹配
如果找到,则匹配成功;找不到,则匹配失败。
提醒: @Autowired注解设置required = false属性表示:能装就装,装不上就不装。
创建组件类:
@Component
public class CommonComponents {
}
创建配置类:
使用@Configuration注解将一个普通的类标记为Spring的配置类
@Configuration
// 配置自动扫描包
@ComponentScan("com.zhang")
public class MyConfiguration {
// @Bean注解相当于XML配置文件中的bean标签
// @Bean注解标记的方法的返回值会被放入IOC容器
@Bean(name = "abc")
public CommonComponents getComponent() {
CommonComponents commonComponent = new CommonComponents();
System.out.println("数码宝贝");
return commonComponent;
}
}
测试
public static void main(String[] args) {
// AnnotationConfigApplicationContext根据配置类创建IOC容器对象
ApplicationContext context= new AnnotationConfigApplicationContext(MyConfiguration.class);
context.getBean("abc");
}
向spring.xml文件中加入依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.3.5</version>
</dependency>
注意:此依赖的版本号要与spring整体的依赖要保持一致,要不然会报错。针对于这个情况,我们以后可以在pom.xml文件中这样配置spring的相关依赖,可以更方便的管理spring相关的版本。
<properties>
<spring-version>5.3.5</spring-version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring-version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring-version}</version>
</dependency>
</dependencies>
junit的使用
// junit的@RunWith注解:指定Spring为Junit提供的运行器
@RunWith(SpringJUnit4ClassRunner.class)
// Spring的@ContextConfiguration指定Spring配置文件的位置
@ContextConfiguration(value = {"classpath:spring.xml"})
public class TestJunit {
@Autowired
private UserController userController;
@Test
public void test(){
userController.add();
}
}
使用junit的好处:
①:不需要之间创建IOC容器对象了。
②:任何的bean都可以在测试类中直接进行自动装配。
代理模式是一种常用的Java设计模式。代理模式就是为一个对象提供一个代理类来扩展自己本身的功能。也就是说通过代理类来访问此对象中的目标方法。例如:广告商找明星拍广告需要通过经纪人。
下面是用图来描述一下。
按照代理的创建时期,代理类可以分为两种。
编写接口
public interface IPlay {
void play(String gameName);
}
编写接口的实现类(被代理类)
public class Play implements IPlay{
@Override
public void play(String gameName) {
System.out.println();
System.out.println("我在玩" + gameName);
System.out.println();
}
}
编写静态代理类
public class PlayProxy implements IPlay{ // 将被代理对象声明为成员变量 private IPlay play; public PlayProxy(IPlay play) { this.play = play; } @Override public void play(String gameName) { System.out.println("调用被代理类方法之前做的准备工作"); // 调用被代理对象执行的目标方法 play.play(gameName); System.out.println("调用被代理类方法完成"); } }
测试:
public class Test {
public static void main(String[] args) {
IPlay play = new Play();
PlayProxy playProxy = new PlayProxy(play);
playProxy.play("王者");
}
}
概念:根据代理类对象的接口,动态创建代理类对象
有两种方法实现动态代理:
通过图来理解jdk实现的动态代理
实现步骤:
①:编写代理类,实现接口 InvocationHandler 用来实现回调
②:声明属性,用来接收所有类型对象(被代理类对象)
③:编写方法,根据被代理类对象动态创建代理类对象
④:重写 InvocationHandler 接口中的 invoke 方法,通过反射调用原有业务方法,做增强功能。
编写代理类
public class ProxyBean implements InvocationHandler { // 声明被代理类对象 private Object biz; public Object newProxyInstance(Object biz){ this.biz = biz; // 创建代理类对象需要三个参数: // ①:加载目标对象的类的类加载器 // ②:目标对象所实现的所有接口组成的数组 // ③:需要 InvocationHandler 对象,因为本类是 InvocationHandler接口的实现类,所以传入本类对象即可 return Proxy.newProxyInstance(biz.getClass().getClassLoader(), biz.getClass().getInterfaces(),this); } /** * * @param proxy 代理对象,但是这里未用到 proxy(代理对象) * @param method 代表目标方法的Method对象 * @param args 外部调用目标方法时传入的实际参数 * @return * @throws Throwable */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("方法名为:" + method.getName()); // invoke方法中的第一个参数是调用目标方法的目标对象,即声明的成员变量。 // 第二个参数是外部调用目标方法时传入的参数。 // 声明这个变量来接收目标方法的返回值 Object targetValue = method.invoke(biz,args); return targetValue; } }
测试
public class Test {
public static void main(String[] args) {
// 创建被代理的对象
IPlay plays = new Play();
// 创建代理类
ProxyBean proxyBean = new ProxyBean();
// 通过代理类来创建被代理类的对象
IPlay play = (IPlay)proxyBean.newProxyInstance(plays);
// 通过代理类的对象调用被代理类对象的方法
play.play("王者");
}
}
通过图来理解cglib实现的动态代理
注意: 在编写业务类的时候不需要实现接口了。
编写业务类
public class Play {
public void play(String gameName) {
System.out.println();
System.out.println("我在玩" + gameName);
System.out.println();
}
}
在pom.xml文化中加入依赖
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
编写代理类
public class CglibProxyBean implements MethodInterceptor { /** * 根据被代理对象,动态生成代理类对象(被代理类的子类) * @param biz 传入被代理类对象 * @return */ public Object newProxyInstance(Object biz){ // 创建动态类对象 Enhancer enhancer = new Enhancer(); // 这个是将代理类对象设置为被代理类对象的子类 // 即代理类对象作为子类,被代理类对象作为父类 enhancer.setSuperclass(biz.getClass()); // 设置回调 enhancer.setCallback(this); return enhancer.create(); } /** * * @param o 代理对象 * @param method 被代理对象的目标方法 * @param objects 调用被代理方法时传入的参数 * @param methodProxy 生成的代理类 * @return * @throws Throwable */ @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { System.out.println("========前置增强========"); System.out.println(method.getName()); Object targetValue = methodProxy.invokeSuper(o, objects); System.out.println("========后置增强========"); return targetValue; } }
测试
public class Test {
public static void main(String[] args) {
IPlay play = new Play();
CglibProxyBean cglibProxyBean = new CglibProxyBean();
play = (IPlay)cglibProxyBean.newProxyInstance(play);
play.play("王者");
}
}
①:实现方式不同
jdk实现动态代理必须依赖接口。
cglib实现动态代理是通过类来实现,即代理类要作为被代理类的子类(代理类继承被代理类)。
②:实现原理不同
jdk的动态代理是利用反射机制生成一个实现代理接口的代理类,在调用具体方法前调用InvokeHandler方法来处理。
cglib动态代理是利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。
③:性能不同
jdk1.6和1.7的时候,jdk实现动态代理要比cglib实现动态代理的速度慢。
jdk1.8的时候,jdk实现动态代理要比cglib实现动态代理的速度快。
关于使用上的注意点:
如果被代理类实现了接口,则Spring会默认使用jdk动态代理。
如果被代理类未实现接口,则Spring要采用cglib动态代理。
如果被代理类实现了接口,Spring也可以强制使用cglib动态代理。
如果被代理类被final修饰,则不能采用cglib动态代理。
概念:把所有通知方法封装到一个类中。
概念:从每个方法中抽取出来的同一类非核心业务。在未改变原有功能的基础上,额外加了几个功能,就有几个横切关注点。
下面用图来可以更好理解。
概念:将方法排成一排,每一个横切位置看成x轴方向,把方法从上到下执行的顺序看成y轴方向,x轴和y轴的相交点就是连接点。即执行通知的方法。
下面用图表示:
切入点也可以这样理解:在所有的方法中都可以使用增强通知,但你只需要其中的一部分的方法使用增强通知,其余的方法不需要使用增强通知,因此这些部分方法使用了增强通知的地方叫做切入点。
注意:AOP技术是通过切入点来定位连接点。
下面用图来理解:
在每一个横切关注点上要做的事情需要写一个方法来实现,这样的方法就叫增强通知。
将通知方法(切面)应用到被代理对象(目标对象)中。
在pom.xml文件中导入依赖
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.7</version>
</dependency>
execution(public * com.zhang..*.*(..))
execution()是最常用的切点函数,其语法如下所示:
整个表达式可以分为五个部分:
①:execution():表达式主体。
②:第一个 * 号:表示返回类型, * 表示所有的类型。
③:包名: 表示需要拦截的包名,后面的两个句点表示当前包和当前包的所有子包。
④:第二个 * 号:表示类名, * 号表示所有的类。
⑤:*(…):最后这个 * 号表示方法名,* 号表示所有的方法,后面括号里面表示方法的参数,两个句点表示任何参数。
注意点:
例子:
①:表示匹配所有名称以Dao结尾的类或接口
public * com.zhang.*Dao..*.*(..)
②:表示匹配所有名称以Test结尾的方法
public * com.zhang.Dao..*Test.*(..)
③:表示匹配所有方法中第一个参数类型为int的方法
public * com.zhang.Dao..*.*(int,..)
④:表示匹配所有方法的返回值类型为int的方法
public int com.zhang.Dao..*.*(..)
五个通知(增强类):
通知 | 执行时间 | 实现方式 |
---|---|---|
前置通知 | 在运行目标方法之前执行的 | 实现MethodBeforeAdvice接口,并重写before()方法 |
后置通知 | 在运行目标方法之后执行的,如果运行目标方法过程中出现异常,则不会执行后置通知 | 实现AfterReturningAdvice接口,并重写afterReturning()方法 |
异常通知 | 在运行目标方法时出现异常执行的 | 实现ThrowsAdvice接口,无重写方法 |
环绕通知 | 在环绕通知中可以定义前置通知、后置通知、异常通知、最终通知 | 实现MethodInterceptor接口,并重写invoke()方法; |
最终通知 | 运行完方法后,不管是否发生异常,都会执行 | 无 |
AdviceBefore(前置通知)
public class AdviceBefore implements MethodBeforeAdvice { private Logger logger = Logger.getLogger(AdviceBefore.class); /** * 功能: 日期+类+方法+参数 * @param method 将来调用的方法 * @param objects 方法的传入参数 * @param o 目标对象 * @throws Throwable */ @Override public void before(Method method, Object[] objects, Object o) throws Throwable { logger.info("---------前置通知开始----------"); logger.info(new Date() + ": " + o.getClass().getName() + "." + method.getName()); logger.info("参数有: " + Arrays.toString(objects)); logger.info("---------前置通知完成---------"); } }
AfterReturningAdvice(后置通知)
public class AdviceAfter implements AfterReturningAdvice{ private Logger logger = Logger.getLogger(AdviceBefore.class); /** * 功能: 日期+类+方法+参数 * @param o 方法的返回值 * @param method 将来调用的方法 * @param objects 方法的传入参数 * @param o1 目标对象 * @throws Throwable */ @Override public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable { logger.info("---------后置通知开始----------"); logger.info(new Date() + ": " + o1.getClass().getName() + "." + method.getName()); logger.info("返回值:" + o); logger.info("参数有: " + Arrays.toString(objects)); logger.info("---------后置通知完成---------"); } }
ThrowsAdvice(异常通知)
public class AdviceThrows implements ThrowsAdvice { private Logger logger = Logger.getLogger(AdviceThrows.class); /** * @param method 将来调用的方法 * @param args 方法的传入参数 * @param target 目标对象 * @param e */ public void afterThrowing(Method method, Object[] args, Object target, Exception e) { logger.info("---------异常通知开始----------"); logger.info(new Date() + ": " + target.getClass().getName() + "." + method.getName()); logger.info("参数有: " + Arrays.toString(args)); logger.info("异常信息为:" + e.getMessage()); logger.info("---------异常通知完成---------"); } }
MethodInterceptor(环绕通知)
public class AdviceAround implements MethodInterceptor { private Logger logger = Logger.getLogger(AdviceBefore.class); @Override public Object invoke(MethodInvocation methodInvocation) throws Throwable { logger.info("---------环绕通知开始----------"); try { logger.info("全类名为:" + methodInvocation.getThis().getClass().getName()); // 获取方法 Method method = methodInvocation.getMethod(); logger.info("方法名:" + method.getName()); // 获取参数 Object[] arguments = methodInvocation.getArguments(); logger.info("参数有:" + Arrays.toString(arguments)); // 必须要使用methodInvocation.proceed(),可以决定业务类方法执行的位置 logger.info("返回值为:" + methodInvocation.proceed()); } catch (Throwable throwable) { logger.info("异常信息为:" + throwable.getMessage()); } finally { logger.info("环绕通知中的最终通知"); } logger.info("---------环绕通知完成---------"); return null; } }
编写业务类
// 业务的接口
public interface IPlay {
void play(String gameName);
}
// 实现业务的接口
public class Play implements IPlay{
@Override
public void play(String gameName) {
System.out.println();
// int i = 10 / 0;
System.out.println("我在玩" + gameName);
System.out.println();
}
}
配置spring.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"> <!-- 业务bean --> <bean id="play" class="com.zhang.test.Play"/> <!-- 前置通知 --> <bean id="adviceBefore" class="com.zhang.aop.AdviceBefore"/> <!-- 后置通知 --> <bean id="adviceAfter" class="com.zhang.aop.AdviceAfter"/> <!-- 异常通知 --> <bean id="adviceThrow" class="com.zhang.aop.AdviceThrows"/> <!-- 环绕通知 --> <bean id="adviceAround" class="com.zhang.aop.AdviceAround"/> <!-- 代理bean --> <bean id="proxyFactoryBean" class="org.springframework.aop.framework.ProxyFactoryBean"> <!-- target属性:指定要代理的目标类--> <property name="target" ref="play"/> <!-- proxyInterfaces属性:指定要代理的接口(即代理要实现的接口)--> <property name="proxyInterfaces" value="com.zhang.test.IPlay"/> <!-- proxyTargetClass属性:如果value为true,则使用cglib;如果value为false,则使用jdk动态代理--> <property name="proxyTargetClass" value="true"/> <!-- interceptorNames属性:指定要在代理的目标类中添加的功能--> <property name="interceptorNames"> <list> <list> <!-- 一般情况配置了环绕通知就不需要配置前置通知、异常通知、后置通知了 --> <value>adviceAround</value> <value>adviceThrow</value> <!--<value>adviceBefore</value> <value>adviceAfter</value>--> </list> </list> </property> </bean> </beans>
与通过代理实现AOP对比,它俩不同的是spring.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 http://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="play" class="com.zhang.test.Play"/> <!-- 前置通知 --> <bean id="adviceBefore" class="com.zhang.aop.AdviceBefore"/> <!-- 后置通知 --> <bean id="adviceAfter" class="com.zhang.aop.AdviceAfter"/> <!-- 异常通知 --> <bean id="adviceThrow" class="com.zhang.aop.AdviceThrows"/> <!-- 环绕通知 --> <bean id="adviceAround" class="com.zhang.aop.AdviceAround"/> <!-- 配置aop,并且导入aop的命名空间--> <aop:config> <!-- 切入点(连接点的集合) 连接点 执行通知的方法 --> <aop:pointcut expression="execution(public * com.zhang.test..*.*(..))" id="pointcut"/> <!-- 定义AOP通知器,这是一种特殊的aspect; 其advice-ref属性是用来配置通知 pointcut-ref属性是用来配置切点 --> <!-- <aop:advisor advice-ref="adviceBefore" pointcut-ref="pointcut"/> <aop:advisor advice-ref="adviceAfter" pointcut-ref="pointcut"/> <aop:advisor advice-ref="adviceThrow" pointcut-ref="pointcut"/>--> <aop:advisor advice-ref="adviceAround" pointcut-ref="pointcut"/> </aop:config> </beans>
自定义的LogAdvice类:
注意:在通知方法的形参位置声明一个JoinPoint类型的参数,Spring就会将这个对象传入。通过JoinPoint对象就可以获取目标方法名、实参数列表等。
public class LogAdvice { private Logger logger = Logger.getLogger(LogAdvice.class); /** * 前置通知 * @param joinPoint */ public void before(JoinPoint joinPoint){ logger.info("****************前置增强处理开始**************"); logger.info("全类名为:" + joinPoint.getTarget().getClass().getName()); // getSignature()可以获取目标方法签名对象,即一个方法的全部声明信息 // 通过方法的签名对象获取目标方法的详细信息 logger.info("方法名为:" + joinPoint.getSignature().getName()); // 通过JoinPoint对象获取外界调用目标方法时传入的实参列表 logger.info("参数为:" + Arrays.toString(joinPoint.getArgs())); logger.info("****************前置增强处理结束**************"); } /** * 后置通知 * @param joinPoint */ public void after(JoinPoint joinPoint){ logger.info("****************最终增强处理开始**************"); logger.info("全类名为:" + joinPoint.getTarget().getClass().getName()); logger.info("方法名为:" + joinPoint.getSignature().getName()); logger.info("参数为:" + Arrays.toString(joinPoint.getArgs())); logger.info("****************最终增强处理结束**************"); } /** * 最终通知 * @param joinPoint * @param val */ public void afterReturning(JoinPoint joinPoint,Object val){ logger.info("****************后置增强处理开始**************"); logger.info(val); logger.info("****************后置增强处理结束**************"); } /** * 异常通知 * @param joinPoint * @param exception */ public void except(JoinPoint joinPoint,Exception exception){ logger.info("****************异常增强处理开始**************"); logger.info(exception.getMessage()); logger.info("****************异常增强处理结束**************"); } /** * 环绕通知 * @param joinPoint */ public void around(ProceedingJoinPoint joinPoint){ logger.info("****************环绕增强处理开始**************"); logger.info("全类名为:" + joinPoint.getTarget().getClass().getName()); logger.info("方法名为:" + joinPoint.getSignature().getName()); logger.info("参数为:" + Arrays.toString(joinPoint.getArgs())); try { logger.info("****************前置增强处理**************"); // 原有业务执行 Object returnVal = joinPoint.proceed(); logger.info("****************后置增强处理**************" + " 返回值:" + returnVal); } catch (Exception e) { e.getMessage(); logger.info("****************异常增强处理**************"); } catch (Throwable throwable) { throwable.printStackTrace(); logger.info(throwable.getMessage()); } finally { logger.info("****************最终增强处理**************"); } logger.info("****************环绕增强处理结束**************"); } }
编写spring.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 http://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="play" class="com.zhang.test.Play"/> <!-- 配置切面类的bean --> <bean id="logAdvice" class="com.zhang.aop.LogAdvice"/> <!-- 配置AOP --> <aop:config> <!-- 配置切入点表达式 --> <aop:pointcut id="pointcut" expression="execution(public * com.zhang.test..*.*(..))"/> <!-- aop:aspect标签:配置切面 --> <!-- ref属性:关联切面类的bean --> <aop:aspect ref="logAdvice"> <!-- aop:before标签: 配置前置通知 --> <!-- method属性:指定前置通知的方法名 --> <!-- pointcut-ref属性:引用切入点表达式 --> <aop:before method="before" pointcut-ref="pointcut"/> <!-- aop:after标签:配置最终通知 --> <aop:after method="after" pointcut-ref="pointcut"/> <!-- aop:after-returning标签:配置后置通知 --> <!-- returning属性:指定通知方法中用来接收目标方法返回值的参数名 --> <aop:after-returning method="afterReturning" pointcut-ref="pointcut" returning="val"/> <!-- aop:after-throwing标签:配置异常通知 --> <!-- throwing属性:指定的通知方法中用来接收目标方法抛出异常的异常对象的参数名--> <aop:after-throwing method="except" throwing="exception" pointcut-ref="pointcut"/> <!-- aop:around标签:配置环绕通知--> <aop:around method="around" pointcut-ref="pointcut"/> </aop:aspect> </aop:config> </beans>
在spring.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"
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/aop https://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!-- 配置自动扫描的包 -->
<context:component-scan base-package="com.zhang.test"/>
<!-- 启用基于注解的AOP功能 -->
<aop:aspectj-autoproxy/>
</beans>
编写业务类
跟之前不同的是: 使用注解实现时不需要实现接口。
public class Play{
public void play(String gameName) {
System.out.println();
// int i = 10 / 0;
System.out.println("我在玩" + gameName);
System.out.println();
}
}
自定义通知类添加注解
// @Aspect表示这个类是一个切面类 @Aspect // @Component注解保证这个切面类能够放入IOC容器 @Component public class LogAdvice { private Logger logger = Logger.getLogger(LogAdvice.class); // 切入点表达式重用 @Pointcut("execution(public * com.zhang.test..*.*(..))") public void myPoint(){} // @Before注解:声明当前方法是前置通知方法 // value属性:指定切入点表达式,由切入点表达式控制当前通知方法要作用在哪一个目标方法上 // 第一种方法是写切入点表达式 // @Before(value = "execution(public * com.zhang.test..*.*(..))") // 第二种方法是使用已经定义好的切入点表达式, // 注意:使用切入点表达式不仅可以使用本类中定义好的,也可以使用其他类中的。 @Before(value = "myPoint()") public void before(JoinPoint joinPoint){ logger.info("****************前置增强处理开始**************"); logger.info("全类名为:" + joinPoint.getTarget().getClass().getName()); logger.info("方法名为:" + joinPoint.getSignature().getName()); logger.info("参数为:" + Arrays.toString(joinPoint.getArgs())); logger.info("****************前置增强处理结束**************"); } // @After注解标记最终通知方法 //@After(value = "execution(public * com.zhang.test..*.*(..))") @Before(value = "myPoint()") public void after(JoinPoint joinPoint){ logger.info("****************最终增强处理开始**************"); logger.info("全类名为:" + joinPoint.getTarget().getClass().getName()); logger.info("方法名为:" + joinPoint.getSignature().getName()); logger.info("参数为:" + Arrays.toString(joinPoint.getArgs())); logger.info("****************最终增强处理结束**************"); } // @AfterReturning注解标记后置通知方法 // 注意:要在@AfterReturning注解中通过returning属性设置形参名称, // 并且要在通知方法中声明一个对应的形参, // 其方法中形参的名称要与returning属性设置的名称要相对应,Spring会将目标方法的返回值从这里传给我们 @AfterReturning(value = "execution(public * com.zhang.test..*.*(..))",returning = "val") public void afterReturning(JoinPoint joinPoint,Object val){ logger.info("****************后置增强处理开始**************"); logger.info(val); logger.info("****************后置增强处理结束**************"); } // @AfterThrowing注解标记异常通知方法 // 在@AfterThrowing注解中声明一个throwing属性设置形参名称 // 并且在通知方法中声明一个对应的形参, // 其方法中的形参名称就是在throwing属性中所设置的, // Spring会将目标方法抛出的异常对象从这里传给我们 @AfterThrowing(value = "execution(public * com.zhang.test..*.*(..))",throwing = "exception") public void except(JoinPoint joinPoint,Exception exception){ logger.info("****************异常增强处理开始**************"); logger.info(exception.getMessage()); logger.info("****************异常增强处理结束**************"); } // 使用@Around注解标明环绕通知方法 // 在通知方法形参位置声明ProceedingJoinPoint类型的形参,Spring会将这个类型对象传给我们 // @Around(value = "execution(public * com.zhang.test..*.*(..))") public void around(ProceedingJoinPoint joinPoint){ logger.info("****************环绕增强处理开始**************"); logger.info("全类名为:" + joinPoint.getTarget().getClass().getName()); // 通过getSignature()方法获取签名对象,然后通过签名对象获取目标方法的方法名 logger.info("方法名为:" + joinPoint.getSignature().getName()); // 通过ProceedingJoinPoint对象获取外界调用目标方法时传入的实参数组 logger.info("参数为:" + Arrays.toString(joinPoint.getArgs())); try { logger.info("****************前置增强处理**************"); // 通过ProceedingJoinPoint对象让原有业务执行 // 声明变量用来存储目标方法的返回值 Object returnVal = joinPoint.proceed(); logger.info("****************后置增强处理**************" + " 返回值:" + returnVal); } catch (Exception e) { e.getMessage(); logger.info("****************异常增强处理**************"); } catch (Throwable throwable) { throwable.printStackTrace(); logger.info(throwable.getMessage()); } finally { logger.info("****************最终增强处理**************"); } logger.info("****************环绕增强处理结束**************"); } }
Spring提供的数据访问模板,提供了很多方便操作数据库的方法,例如JDBCTemplate。
在pom.xml文件中添加依赖
<dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.3.10</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-orm</artifactId> <version>5.3.10</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.3.10</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.24</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.26</version> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.13</version> </dependency> </dependencies>
编写实体类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Book {
private Integer bookid;
private String bookname;
private Float price;
private String author;
private Date pubDate;
}
编写Dao接口
public interface BookDao {
List<Book> queryAll();
}
编写使用Dao接口的类
@Repository public class BookDao implements IBookDao { @Autowired private JdbcTemplate template; @Override public List<Book> queryAll() { String sql = "select bookid,bookname,pubDate,price,author from t_book"; return template.query(sql,new BookRowMapper()); } class BookRowMapper implements RowMapper<Book>{ @Override public Book mapRow(ResultSet rs, int rowNum) throws SQLException { Book book = new Book(); book.setBookid(rs.getInt("bookid")); book.setBookname(rs.getString("bookname")); book.setPrice(rs.getFloat("price")); book.setAuthor(rs.getString("author")); book.setPubDate(rs.getDate("pubDate")); return book; } } }
编写spring-conf.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"> <context:component-scan base-package="com.zhang"/> <bean name="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="username" value="root"></property> <property name="password" value="123456"></property> <property name="url" value="jdbc:mysql://127.0.0.1:3306/testdb"> </property> <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"> </property> </bean> <bean name="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSource"/> </bean> </beans>
编写测试类
@Test
public void test1(){
ApplicationContext context = new ClassPathXmlApplicationContext("spring-conf.xml");
IBookDao bookDao = context.getBean("bookDao", IBookDao.class);
bookDao.queryAll().forEach(System.out::println);
}
IBookDao接口中
public interface IBookDao {
String get(Integer bookid);
void save(Book book);
Integer update(Book book);
Integer delete(Integer bookid);
}
IBookDao接口的实现类中
@Repository public class BookDao implements IBookDao { @Autowired private JdbcTemplate template; // 根据bookid查询图书名字 @Override public String get(Integer bookid) { String sql = "select bookname from t_book where bookid = ?"; return template.queryForObject(sql,String.class,1); } // 添加图书 @Override public void save(Book book) { String sql = "insert into t_book (bookname,pubDate,price,author) values (?,?,?,?)"; template.update(sql,book.getBookname(),book.getPubDate(),book.getPrice(),book.getAuthor()); } // 更新图书 @Override public Integer update(Book book) { String sql = "update t_book set bookname = ? where bookid = ?"; return template.update(sql,book.getBookname(),book.getBookid()); } // 根据id删除图书 @Override public Integer delete(Integer bookid) { String sql = "delete from t_book where bookid = ?"; return template.update(sql,bookid); } }
测试类
@Test public void test2(){ ApplicationContext context = new ClassPathXmlApplicationContext("spring-conf.xml"); IBookDao bookDao = context.getBean("bookDao", IBookDao.class); System.out.println(bookDao.get(1)); } @Test public void test3() { ApplicationContext context = new ClassPathXmlApplicationContext("spring-conf.xml"); IBookDao bookDao = context.getBean("bookDao", IBookDao.class); bookDao.save(new Book(null,"C++",65f,"王五",new Date())); } @Test public void test4() { ApplicationContext context = new ClassPathXmlApplicationContext("spring-conf.xml"); IBookDao bookDao = context.getBean("bookDao", IBookDao.class); Book book = new Book(); book.setBookid(1); book.setBookname("Spring"); bookDao.update(book); } @Test public void test5() { ApplicationContext context = new ClassPathXmlApplicationContext("spring-conf.xml"); IBookDao bookDao = context.getBean("bookDao", IBookDao.class); bookDao.delete(2); }
在IBookDao的接口中
public interface IBookDao {
List<Book> queryAll();
}
在IBookDao接口的实现类中
@Repository
public class BookDao implements IBookDao {
@Autowired
private JdbcTemplate template;
@Override
public List<Book> queryAll() {
String sql = "select bookid,bookname,pubDate,price,author from t_book";
return template.query(sql,new BeanPropertyRowMapper<>(Book.class));
}
}
在IBookDao的接口中
public interface IBookDao {
void batchInsert(List<Book> list);
}
在IBookDao接口的实现类中
@Repository public class BookDao implements IBookDao { @Autowired private JdbcTemplate template; @Override public void batchInsert(List<Book> list) { String sql = "insert into t_book (bookname,price,author,pubDate)values(?,?,?,?)"; List<Object[]> params = new ArrayList<>(); for (Book book : list) { Object[] param = {book.getBookname(),book.getPrice(),book.getAuthor(),book.getPubDate()}; params.add(param); } template.batchUpdate(sql, params); } }
测试类:
@Test
public void test6() {
ApplicationContext context = new ClassPathXmlApplicationContext("spring-conf.xml");
IBookDao bookDao = context.getBean("bookDao", IBookDao.class);
List<Book> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
Book book = new Book();
book.setBookname("Java" + i);
book.setPubDate(new Date());
book.setAuthor("王五" + i);
book.setPrice(65 + 1f);
list.add(book);
}
bookDao.batchUpdate(list);
}
事务功能的相关操作全部通过自己编写代码来实现:
Connection conn = ...;
try {
// 开启事务:关闭事务的自动提交
conn.setAutoCommit(false);
// 核心操作
// 提交事务
conn.commit();
}catch(Exception e){
// 回滚事务
conn.rollBack();
}finally{
// 释放数据库连接
conn.close();
}
缺陷:需要开发人员手动来完成一些细节上的操作,如果开发人员不能将一些代码有效的抽取出来,代码就不能得到高效利用,每次实现功能时都需要自己手动编写额外的代码。
由于编程式事务的缺陷,我们可以使用框架将固定模式的代码抽取出来,进行相关的封装。此时,我们只需要在配置文件中进行简单的配置即可完成操作。
好处:
①:提高了开发效率
②:消除了冗余的代码
声明式事务和编程式事务的区别:
我们在Spring使用的事务管理器是org.springframework.jdbc.datasource.DataSourceTransactionManager,之后整合MyBatis用的也是这个类。
DataSourceTransactionManager类中的方法:
我们可以看到事务的一些主要方法:
这里我们还是用 JDBCTemplate部分 准备工作的数据,再次之外,我们还会加入业务逻辑层,在业务逻辑层中使用事务。
下来会测试Spring在默认情况下的事务是否会生效。
准备 IBookDao接口中添加方法
Integer updateMoney(Integer bookid,String money);
实现IBookDao接口,因为这里是在做测试,所以将下面的Sql语句写错,为了便于测试默认情况下事务是否会生效。
@Override
public Integer updateMoney(Integer bookid,String money) {
// 这里故意将sql语句写错。
String sql = "update t1_book set price = ? where bookid = ?";
return template.update(sql,money,bookid);
}
编写IBookService接口
void updateTwice(Integer bookid,String bookName,String money);
实现IBookService接口
@Autowired
private IBookDao bookDao;
@Override
public void updateTwice(Integer bookid, String bookName, String money) {
Book book = new Book();
book.setBookid(bookid);
book.setBookname(bookName);
// 第一次修改书名
bookDao.update(book);
// 第二次修改金额
bookDao.updateMoney(bookid,money);
}
添加日志依赖
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
配置logback.xml文件
<?xml version="1.0" encoding="UTF-8"?> <configuration debug="true"> <!-- 指定日志输出的位置 --> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <!-- 日志输出的格式 --> <!-- 按照顺序分别是:时间、日志级别、线程名称、打印日志的类、日志主体内容、换行 --> <pattern>[%d{HH:mm:ss.SSS}] [%-5level] [%thread] [%logger] [%msg]%n</pattern> </encoder> </appender> <!-- 设置全局日志级别。日志级别按顺序分别是:DEBUG、INFO、WARN、ERROR --> <!-- 指定任何一个日志级别都只打印当前级别和后面级别的日志。 --> <root level="INFO"> <!-- 指定打印日志的appender,这里通过“STDOUT”引用了前面配置的appender --> <appender-ref ref="STDOUT" /> </root> <!-- 根据特殊需求指定局部日志级别 --> <logger name="org.springframework.jdbc.datasource.DataSourceTransactionManager" level="DEBUG"/> <logger name="org.springframework.jdbc.core.JdbcTemplate" level="DEBUG" /> </configuration>
最终结果: 名字修改成功了,但是书的价格修改失败。因此在默认情况下,事务是不会开启的。
<!-- 配置事务管理器 -->
<bean name="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 事务管理器的bean只需要装配数据源,其他属性保持默认值即可 -->
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 开启基于注解的声明式事务功能 -->
<!-- 使用transaction-manager属性指定当前使用是事务管理器的bean -->
<!-- transaction-manager属性的默认值是transactionManager,如果事务管理器bean的id正好就是这个默认值,则可以省略这个属性 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
这里给大家提醒一下,导入<tx:annotation-driven />这个的命名空间时要注意一下,千万不能导错,否则就会无用。
xmlns:tx="http://www.springframework.org/schema/tx"
@Autowired
private IBookDao bookDao;
@Transactional
public void updateTwice(Integer bookid, String bookName, Float money) {
Book book = new Book();
book.setBookid(bookid);
book.setBookname(bookName);
// 第一次修改书名
bookDao.update(book);
// 第二次修改金额
bookDao.updateMoney(bookid,money);
}
注意: @Transactional注解在类级别标记,会影响到类中的每一个方法。同时,类级别标记的@Transactional注解中设置的事务属性也会延续影响到方法执行时的事务属性。除非在方法上又设置了@Transactional注解。
例:在BookService类上设置了@Transactional注解 readOnly 属性为 true,那么在这个类中有查询的方法都不需要设置@Transactional注解了,而增删改方法需要设置@Transactional注解 readOnly 属性为 false。
@Transactional(readOnly = true) @Service public class BookService implements IBookService { @Autowired private IBookDao bookDao; @Transactional(readOnly = false) @Override public void updateTwice(Integer bookid, String bookName, Float money) { Book book = new Book(); book.setBookid(bookid); book.setBookname(bookName); // 第一次修改书名 bookDao.update(book); // 第二次修改金额 bookDao.updateMoney(bookid,money); } @Override public String getName(Integer bookid) { return bookDao.get(bookid); } }
最终结果: 在配置了事务之后,如果修改数据的途中发生了错误,则会出现事务的回滚,即数据修改失败。
概述:对于一个查询操作来说,如果我们把它设置成只读,就能够明确告诉数据库,这个操作不涉及写操作。这样数据库就能够针对查询操作来进行优化。
设置方式:在业务层所需要的方法上设置此属性。
提醒:从这里开始我们都是在方法上设置@Transactional注解,不在类上声明。
@Autowired
private IBookDao bookDao;
// readOnly = true 把当前事务设置为只读。
@Transactional(readOnly = true)
@Override
public String getName(Integer bookid) {
return bookDao.get(bookid);
}
注意:只读属性只适用于查询操作,如果是增删改操作,会报异常。
Caused by: java.sql.SQLException: Connection is read-only. Queries leading to data modification are not allowed
概述:事务在执行的过程中,会因为某些问题导致程序卡住,从而长时间占用数据库资源。那么此时出现问题的程序应该被回滚,撤销它已做的操作,事务结束,把资源让出来,让其他正常程序可以执行。
也就是说:超时回滚,释放资源。
设置方式:
在业务逻辑层中的方法上设置@Transactional注解的 timeout属性,让其中的IBookDao实现类的update方法睡眠,这里设置的休眠时间必须要比 timeout属性设置的时间要长。
@Autowired
private IBookDao bookDao;
@Transactional(timeout = 3)
@Override
public void updateTwice(Integer bookid, String bookName, Float money) {
Book book = new Book();
book.setBookid(bookid);
book.setBookname(bookName);
// 第一次修改书名
bookDao.update(book);
// 第二次修改金额
bookDao.updateMoney(bookid,money);
}
下面sleep方法必须要放在执行SQL语句之前,要不然就不会起作用
@Override
public Integer update(Book book) {
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
String sql = "update t_book set bookname = ? where bookid = ?";
return template.update(sql,book.getBookname(),book.getBookid());
}
测试时出现的异常信息:
org.springframework.transaction.TransactionTimedOutException: Transaction timed out: deadline was Thu Aug 11 16:45:38 CST 2022
默认情况下对于运行时异常是回滚的,编译时异常不回滚。
@Override
public Integer update(Book book) throws FileNotFoundException{
String sql = "update t_book set bookname = ? where bookid = ?";
int update = template.update(sql, book.getBookname(), book.getBookid());
// 抛出编译时异常测试是否回滚 === 不回滚
new FileInputStream("abcd");
// 抛出运行时异常测试是否回滚 == 回滚
// System.out.println(10/0);
return update;
}
设置形式:在@Transactional设置rollbackFor 属性
在出现编译时异常的情况下设置回滚属性。
它这个原本是不回滚的,设置之后是回滚的。
@Autowired
private IBookDao bookDao;
@Transactional(rollbackFor = Exception.class)
@Override
public Integer update(Book book) throws FileNotFoundException{
int update = bookDao.update(book);
new FileInputStream("abcd");
return update;
}
设置形式:在@Transactional设置noRollbackFor 属性
在出现运行时异常的情况下设置不回滚属性。
它这个原本是回滚的,设置之后是不回滚的。
@Autowired
private IBookDao bookDao;
@Transactional(noRollbackFor = Exception.class)
@Override
public Integer update(Book book) throws FileNotFoundException{
int update = bookDao.update(book);
System.out.println(10/0);
return update;
}
①:范围不同
不管是哪个设置范围大,都是在大范围内在排除小范围的设定。
例如:rollbackFor = Exception.class ; noRollbackFor = FileNotFoundException.class ;
意思是除了 FileNotFoundException 之外,其他所有 Exception 范围的异常都回滚;但是碰到 FileNotFoundException 不回滚。此时小范围的优先级大于大范围的优先级。
②:范围一致
回滚和不回滚的异常设置了相同范围。
例如:rollbackFor = Exception.class ; noRollbackFor = Exception.class ;
那么此时Spring会采纳 rollbackFor 设置的属性,遇到此范围内的异常会回滚。
准备工作:
业务层的方法:
@Autowired private IBookDao bookDao; @Transactional(readOnly = true,isolation = Isolation.READ_UNCOMMITTED) @Override public String getName(Integer bookid) { String name = bookDao.get(bookid); return name; // 这里打一个断点 } @Transactional(readOnly = false,isolation = Isolation.READ_UNCOMMITTED) @Override public Integer updateName(Book book) throws FileNotFoundException{ int update = bookDao.updateName(book); System.out.println(10/0); return update; // 这里打一个断点 }
测试方法: 这里需要启动两个debug界面。运行之后就可以看到 getName()方法取的书名。
@Test public void test10() { ApplicationContext context = new ClassPathXmlApplicationContext("spring-conf.xml"); IBookService bookService = context.getBean("bookService", IBookService.class); Book book = new Book(); book.setBookid(1); book.setBookname("Java9997878"); bookService.updateName(book); } @Test public void test11() { ApplicationContext context = new ClassPathXmlApplicationContext("spring-conf.xml"); IBookService bookService = context.getBean("bookService", IBookService.class); bookService.getName(1); }
测试结果:执行查询操作的事务,它读取了另一个尚未提交的数据。
业务层的方法:
@Autowired private IBookDao bookDao; @Transactional(readOnly = true,isolation = Isolation.READ_COMMITTED) @Override public String getName(Integer bookid) { String name = bookDao.get(bookid); return name; // 这里打一个断点 } @Transactional(readOnly = false,isolation = Isolation.READ_COMMITTED,,noRollbackFor = Exception.class) @Override public Integer updateName(Book book) throws FileNotFoundException{ int update = bookDao.updateName(book); System.out.println(10/0); return update; // 这里打一个断点 }
测试方法: 这里需要启动两个debug界面。运行之后就可以看到 getName()方法取的书名。
@Test public void test10() { ApplicationContext context = new ClassPathXmlApplicationContext("spring-conf.xml"); IBookService bookService = context.getBean("bookService", IBookService.class); Book book = new Book(); book.setBookid(1); book.setBookname("Java9997878"); bookService.updateName(book); } @Test public void test11() { ApplicationContext context = new ClassPathXmlApplicationContext("spring-conf.xml"); IBookService bookService = context.getBean("bookService", IBookService.class); bookService.getName(1); }
测试结果: 执行查询操作的事务读取的是数据库中正确的数据。
用图可能会更好理解。
名称 | 含义 |
---|---|
REQUIRED 默认值 | 当前方法必须工作在事务中; 如果当前线程上有已经开启的事务可用,那么就在这个事务中运行;如果当前线程上没有已经开启的事务,那么就自己开启新事务,在新事务中运行;所以当前方法有可能和其他方法共用事务;在共用事务的情况下:当前方法会因为其他方法回滚而受连累 |
REQUIRES_NEW 建议使用 | 当前方法必须工作在事务中 ;不管当前线程上是否有已经开启的事务,都要开启新事务; 在新事务中运行;不会和其他方法共用事务,避免被其他方法连累 |
IBookService接口
public Integer updateName(Book book);
public Integer updateMoney(Book book);
实现IBookService接口的类
@Transactional(readOnly = false,propagation = Propagation.REQUIRED)
@Override
public Integer updateName(Book book) {
int count = bookDao.updateName(book);
System.out.println(10/0);
return count;
}
@Transactional(readOnly = false,propagation = Propagation.REQUIRED)
@Override
public Integer updateMoney(Book book) {
Integer count = bookDao.updateMoney(book.getBookid(), book.getPrice());
return count;
}
编写TestService的类
@Service
public class TestService {
@Autowired
private IBookService bookService;
@Transactional
public void topTxMethod(){
Book book = new Book();
book.setBookid(4);
book.setPrice(99f);
bookService.updateMoney(book);
book.setBookname("张五");
bookService.updateName(book);
}
}
编写测试类
@Test
public void test(){
ApplicationContext context = new ClassPathXmlApplicationContext("spring-conf.xml");
TestService bookService = context.getBean("testService", TestService.class);
bookService.topTxMethod();
}
事务的执行结果
[18:44:41.809] [DEBUG] [main] [org.springframework.jdbc.datasource.DataSourceTransactionManager] [Creating new transaction with name [com.zhang.service.impl.TopService.topTxMethod]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT] [18:44:44.128] [DEBUG] [main] [org.springframework.jdbc.datasource.DataSourceTransactionManager] [Acquired Connection [com.mysql.cj.jdbc.ConnectionImpl@56aaaecd] for JDBC transaction] [18:44:44.132] [DEBUG] [main] [org.springframework.jdbc.datasource.DataSourceTransactionManager] [Switching JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@56aaaecd] to manual commit] [18:44:44.155] [DEBUG] [main] [org.springframework.jdbc.datasource.DataSourceTransactionManager] [Suspending current transaction, creating new transaction with name [com.zhang.service.impl.BookService.updateMoney]] [18:44:44.177] [DEBUG] [main] [org.springframework.jdbc.datasource.DataSourceTransactionManager] [Acquired Connection [com.mysql.cj.jdbc.ConnectionImpl@14dda234] for JDBC transaction] [18:44:44.177] [DEBUG] [main] [org.springframework.jdbc.datasource.DataSourceTransactionManager] [Switching JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@14dda234] to manual commit] [18:44:44.180] [DEBUG] [main] [org.springframework.jdbc.core.JdbcTemplate] [Executing prepared SQL update] [18:44:44.182] [DEBUG] [main] [org.springframework.jdbc.core.JdbcTemplate] [Executing prepared SQL statement [update t_book set price = ? where bookid = ?]] [18:44:44.235] [DEBUG] [main] [org.springframework.jdbc.datasource.DataSourceTransactionManager] [Initiating transaction commit] [18:44:44.235] [DEBUG] [main] [org.springframework.jdbc.datasource.DataSourceTransactionManager] [Committing JDBC transaction on Connection [com.mysql.cj.jdbc.ConnectionImpl@14dda234]] [18:44:44.243] [DEBUG] [main] [org.springframework.jdbc.datasource.DataSourceTransactionManager] [Releasing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@14dda234] after transaction] [18:44:44.245] [DEBUG] [main] [org.springframework.jdbc.datasource.DataSourceTransactionManager] [Resuming suspended transaction after completion of inner transaction] [18:44:44.247] [DEBUG] [main] [org.springframework.jdbc.datasource.DataSourceTransactionManager] [Suspending current transaction, creating new transaction with name [com.zhang.service.impl.BookService.updateName]] [18:44:44.267] [DEBUG] [main] [org.springframework.jdbc.datasource.DataSourceTransactionManager] [Acquired Connection [com.mysql.cj.jdbc.ConnectionImpl@a4ca3f6] for JDBC transaction] [18:44:44.267] [DEBUG] [main] [org.springframework.jdbc.datasource.DataSourceTransactionManager] [Switching JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@a4ca3f6] to manual commit] [18:44:44.271] [DEBUG] [main] [org.springframework.jdbc.core.JdbcTemplate] [Executing prepared SQL update] [18:44:44.271] [DEBUG] [main] [org.springframework.jdbc.core.JdbcTemplate] [Executing prepared SQL statement [update t_book set bookname = ? where bookid = ?]] [18:44:44.277] [DEBUG] [main] [org.springframework.jdbc.datasource.DataSourceTransactionManager] [Initiating transaction rollback] [18:44:44.278] [DEBUG] [main] [org.springframework.jdbc.datasource.DataSourceTransactionManager] [Rolling back JDBC transaction on Connection [com.mysql.cj.jdbc.ConnectionImpl@a4ca3f6]] [18:44:44.282] [DEBUG] [main] [org.springframework.jdbc.datasource.DataSourceTransactionManager] [Releasing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@a4ca3f6] after transaction] [18:44:44.283] [DEBUG] [main] [org.springframework.jdbc.datasource.DataSourceTransactionManager] [Resuming suspended transaction after completion of inner transaction] [18:44:44.283] [DEBUG] [main] [org.springframework.jdbc.datasource.DataSourceTransactionManager] [Initiating transaction rollback] [18:44:44.284] [DEBUG] [main] [org.springframework.jdbc.datasource.DataSourceTransactionManager] [Rolling back JDBC transaction on Connection [com.mysql.cj.jdbc.ConnectionImpl@56aaaecd]] [18:44:44.285] [DEBUG] [main] [org.springframework.jdbc.datasource.DataSourceTransactionManager] [Releasing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@56aaaecd] after transaction]
总结:
调用BookService层的两个方法都没有修改值,因此总事务回滚了。
注意:不能用try-catch处理异常,如果使用了try-catch,不管有没有捕获到异常,总事务总是提交。
实现IBookService接口的类
@Transactional(readOnly = false,propagation = Propagation.REQUIRES_NEW)
@Override
public Integer updateName(Book book) {
int count = bookDao.updateName(book);
System.out.println(10/0);
return count;
}
@Transactional(readOnly = false,propagation = Propagation.REQUIRES_NEW)
@Override
public Integer updateMoney(Book book) {
Integer count = bookDao.updateMoney(book.getBookid(), book.getPrice());
return count;
}
编写TestService的类
@Service
public class TestService {
@Autowired
private IBookService bookService;
@Transactional
public void topTxMethod(){
Book book = new Book();
book.setBookid(4);
book.setPrice(99f);
bookService.updateMoney(book);
book.setBookname("张五");
bookService.updateName(book);
}
}
编写测试类
@Test
public void test(){
ApplicationContext context = new ClassPathXmlApplicationContext("spring-conf.xml");
TestService bookService = context.getBean("testService", TestService.class);
bookService.topTxMethod();
}
事务的执行结果
[20:02:15.051] [DEBUG] [main] [org.springframework.jdbc.datasource.DataSourceTransactionManager] [Creating new transaction with name [com.zhang.service.impl.TopService.topTxMethod]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT] [20:02:17.687] [DEBUG] [main] [org.springframework.jdbc.datasource.DataSourceTransactionManager] [Acquired Connection [com.mysql.cj.jdbc.ConnectionImpl@56aaaecd] for JDBC transaction] [20:02:17.692] [DEBUG] [main] [org.springframework.jdbc.datasource.DataSourceTransactionManager] [Switching JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@56aaaecd] to manual commit] [20:02:17.717] [DEBUG] [main] [org.springframework.jdbc.datasource.DataSourceTransactionManager] [Suspending current transaction, creating new transaction with name [com.zhang.service.impl.BookService.updateMoney]] [20:02:17.740] [DEBUG] [main] [org.springframework.jdbc.datasource.DataSourceTransactionManager] [Acquired Connection [com.mysql.cj.jdbc.ConnectionImpl@14dda234] for JDBC transaction] [20:02:17.740] [DEBUG] [main] [org.springframework.jdbc.datasource.DataSourceTransactionManager] [Switching JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@14dda234] to manual commit] [20:02:17.742] [DEBUG] [main] [org.springframework.jdbc.core.JdbcTemplate] [Executing prepared SQL update] [20:02:17.743] [DEBUG] [main] [org.springframework.jdbc.core.JdbcTemplate] [Executing prepared SQL statement [update t_book set price = ? where bookid = ?]] [20:02:17.811] [DEBUG] [main] [org.springframework.jdbc.datasource.DataSourceTransactionManager] [Initiating transaction commit] [20:02:17.811] [DEBUG] [main] [org.springframework.jdbc.datasource.DataSourceTransactionManager] [Committing JDBC transaction on Connection [com.mysql.cj.jdbc.ConnectionImpl@14dda234]] [20:02:17.813] [DEBUG] [main] [org.springframework.jdbc.datasource.DataSourceTransactionManager] [Releasing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@14dda234] after transaction] [20:02:17.815] [DEBUG] [main] [org.springframework.jdbc.datasource.DataSourceTransactionManager] [Resuming suspended transaction after completion of inner transaction] [20:02:17.816] [DEBUG] [main] [org.springframework.jdbc.datasource.DataSourceTransactionManager] [Suspending current transaction, creating new transaction with name [com.zhang.service.impl.BookService.updateName]] [20:02:17.839] [DEBUG] [main] [org.springframework.jdbc.datasource.DataSourceTransactionManager] [Acquired Connection [com.mysql.cj.jdbc.ConnectionImpl@a4ca3f6] for JDBC transaction] [20:02:17.839] [DEBUG] [main] [org.springframework.jdbc.datasource.DataSourceTransactionManager] [Switching JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@a4ca3f6] to manual commit] [20:02:17.844] [DEBUG] [main] [org.springframework.jdbc.core.JdbcTemplate] [Executing prepared SQL update] [20:02:17.844] [DEBUG] [main] [org.springframework.jdbc.core.JdbcTemplate] [Executing prepared SQL statement [update t_book set bookname = ? where bookid = ?]] [20:02:17.849] [DEBUG] [main] [org.springframework.jdbc.datasource.DataSourceTransactionManager] [Initiating transaction rollback] [20:02:17.849] [DEBUG] [main] [org.springframework.jdbc.datasource.DataSourceTransactionManager] [Rolling back JDBC transaction on Connection [com.mysql.cj.jdbc.ConnectionImpl@a4ca3f6]] [20:02:17.855] [DEBUG] [main] [org.springframework.jdbc.datasource.DataSourceTransactionManager] [Releasing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@a4ca3f6] after transaction] [20:02:17.856] [DEBUG] [main] [org.springframework.jdbc.datasource.DataSourceTransactionManager] [Resuming suspended transaction after completion of inner transaction] [20:02:17.857] [DEBUG] [main] [org.springframework.jdbc.datasource.DataSourceTransactionManager] [Initiating transaction rollback] [20:02:17.857] [DEBUG] [main] [org.springframework.jdbc.datasource.DataSourceTransactionManager] [Rolling back JDBC transaction on Connection [com.mysql.cj.jdbc.ConnectionImpl@56aaaecd]] [20:02:17.858] [DEBUG] [main] [org.springframework.jdbc.datasource.DataSourceTransactionManager] [Releasing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@56aaaecd] after transaction]
总结:
如果在事务提交前未发生异常,则修改成功;如果在事务提交之前有异常发生,事务回滚。注意:这里是执行一条SQL语句,事务提交一次。
注意:不能用try-catch处理异常,如果使用了try-catch,不管有没有捕获到异常,总事务总是提交。
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.3.1</version>
</dependency>
<aop:config> <!-- 配置切入点表达式,将事务功能定位到具体方法上 --> <aop:pointcut id="txPoint" expression="execution(* com.zhang..*Service.*(..))"/> <!-- 将事务通知和切入点表达式关联起来 --> <aop:advisor advice-ref="txAdvice" pointcut-ref="txPoint"/> </aop:config> <!-- tx:advice标签:配置事务通知 --> <!-- id属性:给事务通知标签设置唯一标识,便于引用 --> <!-- transaction-manager属性:关联事务管理器 --> <tx:advice id="txAdvice" transaction-manager="transactionManager"> <tx:attributes> <!-- tx:method标签:配置具体的事务方法 --> <!-- name属性:指定方法名,可以使用星号代表多个字符 --> <tx:method name="query*" read-only="true"/> <!-- read-only属性:设置只读属性 --> <!-- rollback-for属性:设置回滚的异常 --> <!-- no-rollback-for属性:设置不回滚的异常 --> <!-- isolation属性:设置事务的隔离级别 --> <!-- timeout属性:设置事务的超时属性 --> <!-- propagation属性:设置事务的传播行为 --> <tx:method name="save*" read-only="false" rollback-for="java.lang.Exception" propagation="REQUIRES_NEW"/> <tx:method name="update*" read-only="false" rollback-for="java.lang.Exception" propagation="REQUIRES_NEW"/> <tx:method name="delete*" read-only="false" rollback-for="java.lang.Exception" propagation="REQUIRES_NEW"/> </tx:attributes> </tx:advice>
@Override
public Integer updateName(Book book) {
int update = bookDao.updateName(book);
System.out.println(10/0);
return update;
}
结果:它没有发生回滚。说明设置事务成功!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。