SSM_Spring
1. 概述
- 简化开发
- 免费开源
- 降低耦合度
- 是一个IOC(DI)和AOP容器框架
2. 框架特点:
- 依赖注入【DI】:依赖的对象不需要手动调用赋值,通过配置赋值
- 非侵入式:使用框架时,类不需要实现框架中的接口或继承类
- 控制反转【IOC】:框架发现你依赖一个Dao,框架会帮你自动创建
- Dao组件对象、Bean对象、Service对象等
- 面向切面变成【AOP】:可以在不修改源代码基础上,进行迭代
- 容器:Spring是一个容器,可以管理其对象的声明周期
- 组件化:通过XML或注解方式,将Servlet、Service、Dao等组合起来
- 一站式:Spring 家族:SSM
- 【Spring、SpringMVC、JDBCTemplate】
- 【管家、表述层、持久化层】
Spring模块:
- Test:junt测试单元
- Core Container:核心容器【及对应jar包】
- Beans:spring-beans-5.3.1.jar
- Core:spring-core-5.3.1.jar
- 旧版本依赖:common logs
- 新版本依赖:spring-jcl-5.3.1.jar
- Context:spring-context-5.3.1.jar
- SpEL:spring-expression-5.3.1.jar
- DataAccess/Integration:提供了对数据访问/集成的功能
- Web:提供了面向Web应用程序的集成功能 Spring MVC
- AOP&Aspects:提供了面向切面编程的实现。
注意:Core Container上面的内容需要Core COntainer的支持。在基础上拓展或使用。
单独使用Spring时,没有IOC容器,需要自己new
2. 入门使用:
-
导入相关Jar包:
- Beans:spring-beans-5.3.1.jar
- Core:spring-core-5.3.1.jar
- Context:spring-context-5.3.1.jar
- SpEL:spring-expression-5.3.1.jar
- spring-jcl-5.3.1.jar
-
创建相关Bean:
-
必须要有无参构造器
-
必须有属性【set方法与成员变量名称匹配】
-
示例如下:
private String name; //使用Spring创建类,必须要有无参构造器 public HelloWord() { } //使用Spring创建类,必须要有set方法,用来给其属性赋值 public void setName(String name) { this.name = name; }
-
-
配置Jspring.xml:
<bean id="helloWord" class="com.atguigu.spring.helloword.HelloWord"> <property name="name" value="Spring" ></property> <!-- 在创建实例化对象时,使用对应的set方法,给HelloWord表中的name字段赋值spring --> </bean>
-
获取使用:
-
在只是用Spring框架时,IOC容器需要手动创建
-
实例代码如下:
ApplicationContext ioc = new ClassPathXmlApplicationContext("application.xml"); HelloWord helloWord = (HelloWord) ioc.getBean("helloWord"); //从ioc容器中,取除一个id为helloWord的表 helloWord.sayHello();
-
3. IOC与DI
3.1 IOC
不止是一种设计思想,还是一个容器
- 传统使用:是自己去容器中拿
- Spring框架:框架检测到你依赖这个组件,会自动给你送给来
- IOC容易中的所有bean都是单例的,【即多会儿拿到都是独一份】
- 是一种反转控制的思想
3.2 DI
相当于DI是IOC 的实现,(基础依赖于XML)
3.3 基于xml方式管理Bean
3.3.1 ★ 基于无参构造器(setter方法)配置
-
通过在beans标签中配置标签来创建bean
-
一个标签为一个类
- id在xml中为唯一
- class为全类名
-
通过在标签中配置 标签来通过其bean的setter方法来赋值
- name:就是成员变量名
- value:就是成员变量值
实例代码如下:
<bean id="helloWord" class="com.atguigu.spring.helloword.HelloWord">
<property name="name" value="Spring" ></property>
</bean>
3.3.2 基于全参构造器配置
- 使用全参构造器创建对象时,
- 要在标签中配置相同数量的标签
- 标签中通常只有value属性,只要位置和全参构造器中的顺序匹配即可
- 如顺序不匹配的话,可以通过在标签中配置其他参数
- index:说明其value的对应位置
- name:说明其value的对应属性
- type:说明其value的对应类型(注:在Bean中该类型,应该唯一)
- 如顺序不匹配的话,可以通过在标签中配置其他参数
-
使用P名称空间配置
- 直接在bean中使用p名称空间配置
<!--通过p名称空间配置bean--> <bean id="book5" class="com.atguigu.spring.beans.Book" p:id="5" p:title="解忧杂货店" p:author="藤野圭吾" p:price="33.00" p:sales="100"></bean> </beans>
3.3.3 基于部分参数构造器配置
使用部分参数构造器创建对象时,
- 当匹配的参数构造方法在类中有多个对应的构造器时, 会默认使用最后一个
- 也可使用【index、name等】进行配置
3.3.4 xml配置中特殊字符处理
- null值:可以在中配置一个【普通和自闭和均可】标签
- 单个特殊字符:可以使用转义字符
- 如:【<】 -->【<】
- 多个特殊字符:使用<![CDATA[多个特殊字符]]>进行处理
3.3.5 ★ xml中配置外部bean
外部:指的时在同一个xml配置文件中的其他bean,并不是其他配置文件中的bean
通过指定ref属性或标签来进行连接
<bean id="book" class="com.atguigu.spring.beans.Book">
<constructor-arg value="罗贯中"></constructor-arg>
<constructor-arg index="1" value="《三国演义》"></constructor-arg>
<constructor-arg name="id" value="1"></constructor-arg>
<constructor-arg type="java.lang.Double" value="6.66"></constructor-arg>
<constructor-arg value="100"></constructor-arg>
</bean>
<bean id="cartItem" class="com.atguigu.spring.beans.CartItem">
<property name="book" ref="book"></property>
<property name="amount" value="6.66"></property>
<property name="count" value="1"></property>
</bean>
3.3.6 xml中配置内部bean
- 把这个bean进行私有化,在当前bean中重新赋值
- 不建议对内部bean赋予id属性,因为在其他bean中并不能够引用
<bean id="cartItem2" class="com.atguigu.spring.beans.CartItem">
<property name="book">
<bean class="com.atguigu.spring.beans.Book">
<property name="id" value="2"></property>
<property name="title" value="《解忧杂货铺》"></property>
<property name="author" value="东野圭吾"></property>
<property name="price" value="8.88"></property>
<property name="sales" value="100"></property>
</bean>
</property>
<property name="amount" value="8.88"></property>
<property name="count" value="1"></property>
</bean>
3.3.7 使用P命名空间进行配置
在bean中配置属性常用
想要使用p标签,要先引用,引用路径如下
xmlns:p="http://www.springframework.org/schema/p"
<bean id="book2" class="com.atguigu.spring.beans.Book"
p:title="《一本书》" p:author="不清楚" >
</bean>
3.3.8 ★ 给级联属性赋值
<bean id="cartItem3" class="com.atguigu.spring.beans.CartItem">
<property name="book" ref="book1"></property>
<!--通过给级联属性赋值修改书名-->
<property name="book.title" value="新三国"></property>
<property name="amount" value="10.00"></property>
<property name="count" value="5"></property>
</bean>
3.3.9 给集合属性赋值
-
当bean的属性是集合类型时,可以通过以下标签进行配置:
- :配置数组类型
- :配置List类型
- <map>:配置Map类型
-
使用<util:lXXX >标签时,需要在上方引入
- 引入标签:xmlns:util=“http://www.springframework.org/schema/util”
- 引入约束:https://www.springframework.org/schema/util/spring-util.xsd
<bean id="bookShop" class="com.atguigu.spring.beans.BookShop">
<property name="list">
<!-- <list>-->
<!--配置List中的对象的方式:
1、通过内部bean的方式配置
2、引用外部的bean
-->
<!-- <ref bean="book1"></ref>-->
<!-- <ref bean="book2"></ref>-->
<!-- <ref bean="book3"></ref>-->
<!-- </list>-->
<!--引用外部的集合bean-->
<ref bean="listBean"></ref>
</property>
<property name="map">
<map>
<entry key="bk01" value-ref="book4"></entry>
<entry key="bk02" value-ref="book5"></entry>
</map>
</property>
<property name="props">
<props>
<prop key="username">root</prop>
<prop key="password">root</prop>
<prop key="url">jdbc:mysql://localhost:3306/test</prop>
<prop key="driverClassName">com.mysql.jdbc.Driver</prop>
</props>
</property>
</bean>
<!--配置集合bean-->
<util:list id="listBean">
<ref bean="book1"></ref>
<ref bean="book2"></ref>
<ref bean="book3"></ref>
</util:list>
<!-- 测试外部连接-->
<bean id="bookShop" class="com.atguigu.spring.beans.BookShop">
<property name="list">
<ref bean="listBook"></ref>
</property>
<property name="properties">
<ref bean="stu"></ref>
</property>
<!-- <property name="set" value="12344"></property>-->
<property name="set">。
<!-- 使用外部连接-->
<ref bean="set"></ref>
</property>
</bean>
<!-- 测试外部连接-->
<util:list id="listBook">
<ref bean="book"></ref>
<ref bean="book2"></ref>
</util:list>
<util:properties id="stu">
<prop key="name">zhangsan</prop>
<prop key="age">18</prop>
</util:properties>
<util:set id="set">
<value>set1</value>
<value>set2</value>
</util:set>
3.3.10 自动装配属性
- 通过bean标签的autowire属性设置自动装配的规则,该属性常用的值如下:
- no或default:不自动装配
- byName:根据属性名和IOC容器中bean的id属性值实现自动装配
- 如果找到则装配成功;
- 找不到则不装配
- byType:根据属性的类型和IOC容器中bean的类型实现自动装配
- 如果找到一个则装配成功;
- 如果找到多个则抛出异常;
- 如果找不到则不装配
<bean id="cartItem4" class="com.atguigu.spring.beans.CartItem" autowire="byName">
<property name="amount" value="9.99"></property>
<property name="count" value="1"></property>
</bean>
3.3.11 ★ 引入外部的属性文件
例如:使用druid与MySQL
- 导入相关jar包
- druid-1.1.9.jar
- mysql-connector-java-5.1.37-bin.jar
- 在src目录下创建外部属性文件:【druid.properties】
- 通过在xml文件中配置标签来引入
- <context:property-placeholder location=“classpath:druid.properties”>、</context:property-placeholder>
<!--★引入外部的属性文件-->
<context:property-placeholder location="classpath:druid.properties"></context:property-placeholder>
<!--★配置数据源-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<!--配置连接数据库的相关信息的属性-->
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="driverClassName" value="${jdbc.driverClassName}"></property>
<property name="initialSize" value="${jdbc.initialSize}"></property>
<property name="maxActive" value="${jdbc.maxActive}"></property>
</bean>
3.3.12 设置bean的作用域
- bean的作用域一共有四种,分别为:
- singleton:单例【IOC容易只会存在一份,无论多会儿拿】
- prototype:每次使用getBean()方法都会获取一个新的bean
- request:每次HTTP请求,会创建一个request。该作用域仅能用于WebApplicationContext环境
- session:同一个Session会话中,会共享同一个bean。该作用域仅能用于WebApplicationContext环境
- 如何使用:
- 在Spring相关的XXX.xml中
- 使用bean标签配置其属性时
- 通过给 标签设置其scope属性值【scope=”prototype“】
- 注解使用:@Scope(“prototype”)
实例代码:
<bean id="book" class="com.atguigu.spring.beans.Book" scope="prototype">
<constructor-arg value="罗贯中"></constructor-arg>
<constructor-arg index="1" value="《三国演义》"></constructor-arg>
<constructor-arg name="id" value="1"></constructor-arg>
<constructor-arg type="java.lang.Double" value="6.66"></constructor-arg>
<constructor-arg value="100"></constructor-arg>
</bean>
3.3.13 IOC中的工厂Bean
该处的工厂Bean指的是,通过该工厂bean的相关方法可以从工厂bean中获得一个自定义的一个bean对象。该工厂bean就被我们成为工厂bean
-
想要在配置文件中创建相关的工程Bean,需要以下几个步骤
-
首先创建一个类,让其实现org.springframework.beans.factory.FactoryBean接口
-
实现接口的同时要指定获取bean的类型
类中实例代码:
package com.atguigu.spring.beans; import org.springframework.beans.factory.FactoryBean; public class MyFactoryBean implements FactoryBean<Book> { /** * 获取一个工厂类指定的实体类 * @return * @throws Exception */ @Override public Book getObject() throws Exception { return new Book(); } /** * 获取实体类的class类型 * @return */ @Override public Class<?> getObjectType() { return Book.class; } /** * 设置该实体类是否是单例模式 * @return */ @Override public boolean isSingleton() { return false; }
}
3. 在xml中进行配置:<bean id="FactoryBean" class="spring.beans.FactoryBean"></bean> XML配置文件中实例代码: ```xml <bean id="myFactoryBean" class="com.atguigu.spring.beans.MyFactoryBean"></bean>
-
3.3.14 Bean的生命周期
3.3.14.1 默认周期
IOC会对bean的进行管理,bean会随着IOC的销毁被销毁。默认情况下,生命周期一般有三个步骤,分别为:
- 通过构造方法或者工程类创建
- 使用属性对应的set方法设置值或被其他bean引用
- 通过getBean()方法获取后使用
3.3.14.2 添加初始化与销毁
- 在xml配置bean时,可以通过 init-method 与destroy-method属性指定其初始化方法与销毁方法。
- 注解:
- @PostConstruct相当于init-method=”myInit”
- @PreDestory相当于是destroy-method=”myDestroy”
- 注意:对于销毁的方法它只对bean的scope=singleton有效。
此时BeanLife的生命周期为:
1. BeanLife()执行了。。。
2. setName()执行啦。。。
3. 初始化方法执行。。。
4. 销毁方法被执行。。。
5. BeanLife{name='beanLifeTest'}
-
注意:即使容器已经关闭,但是获取到的类,依旧存在(只要类被从ioc容器中取出来,ioc关不关闭好像没关系了)
例如:
<bean id="beanLife" class="com.atguigu.spring.beans.BeanLife" init-method="init" destroy-method="destroy">
<property name="name" value="beanLifeTest"></property>
</bean>
其类中的代码为:
package com.atguigu.spring.beans;
public class BeanLife {
private String name;
public BeanLife() {
System.out.println("BeanLife()执行了。。。");
}
public void init(){
System.out.println("初始化方法执行。。。");
}
public void destroy(){
System.out.println("销毁方法被执行。。。");
}
public void setName(String name) {
System.out.println("setName()执行啦。。。");
this.name = name;
}
@Override
public String toString() {
return "BeanLife{" +
"name='" + name + '\'' +
'}';
}
}
3.3.14.3 bean的后置处理器
-
bean的后置处理器是对所有在xml中注册的bean起作用的。并不属于其中一个类对象
-
bean的后置处理器有两个部分,也就是两个方法
-
bean的后置处理器主要用在初始化方法的前面与后面
- 如果没有初始化方法,那么这两个方法相相继执行
-
这两个方法并不是随便创建的。他们所在的类要实现一个接口
- 需要实现的接口:org.springframework.beans.factory.config.BeanPostProcessor。
- 重写其中的两个方法:
- postProcessBeforeInitialization(Object, String):作用在初始化方法前
- postProcessAfterInitialization(Object, String):作用在初始化方法后
-
创建之后需要在xml配置文件中进行配置
<bean id="postprocessor" class="com.atguigu.spring.beans.Postprocessor"></bean>
-
最后,生命周期为:
- BeanLife()执行了。。。
- setName()执行啦。。。
- bean后置处理器的before。。。
- 初始化方法执行。。。
- bean后置处理器的after。。。
- 销毁方法被执行。。。
- BeanLife{name=‘beanLifeTest’}
后置处理器其中代码为:
public class Postprocessor implements BeanPostProcessor { @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { System.out.println("bean后置处理器的before。。。"); return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { /* if (beanName.equals("beanLife")) { //对指定的类进行修改 }*/ System.out.println("bean后置处理器的after。。。"); return bean; } }
测试类代码为:
@Test public void testBeanLife() { BeanLife beanLife = (BeanLife) ioc.getBean("beanLife"); //ClassPathXmlApplicationContext 这个类中没有关闭IOC容器的相关方法,需要找他的其中一个父类 //所以需要强转成ConfigurableApplicationContext,然后关闭 ConfigurableApplicationContext cioc = (ConfigurableApplicationContext) ioc; cioc.close(); System.out.println(beanLife);
3.3.15 获取bean的方式
- getBean
- id名称:【获取唯一】
- 需要强转
- bean.class:【获取不唯一】
- Spring配置文件中【类不唯一】
- 获取到一个集合当中
- id名称与bean.class:【获取唯一】
- 不需要强转
- id名称:【获取唯一】
3.4 基于注解
导入相关jar包
- Beans:spring-beans-5.3.1.jar
- Core:spring-core-5.3.1.jar
- Context:spring-context-5.3.1.jar
- SpEL:spring-expression-5.3.1.jar
- spring-jcl-5.3.1.jar
- spring-aop-5.3.1.jar
基于注解如何创建bean
在不适用配置文件的情况下,使用注解的方式来告诉IOC如何创建bean,
3.4.1 ★ 使用的注解一共有四个,他们分别是:
- @Component: 基本管理组件
- @Repository: 持久化层管理组件
- @Service: 业务逻辑层管理组件
- @Controller: 表述层控制器组件
组件命名规则及注意:
- 默认命名:【类名首字母小写】就相当于配置文件中的id
- 在注解的value属性来进行配置,例如【@Component(value = “user”)】value可以省略不写
- 注解是开发者规范,并不是ioc用来判断功能,
3.4.2 ★ 配置文件中的组件扫描
基本扫描:
<context:component-scan base-package="com.atguigu.annotation.aop">
</context:component-scan>
使用<context:component-scan>标签设置扫描路径,路径中也可以使用类似于【过滤器】的东西
- <context:include-filter>:设置只扫描指定路径中的某一部分,要想使用只扫描需要关闭系统默认的扫描配置,即在该标签中设置【use-default-filters=“false”】
- type=“annotation”:通过注解的全类名限定的方式来确认被标注那些注解的可以被扫描
- type=“assignable”:通过注解接口或实现类的的方式来确认被标注那些注解的可以被扫描
- aspectj
- regex
- custom
- <context:exclude-filter>:设置不扫描指定路径中的某一部分
<context:include-filter>
<context:component-scan base-package="com.atguigu.annotation" use-default-filters="false">
要求父类不使用默认的过滤器时,才可使用下方【即use-default-filters="false"】
annotation:通过配置注解全类名的方式确定那些组件被扫描
<context:include-filter type="annotation" expression="org.springframework.stereotype.Repository"/>
assignable:通过配置注解接口或实现类的方式确定那些组件被扫描
<context:include-filter type="assignable" expression="com.atguigu.annotation.dao.UserDao"/>
</context:component-scan>
<context:include-filter>
<context:component-scan base-package="com.atguigu.annotation">
annotation:通过配置注解全类名的方式确定那些组件不被扫描
注意:Component组件不能被指定为过滤不扫描它,因为其他注解组件均在其基础上进行使用
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service"/>
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Repository"/>
assignable:通过配置注解接口或实现类的方式确定那些组件不被扫描
<context:exclude-filter type="assignable" expression="com.atguigu.annotation.dao.UserDao"/>
</context:component-scan>
★ 自动装配
- @Autowired:
- 可以在任何地方使用
- 应用在数组类型上时,此时Spring将会把所有匹配的bean进行自动装配。
- 应用到集合类型上时,Spring读取该集合的类型信息,然后自动装配所有与之兼容的bean 。
- 应用到Map类型上时 ,若该Map的键值为String,那么 Spring将自动装配与值类型兼容的bean作为值,并以bean的id值作为键。
- 默认情况下为必须装配,如果没有找到适配,或者找到多个则会抛出异常
- 可以通过(Required=false)属性将其设置为非必须装配,即有适配就装配,没有则罢
- 自动装配过程:
- 先根据类型,如果IOC中有多个该类型,
- 再通过属性名作为依据【也就是配置中的id】
- 如果还可以适配多个,则抛出异常
- 解决:
- 通过在该注解上方时用@Qualifiter注解的value值属性进行设置(value可以省略)
- 可以在任何地方使用
- @Value:装配普通值【String,Integer等】
- @Resource注解(了解)
- @Inject注解(了解)
完全注解开发
就是使用一个自定义类,用来替代配置文件
使用流程:
- 创建一个类,
- 添加@Configuration注解:说明他是一个配置类
- 添加@ComponentScan注解:设置要扫描的路径
@ComponentScan(basePackages = "com.atguigu.annotation")
@Configuration
public class Annotation {
//注解类配置
private User getUser(){
return new User();
}
}
测试类
public class NoXmlTest {
//完全注解开发测试
@Test
public void testNoXml(){
ApplicationContext ioc = new AnnotationConfigApplicationContext(Annotation.class);
User user = (User) ioc.getBean("user");
System.out.println(user);
}
}
4. AOP
AOP面向切面【aspect】编程,弥补了OOP面向对象编程的不足。通过动态代理事项程序功能拓展和统一维护的一种技术。
4.1动态代理(只要代理,就是代理一个类)
使用一个代理将原来的对象包装起来,即在不改变原有代码的基础上,进行一些操作,
-
相比于静态代理来说,每创建一个被代理对象,都要手动创建一个代理对象,并且自己创建一被代理的对象,代理对象与被代理对象之间是一一对应的。
-
对于AOP中动态代理来说,只要交给系统【通过Proxy.newProxyInstance创建对象】被代理对象的类加载器, 与该对象所实现的接口与参数,那么在系统中就会生成该被代理对象的代理对象,就可以在原来的基础上进行增强,代理对象可以决定是否以及何时将方法调用到原始对象上。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OT8OAzuT-1617632410955)(C:\Users\罗先生\AppData\Roaming\Typora\typora-user-images\1614945676791.png)]
动态代理的方式:
-
基于接口实现动态代理:JDK动态代理
-
基于继承实现动态代理:Cglib、Javassist动态代理(
CGLib底层采用ASM字节码生成框架,使用字节码技术生成代理类,为一个类创建子类,并在子类中采用方法拦截的技术拦截所有对父类方法的调用,并顺势加入横切逻辑。CGlib是针对类来实现代理的,原理是对指定的业务类生成一个子类,并覆盖其中业务方法实现代理,因为采用的是继承,所以不能对final修饰的类进行代理。CGlib和JDK的原理类似,也是通过方法去反射调用目标对象的方法。
4.2 AOP实现动态代理步骤
实现代码:
-------------------------------被代理类实现的接口-------------------------------
package com.atguigu.annotation.proxy;
public interface Calculator {
int add(int a, int b);
int sub(int a, int b);
int mul(int a, int b);
int div(int a, int b);
}
-------------------------------被代理类-------------------------------
/*
需求:在计算之前和得到结果之后打印日志
*/
public class CalculatorImpl implements Calculator{
@Override
public int add(int a, int b) {
int result = a + b;
return result;
}
}
-------------------------------代理类-------------------------------
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class LoggingProxy {
private Calculator target;
public LoggingProxy(Calculator target) {
this.target = target;
}
public Object getProxy() {
Object proxyInstance = Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("代理开始:" + method.getName() + "。。。");
Object invoke = method.invoke(target, args);
System.out.println("代理结束。。。");
return invoke;
}
});
return proxyInstance;
}
}
-------------------------------测试类-------------------------------
import org.junit.Test;
public class ProxyTest {
//测试动态代理
@Test
public void testProxy(){
Calculator calculator = new CalculatorImpl();
LoggingProxy loggingProxy = new LoggingProxy(calculator);
Calculator proxy = (Calculator) loggingProxy.getProxy();
System.out.println(proxy.add(10, 2));
}
}
-------------------------------配置文件-------------------------------
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns: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 https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 设置扫描路径-->
<context:component-scan base-package="com.atguigu.annotation.aop"></context:component-scan>
<!-- 开启AOP自动代理-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
4.3 AOP术语
-
横切关注点:从每个方法中抽取出来的同一类非核心业务
-
切面:封装横切关注点信息的类,每个关注点体现为一个通知方法
-
通知:切面必须要完成的各个具体工作,增强的方法(共五个通知类型)
@Before:前置通知 、@After:后置通知、@AfterRunning:返回通知、 @AfterThrowing:异常通知、 @Around:环绕通知
-
目标:被通知的对象、被代理的对象
-
代理:代理对象
-
连接点:代理对象与被代理对象有关系的某一部分, 也就是目标对象或者代理对象之中的方法 ,横切关注点在程序代码中的具体位置。
- 基于JDK实现动态代理的话,连接点就可以说是目标对象和代理对象都要实现的接口
- 如果是CgLib实现动态代理,,那么代理对象类就是目标对象类的子类,子类中的所有方法都可以看成是与目标对象类的一个连接点。
-
切入点:定位连接点的方式。
4.4 ★ AspectJ—AOP框架,在Spring2.0以上也可以集成了
4.4.1 使用方法为:@Aspect注解
如何使用:
- 在Spring中声明切面【非主业务需求类】使用@Aspect注解,并且切面也要交给IOC容器管理,所以还要添加@Component注解
- IOC容器初始化后,IOC就会为切面匹配相对应的bean创建代理
- 一个切面中,可能有很多通知, 通知是标注有某种注解的简单的Java方法 。
4.5 ★ AOP使用步骤及测试
使用步骤:
- 导入jar包
- 在Spring核心包的基础上添加以下jar包
- spring-aspects-5.3.1.jar
- com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar
- 想要使用AOP中集成的Aspect【面向切面】需要在xml中配置扫描路径与开启AOP自动代理功能
注:AOP的面向切面编程,是基于动态代理实现的
<?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 https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 设置扫描路径-->
<context:component-scan base-package="com.atguigu.annotation.aop"></context:component-scan>
<!-- 开启AOP自动代理-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
-
创建一个类作为切面
-
在类上添加@Aspect注解将该类标识为切面
-
在类上添加@Component注解
-
将该类交给IOC容器管理被代理类与切面要交予AOP生成。你自己new的被代理对象不能够被ioc生成的切面所代理,即两个类要使用@Component注解,交给ioc,并且要在切面的类签名上使用【@Aspect】注解,通知IOC这是一个切面
-
最后,如果要代理某个方法要在代理方的方法上使用对应的通知注解
package com.atguigu.annotation.aop; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.stereotype.Component; @Aspect @Component public class LoggingAspect { // @Before(value = "execution(public int com.atguigu.annotation.aop.Calculator.add(int,int))") //value后面的表达式为切入点表达式 //如果权限修饰符与返回值类型不需要指定的话,可以使用*代替, //如果被代理的类与切面在同一个包中,可以用普通类名将全限定类名代替 //如果要在被代理的类的所有方法中使用前置通知,那么可以将方法名【add】用 【*】 所代替 //如果代理方法的多个参数的类型,不需要指定的话可以用【..】代替 @Before("execution(* Calculator.*(..))") public void beforeAdvice() { System.out.println("前置通知被执行。。。。"); } }
-
-
在类中添加通知方法
-
测试:
package com.atguigu.annotation.aop; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class AspectTest { @Test public void testAspect(){ ApplicationContext ioc = new ClassPathXmlApplicationContext("beans-aop.xml"); Calculator calculator = (Calculator) ioc.getBean("calculatorImpl"); System.out.println(calculator.add(10, 2)); System.out.println(calculator.div(10, 2)); } }
目标类:
package com.atguigu.annotation.aop;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Repository;
/*
需求:在计算之前和得到结果之后打印日志
*/
@Component
public class CalculatorImpl implements Calculator {
@Override
public int add(int a, int b) {
int result = a + b;
return result;
}
@Override
public int sub(int a, int b) {
int result = a - b;
return result;
}
}
共同接口:
package com.atguigu.annotation.aop;
public interface Calculator {
int add(int a, int b);
int sub(int a, int b);
}
4.6 切入点表达式
@Before(value = “execution(public int com.atguigu.aop.Calculator.add(int,int))”)
这个切入点是指在com.atguigu.aop包中Calculator类的,public类型,返回值是int ,且参数是两个int型数据的add方法。
- before后面括号中的就是切入点表达式,通过表达式可以定位一个或多个具体的连接点。
- 可以使用value定义
- 也可以使用pointcut定义
- value后面的表达式为切入点表达式如果权限修饰符与返回值类型不需要指定的话,可以使用【*】代替,
- execution(* com.atguigu.aop.Calculator.add(int,int))
- 如果被代理的类与切面在同一个包中,可以用普通类名将全限定类名代替
- execution(* Calculator.add(int,int))
- 如果要在被代理的类的所有方法中使用该通知,那么可以将方法名【add】用 【*】 所代替
- execution(* Calculator.*(int,int))
- 如果代理方法的多个参数的类型,不需要指定的话可以用【…】代替
- execution(* Calculator.*(…))
- 代理全部方法
- execution(* *.*(…))
- 切入点表达式中也可以使用”&&“,”||“,”!“等逻辑运算符
4.7 连接点与通知细节
- 连接点:
切入点表达式通常都会是从宏观上定位一组方法,和具体某个通知的注解结合起来就能够确定对应的连接点。那么就一个具体的连接点而言,我们可能会关心这个连接点的一些具体信息,例如:当前连接点所在方法的方法名、当前传入的参数值等等。这些信息都封装在JoinPoint接口的实例对象中。
- 通知细节
所以在每一个通知的方法定义的参数中都会要有一个JoinPoint或者是其一个子类,用来数据传输,每个通知定义的参数如下
-
@Before:前置通知,
- 在方法执行之前执行
- 方法声明参数
- JoinPoint:用来获取代理对象的信息
-
@After:后置通知
- 在方法执行之后执行
- 方法声明参数:
- JoinPoint:用来获取代理对象的信息
-
@AfterReturning:返回通知,需要使用注解的returning属性定义连接点的返回值,该属性的值即为用来传入返回值的参数名称,方法声明参数中的返回值的名称必须与注解所定义的要完全相同
-
无论连接点是否抛出异常,都会在方法返回结果之后执行
-
方法声明参数:
-
JoinPoint:用来获取代理对象的信息
-
Object:声明一个返回结果的类型
-
-
-
@AfterThrowing:异常通知
- 在方法抛出异常之后执行
- 方法声明参数:
- JoinPoint:用来获取代理对象的信息
- Throwable:用来声明一个抛出异常的类型,也可以是【Exception】
-
@Around:环绕通知
- 围绕着方法执行,相当于动态代理的全过程,可以实现上面四个方法的所有功能
- JoinPoint的子类ProceedingJoinPoint:用来获取代理对象的信息
在通知中也可以通过@Order注解,来指定优先级,数字越小优先级越高,默认Integer最大值
-
切入点表达式可以引用
//定义切入点表达式时 @Aspect @Component public class LoggingAspect { @Pointcut("execution(* Calculator.*(..))") public void pointCut() { } } //引用切入点表达式时 @Order(1) @Aspect @Component public class ValidateAspect { @Before("com.atguigu.aop.LoggingAspect.pointCut()") public void beforeAdvice(){ System.out.println("验证参数是否合法。。。"); } }
各种通知代码案例如下:
@Before("execution(* Calculator.*(..))")
public void beforeAdvice(JoinPoint joinPoint) {
Object[] args = joinPoint.getArgs();
String methodName = joinPoint.getSignature().getName();
System.out.println("[logging] The method : " + methodName + " starts with" + Arrays.toString(args));
}
@After(value = "execution(public int com.atguigu.aop.Calculator.*(int,int))")
public void afterAdvice(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
System.out.println("[logging] The method : " + methodName + " ends");
}
@AfterReturning(pointcut = "execution(* Calculator.*(..))", returning = "result")
public void afterReturn(JoinPoint joinPoint, Object result) {
String methodName = joinPoint.getSignature().getName();
System.out.println("[logging] The method : " + methodName + " returns:" + result);
}
@AfterThrowing(value = "execution(* Calculator.*(..))", throwing = "e")
public void afterThrows(JoinPoint joinPoint, Throwable e) {
String methodName = joinPoint.getSignature().getName();
System.out.println("[logging] The method : " + methodName + " occurs:" + e);
}
@Order(value = 1)
@Around("execution(* Calculator.*(..))")
public Object aroundAdvice(ProceedingJoinPoint proceedingJoinPoint) {
String methodName = proceedingJoinPoint.getSignature().getName();
Object[] args = proceedingJoinPoint.getArgs();
Object result = null;
try {
//前置通知
System.out.println("[logging] The method : " + methodName + " starts with" + Arrays.toString(args));
result = proceedingJoinPoint.proceed();
//返回通知
System.out.println("[logging] The method : " + methodName + " returns:" + result);
} catch (Throwable throwable) {
// throwable.printStackTrace();
//异常通知
System.out.println("[logging] The method : " + methodName + " occurs:" + throwable);
} finally {
//后置通知
System.out.println("[logging] The method : " + methodName + " ends");
}
return result;
}
4.8 基于xml实现的AOP
- 创建目标类,并在配置文件中声明
- 创建切面类,用来存放通知,在配置文件中声明
- 配置文件中使用<aop:config>标签配置AOP
- 在其内使用<aop:pointcut>配置切入点表达式
- 切入点必须定义在<aop:aspect>元素下,或者直接定义在<aop:config>元素下
- 定义在<aop:aspect>元素下:只对当前切面有效
- 定义在<aop:config>元素下:对所有切面都有效 ,如下图
- 切入点必须定义在<aop:aspect>元素下,或者直接定义在<aop:config>元素下
- 然后使用<aop:aspect>配置切面,一般都是通过引用
- 使用各种<aop:before>或<aop:after>标签来配置通知
- 用其中的method属性指定对应方法
- 通知元素需要使用来引用切入点,或用直接嵌入切入点表达式
- 使用各种<aop:before>或<aop:after>标签来配置通知
- 在其内使用<aop:pointcut>配置切入点表达式
- 具体配置代码如下
<?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 id="calculator" class="com.atguigu.xml.CalculatorImpl"></bean>
<!-- 配置切面类-->
<bean id="loggingAspect" class="com.atguigu.xml.LoggingAspect"></bean>
<!-- 配置AOP-->
<aop:config>
<!-- 配置切入点表达式-->
<aop:pointcut id="pointCut" expression="execution(* com.atguigu.xml.Calculator.*(..))"/>
<!-- 配置切面-->
<aop:aspect ref="loggingAspect">
<!-- 给切面配置通知方法-->
<!-- 前置通知-->
<!--该通知是前置通知,通知的方法为"beforeAdvice",通过引用的方式,配置切入点表达式为"pointCut" -->
<aop:before method="beforeAdvice" pointcut-ref="pointCut"></aop:before>
<!-- 返回通知-->
<aop:after-returning method="afterReturn" pointcut-ref="pointCut" returning="result"></aop:after-returning>
<!-- 异常通知-->
<aop:after-throwing method="afterThrows" pointcut-ref="pointCut" throwing="e"></aop:after-throwing>
<!-- 后置通知-->
<aop:after method="afterAdvice" pointcut-ref="pointCut"></aop:after>
<!-- 环绕通知-->
<aop:around method="aroundAdvice" pointcut-ref="pointCut"></aop:around>
</aop:aspect>
</aop:config>
</beans>
5. JdbcTemplate
Spring框架在底层也集成了jdbc功能,为不同类型的jdbc操作提供模板方法,可以将Spring的jdbcTemplate看作是一个小型的轻量级持久化层框架。
5.1 使用步骤:
- 导入jar包
- Spring的核心包
- spring-beans-5.3.1.jar
- spring-context-5.3.1.jar
- spring-core-5.3.1.jar
- spring-expression-5.3.1.jar
- spring-jcl-5.3.1.jar
- spring-aop-5.3.1.jar
- JdbcTemplate需要的jar包
- spring-jdbc-5.3.1.jar
- spring-orm-5.3.1.jar
- spring-tx-5.3.1.jar
- 连接数据库的驱动jar包和数据源
- mysql-connector-java-5.1.37-bin.jar
- druid-1.1.9.jar
- 单元测试的jar包
- junit-4.12.jar
- hamcrest-core-1.3.jar
- Spring的核心包
- 创建数据库表
- 创建druid.properties文件
- 创建Spring的配置文件
- 数据库操作
- 获取单一值,单一对象:jdbcTemplate.queryForObject(String var1, RowMapper<T> var2)
- sql语句
- 返回值类型
- 获取多个对象:jdbcTemplate.query(String var1, RowMapper<T> var2)
- sql语句
- 返回值类型
- 批处理:jdbcTemplate.batchUpdate(sql, List<Object[]>);
- sql语句
- list.size为执行次数,其中的Object属性为其赋值内容
- 通用增删改:jdbcTemplate.update(sql,args)
- sql语句
- 填充参数
- 获取单一值,单一对象:jdbcTemplate.queryForObject(String var1, RowMapper<T> var2)
5.2 示例代码
druid.properties文件
#key=value
jdbc.driverClassName=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/test?serverTimezone=UTC&useUnicode=true&characterEncoding=UTF-8&rewriteBatchedStatements=true
#url=jdbc:mysql://localhost:3306/mysqldb
jdbc.username=root
jdbc.password=root
jdbc.initialSize=10
jdbc.minIdle=5
jdbc.maxActive=20
jdbc.maxWait=5000
Spring的配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!-- 设置扫描路径-->
<context:component-scan base-package="com.atguigu.jdbc"></context:component-scan>
<!-- 引入外部文件-->
<context:property-placeholder location="classpath:druid.properties"></context:property-placeholder>
<!-- 配置数据源-->
<bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.username}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="driverClassName" value="${jdbc.driverClassName}"></property>
<property name="initialSize" value="${jdbc.initialSize}"></property>
<property name="maxActive" value="${jdbc.maxActive}"></property>
<property name="minIdle" value="${jdbc.minIdle}"></property>
<property name="maxWait" value="${jdbc.maxWait}"></property>
</bean>
<!-- 配置jdbcTemplate-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="druidDataSource"></property>
</bean>
</beans>
数据库操作
package com.atguigu.jdbc.test;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.pool.DruidPooledConnection;
import com.atguigu.jdbc.bean.Employee;
import com.atguigu.jdbc.bean.User;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
public class JdbcTemplateTest {
//创建IOC容器
ApplicationContext ioc = new ClassPathXmlApplicationContext("beans-jdbc.xml");
JdbcTemplate jdbcTemplate = (JdbcTemplate) ioc.getBean("jdbcTemplate");
/*
通过获取连接,
测试jdbcTemplate是否配置成功
*/
@Test
public void testJdbcTemplate() throws SQLException {
// JdbcTemplate jdbcTemplate = (JdbcTemplate) ioc.getBean("jdbcTemplate");
System.out.println(jdbcTemplate);
DruidDataSource druidDataSource = (DruidDataSource) ioc.getBean("druidDataSource");
DruidPooledConnection connection = druidDataSource.getConnection();
System.out.println(connection);
}
//测试与Mysql数据库连接,并查找对象
@Test
public void testUpdateMySql() {
String sql = "INSERT INTO users (username,password, email) VALUES (?, ?,?);";
jdbcTemplate.update(sql, "张三", "123456", "zhangsan@atguigu.com");
}
//测试jdbcTemplate中的批处理
@Test
public void testBatchUpdate() {
String sql = "INSERT INTO users (username,password, email) VALUES (?, ?,?)";
List<Object[]> list = new ArrayList<>();
list.add(new Object[]{"佟刚", "tonggang123", "tonggang@atguigu.com"});
list.add(new Object[]{"雷军", "leijun123", "leijun@xiaomi.com"});
jdbcTemplate.batchUpdate(sql, list);
}
//获取单一值
@Test
public void testGetOneValue() {
String sql = "select count(1) from users";
Integer integer = jdbcTemplate.queryForObject(sql, Integer.class);
System.out.println(integer);
}
//获取单个对象
//如果结果有多个对象或没有对象均会抛出异常
@Test
public void testGetOneBean() {
String sql = "select * from users where uid = ?";
RowMapper<User> rowMapper = new BeanPropertyRowMapper<>(User.class);
User user = jdbcTemplate.queryForObject(sql, rowMapper, 6);
System.out.println(user);
}
//获取一个对象
@Test
public void testGetOneEmployee() {
String sql = "select * from employee where id = ?";
RowMapper<Employee> rowMapper = new BeanPropertyRowMapper<>(Employee.class);
Employee employee = jdbcTemplate.queryForObject(sql, rowMapper, 1);
System.out.println(employee);
}
//获取多个对象
@Test
public void testGetAll() {
String sql = "select id,last_name,email,salary from employee where id = ?";
List<Employee> employee = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(Employee.class), 1);
System.out.println(employee);
}
}
5.3 通过注入JdbcTemplate实现Dao
JdbcTemplate类是线程安全的,所以可以在IOC容器中声明它的单个实例,并将这个实例注入到所有的Dao实例中。
使用步骤:
-
在spring配置文件中,添加自动扫描配置的包
<!-- 设置扫描路径--> <context:component-scan base-package="com.atguigu.jdbc"></context:component-scan>
-
创建Dao及实现类,并且注入JdbcTemplate
@Repository public class EmployeeDaoImpl implements EmployeeDao { @Autowired private JdbcTemplate jdbcTemplate; @Override public List<Employee> getAll() { String sql = "select id,last_name,email,salary from employee "; List<Employee> employees = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(Employee.class)); return employees; }
-
测试即可
ApplicationContext ioc = new ClassPathXmlApplicationContext("beans-jdbc.xml"); @Test public void testEmployeeDao() { EmployeeDao employeeDao = (EmployeeDao) ioc.getBean("employeeDaoImpl"); List<Employee> employees = employeeDao.getAll(); for (Employee employee : employees) { System.out.println(employee); }
6. 事务
什么是事务?
事务是数据库操作的最小工作单元,是作为单个逻辑工作单元执行的一系列操作;这些操作作为一个整体一起向系统提交,要么都执行、要么都不执行;事务是一组不可再分割的操作集合(工作逻辑单元);
6.1 事务的四大特性:
- 原子性:每个事务为一个最小单位,理论上不能够再次分割
- 一致性:满足业务规则的一致性状态,要么统一成功,要么统一失败
- 隔离性:多个事务在并发过程中不会互相干扰
- 持久性:事务只要执行完成就会永久保存下载,写入到持久化存储器中
原生的jdbc事务,是嵌入到业务方法中的非核心业务代码,如果每个方法中都需要开启事务的话,那么就会造成大量的代码冗余,同时需要关注的点太多,也不利于后期的维护。
声明式事务管理:当把事务作为横切关注点,就可以通过Spring 的AOP框架将其冲核心代码中抽离,形成切面。在切面中进行事务的管理。
Spring为其配置了一个抽象层,通过配置方式使其生效,从而减少开发人员对事务管理细节的关注
6.2 事务管理器
核心事务管理是 【PlatformTransactionManager 】接口,我们主要使用的实现类为
- DataSourceTransactionManager:使用JDBC存取,且应用程序中只需要处理一个数据源
org.springframework.jdbc.datasource包下的 - HibernateTransactionManager:用Hibernate框架存取数据库
6.3 基于注解使用
使用步骤:
-
导入AOP相关jar包
-
在spring配置文件中配置事务
- 如果配置事务的id为transactionManager时,在开启事务时,可以不用transaction-manager该属性指定
<!-- 配置事务--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="druidDataSource3"></property> </bean> <!--设置开启事务,如果配置事务的id为 transactionManager时,在开启事务时,可以不用transaction-manager该属性指定--> <tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
-
并且在需要添加事务的方法上添加注解@Transactional设置事务
-
该注解可以加载类上,也可以加在方法上
- 加在类上,表示类中的所有属性都会开启事务
- 加在方法上,表示当前方法开启事务
-
注解属性propagation设置传播性
- REQUIRED:
- 有事务运行,就在当前事务内运行(有事务运行,自己就是整体的一部分)
- 没有事务运行,就启动一个新的事物,并且在自己的事务内运行
- REQUIRES_NEW :
- 当前的方法必须启动新事务,在自己事务内运行
- 如过有事务在运行,就挂起,不管它
- 不管有没有事务,自己都是一个小整体,不受其他事务影响
- REQUIRED:
-
注解属性isolation设置事务隔离级别
- MySql:
- 读未提交: READ UNCOMMITTED :可以读到在其他事务中并未提交的数据
- 读已提交: READ COMMITTED :只能读到别人提交之后的数据
- 可重复读【默认】: REPEATABLE READ :读取期间禁止其他事务对该字段更新
- 串行化: SERIALIZABLE :如果当前事务未提交,其他事务只能等待
- Oracle:
- 读已提交【默认】:
- 串行化:
- MySql:
-
注解属性timeout设置超时时间(单位为S),超时自动回滚,避免线程占用
-
注解属性readonly,设置是否只读(如果当前方法只是查询,没有对数据进行更新【增删改】操作,可以指定值为true)
事务隔离级别 脏读 不可重复读 幻读 READ UNCOMMITTED 有 有 有 READ COMMITTED 无 有 有 REPEATABLE READ 无 无 有 SERIALIZABLE 无 无 无
-
实例代码【基础接口并没有写】:
spring配置文件
<?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 https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
<!-- 配置扫描路径-->
<context:component-scan base-package="com.atguigu.spring.tx"></context:component-scan>
<!--配置外部加载文件资源-->
<context:property-placeholder location="classpath:druid-affairs.properties"></context:property-placeholder>
<!--配置数据连接池-->
<bean id="druidDataSource3" class="com.alibaba.druid.pool.DruidDataSource">
<property name="url" value="${jdbc.url}"></property>
<property name="driverClassName" value="${jdbc.driverClassName}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
<property name="initialSize" value="${jdbc.initialSize}"></property>
<property name="minIdle" value="${jdbc.minIdle}"></property>
<property name="maxActive" value="${jdbc.maxActive}"></property>
<property name="maxWait" value="${jdbc.maxWait}"></property>
</bean>
<!--配置jdbcTemplate-->
<bean id="jdbcTemplate3" class="org.springframework.jdbc.core.JdbcTemplate">
<!-- 配置数据源-->
<property name="dataSource" ref="druidDataSource3"></property>
</bean>
<!-- 配置事务-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="druidDataSource3"></property>
</bean>
<!--设置开启事务,如果配置事务的id为transactionManager时,在开启事务时,可以不用transaction-manager该属性指定-->
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
</beans>
Service层
package com.atguigu.spring.tx.service.impl;
import com.atguigu.spring.tx.dao.BookShopDao;
import com.atguigu.spring.tx.service.BookShopService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Service("bookShopService")
public class BookShopServiceImpl implements BookShopService {
@Autowired
private BookShopDao bookShopDao ;
@Transactional(propagation = Propagation.REQUIRES_NEW,isolation = Isolation.READ_COMMITTED,timeout = 5,noRollbackFor = ArithmeticException.class,readOnly = false)
@Override
public void purchase(int userId, String isbn) {
Double bookPrice = bookShopDao.getBookPrice(isbn);
bookShopDao.updateBookStock(isbn);
bookShopDao.updateUserBalance(userId,bookPrice);
}
}
Dao层
package com.atguigu.spring.tx.dao.impl;
import com.atguigu.spring.tx.dao.BookShopDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
@Repository("bookShopDao")
public class BookShopDaoImpl implements BookShopDao {
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public Double getBookPrice(String isbn) {
String sql = " select price from book where isbn = ?";
Double bookPrice = jdbcTemplate.queryForObject(sql, Double.class, isbn);
return bookPrice;
}
@Override
public void updateBookStock(String isbn) {
String sql = " update book set stock = stock -1 where isbn = ?";
jdbcTemplate.update(sql, isbn);
}
@Override
public void updateUserBalance(int userId, Double bookPrice) {
String sql = " update account set balance = balance - ? where id = ?";
jdbcTemplate.update(sql, bookPrice, userId);
}
}
6.4 基于配置文件使用
当基于配置文件使用事务时,所有的注解都可以去掉
实例代码如下【依旧不写接口】:
BookDaoImpl
package com.atguigu.spring.xml.dao;
import org.springframework.jdbc.core.JdbcTemplate;
public class BookShopDaoImpl implements BookShopDao {
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
private JdbcTemplate jdbcTemplate;
@Override
public Double getBookPrice(String isbn) {
String sql = " select price from book where isbn = ?";
Double bookPrice = jdbcTemplate.queryForObject(sql, Double.class, isbn);
return bookPrice;
}
@Override
public void updateBookStock(String isbn) {
String sql = " update book set stock = stock -1 where isbn = ?";
jdbcTemplate.update(sql, isbn);
}
@Override
public void updateUserBalance(int userId, Double bookPrice) {
String sql = " update account set balance = balance - ? where id = ?";
jdbcTemplate.update(sql, bookPrice, userId);
}
}
BookService
package com.atguigu.spring.xml.service;
import com.atguigu.spring.xml.dao.BookShopDao;
public class BookShopServiceImpl implements BookShopService {
public void setBookShopDao(BookShopDao bookShopDao) {
this.bookShopDao = bookShopDao;
}
private BookShopDao bookShopDao;
@Override
public void purchase(int userId, String isbn) {
Double bookPrice = bookShopDao.getBookPrice(isbn);
bookShopDao.updateBookStock(isbn);
bookShopDao.updateUserBalance(userId, bookPrice);
}
}
配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
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 https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
设置加载外部文件
<context:property-placeholder location="classpath:druid-affairs.properties"></context:property-placeholder>
配置德鲁伊的数据连接池
<bean id="druidDataSource9" class="com.alibaba.druid.pool.DruidDataSource">
<property name="url" value="${jdbc.url}"></property>
<property name="driverClassName" value="${jdbc.driverClassName}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
<property name="initialSize" value="${jdbc.initialSize}"></property>
<property name="minIdle" value="${jdbc.minIdle}"></property>
<property name="maxActive" value="${jdbc.maxActive}"></property>
<property name="maxWait" value="${jdbc.maxWait}"></property>
</bean>
配置spring内置的jdbcTemplate
<bean id="jdbcTemplate9" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="druidDataSource9"></property>
</bean>
配置bookShopDao
<bean id="bookShopDao9" class="com.atguigu.spring.xml.dao.BookShopDaoImpl">
<property name="jdbcTemplate" ref="jdbcTemplate9"></property>
</bean>
配置bookShopService
<bean id="bookShopService9" class="com.atguigu.spring.xml.service.BookShopServiceImpl">
<property name="bookShopDao" ref="bookShopDao9"></property>
</bean>
配置事务管理器-->JDBC使用DataSourceTransactionManager实现类
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="druidDataSource9"></property>
</bean>
<!-- Spring的声明式事务-->
<tx:advice id="tx" transaction-manager="transactionManager">
<!-- 配置添加事务的方法-->
<tx:attributes>
<!-- 给purchase方法添加事务,并且设置传播性,与隔离性等-->
<tx:method name="purchase" propagation="REQUIRED" isolation="READ_COMMITTED" timeout="3"/>
</tx:attributes>
</tx:advice>
<!-- 配置AOP-->
<aop:config>
配置切入点表达式
<aop:pointcut id="pointCut"
expression="execution( * com.atguigu.spring.xml.service.BookShopServiceImpl.purchase(..))"/>
<!-- 将切入点表达式与通知方法关联起来-->
<aop:advisor advice-ref="tx" pointcut-ref="pointCut"></aop:advisor>
</aop:config>
</beans>
测试代码:
public class TestBookShopService {
private ApplicationContext ioc = new ClassPathXmlApplicationContext("beans-xml.xml");
@Test
public void testBookShopService(){
BookShopService bookShopService2 = (BookShopService) ioc.getBean("bookShopService9");
bookShopService2.purchase(1,"1001");
}
}
Spring集成Junit4
- 导入jar包:spring-test-5.3.1.jar
- 到测试类添加注解:@RunWith
- @ContextConfiguration通过配置其属性,来确定是基于配置文件还是基于配置类
//@ContextConfiguration(locations = "classpath:beans-annotation.xml")//基于配置文件的设置
@ContextConfiguration(classes = Annotation.class)//基于配置类的设置
@RunWith(SpringJUnit4ClassRunner.class)
public class AnnotationBuiltIn {
@Autowired
private User user ;
/*
使用spring内置的junit4测试
*/
@Test
public void testSpringJunit(){
System.out.println(user);
}
}
7. Spring5型特性
- 基于Java8 ,兼容JDK9,删除许多类和方法
- 自带通用的日志封装
- 支持@Nullable注解
- Spring支持整合JUint5(之前一般用Junit4)
7.1 通用日志封装
使用步骤:
- 导入jar包
- log4j-api-2.11.2.jar
- log4j-core-2.11.2.jar
- log4j-slf4j-impl-2.11.2.jar
- slf4j-api-1.7.30.jar
- 创建log4j2.xml配置文件
<?xml version="1.0" encoding="UTF-8"?>
<!--日志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL -->
<!--Configuration后面的status用于设置log4j2自身内部的信息输出,可以不设置,当设置成trace时,可以看到log4j2内部各种详细输出-->
<configuration status="INFO">
<!--先定义所有的appender-->
<appenders>
<!--输出日志信息到控制台-->
<console name="Console" target="SYSTEM_OUT">
<!--控制日志输出的格式-->
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</console>
</appenders>
<!--然后定义logger,只有定义了logger并引入的appender,appender才会生效-->
<!--root:用于指定项目的根日志,如果没有单独指定Logger,则会使用root作为默认的日志输出-->
<loggers>
<root level="DEBUG">
<appender-ref ref="Console"/>
</root>
</loggers>
</configuration>
7.2支持@Nullable注解
当使用@Nullable注解时,代表当前位置的数据可以为空
使用位置
- 注解在方法上:方法返回值可以为空
- 注解在属性上:属性可以为空
- 注解在参数前:参数可以为空
7.3 整合Junit5
使用步骤:
- 导入jar包
- Spring5的测试包
- spring-test-5.3.1.jar
- 导入Junit5相关jar包
- apiguardian-api-1.1.0.jar
- junit-jupiter-api-5.7.0.jar
- junit-platform-commons-1.7.0.jar
- opentest4j-1.2.0.jar
- Spring5的测试包
- 给相应的测试类添加注解【一下两种方法均可】
- 添加单个注解:
- SpringJUintConfig
- 添加两个注解:
- @ExtendWith:
- @ContextConfiguration:可以通过其属性标签来引入配置文件(可以参考junit4)
- locations://基于配置文件的设置
- classes://基于配置类的设置
- 添加单个注解:
单个注解案例:
import com.atguigu.spring.tx.dao.BookShopDao;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
@SpringJUnitConfig(locations = "classpath:beans-tx.xml")
public class Junit5Test {
@Autowired
private BookShopDao bookShopDao;
/*
测试Spring整合Junit5
*/
@Test
public void testJunit5(){
System.out.println(bookShopDao);
}
}
两个注解案例
import com.atguigu.spring.tx.dao.BookShopDao;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
@ExtendWith(SpringExtension.class)
@ContextConfiguration(locations = "classpath:beans-tx.xml")
public class Junit5Test {
@Autowired
private BookShopDao bookShopDao;
/*
测试Spring整合Junit5
*/
@Test
public void testJunit5(){
System.out.println(bookShopDao);
}
}
7.3 整合Junit5
使用步骤:
- 导入jar包
- Spring5的测试包
- spring-test-5.3.1.jar
- 导入Junit5相关jar包
- apiguardian-api-1.1.0.jar
- junit-jupiter-api-5.7.0.jar
- junit-platform-commons-1.7.0.jar
- opentest4j-1.2.0.jar
- Spring5的测试包
- 给相应的测试类添加注解【一下两种方法均可】
- 添加单个注解:
- SpringJUintConfig
- 添加两个注解:
- @ExtendWith:
- @ContextConfiguration:可以通过其属性标签来引入配置文件(可以参考junit4)
- locations://基于配置文件的设置
- classes://基于配置类的设置
- 添加单个注解:
单个注解案例:
import com.atguigu.spring.tx.dao.BookShopDao;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
@SpringJUnitConfig(locations = "classpath:beans-tx.xml")
public class Junit5Test {
@Autowired
private BookShopDao bookShopDao;
/*
测试Spring整合Junit5
*/
@Test
public void testJunit5(){
System.out.println(bookShopDao);
}
}
两个注解案例
import com.atguigu.spring.tx.dao.BookShopDao;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
@ExtendWith(SpringExtension.class)
@ContextConfiguration(locations = "classpath:beans-tx.xml")
public class Junit5Test {
@Autowired
private BookShopDao bookShopDao;
/*
测试Spring整合Junit5
*/
@Test
public void testJunit5(){
System.out.println(bookShopDao);
}
}