- Spring
- 1. Spring 框架概述
- 2. IOC 容器
- 3. Aop
- 4. JdbcTemplate
- 5. 事务管理
- 6. Spring5 新特性
- 7. Spring5 框架新功能(Webflux)
Spring
1. Spring 框架概述
1.1. Spring介绍
- Spring 是轻量级的开源的 JavaEE 框架
- Spring 可以解决企业应用开发的复杂性
- Spring 有两个核心部分:IOC 和 Aop
- IOC:控制反转,把创建对象过程交给 Spring 进行管理
- Aop:面向切面,不修改源代码进行功能增强
- Spring 特点
- 方便解耦,简化开发
- Aop 编程支持
- 方便程序测试
- 方便和其他框架进行整合
- 方便进行事务操作
- 降低 API 开发难度
1.2. Spring入门案例
1.2.1. 下载Spring5
- 使用 Spring 最新稳定版本 5.2.6
- 下载地址
https://repo.spring.io/release/org/springframework/spring/
1.2.2. 打开 idea 工具,创建普通 Java 工程
1.2.3. 导入Spring5相关jar包
- 需要的jar包


- 在java工程中创建lib目录,导入以上jar包,再把jar包添加到当前模块中
1.2.4. 创建普通类,在这个类创建普通方法
public class User {public void add() {System.out.println("add......");}}
1.2.5. 创建 Spring 配置文件,在配置文件配置创建的对象
- Spring 配置文件使用 xml 格式

<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd"><!--配置 User 对象创建--><bean id="user" class="com.atguigu.spring5.User"></bean></beans>
1.2.6. 进行测试代码编写
@Test
public void testAdd() {
//1 加载 spring 配置文件
ApplicationContext context =
new ClassPathXmlApplicationContext("bean1.xml");
//2 获取配置创建的对象
User user = context.getBean("user", User.class);
System.out.println(user);
user.add();
}
2. IOC 容器
2.1. IOC(概念和底层原理)
2.1.1. 什么是 IOC
- 控制反转,把对象创建和对象之间的调用过程,交给 Spring 进行管理。在使用 Spring 之前,对象的创建都是由我们使用new关键字创建的,而使用 Spring 之后,对象的创建都交给了 Spring 框架。Spring 通过 IoC 容器来管理对象的实例化和初始化,以及对象从创建到销毁的整个生命周期。Spring 中使用的对象都由 IoC 容器管理,不需要我们手动使用 new 运算符创建对象。由 IoC 容器管理的对象称为 Spring Bean,Spring Bean 就是 Java 对象,和使用 new 运算符创建的对象没有区别。
- 使用 IOC 目的:为了耦合度降低
2.1.2. IOC底层原理
xml 解析、工厂模式、反射
2.1.3. 画图讲解 IOC 底层原理


2.2. IOC接口
- IOC 思想基于 IOC 容器完成,IOC 容器底层就是对象工厂
- Spring 提供 IOC 容器实现两种方式:(两个接口)
- BeanFactory:IOC 容器基本实现,是 Spring 内部的使用接口,不提供开发人员进行使用 。加载配置文件时候不会创建对象,在获取对象(使用)才去创建对象,采用懒加载(lazy-load),所以容器启动比较快
- ApplicationContext:BeanFactory 接口的子接口,提供更多更强大的功能,一般由开发人 员进行使用。加载配置文件时候就会把在配置文件对象进行创建
ApplicationContext 接口有两个常用的实现类:1、ClassPathXmlApplicationContext 2、FileSystemXmlApplicationContext

FileSystemXmlApplicationContext从指定的文件系统路径中寻找指定的 XML 配置文件,并完成 ApplicationContext 的实例化工作。而ClassPathXmlApplicationContext从类路径 ClassPath 中寻找指定的 XML 配置文件,并完成 ApplicationContext 的实例化工作。
通常在 Java 项目中,会采用 ClassPathXmlApplicationContext 类实例化 ApplicationContext 容器的方式,而在 Web 项目中,ApplicationContext 容器的实例化工作会交由 Web 服务器完成。Web 服务器实例化 ApplicationContext 容器通常使用基于 ContextLoaderListener 实现的方式,它只需要在 web.xml 中添加如下代码:<!--指定Spring配置文件的位置,有多个配置文件时,以逗号分隔--> <context-param> <param-name>contextConfigLocation</param-name> <!--spring将加载spring目录下的applicationContext.xml文件--> <param-value> classpath:spring/applicationContext.xml </param-value> </context-param> <!--指定以ContextLoaderListener方式启动Spring容器--> <listener> <listener-class> org.springframework.web.context.ContextLoaderListener </listener-class> </listener>BeanFactory接口实现类
2.3. IOC操作Bean管理
Bean 管理指的是两个操作:
- Spring创建对象
- Spring注入属性
Bean 管理操作有两种方式
- 基于 xml 配置文件方式实现
- 基于注解方式实现
控制反转IOC和依赖注入DI的区别?
IOC: 控制反转,将类的对象的创建交给Spring类管理创建.
DI: 依赖注入,将类里面的属性在创建类的对象过程中给属性赋值.
DI和IOC的关系: DI不能单独存在,DI需要在IOC的基础上来完成.
2.4. IOC操作 Bean管理(基于 xml 方式)
2.4.1. 基于xml方式创建对象

