尚硅谷 Spring 5
视频地址:
Spring 概念
IoC 容器
IoC 底层原理
IoC 接口(BeanFactory)
IoC 操作 Bean 管理(基于xml)
IoC 操作 Bean 管理(基于注解)
AOP
JdbcTemplate
事务管理
Spring 5 新特性
Spring 是轻量级的开源的 JavaEE 框架
Spring 可以解决企业应用开发的复杂性
两个核心部分:IoC、AOP
Spring 特点
本课程选取 Spring 5.x
1、什么是 IoC ?
控制反转,把对象创建和对象之间的调用过程,交给 Spring 进行管理
使用 IoC 目的:降低耦合度
入门案例就是 IoC 实现
2、IoC 底层原理
xml 解析、工厂模式、反射
1、IoC 思想基于 IoC 容器完成,IoC 容器底层就是对象工厂
2、Spring 提供 IoC 容器实现两种方式(两个接口):
(1) BeanFactory
:IoC 容器基本实现,是 Spring 内部使用接口,一般不提供开发人员使用
加载配置文件时不会创建对象,在获取(使用)对象时才创建
(2) ApplicationContext
: BeanFactory
接口的子接口,提供更多更强大的功能,一般由开发人员使用
加载配置文件时就会将配置的对象创建
3、 ApplicationContext
接口有实现类
1、什么是 Bean 管理
Bean 管理指的是两个操作:Spring 创建对象,Spring 注入属性
2、Bean 管理操作有两种方式
基于 xml 配置文件方式实现
基于注解方式实现
<bean id="alice" class="com.yin.spring5.bean.User"></bean>
在 Spring 配置文件中,使用 bean 标签,标签中添加对应属性,就可以实现对象创建
bean 标签有很多属性,常用属性:
id
:唯一标识class
:全类名创建对象的时候,默认执行无参构造器
DI:Dependency Injection,依赖注入,是 IoC 的一种实现方式
第一种注入方式:set 方法注入
<!--set 方法注入-->
<bean id="alice" class="com.yin.spring5.bean.User"><property name="name" value="Alice"/><property name="age" value="20"/>
</bean>
第二种注入方式:有参构造器注入
<!--有参构造器注入-->
<bean id="bob" class="com.yin.spring5.bean.User"><constructor-arg name="name" value="Bob"/><constructor-arg name="age" value="20"/>
</bean>
p 名称空间注入(了解即可)
添加约束:
xmlns:p=""
<!--p命名空间-->
<bean id="cindy" class="com.yin.spring5.bean.User" p:name="Cindy" p:age="18">
</bean>
c 名称空间注入(了解即可)
添加约束:
xmlns:c=""
<!--c命名空间-->
<bean id="david" class="com.yin.spring5.bean.User" c:name="David" c:age="18">
</bean>
字面量
注入 null
<!--注入null-->
<bean id="eva" class="com.yin.spring5.bean.User"><property name="name" value="Eva"/><property name="age"><null/></property>
</bean>
属性值包含特殊字符
将特殊字符进行转义
<!--属性值包含特殊字符-->
<bean id="special" class="com.yin.spring5.bean.User"><!--将特殊字符进行转义--><property name="name" value="<<TRUMP>>"/><property name="age" value="18"/>
</bean>
输出:User{name='<<TRUMP>>', age=18}
使用 CDATA
<!--属性值包含特殊字符-->
<bean id="special" class="com.yin.spring5.bean.User"><!--使用 CDATA--><property name="name"><value><![CDATA[<<BIDEN>>]]></value></property><property name="age" value="18"/>
</bean>
输出:User{name='<<BIDEN>>', age=18}
外部 bean
public class UserDao {public void delete() {System.out.println("UserDao#delete()...");}
}
public class UserService {UserDao dao;public void setDao(UserDao dao) {this.dao = dao;}public void deleteUser() {System.out.println("UserService#deleteUser()...");dao.delete();}
}
<bean id="userDao" class="com.yin.spring5.dao.UserDao">
</bean>
<bean id="userService" class="com.yin.spring5.service.UserService"><!--注入外部 bean--><property name="dao" ref="userDao"/>
</bean>
内部 bean
public class Department {private String departName;// getter setter toString 等方法在此省略
}
public class Employee {private String empName;private String email;private Department dept;// getter setter toString 等方法在此省略
}
<bean id="employee" class="com.yin.spring5.bean.Employee"><property name="empName" value="杰克"/><property name="email" value="jack@jack"/><!--内部 bean--><property name="dept"><bean class="com.yin.spring5.bean.Department"><property name="departName" value="开发部"/></bean></property>
</bean>
输出:Employee{empName='杰克', email='jack@jack', dept=Department{departName='开发部'}}
级联赋值
写法一:
<bean id="employee2" class="com.yin.spring5.bean.Employee"><property name="empName" value="Pony"/><property name="email" value="pony@pony"/><property name="dept" ref="department"/>
</bean>
<bean id="department" class="com.yin.spring5.bean.Department"><property name="departName" value="运维部"/>
</bean>
输出:Employee{empName='Pony', email='pony@pony', dept=Department{departName='运维部'}}
写法二:
<bean id="employee2" class="com.yin.spring5.bean.Employee"><property name="empName" value="Pony"/><property name="email" value="pony@pony"/><property name="dept" ref="department"/><!--注意这里--><property name="dept.departName" value="开发部"/>
</bean>
<bean id="department" class="com.yin.spring5.bean.Department"><property name="departName" value="运维部"/>
</bean>
输出:Employee{empName='Pony', email='pony@pony', dept=Department{departName='开发部'}}
注入数组类型属性
<!--数组类型注入-->
<property name="array"><array><value>array1</value><value>array2</value><value>array3</value></array>
</property>
注入 List 集合类型属性
<!--List 类型注入-->
<property name="list"><list><value>list1</value><value>list2</value><value>list3</value></list>
</property>
注入 Map 集合类型属性
<!--Map 类型注入-->
<property name="map"><map><entry key="key1" value="value1"/><entry key="key2" value="value2"/><entry key="key3" value="value3"/></map>
</property>
注入 Set 集合类型属性
<!--Set 类型注入-->
<property name="set"><set><value>set1</value><value>set2</value><value>set3</value></set>
</property>
输出:
Student{stuName='Pony', array=[array1, array2, array3], list=[list1, list2, list3], map={key1=value1, key2=value2, key3=value3}, set=[set1, set2, set3]}
在集合里面设置对象类型
<bean id="student" class="com.yin.spring5.bean.Student"><!--省略部分代码,只看有关的--><!--在集合里面设置对象类型--><property name="courses"><list><ref bean="course1"/><ref bean="course2"/></list></property>
</bean><bean id="course1" class="com.yin.spring5.bean.Course"><property name="courseName" value="数据结构"/><property name="credit" value="5"/>
</bean>
<bean id="course2" class="com.yin.spring5.bean.Course"><property name="courseName" value="操作系统"/><property name="credit" value="4"/>
</bean>
输出:
[Course{courseName='数据结构', credit=5}, Course{courseName='操作系统', credit=4}]
把集合注入部分提取出来
a. 在配置文件中引入 util
名称空间
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns=""xmlns:xsi=""xmlns:util=""xsi:schemaLocation=" .xsd .xsd"></beans>
b. 使用 util
标签完成 List 集合注入提取
public class Book {private List<String> list;// getter setter toString 等方法在此省略
}
<bean id="book" class="com.yin.spring5.bean.Book"><property name="list" ref="bookList"/>
</bean><!--List 集合注入提取-->
<util:list id="bookList"><value>Java编程思想</value><value>算法导论</value><value>Effective Java</value>
</util:list>
输出:
[Java编程思想, 算法导论, Effective Java]
普通 bean:配置文件中定义的 bean 类型就是返回类型
工厂 bean:配置文件中定义的 bean 类型可以和返回类型不一样
FactoryBean
接口/*** MyFactoryBean 工厂Bean*/
public class MyFactoryBean implements FactoryBean<Course> {@Overridepublic Course getObject() throws Exception {return new Course();}@Overridepublic Class<?> getObjectType() {return null;}@Overridepublic boolean isSingleton() {return false;}
}
<bean id="myFactoryBean" class="com.yin.spring5.bean.MyFactoryBean">
</bean>
@Test
public void test3() {ApplicationContext ioc =new ClassPathXmlApplicationContext(l");Course course = Bean("myFactoryBean", Course.class);course.setCourseName("数据结构");course.setCredit(5);System.out.println(course);
}
测试:
Course{courseName='数据结构', credit=5}
在 Spring 中,设置创建 bean 实例是单实例还是多实例(默认单实例)
@Test
public void test4() {ApplicationContext ioc =new ClassPathXmlApplicationContext(l");Book book1 = Bean("book", Book.class);Book book2 = Bean("book", Book.class);// 这里打印对象地址,需要把其 toString() 方法注释掉System.out.println(book1);System.out.println(book2);System.out.println(book1==book2);
}
输出:
com.yin.spring5.bean.Book@433defed
com.yin.spring5.bean.Book@433defed
true
如何设置单实例/多实例?bean 标签中 scope
属性设置作用域
scope
属性值:
singleton
:单实例prototype
:多实例将 scope 属性值设置为 `` 后,再进行上面测试:
<bean id="book" class="com.yin.spring5.bean.Book" scope="prototype">
输出:
com.yin.spring5.bean.Book@548a24a
com.yin.spring5.bean.Book@433defed
false
singleton
和 prototype
的区别:
singleton
是单实例,prototype
是多实例
singleton
在 Spring 加载配置文件时就会创建单实例对象
prototype
在获取对象时才会进行创建
生命周期:从对象创建到销毁的过程
bean 生命周期:
代码验证:
public class User {private String name;private Integer age;private Character gender;public User() {System.out.println("User 无参构造器执行。。。");}public String getName() {return name;}public void setName(String name) {System.out.println("User.setName 执行。。。");this.name = name;}public Integer getAge() {return age;}public void setAge(Integer age) {System.out.println("User.setAge 执行。。。");this.age = age;}public Character getGender() {return gender;}public void setGender(Character gender) {System.out.println("User.setGender 执行。。。");der = gender;}public void init() {System.out.println("自定义 User.init 执行。。。");}public void destroy() {System.out.println("自定义 User.destroy 执行。。。");}
}
<bean id="user" class="com.yin.spring5.bean.User"init-method="init" destroy-method="destroy"><property name="name" value="Alice"/><property name="gender" value="F"/><property name="age" value="20"/>
</bean>
@Test
public void test5() {ClassPathXmlApplicationContext ioc =new ClassPathXmlApplicationContext(l");User user = Bean("user", User.class);System.out.println(user);// 关闭容器ioc.close();
}
输出:
User 无参构造器执行。。。
User.setName 执行。。。
User.setGender 执行。。。
User.setAge 执行。。。
自定义 User.init 执行。。。
com.yin.spring5.bean.User@16eb3ea3
自定义 User.destroy 执行。。。
其实完整来讲,bean 的声明周期有 7 步。在初始化方法执行前后,分别有 postProcessBeforeInitialization
和 postProcessAfterInitialization
方法执行。
在上述代码的基础上,再添加以下内容:
public class MyBeanPost implements BeanPostProcessor {@Overridepublic Object postProcessBeforeInitialization(Object bean,String beanName) throws BeansException {System.out.println(" postProcessBeforeInitialization 执行。。。");return null;}@Overridepublic Object postProcessAfterInitialization(Object bean,String beanName) throws BeansException {System.out.println(" postProcessAfterInitialization 执行。。。");return null;}
}
<bean id="myBeanPost" class="com.yin.spring5.bean.MyBeanPost"></bean>
再次进行测试,输出如下:
User 无参构造器执行。。。
User.setName 执行。。。
User.setGender 执行。。。
User.setAge 执行。。。postProcessBeforeInitialization 执行。。。
自定义 User.init 执行。。。postProcessAfterInitialization 执行。。。
com.yin.spring5.bean.User@33bc72d1
自定义 User.destroy 执行。。。
综上,bean 生命周期完整表述如下:
postProcessBeforeInitialization
postProcessAfterInitialization
什么是自动装配?
根据指定装配规则(属性名称或属性类型),Spring 自动将匹配的属性值进行注入
bean 标签中的 autowire
属性可以配置自动装配,autowire
常用属性值:
byName
:根据名称装配(注入 bean 的 id 要与被注入 bean 的 set 方法一致)byType
:根据类型装配(有多个同类型的 bean 时,此属性值无法使用)代码演示:
public class Department {// 此处略去 get set toString 等方法private String deptName;
}
public class Employee {// 此处略去 get set toString 等方法private String empName;private Department dept;
}
<bean id="dept" class="com.yin.spring5.autowire.Department"><property name="deptName" value="开发部"/>
</bean>
<bean id="dept2" class="com.yin.spring5.autowire.Department"><property name="deptName" value="测试部"/>
</bean><bean id="emp" class="com.yin.spring5.autowire.Employee" autowire="byName"><property name="empName" value="Pony"/>
</bean>
输出:
Employee{empName='Pony', dept=Department{deptName='开发部'}}
若将 Employee
类中默认生成的 setDept
方法名修改为 setDept2
,则输出变为:
Employee{empName='Pony', dept=Department{deptName='测试部'}}
由此证实:byName
的自动注入是根据 set 方法判断的,而不是类中的属性名。
以数据库连接池为例:
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"><property name="driverClassName" value=sql.cj.jdbc.Driver"/><property name="url" value="jdbc:mysql://localhost:3306/jdbc_template?serverTimezone=Asia/Shanghai"/><property name="username" value="root"/><property name="password" value="123456"/>
</bean>
将数据库配置抽取到 properties 文件
jdbc.driversql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/jdbc_template?serverTimezone=Asia/Shanghai
jdbc.username=root
jdbc.password=123456
则 Spring 配置文件改写如下,注意需要引入 context 名称空间:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns=""xmlns:xsi=""xmlns:context=""xsi:schemaLocation=" .xsd .xsd"><!--引入外部 properties 文件--><context:property-placeholder location="classpath:db-config.properties"/><bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"><!--取出 properties 文件中的值--><property name="driverClassName" value="${jdbc.driver}"/><property name="url" value="${jdbc.url}"/><property name="username" value="${jdbc.username}"/><property name="password" value="${jdbc.password}"/></bean>
</beans>
Spring 创建 bean 的四个注解:@Component
,@Controller
,@Service
,@Repository
。
<!--开启组件扫描,将会扫描 base-package 及其子包下所有组件-->
<context:component-scan base-package="com.yin.spring5"/>
排除某些组件,以排除 @Controller
组件扫描为例:
<!--开启组件扫描-->
<context:component-scan base-package="com.yin.spring5"><!--排除 Controller 组件扫描--><context:exclude-filter type="annotation"expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
context:component-scan
标签部分属性:
base-package
:要扫描组件的基础包,默认将会扫描 base-package 及其子包下所有组件use-default-filters
:是否使用默认的扫描规则context:component-scan
标签的子标签:
context:exclude-filter
:指定排除规则context:include-filter
:指定包含规则@Autowired
:根据属性类型自动装配@Qualifier
:根据属性名称注入@Resource
:可以根据类型注入,可以根据名称注入(这是 javax 包下的注解,已移除)@Value
:注入普通类型属性AOP,Aspect Oriented Programming,面向切面编程
利用 AOP 可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
AOP 底层使用动态代理。有两种情况:
有接口,使用 JDK 动态代理
创建接口实现类代理对象,增强类的方法
没有接口,使用 CGLIB 动态代理
创建子类的代理对象,增强类的方法
使用 JDK 动态代理,使用 flect.Proxy
类里面的方法创建代理对象
代码演示:
创建接口,定义方法
public interface UserDao {int add(int a, int b);String update(String id);
}
创建接口实现类,实现方法
public class UserDaoImpl implements UserDao {@Overridepublic int add(int a, int b) {System.out.println("add 方法执行");return a + b;}@Overridepublic String update(String id) {System.out.println("update 方法执行");return id;}
}
使用 flect.Proxy
类创建接口代理对象
public class JdkProxy {public static void main(String[] args) {// 创建接口实现类代理对象Class[] interfaces = {UserDao.class};UserDao userDao = new UserDaoImpl(); // 被代理对象UserDao proxyInstance =(UserDao) wProxyInstance(ClassLoader(),interfaces, new UserDaoProxy(userDao));System.out.println(proxyInstance.add(2, 3));System.out.println(proxyInstance.update("abc"));}
}// 创建代理对象代码
class UserDaoProxy implements InvocationHandler {private Object obj;// 把被代理对象传递过来public UserDaoProxy(Object obj) {this.obj = obj;}// 增强的逻辑@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// 方法之前System.out.Name() + " 方法之前执行,参数:" + String(args));// 被增强的方法执行Object returnValue = method.invoke(obj, args);// 方法之后System.out.Name() + " 方法之后执行");return returnValue;}
}
输出:
add 方法之前执行,参数:[2, 3]
add 方法执行
add 方法之后执行
5
update 方法之前执行,参数:[abc]
update 方法执行
update 方法之后执行
abc
Spring 框架一般基于 AspectJ 实现 AOP 操作
AspectJ 不是 Spring 组成部分,而是独立的 AOP 框架,但是一般把 AspectJ 和 Spring 框架一起使用,进行 AOP 操作
Spring 基于 AspectJ 实现 AOP 操作有两种方式:xml 配置文件方式、注解方式。
<dependencies><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>${spring.version}</version></dependency><!-- .springframework/spring-aspects --><dependency><groupId>org.springframework</groupId><artifactId>spring-aspects</artifactId><version>${spring.version}</version></dependency>
</dependencies>
切入点表达式作用:指明对哪个类的哪个方法进行增强
语法结构:
execution([权限修饰符][返回类型][全类名][方法名]([参数列表]))
例 1:对 com.yin.spring5.UserDao#add
方法进行增强
execution(* com.yin.spring5.UserDao.add(..)) // 权限修饰符省略
例 2:对 com.yin.spring5.UserDao
类中所有方法增强
execution(* com.yin.spring5.UserDao.*(..)) // 权限修饰符省略
例 2:对 com.yin.spring5
包下所有类中所有方法增强
execution(* com.yin.spring5.*.*(..)) // 权限修饰符省略
创建类,在类中定义方法
public class User {public void add() {System.out.println("User.add 方法执行");}
}
创建增强类,编写增强逻辑
在增强类中,创建方法,让不同的方法代表不同的通知
public class UserProxy {// 前置通知public void before() {System.out.println("UserProxy.before 方法执行");}......
}
进行通知的配置
配置文件开启注解扫描
<context:component-scan base-package="com.yin.spring5.anno"/>
使用注解创建 User 和 UserProxy 对象
@Component
public class User {...}
@Component
public class UserProxy {...}
在增强类上面添加注解 @Aspect
@Component
@Aspect
public class UserProxy {...}
配置文件开启生成代理对象
<!--开启 Aspect 生成代理对象,要引入aop名称空间-->
<aop:aspectj-autoproxy/>
配置不同类型的通知
在增强类的里面,在作为通知方法上面添加通知类型注解,使用切入点表达式配置
@Component
@Aspect
public class UserProxy {// 前置通知@Before("execution(* com.yin.spring5.anno.User.add(..))")public void before() {System.out.println("UserProxy.before 方法执行");}// 最终通知@After("execution(* com.yin.spring5.anno.User.add(..))")public void after() {System.out.println("UserProxy.after 方法执行");}// 后置通知(返回通知)@AfterReturning("execution(* com.yin.spring5.anno.User.add(..))")public void afterReturning() {System.out.println("UserProxy.afterReturning 方法执行");}// 异常通知@AfterThrowing("execution(* com.yin.spring5.anno.User.add(..))")public void afterThrowing() {System.out.println("UserProxy.afterThrowing 方法执行");}// 环绕通知@Around("execution(* com.yin.spring5.anno.User.add(..))")public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {System.out.println("环绕之前。。。");// 被增强的方法执行proceedingJoinPoint.proceed();System.out.println("环绕之后。。。");}
}
测试:
@Test
public void test1() {ApplicationContext context = new ClassPathXmlApplicationContext(l");User user = Bean("user", User.class);user.add();
}
输出:
环绕之前。。。
UserProxy.before 方法执行
User.add 方法执行
UserProxy.afterReturning 方法执行
UserProxy.after 方法执行
环绕之后。。。
抽取相同的切入点
@Component
@Aspect
public class UserProxy {// 抽取相同的切入点@Pointcut("execution(* com.yin.spring5.anno.User.add(..))")public void pointDemo() {}// 前置通知@Before("pointDemo()")public void before() {System.out.println("UserProxy.before 方法执行");}...
}
有多个增强类对同一个方法进行增强,设置增强类优先级
在增强类上添加注解 @Order(value = )
,数字越小,优先级越高
完全使用注解
创建配置类,不需要创建 xml 配置文件
@Configuration
@ComponentScan(basePackages = "com.yin.spring5.anno")
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class AopConfig {
}
什么是 JdbcTemplate ?
Spring 对 JDBC 进行封装,使用 JdbcTemplate 方便实现对数据库操作
<dependencies><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>${spring.version}</version></dependency><!-- .springframework/spring-jdbc --><dependency><groupId>org.springframework</groupId><artifactId>spring-jdbc</artifactId><version>${spring.version}</version></dependency><!-- --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.19</version></dependency><!-- .alibaba/druid --><dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.1.22</version></dependency>
</dependencies>
<!--引入外部数据库配置文件-->
<context:property-placeholder location="classpath:db-config.properties"/>
<!--Druid 数据源-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"destroy-method="close"><property name="driverClassName" value="${jdbc.driverClass}"/><property name="url" value="${jdbc.url}"/><property name="username" value="${jdbc.username}"/><property name="password" value="${jdbc.password}"/>
</bean>
<!--JdbcTemplate-->
<bean id="jdbcTemplate" class="org.JdbcTemplate"><!--注入数据源--><property name="dataSource" ref="dataSource"/>
</bean>
编写 Book 实体类,创建对应数据表
public class Book {private Integer id;private String title;private String author;private BigDecimal price;// 此处省略 getter setter toString 等方法
}
环境一览
<?xml version="1.0" encoding="UTF-8"?>
<beans><!--注意:此处文件头等信息略去--><!--组件扫描--><context:component-scan base-package="com.yin.spring5"/><!--引入外部数据库配置文件--><context:property-placeholder location="classpath:db-config.properties"/><!--Druid 数据源--><bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"destroy-method="close"><property name="driverClassName" value="${jdbc.driverClass}"/><property name="url" value="${jdbc.url}"/><property name="username" value="${jdbc.username}"/><property name="password" value="${jdbc.password}"/></bean><!--JdbcTemplate--><bean id="jdbcTemplate" class="org.JdbcTemplate"><!--注入数据源--><property name="dataSource" ref="dataSource"/></bean>
</beans>
public interface BookDao {
}
@Repository
public class BookDaoImpl implements BookDao {JdbcTemplate jdbcTemplate;@Autowiredpublic void setJdbcTemplate(JdbcTemplate jdbcTemplate) {this.jdbcTemplate = jdbcTemplate;}
}
@Service
public class BookService {BookDao bookDao;@Autowiredpublic void setBookDao(BookDao bookDao) {this.bookDao = bookDao;}
}
@Override
public int insert(Book book) {String sql = "INSERT INTO book(title,author,price) VALUES(?,?,?)";return jdbcTemplate.update(sql, Title(), Author(), Price());
}
@Override
public int update(Book book) {String sql = "UPDATE book SET title=?, author=?, price=? WHERE id=?";return jdbcTemplate.update(sql, Title(), Author(),Price(), Id());
}
@Override
public int delete(Integer id) {String sql = "DELETE FROM book WHERE id=?";return jdbcTemplate.update(sql, id);
}
@Override
public int recordCount() {String sql = "SELECT COUNT(*) FROM book";return jdbcTemplate.queryForObject(sql, Integer.TYPE);
}
@Override
public Book getOneById(Integer id) {String sql = "SELECT id, title, author, price FROM book WHERE id=?";return jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(Book.class), id);
}
@Override
public List<Book> getAll() {String sql = "SELECT * FROM book";return jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(Book.class));
}
@Override
public int[] batchInsert(List<Object[]> bookList) {String sql = "INSERT INTO book(title,author,price) VALUES(?,?,?)";return jdbcTemplate.batchUpdate(sql, bookList);
}
@Test
public void testBatchAdd() {ApplicationContext context = new ClassPathXmlApplicationContext(l");BookService bookService = Bean("bookService", BookService.class);List<Object[]> bookList = new ArrayList<>();// 按占位符顺序写参数Object[] book1 = {"城南旧事", "林海音", 29.00};Object[] book2 = {"追风筝的人", "忘了", 29.00};Object[] book3 = {"小王子", "安东尼", 19.00};bookList.add(book1);bookList.add(book2);bookList.add(book3);bookService.batchAddBook(bookList);
}
@Override
public int[] batchUpdate(List<Object[]> bookList) {String sql = "UPDATE book SET title=?, author=?, price=? WHERE id=?";return jdbcTemplate.batchUpdate(sql, bookList);
}
@Test
public void testBatchUpdate() {ApplicationContext context = new ClassPathXmlApplicationContext(l");BookService bookService = Bean("bookService", BookService.class);List<Object[]> bookList = new ArrayList<>();// 按占位符顺序写参数Object[] book1 = {"城南旧事", "林海音(台湾)", 26.00, 24};Object[] book2 = {"追风筝的人", "还是没想起来", 29.00, 25};Object[] book3 = {"小王子", "安东尼奥", 20.00, 26};bookList.add(book1);bookList.add(book2);bookList.add(book3);bookService.batchUpdateBook(bookList);
}
@Override
public int[] batchDelete(List<Object[]> idList) {String sql = "DELETE FROM book WHERE id=?";return jdbcTemplate.batchUpdate(sql, idList);
}
@Test
public void testBatchDelete() {ApplicationContext context = new ClassPathXmlApplicationContext(l");BookService bookService = Bean("bookService", BookService.class);List<Object[]> idList = new ArrayList<>();// 按占位符顺序写参数Object[] id1 = {19};Object[] id2 = {20};Object[] id3 = {21};Object[] id4 = {25};idList.add(id1);idList.add(id2);idList.add(id3);idList.add(id4);bookService.batchDeleteBook(idList);
}
什么是事务?
事务是数据库操作的最基本单元,逻辑上的一组操作,要么都成功,如果有一个操作失败,所有操作都失败
典型场景:银行转账
事务四个特性 ACID
原子性 Atomicity
原子性是指事务是一个不可分割的工作单位,事务中的操作要么都成功,要么都不成功
一致性 Consistency
事务必须使数据库从一个一致性状态变换到另一个一致性状态
隔离性 Isolation
事务的隔离性是指一个事务的执行不能被其他事务干扰,即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰
持久性 Durability
持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来的其他操作和数据库故障不应该对其有任何影响
引入依赖
<dependencies><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>${spring.version}</version></dependency><!-- .springframework/spring-jdbc --><dependency><groupId>org.springframework</groupId><artifactId>spring-jdbc</artifactId><version>${spring.version}</version></dependency><!-- .springframework/spring-orm --><dependency><groupId>org.springframework</groupId><artifactId>spring-orm</artifactId><version>${spring.version}</version></dependency><!-- --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.19</version></dependency><!-- .alibaba/druid --><dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.1.22</version></dependency>
</dependencies>
创建数据库表
use `jdbc_template`;create table `account` (`id` int primary key auto_increment comment 'id',`name` varchar(50) comment '姓名',`balance` int comment '余额'
);insert into `account`(`name`, `balance`)
VALUES ('亚索', 1000),('永恩', 1000);
编写实体类、DAO、Service 等
public class Account {private Integer id;private String name;private Integer balance;// 此处省略 getter setter toString 等方法
}
public interface AccountDao {/*** 增加金额** @param id 用户id* @param money 金额数目*/void addMoney(int id, int money);/*** 扣除金额** @param id 用户id* @param money 金额数目*/void deductMoney(int id, int money);
}
@Repository
public class AccountDaoImpl implements AccountDao {final JdbcTemplate jdbcTemplate;@Autowiredpublic AccountDaoImpl(JdbcTemplate jdbcTemplate) {this.jdbcTemplate = jdbcTemplate;}@Overridepublic void addMoney(int id, int money) {String sql = "UPDATE account SET balance=balance+? WHERE id=?";jdbcTemplate.update(sql, money, id);}@Overridepublic void deductMoney(int id, int money) {String sql = "UPDATE account SET balance=balance-? WHERE id=?";jdbcTemplate.update(sql, money, id);}
}
public class AccountService {final AccountDao accountDao;@Autowiredpublic AccountService(AccountDao accountDao) {this.accountDao = accountDao;}/*** 转账业务** @param deductId 扣除金额方id* @param addId 增加金额方id* @param money 转账金额数目*/public void transferAccount(int deductId, int addId, int money) {accountDao.deductMoney(deductId, money);accountDao.addMoney(addId, money);}
}
上面的代码,如果正常执行是没有问题的,但是如果代码执行过程中出现异常,会有问题
比如:
public void transferAccount(int deductId, int addId, int money) {accountDao.deductMoney(deductId, money);// 模拟异常int i = 10 / 0;accountDao.addMoney(addId, money);
}
为避免诸如上述问题,就需要事务。
事务操作流程:
事务一般添加到 Service 层
Spring 进行事务管理有两种方式:编程式事务管理 和 声明式事务管理。一般都使用声明式事务管理。
声明式事务管理有两种方式:基于注解,基于 xml 配置文件。
Spring 进行声明式事务管理,底层使用 AOP 原理。
Spring 事务管理 API
提供 PlatformTransactionManager
接口,代表事务管理器,这个接口针对不同框架提供不同的实现类
配置事务管理器
<!--配置事务管理器-->
<bean id="transactionManager"class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><!--注入数据源--><property name="dataSource" ref="dataSource"/>
</bean>
开启事务注解
<!--开启事务注解-->
<tx:annotation-driven transaction-manager="transactionManager"/>
上面讲到,事务一般添加到 Service 层
在 Service 类上(或者类中的方法上)添加事务注解 @Transactional
事务注解 @Transactional
既可以添加到类上,也可以添加到方法上
@Service
@Transactional
public class AccountService {...}
事务注解 @Transactional
部分参数:
propagation
:事务传播类型(默认 REQUIRED
)
多事务方法直接进行调用,这个过程中事务是如何进行管理的
isolation
:事务隔离级别
事务特性之一。不考虑隔离性会产生很多问题,三个读问题:脏读、不可重复读、幻读(虚读)。
timeout
:事务超时时间(秒,默认 -1 即永不超时)
事务需要在指定时间内进行提交,如果不提交就会回滚
readOnly
:事务是否只读(默认 false
)
rollbackFor
:指明哪些异常类型进行事务回滚
noRollbackFor
:指明哪些异常类型不进行事务回滚
事务方法:对数据库表数据进行变化的操作
事务的传播行为可以由传播类型指定,Spring 定义了 7 种传播类型
传播类型 | 描述 |
---|---|
REQUIRED | 如果有事务在运行,当前的方法就在这个事务内运行;否则,就启动一个新的事务,并在自己的事务内运行 |
REQUIRES_NEW | 当前的方法必须启动新事务,并在它自己的事务内运行;如果有事务正在运行,应该将它挂起 |
SUPPORTS | 如果有事务在运行,当前的方法就在这个事务内运行,否则它可以不运行在事务中 |
NOT_SUPPORTED | 当前的方法不应该运行在事务中,如果有运行的事务,将它挂起 |
MANDATORY | 当前的方法必须运行在事务内部,如果没有正在运行的事务,就抛出异常 |
NEVER | 当前的方法不应该运行在事务中,如果有运行的事务,就抛出异常 |
NESTED | 如果有事务在运行,当前的方法就应该在这个事务的嵌套事务内运行;否则,就启动一个新的事务,并在它自己的事务内运行 |
事务隔离级别
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
READ_UNCOMMITTED(读未提交) | 有 | 有 | 有 |
READ_COMMITTED(读已提交) | 无 | 有 | 有 |
REPEATABLE_READ(可重复读,MySQL默认) | 无 | 无 | 有 |
SERIALIZABLE(串行化) | 无 | 无 | 无 |
第一步,配置事务管理器;第二步,配置通知;第三步,配置切入点和切面
<!--基于 XML 的事务-->
<!--1.配置事务管理器-->
<bean id="transactionManager2"class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><!--注入数据源--><property name="dataSource" ref="dataSource2"/>
</bean><!--2.配置通知-->
<tx:advice id="txAdvice"><!--配置事务参数--><tx:attributes><!--指定哪种规则的方法上添加事务--><tx:method name="transferAccount" propagation="REQUIRED"isolation="REPEATABLE_READ"/></tx:attributes>
</tx:advice><!--3.配置切入点和切面-->
<aop:config><!--配置切入点--><aop:pointcut id="myPointCut"expression="execution(* com.yin.spring5.service.AccountService.*(..))"/><!--配置切面--><aop:advisor advice-ref="txAdvice" pointcut-ref="myPointCut"/>
</aop:config>
创建配置类,代替 xml 配置文件
@Configuration // 配置类
@ComponentScan(basePackages = "com.yin.spring5") // 组件扫描
@EnableTransactionManagement // 开启事务
public class TxConfig {@Beanpublic DataSource dataSource() {DruidDataSource dataSource = new DruidDataSource();dataSource.setDriverClassName(sql.cj.jdbc.Driver");dataSource.setUrl("jdbc:mysql://localhost:3306/jdbc_template" +"?serverTimezone=Asia/Shanghai");dataSource.setUsername("root");dataSource.setPassword("123456");return dataSource;}@Beanpublic JdbcTemplate jdbcTemplate(DataSource dataSource) {JdbcTemplate jdbcTemplate = new JdbcTemplate();jdbcTemplate.setDataSource(dataSource);return jdbcTemplate;}@Beanpublic DataSourceTransactionManager transactionManager(DataSource dataSource) {DataSourceTransactionManager transactionManager =new DataSourceTransactionManager();transactionManager.setDataSource(dataSource);return transactionManager;}
}
Spring 5 基于 Java 8,运行时兼容 Java 9,移除了不建议使用的类和方法。
Spring 5 自带了通用的日志封装
Spring 5 移除了 Log4jConfigListener
,官方建议使用 Log4j2
Spring 5 核心注解支持 @Nullable
注解
@Nullable
注解可以用在方法上、属性上、参数上,表示方法返回值、属性、参数可以为空
支持函数式风格 GenericApplicationContext / AnnotationConfigApplicationContext
@Test
public void test4() {GenericApplicationContext context = new GenericApplicationContext();fresh();// 注册 isterBean("acct", Account.class, Account::new);// 获取注册的对象Account acct = Bean("acct", Account.class);System.out.println(acct);
}
Spring 5 支持整合 JUnit 5
Spring 5 整合 Log4j2
Spring WebFlux 是 Spring 5 新增的模块,用于 Web 开发,功能与 Spring MVC 类似,使用当前比较流行的响应式编程
使用传统 Web 框架,比如 Spring MVC,这些基于 Servlet 容器。WebFlux 是一种异步非阻塞式的框架,异步非阻塞的框架在 Servlet 3.1 以后才支持,核心是基于 Reactor 的相关 API 实现的。
同步与异步
异步和同步针对调用者。调用者发送请求,如果等着对方回应之后才去左其他事就是同步;如果发送请求后不等着对方回应就去做其他事就是异步。
阻塞与非阻塞
阻塞和非阻塞针对被调用者。被调用者收到请求之后,做完请求任务之后才给出反馈就是阻塞,收到请求之后马上给出反馈然后再去做事情就是非阻塞。
响应式编程是一种面向数据流和变化传播的编程范式。这意味着可以在编程语言中很方便地表达静态或动态的数据流,而相关的计算模型会自动将变化的值通过数据流进行传播。
例如,在命令式编程环境中,a=b+c
表示将表达式的结果赋给 a,而之后改变 b 或 c 的值不会影响 a。但在响应式编程中,a 的值会随着 b 或 c 的更新而更新。
电子表格程序就是响应式编程的一个例子。单元格可以包含字面值或类似 “=B1+C1” 的公式,而包含公式的单元格的值会依据其他单元格的值的变化而变化。
提供的观察者模式两个类:Observer 接口,Observable 类(两者从 Java 9 开始被遗弃)
引入依赖
<!-- .projectreactor/reactor-core -->
<dependency><groupId>io.projectreactor</groupId><artifactId>reactor-core</artifactId><version>3.4.0</version>
</dependency>
调用 just 方法或者其他方法只是声明数据流,数据流并没有发出,只有进行订阅之后才会触发数据流,不订阅什么都不会发生。
// just 方法直接声明
Flux.just(1, 2, 3, 4).subscribe(System.out::println);
Mono.just(5).subscribe(System.out::println);// 其他的方法
Integer[] array = {1,2,3,4};
Flux.fromArray(array);
List<Integer> list = Arrays.asList(array);
Flux.fromIterable(list);
Stream<Integer> stream = list.stream();
Flux.fromStream(stream);
对数据流进行一道道操作,称为操作符,比如工厂流水线
map:将元素映射为新元素
flatMap:将元素映射为流。把每个元素转换流,把转换之后多个流合并为大的流
Spring WebFlux 基于 Reactor,默认使用容器是 Netty,Netty 是高性能 NIO(Non-blocking IO,非阻塞IO) 框架。(与 NIO 相对的是 BIO,即 Blocking IO)
BIO 的通信方式:
NIO 的通信方式:
Spring WebFlux 执行过程与 Spring MVC 相似
Spring WebFlux 核心控制器 DispatchHandler
,实现接口 WebHandler
。
DispatcherHandler.handle
方法:
在 Spring WebFlux 中,DispatcherHandler 负责请求的处理
以下三个都是接口:
HandlerMapping
:请求查询到处理的方法。
Interface to be implemented by objects that define a mapping between requests and handler objects.
由定义请求和处理程序对象之间的映射关系的对象实现的接口。
HandlerAdapter
:真正负责请求处理。
Contract that decouples the {@link DispatcherHandler} from the details of invoking a handler and makes it possible to support any handler type.
使 DispatcherHandler 与调用处理程序的详细信息分离的契约,并且可以支持任何处理程序类型。
HandlerResultHandler
:响应结果处理。
Process the {@link HandlerResult}, usually returned by an {@link HandlerAdapter}.
处理 HandlerResult,通常由 HandlerAdapter 返回。
Spring WebFlux 实现函数式编程,两个接口:
RouterFunction
:Represents a function that routes to a {@linkplain HandlerFunction handler function}.HandlerFunction
:Represents a function that handles a {@linkplain ServerRequest request}.使用注解方式,与之前 Spring MVC 使用相似,只需要引入相关依赖,Spring Boot 自动配置相关运行容器,默认情况下使用 Netty 服务器。
引入依赖
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
编写代码
public interface UserService {/*** 根据 id 查询 User** @param id 要查询的 id* @return 查询的结果*/Mono<User> getUserById(int id);/*** 查询所有 User** @return 查询的结果*/Flux<User> getAllUsers();/*** 添加 User** @param user 要添加的 User* @return 。*/Mono<Void> saveUser(Mono<User> user);
}
@Service
public class UserServiceImpl implements UserService {// 为了方便,这里使用 Map 代替数据库存储数据private final Map<Integer, User> map = new HashMap<>();public UserServiceImpl() {this.map.put(1, new User(1, "Alice", 'F', 18));this.map.put(2, new User(2, "Bella", 'F', 20));this.map.put(3, new User(3, "Cindy", 'F', 18));this.map.put(4, new User(4, "Diana", 'F', 20));this.map.put(5, new User(5, "Emily", 'F', 18));}@Overridepublic Mono<User> getUserById(int id) {return Mono.justOrEmpty((id));}@Overridepublic Flux<User> getAllUsers() {return Flux.fromIterable(this.map.values());}@Overridepublic Mono<Void> saveUser(Mono<User> userMono) {return userMono.doOnNext(user -> map.Id(), user)).pty());}
}
@RestController
public class UserController {UserService userService;@Autowiredpublic void setUserService(UserService userService) {this.userService = userService;}@GetMapping("/user/{id}")public Mono<User> getUserById(@PathVariable("id") int id) {UserById(id);}@GetMapping("/users")public Flux<User> getAllUsers() {AllUsers();}@PostMapping("/user")public Mono<Void> saveUser(User user) {Mono<User> userMono = Mono.just(user);return userService.saveUser(userMono);}
}
把基于注解的复制一份,删去 controller 部分
创建 Handler
public class UserHandler {private final UserService userService;public UserHandler(UserService userService) {this.userService = userService;}public Mono<ServerResponse> getUserById(ServerRequest request) {// 获取 id 值int id = Integer.parseInt(request.pathVariable("id"));// 空值处理Mono<ServerResponse> notFound = Found().build();// 调用 service 得到数据Mono<User> userMono = UserById(id);// 把 userMono 进行转换返回,使用 Reactor 操作符 flatMapreturn userMono.flatMap(user -> ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(BodyInserters.fromValue(user))).switchIfEmpty(notFound);}public Mono<ServerResponse> getAllUsers() {Flux<User> allUsers = AllUsers();return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(allUsers, User.class);}public Mono<ServerResponse> saveUser(ServerRequest request) {Mono<User> userMono = request.bodyToMono(User.class);return ServerResponse.ok().build(userService.saveUser(userMono));}
}
初始化服务器,编写 Router
public class Server {public static void main(String[] args) throws IOException {Server server = new Server();ateReactorServer();System.out.println("enter to exit");ad();}// 1. 创建 Router 路由public RouterFunction<ServerResponse> routingFunction() {// 创建 handler 对象UserService userService = new UserServiceImpl();UserHandler userHandler = new UserHandler(userService);// 设置路由ute(GET("/user/{id}").and(accept(MediaType.APPLICATION_JSON)), userHandler::getUserById).andRoute(GET("/users").and(accept(MediaType.APPLICATION_JSON)),userHandler::getAllUsers);}// 2. 创建服务器完成适配public void createReactorServer() {// 路由和 handler 适配RouterFunction<ServerResponse> route = routingFunction();HttpHandler httpHandler = toHttpHandler(route);ReactorHttpHandlerAdapter handlerAdapter =new ReactorHttpHandlerAdapter(httpHandler);// 创建服务器HttpServer httpServer = ate();httpServer.handle(handlerAdapter).bindNow();}
}
使用 WebClient 调用
public class Client {public static void main(String[] args) {WebClient webClient = ate("localhost:8235");User user = ().uri("/user/{id}", 1).accept(MediaType.APPLICATION_JSON).retrieve().bodyToMono(User.class).block();System.out.println(user);Flux<User> users = ().uri("/users").accept(MediaType.APPLICATION_JSON).retrieve().bodyToFlux(User.class);users.map(User::toString).buffer().doOnNext(System.out::println).blockFirst();}
}
Spring 框架概述
轻量级开源 JavaEE 框架,核心 IoC 和 AOP
IoC 容器
AOP
JdbcTemplate
事务管理
Spring 5 新特性
@Nullable
注解本文发布于:2024-02-04 17:28:31,感谢您对本站的认可!
本文链接:https://www.4u4v.net/it/170712664057789.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
留言与评论(共有 0 条评论) |