教程来自b站尚硅谷的spring5教程
1 spring框架概念
1.1 spring 概述
课程中选择的是spring5.2.6的版本
笔者编写时官网是5.3.10:
1.2 入门案例
1.首先去https://repo.spring.io/ui/下spring的相关版本jar包。
解压后可以看到libs里面每个jar包三部分,分别是jar包、文档、源代码
spring5的组成结构图:
可以看到,最下面是测试层,往上是核心容器,再往上aop,再往上data层和web层。
bean、core对应ioc的核心部分,context上下文,expression表达式。
2.先导入4个核心包:
建一个lib文件夹,放这5个jar包:
commons-logging包去maven仓库下https://mvnrepository.com/artifact/commons-logging/commons-logging/1.1.1
3. 导入lib中的jar包:
4.创建普通类,类里创建普通方法
5.创建spring配置文件,在配置文件中配置创建的对象
(1)配置文件使用xml格式
注意创建的bean1.xml在src下:
6.编写测试代码
这里单元测试使用的是自动引入的junit4
package com.atguigu.spring5.testdemo;
import com.atguigu.spring5.User;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;
/**
* @PackageName: com.atguigu.spring5.testdemo
* @ClassName: TestSpring5
* @Author: user-lihao
* @Date: 2021/10/7 10:10
* @Description:
**/
public class TestSpring5 {
@Test
public void testAdd(){
//加载配置文件
//ClassPath是类路径(src)下
ApplicationContext context=new ClassPathXmlApplicationContext("bean1.xml");
//FileSystem是具体路径
// ApplicationContext context=new FileSystemXmlApplicationContext("F:\\project\\learn\\spring5\\src\\bean1.xml");
//获取配置文件的对象
//user是配置的时候的id值
User user=context.getBean("user", User.class);
System.out.println(user);
user.add();
User user1=context.getBean("user", User.class);
System.out.println(user==user1);//true,说明user=user1,单例模式
}
}
2 IOC容器
什么是ioc?
关于ioc解耦的理解
解耦合发展史、控制反转、依赖注入
IoC模式,系统中通过引入实现了IoC模式的IoC容器,即可由IoC容器来管理对象的生命周期、依赖关系等,从而使得应用程序的配置和依赖性规范与实际的应用程序代码分离。其中一个特点就是通过文本的配置文件进行应用程序组件间相互关系的配置,而不用重新修改并编译具体的代码。
可以把IoC模式看作工厂模式的升华,把IoC容器看作是一个大工厂,只不过这个大工厂里要生成的对象都是在XML文件中给出定义的。利用Java 的“反射”编程,根据XML中给出的类定义生成相应的对象。从实现来看,以前在工厂模式里写死了的对象,IoC模式改为配置XML文件,这就把工厂和要生成的对象两者隔离,极大提高了灵活性和可维护性。
2.1 IOC底层原理
xml解析、工厂模式、反射
使用工厂模式,调用改变的时候只要改工厂就行了,不用动其他的,如果不用工厂,所有调用的地方都要改。
2.2 IOC接口 BeanFactory
ApplicationContext两个主要实现类:
2.3 IOC操作 bean管理:xml/注解
2.3.1 xml方式
注解方式是重中之重,xml使用频率现在已经很低了
(1)set注入和有参构造注入
user.java:
package com.atguigu.spring5;
/**
* @PackageName: com.atguigu.spring5
* @ClassName: User
* @Author: user-lihao
* @Date: 2021/10/7 10:04
* @Description:
**/
public class User {
private String name;
public String getName() {
return name;
}
// 属性注入方式1,setter
public void setName(String name) {
this.name = name;
}
// 属性注入方式2,有参构造
public User(String name) {
this.name = name;
}
// 使用有参构造器会自动去掉无参构造器
// 这时set注入方式就报错了,因为set注入方式是通过无参构造器创建对象,再通过setter注入属性
// 因此需要主动添加上无参构造
public User() {
}
public void add() {
System.out.println("add()");
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
'}';
}
}
bean1.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">
<!-- set注入-->
<bean id="user1" class="com.atguigu.spring5.User">
<property name="name" value="coderhao1"></property>
</bean>
<!-- 有参构造注入-->
<bean id="user2" class="com.atguigu.spring5.User">
<constructor-arg name="name" value="coderhao2"></constructor-arg>
</bean>
</beans>
测试类:
package com.atguigu.spring5.testdemo;
import com.atguigu.spring5.User;
import org.junit.Test;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;
/**
* @PackageName: com.atguigu.spring5.testdemo
* @ClassName: TestSpring5
* @Author: user-lihao
* @Date: 2021/10/7 10:10
* @Description:
**/
public class TestSpring5 {
@Test
public void testAdd() {
ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
User user1 = context.getBean("user1", User.class);
System.out.println(user1);//User{name='coderhao1'}
User user2 = context.getBean("user2", User.class);
System.out.println(user2);//User{name='coderhao2'}
}
}
注:set注入方式的简化:p名称空间注入(了解)
(2)其他的各种类型的注入
DITestEntity.java:
package com.atguigu.spring5;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class DITestEntity {
private String stringvalue;
private int numbervalue;
private Character charvalue;
private List listvalue;
private boolean boolvalue;
private int[] arrvalue;
private User objectvalue;
private List<User> userlistvalue;
private Map<String, String> mapvalue;
private Set<String> setvalue;
public String getStringvalue() {
return stringvalue;
}
public void setStringvalue(String stringvalue) {
this.stringvalue = stringvalue;
}
public int getNumbervalue() {
return numbervalue;
}
public void setNumbervalue(int numbervalue) {
this.numbervalue = numbervalue;
}
public Character getCharvalue() {
return charvalue;
}
public void setCharvalue(Character charvalue) {
this.charvalue = charvalue;
}
public List getListvalue() {
return listvalue;
}
public void setListvalue(List listvalue) {
this.listvalue = listvalue;
}
public boolean isBoolvalue() {
return boolvalue;
}
public void setBoolvalue(boolean boolvalue) {
this.boolvalue = boolvalue;
}
public int[] getArrvalue() {
return arrvalue;
}
public void setArrvalue(int[] arrvalue) {
this.arrvalue = arrvalue;
}
public User getObjectvalue() {
return objectvalue;
}
public void setObjectvalue(User objectvalue) {
this.objectvalue = objectvalue;
}
public List<User> getUserlistvalue() {
return userlistvalue;
}
public void setUserlistvalue(List<User> userlistvalue) {
this.userlistvalue = userlistvalue;
}
public Map<String, String> getMapvalue() {
return mapvalue;
}
public void setMapvalue(Map<String, String> mapvalue) {
this.mapvalue = mapvalue;
}
public Set<String> getSetvalue() {
return setvalue;
}
public void setSetvalue(Set<String> setvalue) {
this.setvalue = setvalue;
}
@Override
public String toString() {
return "DITestEntity{" +
"stringvalue='" + stringvalue + '\'' +
", numbervalue=" + numbervalue +
", charvalue=" + charvalue +
", listvalue=" + listvalue +
", boolvalue=" + boolvalue +
", arrvalue=" + Arrays.toString(arrvalue) +
", objectvalue=" + objectvalue +
", userlistvalue=" + userlistvalue +
", mapvalue=" + mapvalue +
", setvalue=" + setvalue +
'}';
}
}
在不指定注入值时的默认值为:
DITestEntity{stringvalue='null', numbervalue=0, charvalue=null, listvalue=null, boolvalue=false, arrvalue=null, objectvalue=null, userlistvalue=null, mapvalue=null, setvalue=null}
看得出来,布尔型是false,int是0,其他都是null,(string型是null字符串?,因为输出结果带引号)
bean1.xml指定value方式:
bean1.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: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/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/util https://www.springframework.org/schema/util/spring-util.xsd">
<!-- set注入-->
<bean id="user1" class="com.atguigu.spring5.User">
<property name="name" value="coderhao1"></property>
</bean>
<!-- 有参构造注入-->
<bean id="user2" class="com.atguigu.spring5.User">
<constructor-arg name="name" value="coderhao2"></constructor-arg>
</bean>
<!-- set注入的公共部分,注意和bean标签平级-->
<util:set id="gonggongSet">
<value>set1</value>
<value>set2</value>
<value>set3</value>
</util:set>
<bean id="diTestEntity" class="com.atguigu.spring5.DITestEntity">
<!-- 字面量注入-->
<property name="numbervalue" value="1"></property>
<property name="charvalue" value="a"></property>
<property name="boolvalue" value="true"></property>
<!--null注入,set注入方式的null也可以不写,默认null-->
<!-- <property name="stringvalue">-->
<!-- <null></null>-->
<!-- </property>-->
<!-- 带特殊符号的注入-->
<property name="stringvalue">
<value><![CDATA[<<coderhao>>]]></value>
</property>
<!-- 注入外部bean,使用ref-->
<property name="objectvalue" ref="user1"></property>
<!-- 这种级联赋值方式会把user1的bean配置改变,导致后面再使用user1时name的value变了,变成‘coderhao-级联赋值’-->
<property name="objectvalue.name" value="coderhao-级联赋值"></property>
<!-- 内部bean写法,bean标签嵌套-->
<!-- <property name="objectvalue">-->
<!-- 内部bean由于作用范围限制在里面,所以可以不写id-->
<!-- <bean class="com.atguigu.spring5.User">-->
<!-- <property name="name" value="coderhao-innerbean"></property>-->
<!-- </bean>-->
<!-- </property>-->
<!-- 数组注入-->
<property name="arrvalue">
<array>
<value>1</value>
<value>2</value>
<value>3</value>
</array>
</property>
<!-- list集合注入-->
<property name="listvalue">
<list>
<value>coderhao1</value>
<value>coderhao2</value>
<value>coderhao3</value>
</list>
</property>
<!-- set集合注入-->
<!-- 这里把注入的内容提取出来,用ref注入-->
<!-- 不提取写法和list相同,只是list标签换成set-->
<property name="setvalue" ref="gonggongSet">
</property>
<!-- map集合注入-->
<property name="mapvalue">
<map>
<entry key="k1" value="v1"></entry>
<entry key="k2" value="v2"></entry>
<entry key="k3" value="v3"></entry>
</map>
</property>
<!-- list集合里面有对象的注入-->
<property name="userlistvalue">
<list>
<ref bean="user1"></ref>
<ref bean="user2"></ref>
</list>
</property>
</bean>
</beans>
注入属性后结果:
DITestEntity{stringvalue='<<coderhao>>', numbervalue=1, charvalue=a, listvalue=[coderhao1, coderhao2, coderhao3], boolvalue=true, arrvalue=[1, 2, 3], objectvalue=User{name='coderhao-级联赋值'}, userlistvalue=[User{name='coderhao-级联赋值'}, User{name='coderhao2'}], mapvalue={k1=v1, k2=v2, k3=v3}, setvalue=[set1, set2, set3]}
(3)补充:工厂bean的管理
以上都是普通bean的管理,还有工厂bean(FactoryBean).
这里用MyBean做一个工厂bean:
MyBean.java:
package com.atguigu.spring5.factorybean;
import com.atguigu.spring5.User;
import org.hamcrest.Factory;
import org.springframework.beans.factory.FactoryBean;
//工厂bean要实现FactoryBean接口
public class MyBean implements FactoryBean<User> {
//返回bean实例
@Override
public User getObject() throws Exception {
return new User();
}
//返回bean类型
@Override
public Class<?> getObjectType() {
return null;
}
//是否为单例
@Override
public boolean isSingleton() {
return false;
}
}
xml里写上:
<bean id="myBean" class="com.atguigu.spring5.factorybean.MyBean"></bean>
测试类中写法片段:
ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
User user = context.getBean("myBean", User.class);
System.out.println(user);//User{name='null'}
(4)补充bean的作用域/生命周期相关的一些常见标签配置
- scope(作用域):
singleton 单例,加载配置文件时创建对象
prototype 多例,getBean时创建对象
request 一次请求
session 一次会话 - 生命周期
(1)无参构造器创建bean
(2)调用bean的set方法设置属性
(3)初始化bean,调用bean的初始化方法
(4)使用bean
(5)容器关闭时,调用销毁方法
验证:
bean类:Orders.java
public class Orders {
private String oname;
public Orders() {
System.out.println("1,无参构造器Orders()");
}
public String getOname() {
return oname;
}
public void setOname(String oname) {
System.out.println("2,setter方法");
this.oname = oname;
}
// 创建执行的初始化方法
public void initMethod() {
System.out.println("3,初始化方法initMethod()");
}
public void desMethod() {
System.out.println("5,销毁方法desMethod()");
}
}
xml片段:
<!--初始化和销毁方法的配置标签:init-method,destroy-method-->
<bean id="orders" class="com.atguigu.spring5.Orders" init-method="initMethod" destroy-method="desMethod">
<property name="oname" value="phone"></property>
</bean>
测试类片段:
@Test
public void test1() {
ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("bean1.xml");
Orders orders = classPathXmlApplicationContext.getBean("orders", Orders.class);
System.out.println("4,使用bean");
System.out.println(orders);
//手动销毁bean实例
classPathXmlApplicationContext.close();
}
输出:
1,无参构造器Orders()
2,setter方法
3,初始化方法initMethod()
4,使用bean
com.atguigu.spring5.Orders@181ae3f
5,销毁方法desMethod()
其实在第三步初始化方法的前面和后面还各有一个aop相关的后置处理器方法:
后置处理器方法:
public class OrdersPost implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("初始化之前执行的方法");
return null;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("初始化之后执行的方法");
return null;
}
}
xml配置:
<!--配置后置处理器后,所有bean的创建都会添加后置处理器-->
<bean id="ordersPost" class="com.atguigu.spring5.OrdersPost"></bean>
输出:
1,无参构造器Orders()
2,setter方法
初始化之前执行的方法
3,初始化方法initMethod()
初始化之后执行的方法
4,使用bean
com.atguigu.spring5.Orders@9cfc36
5,销毁方法desMethod()
(5) xml自动装配
之前都是在xml中配置手动装配bean,这回自动一下
夹着有两个类,在xml中配置:
<bean id="dept" class="com.atguigu.spring5.autowired.Dept"></bean>
<bean id="emp" class="com.atguigu.spring5.autowired.Emp">
<property name="dept" ref="dept"></property>
</bean>
上面这是手动装配,自动装配如下:
<bean id="dept" class="com.atguigu.spring5.autowired.Dept"></bean>
<!--自动装配,标签autowire,根据名称/类型装配-->
<bean id="emp" class="com.atguigu.spring5.autowired.Emp" autowire="byType">
<!--<property name="dept" ref="dept"></property>-->
</bean>
(6) 外部属性文件
2.3.2 注解方式
注解方式是重中之重,xml使用频率现在已经很低了
xml里开启包扫描:
类上加注解:
(1)包扫描详细配置
只扫描Controller注解:
不扫描Controller注解:
(2)属性注入
(3)完全注解开发,替代xml配置文件
3 AOP
3.1 aop介绍
引出aop:
- 功能分两大类,辅助功能和核心业务功能
- 辅助功能和核心业务功能彼此独立进行开发
- 比如登陆功能,即便是没有性能统计和日志输出,也可以正常运行
- 如果有需要,就把"日志输出" 功能和 “登陆” 功能 编织在一起,这样登陆的时候,就可以看到日志输出了
- 辅助功能,又叫做切面,这种能够选择性的,低耦合的把切面和核心业务功能结合在一起的编程思想,就叫做切面编程
aop术语:
aop操作一般使用aop框架实现,spring一般和AspectJ这个aop框架组合使用实现aop操作。
3.2 aop实现
依赖:
3.2.1 xml方式实现
3.2.2 注解方式实现
例如定义切入点表达式 execution (* com.sample.service.impl….(…))
execution()是最常用的切点函数,其语法如下所示:
整个表达式可以分为五个部分:
1.execution(): 表达式主体。
2.第一个*号:表示返回类型,*号表示所有的类型。
3.包名:表示需要拦截的包名,后面的两个句点表示当前包和当前包的所有子包,com.sample.service.impl包、子孙包下所有类的方法。
4.第二个*号:表示类名,*号表示所有的类。
5.*(…):最后这个星号表示方法名,*号表示所有的方法,后面括弧里面表示方法的参数,两个句点表示任何参数。
- 开启aop操作
<!-- 开启aop代理-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
- 配置被增强类和增强类
被增强类:
@Component
public class Book {
public void method1() {
System.out.println("method1..........");
// System.out.println(1/0);
}
}
增强类:
@Component
@Aspect
public class BookProxy {
@Before(value = "execution(* aop.Book.*(..))")
public void before() {
System.out.println("Before");
}
@After(value = "execution(* aop.Book.*(..))")
public void after() {
System.out.println("After[method1()返回结果之前执行]");
}
@AfterReturning(value = "execution(* aop.Book.*(..))")
public void AfterReturning() {
System.out.println("AfterReturning[method1()返回结果之后执行]");
}
@AfterThrowing(value = "execution(* aop.Book.*(..))")
public void yichuang() {
System.out.println("AfterThrowing");
}
@Around(value = "execution(* aop.Book.*(..))")
public void huanrao(ProceedingJoinPoint point) throws Throwable {
System.out.println("Around(前)");
point.proceed();
System.out.println("Around(后)");
}
}
测试类:
public class SpringTest {
@Test
public void testAOP(){
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
Book book = context.getBean("book", Book.class);
book.method1();
}
}
输出结果:
异常情况:
补充1:相同切入点的抽取
补充2:多个增强类增强一个方法的优先级
这里注意到before1不止在Before前面,还在Around前面。
补充3:完全注解
替掉xml。
4 JDBCTemplate被ORM框架代替,不学
5 事务操作
5.1 事务的概念相关
什么是事务:
银行转账,一个账户加钱和另一个账户扣钱都要执行成功才行,不能一个成功一个失败。
事物4特性(ACID):
- 原子性:操作要么全成功,要么全失败,不能分割。
- 一致性:一致性是指事务必须使数据库从一个一致性状态变换到另一个一致性状态。
举例来说,假设用户A和用户B两者的钱加起来一共是1000,那么不管A和B之间如何转账、转几次账,事务结束后两个用户的钱相加起来应该还得是1000,这就是事务的一致性。一致性和原子性密切相关。 - 隔离性:隔离性是当多个用户并发访问数据库时,比如同时操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离。
- 持久性:持久性是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作。
5.2 事务案例
案例搭建:
搭建结构:
xml片段:
<context:component-scan base-package="transaction"></context:component-scan>
<aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy>
User:
package transaction.entity;
public class User {
private int money;
public int getMoney() {
return money;
}
public void setMoney(int money) {
this.money = money;
}
public User(int money) {
this.money = money;
}
}
IUserDao:
package transaction.dao;
import transaction.entity.User;
public interface IUserDao {
public void increase(User user, int money);
public void decrease(User user, int money);
}
UserDaoImpl:
package transaction.dao;
import org.springframework.stereotype.Repository;
import transaction.entity.User;
@Repository
public class UserDaoImpl implements IUserDao {
@Override
public void increase(User user, int money) {
user.setMoney(user.getMoney() + money);
}
@Override
public void decrease(User user, int money) {
user.setMoney(user.getMoney() - money);
}
}
IUserService:
package transaction.service;
import transaction.entity.User;
public interface IUserService {
public void jieqian(User qiongbi, User gaofushuai, int money);
public void huanqian(User qiongbi, User gaofushuai, int money);
public void showMoney(User qiongbi, User gaofushuai);
}
UserServiceImpl:
package transaction.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import transaction.dao.IUserDao;
import transaction.entity.User;
@Service
public class UserServiceImpl implements IUserService {
@Autowired
private IUserDao userDao;
@Override
public void jieqian(User qiongbi, User gaofushuai, int money) {
userDao.increase(qiongbi, money);
//System.out.println(1/0);
userDao.decrease(gaofushuai, money);
}
@Override
public void huanqian(User qiongbi, User gaofushuai, int money) {
userDao.decrease(qiongbi, money);
userDao.increase(gaofushuai, money);
}
@Override
public void showMoney(User qiongbi, User gaofushuai) {
System.out.println("qiongbi:" + qiongbi.getMoney());
System.out.println("gaofushuai:" + gaofushuai.getMoney());
}
}
SpringTest:
package transaction.test;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import transaction.config.SpringConfig;
import transaction.entity.User;
import transaction.service.IUserService;
import transaction.service.UserServiceImpl;
public class SpringTest {
private ApplicationContext context;
private IUserService userService;
private User qiongbi;
private User gaofushuai;
@Before
public void before() {
//context = new AnnotationConfigApplicationContext(SpringConfig.class);
context=new ClassPathXmlApplicationContext("transaction/bean.xml");
userService = context.getBean("userServiceImpl", UserServiceImpl.class);
qiongbi = new User(100);
gaofushuai = new User(1000);
}
@Test
public void test1() {
try {
userService.jieqian(qiongbi, gaofushuai, 50);
} catch (Exception e) {
System.out.println(e);
} finally {
userService.showMoney(qiongbi, gaofushuai);
}
}
}
执行test1(),可以看到,正常执行:
但如果借钱方法中途出现异常,模拟该情况,将UserServiceImpl中jieqian()方法中的注释放开,再运行test1(),可以发现,第一步执行了,第二步没执行:
这是个严重的事务问题,不符合事务的原子性和一致性。
防止事务异常的处理步骤(编程式):
而在spring中,可以使用声明式事务管理,简单直接。
5.3 spring事务管理介绍
Spring并不直接管理事务,而是提供了多种事务管理器,他们将事务管理的职责委托给Hibernate或者JTA等持久化机制所提供的相关平台框架的事务来实现。
- JDBC: 如果应用程序中直接使用JDBC来进行持久化,DataSourceTransactionManager会为你处理事务。
里面那个ref指定是哪个数据源需要进行事务。
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
实际上,DataSourceTransactionManager是通过调用java.sql.Connection来管理事务,而后者是通过DataSource获取到的。通过调用连接的commit()方法来提交事务,同样,事务失败则通过调用rollback()方法进行回滚。
- Hibernate:如果应用程序的持久化是通过Hibernate实现的,那么你需要使用HibernateTransactionManager。
<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
- JPA:如果你计划使用JPA的话,那你需要使用Spring的JpaTransactionManager来处理事务。
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
- JTA:如果你没有使用以上所述的事务管理,或者是跨越了多个事务管理源(比如两个或者是多个不同的数据源),你就需要使用JtaTransactionManager。
<bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager">
<property name="transactionManagerName" value="java:/TransactionManager" />
</bean>
JtaTransactionManager将事务管理的责任委托给javax.transaction.UserTransaction和javax.transaction.TransactionManager对象,其中事务成功完成通过UserTransaction.commit()方法提交,事务失败通过UserTransaction.rollback()方法回滚。
下面通过JDBC实现的事务管理器来
5.4 声明式事务的实现
5.4.1 注解方式
首先引入jar包:
xml开启事务:
这里假设使用jdbc操作mysql,其实没有,只是形式而已。
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="url" value="jdbc:mysql://localhost:3306/how2java?characterEncoding=UTF-8"></property>
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="username" value="root"></property>
<property name="password" value="root"></property>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 开启事务,指定事务管理器-->
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
添加注解:
就有事务了。
本人不清楚不涉及数据库的事务处理代码。
补充:参数配置
- propagation:事务的传播行为
- isolation:事务的隔离级别
是解决脏读、不可重复读、幻(虚)读的问题
不同的隔离级别对并发问题的解决情况如图:
mysql默认repeatable read级别。
注意:事务的隔离级别和数据库并发性是成反比的,隔离级别越高,并发性越低。最后一个效果最好但会给整表加锁,效率很低。 - timeout:超时时间
- readonly:
- rollbackfor
哪些异常需要回滚 - norollbackfor
哪些异常不需要回滚
补充:脏读、幻读、不可重复读介绍
参考:https://baijiahao.baidu.com/s?id=1717095300761675602&wfr=spider&for=pc
一、脏读、不可重复读、幻读
1、脏读:脏读就是指当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,另外一个事务也访问这个数据,然后使用了这个数据。
例如:
张三的工资为5000,事务A中把他的工资改为8000,但事务A尚未提交。
与此同时,
事务B正在读取张三的工资,读取到张三的工资为8000。
随后,
事务A发生异常,而回滚了事务。张三的工资又回滚为5000。
最后,
事务B读取到的张三工资为8000的数据即为脏数据,事务B做了一次脏读。
2、不可重复读:是指在一个事务内,多次读同一数据。在这个事务还没有结束时,另外一个事务也访问该同一数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改,那么第一个事务两次读到的的数据可能是不一样的。这样就发生了在一个事务内两次读到的数据是不一样的,因此称为是不可重复读。
例如:
在事务A中,读取到张三的工资为5000,操作没有完成,事务还没提交。
与此同时,
事务B把张三的工资改为8000,并提交了事务。
随后,
在事务A中,再次读取张三的工资,此时工资变为8000。在一个事务中前后两次读取的结果并不致,导致了不可重复读。
3、幻读:是指当事务不是独立执行时发生的一种现象,例如第一个事务对一个表中的数据进行了修改,这种修改涉及到表中的全部数据行。同时,第二个事务也修改这个表中的数据,这种修改是向表中插入一行新数据。那么,以后就会发生操作第一个事务的用户发现表中还有没有修改的数据行,就好象发生了幻觉一样。
幻读,并不是说两次读取获取的结果集不同,幻读侧重的方面是某一次的 select 操作得到的结果所表征的数据状态无法支撑后续的业务操作。更为具体一些:select 某记录是否存在,不存在,准备插入此记录,但执行 insert 时发现此记录已存在,无法插入,此时就发生了幻读。幻读是说 读-写,用写来证实读的是鬼影。
例如:
目前工资为5000的员工有10人,事务A读取所有工资为5000的人数为10人。
此时,
事务B插入一条工资也为5000的记录。
这是,事务A再次读取工资为5000的员工,记录为11人。此时产生了幻读。
4、提醒
不可重复读的重点是修改:
同样的条件,你读取过的数据,再次读取出来发现值不一样了
幻读的重点在于新增或者删除:
同样的条件,第 1 次和第 2 次读出来的记录数不一样
5、第一类丢失更新
A事务撤销时,把已经提交的B事务的更新数据覆盖了。例如:
这时候取款事务A撤销事务,余额恢复为1000,这就丢失了更新。
6、第二类丢失更新
A事务覆盖B事务已经提交的数据,造成B事务所做的操作丢失
二、如何解决
为了解决上述问题,数据库通过锁机制解决并发访问的问题。根据锁定对象不同:分为行级锁和表级锁;根据并发事务锁定的关系上看:分为共享锁定和独占锁定,共享锁定会防止独占锁定但允许其他的共享锁定。而独占锁定既防止共享锁定也防止其他独占锁定。为了更改数据,数据库必须在进行更改的行上施加行独占锁定,insert、update、delete和selsct for update语句都会隐式采用必要的行锁定。
但是直接使用锁机制管理是很复杂的,基于锁机制,数据库给用户提供了不同的事务隔离级别,只要设置了事务隔离级别,数据库就会分析事务中的sql语句然后自动选择合适的锁。
完全注解开发
config类这样写:
package transaction.config;
import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.sql.DataSource;
@Configuration
@ComponentScan(basePackages = {"transaction"})
@EnableTransactionManagement//开启事务
public class SpringConfig {
// 数据库连接池
@Bean
public DruidDataSource getDruidDataSource() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/how2java?characterEncoding=UTF-8");
dataSource.setUsername("root");
dataSource.setPassword("password");
return dataSource;
}
//jdbcTemplate
public JdbcTemplate getJdbcTemplate(DataSource dataSource) {//根据类型从ioc中找到dataSource放进去
JdbcTemplate jdbcTemplate = new JdbcTemplate();
jdbcTemplate.setDataSource(dataSource);
return jdbcTemplate;
}
// 事务管理器
public DataSourceTransactionManager getDataSourceTransactionManager(DataSource dataSource) {
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
transactionManager.setDataSource(dataSource);
return transactionManager;
}
}
5.4.2 xml方式
xml方式配置事务和配置AOP一样,因为spring的事务本身就是用aop实现的。
1.配置事务管理器
不需要开启事务注解的配置了:
2.配置通知
3.配置切入点和切面
6 Spring5新特性
6.1 spring5基于jdk8
6.2 spring5移除了log4j,这里手动整合log4j2:
引入jar包:
创建log4j2的xml配置文件:
测试:
6.3 spring5支持@Nullable注解
6.4 支持lambda表达式
本质上是因为支持jdk8,jdk8的新特性有lambda表达式。
6.5 测试方面改进
spring5支持整合junit5。
需要jar包:
6.6 webflux
新的模块webflux,和webmvc对标。
和springmvc的比较:
具体的响应式编程的使用这里不涉及,只是做一个初步介绍,需要的时候再学。