- 在 spring 配置文件中,使用 bean 标签,标签里面添加对应属性,就可以实现对象创建
- 在 bean 标签有很多属性,介绍常用的属性
- id 属性:唯一标识
- class 属性:该属性指定了 Bean 的具体实现类,它必须是一个完整的类名,即类的全限定名。
- 创建对象时候,默认也是执行无参数构造方法完成对象创建
2.4.2. 基于xml方式注入属性
DI:依赖注入,就是注入属性
- 第一种注入方式:使用 set 方法进行注入
- 创建类,定义属性和对应的 set 方法
使用set方法注入要求Bean对应的类必须满足以下两点 ```java /**
- 创建类,定义属性和对应的 set 方法
演示使用 set 方法进行注入属性 */ public class Book { //创建属性 private String bname; private String bauthor; //创建属性对应的 set 方法 public void setBname(String bname) {
this.bname = bname;} public void setBauthor(String bauthor) {
this.bauthor = bauthor;} }
- 必须提供一个默认的无参构造方法 - 必须为需要注入的属性提供对应的 setter 方法 2. 在 spring 配置文件配置对象创建,配置属性注入 ```xml <!--2 set 方法注入属性--> <bean id="book" class="com.atguigu.spring5.Book"> <!--使用 property 完成属性注入 name:类里面属性名称 value:向属性注入的值 --> <property name="bname" value="易筋经"></property> <property name="bauthor" value="达摩老祖"></property> </bean>
- 第二种注入方式:使用有参数构造进行注入
- 创建类,定义属性,创建属性对应有参数构造方法
```java /**
- 创建类,定义属性,创建属性对应有参数构造方法
使用有参数构造注入 */ public class Orders { //属性 private String oname; private String address; //有参数构造 public Orders(String oname,String address) {
this.oname = oname; this.address = address;} }
2. 在 spring 配置文件中进行配置 ```xml <!--3 有参数构造注入属性--> <bean id="orders" class="com.atguigu.spring5.Orders"> <constructor-arg name="oname" value="电脑"></constructor-arg> <constructor-arg name="address" value="China"></constructor-arg> </bean>
- p名称空间注入(了解)
使用 p 名称空间注入,可以简化基于 xml 配置方式- 第一步 添加 p 名称空间在配置文件中
- 第二步 进行属性注入,在 bean 标签里面进行操作
<!--2 set 方法注入属性--> <bean id="book" class="com.atguigu.spring5.Book" p:bname="九阳神功" p:bauthor="无名氏"></bean>
- 第一步 添加 p 名称空间在配置文件中
2.5. IOC 操作 Bean 管理(xml 注入其他类型属性)
2.5.1. 字面量
null 值
<!--null 值--> <property name="address"> <null/> </property>属性值包含特殊符号
<!--属性值包含特殊符号 方法1 把<>进行转义 < > 方法2 把带特殊符号内容写到 CDATA --> <property name="address"> <value><![CDATA[<<南京>>]]></value> </property>
2.5.2. 注入属性—外部bean
步骤:
- 创建两个类 service 类和 dao 类
- 在 service 调用 dao 里面的方法
- 在 spring 配置文件中进行配置
UserService.java
public class UserService {
//创建 UserDao 类型属性,生成 set 方法
private UserDao userDao;
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
public void add() {
System.out.println("service add...............");
userDao.update();
}
}
UserDao.java
public class UserDaoImpl implements UserDao {
@Override
public void update() {
System.out.println("dao update...........");
}
}
bean.xml
<!--1 service 和 dao 对象创建-->
<bean id="userService" class="com.atguigu.spring5.service.UserService">
<!--注入 userDao 对象
name 属性:类里面属性名称
ref 属性:创建 userDao 对象 bean 标签 id 值
-->
<property name="userDao" ref="userDaoImpl"></property>
</bean>
<bean id="userDaoImpl" class="com.atguigu.spring5.dao.UserDaoImpl"></bean>
2.5.3. 注入属性—内部bean
(1) 一对多关系:部门和员工 一个部门有多个员工,一个员工属于一个部门 部门是一,员工是多。
(2) 在实体类之间表示一对多关系,员工表示所属部门,使用对象类型属性进行表示
Dept.java
//部门类
public class Dept {
private String dname;
public void setDname(String dname) {
this.dname = dname;
}
}
Emp.java
//员工类
public class Emp {
private String ename;
private String gender;
//员工属于某一个部门,使用对象形式表示
private Dept 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;
}
}
bean.xml
<!--内部 bean-->
<bean id="emp" class="com.atguigu.spring5.bean.Emp">
<!--设置两个普通属性-->
<property name="ename" value="lucy"></property>
<property name="gender" value="女"></property>
<!--设置对象类型属性-->
<property name="dept">
<bean id="dept" class="com.atguigu.spring5.bean.Dept">
<property name="dname" value="安保部"></property>
</bean>
</property>
</bean>
2.5.4. 注入属性—级联赋值
- 第一种写法
<!--级联赋值-->
<bean id="emp" class="com.atguigu.spring5.bean.Emp">
<!--设置两个普通属性-->
<property name="ename" value="lucy"></property>
<property name="gender" value="女"></property>
<!--级联赋值-->
<property name="dept" ref="dept"></property>
</bean>
<bean id="dept" class="com.atguigu.spring5.bean.Dept">
<property name="dname" value="财务部"></property>
</bean>
- 第二种写法
给Emp.java加上Dept的get方法

<!--级联赋值-->
<bean id="emp" class="com.atguigu.spring5.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="com.atguigu.spring5.bean.Dept">
<!-- <property name="dname" value="财务部"></property> -->
</bean>
2.6. IOC操作Bean管理(xml注入集合属性)
- 创建类,定义数组、list、map、set、Properties类型属性,生成对应 set 方法
public class Stu {
//1 数组类型属性
private String[] courses;
//2 list 集合类型属性
private List<String> list;
//3 map 集合类型属性
private Map<String,String> maps;
//4 set 集合类型属性
private Set<String> sets;
//5 properties
private Properties prop;
public void setSets(Set<String> sets) {
this.sets = sets;
}
public void setCourses(String[] courses) {
this.courses = courses;
}
public void setList(List<String> list) {
this.list = list;
}
public void setMaps(Map<String, String> maps) {
this.maps = maps;
}
public void setProp(Properties prop) {
this.prop = prop;
}
}
- 在 spring 配置文件进行配置
<!--1 集合类型属性注入-->
<bean id="stu" class="com.atguigu.spring5.collectiontype.Stu">
<!--数组类型属性注入-->
<property name="courses">
<array>
<value>java 课程</value>
<value>数据库课程</value>
</array>
</property>
<!--list 类型属性注入-->
<property name="list">
<list>
<value>张三</value>
<value>小三</value>
</list>
</property>
<!--map 类型属性注入-->
<property name="maps">
<map>
<entry key="JAVA" value="java"></entry>
<entry key="PHP" value="php"></entry>
</map>
</property>
<!--set 类型属性注入-->
<property name="sets">
<set>
<value>MySQL</value>
<value>Redis</value>
</set>
</property>
<!-- Properties类型属性注入 -->
<property name="prop">
<props>
<prop key="hobby1">Java</prop>
<prop key="hobby2">Go</prop>
<prop key="hobby3">Python</prop>
</props>
</property>
</bean>
- 在集合里面设置对象类型值
bean.xml
<!--1 集合类型属性注入-->
<bean id="stu" class="com.atguigu.spring5.collectiontype.Stu">
<!--数组类型属性注入-->
<property name="courses">
<array>
<value>java课程</value>
<value>数据库课程</value>
</array>
</property>
<!--list类型属性注入-->
<property name="list">
<list>
<value>张三</value>
<value>小三</value>
</list>
</property>
<!--map类型属性注入-->
<property name="maps">
<map>
<entry key="JAVA" value="java"></entry>
<entry key="PHP" value="php"></entry>
</map>
</property>
<!--set类型属性注入-->
<property name="sets">
<set>
<value>MySQL</value>
<value>Redis</value>
</set>
</property>
<!--注入list集合类型,值是对象-->
<property name="courseList">
<list>
<ref bean="course1"></ref>
<ref bean="course2"></ref>
</list>
</property>
</bean>
<!--创建多个course对象-->
<bean id="course1" class="com.atguigu.spring5.collectiontype.Course">
<property name="cname" value="Spring5框架"></property>
</bean>
<bean id="course2" class="com.atguigu.spring5.collectiontype.Course">
<property name="cname" value="MyBatis框架"></property>
</bean>
- 把集合注入部分提取出来
- 在 spring 配置文件中引入名称空间 util
<?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"
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">
- 使用 util 标签完成 list 集合注入提取
<!--1 提取 list 集合类型属性注入-->
<util:list id="bookList">
<value>易筋经</value>
<value>九阴真经</value>
<value>九阳神功</value>
</util:list>
<!--2 提取 list 集合类型属性注入使用-->
<bean id="book" class="com.atguigu.spring5.collectiontype.Book">
<property name="list" ref="bookList"></property>
</bean>
2.7. IOC操作Bean管理(FactoryBean)
Spring 有两种类型 bean,一种普通 bean,另外一种工厂 bean(FactoryBean)
普通 bean:在配置文件中定义 bean 类型就是返回类型
工厂 bean:在配置文件定义 bean 类型可以和返回类型不一样
- 第一步 创建类,让这个类作为工厂 bean,实现接口 FactoryBean
- 第二步 实现接口里面的方法,在实现的方法中定义返回的 bean 类型
MyBean.java
public class MyBean implements FactoryBean<Course> {
//定义返回 bean
@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;
}
}
bean.xml
<bean id="myBean" class="com.atguigu.spring5.factorybean.MyBean"></bean>
测试类
@Test
public void test3() {
ApplicationContext context =
new ClassPathXmlApplicationContext("bean3.xml");
Course course = context.getBean("myBean", Course.class);
System.out.println(course);
}
注意:这里的getBean方法中的第二个参数应该填返回值类型(即实现的接口中返回的类型)。而不是填在bean.xml中定义的类型
2.8. IOC操作Bean管理(bean作用域)
在 Spring 里面,设置创建 bean 实例是单实例还是多实例就是设置bean的作用域
在 Spring 里面,默认情况下,bean 是单实例对象

