Spring框架学习笔记
尚硅谷网课《Spring5框架最新版教程》
链接地址:https://www.bilibili.com/video/BV1Vf4y127N5
学习时间:2021/07/20
所用Spring版本:Spring5.2.6
第一部分 Spring框架概述
- Spring 是轻量级的开源的 JavaEE 框架
- Spring可以解决企业应用开发的复杂性
- Spring 有两个核心部分:IOC 和 AOP
(1)IOC:控制反转,把创建对象过程交给 Spring 进行管理
(2)AOP:面向切面,不修改源代码进行功能增强 - Spring 的特点
(1)方便解耦,简化开发
(2)Aop 编程支持
(3)方便程序测试
(4)方便和其他框架进行整合
(5)方便进行事务操作
(6)降低 API 开发难度
Spring5 的一个入门案例
第一步:创建工程,导入Spirng5相关jar包。
第二步:创建一个普通类,在这个类中创建一个普通方法。
public class User {
public void add(){
System.out.println("add......");
}
}
第三步:创建 Spring 配置文件,在配置文件配置创建的对象
(1)Spring 配置文件使用 xml 格式
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--配置User对象创建-->
<bean id="user" class="com.xiangll.spring5.User"></bean>
</beans>
第四步:测试我们创建的对象
@Test
public void testAdd(){
//1.加载spring的配置文件
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
//2.获取配置创建的对象
User user = context.getBean("user", User.class);
System.out.println(user);
user.add();
}
执行结果:
com.xiangll.spring5.User@7b69c6ba
add......
第二部分 IOC容器
一.IOC的概念和底层原理
-
什么是IOC?
控制反转,把对象创建和对象之间的调用过程,交给 Spring 进行管理 。 -
使用 IOC 的目的:
为了降低耦合度。 -
IOC底层原理:
IOC涉及的三项技术:xml 解析、工厂模式、反射 。
图解:
简述:假设有两个类UserService类和UserDao类,在UserService类中想调用UserDao类的方法,怎么办?
传统方法直接new对象,但是耦合度太高。
IOC:第一步进行xml文件配置,配置创建的对象;第二步利用工厂模式,创建一个工厂类UserFactory,在类中写一个方法:先进行xml文件解析,在通过反射创建UserDao类的对象。通过调用这个工厂类中的方法,从而进一步降低耦合度。
二.IOC中的接口
- IOC 思想基于 IOC 容器完成,IOC 容器底层就是对象工厂 。
- Spring 提供了 IOC 容器实现的两种方式:(两个接口)
(1)BeanFactory
:IOC 容器基本实现,是 Spring 内部的使用接口,不提供开发人员进行使用。
加载配置文件时候不会创建对象,在获取对象(使用)才去创建对象。
(2)ApplicationContext
:BeanFactory
接口的子接口,提供更多更强大的功能,一般由开发人员进行使用。
加载配置文件时候就会把在配置文件对象进行创建。 ApplicationContext
接口的两个主要实现类:
(1)FileSystemXmlApplicationContext:要求写全路径
FileSystemXmlApplicationContext context = new FileSystemXmlApplicationContext("I:\\spring5_code\\src\\bean.xml");
(2)ClassPathXmlApplicationContext:类路径,即src下路径
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
三.IOC中的具体操作——Bean管理
- 什么是Bean管理?
Bean 管理指的是两个操作:
(1)Spring 创建对象
(2)Spirng 注入属性 - Bean 管理操作有两种方式:
(1)基于 xml 配置文件方式实现
(2)基于注解方式实现
3.1 基于 xml 方式
3.1.1 基于 xml 方式创建对象
<!--配置User对象创建-->
<bean id="user" class="com.xiangll.spring5.User"></bean>
-
在 spring 配置文件中,使用 bean 标签,标签里面添加对应属性,就可以实现对象创建
-
在 bean 标签有很多属性,常用的属性有:
(1)id 属性:唯一标识
(2)class 属性:类全路径(包类路径) -
创建对象时候,默认执行无参构造方法完成对象创建
3.1.2 基于 xml 方式注入属性
-
DI:依赖注入,就是注入属性
-
IOC和DI的区别?(面试题)
答:IOC是控制反转,DI是IOC中的一种具体实现,表示依赖注入(注入属性),但是注入属性需要在创建对象的基础之上进行完成。 -
注入属性的几种方式:
第一种注入方式:使用 set 方法进行注入
(1)创建类,定义属性和对应的 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;
}
}
(2)在 spring 配置文件配置对象创建,配置属性注入
<!--set方法注入属性-->
<bean id="book" class="com.xiangll.spring5.Book">
<!--使用property完成属性注入
name:类中属性名称
value:向属性中注入的值-->
<property name="bname" value="安徒生童话"></property>
<property name="bauthor" value="安徒生"></property>
</bean>
写一个单元测试方法测试结果:
@Test
public void testBook(){
//1.加载spring的配置文件
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
//2.获取配置创建的对象
Book book = context.getBean("book", Book.class);
System.out.println(book);
}
执行结果:
Book{bname='安徒生童话', bauthor='安徒生'}
第二种注入方式:使用有参构造进行注入
(1)创建类,定义属性,创建属性对应有参数构造方法
/**
* 使用有参数构造注入
*/
public class Orders {
//属性
private String oname;
private String address;
//有参数构造
public Orders(String oname, String address) {
this.oname = oname;
this.address = address;
}
}
(2)在 spring 配置文件中进行配置
方式一:按name名称搜索,常用,准确
<!--有参数构造注入属性-->
<bean id="orders" class="com.xiangll.spring5.Orders">
<constructor-arg name="oname" value="电脑"></constructor-arg>
<constructor-arg name="address" value="中国"></constructor-arg>
</bean>
方式二:按index索引搜索,不推荐
<bean id="orders" class="com.xiangll.spring5.Orders">
<constructor-arg index="0" value="电脑"></constructor-arg>
<constructor-arg index="1" value="中国"></constructor-arg>
</bean>
写一个单元测试方法测试结果:
@Test
public void testOrder(){
//1.加载spring的配置文件
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean4.xml");
//2.获取配置创建的对象
Orders orders = context.getBean("orders", Orders.class);
System.out.println(orders);
orders.ordersTest();
}
执行结果:
Orders{oname='电脑', address='中国'}
第三种注入方式:p 名称空间注入
(其实是简化了set注入方式,了解即可)
(1)添加 p 名称空间在配置文件中
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
(2)进行属性注入,在 bean 标签里面进行操作
<bean id="book" class="com.xiangll.spring5.Book" p:bname="安徒生童话" p:bauthor="安徒生"></bean>
3.1.3 基于 xml 方式注入其他类型属性
1.字面量
(1)null值
<!--null值-->
<property name="address">
<null/>
</property>
(2)属性值包含特殊符号
<!--属性包含特殊符号
方式一:把 <> 进行转义 < >-->
<property name="address" value="<<南京>>"></property>
方式二:
<!--属性包含特殊符号
方式二:把带特殊符号的内容写到CDATA中-->
<property name="address">
<value><![CDATA[<<南京>>]]></value>
</property>
2.注入属性-外部 bean
利用一个例子进行解释:在一个类中(UserService)调用另一个类里面的方法
- 创建两个类:
public class UserService {
public void add(){
System.out.println("service add...........");
}
}
public interface UserDao {
public void update();
}
public class UserDaoImpl implements UserDao{//UserDao接口的一个实现类
@Override
public void update() {
System.out.println("dao update.......");
}
}
传统方法,我们new对象
public class UserService {
public void add(){
System.out.println("service add...........");
}
//创建UserDao对象
UserDao userDao=new UserDaoImpl();
userDao.update();
}
Spring的方法:
第一步:在UserService类中创建UserDao类型属性,生成set方法
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();
}
}
第二步: spring 配置文件
<!--1 UserService和UserDao对象创建-->
<bean id="userService" class="com.xiangll.spring5.service.UserService">
<!--注入userDao对象
name属性值:类里面的属性名称
ref属性值:创建的UserDao对象中bean标签内的id值-->
<property name="userDao" ref="userDaoImpl"></property>
</bean>
<bean id="userDaoImpl" class="com.xiangll.spring5.dao.UserDaoImpl"></bean>
写一个单元测试方法测试结果:
@Test
public void testAdd(){
//1.加载spring的配置文件
ApplicationContext context = new ClassPathXmlApplicationContext("bean2.xml");
//2.获取配置创建的对象
UserService userService = context.getBean("userService", UserService.class);
userService.add();
}
执行结果:
service add...........
dao update.......
3.注入属性-内部 bean
利用一个例子进行解释:
(1)一对多关系:部门和员工
一个部门有多个员工,一个员工属于一个部门
部门是一,员工是多
(2)在实体类之间表示一对多关系,员工表示所属部门,使用对象类型属性进行表示
第一步:创建员工类和部门类,以及相应的set方法。
public class Emp {
private String ename;
private String gender;
private Dept dept;
public void setEname(String ename) {
this.ename = ename;
}
public void setGender(String gender) {
this.gender = gender;
}
public void setDept(Dept dept) {
this.dept = dept;
}
}
public class Dept {
private String dname;
public void setDname(String dname) {
this.dname = dname;
}
}
第二步:在 spring 配置文件中进行配置
<!--内部bean-->
<bean id="emp" class="com.xiangll.spring5.bean.Emp">
<!--设置两个普通属性-->
<property name="ename" value="lucy"></property>
<property name="gender" value="女"></property>
<!--设置对象类型属性-->
<property name="dept">
<bean id="dept" class="com.xiangll.spring5.bean.Dept">
<property name="dname" value="安保部"></property>
</bean>
</property>
</bean>
写一个单元测试方法测试结果:
@Test
public void testEmp(){
//1.加载spring的配置文件
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean6.xml");
//2.获取配置创建的对象
Emp emp = context.getBean("emp", Emp.class);
System.out.println(emp);
}
执行结果:
Emp{ename='lucy', gender='女', dept=Dept{dname='安保部'}}
4.注入属性-级联赋值
(1)第一种写法:
<!--级联赋值-->
<bean id="emp" class="com.xiangll.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.xiangll.spring5.bean.Dept">
<property name="dname" value="财务部"></property>
</bean>
(1)第二种写法:
<!--级联赋值-->
<bean id="emp" class="com.xiangll.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.xiangll.spring5.bean.Dept">
<property name="dname" value="财务处"></property>
</bean>
4.注入属性-xml注入集合属性
- 注入数组类型属性
- 注入 List 集合类型属性
- 注入 Map 集合类型属性
- 注入set 集合类型属性
第一步:创建一个类,定义数组、list、map、set 类型属性,生成对应 set 方法
public class Stu {
//1 数组类型的属性
private String[] array;
public void setArray(String[] array) {
this.array = array;
}
//2 List集合类型属性
private List<String> list;
public void setList(List<String> list) {
this.list = list;
}
//3 Map集合类型属性
private Map<String,String> map;
public void setMap(Map<String, String> map) {
this.map = map;
}
//4 Set集合类型属性
private Set<String> set;
public void setSet(Set<String> set) {
this.set = set;
}
}
第二步:在 spring 配置文件进行配置
<bean id="stu" class="com.xiangll.spring5.collectiontype.Stu">
<!--数组类型属性注入-->
<property name="array">
<array>
<value>java课程</value>
<value>java课程</value>
</array>
</property>
<!--List类型属性注入-->
<property name="list">
<list>
<value>张三</value>
<value>小三</value>
</list>
</property>
<!--Map类型属性注入-->
<property name="map">
<map>
<entry key="JAVA" value="java"></entry>
<entry key="PHP" value="php"></entry>
</map>
</property>
<!--Set类型属性注入-->
<property name="set">
<set>
<value>MySQL</value>
<value>Redis</value>
</set>
</property>
</bean>
写一个单元测试方法测试结果:
@Test
public void testStu(){
//1.加载spring的配置文件
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
//2.获取配置创建的对象
Stu stu = context.getBean("stu", Stu.class);
System.out.println(stu);
}
执行结果:
Stu{array=[java课程, java课程], list=[张三, 小三], map={JAVA=java, PHP=php}, set=[MySQL, Redis]}
- 在集合里面设置对象类型值
//课程类
public class Course {
private String cname;
public void setCname(String cname) {
this.cname = cname;
}
}
第一步:定义值为对象的集合类型属性,生成对应 set 方法
public class Stu {
//学生所学的多门课程
private List<Course> courseList;
public void setCourseList(List<Course> courseList) {
this.courseList = courseList;
}
}
第二步:在 spring 配置文件进行配置
<bean id="stu" class="com.xiangll.spring5.collectiontype.Stu">
<!--注入List集合类型,值是对象-->
<property name="courseList">
<list>
<ref bean="course1"></ref>
<ref bean="course2"></ref>
</list>
</property>
</bean>
<!--创建多个course对象-->
<bean id="course1" class="com.xiangll.spring5.collectiontype.Course">
<property name="cname" value="Spring5框架课程"></property>
</bean>
<bean id="course2" class="com.xiangll.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: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 集合注入提取
创建一个类:
public class Book {
private List<String> list;
public void setList(List<String> list) {
this.list = list;
}
}
第二步:在 spring 配置文件进行配置
<!--1 提取 list 集合类型属性注入-->
<util:list id="booklist">
<value>安徒生童话</value>
<value>格林童话</value>
<value>伊索寓言</value>
</util:list>
<!--2 提取 list 集合类型属性注入使用-->
<bean id="book" class="com.xiangll.spring5.collectiontype.Book">
<property name="list" ref="booklist"></property>
</bean>
执行结果:
Book{list=[安徒生童话, 格林童话, 伊索寓言]}
3.1.4 FactoryBean
-
Spring 有两种类型 bean,一种是普通 bean,另外一种是工厂 bean(FactoryBean)
-
普通 bean:在配置文件中定义的 bean 类型就是返回类型
-
工厂 bean:在配置文件定义 bean 类型可以和返回类型不一样
第一步 创建类,让这个类作为工厂 bean,实现接口 FactoryBean
第二步 实现接口里面的方法,在实现的方法中定义返回的 bean 类型
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 FactoryBean.super.isSingleton();
}
}
<bean id="mybean" class="com.xiangll.spring5.factorybean.MyBean">
</bean>
写一个单元测试方法测试结果:
public class MyBeanTest {
@Test
public void testMyBean(){
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean2.xml");
Course course = context.getBean("mybean", Course.class);
System.out.println(course);
}
}
执行结果:
Course{cname='abc'}
3.1.5 Bean的作用域
- 在 Spring 里面,可以设置创建的 bean 实例是单实例还是多实例 。
- 在 Spring 里面,默认情况下,bean 是单实例对象 。
@Test
public void testbook(){
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
Book book1 = context.getBean("book", Book.class);
Book book2 = context.getBean("book", Book.class);
System.out.println(book1);
System.out.println(book2);
}
执行结果:
//相同的两个对象实例
com.xiangll.spring5.collectiontype.Book@6d3af739
com.xiangll.spring5.collectiontype.Book@6d3af739
- 如何设置单实例还是多实例 ?
(1)在 spring 配置文件 bean 标签里面有属性(scope)用于设置单实例还是多实例
(2)scope 属性值
第一个值 默认值,singleton,表示是单实例对象
第二个值 prototype,表示是多实例对象
<bean id="book" class="com.xiangll.spring5.collectiontype.Book" scope="prototype">
<property name="list" ref="booklist"></property>
</bean>
//不同的两个对象实例
com.xiangll.spring5.collectiontype.Book@6d3af739
com.xiangll.spring5.collectiontype.Book@1da51a35
- singleton 和 prototype 区别?
第一:singleton 单实例,prototype 多实例
第二:
设置 scope 值是 singleton 时候,加载 spring 配置文件时候就会创建单实例对象
设置 scope 值是 prototype 时候,不是在加载 spring 配置文件时候创建对象,在调用 getBean 方法时候创建多实例对象
request:一次请求
session:一次会话
3.1.6 Bean的生命周期
- 什么是生命周期?
从对象创建到对象销毁的过程 - bean的生命周期:
(1)通过构造器创建 bean 实例(无参数构造)
(2)为 bean 的属性设置值和对其他 bean 引用(调用 set 方法)
(3)调用 bean 的初始化的方法(需要进行配置初始化的方法)
(4)bean 可以使用了(对象获取到了)
(5)当容器关闭时候,调用 bean 的销毁的方法(需要进行配置销毁的方法) - 代码演示Bean的声明周期
(1)先写一个Order类:
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 destoryMethod(){
System.out.println("第五步 执行销毁的方法");
}
}
(2)xml文件配置:
<bean id="orders" class="com.xiangll.spring5.bean.Orders" init-method="initMethod" destroy-method="destoryMethod">
<property name="oname" value="手机"></property>
</bean>
(3)写一个单元测试方法:
public class TestBean {
@Test
public void testbean(){
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean3.xml");
Orders orders = context.getBean("orders", Orders.class);
System.out.println("第四步 获取创建bean实例对象");
//手动让bean实例销毁
context.close();
}
}
(4)执行结果:
第一步 执行无参数构造创建bean实例
第二步 调用set方法设置属性的值
第三步 执行初始化方法
第四步 获取创建bean实例对象
第五步 执行销毁的方法
- 如果考虑bean 的后置处理器,那么bean 生命周期有七步 :
(1)通过构造器创建 bean 实例(无参数构造)
(2)为 bean 的属性设置值和对其他 bean 引用(调用 set 方法)
(3)把 bean 实例传递 bean 后置处理器的方法 postProcessBeforeInitialization
(4)调用 bean 的初始化的方法(需要进行配置初始化的方法)
(5)把 bean 实例传递 bean 后置处理器的方法 postProcessAfterInitialization
(6)bean 可以使用了(对象获取到了)
(7)当容器关闭时候,调用 bean 的销毁的方法(需要进行配置销毁的方法) - 代码演示添加后置处理器效果
(1)创建一个类,实现接口 BeanPostProcessor,创建作为后置处理器
//后置处理器会为配置文件中创建的所有bena实例添加上后置处理器,里面的方法都会执行
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;
}
}
(2)在xml文件中配置后置处理器
<bean id="orders" class="com.xiangll.spring5.bean.Orders" init-method="initMethod" destroy-method="destoryMethod">
<property name="oname" value="手机"></property>
</bean>
<!--配置后置处理器-->
<bean id="myBeanPost" class="com.xiangll.spring5.bean.MyBeanPost"></bean>
执行之前的单元测试方法,执行结果:
第一步 执行无参数构造创建bean实例
第二步 调用set方法设置属性的值
在初始化之前执行的方法
第三步 执行初始化方法
在初始化之后执行的方法
第四步 获取创建bean实例对象
第五步 执行销毁的方法
3.1.7 xml自动装配
(使用率比较小,只是为了操作方便,在实际开发中,我们一般使用注解方式简化操作。)
- 什么是自动装配 ?
根据指定装配规则(属性名称或者属性类型),Spring 自动将匹配的属性值进行注入。 - 演示自动装配过程 :
(1)根据属性名称自动注入
<!--实现自动装配
bean标签属性autowire,配置自动装配
autowire属性常用两个值:byName根据属性名称注入,注入值bean的id值和类属性名称一样
byType根据属性类型注入-->
<bean id="emp" class="com.xiangll.spring5.autowire.Emp" autowire="byName">
<!--<property name="dept" ref="dept"></property>-->
</bean>
<bean id="dept" class="com.xiangll.spring5.autowire.Dept">
<property name="dname" value="财务部"></property>
</bean>
(2)根据属性类型自动注入
<!--实现自动装配
bean标签属性autowire,配置自动装配
autowire属性常用两个值:byName根据属性名称注入,注入值bean的id值和类属性名称一样
byType根据属性类型注入-->
<bean id="emp" class="com.xiangll.spring5.autowire.Emp" autowire="byType">
<!--<property name="dept" ref="dept"></property>-->
</bean>
<bean id="dept" class="com.xiangll.spring5.autowire.Dept">
<property name="dname" value="财务部"></property>
</bean>
3.1.8 外部属性文件
- 以配置德鲁伊数据库连接池为例子说明:
两种方式:
方式一:直接配置数据库信息
<!--直接配置Druid连接池-->
<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/test"></property>
<property name="username" value="root"></property>
<property name="password" value="131702"></property>
</bean>
写一个单元测试方法测试连接情况:
@Test
public void test1() throws SQLException {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean5.xml");
DruidDataSource source = context.getBean("dataSource", DruidDataSource.class);
DruidPooledConnection connection = source.getConnection();
System.out.println(connection);
}
执行结果:成功连接
七月 22, 2021 9:31:12 上午 com.alibaba.druid.support.logging.JakartaCommonsLoggingImpl info
信息: {dataSource-1} inited
com.mysql.jdbc.JDBC4Connection@2357d90a
方式二:引入外部属性文件配置数据库连接池 (推荐)
(1)创建外部属性文件,properties 格式文件,写数据库信息
//jdbc.properties
user=root
password=131702
url=jdbc:mysql://localhost:3306/test
driverClass=com.mysql.jdbc.Driver
(2)把外部 properties 属性文件引入到 spring 配置文件中
第一步:引入context名称空间
第二步:在 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 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="${driverClass}"></property>
<property name="url" value="${url}"></property>
<property name="username" value="${user}"></property>
<property name="password" value="${password}"></property>
</bean>
</beans>
写一个单元测试方法测试连接情况,测试结果亦通过。
七月 22, 2021 9:42:31 上午 com.alibaba.druid.support.logging.JakartaCommonsLoggingImpl info
信息: {dataSource-1} inited
com.mysql.jdbc.JDBC4Connection@53ca01a2
3.1 基于注解方式
- 什么是注解 ?
(1)注解是代码特殊标记,格式:@注解名称(属性名称=属性值, 属性名称=属性值…) (2)使用注解,注解作用在类上面,方法上面,属性上面
(3)使用注解目的:简化 xml 配置 - Spring 针对 Bean 管理中创建对象提供注解
(1)@Component
(2)@Service
(3)@Controller
(4)@Repository
上面四个注解功能是一样的,都可以用来创建 bean实例
3.1.1 演示:基于注解方式实现对象创建
第一步:引入依赖(jar包)
第二步:开启组件扫描
<!--开启组件扫描
1.如果扫描多个包,多个包使用逗号隔开
2.扫描包上层目录
-->
<context:component-scan base-package="com.xiangll.spring5"></context:component-scan>
第三步:创建一个类,在类上面添加创建对象注解
/*
*在注解里面的value属性值可以省略不写
* 默认值是类名称,首字母小写
*/
@Component(value = "userService") //等价于 <bean id="userService" class="com.xiangll.spring5.service.UserService"></bean>
public class UserService {
public void add(){
System.out.println("service add......");
}
}
写一个单元测试方法,测试结果:
@Test
public void test1(){
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
UserService userService = context.getBean("userService", UserService.class);
System.out.println(userService);
userService.add();
}
执行结果:成功创建对象实例
com.xiangll.spring5.service.UserService@7c417213
service add......
3.1.2 开启组件扫描中的细节配置
- 示例1:
<!--示例1
use-default-filters="false":表示现在不使用默认的filter,使用自己配置的filter
context:include-filter:设置扫描哪些内容
-->
<context:component-scan base-package="com.xiangll.spring5" use-default-filters="false">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
- 示例2:
<!--示例2
context:exclude-filter:设置哪些内容不扫描-->
<context:component-scan base-package="com.xiangll.spring5">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
3.1.3 基于注解方式实现属性注入
(1)-(3)注入对象属性的三个最常用的注解,(4)是注入一般的普通类型属性。
(1)@Autowired:根据属性类型进行自动装配 (根据类型注入)
案例演示:准备工作,创建类
public class UserService {
public void add(){
System.out.println("service add......");
}
}
public interface UserDao {
public void add();
}
public class UserDaoImpl implements UserDao{
@Override
public void add() {
System.out.println("dao add......");
}
}
目标:在UserService中调用UserDao的方法
第一步:把 service 和 dao 对象创建,在 service 和 dao 类添加创建对象注解
第二步:在 service 注入 dao 对象,在 service 类添加 dao 类型属性,在属性上面使用注解
@Service
public class UserService {
//定义dao类型属性
//这里不需要添加set方法
@Autowired
private UserDao userDao;
public void add(){
System.out.println("service add......");
userDao.add();
}
}
@Repository
public class UserDaoImpl implements UserDao{
@Override
public void add() {
System.out.println("dao add......");
}
}
(2)@Qualifier:根据名称进行注入
这个@Qualifier 注解的使用,和上面@Autowired 一起使用
案例演示:
@Service
public class UserService {
//定义dao类型属性
//这里不需要添加set方法
@Autowired //根据类型进行注入
@Qualifier(value = "userDaoImpl1") //根据名称进行注入
private UserDao userDao;
public void add(){
System.out.println("service add......");
userDao.add();
}
}
@Repository(value = "userDaoImpl1")
public class UserDaoImpl implements UserDao{
@Override
public void add() {
System.out.println("dao add......");
}
}
(3)@Resource:既可以根据类型注入,也可以根据名称注入
@Resource是javax中的拓展包,而前两者是spring官方的包,所以spring推荐我们用前两个。
案例演示:
@Service
public class UserService {
@Resource //根据类型进行注入
private UserDao userDao;
public void add(){
System.out.println("service add......");
userDao.add();
}
}
@Service
public class UserService {
@Resource(name = "userDaoImpl1") //根据名称进行注入
private UserDao userDao;
public void add(){
System.out.println("service add......");
userDao.add();
}
}
(4)@Value:注入普通类型属性
@Value(value = "abc")
private String name;
3.1.4 完全注解开发 (即使开发中——SpringBoot)
(1)创建一个配置类,替代 xml 配置文件
@Configuration //作为配置类,替代xml配置文件
@ComponentScan(basePackages = {"com.xiangll.spring5"})
public class SpringConfig {
}
(2)写一个单元测试方法(写法与之前不同)
@Test
public void test2(){
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);//加载配置类
UserService userService = context.getBean("userService", UserService.class);
System.out.println(userService);
userService.add();
}
执行结果:
com.xiangll.spring5.service.UserService@3c419631
service add......
dao add......
第三部分 AOP
一.AOP的概念
- 什么是 AOP ?
面向切面编程(方面),利用 AOP 可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。 - 通俗描述:不通过修改源代码方式,在主干功能里面添加新功能。
- 使用登录例子说明 AOP
二.AOP的底层原理
- AOP 底层使用动态代理 (两种情况的动态代理)
情况一:有接口的情况:使用JDK动态代理
解决办法:创建接口实现类代理对象,增强类的方法
情况二:没有接口的情况 :使用CGLIB动态代理
解决办法:创建子类的代理对象,增强类的方法
- 代码书写演示(JDK 动态代理)
使用 JDK 动态代理,使用 Proxy 类里面的方法创建代理对象
(1)调用 newProxyInstance 方法
方法有三个参数:
第一个参数,类加载器
第二个参数,增强方法所在的类,这个类实现的接口,支持多个接口
第三个参数,实现这个接口 InvocationHandler,创建代理对象,写增强的部分
第一步:创建接口,定义方法
public interface UserDao {
public int add(int a,int b);
public String update(String id);
}
第二步:创建接口实现类,实现方法
public class UserDaoImpl implements UserDao{
@Override
public int add(int a, int b) {
return a+b;
}
@Override
public String update(String id) {
return id;
}
}
第三步:使用 Proxy 类创建接口代理对象
public class JDKProxy {
public static void main(String[] args) {
//创建接口实现类的代理对象
Class[] interfaces={UserDao.class};
/* Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), interfaces, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return null;
}
});*/
UserDaoImpl userDao = new UserDaoImpl();
UserDao dao = (UserDao) Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), interfaces, new UserDaoProxy(userDao));
int result = dao.add(1, 2);
System.out.println(result);
}
}
//创建代理对象代码
class UserDaoProxy implements InvocationHandler{
//1 把创建的是谁的代理对象,就把谁传进来
//通过有参构造进行传递
private Object obj;
public UserDaoProxy(Object obj){
this.obj=obj;
}
//增强的逻辑
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//方法之前
System.out.println("方法之前执行......"+method.getName()+";传递的参数"+ Arrays.toString(args));
//被增强的方法执行
Object res = method.invoke(obj, args);
//方法之后
System.out.println("方法之后执行......"+obj);
return res;
}
}
执行结果:
方法之前执行......add;传递的参数[1, 2]
add方法执行了......
方法之后执行......com.xiangll.spring5.UserDaoImpl@d716361
3
三.AOP中的相关术语
- 连接点:类里面哪些方法可以被增强,这些方法称为连接点
- 切入点:实际被真正增强的方法,称为切入点
- 通知(增强):实际增强的逻辑部分称为通知(增强)
通知有多种类型:
前置通知
后置通知
环绕通知
异常通知
最终通知
- 切面:是一个动作,把通知(增强)应用到切入点的过程
四.AOP操作
- Spring 框架中一般都是基于 AspectJ 实现 AOP 操作
- 什么是AspectJ ?
AspectJ 不是 Spring 组成部分,它是一个独立的 AOP 框架,一般把 AspectJ 和 Spirng 框架一起使用,进行 AOP 操作 - 基于 AspectJ 实现 AOP 操作 (两种方式)
(1)基于 xml 配置文件实现
(2)基于注解方式实现(实际使用) - 切入点表达式
(1)切入点表达式作用:知道对哪个类里面的哪个方法进行增强
(2)语法结构:
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.*.* (..))
4.1 AOP 操作之AspectJ 注解方式
举例演示:
第一步:创建一个类,在该类中定义一个方法
//被增强的类
public class User {
public void add(){
System.out.println("add......");
}
}
第二步:创建增强类(编写增强逻辑)
在增强类里面,创建方法,让不同方法代表不同通知类型
//增强的类
public class UserProxy {
//前置通知
public void before(){
System.out.println("before......");
}
}
第三步:进行通知的配置
(1)在 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.xiangll.spring5.AOPanno"></context:component-scan>
</beans>
(2)使用注解创建 User 和 UserProxy 对象
//被增强的类
@Component
public class User {
public void add(){
System.out.println("add......");
}
}
//增强的类
@Component
public class UserProxy {
//前置通知
public void before(){
System.out.println("before......");
}
}
(3)在增强类上面添加注解 @Aspect
//增强的类
@Component
@Aspect
public class UserProxy {
//前置通知
public void before(){
System.out.println("before......");
}
}
(4)在 spring 配置文件中开启生成代理对象
<!--开启Aspect生成代理对象-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
第四步:配置不同类型的通知
(1)在增强类的里面,在作为通知方法上面添加通知类型注解,使用切入点表达式配置
public class UserProxy {
//前置通知
//@Before注解表示作为前置通知
@Before(value = "execution(* com.xiangll.spring5.AOPanno.User.add(..))")
public void before(){
System.out.println("before......");
}
}
写一个单元测试方法:
@Test
public void test1(){
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
User user = context.getBean("user", User.class);
user.add();
}
执行结果:成功,增强了add方法,before在add之前生成
before......
add......
- 剩余的四种通知类型展示:
//增强的类
@Component
@Aspect
public class UserProxy {
//前置通知
//@Before注解表示作为前置通知
@Before(value = "execution(* com.xiangll.spring5.AOPanno.User.add(..))")
public void before(){
System.out.println("before......");
}
//后置通知(返回通知)
@AfterReturning(value = "execution(* com.xiangll.spring5.AOPanno.User.add(..))")
public void afterReturning(){
System.out.println("afterRunning......");
}
//最终通知
@After(value = "execution(* com.xiangll.spring5.AOPanno.User.add(..))")
public void after(){
System.out.println("after......");
}
//异常通知
@AfterThrowing(value = "execution(* com.xiangll.spring5.AOPanno.User.add(..))")
public void afterThrowing(){
System.out.println("afterThrowing.....");
}
//环绕通知
@Around(value = "execution(* com.xiangll.spring5.AOPanno.User.add(..))")
public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("环绕之前......");
proceedingJoinPoint.proceed();
System.out.println("环绕之后......");
}
}
执行结果:
环绕之前......
before......
add......
环绕之后......
after......
afterRunning......
故意在方法中手动加入一个异常:
//被增强的类
@Component
public class User {
public void add(){
int i=10/0;
System.out.println("add......");
}
}
执行结果:
环绕之前......
before......
after......
afterThrowing.....
java.lang.ArithmeticException: / by zero
- 可优化点一:相同的切入点抽取
//相同切入点抽取
@Pointcut(value = "execution(* com.xiangll.spring5.AOPanno.User.add(..))")
public void pointcut(){
}
//前置通知
//@Before注解表示作为前置通知
@Before(value = "pointcut()")
public void before(){
System.out.println("before......");
}
- 可优化点二:有多个增强类多同一个方法进行增强,设置增强类优先级
在增强类上面添加注解 @Order(数字类型值),数字类型值越小优先级越高
@Component
@Aspect
@Order(1)
public class PersonProxy {
//前置通知
@Before(value = "execution(* com.xiangll.spring5.AOPanno.User.add(..))")
public void before(){
System.out.println("Person Before......");
}
}
- 可优化点三:完全使用注解开发
创建一个配置类,这样可以不需要创建xml配置文件
@Configuration
@ComponentScan(basePackages = {"com.xiangll.spring5.AOPanno"})
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class ConfigAOP {
}
写一个测试方法
@Test
public void test2(){
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ConfigAOP.class);
User user = context.getBean("user", User.class);
user.add();
}
成功执行。
4.2 AOP 操作之AspectJ xml配置文件方式(了解即可,不需要掌握)
代码距离说明:
(1)创建两个类,增强类和被增强类,创建方法
public class Book {
public void buy(){
System.out.println("buy......");
}
}
public class BookProxy {
public void before(){
System.out.println("before......");
}
}
(2)在 spring 配置文件中创建两个类对象
<!--创建对象-->
<bean id="book" class="com.xiangll.spring5.AOPxml.Book"></bean>
<bean id="bookproxy" class="com.xiangll.spring5.AOPxml.BookProxy"></bean>
(3)在 spring 配置文件中配置切入点
<!--创建对象-->
<bean id="book" class="com.xiangll.spring5.AOPxml.Book"></bean>
<bean id="bookproxy" class="com.xiangll.spring5.AOPxml.BookProxy"></bean>
<!--配置aop增强-->
<aop:config>
<!--切入点-->
<aop:pointcut id="p" expression="execution(* com.xiangll.spring5.AOPxml.Book.buy(..))"/>
<!--配置切面-->
<aop:aspect ref="bookproxy">
<!--增强作用在具体的方法上-->
<aop:before method="before" pointcut-ref="p"/>
</aop:aspect>
</aop:config>
第四部分 JdbcTemplate
一.JdbcTemplate的概念
- 什么是JdbcTemplate?
Spring 框架对 JDBC 进行封装,使用 JdbcTemplate 方便实现对数据库操作 - 准备工作:
(1)导入相关 jar 包
(2)在 spring xml配置文件配置数据库连接池
<!-- 数据库连接池 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
destroy-method="close">
<property name="url" value="jdbc:mysql:///test" />
<property name="username" value="root" />
<property name="password" value="131702" />
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
</bean>
(3)配置 JdbcTemplate 对象,注入 DataSource
<!--JdbcTemplate对象-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!--注入dataSource-->
<property name="dataSource" ref="dataSource"></property>
</bean>
(4)创建 service 类,创建 dao 类,在 dao 注入 jdbcTemplate 对象
xml配置文件中:
<!--组件扫描-->
<context:component-scan base-package="com.xiangll.spring5"></context:component-scan>
@Service
public class BookService {
//注入dao
@Autowired
private BookDao bookDao;
}
public interface BookDao {
}
@Repository
public class BookDaoImpl implements BookDao{
//注入JdbcTemplate
@Autowired
private JdbcTemplate jdbcTemplate;
}
二.JdbcTemplate 操作数据库(添加)
1.对应数据库创建实体类
public class User {
private int id;
private String name;
private String password;
private String address;
private String phone;
public void setId(int id) {
this.id = id;
}
public void setName(String name) {
this.name = name;
}
public void setPassword(String password) {
this.password = password;
}
public void setAddress(String address) {
this.address = address;
}
public void setPhone(String phone) {
this.phone = phone;
}
public int getId() {
return id;
}
public String getName() {
return name;
}
public String getPassword() {
return password;
}
public String getAddress() {
return address;
}
public String getPhone() {
return phone;
}
}
2.编写 service 和 dao
(1)在 dao 进行数据库添加操作
(2)调用 JdbcTemplate 对象里面 update 方法实现添加操作
有两个参数
第一个参数:sql 语句
第二个参数:可变参数,设置 sql 语句中的占位符
@Service
public class BookService {
//注入dao
@Autowired
private BookDao bookDao;
//添加方法
private void addUser(User user){
bookDao.add(user);
}
}
public interface BookDao {
void add(User user);
}
@Repository
public class BookDaoImpl implements BookDao{
//注入JdbcTemplate
@Autowired
private JdbcTemplate jdbcTemplate;
//添加的方法
@Override
public void add(User user) {
//1.创建sql语句
String sql="insert into user values(?,?,?,?,?)";
//2.调用方法实现
//int update = jdbcTemplate.update(sql, user.getId(), user.getName(), user.getPassword(), user.getAddress(), user.getPhone());
Object[] args ={user.getId(), user.getName(), user.getPassword(), user.getAddress(), user.getPhone()};
int update = jdbcTemplate.update(sql, args);
System.out.println(update);
}
}
写一个单元测试方法,测试结果:
@Test
public void test2(){
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
BookService bookService = context.getBean("bookService", BookService.class);
User user = new User();
user.setId(6);
user.setName("项亮亮");
user.setPassword("131702");
user.setAddress("南京市江宁区");
user.setPhone("131702");
bookService.addUser(user);
}
执行结果,数据添加成功:
三.JdbcTemplate 操作数据库(修改和删除)
- 修改:
@Override
public void update(User user) {
//1.创建sql语句
String sql="update user set name=?,password=?,address=?,phone=? where id=?";
//2.调用方法实现
Object[] args ={ user.getName(), user.getPassword(), user.getAddress(), user.getPhone(),user.getId()};
int update = jdbcTemplate.update(sql, args);
System.out.println(update);
}
- 删除:
@Override
public void delete(User user) {
//1.创建sql语句
String sql="delete from user where id=?";
//2.调用方法实现
int id = user.getId();
int update = jdbcTemplate.update(sql,id );
System.out.println(update);
}
四.JdbcTemplate 操作数据库(查询返回某个值)
场景:查询表里面有多少条记录,返回是某个值
- 使用 JdbcTemplate 实现查询返回某个值代码
有两个参数 :
第一个参数:sql 语句
第二个参数:返回类型 Class
@Override
public int selectCount() {
String sql="select count(*) from user";
Integer count = jdbcTemplate.queryForObject(sql, Integer.class);
return count;
}
五.JdbcTemplate 操作数据库(查询返回对象)
场景:查询user详情
- JdbcTemplate 实现查询返回对象
有三个参数:
第一个参数:sql 语句
第二个参数:RowMapper 是接口,针对返回不同类型数据,使用这个接口里面实现类完成 数据封装
第三个参数:sql 语句值
@Override
public User findUserInfo() {
String sql="select * from user where id =?";
User user = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<User>(User.class), 1);
return user;
}
六.JdbcTemplate 操作数据库(查询返回集合)
场景:返回user对象集合
- 调用 JdbcTemplate 方法实现查询返回集合
有三个参数 :
第一个参数:sql 语句
第二个参数:RowMapper 是接口,针对返回不同类型数据,使用这个接口里面实现类完成 数据封装
第三个参数:sql 语句值
@Override
public List<User> findAllUser() {
String sql="select * from user";
List<User> list = jdbcTemplate.query(sql, new BeanPropertyRowMapper<User>(User.class));
return list;
}
七.JdbcTemplate 操作数据库(批量添加)
- JdbcTemplate 实现批量添加操作
有两个参数 :
第一个参数:sql 语句
第二个参数:List 集合,添加多条记录数据
@Override
public void batchAddUser(List<Object[]> batchArgs) {
String sql="insert into user value(?,?,?,?,?)";
int[] ints = jdbcTemplate.batchUpdate(sql, batchArgs);
System.out.println(Arrays.toString(ints));
}
写一个单元测试方法:
@Test
public void test8(){
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
BookService bookService = context.getBean("bookService", BookService.class);
List<Object[]> batchArgs=new ArrayList<>();
Object[] o1={"6","项亮亮","111","111","111"};
Object[] o2={"7","欧阳卓","222","222","222"};
Object[] o3={"8","张悦","333","333","333"};
batchArgs.add(o1);
batchArgs.add(o2);
batchArgs.add(o3);
bookService.batchAdd(batchArgs);
}
批量添加成功。
八.JdbcTemplate 操作数据库(批量修改)
@Override
public void batchUpdateUser(List<Object[]> batchArgs) {
String sql="update user set password=?,address=?,phone=? where id =?;";
int[] ints = jdbcTemplate.batchUpdate(sql, batchArgs);
System.out.println(Arrays.toString(ints));
}
写一个单元测试方法:
@Test
public void test9(){
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
BookService bookService = context.getBean("bookService", BookService.class);
List<Object[]> batchArgs=new ArrayList<>();
Object[] o1={"999","999","999","6"};
Object[] o2={"888","888","888","7"};
Object[] o3={"777","777","777","8"};
batchArgs.add(o1);
batchArgs.add(o2);
batchArgs.add(o3);
bookService.batchUpdate(batchArgs);
}
批量修改成功。
九.JdbcTemplate 操作数据库(批量删除)
@Override
public void batchDeleteUser(List<Object[]> batchArgs) {
String sql="delete from user where id =?;";
int[] ints = jdbcTemplate.batchUpdate(sql, batchArgs);
System.out.println(Arrays.toString(ints));
}
写一个单元测试方法:
@Test
public void test10(){
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
BookService bookService = context.getBean("bookService", BookService.class);
List<Object[]> batchArgs=new ArrayList<>();
Object[] o1={"6"};
Object[] o2={"7"};
Object[] o3={"8"};
batchArgs.add(o1);
batchArgs.add(o2);
batchArgs.add(o3);
bookService.batchDelete(batchArgs);
}
批量删除成功。
第五部分 事务
一.事务的概念
- 什么是事务?
事务是数据库操作最基本单元,逻辑上一组操作,要么都成功,如果有一个失败所有操 作都失败。 - 典型场景:
银行转账 :
lucy 转账 100 元 给 mary
lucy 少 100,mary 多 100 - 事务四个特性(ACID)
(1)原子性
(2)一致性
(3)隔离性
(4)持久性
二.搭建事务操作环境
1.创建数据库表,添加记录
2、创建 service,搭建 dao,完成对象创建和注入关系
(1)service 注入 dao,在 dao 注入 JdbcTemplate,在 JdbcTemplate 注入 DataSource
@Service
public class UserService {
//注入UserDao对象
@Autowired
private UserDao userDao;
}
public interface UserDao {
}
@Repository
public class UserDaoImpl implements UserDao{
//注入JdbcTemplate对象
@Autowired
private JdbcTemplate jdbcTemplate;
}
<?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:component-scan base-package="com.xiangll.spring5"></context:component-scan>
<!-- 数据库连接池 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
destroy-method="close">
<property name="url" value="jdbc:mysql:///test" />
<property name="username" value="root" />
<property name="password" value="131702" />
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
</bean>
<!--JdbcTemplate对象-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!--注入dataSource-->
<property name="dataSource" ref="dataSource"></property>
</bean>
</beans>
3.在 dao 创建两个方法:多钱和少钱的方法,在 service 创建方法(转账的方法)
@Service
public class UserService {
//注入UserDao对象
@Autowired
private UserDao userDao;
//转账的操作
public void accountMoney(){
//lucy少钱
userDao.reduceMoney();
//mary多钱
userDao.addMoney();
}
}
@Repository
public class UserDaoImpl implements UserDao{
//注入JdbcTemplate对象
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public void reduceMoney() {
String sql="update account set money=money-? where username=?;";
jdbcTemplate.update(sql,100,"lucy");
}
@Override
public void addMoney() {
String sql="update account set money=money+? where username=?;";
jdbcTemplate.update(sql,100,"mary");
}
}
4.上面代码,如果正常执行没有问题的
但是如果代码执行过程中出现异常,代码就会出现问题
@Service
public class UserService {
//注入UserDao对象
@Autowired
private UserDao userDao;
//转账的操作
public void accountMoney(){
//lucy少钱
userDao.reduceMoney();
//模拟一个异常
int i =10/0;
//mary多钱
userDao.addMoney();
}
}
执行结果:lucy成功少钱,但是mary没有多钱,出问题。
- 上面问题如何解决呢?
使用事务进行解决 。 - 代码流程:
//转账的操作
public void accountMoney(){
try {
//第一步:开启事务
//第二步:进行业务操作
//lucy少钱
userDao.reduceMoney();
//模拟一个异常
int i =10/0;
//mary多钱
userDao.addMoney();
//第三步:没有发送异常,提交异常
} catch (Exception e) {
//第四步:出现异常,事务回滚
}
}
三.Spring 事务管理介绍
- spring中的三层结构:
WEB层
Service层:业务操作
Dao层:数据库操作,不写业务 - 事务添加到 JavaEE 三层结构里面 Service 层(业务逻辑层)
- 在 Spring 进行事务管理操作有两种方式:
(1)编程式事务管理
(2)声明式事务管理(真正使用) - 声明式事务管理又有两种方式:
(1)基于 xml 配置文件方式
(2)基于注解方式(真正使用) - 在 Spring 进行声明式事务管理,底层使用 AOP 原理
- Spring 事务管理 API :
四.事务操作
4.1 注解声明式事务管理
第一步:在 spring 配置文件配置事务管理器
<!--创建事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--注入数据源-->
<property name="dataSource" ref="dataSource"></property>
</bean>
第二步:在 spring 配置文件,开启事务注解
(1)在 spring 配置文件引入名称空间 tx
<?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"
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/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
(2)开启事务注解
<!--开启事务注解-->
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
第三步:3、在 service 类上面(或者 service 类里面方法上面)添加事务注解
(1)@Transactional,这个注解添加到类上面,也可以添加方法上面
(2)如果把这个注解添加类上面,这个类里面所有的方法都添加事务
(3)如果把这个注解添加方法上面,为这个方法添加事务
@Service
@Transactional
public class UserService {
4.2 声明式事务管理参数配置
- 在 service 类上面添加注解@Transactional,在这个注解里面可以配置事务相关参数
- 参数逐个解释:
1.propagation:事务传播行为
- 什么是事务传播行为?
多事务方法直接进行调用,这个过程中事务是如何进行管理的 - 事务方法:对数据库表数据进行变化的操作
简而言之,就是考虑在一个事务方法中调用一个非事务方法或者在一个非事务方法中调用一个事务方法或事务方法之间相互调用,它们之间该怎么进行处理。
- 代码:
@Transactional(propagation = Propagation.REQUIRED)//默认情况下
2.ioslation:事务隔离级别
- 事务有特性成为隔离性,多事务操作之间不会产生影响。不考虑隔离性产生很多问题
- 有三个读问题:脏读、不可重复读、幻读
(1)脏读:一个未提交事务读取到另一个未提交事务的数据
(2)不可重复读:一个未提交事务读取到另一提交事务修改数据
(3)幻读:一个未提交事务读取到另一提交事务添加数据 - 解决:通过设置事务隔离级别,解决读问题
- 代码:
@Service
@Transactional(propagation = Propagation.REQUIRED,isolation = Isolation.READ_COMMITTED)
public class UserService {
4.timeout:超时时间
- 事务需要在一定时间内进行提交,如果不提交进行回滚
- 默认值是 -1 ,设置时间以秒单位进行计算
@Service
@Transactional(timeout = -1,propagation = Propagation.REQUIRED,isolation = Isolation.READ_COMMITTED)
public class UserService {
5.readOnly:是否只读
(1)读:查询操作,写:添加修改删除操作
(2)readOnly 默认值 false,表示可以查询,可以添加修改删除操作
(3)设置 readOnly 值是 true,设置成 true 之后,只能查询
- 代码:
@Service
@Transactional(readOnly = false,timeout = -1,propagation = Propagation.REQUIRED,isolation = Isolation.READ_COMMITTED)
public class UserService {
6.rollbackFor:回滚
- 设置出现哪些异常进行事务回滚
7.noRollbackFor:不回滚
- 设置出现哪些异常不进行事务回滚
4.3 XML 声明式事务管理
1.在 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: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 http://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 http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--组件扫描-->
<context:component-scan base-package="com.xiangll.spring5"></context:component-scan>
<!-- 数据库连接池 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
destroy-method="close">
<property name="url" value="jdbc:mysql:///test" />
<property name="username" value="root" />
<property name="password" value="131702" />
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
</bean>
<!--JdbcTemplate对象-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!--注入dataSource-->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--1 创建事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--注入数据源-->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--2 配置通知-->
<tx:advice id="txadvice">
<!--配置事务参数-->
<tx:attributes>
<!--指定哪种规则的方法上面添加事务-->
<tx:method name="accountMoney" propagation="REQUIRED"/>
<!--<tx:method name="account*"/>-->
</tx:attributes>
</tx:advice>
<!--3 配置切入点和切面-->
<aop:config>
<!--配置切入点-->
<aop:pointcut id="pt" expression="execution(* com.xiangll.spring5.service.UserService.*(..))"/>
<!--配置切面-->
<aop:advisor advice-ref="txadvice" pointcut-ref="pt"/>
</aop:config>
</beans>
4.4 完全注解声明式事务管理
- 创建配置类,使用配置类替代 xml 配置文件
@Configuration//配置类
@ComponentScan(basePackages = "com.xiangll.spring5")
@EnableTransactionManagement //开启事务
public class TxConfig {
//创建数据库连接池
@Bean
public DruidDataSource getDruidDataSource(){
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql:///test");
dataSource.setUsername("root");
dataSource.setPassword("131702");
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;
}
}
第六部分 Spring5 新功能
(1)整合日志框架
(2)@Nullable 注解
(3)函数式注册对象
(4)整合 JUnit5 单元测试框架
(5)SpringWebflux 使用
这部分内容暂时没看,略。