Spring从0开始最全面详解
1、spring的概述
-
spring是什么?
Spring是分层的Java SE/EE应用 full-stack 轻量级开源框架,以IOC(Inverse Of Control :反转控制)和AOP(Aspect Oriented Programming :面向切面编程)为内核,提供了展现层Spring MVC 和持久层 Spring JDBC 以及业务层事务管理等众多的企业级应用技术,还能整合开源世界众多著名的第三框架和类库。
-
spring的两大核心?
IOC 和 AOP -
spring的发展历程和优势
优势:方便解耦,简化开发(利用了IOC容器)
AOP编程的支持
声明事务的支持
方便程序的测试
方便集成各种优秀框架
降低 JavaEE API 的使用难度
Java 源码是经典学习典范 -
spring体系结构
2、程序的耦合及解耦
耦合:程序间的依赖关系
包括:
类之间的依赖
方法间的依赖
解耦:
降低程序间的依赖关系
实际开发中:
应该做到:编译期不依赖,运行期才依赖
解耦的思路:
第一步:使用反射来创建对象,而避免使用new 关键字。
第二步:通过读取配置文件来获取要创建的对象的全限定类名。
3、IOC概念和spring中的IOC
- IOC(控制反转):不用new对象了,用工厂来创建对象,控制权发生了反转
- IOC作用:削减计算机程序的耦合(解除代码中的依赖关系)
- 获取spring 的ioc核心容器,并根据id获取对象
ApplicationContext的三个常用实现类
1.ClassPathXmlApplicationContext:它可以加载类路径下的配置文件,要求配置文件必须在类路径下。不在的话,加载不了。(更常用)
2.FileSystemXmlApplicationContext:它可以加载磁盘任意路径下的配置文件(但是必须要有访问权限)
3.AnnotationConfigApplicationContext:它是用于读取注解创建容器的 - 核心容器的两个接口引发的问题:
ApplicationContext:单例对象适用 一般采用此接口
它在构建核心容器时,创建对象采用的策略是采用立即加载的方式,也就是说,只要一读取完配置文件马上就创建配置文件中配置的对象。
BeanFactory:多例对象适用
它在构建核心容器时,创建对象采用的策略是采用延迟加载的方式。也就是说,什么时候根据id获取对象了,什么时候才真正的创建对象。//1.获取核心容器对象 //ApplicationContext ac = new FileSystemXmlApplicationContext("D:\\bean.xml");(绝对) ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml"); //2.根据id获取Bean对象 UserService service = (UserService) ac.getBean("userService"); UserDao dao = ac.getBean("userDao",UserDao.class); System.out.println(service); System.out.println(dao);
spring中基于XML的IOC环境搭建,基于spring在xml中的配置
-
创建bean对象的三种方式:
<?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 https://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- 把对象的创建交给spring来管理 --> <bean id="userService" class="com.itheima.service.Imp.UserServiceImpl"></bean> <bean id="userDao" class="com.itheima.dao.Imp.UserDaoImpl"></bean> <!-- 把对象的创建交给spring管理 --> <!-- spring对bean的管理细节 1.创建bean的三种方式 2.bean对象的作用范围 3.bean对象的生命周期 --> <!-- 创建bean的三种方式 --> <!-- 第一种方式:使用默认的无参构造函数创建 在spring的配置文件中使用bean标签,配以id和class属性之后,且没有其他属性和标签时。 采用的就是默认无参构造函数创建bean对象,此时如果类中没有没有默认的无参构造,则对象无法创建。--> <bean id="userService" class="com.lucky.service.Impl.UserServiceImpl"></bean> <!-- 第二种方式:使用普通工厂中的方法创建对象(使用某个类中的方法创建对象,并存入spring容器中) --> <bean id="instanceFactory" class="com.lucky.factory.InstanceFactory"></bean> 得到InstanceFactory对象 <bean id="userService" factory-bean="instanceFactory" factory-method="getUserService"></bean> 调用里面的方法 <!-- 第三种方式:使用工厂中的静态方法创建对象(使用某个类中的静态方法创建对象,并存入spring容器中)--> <!-- class创建 StaticFactory实体类,在通过静态方法创建实体类--> <bean id="userService" class="com.lucky.factory.StaticFactory" factory-method="getUserService"></bean> </beans>
-
bean对象的作用范围:
<!-- bean的作用范围: bean标签的scope属性: 作用:用于指定bean的作用范围 取值:常用的就是单例的和多例的 singleton:单例的(默认值)(产生的对象为一个) prototype:多例的 request:作用域于web应用的请求范围 session:作用于web应用的会话范围 global-session:作用与集群环境的会话范围(全局会话范围),当不是集群环境时,他就是session --> <bean id="userService" class="com.lucky.service.Impl.UserServiceImpl" scope="prototype"></bean>
-
bean对象的生命周期:
<!-- bean对象的生命周期:
单例对象:
出生:当容器创建时产生
活着:只要容器还在,对象一直活着
死亡:容器销毁,对象灭亡 (销毁时,还没来得及调用销毁对象就已经消失)
总结:单例对象的声明周期和容器相同
多例对象:
出生:当我们使用对象时spring框架为我们创建
活着:对象只要是在使用过程中就一直活着
死亡:当对象时间不用,且没有别的对象引用时,由java的垃圾回收机制回收
-->
<bean id="userService" class="com.lucky.service.Impl.UserServiceImpl" scope="singleton"
init-method="init" destroy-method="destroy"></bean>
依赖注入(Dependency Injection)
- 依赖关系的 管理:
以后都交给spring来维护。
在当前类需要用到其他类的对象,由spring为我们提供,我们只需要在配置文件中说明。 - 依赖关系的维护:
就称之为依赖注入。 - 依赖注入:
能注入的数据:
1. 基本类型和String
2. 其他bean类型(在配置文件中或者注解配置过的bean)
3. 复杂类型
注入的方式:
1. 使用构造函数提供
2. 使用set方法提供
3. 使用注解提供
<!-- 构造函数依赖 :
<--1. 构造函数的注入:
使用的标签:constructor-arg (bean标签内部)
标签中的属性:
type:用于指定要注入的数据的数据类型,该数据类型也是构造函数中某个或某些参数的类型。
type遇到的问题:不知道是否还有其他相同的类型。
index:用于指定要注入的数据给构造函数中指定索引位置的参数赋值。索引的位置是从0开始。
index遇到的问题:不知道具体位置的类型是什么。
name:用于指定给构造函数中指定名称的参数赋值 (最常用)。
========================================================================
value:用于提供基本类型和String类型的数据。
ref:用于指定其他的bean类型的数据。它指的就是在spring的ioc核心容器中出现过的bean对象。
优势:
在获取bean对象,注入数据是必须的操作,否则对象无法创建成功。
弊端:
改变了bean对象的实例化方式,使我们在创建对象时,如果用不到这些数据,也必须提供。
-->
<bean id="accountService" class="com.lucky.service.Impl.AccountServiceImpl">
<!-- <constructor-arg type="java.lang.String" value="显示"></constructor-arg>
<constructor-arg index="1" value="18"></constructor-arg>-->
<constructor-arg name="name" value="范德萨"></constructor-arg>
<constructor-arg name="age" value="16"></constructor-arg>
<constructor-arg name="birthday" ref="date"></constructor-arg>
</bean>
<bean id="date" class="java.util.Date"></bean>
<!-- set方法注入-->
<!--使用标签:property (在bean标签内部)
标签属性:
name:用于指定给构造函数中指定名称的参数赋值 (最常用)。
value:用于提供基本类型和String类型的数据。
ref:用于指定其他的bean类型的数据。它指的就是在spring的ioc核心容器中出现过的bean对象。
优势:
创建对象时没有明确的限制,可以直接使用默认构造函数。
弊端:
如果有某个成员必须有值,则获取对象是有可能set方法没有执行。
-->
<bean id="accountService" class="com.lucky.service.Impl.AccountServiceImpl2">
<property name="name" value="大数"></property>
<property name="age" value="48"></property>
<property name="birthday" ref="date"></property>
</bean>
spring基于注解的IOC以及ioc的案例
-
spring中ioc的常用注解
/* 用于创建对象的: 他们的作用域就和在XML配置文件中编写一个 <bean></bean> 标签实现的功能是一样的 @Component: 作用:用于把当前类对象存入spring容器中 属性: value:用于指定bean的id。当我们不写时,它的默认值就是当前类名,且首字母改小写 @Controller:一般用在表现层 @Service:一般用在业务层 @Repository:一般用在持久层 以上三个注解它们的作用和属性与Component是一模一样的。 它们三个是spring框架为我们提供明确的三层使用的注解,使我们的三层对象更加清晰。 用于注入数据的: 他们的作用域就和在xml配置文件中的bean标签中写一个 <property></property> 标签的作用是一样的 @Autowired: 作用:自动按照类型注入。只要容器中有唯一的一个bean对象类型和要注入的变量类型匹配,就可以注入成功 如果ioc容器中没有任何bean的类型和要注入的变量类型匹配,则报错。 如果ioc容器中有多个类型匹配时,则会按照创建UserDao的变量名称和注入时的名称匹配 出现位置:可以是变量上,也可以是方法上 细节:在使用注解注入时,set方法就不是必须的了 @Qualifier: 作用:在按照指定类中注入的基础之上在按照名称注入。他在给成员注入时不能单独使用(必须依赖与Autowired)。但是在给方法参数注入可以单独使用 属性:value:用于指定注入bean的id。 @Resource: 作用:直接按照bean的id注入。它可以独立使用 属性: name:用于指定bean的id 以上三个注入都只能注入其他bean类型的数据,而基本类型和String类型无法使用上述注解实现。 另外,集合类型的注入只能通过XML来实现 @Value: 作用:用于注入基本数据类型和String类型的数据 属性: value:用于指定数据的值。它可以使用spring中SpEL(也就是spring的el表达式) SpEL的写法:${表达式} */ @Component(value = "userService") public class UserServiceImpl implements UserService { @Autowired private UserDao userDao2 = null; } @Repository public class UserDaoImpl implements UserDao { } @Repository(value = "userDao2") public class UserDaoImpl2 implements UserDao { } //使用Qualifier时,两个必须一起使用 //@Autowired //@Qualifier("userDaoImpl") @Resource(name = "userDaoImpl") private UserDao userDao2 = null;
/* 用于改变作用范围的: * 他们的作用就和在bean标签中使用scope属性实现的功能是一样的 * @Scope: * 作用:用于指定bean的作用范围 * 属性:value:指定范围的取值。常用取值:singleton prototype * 和生命周期相关 * 他们的作用就和在bean标签中使用 init-method 和 destroy-method 的作用是一样的 * @PreDestroy: * 作用:用于指定销毁方法 * @PostConstruct: * 作用:用于指定初始化方法 * */ @Component(value = "userService") @Scope("prototype") public class UserServiceImpl implements UserService { @Override @PostConstruct public void init() { System.out.println("我初始化了..."); } @Override @PreDestroy public void destroy() { System.out.println("我销毁了..."); } } @Value(value = "${driver}") private String driver; @Value("username") private String username;
-
改造基于注解的ioc案例,使用纯注解的方式实现
/** * 该类是一个配置类,它的作用和bean.xml是一样的: * spring中的新注解: * @Configuration: * 作用:指定当前类是一个配置类 细节:当配置类作为AnnotationConfigApplicationContext对象创建的参数时,该注解可以不写。 * @ComponentScan: * 作用:用于通过注解指定spring在创建容器时要扫描的包 * 属性: * value:它和basePackages的作用是一样的,都是用于指定创建容器时要扫描的包。 * 等同于这个配置:<context:component-scan base-package="com.lucky"></context:component-scan> */ @Configuration @ComponentScan("com.lucky") //要扫描的包 public class ConfigReplaceXml { }
注意:ComponentScan扫描时,只会扫描有 @Configuration 注解的类
Configuration细节:当配置类作为AnnotationConfigApplicationContext对象创建的参数时,该注解可以不写。
/* @Bean: 作用:用于把当前方法的返回值作为bean对象存入spring的ioc容器中。 属性: name:用于指定bean的id。当不写时,默认值是当前方法的名称, 细节: 当我们使用注解配置方法时,如果方法有参数,spring框架会去容器中查找有没有可用的bean对象。 查找的方式和Autowired注解的作用是一样的 */ @Bean @Scope("prototype") public QueryRunner createQueryRunner(DataSource dataSource){ return new QueryRunner(dataSource); } @Bean public DataSource createDataSource(){ try{ ComboPooledDataSource dataSource = new ComboPooledDataSource(); dataSource.setDriverClass("com.mysql.cj.jdbc.Driver"); dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test"); dataSource.setUser("root"); dataSource.setPassword("root"); return dataSource; }catch (Exception e){ throw new RuntimeException(e); } } @Test public void test2(){ ApplicationContext ac = new AnnotationConfigApplicationContext(ConfigReplaceXml.class); //创建方式改变,基于注解创建 QQUserService service = ac.getBean("userService",QQUserService.class); QQUser one = service.findOne(2); System.out.println(one); }
@Bean注解的细节:细节: 当我们使用注解配置方法时,如果方法有参数,spring框架会去容器中查找有没有可用的bean对象,没有就会报错
(1).实现配置类调用另一配置类的方法:
/* @Import: 作用:用于导入其它的配置类 属性:value:用于指定其他配置类的字节码。 当使用Import的注解之后,有Import注解的类就是父配置类,而导入的都是子配置类 */ //第一种 @ComponentScan({"com.lucky"}) public class ConfigReplaceXml { } @Configuration public class JDBCConfig { } ApplicationContext ac = new AnnotationConfigApplicationContext(ConfigReplaceXml.class); //第二种 @ComponentScan({"com.lucky"}) public class ConfigReplaceXml { } public class JDBCConfig { } ApplicationContext ac = new AnnotationConfigApplicationContext(ConfigReplaceXml.class, JDBCConfig.class);//两个类都加上,为兄弟关系 //第三种(具有父子关系) @ComponentScan({"com.lucky"}) @Import(value = {JDBCConfig.class}) public class ConfigReplaceXml { } ApplicationContext ac = new AnnotationConfigApplicationContext(ConfigReplaceXml.class);
(2)配置连接数据库的资源文件
/* PropertySource: * 作用:用于指定properties文件的位置。 * 属性:value:指定文件的的名称和路径。 关键字:classpath:表示类路径下 */ driver=com.mysql.cj.jdbc.Driver url=jdbc:mysql://localhost:3306/test user=root password=root @ComponentScan({"com.lucky"}) @Import(value = {JDBCConfig.class}) @PropertySource("classpath:jdbcConfig.properties") public class ConfigReplaceXml { } @Value(value = "${driver}") private String driver; @Value(value = "${url}") private String url; @Value("${user}") private String username; @Value("${password}") private String password;
(3)在注解中springIOC容器中同一个对象有多个实现方法时
/*当方法重载时,用 @Qualifier 注解可以实现使用的是哪一个方法 */ @Bean @Scope("prototype") public QueryRunner createQueryRunner(@Qualifier(value = "ds1") DataSource dataSource){ return new QueryRunner(dataSource); } @Bean(name = "ds1") public DataSource createDataSource(){} @Bean(name = "ds2") public DataSource createDataSource2(){}
4、动态代理的两种方式
1、动态代理(基于接口实现)
/*基于接口的动态代理:
* 特点:字节码随用随创建,随用随加载
* 作用:不修改源码的基础上对方法增强
* 分类:
* 基于接口的动态代理
* 基于子类的动态代理
* 基于接口的动态代理:
* 涉及的类:Proxy
* 提供者:JDK官方
* 如何创建代理对象:
* 使用Proxy类中的newProxyInstance方法
* 创建代理对象的要求:
* 被代理类最少实现一个接口,如果没有则不能使用
* newProxyInstance方法的参数:
* 1.类加载器:它是用于加载代理对象字节码的。和被代理对象使用相同的类加载器。真实对象.getClass().getClassLoader()
* 2.接口数组:字节码数组,它是用于让代理对象和被代理对象有相同的方法。真实对象.getClass().getInterfaces()
* 3.处理器:new InvocationHandler() */
Huawei huawei = new HuaWei();
Producer proxyProducer = (Producer) Proxy.newProxyInstance(huaWei.getClass().getClassLoader(), huiWei.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//增强代码
Object returnValue = null;
//1.获取方法执行的参数
double money = (double) args[0];
//2.判断当前方法是不是销售
if("saleProduce".equals(method.getName())){
//若是销售的方法名,则要进行处理,代理商要拿到 20%,生产商只能拿到 80%
returnValue = method.invoke(huaWei, money * 0.8);
}
return returnValue;
}
});
proxyProducer.saleProduce(10000);
- 动态代理另一种实现方式(基于子类)
/*基于子类的动态代理:
* 特点:字节码随用随创建,随用随加载
* 作用:不修改源码的基础上对方法增强
* 分类:
* 基于接口的动态代理
* 基于子类的动态代理
* 基于子类的动态代理:
* 涉及的类:Enhancer
* 提供者:第三方cglib库
* 如何创建代理对象:
* 使用Enhancer类中的create方法
* 创建代理对象的要求:
* 被代理类不能时最终类
* create方法的参数:
* 1.Class:字节码,用于指定被代理对象的字节码,
* 2.Callback:用于指定提供增强的代码,
* 它是让我们写如何代理。我们一般都是写一个该接口的实现类,通常情况下都是匿名内部类,但不是必须的。
* 此接口的实现类都是谁用谁写
* 我们一般写的都是该接口的子接口的实现类:MethodInterceptor
*/
Huawei huawei = new HuaWei();
Producer cglibProducer = (Producer) Enhancer.create(huaWei.getClass(), new MethodInterceptor() {
/**
* 执行被代理对象的任何方法都会经过该方法
*/
@Override
public Object intercept(Object proxy, Method method, Object[] ags, MethodProxy methodProxy) throws Throwable {
//增强代码
Object returnValue = null;
//1.获取方法执行的参数
double money = (double) ags[0];
//2.判断当前方法是不是销售
if ("saleProduce".equals(method.getName())) {
//若是销售的方法名,则要进行处理,代理商要拿到 20%,生产商只能拿到 80%
returnValue = method.invoke(huaWei, money * 0.8);
}
return returnValue;
}
});
cglibProducer.saleProduce(12000);
5、AOP
AOP:Aspect Oriented Programing(面向切面编程)。
spring中基于XML的AOP配置
spring中基于XML的 AOP 配置步骤 1.把通知 bean 也交给 spring 来管理 2.使用 aop:config 标签表明开始aop配置 3.使用 aop:aspect 标签表明配置切面 id属性 : 是给切面提供一个唯一标识 ref属性 : 是指定通知类的 id 4.在 aop:aspect 标签内部使用对应标签来配置通知的类型 我们现在实例是让 printLog 方法在切入点方法执行之前执行,所以是前置通知 aop:before : 表示配置前置通知 method属性:用于指定Logger类中哪个方法是前置通知 pointcut属性:用于指定切入点表达式,该表达式的含义指的是对业务层中哪些方法的增强
切入点表达式的写法:
关键字:execution(表达式)
表达式:
访问修饰符 返回值 包名.包名.包名...类名.方法名.(参数列表)
举例:
public void com.lucky.service.impl.AccountServiceImpl.save()
修饰符可以不写:
void com.lucky.service.impl.AccountServiceImpl.save()
返回值可以使用通配符,表示任意返回值:
* com.lucky.service.impl.AccountServiceImpl.save()
包名可以使用通配符,表示任意包。但是有几级包,就需要写几个 *.
void *.*.*.*.AccountServiceImpl.save()
包名可以使用 .. 表示当前包及其子包:
void *..AccountServiceImpl.save()
类名和方法名都可以使用 * 来通配
* *..*.*()
参数列表:
可以直接写数据类型:
基本类型直接写名称 int
引用类型写包名.类名的方式 java.lang.String
可以使用通配符表示任意的类型,但是必须有参数
可以使用 .. 表示有无参数均可,有参数可以是任意类型
全通配符写法:
* *..*.*(..)
实际开发中切入点表达式的通常写法 :
切到业务层实现类下的所有方法
* com.lucky.service.impl.*.*(..)
<!-- 所有方法都可 -->
<aop:before method="printLog" pointcut="execution(* *..*.*(..))"></aop:before>
<!-- 省略修饰符 -->
<aop:before method="printLog" pointcut="execution(void com.lucky.service.impl.AccountServiceImpl.save())"></aop:before>
<!-- 包名改 *. -->
<aop:before method="printLog" pointcut="execution(void *.*.*.*.AccountServiceImpl.save())"></aop:before>
<!-- 任意包下或子包下有AccountServiceImpl类的 -->
<aop:before method="printLog" pointcut="execution(void *..AccountServiceImpl.save())"></aop:before>
<!-- 任意包下或子包下的任意类、任意方法,只要是无参就执行 -->
<aop:before method="printLog" pointcut="execution(void *..*.*())"></aop:before>
<!-- 参数通配符(可..) -->
<aop:before method="printLog" pointcut="execution(void *..*.*(..))"></aop:before>
<!-- 实际开发需要写的 -->
<aop:before method="printLog" pointcut="execution(* com.lucky.service.impl.*.*(..))"></aop:before>
四种常用的通知类型
method:指定一个类中的方法 pointcut:指定切入点表达式,该表达式的含义指的是对业务层中哪些方法的增强 <!-- 配置前置通知,在切入点方法执行之前执行 --> <aop:before method="beforePrintLog" pointcut="execution(* com.lucky.service.impl.*.*(..))"></aop:before> <!-- 配置后置通知 : 在切入点方法正常之后执行。它和异常通知永远只能执行一个 --> <aop:after-returning method="afterPrintLog" pointcut="execution(* com.lucky.service.impl.*.*(..))"></aop:after-returning> <!-- 配置异常通知 : 在切入点方法执行产生异常之后执行。它和后置通知永远只能执行一个 --> <aop:after-throwing method="afterExceptionPrintLog" pointcut="execution(* com.lucky.service.impl.*.*(..))"></aop:after-throwing> <!-- 配置最终通知 : 无论切入点方法是否正常执行它都会在最后执行--> <aop:after method="finallyPrintLog" pointcut="execution(* com.lucky.service.impl.*.*(..))"></aop:after>
配置通用表达式
<!-- 配置通用切入点表达式 id属性用于指定表达式的唯一标识。expression属性用于指定表达式内容 此标签写在 aop:aspect 标签内部,只能当切面使用。 他还可以写在 aop:aspect 外面。此时就变成了所有切面可用 --> <!-- 要配置在 aop:aspect 的外面,必须是在 aop:aspect 的前面 --> <aop:pointcut id="pt1" expression="execution(* com.lucky.service.impl.*.*(..))"/>
环绕通知
//invoke方法执行的就是环绕通知 /** * 环绕通知 * 问题:当我们配置了环绕 之后,切入点方法没有执行,而通知方法执行了 * 分析:通过对比动态代理中的环绕通知代码,发现动态代理的环绕通知有明确的切入点方法调用,而我们的代码中没有。 * 解决:Spring框架为我们提供了一个接口:ProceedingJoinPoint。该接口有一个方法 proceed() ,此方法就相当于明确调用切入点方法。 * 该接口可以作为环绕通知的方法参数,在程序执行时,spring框架会为我们提供该接口的实现类供我们使用 */ public Object aroundPrintLog(MethodInvocationProceedingJoinPoint pjp){ System.out.println("环绕通知:打印aroundPrintLog..."); return null; }
基于注解的AOP
使用注解时:
XML中要配置:<aop:aspectj-autoproxy proxy-target-class=“true”></aop:aspectj-autoproxy>
@Aspect放在类上:表示当前类是一个切面类
6、事务控制
1、基于XML配置
<!-- spring中基于XML的声明式事务控制配置步骤 1.配置事务管理器 2.配置事务的通知 此时我们需要导入事务的约束 tx 名称空间和约束,同时也需要aop的 使用 tx:advice 标签配置事务通知 属性: id : 给事务通知起一个唯一标识 transaction-manager : 给事务通知提供一个事务管理器引用 3.配置AOP中的切入点表达式 4.建立事务通知和切入点表达式的对应关系 5.配置事务的属性: 是在事务的通知 tx:advice 标签内部 --> <!-- 1.配置事务管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 2.配置事务的通知 --> <tx:advice id="txAdvice" transaction-manager="transactionManager"> <!-- 5.配置事务的属性 isolation :用于指定事务的隔离级别。默认方法时DEFAULT,表示使用数据库的默认隔离级别。 propagation :用于指定事务的传播行为。默认值是REQUIRED,表示一定会有事务,增删改的选择。查询方法可以选择SUPPORTS。 read-only :用于指定事务的超时时间,默认值是 -1,表示永不超时。如果指定了数值,以秒为单位。 rollback-for :用于指定一个异常,当产生该异常时,事务不回滚,产生其他异常时,事务不回滚。没有默认值,表示任何异常都回滚。 no-rollback-for:用于指定一个异常,当产生异常时,事务不回滚,产生其他异常时事务回滚。没有默认值,表示任何异常都回滚。 --> <tx:attributes> <!-- 写的两个属性,优先级是 2>1 因为下面的只是一部分(查询的)适用,且只能为只读方式;第一个是所有的都适用 --> <tx:method name="*" propagation="REQUIRED" read-only="false"/> <tx:method name="find*" read-only="true" propagation="SUPPORTS"></tx:method> </tx:attributes> </tx:advice> <!-- 配置AOP --> <aop:config> <!-- 3.配置切入点表达式 --> <aop:pointcut id="pt1" expression="execution(* com.smile.service.impl.*.*(..))"/> <!-- 4.建立切入点表达式和事务通知的关系 --> <aop:advisor advice-ref="txAdvice" pointcut-ref="pt1"></aop:advisor> </aop:config>
2、基于注解配置
<!--配置spring创建容器时要扫描的包-->
<context:component-scan base-package="com.smile"></context:component-scan>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://localhost:3306/test"></property>
<property name="username" value="root"></property>
<property name="password" value="root"></property>
</bean>
<!-- spring中基于 注解 的声明式事务控制配置步骤
1.配置事务管理器
2.开启spring对注解事务的支持
3.在需要事务支持的地方使用 @Transactional
-->
<!-- 1.配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 2.开启spring对注解事务的支持 -->
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
@Service("accountService")
//@Transactional //需要事务支持
@Transactional(propagation = Propagation.SUPPORTS, readOnly = true) //对里面的全部方法配置针对查询的只读形式
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
@Override
public Account findAccountById(int id) {
return accountDao.findAccountById(id);
}
@Override
@Transactional(propagation = Propagation.REQUIRED, readOnly = false) //只对该方法针对增删改查的读写形式
public void transfer(String sourceName, String targetName, double money) {}