如何设置单实例还是多实例:
在 spring 配置文件 bean 标签里面有属性(scope)用于设置单实例还是多实例
scope 属性值:
- singleton:默认值,单例模式,表示在 Spring 容器中只有一个 Bean 实例,Bean 以单例的方式存在。
- prototype:原型模式,表示每次通过 Spring 容器获取 Bean 时,容器都会创建一个 新的Bean 实例。
- request:每次 HTTP 请求,容器都会创建一个 Bean 实例。该作用域只在当前 HTTP Request 内有效。
- session:同一个 HTTP Session 共享一个 Bean 实例,不同的 Session 使用不同的 Bean 实例。该作用域仅在当前 HTTP Session 内有效。
application:同一个 Web 应用共享一个 Bean 实例,该作用域在当前 ServletContext 内有效。
类似于 singleton,不同的是,singleton 表示每个 IoC 容器中仅有一个 Bean 实例,而同一个 Web 应用中可能会有多个 IoC 容器,但一个 Web 应用只会有一个 ServletContext,也可以说 application 才是 Web 应用中货真价实的单例模式。
websocket:websocket 的作用域是 WebSocket ,即在整个 WebSocket 中有效。
注意:Spring 5 版本之前还支持 global Session,该值表示在一个全局的 HTTP Session 中,容器会返回该 Bean 的同一个实例。一般用于 Portlet 应用环境。Spring 5.2.0 版本中已经将该值移除了。
request、session、application、websocket 和 global Session 作用域只能在 Web 环境下使用,如果使用ClassPathXmlApplicationContext 加载这些作用域中的任意一个的 Bean,就会抛出以下异常。
java.lang.IllegalStateException: No Scope registered for scope name 'xxx'
示例:

singleton 和 prototype 区别
- 第一: singleton 单实例,prototype 多实例
- 第二:设置 scope 值是 singleton 时候,加载 spring 配置文件时候就会创建单实例对象 ,设置 scope 值是 prototype 时候,不是在加载 spring 配置文件时候创建对象,在调用 getBean 方法时候创建多实例对象。
2.9. IOC操作Bean管理(bean生命周期)
生命周期:从对象创建到对象销毁的过程。
bean生命周期:
- 第一步:通过构造器创建 bean 实例(无参数构造)
- 第二步:为 bean 的属性设置值和对其他 bean 引用(调用 set 方法)
- 第三步:调用 bean 的初始化的方法(需要进行配置初始化的方法)
- 第四步:bean 可以使用了(对象获取到了)
- 第五步:当容器关闭时候,调用 bean 的销毁的方法(需要进行配置销毁的方法)
演示 bean 生命周期:
Orders.java
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("第五步 执行销毁的方法");
}
}
bean.xml
<bean id="orders" class="com.atguigu.spring5.bean.Orders" init-method="initMethod" destroy-method="destroyMethod">
<property name="oname" value="手机"></property>
</bean>
测试类:
@Test
public void testBean3() {
// ApplicationContext context =
// new ClassPathXmlApplicationContext("bean4.xml");
ClassPathXmlApplicationContext context =
new ClassPathXmlApplicationContext("bean4.xml");
Orders orders = context.getBean("orders", Orders.class);
System.out.println("第四步 获取创建 bean 实例对象");
System.out.println(orders);
//手动让 bean 实例销毁
context.close();
}
执行结果:

bean的后置处理器:加上bean的后置处理器,bean的生命周期总共有七步。
- 第一步:通过构造器创建 bean 实例(无参数构造)
- 第二步:为 bean 的属性设置值和对其他 bean 引用(调用 set 方法)
- 第三步:把 bean 实例传递 bean 后置处理器的方法 postProcessBeforeInitialization
- 第四步:调用 bean 的初始化的方法(需要进行配置初始化的方法)
- 第五步:把 bean 实例传递 bean 后置处理器的方法 postProcessAfterInitialization
- 第六步:bean 可以使用了(对象获取到了)
- 第七步:当容器关闭时候,调用 bean 的销毁的方法(需要进行配置销毁的方法)
演示添加后置处理器效果:
- 创建类,实现接口 BeanPostProcessor,创建后置处理器
public class MyBeanPost implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
System.out.println("在初始化之前执行的方法");
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName)
throws BeansException {
System.out.println("在初始化之后执行的方法");
return bean;
}
}
- 在bean.xml配置文件中配置后置处理器
<bean id="myBeanPost" class="com.atguigu.spring5.bean.MyBeanPost"></bean>
在加载配置文件的时候,会把bean创建,并且把后置处理器一并创建。因为MyBeanPost实现了BeanPostProcessor接口,所以Spring会把MyBeanPost作为后置处理器执行,而后置处理器会对当前配置文件中的所有bean都添加这个后置处理器的处理。
执行效果:

