spring:
Spring Core: 基础,可以说 Spring 其他所有的功能都需要依赖于该类库。主要提供 IoC 依 赖注⼊功能。 Spring Aspects : 该模块为与AspectJ的集成提供⽀持。
Spring AOP :提供了⾯向切⾯的编程实现。
Spring JDBC : Java数据库连接。
Spring JMS :Java消息服务。
Spring ORM : ⽤于⽀持Hibernate等ORM⼯具。
Spring Web : 为创建Web应⽤程序提供⽀持。
Spring Test : 提供了对 JUnit 和 TestNG 测试的⽀持
概述: Spring 是个java企业级应用的开源开发框架。
Spring 是一个轻量级的框架、最基础的版本只有2MB左右
Spring主要用来开发Java应用,但是在Java EE 平台上构建web应用程序是需要进行扩展的。
Spring 框架目标是简化Java企业级应用开发,减少开发难度和周期,并通过POJO为基础的编程模型促进良好的编程习惯。
特点: 轻量级: 从大小与开销,Spring都是轻量级的,完整的Spring框架可以在只有1M多的JAR文件里面进行发布
非侵入式的: 基于Spring 开发中的对象不依赖于Spring中的API
控制反转(IOC -Inversion of control): ji目的: 促进耦合
将对象的创建权交给Spring容器去创建,当一个对象依赖其他对象,会通过被动的方式传递进来,而不是通过这个对象创建或者查找依赖的对象
面向切面(Aspect Oriented Programming——AOP) : Spring支持面向切面编程,并且把业务逻辑和系统服务分开
容器 :spring 包含并管理应用中的对象的生命周期和配置。可以配置每个bean是如何被创建的—基于一个可配置的原型。所配置的bean能够单独的创建一个实例,或者每次生成一个新的实例
框架集合
Spring BeanFactory 容器 Spring 有两种类型bean,一种是普通bean,另一种是工厂bean(FactoryBean)
普通bean: 在配置文件中定义bean类型就是返回类型
工厂bean:在配置文件定义bean可以和返回类型不一样
创建类,作为工厂bean 实现FactoryBean
实现接口里的方法,在实现的方法中定义返回的bean
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class MyBean implements FactoryBean <Course > { @Override public Course getObject () throws Exception { Course course = new Course(); course.setCname("abc" ); return course; } @Override public Class<?> getObjectType() { return null ; } @Override public boolean isSingleton () { return false ; } }
Spring ApplicationContext 容器 Spring Bean 作用域
在 Spring 中定义一个 bean 时,你必须声明该 bean 的作用域的选项。例如,为了强制 Spring 在每次需要时都产生一个新的 bean 实例,你应该声明 bean 的作用域的属性为 prototype 。同理,如果你想让 Spring 在每次需要时都返回同一个bean实例,你应该声明 bean 的作用域的属性为 singleton 。
Spring 框架支持以下五个作用域,分别为 singleton、prototype、request、session 和 global session,5种作用域说明如下所示,
Spring默认是单实例对象
通过(scope)设置单实例还是多实例:
作用域
描述
singleton
在spring IoC容器仅存在一个Bean实例,Bean以单例方式存在,默认值,程序加载时就进行创建
prototype
每次从容器中调用Bean时,都返回一个新的实例,即每次调用getBean()时,相当于执行newXxxBean() ,每次调用都进行创建
request
每次HTTP请求都会创建一个新的Bean,该作用域仅适用于WebApplicationContext环境
session
同一个HTTP Session共享一个Bean,不同Session使用不同的Bean,仅适用于WebApplicationContext环境
global-session
一般用于Portlet应用环境,该作用域仅适用于WebApplicationContext环境
1 2 3 4 5 6 7 8 <bean id ="course1" class ="collectiontype.Course" scope ="singleton" > <property name ="cname" value ="Spring5框架" > </property > </bean >
Spring Bean 生命周期 当一个bean被实例化,他可能需要执行一些初始化的工作,使他转换为可用的状态
init-method
容器加载 Bean 时调用该方法,类似于 Servlet 中的 init() 方法
destroy-method
容器删除 Bean 时调用该方法,类似于 Servlet 中的 destroy() 方法。该方法只在 scope=singleton 时有效
lazy-init
懒加载,值为 true,容器在首次请求时才会创建 Bean 实例;值为 false,容器在启动时创建 Bean 实例。该方法只在 scope=singleton 时有效
Bean的生命周期可以表达为:Bean的定义——Bean的初始化——Bean的使用——Bean的销毁
Spring的执行流程:
Bean 生命周期的整个执行过程描述如下。
Spring 启动,查找并加载需要被 Spring 管理的 Bean,并实例化 Bean。
利用依赖注入完成 Bean 中所有属性值的配置注入。
如果 Bean 实现了 BeanNameAware 接口,则 Spring 调用 Bean 的 setBeanName() 方法传入当前 Bean 的 id 值。
2222222
如果 Bean 实现了 ApplicationContextAware 接口,则 Spring 调用 setApplicationContext() 方法传入当前 ApplicationContext 实例的引用。
如果 Bean 实现了 BeanPostProcessor 接口,则 Spring 调用该接口的预初始化方法 postProcessBeforeInitialzation() 对 Bean 进行加工操作,此处非常重要,Spring 的 AOP 就是利用它实现的。
如果 Bean 实现了 InitializingBean 接口,则 Spring 将调用 afterPropertiesSet() 方法。
如果在配置文件中通过 init-method 属性指定了初始化方法,则调用该初始化方法。
如果 BeanPostProcessor 和 Bean 关联,则 Spring 将调用该接口的初始化方法 postProcessAfterInitialization()。此时,Bean 已经可以被应用系统使用了。
如果在 中指定了该 Bean 的作用域为 singleton,则将该 Bean 放入 Spring IoC 的缓存池中,触发 Spring 对该 Bean 的生命周期管理;如果在 中指定了该 Bean 的作用域为 prototype,则将该 Bean 交给调用者,调用者管理该 Bean 的生命周期,Spring 不再管理该 Bean。
如果 Bean 实现了 DisposableBean 接口,则 Spring 会调用 destory() 方法销毁 Bean;如果在配置文件中通过 destory-method 属性指定了 Bean 的销毁方法,则 Spring 将调用该方法对 Bean 进行销毁。
为了定义安装和拆卸一个 bean,我们只要声明带有 init-method 和/或 destroy-method 参数的 。init-method 属性指定一个方法,在实例化 bean 时,立即调用该方法。同样,destroy-method 指定一个方法,只有从容器中移除 bean 之后,才能调用该方法。
以下进行实现:
1、通过构造器创建bean实例(无参构造),
2、为bean设置属性值和其他bean的引用
3、调用bean的初始化方法
4、获取bean对象
5、当容器关闭,调用bean的销毁方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 public class Orders { public Orders () { System.out.println("第一步 执行无参数构造创建bean实例" ); } private String oname; public void setOname (String oname) { this .oname = oname; System.out.println("第二步 调用set方法设置属性值" ); } public void initMethod () { System.out.println("第三步 执行初始化的方法" ); } public void destroyMethod () { System.out.println("第五步 执行销毁的方法" ); } }
1 2 3 4 5 6 7 8 9 10 <?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 id ="orders" class ="demon02.bean.Orders" init-method ="initMethod" destroy-method ="destroyMethod" > <property name ="oname" value ="手机" > </property > </bean > </beans >
1 2 3 4 5 6 7 8 @Test public void TestBean () { ApplicationContext context = new ClassPathXmlApplicationContext("bean4.xml" ); Orders orders= context.getBean("orders" , Orders.class); System.out.println("第四步: 获取创建的bean实例对象" ); ((ClassPathXmlApplicationContext) context).close(); }
Spring依赖注入: Spring创建对象,注入
什么是bean管理
(1)Spring创建对象
(2)Spring 进行属性注入
bean管理有两个方式
(1)基于xml配置文件方式实现
(1)在 spring 配置文件中,使用 bean 标签,标签里面添加对应属性,就可以实现对象创建
(2)在 bean 标签有很多属性,介绍常用的属性
id 属性:唯一标识
class 属性:类全路径(包类路径)
(2) 基于注解方式进行实现
基于set构造函数的依赖注入 book实体类创建:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 public class Book { private String bname; private String bauthor; private String address; public void setBname (String bname) { this .bname = bname; } public void setBauthor (String bauthor) { this .bauthor = bauthor; } public void setAddress (String address) { this .address = address; } public void testDemo () { System.out.println(bname+"::" +bauthor+"::" +address); } }
bean文件配置(特殊符号以及空值注入):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <bean id ="book" class ="Demon01.bean.Book" > <property name ="bname" value ="金" > </property > <property name ="bauthor" value ="金" > </property > <property name ="address" > <value > <![CDATA[<<南京>>]]></value > </property > </bean >
test
1 2 3 4 5 6 @Test public void testBean2 () { ApplicationContext context = new ClassPathXmlApplicationContext("bean2.xml" ); Book book = context.getBean("book" , Book.class); book.testDemo(); }
p名称空间注入: 首先添加p名称空间在配置文件中
1 2 3 4 5 <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" >
进行属性注入:
1 2 <bean id ="book1" class ="Demon01.bean.Book" p:bname ="九阳神功" p:bauthor ="无名氏" > </bean >
基于构造函数的依赖注入: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 * 使用有参数构造注入 */ public class Orders { private String oname="" ; private String address; public Orders (String oname,String address) { this .oname = oname; this .address = address; } public void ordersTest () { System.out.println(oname+"::" +address); } }
1 2 3 4 5 6 <bean id ="orders" class ="Demon01.bean.Orders" > <constructor-arg name ="oname" value ="电脑" > </constructor-arg > <constructor-arg name ="address" value ="China" > </constructor-arg > </bean >
spring注入外部bean 通过三层结构,Dao\service,在service调用dao层的方法,在service中调用dao层的方法,ref 进行属性的级联操作
1 2 3 4 5 6 7 8 9 10 11 12 13 public class UserService { private UserDao userDao; public void setUserDao (UserDao userDao) { this .userDao = userDao; } public void add () { System.out.println("service add..............." ); userDao.update(); } }
1 2 3 4 5 6 7 8 9 public class UserDaoImpl implements UserDao { @Override public void update () { System.out.println("dao update..........." ); } }
1 2 3 4 5 6 7 8 9 <bean id ="userService" class ="Demon01.service.UserService" p:userDao-ref ="UserDaoImpl" > </bean > <bean id ="UserDaoImpl" class ="Demon01.dao.UserDaoImpl" > </bean >
test
1 2 3 4 5 6 7 8 9 10 11 @Test public void testBean1 () { ApplicationContext context = new ClassPathXmlApplicationContext("bean2.xml" ); UserService userService = context.getBean("userService" , UserService.class); userService.add(); }
spring注入内部bean以及级联操作: 一对多关系:部门和员工的关系,一个部门之中有多个员工
在实体类中表示一对多关系,员工表示所属部门,使用对象的属性类型进行表示
部门类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class Dept { private String dname; public void setDname (String dname) { this .dname = dname; } @Override public String toString () { return "Dept{" + "dname='" + dname + '\'' + '}' ; } }
员工类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 public class Emp { private String ename; private String gender; private Dept dept; public Dept getDept () { return dept; } public void setDept (Dept dept) { this .dept = dept; } public void setEname (String ename) { this .ename = ename; } public void setGender (String gender) { this .gender = gender; } public void add () { System.out.println(ename+"::" +gender+"::" +dept); } }
xml配置:
1 2 3 4 5 6 7 8 9 10 11 12 <bean id ="emp" class ="Demon01.bean.Emp" > <property name ="ename" value ="lucy" > </property > <property name ="gender" value ="女" > </property > <property name ="dept" > <bean id ="dept" class ="Demon01.bean.Dept" > <property name ="dname" value ="安保部" > </property > </bean > </property > </bean >
级联操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <bean id ="emp2" class ="Demon01.bean.Emp" > <property name ="ename" value ="lucy" > </property > <property name ="gender" value ="女" > </property > <property name ="dept" ref ="dept" > </property > <property name ="dept.dname" value ="技术部" > </property > </bean > <bean id ="dept" class ="Demon01.bean.Dept" > <property name ="dname" value ="财务部" > </property > </bean >
注入集合属性:
1 2 3 4 5 6 7 8 9 10 11 private String[] courses;private List<String> list;private Map<String,String> maps;private Set<String> sets;private List<Course> courseList;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 <bean id ="stu" class ="collectiontype.Stu" > <property name ="courses" > <array > <value > java课程</value > <value > 数据库课程</value > </array > </property > <property name ="list" > <list > <value > 张三</value > <value > 小三</value > </list > </property > <property name ="maps" > <map > <entry key ="JAVA" value ="java" > </entry > <entry key ="PHP" value ="php" > </entry > </map > </property > <property name ="sets" > <set > <value > MySQL</value > <value > Redis</value > </set > </property > <property name ="courseList" > <list > <ref bean ="course1" > </ref > <ref bean ="course2" > </ref > </list > </property > </bean > <bean id ="course1" class ="collectiontype.Course" > <property name ="cname" value ="Spring5框架" > </property > </bean > <bean id ="course2" class ="collectiontype.Course" > <property name ="cname" value ="MyBatis框架" > </property > </bean >
测试:
1 2 3 4 5 6 @Test public void testCollection1 () { ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml" ); Stu stu = context.getBean("stu" , Stu.class); stu.test(); }
结果:
把集合注入的部分提取出来
(1)在spring配置文件中引入名称空间util
1 2 3 4 5 6 <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:util ="http://www.springframework.org/schema/util" xsi:schemaLocation ="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd" >
1 2 3 4 5 6 7 8 9 10 11 <util:list id ="bookList" > <value > 易筋经</value > <value > 九阴真经</value > <value > 九阳神功</value > </util:list > <bean id ="book" class ="collectiontype.Book" scope ="prototype" > <property name ="list" ref ="bookList" > </property > </bean >
1 2 3 4 5 6 7 8 9 10 11 12 public class Book { private List<String> list; public void setList (List<String> list) { this .list = list; } public void test () { System.out.println(list); } }
Spring Beans自动装配
bean之间的依赖关系之间的注入,Spring 容器支持多种装配 Bean 的方式,如基于 XML 的 Bean 装配、基于 Annotation 的 Bean 装配和自动装配等。
自动装配就是指 Spring 容器在不使用 和 标签的情况下,可以自动装配(autowire)相互协作的 Bean 之间的关联关系,将一个 Bean 注入其他 Bean 的 Property 中。
使用自动装配需要配置 元素的 autowire 属性。autowire 属性有五个值,具体说明如下表所示。
名称
说明
no
默认值,表示不使用自动装配,Bean 依赖必须通过 ref 元素定义。
byName
根据 Property 的 name 自动装配,如果一个 Bean 的 name 和另一个 Bean 中的 Property 的 name 相同,则自动装配这个 Bean 到 Property 中。
byType
根据 Property 的数据类型(Type)自动装配,如果一个 Bean 的数据类型兼容另一个 Bean 中 Property 的数据类型,则自动装配。
constructor
类似于 byType,根据构造方法参数的数据类型,进行 byType 模式的自动装配。
autodetect(3.0版本不支持)
如果 Bean 中有默认的构造方法,则用 constructor 模式,否则用 byType 模式。
1 2 3 4 5 6 7 public class Dept { @Override public String toString () { return "Dept{}" ; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class Emp { private Dept dept; public void setDept (Dept dept) { this .dept = dept; } @Override public String toString () { return "Emp{" + "dept=" + dept + '}' ; } public void test () { System.out.println(dept); } }
不使用bean的时候会进行一个级联的操作
1 2 3 4 5 6 7 8 9 10 11 <bean id ="emp" class ="demon02.autowire.Emp" autowire ="byType" > </bean > <bean id ="dept" class ="demon02.autowire.Dept" > </bean >
自动装配的优缺点
优点
缺点
不能自动装配简单数据类型,比如 int、boolean、String 等。
相比较显示装配,自动装配不受程序员控制。
使用Bean管理xml方式(外部属性文件) 创建jdbc.properties文件
1 2 3 4 prop.driverClass =com.mysql.jdbc.Driver prop.url =jdbc:mysql://localhost:3306/userDb prop.userName =root prop.password =root
bean.xml中引入外部文件
xml中需要引入名称空间:
xmlns:context=”http://www.springframework.org/schema/context"
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 <?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:util ="http://www.springframework.org/schema/util" 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/util http://www.springframework.org/schema/util/spring-util.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd" > <context:property-placeholder location ="classpath:jdbc.properties" /> <bean id ="dataSource" class ="com.alibaba.druid.pool.DruidDataSource" > <property name ="driverClassName" value ="${prop.driverClass}" > </property > <property name ="url" value ="${prop.url}" > </property > <property name ="username" value ="${prop.userName}" > </property > <property name ="password" value ="${prop.password}" > </property > </bean > </beans >
基于注解的配置:
在 Spring 中,尽管可以使用 XML 配置文件实现 Bean 的装配工作,但如果应用中 Bean 的数量较多,会导致 XML 配置文件过于臃肿,从而给维护和升级带来一定的困难。
Java 从 JDK 5.0 以后,提供了 Annotation(注解)功能,Spring 2.5 版本开始也提供了对 Annotation 技术的全面支持,我们可以使用注解来配置依赖注入。
Spring 默认不使用注解装配 Bean,因此需要在配置文件中添加 context:annotation-config/ ,启用注解。
对象创建注解: 1)@Component
可以使用此注解描述 Spring 中的 Bean,但它是一个泛化的概念,仅仅表示一个组件(Bean),并且可以作用在任何层次。使用时只需将该注解标注在相应类上即可。
2)@Repository
用于将数据访问层(DAO层)的类标识为 Spring 中的 Bean,其功能与 @Component 相同。
3)@Service
通常作用在业务层(Service 层),用于将业务层的类标识为 Spring 中的 Bean,其功能与 @Component 相同。
4)@Controller
通常作用在控制层(如 Struts2 的 Action、SpringMVC 的 Controller),用于将控制层的类标识为 Spring 中的 Bean,其功能与 @Component 相同。
引入依赖:
xml配置中需要引入
1 xmlns:context="http://www.springframework.org/schema/context"
1 2 3 4 5 6 7 8 9 10 11 12 <?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:util ="http://www.springframework.org/schema/util" 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/util http://www.springframework.org/schema/util/spring-util.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd" > <context:component-scan base-package ="demon03" > </context:component-scan > </beans >
相当于读取bean的类项
zuzu
组件扫描配置: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 <?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:util ="http://www.springframework.org/schema/util" 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/util http://www.springframework.org/schema/util/spring-util.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd" > <context:component-scan base-package ="demon03" > </context:component-scan > <context:component-scan base-package ="demon03" use-default-filters ="false" > <context:include-filter type ="annotation" expression ="org.springframework.stereotype.Component" /> </context:component-scan > <context:component-scan base-package ="demon03" > <context:exclude-filter type ="annotation" expression ="org.springframework.stereotype.Component" /> </context:component-scan > </beans >
基于注解方式进行属性注入
序号
注解 & 描述
1
@Required @Required 注解应用于 bean 属性的 setter 方法。
2
@Autowired @Autowired 注解可以应用到 bean 属性的 setter 方法,非 setter 方法,构造函数和属性。配合对应的注解处理器完成 Bean 的自动配置工作。默认按照 Bean 的类型进行装配。
3
@Qualifier 与 @Autowired 注解配合使用,会将默认的按 Bean 类型装配修改为按 Bean 的实例名称装配,Bean 的实例名称由 @Qualifier 注解的参数指定。@Autowired 和 @Qualifier 注解可以用来删除混乱。(当使用@Autowired 进行属性注入的时候,如果有多个实现类的话,使用 @Qualifier进行具体指明实现哪个)
4
JSR-250 Annotations Spring 支持 JSR-250 的基础的注解,其中包括了 @Resource,@PostConstruct 和 @PreDestroy 注解。
5
@Resource 作用与 Autowired 相同,区别在于 @Autowired 默认按照 Bean 类型装配 ,而 @Resource 默认按照 Bean 实例名称 进行装配。Spring 将 name 属性解析为 Bean 的实例名称,type 属性解析为 Bean 的实例类型。如果指定 name 属性,则按实例名称进行装配;如果指定 type 属性,则按 Bean 类型进行装配。如果都不指定,则先按 Bean 实例名称装配,如果不能匹配,则再按照 Bean 类型进行装配;如果都无法匹配,则抛出 NoSuchBeanDefinitionException 异常。
6
@Value 注入固定的值
@Qualifier与@Autowired 演示:
创建 UserDao接口
1 2 3 4 5 6 public interface UserDao { public void add () ; }
实现UserDaoImpl
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import org.springframework.stereotype.Repository;@Repository(value = "userDaoImpl1") public class UserDaoImpl implements UserDao { @Override public void add () { System.out.println("dao add ......." ); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 package demon03.service;import demon03.dao.UserDao;import demon03.dao.UserDaoImpl;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.beans.factory.annotation.Qualifier;import org.springframework.stereotype.Component;@Component(value = "userService") public class UserService { @Autowired @Qualifier(value = "userDaoImpl1") private UserDao userDao; public void add () { System.out.println("service add......." ); userDao.add(); } }
test :
1 2 3 4 5 6 7 8 9 10 11 @Test public void TestBean () { ApplicationContext context = new ClassPathXmlApplicationContext("bean5.xml" ); UserService userService= context.getBean("userService" , UserService.class); System.out.println(userService); userService.add(); }
@Resource
不是Spring官方进行提供的
1 import javax.annotation.Resource;
完全注解开发: 不需要使用配置文件,创建配置类取代xml文件
1 2 3 4 5 @Configurable @ComponentScan(basePackages = "demon03") public class SpringConfig {}
1 2 3 4 5 6 7 8 9 10 @Test public void TestSpringConfig () { ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class); UserService userService= context.getBean("userService" , UserService.class); System.out.println(userService); userService.add(); }
AOP:
参考:
彻底征服 Spring AOP 之 理论篇 - SegmentFault 思否
AOP全称是”Aspect Oriented Programming“ ,即面向切面编程。
AOP 采取横向抽取机制(动态代理),取代传统得纵向继承机制的重复性代码,主要体现在事务处理、日志管理、权限控制、异常处理等方面
AOP 的作用就是保证开发者在不修改源代码的前提下,为系统中的业务组件添加某种通用功能。AOP 就是代理模式的典型应用。
所谓”切面”,简单说就是那些与业务无关,却为业务模块所共 同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性
目前最流行的 AOP 框架有两个,分别为 Spring AOP 和 AspectJ。
应用场景:
Authentication
权限
Caching
缓存
Context passing
内容传递
Error handing
错误处理
Lazy loading
懒加载
Debugging
调试
logging trancing profiling and monitoring
记录、跟踪、优化、校准
Performance optimization
性能优化
Persistence
持久化
Resource pooling
资源池
Synchronization
同步
Transactions
事务
名称
说明
Joinpoint(连接点)
指那些被拦截到的点,在 Spring 中,就是指被拦截到的方法
Pointcut(切入点)
指要对哪些 Joinpoint 进行拦截,即被拦截的连接点。对拦截点进行定义
Advice(通知)
指拦截到 Joinpoint 之后要做的事情,即对切入点增强的内容。
Target(目标)
指代理的目标对象。
Weaving(植入)
指把增强代码应用到目标上,生成代理对象的过程。
Proxy(代理)
指生成的代理对象。
Aspect(切面)
切入点和通知的结合。对横切关注点的抽象
横切关注点
对哪些方法进行拦截,拦截后怎么处理,这些关注点称之为横切关注点
Advice 直译为通知,也有的资料翻译为“增强处理”,共有 5 种类型,如下表所示。
通知
说明
before(前置通知)
通知方法在目标方法调用之前执行
after(后置通知)
通知方法在目标方法返回或异常后调用
after-returning(返回后通知)
通知方法会在目标方法返回后调用
after-throwing(抛出异常通知)
通知方法会在目标方法抛出异常后调用
around(环绕通知)
通知方法会将目标方法封装起来
Spring AOP 和 AspectJ 基本概念
“横切”的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块, 并将其命名为”Aspect”,即切面。所谓”切面”,简单说就是那些与业务无关,却为业务模块所共 同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未 来的可操作性和可维护性。
使用”横切”技术,AOP 把软件系统分为两个部分:核心关注点和横切关注点 。业务处理的主要流 程是核心 关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生 在核心关注点的多处,而各处基本相似,比如权限认证、日志、事物。AOP 的作用在于分离系统 中的各种关注点,将核心关注点和横切关注点分离开来。
Spring AOP 是基于 AOP 编程模式的一个框架,它能够有效的减少系统间的重复代码,达到松耦合的目 的。Spring AOP 使用纯 Java 实现,不需要专门的编译过程和类加载器,在运行期间通过代理方式向目标类植入增强的代码。有两种实现方式:基于接口的 JDK 动态代理 和基于继承的 CGLIB 动态代理 。
AspectJ 是一个基于 Java 语言的 AOP 框架,从 Spring 2.0 开始,Spring AOP 引入了对 AspectJ 的支持。AspectJ 扩展了 Java 语言,提供了一个专门的编译器,在编译时提供横向代码的植入。
Spring AOP和AspectJ AOP有什么区别? Spring AOP是属于运行时增强,而AspectJ是编译时增强。
Spring AOP基于代理(Proxying),而 AspectJ基于字节码操作(Bytecode Manipulation)。
Spring AOP已经集成了AspectJ,AspectJ应该算得上是Java生态系统中最完整的AOP框架了。
AspectJ相 比于Spring AOP功能更加强大,但是Spring AOP相对来说更简单。 如果我们的切面比较少,那么两者性能差异不大。但是,当切面太多的话,最好选择AspectJ,它比 SpringAOP快很多。
Spring AOP:
两种代理的方式:
Spring 提供两种方式来生成代理对象 ,具体使用哪种方式生成由AopProxyFactory 根据AdvisedSupport对象的配置来进行决定。默认策略是,如果目标类是接口,就使用JDK动态代理技术,否则使用Cglib来生成代理
JDK代理和CGLIB代理的区别
JDK 动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用 InvokeHandler 来处理。而 CGLIB 动态代理是利用 ASM 开源包,加载代理对象类的 class 文件,通过修改其字节码生成子类来处理。
JDK 动态代理只能对实现了接口的类生成代理,而不能针对类。
CGLIB 是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法。因为是继承,所以该类或方法不能声明成 final 类型。
JDK动态代理特点
代理对象必须实现一个或多个接口
以接口的形式接收代理实例,而不是代理类
CGLIB动态代理特点
代理对象不能被 final 修饰
以类或接口形式接收代理实例
JDK与CGLIB动态代理的性能比较
生成代理实例性能:JDK > CGLIB 代理实例运行性能:JDK > CGLIB
JDK 动态接口代理
JDK 动态代理主要涉及到 java.lang.reflect 包中的两个类:Proxy 和 InvocationHandler。
Spring JDK 动态代理需要实现 InvocationHandler 接口,重写 invoke 方法,客户端使用 Java.lang.reflect.Proxy 类产生动态代理类的对象。
1、使用 Proxy 类里面的方法创建代理对象
创建接口,定义方法
1 2 3 4 public interface UserDao { public int add (int a,int b) ; public String update (String id) ; }
创建接口的实现类,实现方法
1 2 3 4 5 6 7 8 9 10 11 public class UserDaoImpl implements UserDao { @Override public int add (int a, int b) { return a+b; } @Override public String update (String id) { return id; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 package proxy.test;import proxy.dao.UserDao;import proxy.dao.UserDaoImpl;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;import java.util.Arrays;public class JDKProxy { public static void main (String[] args) { Class[] interfaces ={UserDao.class}; UserDaoImpl userDao =new UserDaoImpl(); UserDao useDao= (UserDao)Proxy.newProxyInstance(JDKProxy.class.getClassLoader(),interfaces,new UserDaoProxy(userDao)); int add = useDao.add(1 , 2 ); System.out.println("add = " + add); } } class UserDaoProxy implements InvocationHandler { private Object obj; public UserDaoProxy (Object obj) { this .obj = obj; } @Override public Object invoke (Object proxy, Method method, Object[] args) throws Throwable { System.out.println("方法之前执行...." +method.getName()+" :传递的参数..." + Arrays.toString(args)); Object res = method.invoke(obj, args); System.out.println("方法之后执行...." +obj); return res; } }
基于继承的 CGLIB (Code Generation Library)动态代理
JDK 动态代理使用起来非常简单,但是 JDK 动态代理的目标类必须要实现一个或多个接口,具有一定的局限性。如果不希望实现接口,可以使用 CGLIB代理。
AspectJ:
名称
说明
@Aspect
用于定义一个切面,
@Pointcut
用于定义一个切入点,在哪个地方进行切入
@Before
用于定义前置通知,相当于 BeforeAdvice。
@AfterReturning
用于定义后置通知,相当于 AfterReturningAdvice。
@Around
用于定义环绕通知,相当于MethodInterceptor。
@AfterThrowing
用于定义抛出通知,相当于ThrowAdvice。
@After
用于定义最终final通知,不管是否异常,该通知都会执行。
@DeclareParents
用于定义引介通知,相当于IntroductionInterceptor(不要求掌握)。
切入点表达式: execution (权限修饰符 返回类型 类路径 方法名称(【参数列表】))
对 com.dao.BookDao 类里面的 add 进行增强
1 execution(* com.dao.BookDao.add(..))
对 com.dao.BookDao 类里面的 所有方法 进行增强
1 execution(* com.dao.BookDao.*(..))
对 com.atguigu.dao 包里面所有类,类里面所有方法进行增强
1 execution(* com.dao.*.* (..))
基于注解测试:
导入所需要的依赖
注解配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <?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: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/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" > <context:component-scan base-package ="proxy.*" > </context:component-scan > <aop:aspectj-autoproxy > </aop:aspectj-autoproxy > </beans >
1 2 3 4 5 6 7 8 9 10 11 12 import org.springframework.stereotype.Component;@Component public class User { public void add () { int i=10 /0 ; System.out.println(" add " ); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 import org.aspectj.lang.ProceedingJoinPoint;import org.aspectj.lang.annotation.*;import org.springframework.stereotype.Component;@Component @Aspect public class UserProxy { @Pointcut(value = "execution(* proxy.aopanno.User.add(..))") public void pointDemo () { } @Before(value = "pointDemo()") public void before () { System.out.println("before........." ); } @AfterReturning(value = "execution(* proxy.aopanno.*.*(..))") public void afterReturning () { System.out.println("afterReturning........." ); } @After(value = "execution(* proxy.aopanno.User.add(..))") public void after () { System.out.println("after........." ); } @AfterThrowing(value = "execution(* proxy.aopanno.User.add(..))") public void afterThrowing () { System.out.println("afterThrowing........." ); } @Around(value = "execution(* proxy.aopanno.User.add(..))") public void around (ProceedingJoinPoint proceedingJoinPoint) throws Throwable { System.out.println("环绕之前" ); proceedingJoinPoint.proceed(); System.out.println("环绕之后执行" ); } } }
1 2 3 4 5 6 7 8 9 10 11 import org.springframework.context.annotation.ComponentScan;import org.springframework.context.annotation.Configuration;import org.springframework.context.annotation.EnableAspectJAutoProxy;@Configuration @ComponentScan(basePackages = {"proxy.*"}) @EnableAspectJAutoProxy(proxyTargetClass = true) public class ConfigAop {}
正常时:
出错时
当有多个增强类对同一个方法进行增强时,设置增强类的优先级
在增强类添加注解 @Order(数字类型的值),数字类型值越小,优先级越高
1 2 3 4 5 6 7 8 9 10 11 12 @Component @Aspect @Order(1) public class PersonProxy { @Before(value = "execution(* proxy.aopanno.*.*(..))") public void before () { System.out.println("before1........." ); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 mport org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*;import org.springframework.core.annotation.Order;import org.springframework.stereotype.Component;@Component @Aspect @Order(3) public class UserProxy { @Pointcut(value = "execution(* proxy.aopanno.User.add(..))") public void pointDemo () { } @Before(value = "pointDemo()") public void before () { System.out.println("before3........." ); } @AfterReturning(value = "execution(* proxy.aopanno.*.*(..))") public void afterReturning () { System.out.println("afterReturning........." ); } @After(value = "execution(* proxy.aopanno.User.add(..))") public void after () { System.out.println("after........." ); } @AfterThrowing(value = "execution(* proxy.aopanno.User.add(..))") public void afterThrowing () { System.out.println("afterThrowing........." ); } @Around(value = "execution(* proxy.aopanno.User.add(..))") public void around (ProceedingJoinPoint proceedingJoinPoint) throws Throwable { System.out.println("环绕之前" ); proceedingJoinPoint.proceed(); System.out.println("环绕之后执行" ); } }
基于xml配置:
基于 XML 的声明式是指通过 Spring 配置文件的方式来定义切面、切入点及通知,而所有的切面和通知都必须定义在 aop:config 元素中。
AOP操作(AspectJ 配置文件)
创建被增强类
1 2 3 4 5 6 7 8 public class Book { public void buy () { System.out.println("buy............." ); } }
创建增强类
1 2 3 4 5 6 public class BookProxy { public void before () { System.out.println("before........." ); } }
配置文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 <?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: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/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" > <bean id ="book" class ="proxy.aopxml.Book" > </bean > <bean id ="bookProxy" class ="proxy.aopxml.BookProxy" > </bean > <aop:config > <aop:pointcut id ="p" expression ="execution(* proxy.aopxml.Book.buy(..) )" /> <aop:aspect ref ="bookProxy" > <aop:before method ="before" pointcut-ref ="p" /> </aop:aspect > </aop:config > </beans >
应用场景
Spring 使用 JdbcTemplate 引入操作所需要的jar包:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 <?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: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/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 " > <context:property-placeholder location ="classpath:jdbc.properties" /> <context:component-scan base-package ="JdbcTemplate.*" > </context:component-scan > <bean id ="dataSource" class ="com.alibaba.druid.pool.DruidDataSource" > <property name ="driverClassName" value ="${prop.driverClass}" > </property > <property name ="url" value ="${prop.url}" > </property > <property name ="username" value ="${prop.userName}" > </property > <property name ="password" value ="${prop.password}" > </property > </bean > <bean id ="jdbcTemplate" class ="org.springframework.jdbc.core.JdbcTemplate" > <property name ="dataSource" ref ="dataSource" > </property > </bean > </beans >
1 2 3 4 prop.driverClass =com.mysql.cj.jdbc.Driver prop.url =jdbc:mysql://localhost:3306/wz?useSSL=false&serverTimezone=UTC prop.userName =root prop.password =jin200727
1 2 3 4 5 6 7 8 9 10 11 12 13 @org .junit.Test public void TestConnection () throws SQLException { ApplicationContext context = new ClassPathXmlApplicationContext("bean8.xml" ); DataSource bean = context.getBean(DataSource.class); Connection connection = bean.getConnection(); System.out.println("connection = " + connection); connection.close(); }
Spring事务(Transaction)
事务是一组由逻辑上紧密关联而合并成一个整体(工作单元)的多个数据库操作,这些操作要么执行,要么都不执行
事务的四个关键属性
○原子性 (atomicity):“原子”的本意是“不可再分 ”,事务的原子性表现为一个事务中涉及到的多个操作在逻辑上缺一不可。事务的原子性要求事务中的所有操作要么都执行,要么都不执行。
○一致性 (consistency):“一致”指的是数据的一致,具体是指:所有数据都处于满足业务规则的一致性状态 。一致性原则要求:一个事务中不管涉及到多少个操作,都必须保证事务执行之前数据是正确的,事务执行之后数据仍然是正确的。如果一个事务在执行的过程中,其中某一个或某几个操作失败了,则必须将其他所有操作撤销,将数据恢复到事务执行之前的状态,这就是回滚 。
○隔离性 (isolation):在应用程序实际运行过程中,事务往往是并发执行的,所以很有可能有许多事务同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏。隔离性原则要求多个事务在并发执行过程中不会互相干扰 。
○持久性 (durability):持久性原则要求事务执行完成后,对数据的修改永久的保存 下来,不会因各种系统错误或其他意外情况而受到影响。通常情况下,事务对数据的修改应该被写入到持久化存储器中。
编程式和声明式
传统的编程式事务管理,即通过编写代码实现的事务管理;
基于 AOP 技术实现的声明式事务管理。
1. 编程式事务管理
编程式事务管理是通过编写代码实现的事务管理,灵活性高,但难以维护。
2. 声明式事务管理
Spring 声明式事务管理在底层采用了 AOP 技术,其最大的优点在于无须通过编程的方式管理事务,只需要在配置文件中进行相关的规则声明,就可以将事务规则应用到业务逻辑中。
Spring 实现声明式事务管理主要有 2 种方式:
基于 XML 方式的声明式事务管理。
通过 Annotation 注解方式的事务管理。
事务管理接口
PlatformTransactionManager、TransactionDefinition 和 TransactionStatus 是事务的 3 个核心接口。
PlatformTransactionManager 接口用于管理事务,接口定义如下:
1 2 3 4 5 public interface PlatformTransactionManager { TransactionStatus getTransaction (TransactionDefinition definition) throws TransactionException ; void commit (TransactionStatus status) throws TransactionException ; void rollback (TransactionStatus status) throws TransactionException ; }
该接口中方法说明如下:
名称
说明
TransactionStatus getTransaction(TransactionDefinition definition)
用于获取事务的状态信息
void commit(TransactionStatus status)
用于提交事务
void rollback(TransactionStatus status)
用于回滚事务
在项目中,Spring 将 xml 中配置的事务信息封装到对象 TransactionDefinition 中,然后通过事务管理器的 getTransaction() 方法获得事务的状态(TransactionStatus),并对事务进行下一步的操作。
TransactionDefinition接口 TransactionDefinition 接口提供了获取事务相关信息的方法,接口定义如下。
1 2 3 4 5 6 7 public interface TransactionDefinition { int getPropagationBehavior () ; int getIsolationLevel () ; String getName () ; int getTimeout () ; boolean isReadOnly () ; }
该接口中方法说明如下。
方法
说明
String getName()
获取事务的名称
int getIsolationLevel()
获取事务的隔离级别
int getPropagationBehavior()
获取事务的传播行为
int getTimeout()
获取事务的超时时间
boolean isReadOnly()
获取事务是否只读
以下是隔离级别的值。
方法
说明
ISOLATION_DEFAULT
使用后端数据库默认的隔离级别
ISOLATION_READ_UNCOMMITTED
允许读取尚未提交的更改,可能导致脏读、幻读和不可重复读
ISOLATION_READ_COMMITTED
(Oracle 默认级别)允许读取已提交的并发事务,防止脏读,可能出现幻读和不可重复读
ISOLATION_REPEATABLE_READ
(MySQL 默认级别),多次读取相同字段的结果是一致的,防止脏读和不可重复读,可能出现幻读
ISOLATION_SERIALIZABLE
完全服从 ACID 的隔离级别,防止脏读、不可重复读和幻读
以下是传播行为的可能值,传播行为用来控制是否需要创建事务以及如何创建事务。
名称
说明
PROPAGATION_MANDATORY
支持当前事务,如果不存在当前事务,则引发异常
PROPAGATION_NESTED
如果当前事务存在,则在嵌套事务中执行
PROPAGATION_NEVER
不支持当前事务,如果当前事务存在,则引发异常
PROPAGATION_NOT_SUPPORTED
不支持当前事务,始终以非事务方式执行
PROPAGATION_REQUIRED
默认传播行为,支持当前事务,如果不存在,则创建一个新的
PROPAGATION_REQUIRES_NEW
创建新事务,如果已经存在事务则暂停当前事务
PROPAGATION_SUPPORTS
支持当前事务,如果不存在事务,则以非事务方式执行
TransactionStatus接口 TransactionStatus 接口提供了一些简单的方法来控制事务的执行和查询事务的状态,接口定义如下。
1 2 3 4 5 6 7 public interface TransactionStatus extends SavepointManager { boolean isNewTransaction () ; boolean hasSavepoint () ; void setRollbackOnly () ; boolean isRollbackOnly () ; boolean isCompleted () ; }
Spring编程式事务: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package Tx.dao;import Tx.model.User;import java.util.List;public interface UserDao { void createUserTable () ; void saveUser (User user) ; List<User> listUser () ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 package Tx.model;public class User { private int id; private String name; private int age; public User () { } public User (String name, Integer age) { this .name = name; this .age = age; } public int getId () { return id; } public void setId (int id) { this .id = id; } public String getName () { return name; } public void setName (String name) { this .name = name; } public int getAge () { return age; } public void setAge (int age) { this .age = age; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 package Tx.dao;import Tx.dao.UserDao;import Tx.model.User;import org.springframework.jdbc.core.JdbcTemplate;import org.springframework.jdbc.core.RowMapper;import org.springframework.transaction.PlatformTransactionManager;import org.springframework.transaction.TransactionDefinition;import org.springframework.transaction.TransactionStatus;import org.springframework.transaction.support.DefaultTransactionDefinition;import javax.sql.DataSource;import java.sql.ResultSet;import java.sql.SQLException;import java.util.List;public class UserDaoImpl implements UserDao { private JdbcTemplate jdbcTemplate; private UserDao userDao; private PlatformTransactionManager transactionManager; @Override public void createUserTable () { this .jdbcTemplate.execute("CREATE TABLE `user` (\r\n" + " `id` int(11) NOT NULL AUTO_INCREMENT,\r\n" + " `name` varchar(50) DEFAULT NULL,\r\n" + " `age` int(11) DEFAULT NULL,\r\n" + " PRIMARY KEY (`id`)\r\n" + ") ENGINE=InnoDB DEFAULT CHARSET=utf8;" ); } @Override public void saveUser (User user) { TransactionDefinition def = new DefaultTransactionDefinition(); TransactionStatus status = transactionManager.getTransaction(def); this .jdbcTemplate.update("INSERT INTO USER(NAME,age) VALUES (?,?)" , user.getName(), user.getAge()); transactionManager.commit(status); } @Override public List<User> listUser () { List<User> users = this .jdbcTemplate.query("SELECT NAME,age FROM USER" , new RowMapper<User>() { public User mapRow (ResultSet rs, int rowNum) throws SQLException { User user = new User(); user.setName(rs.getString("name" )); user.setAge(rs.getInt("age" )); return user; } }); return users; } public JdbcTemplate getJdbcTemplate () { return jdbcTemplate; } public UserDao getUserDao () { return userDao; } public void setUserDao (UserDao userDao) { this .userDao = userDao; } public PlatformTransactionManager getTransactionManager () { return transactionManager; } public void setJdbcTemplate (JdbcTemplate jdbcTemplate) { this .jdbcTemplate = jdbcTemplate; } public void setDataSource (DataSource datasource) { this .jdbcTemplate = new JdbcTemplate(datasource); } public void setTransactionManager (PlatformTransactionManager transactionManager) { this .transactionManager = transactionManager; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 package Tx;import Tx.dao.UserDao;import Tx.model.User;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;import java.util.List;public class MainApp { public static void main (String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("tx.xml" ); UserDao dao = (UserDao) ctx.getBean("userdao" ); List<User> users = dao.listUser(); for (User user : users) { System.out.println("姓名:" + user.getName() + "\t年龄:" + user.getAge()); } } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 <?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:aop ="http://www.springframework.org/schema/aop" 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/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd" > <bean id ="dataSource" class ="org.springframework.jdbc.datasource.DriverManagerDataSource" > <property name ="driverClassName" value ="com.mysql.cj.jdbc.Driver" /> <property name ="url" value ="jdbc:mysql://localhost:3306/wz?useSSL=false& serverTimezone=UTC" /> <property name ="username" value ="root" /> <property name ="password" value ="jin200727" /> </bean > <bean id ="jdbcTemplate" class ="org.springframework.jdbc.core.JdbcTemplate" > <property name ="dataSource" ref ="dataSource" /> </bean > <bean id ="transactionManager" class ="org.springframework.jdbc.datasource.DataSourceTransactionManager" > <property name ="dataSource" ref ="dataSource" /> </bean > <bean id ="userdao" class ="Tx.dao.UserDaoImpl" > <property name ="jdbcTemplate" ref ="jdbcTemplate" /> <property name ="transactionManager" ref ="transactionManager" /> </bean > </beans >
使用原生的JDBC API 实现事务管理是所有事务管理方式的基石,同时也是最典型的编程式事务管理。编程式事务管理需要将事务管理代码嵌入到业务方法中 来控制事务的提交和回滚。在使用编程的方式管理事务时,必须在每个事务操作中包含额外的事务管理代码。相对于核心业务 而言,事务管理的代码显然属于非核心业务 ,如果多个模块都使用同样模式的代码进行事务管理,显然会造成较大程度的代码冗余 。
声明式事务管理 大多数情况下,声明式事务比编程式事务管理更好,它将事务的管理代码从业务中分离出来,以声明的方式来实现事务
事务管理代码的固定模式作为一种横向的关注点,可以通过AOP的方式进行模块化
Spring在不同的事务管理API之上定义了一个抽象层 ,通过配置 的方式使其生效,从而让应用程序开发人员不必了解事务管理API的底层实现细节 ,就可以使用Spring的事务管理机制。
Spring既支持编程式事务管理,也支持声明式的事务管理。
1.1 Spring提供的事务管理器
Spring从不同的事务管理API中抽象出了一整套事务管理机制,让事务管理代码从特定的事务技术中独立出来。开发人员通过配置的方式进行事务管理,而不必了解其底层是如何实现的。
Spring的核心事务管理抽象是PlatformTransactionManager。它为事务管理封装了一组独立于技术的方法。无论使用Spring的哪种事务管理策略(编程式或声明式),事务管理器都是必须的。
事务管理器可以以普通的bean的形式声明在Spring IOC容器中。
1.2 事务管理器的主要实现
①DataSourceTransactionManager:在应用程序中只需要处理一个数据源,而且通过JDBC存取。
②JtaTransactionManager:在JavaEE应用服务器上用JTA(Java Transaction API)进行事务管理
③HibernateTransactionManager:用Hibernate框架存取数据库
测试数据准备: 1.1 需求
2数据库表
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 CREATE TABLE book ( isbn VARCHAR (50 ) PRIMARY KEY, book_name VARCHAR (100 ), price INT ) ; CREATE TABLE book_stock ( isbn VARCHAR (50 ) PRIMARY KEY, stock INT , CHECK (stock > 0 ) ) ; CREATE TABLE account ( username VARCHAR (50 ) PRIMARY KEY, balance INT , CHECK (balance > 0 ) ) ; INSERT INTO account (`username`,`balance`) VALUES ('Tom' ,100000 );INSERT INTO account (`username`,`balance`) VALUES ('Jerry' ,150000 );INSERT INTO book (`isbn`,`book_name`,`price`) VALUES ('ISBN-001' ,'book01' ,100 );INSERT INTO book (`isbn`,`book_name`,`price`) VALUES ('ISBN-002' ,'book02' ,200 );INSERT INTO book (`isbn`,`book_name`,`price`) VALUES ('ISBN-003' ,'book03' ,300 );INSERT INTO book (`isbn`,`book_name`,`price`) VALUES ('ISBN-004' ,'book04' ,400 );INSERT INTO book (`isbn`,`book_name`,`price`) VALUES ('ISBN-005' ,'book05' ,500 );INSERT INTO book_stock (`isbn`,`stock`) VALUES ('ISBN-001' ,1000 );INSERT INTO book_stock (`isbn`,`stock`) VALUES ('ISBN-002' ,2000 );INSERT INTO book_stock (`isbn`,`stock`) VALUES ('ISBN-003' ,3000 );INSERT INTO book_stock (`isbn`,`stock`) VALUES ('ISBN-004' ,4000 );INSERT INTO book_stock (`isbn`,`stock`) VALUES ('ISBN-005' ,5000 );
配置文件: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 <?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:aop ="http://www.springframework.org/schema/aop" 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/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd" > <bean id ="dataSource" class ="org.springframework.jdbc.datasource.DriverManagerDataSource" > <property name ="driverClassName" value ="com.mysql.cj.jdbc.Driver" /> <property name ="url" value ="jdbc:mysql://localhost:3306/wz?useSSL=false& serverTimezone=UTC" /> <property name ="username" value ="root" /> <property name ="password" value ="jin200727" /> </bean > <bean id ="jdbcTemplate" class ="org.springframework.jdbc.core.JdbcTemplate" > <property name ="dataSource" ref ="dataSource" /> </bean > <bean id ="transactionManager" class ="org.springframework.jdbc.datasource.DataSourceTransactionManager" > <property name ="dataSource" ref ="dataSource" /> </bean > <bean id ="userdao" class ="Tx.dao.UserDaoImpl" > <property name ="jdbcTemplate" ref ="jdbcTemplate" /> <property name ="transactionManager" ref ="transactionManager" /> </bean > </beans >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 @Repository public class BookDao { @Autowired JdbcTemplate jdbcTemplate; public void updateBalance (String userName,int price) { String sql = "UPDATE account SET balance=balance-? WHERE username=?" ; jdbcTemplate.update(sql, price,userName); } public int getPrice (String isbn) { String sql = "SELECT price FROM book WHERE isbn=?" ; return jdbcTemplate.queryForObject(sql, Integer.class, isbn); } public void updateStock (String isbn) { String sql = "UPDATE book_stock SET stock=stock-1 WHERE isbn=?" ; jdbcTemplate.update(sql, isbn); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 package Tx2.service;import Tx2.dao.BookDao;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;import org.springframework.transaction.annotation.Transactional;@Service public class BookService { @Autowired BookDao bookDao; @Transactional public void checkout (String username,String isbn) { bookDao.updateStock(isbn); int price = bookDao.getPrice(isbn); bookDao.updateBalance(username, price); int i =10 /0 ; } }
事务的传播行为: 但事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:饭阀可能继续在现在的事务中运行,也可能开启一个新的事务,并在自己的事务之中运行,事务的传播可以由传播属性指定。spring定义了7种传播的行为。(如果多个事务进行嵌套运行,子事务是否要和大事务共用一个事务)
传播属性
required 看上级事务是相同吗,事务的属性都是继承大事务的,
事务传播属性可以在@Transactional注解的propagation属性中定义。
4、timeout:超时时间
(1)事务需要在一定时间内进行提交,如果不提交进行回滚
(2)默认值是 -1 ,设置时间以秒单位进行计算
5、readOnly:是否只读
(1)读:查询操作,写:添加修改删除操作
(2)readOnly 默认值 false,表示可以查询,可以添加修改删除操作
(3)设置 readOnly 值是 true,设置成 true 之后,只能查询 6、rollbackFor:回滚
(1)设置出现哪些异常进行事务回滚
7、noRollbackFor:不回滚
(1)设置出现哪些异常不进行事务回滚
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 package Tx2.service;import Tx2.dao.BookDao;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;import org.springframework.transaction.annotation.Transactional;@Service public class BookService { @Autowired BookDao bookDao; @Transactional(timeout = 3) public void checkout (String username,String isbn) { bookDao.updateStock(isbn); try { Thread.sleep(3000 ); } catch (InterruptedException e) { e.printStackTrace(); } int price = bookDao.getPrice(isbn); bookDao.updateBalance(username, price); int i =10 /0 ; } }
事务的隔离级别 数据库事务并发问题
假设现在有两个事务:Transaction01和Transaction02并发执行。
①脏读 :一个未提交事务读取到另一个未提交事务的数据
[1]Transaction01将某条记录的AGE值从20修改为30。
[2]Transaction02读取了Transaction01更新后的值:30。
[3]Transaction01回滚,AGE值恢复到了20。
[4]Transaction02读取到的30就是一个无效的值。
②不可重复读 :一个未提交事务读取到另一提交事务修改数据
[1]Transaction01读取了AGE值为20。
[2]Transaction02将AGE值修改为30。
[3]Transaction01再次读取AGE值为30,和第一次读取不一致。
③幻读 ::一个未提交事务读取到另一提交事务添加数据
[1]Transaction01读取了STUDENT表中的一部分数据。
[2]Transaction02向STUDENT表中插入了新的行。
[3]Transaction01读取了STUDENT表时,多出了一些行。
通过设置事务的隔离级别,解决读的问题:
数据库系统必须具有隔离并发运行各个事务的能力,使它们不会相互影响,避免各种并发问题。一个事务与其他事务隔离的程度称为隔离级别 。SQL标准中规定了多种事务隔离级别,不同隔离级别对应不同的干扰程度,隔离级别越高,数据一致性就越好,但并发性越弱。
①读未提交 :READ UNCOMMITTED
允许Transaction01读取Transaction02未提交的修改。
②读已提交 :READ COMMITTED
要求Transaction01只能读取Transaction02已提交的修改。
③可重复读:REPEATABLE READ
确保Transaction01可以多次从一个字段中读取到相同的值,即Transaction01执行期间禁止其它事务对这个字段进行更新。
④串行化 :SERIALIZABLE
确保Transaction01可以多次从一个表中读取到相同的行,在Transaction01执行期间,禁止其它事务对这个表进行添加、更新、删除操作。可以避免任何并发问题,但性能十分低下。
⑤各个隔离级别解决并发问题的能力见下表
脏读
不可重复读
幻读
READ UNCOMMITTED
有
有
有
READ COMMITTED
无
有
有
REPEATABLE READ
无
无
有
SERIALIZABLE
无
无
无
⑥各种数据库产品对事务隔离级别的支持程度
Oracle
MySQL
READ UNCOMMITTED
×
√
READ COMMITTED
√
√
REPEATABLE READ
×
√(默认)
SERIALIZABLE
√
√
同一事务之下避免了脏读,就是不会读取到已修改未提交的数据,但是没有避免同一事务不可重复度的问题,就是同一个事务下,读到不同的数据
基于xml配置: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 <?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:aop ="http://www.springframework.org/schema/aop" 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/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd" > <context:property-placeholder location ="classpath:tx.properties" /> <context:component-scan base-package ="Tx2.*" > </context:component-scan > <bean id ="dataSource" class ="com.alibaba.druid.pool.DruidDataSource" > <property name ="driverClassName" value ="${prop.driverClass}" > </property > <property name ="url" value ="${prop.url}" > </property > <property name ="username" value ="${prop.userName}" > </property > <property name ="password" value ="${prop.password}" > </property > </bean > <bean id ="jdbcTemplate" class ="org.springframework.jdbc.core.JdbcTemplate" > <property name ="dataSource" ref ="dataSource" /> </bean > <bean id ="transactionManager" class ="org.springframework.jdbc.datasource.DataSourceTransactionManager" > <property name ="dataSource" ref ="dataSource" /> </bean > <tx:advice id ="txadvice" > <tx:attributes > <tx:method name ="checkout" propagation ="REQUIRED" /> </tx:attributes > </tx:advice > <aop:config > <aop:pointcut id ="pt" expression ="execution(* Tx2.*(..))" /> <aop:advisor advice-ref ="txadvice" pointcut-ref ="pt" /> </aop:config > </beans >
基于注解配置: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 @Configuration @ComponentScan(basePackages = "com.atguigu") @EnableTransactionManagement public class TxConfig { @Bean public DruidDataSource getDruidDataSource () { DruidDataSource dataSource = new DruidDataSource(); dataSource.setDriverClassName("com.mysql.jdbc.Driver" ); dataSource.setUrl("jdbc:mysql:///user_db" ); dataSource.setUsername("root" ); dataSource.setPassword("root" ); return dataSource; } @Bean public JdbcTemplate getJdbcTemplate (DataSource dataSource) { JdbcTemplate jdbcTemplate = new JdbcTemplate(); jdbcTemplate.setDataSource(dataSource) @Configuration @ComponentScan(basePackages = "com.atguigu") @EnableTransactionManagement public class TxConfig { @Bean public DruidDataSource getDruidDataSource () { DruidDataSource dataSource = new DruidDataSource(); dataSource.setDriverClassName("com.mysql.jdbc.Driver" ); dataSource.setUrl("jdbc:mysql:///user_db" ); dataSource.setUsername("root" ); dataSource.setPassword("root" ); return dataSource; } @Bean public JdbcTemplate getJdbcTemplate (DataSource dataSource) { JdbcTemplate jdbcTemplate = new JdbcTemplate(); jdbcTemplate.setDataSource(dataSource)