2.10. IOC操作Bean管理(xml 自动装配)
什么是自动装配?
根据指定装配规则(属性名称或者属性类型),Spring 自动将匹配的属性值进行注入
演示自动装配过程
- 根据属性名称自动注入
bean.xml
<!--实现自动装配
bean 标签属性 autowire,配置自动装配
autowire 属性常用两个值:
byName 根据属性名称注入 ,注入值 bean 的 id 值和类属性名称一样
byType 根据属性类型注入
-->
<bean id="emp" class="com.atguigu.spring5.autowire.Emp" autowire="byName">
<!--<property name="dept" ref="dept"></property>-->
</bean>
<bean id="dept" class="com.atguigu.spring5.autowire.Dept"></bean>
- 根据属性类型自动注入
<!--实现自动装配
bean 标签属性 autowire,配置自动装配
autowire 属性常用两个值:
byName 根据属性名称注入 ,注入值 bean 的 id 值和类属性名称一样
byType 根据属性类型注入
-->
<bean id="emp" class="com.atguigu.spring5.autowire.Emp" autowire="byType">
<!--<property name="dept" ref="dept"></property>-->
</bean>
<bean id="dept" class="com.atguigu.spring5.autowire.Dept"></bean>
注意:
1. 根据属性类型自动注入的方式要求bean.xml配置文件中不能配置多个相同类型的bean,否则在根据类型自动注入的时候会报异常。
2. 不能自动装配简单数据类型,比如 int、boolean、String 等。
2.11. IOC操作Bean管理(外部属性文件)
2.11.1. 在bean.xml中直接配置连接池
- 导入德鲁伊连接池依赖jar包
druid-1.1.9.jar
- bean.xml
<!--直接配置连接池-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url"
value="jdbc:mysql://localhost:3306/userDb"></property>
<property name="username" value="root"></property>
<property name="password" value="root"></property>
</bean>
2.11.2. 通过引入外部属性文件配置数据库连接池
- 创建外部属性文件,properties 格式文件,写数据库信息

注意:jdbc.properties文件中的key可以随便填,这样加前缀为了避免冲突。
把外部 properties 属性文件引入到 spring 配置文件中
引入 context 名称空间
<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">在 spring 配置文件使用标签引入外部属性文件
<!--引入外部属性文件--> <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>
2.12. IOC操作Bean管理(基于注解方式)
什么是注解?
- 注解是代码特殊标记,格式:@注解名称(属性名称=属性值, 属性名称=属性值..)
- 使用注解,注解作用在类上面,方法上面,属性上面
- 使用注解目的:简化 xml 配置
Spring 针对 Bean 管理中创建对象提供了4个注解
- @Component:创建普通bean对象
- @Service:建议用在业务层
- @Controller:建议用在web层
- @Repository:建议用在持久层
注意:上面四个注解功能是一样的,都可以用来创建 bean 实例,实际使用的时候可以混用。只是为了让开发人员开发时更加清晰当前类所充当的角色
2.12.1. 基于注解方式实现对象创建
- 第一步:引入aop的依赖
spring-aop-5.2.6.RELEASE.jar 第二步:引入context名称空间
<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">第三步:开启组件扫描
<!--开启组件扫描 1 如果扫描多个包,多个包使用逗号隔开 2 扫描包上层目录 --> <context:component-scan base-package="com.atguigu"></context:component-scan>第四步:创建类,在类上面添加创建对象注解
//在注解里面 value 属性值可以省略不写, //默认值是类名称,首字母小写 //UserService -- userService @Component(value = "userService") //等价于<bean id="userService" class="com.atguigu.service.UserService"/> public class UserService { public void add() { System.out.println("service add......."); } }测试
@Test public void testService2() { //加载配置类 ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml"); UserService userService = context.getBean("userService", UserService.class); System.out.println(userService); userService.add(); }
2.12.2. 开启组件扫描细节配置
<!--示例 1
use-default-filters="false" 表示现在不使用默认 filter,自己配置 filter
context:include-filter ,设置扫描哪些内容
示例1表示不使用默认filter(不扫描com.atguigu包下所有的类),只扫描带Controller注解的类
-->
<context:component-scan base-package="com.atguigu" use-default-filters="false">
<context:include-filter type="annotation"
expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
<!--示例 2
下面配置扫描包所有内容
context:exclude-filter: 设置哪些内容不进行扫描
示例2表示扫描com.atguigu包下所有的类除了Controller注解不扫描
-->
<context:component-scan base-package="com.atguigu">
<context:exclude-filter type="annotation"
expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
2.12.3. 基于注解方式实现属性注入(基于注解的自动装配)
@Autowired:根据属性类型进行自动装配
- 第一步 把 service 和 dao 对象创建,在 service 和 dao 类添加创建对象注解
- 第二步 在 service 注入 dao 对象,在 service 类添加 dao 类型属性,在属性上面使用注解
注意:1、使用@Autowired注解注入属性不需要给属性添加set方法。2、@Autowired注解的注入规则:默认根据属性的类型注入,如果发现容器中有多个bean匹配,那么再根据属性的属性名去和容器中的bean名字进行匹配,如果还是匹配不到就报错,如果容器中有一个bean的名称和属性名匹配成功,那么仍然可以自动转配成功@Service public class UserService { //定义 dao 类型属性 //不需要添加 set 方法 //添加注入属性注解 @Autowired private UserDao userDao; public void add() { System.out.println("service add......."); userDao.add(); } }总结:
1、一个接口只有一个实现类的情况下,属性名字怎么写都无所谓,因为@Autowired注解按照类型匹配就只有一个bean
2、一个接口多个实现类的情况下:
属性名字跟组件名字一致,组件名字可以在声明的时候指定,比如 @Service("abc"),使用@Autowired注解可以根据属性名称和组件名称进行匹配,然后进行自动装配 属性名字跟组件名字不一致时,配合[@Qualifier ](/Qualifier ) 注解指定组件名字
@Qualifier:根据名称进行注入
这个@Qualifier 注解的使用, 需要和上面@Autowired 一起使用 。因为UserDao接口可能有很多实现类。根据类型无法识别到底要注入哪一个实现类。所以需要根据名称来识别。@Service public class UserService { //定义dao类型属性 //不需要添加set方法 //添加注入属性注解 @Autowired //根据类型进行注入 @Qualifier(value = "userDaoImpl1") //根据名称进行注入 private UserDao userDao; public void add() { System.out.println("service add......."+name); userDao.add(); } }@Repository(value = "userDaoImpl1") public class UserDaoImpl implements UserDao { @Override public void add() { System.out.println("dao add....."); } }@Resource:可以根据类型注入,可以根据名称注入(默认按照 Bean 实例名称进行装配)
注意:@Resource注解是javax包下的。它并不是Spring官方提供的注解。而@Autowired和@Qualifier才是Spring提供的注解。 ```java package com.atguigu.spring5.service;
import com.atguigu.spring5.dao.UserDao; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import org.springframework.stereotype.Controller; import org.springframework.stereotype.Repository; import org.springframework.stereotype.Service;
import javax.annotation.Resource;
//在注解里面value属性值可以省略不写,
//默认值是类名称,首字母小写
//UserService — userService
//@Component(value = “userService”) //
//@Resource //根据类型进行注入
@Resource(name = "userDaoImpl1") //根据名称进行注入
private UserDao userDao;
public void add() {
System.out.println("service add......."+name);
userDao.add();
}
}
- @Value:注入普通类型属性
```java
@Service
public class UserService {
@Value(value = "abc")
private String name;
@Resource(name = "userDaoImpl1") //根据名称进行注入
private UserDao userDao;
public void add() {
System.out.println("service add......."+name);
userDao.add();
}
}
2.12.4. 完全注解开发
第一步:创建配置类:替代spring的配置文件
@Configuration //作为配置类,替代 xml 配置文件 @ComponentScan(basePackages = {"com.atguigu"}) public class SpringConfig { }编写测试类
@Test public void testService2() { //加载配置类 ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class); UserService userService = context.getBean("userService", UserService.class); System.out.println(userService); userService.add(); }
3. Aop
3.1. Aop概念
什么是 AOP?
- 面向切面编程,AOP 用来封装多个类的公共行为,将那些与业务无关,却为业务模块所共同调用的逻辑封装起来,减少系统的重复代码,降低模块间的耦合度
- 通俗描述:不通过修改源代码方式,在主干功能里面添加新功能
- 使用登录例子说明 AOP
3.2. AOP(底层原理)
AOP 底层使用动态代理
有两种情况动态代理:
3.2.1. 第一种 有接口情况,使用 JDK 动态代理
创建接口实现类代理对象,增强类的方法

3.2.2. 第二种 没有接口情况,使用 CGLIB 动态代理
创建子类的代理对象,增强类的方法

3.3. AOP(JDK 动态代理)
3.3.1. 使用 JDK 动态代理,使用 Proxy 类里面的方法创建代理对象
在java.lang.reflect包下有一个Proxy类。Proxy类里面有一个newProxyInstance(ClassLoader loader, 类<?>[] inferaces, InvocationHandler h)

方法有三个参数:
第一参数,类加载器
第二参数,增强方法所在的类,这个类实现的接口,支持多个接口
第三参数,实现这个接口 InvocationHandler,创建代理对象,写增强的部分
3.3.2. 编写 JDK 动态代理代码
- 创建接口,定义方法
```java package com.gzc.service;
/**
- @author GuoZhiChao
@create 2021-09-26 14:11 */ public interface MessageService {
void sendMessage(String msg); } ```
- 创建接口实现类,实现方法
```java package com.gzc.service.impl;
import com.gzc.service.MessageService;
/**
- @ClassName:MessageService
- @Description: TODO
- @Author GuoZhiChao
- @Date 2021/9/26 14:04
- @Version 1.0 */ public class MessageServiceImpl implements MessageService {
@Override
public void sendMessage(String msg) {
System.out.println("发送的短信内容是:"+msg);
}
}
3. 使用 Proxy 类创建接口代理对象
```java
package com.gzc.proxy;
import java.lang.reflect.Proxy;
/**
* @ClassName:JDKProxyFactory
* @Description: 生成代理对象的工厂
* @Author GuoZhiChao
* @Date 2021/9/26 13:53
* @Version 1.0
*/
public class JDKProxyFactory {
public static Object getProxyObject(Object target){
//使用Proxy.newProxyInstance()方法生成代理对象
return Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new MyInvocationHandler(target));
}
}
- 创建一个类实现InvocationHandler接口,并实现invoke方法
```java package com.gzc.proxy;
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.util.Arrays;
/**
- @ClassName:MyInvocationHandler
- @Description: TODO
- @Author GuoZhiChao
- @Date 2021/9/26 13:56
@Version 1.0 */ public class MyInvocationHandler implements InvocationHandler {
/**
要代理的目标对象:即被代理对象 */ private final Object target;
public MyInvocationHandler(Object target) { this.target = target; }
@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//在执行被代理对象的原生方法之前增加新的功能 System.out.println(“执行的方法名是:”+method.getName()); //执行被代理对象的原生方法 Object result = method.invoke(target, args);
//在执行被代理对象的原生方法之后增加新的功能 System.out.println(“方法参数列表是:”+ Arrays.toString(args));
return result; } } ```
- 测试类
测试结果:
```java
package com.gzc.test;
import com.gzc.proxy.JDKProxyFactory; import com.gzc.service.MessageService; import com.gzc.service.impl.MessageServiceImpl; import org.junit.Test;
/**
- @ClassName:JDKProxyTest
- @Description: 测试JDK动态代理
- @Author GuoZhiChao
- @Date 2021/9/26 14:03
@Version 1.0 */ public class JDKProxyTest {
@Test public void test() {
//JDK动态代理生成的代理对象只能强转成接口类型,不能强转成被代理类的类型 MessageService proxyMessageService = (MessageService) JDKProxyFactory.getProxyObject(new MessageServiceImpl()); proxyMessageService.sendMessage("who am i? i am a programmer");} } ```
3.4. AOP术语
3.4.1. 连接点
类里面哪些方法可以被增强,这些方法称为连接点。
3.4.2. 切入点
实际被真正增强的方法,称为切入点。
3.4.3. 通知(增强)
实际增强的逻辑部分称为通知(增强)。例如增删改的日志记录,或者登陆后的权限判断等。
通知有多种类型:
- 前置通知(@Before):方法之前执行
- 后置通知(@After):方法之后执行,无论被增强类的方法是否出现异常都会执行。
- 环绕通知(@Around):方法之前之后都执行
- 异常通知(@AfterThrowing):方法中出现异常的时候执行
- 返回通知(@AfterReturning):是在方法返回结果之后执行,如果被增强类的方法有异常,则不会执行
3.4.4. 切面
切面指的是一个动作,把通知应用到切入点的过程就称为切面。
3.5. AOP 操作(准备工作)
Spring 框架一般都是基于 AspectJ 实现 AOP 操作。
AspectJ 不是 Spring 组成部分,独立 AOP 框架,一般把 AspectJ 和 Spirng 框架一起使 用,进行 AOP 操作
3.5.1. 基于AspectJ实现AOP操作
- 基于 xml 配置文件实现
- 基于注解方式实现(使用)
3.5.2. 在项目工程里面引入AOP相关依赖

3.5.3. 切入点表达式
切入点表达式作用:知道对哪个类里面的哪个方法进行增强
语法结构: 语法结构: execution([权限修饰符] [返回类型] [类全路径] [方法名称]([参数列表]) )
切入点表达式示例
//举例 1:对 com.atguigu.dao.BookDao 类里面的 add 进行增强
execution(* com.atguigu.dao.BookDao.add(..))
//举例 2:对 com.atguigu.dao.BookDao 类里面的所有的方法进行增强
execution(* com.atguigu.dao.BookDao.* (..))
//举例 3:对 com.atguigu.dao 包里面所有类,类里面所有方法进行增强
execution(* com.atguigu.dao.*.* (..))
3.6. AOP操作(AspectJ 注解)
3.6.1. 创建类,在类里面定义方法
//被增强的类
public class User {
public void add() {
System.out.println("add.......");
}
}
3.6.2. 创建增强类(编写增强逻辑)
在增强类里面,创建方法,让不同方法代表不同通知类型
//增强的类
public class UserProxy {
public void before() {//前置通知
System.out.println("before......");
}
}
3.6.3. 进行通知的配置
- 在Spring配置文件中配置context名称空间用于开启注解扫描
- 在Spring配置文件中配置aop名称空间用于在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"
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="com.atguigu.spring5.aopanno"></context:component-scan>
- 使用注解创建 User 和 UserProxy 对象
在增强类上面添加注解 @Aspec
//增强的类 @Component @Aspect //生成代理对象 public class UserProxy { public void before() {//前置通知 System.out.println("before......"); } }在 spring 配置文件中开启AspectJ生成代理对象
这个配置的作用是开启之后,spring会去扫描看哪个类上面有@Aspect注解,如果有就把这个类的对象生成一个代理对象。<!-- 开启 Aspect 生成代理对象--> <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
3.6.4. 配置不同类型的通知
在增强类的里面,在作为通知方法上面添加通知类型注解,使用切入点表达式配置
//增强的类
@Component
@Aspect //生成代理对象
public class UserProxy {
//前置通知
//@Before 注解表示作为前置通知
@Before(value = "execution(* com.atguigu.spring5.aopanno.User.add(..))")
public void before() {
System.out.println("before.........");
}
//返回通知:是在方法返回结果之后执行,如果被增强类的方法有异常,则不会执行
@AfterReturning(value = "execution(*
com.atguigu.spring5.aopanno.User.add(..))")
public void afterReturning() {
System.out.println("afterReturning.........");
}
//后置通知:方法之后执行,而且被增强类的方法不管有没有异常都会执行
@After(value = "execution(* com.atguigu.spring5.aopanno.User.add(..))")
public void after() {
System.out.println("after.........");
}
//异常通知:在被增强类的方法有异常的时候执行
@AfterThrowing(value = "execution(*
com.atguigu.spring5.aopanno.User.add(..))")
public void afterThrowing() {
System.out.println("afterThrowing.........");
}
//环绕通知:在方法之前和方法之后都执行。如果被增强的类的方法有异常的时候则环绕通知中proceed方法之后的部分不会执行
@Around(value = "execution(* com.atguigu.spring5.aopanno.User.add(..))")
public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("环绕之前.........");
//被增强的方法执行
proceedingJoinPoint.proceed();
System.out.println("环绕之后.........");
}
}
3.6.5. 相同的切入点抽取
//相同切入点抽取
@Pointcut(value = "execution(* com.atguigu.spring5.aopanno.User.add(..))")
public void pointdemo() {
}
//前置通知
//@Before 注解表示作为前置通知
@Before(value = "pointdemo()")
public void before() {
System.out.println("before.........");
}
3.6.6. 有多个增强类多同一个方法进行增强,设置增强类优先级
在增强类上面添加注解 @Order(数字类型值),数字类型值越小优先级越高
@Component
@Aspect
@Order(1)
public class PersonProxy
//增强的类
@Component
@Aspect //生成代理对象
@Order(3)
public class UserProxy {
3.6.7. 完全使用注解开发
创建配置类,不需要创建 xml 配置文件
@Configuration
@ComponentScan(basePackages = {"com.atguigu"})
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class ConfigAop {
}
3.7. AOP 操作(AspectJ 配置文件)
3.7.1. 创建两个类,增强类和被增强类,创建方法
创建被代理类
public class Book {
public void buy() {
System.out.println("buy.............");
}
}
创建代理类
public class BookProxy {
public void before() {
System.out.println("before.........");
}
}
3.7.2. 在 spring 配置文件中创建两个类对象
<!--创建对象-->
<bean id="book" class="com.atguigu.spring5.aopxml.Book"></bean>
<bean id="bookProxy" class="com.atguigu.spring5.aopxml.BookProxy"></bean>
3.7.3. 在 spring 配置文件中配置切入点
<!--配置 aop 增强-->
<aop:config>
<!--切入点-->
<aop:pointcut id="p" expression="execution(* com.atguigu.spring5.aopxml.Book.buy(..))"/>
<!--配置切面-->
<aop:aspect ref="bookProxy">
<!--增强作用在具体的方法上-->
<aop:before method="before" pointcut-ref="p"/>
</aop:aspect>
</aop:config>
4. JdbcTemplate
4.1. 什么是 JdbcTemplate?
Spring 框架对 JDBC 进行封装,使用 JdbcTemplate 方便实现对数据库操作
4.2. 准备工作
4.2.1. 引入相关 jar 包

4.2.2. 在 spring 配置文件配置数据库连接池
jdbc.properties
jdbc.driverClass=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/book
jdbc.user=root
jdbc.password=123456
注意:这里的key尽量加上前缀,避免冲突
bean.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 引入外部属性文件 -->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!-- 配置druid数据库连接池 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driverClass}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.user}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
4.2.3. 配置 JdbcTemplate 对象,注入 DataSource
<!-- JdbcTemplate 对象 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!--注入 dataSource-->
<property name="dataSource" ref="dataSource"></property>
</bean>
4.2.4. 创建 service 类,创建 dao 类,在 dao 注入 jdbcTemplate 对象
开启注解扫描
<!-- 组件扫描 -->
<context:component-scan base-package="com.atguigu"></context:component-scan>
BookService
@Service
public class BookService {
//注入 dao
@Autowired
private BookDao bookDao;
}
BookDao
@Repository
public class BookDaoImpl implements BookDao {
//注入 JdbcTemplate
@Autowired
private JdbcTemplate jdbcTemplate;
}
4.3. JdbcTemplate 操作数据库(添加)
4.3.1. 对应数据库创建实体类
public class Book {
/**
* id
*/
private Integer id;
/**
* 书名
*/
private String name;
/**
* 价格
*/
private BigDecimal price;
/**
* 作者
*/
private String author;
/**
* 销量
*/
private Integer sales;
/**
* 库存
*/
private Integer stock;
/**
* 图片路径
*/
private String imgPath;
/*
*这里省略了空参和有参构造方法,以及get和set方法
*/
}
4.3.2. 编写 service 和 dao
BookService
@Service
public class BookService {
/**
* 给bookDao属性注入值
*/
@Autowired
private BookDao bookDao;
public void addBook(Book book) {
int updateRow = bookDao.saveBook(book);
if (updateRow == 1) {
System.out.println("添加图书成功");
}
}
}
BookDaoImpl
@Repository
public class BookDaoImpl implements BookDao {
/**
* 给属性jdbcTemplate注入值
*/
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public int saveBook(Book book) {
String sql = "insert into t_book(`name`,`price`,`author`,`sales`,`stock`,`img_path`) values(?,?,?,?,?,?)";
return jdbcTemplate.update(sql, book.getName(), book.getPrice(), book.getAuthor(), book.getSales(), book.getStock(), book.getImgPath());
}
}
4.3.3. 测试类
@Test
public void testInsert() {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean3.xml");
BookService bookService = context.getBean("bookService", BookService.class);
Book book = new Book();
book.setName("aaa");
book.setAuthor("张三aaa");
book.setStock(50);
book.setSales(100);
book.setPrice(new BigDecimal(66.6));
book.setImgPath("a.jpg");
bookService.addBook(book);
}
4.4. JdbcTemplate 操作数据库(修改和删除)
BookDaoImpl
@Override
public int updateBook(Book book) {
String sql = "update t_book set `name`=? , `price`=? , `author`=? , `sales`=?, `stock`=? , `img_path`=? where id=?";
return jdbcTemplate.update(sql,book.getName(), book.getPrice(), book.getAuthor(), book.getSales(), book.getStock(), book.getImgPath(),book.getId());
}
@Override
public int deleteBook(Integer id) {
String sql = "delete from t_book where id = ?";
return jdbcTemplate.update(sql,id);
}
4.5. JdbcTemplate 操作数据库(查询返回某个值)
使用 JdbcTemplate 实现查询返回某个值代码
queryForObject(String sql, Class<T> requiredType)
有两个参数
第一个参数:sql 语句
第二个参数:返回类型 Class
@Override
public int queryCount() {
String sql = "select count(*) from t_book";
return jdbcTemplate.queryForObject(sql,Integer.class);
}
4.6. JdbcTemplate 操作数据库(查询返回对象)
queryForObject(String sql, RowMapper<T> rowMapper, Object... args)
有三个参数
第一个参数:sql 语句
第二个参数:RowMapper 是接口,针对返回不同类型数据,使用这个接口里面实现类完成 数据封装
第三个参数:sql 语句值
@Override
public Book queryOneById(Integer id) {
String sql = "select `id`,`name`,`price`,`author`,`sales`,`stock`,`img_path` imgPath from t_book where `id`=?";
return jdbcTemplate.queryForObject(sql,new BeanPropertyRowMapper<Book>(Book.class),32);
}
4.7. JdbcTemplate 操作数据库(查询返回集合)
query(String sql, RowMapper<T> rowMapper, Object... args)
有三个参数
第一个参数:sql 语句
第二个参数:RowMapper 是接口,针对返回不同类型数据,使用这个接口里面实现类完成 数据封装
第三个参数:sql 语句值
@Override
public List<Book> queryAllBook() {
String sql = "select `id`,`name`,`price`,`author`,`sales`,`stock`,`img_path` imgPath from t_book";
return jdbcTemplate.query(sql,new BeanPropertyRowMapper<Book>(Book.class));
}
4.8. JdbcTemplate 操作数据库(批量操作)
batchUpdate(String sql, List<Object[]> batchArgs)
有两个参数
第一个参数:sql 语句
第二个参数:List 集合,添加多条记录数据
BookDaoImpl
@Override
public void batchAddBook(List<Object[]> list) {
String sql = "insert into t_book(`name`,`price`,`author`,`sales`,`stock`,`img_path`) values(?,?,?,?,?,?)";
int[] batchInsert = jdbcTemplate.batchUpdate(sql, list);
System.out.println(Arrays.toString(batchInsert));
}
@Override
public void batchUpdateBook(List<Object[]> list) {
String sql = "update t_book set `name`=? , `price`=? , `author`=? , `sales`=?, `stock`=? , `img_path`=? where id=?";
int[] batchUpdate = jdbcTemplate.batchUpdate(sql, list);
System.out.println(Arrays.toString(batchUpdate));
}
@Override
public void batchDelete(List<Object[]> list) {
String sql = "delete from t_book where id = ?";
int[] batchDelete = jdbcTemplate.batchUpdate(sql, list);
System.out.println(Arrays.toString(batchDelete));
}
测试
@Test
public void testBatchInsert() {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean3.xml");
BookService bookService = context.getBean("bookService", BookService.class);
ArrayList<Object[]> list = new ArrayList<>();
Object[] a1 = {"111",new BigDecimal(100),"aaa",100,10,"a.jpg"};
Object[] a2 = {"222",new BigDecimal(100),"bbb",100,10,"b.jpg"};
Object[] a3 = {"333",new BigDecimal(100),"ccc",100,10,"c.jpg"};
list.add(a1);
list.add(a2);
list.add(a3);
bookService.batchAddBook(list);
}
@Test
public void testBatchUpdate() {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean3.xml");
BookService bookService = context.getBean("bookService", BookService.class);
ArrayList<Object[]> list = new ArrayList<>();
Object[] a1 = {"甲",new BigDecimal(100),"aaa",100,10,"a.jpg",39};
Object[] a2 = {"乙",new BigDecimal(100),"bbb",100,10,"b.jpg",40};
Object[] a3 = {"丙",new BigDecimal(100),"ccc",100,10,"c.jpg",41};
list.add(a1);
list.add(a2);
list.add(a3);
bookService.batchUpdateBook(list);
}
@Test
public void testBatchDelete() {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean3.xml");
BookService bookService = context.getBean("bookService", BookService.class);
ArrayList<Object[]> list = new ArrayList<>();
Object[] a1 = {39};
Object[] a2 = {40};
Object[] a3 = {41};
list.add(a1);
list.add(a2);
list.add(a3);
bookService.batchDelete(list);
}
5. 事务管理
5.1. 什么是事务?
事务是数据库操作最基本单元,是逻辑上的一组操作序列,要么都成功,如果有一个失败所有操作都失败。
典型场景:银行转账 lucy 转账 100 元 给 mary,lucy 少 100,mary 多 100。
事务四个特性(ACID)
(1)原子性 (2)一致性 (3)隔离性 (4)持久性
5.2. 事务操作(搭建事务操作环境)

5.2.1. 创建数据库表,添加记录

5.2.2. 创建 service,搭建 dao,完成对象创建和注入关系
service 注入 dao,在 dao 注入 JdbcTemplate,在 JdbcTemplate 注入 DataSource
UserService
@Service
public class UserService {
//注入 dao
@Autowired
private UserDao userDao;
}
UserDaoImpl
@Repository
public class UserDaoImpl implements UserDao {
@Autowired
private JdbcTemplate jdbcTemplate;
}
5.2.3. 在 dao 创建两个方法:多钱和少钱的方法,在 service 创建方法(转账的方法)
UserDaoImpl
@Repository
public class UserDaoImpl implements UserDao {
@Autowired
private JdbcTemplate jdbcTemplate;
//lucy 转账 100 给 mary
//少钱
@Override
public void reduceMoney() {
String sql = "update t_account set money=money-? where username=?";
jdbcTemplate.update(sql,100,"lucy");
}
//多钱
@Override
public void addMoney() {
String sql = "update t_account set money=money+? where username=?";
jdbcTemplate.update(sql,100,"mary");
}
}
UserService
@Service
public class UserService {
//注入 dao
@Autowired
private UserDao userDao;
//转账的方法
public void accountMoney() {
//lucy 少 100
userDao.reduceMoney();
//mary 多 100
userDao.addMoney();
}
}
5.2.4. 上面代码,如果正常执行没有问题的,但是如果代码执行过程中出现异常,有问题

上面问题如何解决呢?
使用事务解决
事务操作过程

5.3. 事务操作(Spring 事务管理介绍)
- 事务添加到 JavaEE 三层结构里面 Service 层(业务逻辑层)
- 在 Spring 进行事务管理操作
有两种方式:编程式事务管理和声明式事务管理(使用) - 声明式事务管理
基于注解方式(使用)
基于 xml 配置文件方式 - 在 Spring 进行声明式事务管理,底层使用 AOP 原理
- Spring 事务管理 API
提供一个接口,代表事务管理器,这个接口针对不同的框架提供不同的实现类
5.4. 事务操作(注解声明式事务管理)
5.4.1. 在 spring 配置文件配置事务管理器
<!--创建事务管理器-->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--注入数据源-->
<property name="dataSource" ref="dataSource"></property>
</bean>
5.4.2. 在 spring 配置文件,开启事务注解
- 在 spring 配置文件引入名称空间 tx
<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">
- 开启事务注解
<!--开启事务注解-->
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
- 在 service 类上面(或者 service 类里面方法上面)添加事务注解
(1)@Transactional,这个注解添加到类上面,也可以添加方法上面
(2)如果把这个注解添加类上面,这个类里面所有的方法都添加事务
(3)如果把这个注解添加方法上面,为这个方法添加事务@Service @Transactional public class UserService { }
5.5. 事务操作(声明式事务管理参数配置)
在 service 类上面添加注解@Transactional,在这个注解里面可以配置事务相关参数

5.5.1. propagation:事务传播行为
多事务方法之间进行调用,这个过程中事务 是如何进行管理的,则称为事务传播行为。
支持当前事务的情况:
- REQUIRED: 如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。默认的事务传播行为
- SUPPORTS: 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
- MANDATORY: 如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。(mandatory:强制性)
不支持当前事务的情况:
- REQUIRES_NEW: 创建一个新的事务,如果当前存在事务,则把当前事务挂起。
- NOT_SUPPORTED: 以非事务方式运行,如果当前存在事务,则把当前事务挂起。
- NEVER: 以非事务方式运行,如果当前存在事务,则抛出异常。
其他情况:
- NESTED: 如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于REQUIRED。
@Service
@Transactional(propagation = Propagation.REQUIRED)
public class UserService{
}
5.5.2. ioslation:事务隔离级别
事务有特性成为隔离性,多事务操作之间不会产生影响。不考虑隔离性产生很多问题,有三个读问题:脏读、不可重复读、虚(幻)读
- 脏读:一个未提交事务读取到另一个未提交事务的数据
- 不可重复读:一个未提交事务读取到另一提交事务修改数据
- 幻读:一个未提交事务读取到另一提交事务添加数据
解决:通过设置事务隔离级别,解决读问题

@Service
@Transactional(propagation = Propagation.REQUIRED,isolation=Isolation.REPEATABLE_READ)
public class UserService{
}
5.5.3. timeout:超时时间
事务需要在一定时间内进行提交,如果不提交进行回滚
默认值是 -1 表示永不超时,设置时间以秒单位进行计算
5.5.4. readOnly:是否只读
读:查询操作,写:添加修改删除操作
readOnly 默认值 false,表示可以查询,可以添加修改删除操作
设置 readOnly 值是 true,设置成 true 之后,只能查询
5.5.5. rollbackFor:回滚
设置出现哪些异常进行事务回滚
5.5.6. noRollbackFor:不回滚
设置出现哪些异常不进行事务回滚
5.6. 事务操作(XML 声明式事务管理)
5.6.1. 配置事务管理器
<!--1 创建事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--注入数据源-->
<property name="dataSource" ref="dataSource"></property>
</bean>
5.6.2. 配置通知
<!--2 配置通知-->
<tx:advice id="txadvice">
<!--配置事务参数-->
<tx:attributes>
<!--指定哪种规则的方法上面添加事务-->
<tx:method name="accountMoney" propagation="REQUIRED"/>
<!--<tx:method name="account*"/>-->
</tx:attributes>
</tx:advice>
5.6.3. 配置切入点和切面
<!--3 配置切入点和切面-->
<aop:config>
<!--配置切入点-->
<aop:pointcut id="pt" expression="execution(* com.atguigu.spring5.service.UserService.*(..))"/>
<!--配置切面-->
<aop:advisor advice-ref="txadvice" pointcut-ref="pt"/>
</aop:config>
5.7. 事务操作(完全注解声明式事务管理)
创建配置类,使用配置类替代 xml 配置文件
@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;
}
//创建 JdbcTemplate 对象
@Bean
public JdbcTemplate getJdbcTemplate(DataSource dataSource) {
//到 ioc 容器中根据类型找到 dataSource
JdbcTemplate jdbcTemplate = new JdbcTemplate();
//注入 dataSource
jdbcTemplate.setDataSource(dataSource);
return jdbcTemplate;
}
//创建事务管理器
@Bean
public DataSourceTransactionManager
getDataSourceTransactionManager(DataSource dataSource) {
DataSourceTransactionManager transactionManager = new
DataSourceTransactionManager();
transactionManager.setDataSource(dataSource);
return transactionManager;
}
}
@Bean标注的方法有参数,则会从Spring容器中根据类型注入(多个类型的话会根据参数名称注入)
6. Spring5 新特性
整个 Spring5 框架的代码基于 Java8,运行时兼容 JDK9,许多不建议使用的类和方 法在代码库中删除
6.1. Spring整合日志框架
Spring 5.0 框架自带了通用的日志封装
Spring5 已经移除 Log4jConfigListener,官方建议使用 Log4j2
6.1.1. 第一步 引入 jar 包

6.1.2. 第二步 创建 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="info">
<appender-ref ref="Console"/>
</root>
</loggers>
</configuration>
6.2. Spring5框架核心容器支持Nullable注解
@Nullable 注解可以使用在方法上面,属性上面,参数上面,表示方法返回可以为空,属性值可以为空,参数值可以为空
- 注解用在方法上面,方法返回值可以为空
- 注解使用在方法参数里面,方法参数可以为空
- 注解使用在属性上面,属性值可以为空
6.3. Spring5 核心容器支持函数式风格GenericApplicationContext
//函数式风格创建对象,交给 spring 进行管理
@Test
public void testGenericApplicationContext() {
//1 创建 GenericApplicationContext 对象
GenericApplicationContext context = new GenericApplicationContext();
//2 调用 context 的方法对象注册
context.refresh();
context.registerBean("user1",User.class,() -> new User());
//3 获取在 spring 注册的对象
// User user = (User)context.getBean("com.atguigu.spring5.test.User");
User user = (User)context.getBean("user1");
System.out.println(user);
}
6.4. Spring5 支持整合 JUnit4
- 第一步 引入 Spring 相关针对测试依赖
- 第二步 创建测试类,使用注解方式完成
@RunWith(SpringJUnit4ClassRunner.class) //单元测试框架 @ContextConfiguration("classpath:bean1.xml") //加载配置文件 public class JTest4 { @Autowired private UserService userService; @Test public void test1() { userService.accountMoney(); } }
6.5. Spring5整合JUnit5
- 第一步 引入 JUnit5 的 jar 包
第二步 创建测试类,使用注解完成
@ExtendWith(SpringExtension.class) @ContextConfiguration("classpath:bean1.xml") public class JTest5 { @Autowired private UserService userService; @Test public void test1() { userService.accountMoney(); } }使用一个复合注解替代上面两个注解完成整合
@SpringJUnitConfig(locations = "classpath:bean1.xml") public class JTest5 { @Autowired private UserService userService; @Test public void test1() { userService.accountMoney(); } }
