同步操作将从 Tonited/Spring-Notes 强制同步,此操作会覆盖自 Fork 仓库以来所做的任何修改,且无法恢复!!!
确定后同步将在后台操作,完成时将刷新页面,请耐心等待。
titile | author | picture-resources | IDE | java-version | date |
---|---|---|---|---|---|
Spring入门笔记 | Tonited | imooc | IDEA2019.2 | 11.0.3 | 2019.08.15 |
Tonited
picture-resources: imooc
IDE: IDEA2019.2
java-version: 11.0.3
date: 2019.08.15
这篇笔记是本人零基础看Spring入门网课时整理的笔记,同时加入了一些自己的理解,方便大家学习和使用,内容可能存在错误或者在理解上有一定问题,如果有任何问题、意见、见解或关于侵权请联系我,QQ710260712,申请理由请写“Spring入门笔记勘误”。
——Tonited
工厂类
传统方式工厂类:BeanFactory
ApplicationContext在创造时调用类的构造函数
创建工厂类,ApplicationContext在创造时通过工厂生成Bean
这个Bean由静态方法创建,只会创造一个Bean,获取时获取的是同一个Bean
与上图简单工厂模式相同,区别是工厂方法创建的Bean不是单例
类别 | 说明 |
---|---|
singleton | 在SpringIOC容器中仅存在一个Bean实例,Bean一单实例的方式存在(单例模式) |
prototype | 每次调用getBean()时都会返回一个新的实例(既非单例模式) |
request | 每次HTTP请求都会创建一个新的Bean,该作用域仅适于WebApplicationContext环境 |
session | 同一个HTTP Session共享一个Bean,不同的HTTP Session使用不同的Bean。该作用域仅适于WebApplicationContext环境 |
Spring初始化Bean或销毁Bean时,有时需要作一些处理工作,因此spring可以在创建和拆卸Bean的时候调用Bean的两个生命周期方法
注意:destory只有在scope=singleton时有效
顺序:
instantiate (bean对象实例化
populate properties (封装属性
如果Bean实现BeanNameAware接口,执行接口的setBeanName方法
如果Bean实现BeanFactoryAware或者ApplicationContextAware,执行 设置工厂setBeanFactory方法 或者上 下文对象setApplicationContext方法
如果存在类实现 BeanPostProcessor 接口(Bean后处理器),执行**postProcessBeforeInitialization **方法
实现Bean后处理器接口的类并不是被创建的Bean
该生命周期会在每个Bean实例化都会被调用
如果Bean实现 InitalizingBean 接口,执行 afterPropertiesSet方法
调用 指定初始化方法 init
如果存在类实现 BeanPostProcessor接口 (Bean后处理器),执行 postProcessAfterInitialization 方法
与第5步相同,为Bean后处理器
实现Bean后处理器接口的类并不是被创建的Bean
该生命周期会在每个Bean实例化都会被调用
执行业务逻辑
如果 Bean 实现 DisposableBean 执行 destroy
调用 指定的销毁方法 customerDestory
可以在创造Bean时产生代理
可以对Bean的方法增强
下面的例子使用了Java的动态代理
通过构造方法注入Bean的属性值或依赖的对象,它保证了Bean实例在实例化之后就可以使用
构造器注入在元素里生命的属性
在Spring配置文件ApplicationContext.xml中,通过设置注入的属性
为了简化XML文件配置,Spring从2.5开始引入一个新的p名称空间
要在中加入
xmlns:p="http://www.springframework.org/schema/p"
p:<属性名>="XXX"引入常量值
p:<属性名>-ref="XXX"引用其他Bean对象
需要set
写在之间
数组类型的属性注入
List集合类型的属性注入
Set集合类型的属性注入
Map集合类型的属性注入
Properties类型的属性注入
使用注解时
想使用注解要引入依赖**spring-aop **
替换ApplicationContext.xml头为
<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-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
开启注解扫描
<context:component-scan base-package="要扫描的包名"/>
Spring2.5引入使用注解去定义Bean
@Component 描述Spring框架中的Bean
除了@Component外,Spring提供了3个功能基本和@Component等效的注解
@Value可以直接注入属性
使用@Autowired进行自动注入
@Autowired按照默认类型进行注入
如果存在两个Bean类型相同,则按照名称注入
@Autowired注入针对成员变量或者set方法都可以
通过@Autowired的required属性,设置一定要找到匹配的Bean
@Qualifier指定注入Bean的名称,即实现了required属性
使用@Qualifier 指向Bean名称时,Bean必须指定相同名称
Spring提供对 JSR-250中定义@Resource标准注解的支持
@Resource相当于@Autowried和@Qualifier的组合形式
想要使用@Resource要引入依赖
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>jsr250-api</artifactId>
<version>1.0</version>
</dependency>
Spring初始化bean或销毁bean时,有时需要做一些处理工作,因此Spring可以在创建和拆卸bean的时候调用bean的两个生命周期方法
@Scope注解用于指定Bean的作用范围
使用注解配置的Bean和配置的一样,默认作用范围都是singleton
XML方式的优势
结构清晰,易于阅读
注解方式的优势
XML与注解的整合开发
引入context命名空间:修改ApplicationContext.xml文件为
<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-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
开启属性注入
在配置文件中添加
<context:annotation-config/>
组合开发
ApplicationContext.xml中只需配置的 id 和 class ,不需要配置属性了
属性通过注解注入,不需要set方法了
因为在ApplicationContext.xml中已经配置了bean的id,所以不需要每个类上面用@Component等标注id了
假设现在我们有以下实现了一个名为UserDao接口的类,我们想在save()方法被调用前做权限校验,可以在类中写一个用于权限校验的方法checkPrivilege(),并在save()的逻辑执行前调用这个类的checkPrivilege()方法。
如果现在我们有100实现了UserDao接口的类,想在每一个类中的某个方法被调用之前都进行一步权限调用,就需要给100个类中的每一个类都写一个checkPrivilege()方法。
这时你可能想到:要将权限校验的方法checkPrivilege()写在接口中,让每一个继承它的类都实现这一方法。
这种做法就是所谓的纵向继承,”纵向“指的是从接口继承方法这种上下父子关系
可是你也能很容易的发现,这样写的代码量很大,最重要的是:会出现大量重复代码
为了解决这一问题,出现了AOP,关于AOP的具体概念稍后会具体讲解。
AOP采用的是横向抽取机制
JDK的动态代理只能对实现了接口的类进行代理
首先我们要创建一个代理对象,用于稍后传给 InvocationHandler 的 invoke() 函数,创建对象使用 Proxy.newProxyInstant 函数,它需要三个参数
被代理类的类加载器
被代理类所实现的接口
执行代理的类,这个类需要实现 InvocationHandler 接口的 invoke() 方法
public Object createProxy(){
Object proxy = Proxy.newProxyInstance(
userDao.getClass().getClassLoader(),
userDao.getClass().getInterfaces(),
this
);
return proxy;
}
InvocationHandler接口的 invoke(Object proxy, Method method, Object[] args)
它包含三个参数
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {}
返回值需要返回“方法的执行:method.invoke()”, 它包含两个参数
被代理的类实例
invoke参数列表中的args(args内部是method方法执行时的参数)
return method.invoke(userDao,args);
代理类
public class MyJdkProxy implements InvocationHandler{
// UserDao是接口
private UserDao userDao;
public MyJdkProxy(UserDao userDao){
this.userDao = userDao;
}
public Object createProxy(){
Object proxy = Proxy.newProxyInstance(
userDao.getClass().getClassLoader(),
userDao.getClass().getInterfaces(),
this);
return proxy;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if("save".equals(method.getName())){
System.out.println("权限校验..");
return method.invoke(userDao,args);
}
return method.invoke(userDao,args);
}
}
使用
public class SpringDemo1 {
public void demo1(){
// UserDaoImpl是实现UserDao接口的类
UserDao userDao = new UserDaoImpl();
UserDao proxy = (UserDao)new MyJdkProxy(userDao).createProxy();
proxy.save(); //UserDao中的方法
}
}
首先创造生成代理函数,经历以下四步
创建核心类:创建Enhancer核心类,接下来的三步设置都是对这个核心类操作
//1. 设置核心类
Enhancer enhancer = new Enhancer();
设置被代理类:用 Enhancer 的 setSuperClass() 方法告诉enhancer所要代理的类,参数为被代理类
// 2,设置父类
enhancer.setSuperclass(productDao.getClass());
设置回调:用 Enhancer 的 setCallBack() 设置代理执行类,这个类要实现 MethodInterceptor 接口,参数为代理执行类
本例中的代理类实现了 MethodInterceptor 接口,既是代理类也是代理执行类,所以这里的参数为 this
// 3.设置回调
enhancer.setCallback(this);
生成代理,前三步完成了对代理的设置,现在使用 Enhancer 的 create() 方法创建代理并将代理返回
// 4.生成代理
Object proxy = enhancer.create();
return proxy;
创造生成代理函数完整代码示例
public Object createProxy(){
// 1.创建核心类
Enhancer enhancer = new Enhancer();
// 2.设置父类
enhancer.setSuperclass(productDao.getClass());
// 3.设置回调
enhancer.setCallback(this);
// 4.生成代理
Object proxy = enhancer.create();
return proxy;
}
MethodInterceptor接口的intercept() 方法
需要四个参数
返回值需要返回“方法代理执行父类:methodProxy.invokeSuper()”, 它包含两个参数
intercept完整代码示例
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
if("save".equals(method.getName())){
System.out.println("权限校验===================");
return methodProxy.invokeSuper(proxy,args);
}
return methodProxy.invokeSuper(proxy,args);
}
代理类
public class MyCglibProxy implements MethodInterceptor{
private ProductDao productDao;
public MyCglibProxy(ProductDao productDao){
this.productDao = productDao;
}
public Object createProxy(){
// 1.创建核心类
Enhancer enhancer = new Enhancer();
// 2.设置父类
enhancer.setSuperclass(productDao.getClass());
// 3.设置回调
enhancer.setCallback(this);
// 4.生成代理
Object proxy = enhancer.create();
return proxy;
}
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
if("save".equals(method.getName())){
System.out.println("权限校验===================");
return methodProxy.invokeSuper(proxy,args);
}
return methodProxy.invokeSuper(proxy,args);
}
}
使用
public class SpringDemo2 {
public void demo1(){
ProductDao productDao = new ProductDao();
ProductDao proxy = (ProductDao) new MyCglibProxy(productDao).createProxy();
proxy.save();
proxy.update();
proxy.delete();
proxy.find();
}
}
AOP不是Spring特有的,AOP联盟提出AOP,为通知Advice定义了Advice——org.aopalliance.aop.Interface.Advice
增强类型也称通知类型
Spring按照“通知Advice在目标类方法的连接点位置”来分为五类
前置通知 org.springframework.aop.MethodBeforeAdvice
在目标方法执行前实施增强
public class MyBeforeAdvice implements MethodBeforeAdvice {
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("前置增强======================");
}
}
后置通知 org.springframework.aop.AfterReturningAdvice
环绕通知 org.aopalliance.intercept.MethodInterceptor
在目标方法执行前后实施增强
public class MyAroundAdvice implements MethodInterceptor {
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("环绕前增强===================");
Object obj = invocation.proceed();
System.out.println("环绕后增强===================");
return obj;
}
}
异常抛出通知 org.springframework.aop.ThrowsAdvices
引介通知 org.springframework.aop.IntroductionInterceptor
增强类要实现这些接口
Aop联盟:aoppalliance
Spring AOP:spring-aop
配置目标类
配置切面
当通知类中方法对目标类的全部方法拦截时,通知类就是切面(不带有切入点的切面)
否则就要单独配置切面(带有切入点的切面)
这时的切面其实就是一个只有id/name和class的Bean
class必须是org.springframework.aop.support.RegexpMethodPointcutAdvisor
属性pattern:使用正则限制切入点
<property name="patterns" value=".*save.*,.*delete.*"/>
属性advice:所需要使用的切面
<property name="advice" ref="myAroundAdvice"/>
配置产生代理——ProxyFactoryBean
产生代理ProxyFactoryBean也是一个只有id/name和class的类
class为 org.springframework.aop.framework.ProxyFactoryBean
常用属性
target:代理的目标对象
proxyInterfaces:代理实际要实现的接口
如果多个接口使用以下格式
<list>
<value></value>
..
</list>
proxyTargetClass:是否对类代理而不是接口,即是否使用CGLib代理,值为true/false
interceptorName:需要织入目标的Advice
singleton:返回代理是否为单例,默认为单例
optimize:设置为true时,强制使用CGLib
Advisor切面完整配置示例
<!--配置目标类============-->
<bean id="customerDao" class="com.aop.demo4.CustomerDao"/>
<!--配置通知============== -->
<bean id="myAroundAdvice" class="com.aop.demo4.MyAroundAdvice"/>
<!--一般的切面是使用通知作为切面的,因为要对目标类的某个方法进行增强就需要配置一个带有切入点的切面-->
<bean id="myAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<!--pattern中配置正则表达式:.任意字符 *任意次数 -->
<!--<property name="pattern" value=".*save.*"/>-->
<property name="patterns" value=".*save.*,.*delete.*"/>
<property name="advice" ref="myAroundAdvice"/>
</bean>
<!-- 配置产生代理 -->
<bean id="customerDaoProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target" ref="customerDao"/>
<property name="proxyTargetClass" value="true"/>
<property name="interceptorNames" value="myAdvisor"/>
</bean>
<!--配置目标类-->
<bean id="studentDao" class="com.aop.demo5.StudentDaoImpl"/>
<bean id="customerDao" class="com.aop.demo5.CustomerDao"/>
<!-- 配置增强-->
<bean id="myBeforeAdvice" class="com.aop.demo5.MyBeforeAdvice"/>
<bean id="myAroundAdvice" class="com.aop.demo5.MyAroundAdvice"/>
<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<!-- .任意字符 *任意次数 -->
<property name="beanNames" value="*Dao"/>
<property name="interceptorNames" value="myBeforeAdvice"/>
</bean>
测试使用
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext3.xml")
public class SpringDemo5 {
@Resource(name="studentDao")
private StudentDao studentDao;
@Resource(name="customerDao")
private CustomerDao customerDao;
@Test
public void demo1(){
studentDao.find();
studentDao.save();
customerDao.find();
customerDao.save();
}
}
<!--配置目标类-->
<bean id="studentDao" class="com.aop.demo6.StudentDaoImpl"/>
<bean id="customerDao" class="com.aop.demo6.CustomerDao"/>
<!-- 配置增强-->
<bean id="myBeforeAdvice" class="com.aop.demo6.MyBeforeAdvice"/>
<bean id="myAroundAdvice" class="com.aop.demo6.MyAroundAdvice"/>
<!--配置切面-->
<bean id="myAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<property name="pattern" value="com\.aop\.demo6\.CustomerDao\.save"/>
<property name="advice" ref="myAroundAdvice"/>
</bean>
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>
测试使用
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext4.xml")
public class SpringDemo6 {
@Resource(name="studentDao")
private StudentDao studentDao;
@Resource(name="customerDao")
private CustomerDao customerDao;
@Test
public void demo1(){
studentDao.find();
studentDao.save();
customerDao.find();
customerDao.save();
}
}
配置Spring配置文件:applicationContext.xml
开启 AspectJ 自动代理 <aop:aspectj-autoproxy />
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--开启AspectJ自动代理-->
<aop:aspectj-autoproxy />
</beans>
通过execution函数,可以定义切点的方法切入
语法
execution( [<访问修饰符>] <返回类型> <方法名>([<参数>]) <异常> )
例如
- 匹配所有类的public方法
execution(public * *(.))
- 匹配指定包下所有类方法(不包含子包)(第一个*是返回类型)
execution (* com.dao.*(.))
- 匹配指定包下所有类方法(包含包、子包下的所有类)(在包名后变为两个点即可)
execution(* com.dao.*(.))
- 匹配指定类所有方法
execution(* com.service.UserService.*(.))
- 匹配实现特定接口所有类方法
execution(* com.dao.GenericDAO+.*(.))
- 匹配所有save开头的方法
execution(* save*(.))
使用@Aspect声明一个类为切面类
@Aspect
public class MyAspectAnno {}
配置文件配置目标类,配置切面
<!--开启AspectJ的注解开发,自动代理=====================-->
<aop:aspectj-autoproxy/>
<!--目标类===================-->
<bean id="productDao" class="com.aspectJ.demo1.ProductDao"/>
<!--定义切面-->
<bean class="com.aspectJ.demo1.MyAspectAnno"/>
通知中通过value属性定义切点
@Aspect
public class MyAspectAnno
{
@Before(value="execution(* com.aspectJ.demo1.ProductDao.save(.))")
public void before(JoinPoint joinPoint){
System.out.println("前置通知");
}
}
@Before 前置通知
可以在方法中传入JoinPoint对象,用来获得切点信息
//要增强的代码
@Before(value="execution(* com.aspectJ.demo1.ProductDao.save(.))")
//通知
public void before(JoinPoint joinPoint){
System.out.println("前置通知");
}
@AfterReturning 后置通知
@AfterReturning 存在参数returning,值为字符串,作用是把切入点的返回值注入变量,变量名就是returning的值,这个变量作为通知函数的参数
@AfterRuning(
value="execution(* com.aspectJ.demo1.ProductDao.save(.))",
returning="resu"
)
public void afterReturning(Object resu){
System.out.println("后置通知");
}
@Around 环绕通知
@Around("execution(* com.aspectJ.demo1.ProductDao.save(.))")
public Object around( ProceedingJoinPoint joinPoint )
{
System.out.println("==前环绕通知==");
Object obj = joinPoint.proceed();
System.out.println("==后环绕通知==");
return obj;
}
@AfterThrowing 异常抛出通知
通过设置throwing属性,可以设置发生异常对象参数
@AfterThrowing(
value="execution(* com.aspectJ.demo1.ProductDao.save(.))",
throwing = "e"
)
public void afterThrowing(Throwable e){
System.out.println("异常抛出通知=============="+e.getMessage());
}
@After 最终通知
@After(value="execution(* com.aspectJ.demo1.ProductDao.save(.))")
public void after(){
System.out.println("最终通知==================");
}
@Before(value="myPointcut1()")
public void before(JoinPoint joinPoint){
System.out.println("前置通知=================="+joinPoint);
}
@Pointcut(value="execution(* com.aspectJ.demo1.ProductDao.save(.))")
private void myPointcut1(){}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--开启AspectJ的注解开发,自动代理=====================-->
<aop:aspectj-autoproxy/>
<!--目标类===================-->
<bean id="productDao" class="com.aspectJ.demo1.ProductDao"/>
<!--定义切面-->
<bean class="com.aspectJ.demo1.MyAspectAnno"/>
</beans>
/**
* 切面类
*/
@Aspect
public class MyAspectAnno {
@Before(value="myPointcut1()")
public void before(JoinPoint joinPoint){
System.out.println("前置通知=================="+joinPoint);
}
@AfterReturning(value="myPointcut2()",returning = "result")
public void afterReturing(Object result){
System.out.println("后置通知=================="+result);
}
@Around(value="myPointcut3()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("环绕前通知================");
Object obj = joinPoint.proceed(); // 执行目标方法
System.out.println("环绕后通知================");
return obj;
}
@AfterThrowing(value="myPointcut4()",throwing = "e")
public void afterThrowing(Throwable e){
System.out.println("异常抛出通知=============="+e.getMessage());
}
@After(value="myPointcut5()")
public void after(){
System.out.println("最终通知==================");
}
@Pointcut(value="execution(* com.aspectJ.demo1.ProductDao.save(.))")
private void myPointcut1(){}
@Pointcut(value="execution(* com.aspectJ.demo1.ProductDao.update(.))")
private void myPointcut2(){}
@Pointcut(value="execution(* com.aspectJ.demo1.ProductDao.delete(.))")
private void myPointcut3(){}
@Pointcut(value="execution(* com.aspectJ.demo1.ProductDao.findOne(.))")
private void myPointcut4(){}
@Pointcut(value="execution(* com.aspectJ.demo1.ProductDao.findAll(.))")
private void myPointcut5(){}
}
XML开发方式与注解方式只是用了不同给方式完成相同功能,两者使用上有相似之处
编写切面类(切面类上不使用注解)
完成目标类切面类的配置
<!--配置目标类=================-->
<bean id="customerDao" class="com.aspectJ.demo2.CustomerDaoImpl"/>
<!--配置切面类-->
<bean id="myAspectXml" class="com.aspectJ.demo2.MyAspectXml"/>
配置AOP完成增强
public class MyAspectXml {
// 前置通知
public void before(JoinPoint joinPoint){
System.out.println("XML方式的前置通知=============="+joinPoint);
}
// 后置通知
public void afterReturing(Object result){
System.out.println("XML方式的后置通知=============="+result);
}
// 环绕通知
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("XML方式的环绕前通知==============");
Object obj = joinPoint.proceed();
System.out.println("XML方式的环绕后通知==============");
return obj;
}
// 异常抛出通知
public void afterThrowing(Throwable e){
System.out.println("XML方式的异常抛出通知============="+e.getMessage());
}
// 最终通知
public void after(){
System.out.println("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:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--XML的配置方式完成AOP的开发===============-->
<!--配置目标类=================-->
<bean id="customerDao" class="com.aspectJ.demo2.CustomerDaoImpl"/>
<!--配置切面类-->
<bean id="myAspectXml" class="com.aspectJ.demo2.MyAspectXml"/>
<!--aop的相关配置=================-->
<aop:config>
<!--配置切入点-->
<aop:pointcut id="pointcut1" expression="execution(* com.aspectJ.demo2.CustomerDao.save(.))"/>
<aop:pointcut id="pointcut2" expression="execution(* com.aspectJ.demo2.CustomerDao.update(.))"/>
<aop:pointcut id="pointcut3" expression="execution(* com.aspectJ.demo2.CustomerDao.delete(.))"/>
<aop:pointcut id="pointcut4" expression="execution(* com.aspectJ.demo2.CustomerDao.findOne(.))"/>
<aop:pointcut id="pointcut5" expression="execution(* com.aspectJ.demo2.CustomerDao.findAll(.))"/>
<!--配置AOP的切面-->
<aop:aspect ref="myAspectXml">
<!--配置前置通知-->
<aop:before method="before" pointcut-ref="pointcut1"/>
<!--配置后置通知-->
<aop:after-returning method="afterReturing" pointcut-ref="pointcut2" returning="result"/>
<!--配置环绕通知-->
<aop:around method="around" pointcut-ref="pointcut3"/>
<!--配置异常抛出通知-->
<aop:after-throwing method="afterThrowing" pointcut-ref="pointcut4" throwing="e"/>
<!--配置最终通知-->
<aop:after method="after" pointcut-ref="pointcut5"/>
</aop:aspect>
</aop:config>
</beans>
——使用Spring组件JDBC Template简化持久化操作
为了持久化操作,Spring再JDBC API之上提供了JDBC Template组件
JDBC Template 提供统一的模板方法,在保留代码灵活性的基础上,尽量减少持久化代码
// JDBC API
Statement statement = conn.createStatement();
ResultSet resultset = statement.executeQuery("select count(*) COUNT from student");
if(resultSet.next()){
Integer count = resultSet.getInt("COUNT");
}
// JDBC Template
Integer count = jt.queryForObject("select count(*) from student", Integer.class);
Maven
Mysql驱动
<!-- mysql8以上版本使用8以上依赖-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.17</version>
</dependency>
Spring组件:core、beans、context、aop
<!--设置属性-->
<properties>
<spring.version>4.2.4.RELEASE</spring.version>
</properties>
<!--配置依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${spring.version}</version>
</dependency>
JDBC Template:jdbc、tx
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.version}</version>
</dependency>
单元测试
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
Spring 配置
数据源配置
<!-- 数据源 -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:数据库端口默认3306/数据库名"/>
<!--使用Unicode需要在url后加上:?useUnicode=true&characterEncoding=utf-8 -->
<!--账号密码正确但连接失败(时区问题)在url后加上:&serverTimezone=UTC -->
<property name="username" value="mysql的用户名"/>
<property name="password" value="mysql的密码"/>
</bean>
Jdbc Template
<!-- JdbcTemplate -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
不要忘记开启自动扫描,用于稍后注入JdbcTemplate
<context:component-scan base-package="com.tonited.sc"/>
execute方法可以直接执行sql语句,常用于执行对表的操作
public void testExecute(){
jdbcTemplate.execute("create table user1(id int, name varchar(20))");
}
update
对数据进行增删改查操作,返回值为被影响的行数
sql为sql语句字符串
args为参数
int update(String sql, Object[] args)
int update(String sql, Object.. args)//不定参数
重载一
public void testUpdate(){
String sql = "insert into student(name,sex) value(?,?)";
jdbcTemplate.update(sql, new Object[]{"张飞","男"});
}
重载二
public void testUpdate2(){
String sql = "update student set sex=? where id=?";
jdbcTemplate.update(sql, "男",1);
}
batchUpdate方法
批量增删改查,返回值为每个操作影响的行数
int[] batchUpdate(String[] sql)
int[] batchUpdate(String sql, List<Object[]> args)
重载一
public void testBatchUpdate(){
String[] sqls = {
"insert into student(name, sex) value('关羽', '女')",
"insert into student(name, sex) value('刘备','男')",
"update student set sex='男' where id=2"
};
jdbcTemplate.batchUpdate(sqls);
}
重载二
public void testBatchUpdat2(){
String sql = "insert into selection(student, course) value(?,?)";
List<Object[]> list = new ArrayList<>();
list.add(new Object[] {3,1});
jdbcTemplate.batchUpdate(sql,list);
}
获取一条数据
T queryForObject (String sql, Class<T> type)
T queryForObject (String sql, Object[] args, Class<T> type)
T queryForObject (String sql, Class<T> type, Object.. arg)
重载一
public void testQuerySimple(){
String sql = "select count(*) from student";
int count = jdbcTemplate.queryForObject(sql, Integer.class);
System.out.println(count);
}
获取多条数据
List<T> queryForList(String sql, Class<T> type)
List<T> queryForList(String sql, Object[] args, Class<T> type)
List<T> queryForList(String sql, Class<T> type, Object.. args)
重载三
public void testQuerySimple2(){
String sql = "select name from student where sex=?";
List<String> names = jdbcTemplate.queryForList(sql,String.class,"女");
System.out.println(names);
}
查询一条数据
Map queryForMap(String sql)
Map queryForMap(String sql, Object[] args)
Map queryForMap(String sql, Object.. args)
查询多条数据
List< Map<String,Object> > queryForList(String sql)
List< Map<String,Object> > queryForList(String sql, Object[] args)
List< Map<String,Object> > queryForList(String sql, Object.. args)
重载三
public void testQueryMap1(){
String sql = "select name,sex from student where sex=?";
List<Map<String,Object>> stu = jdbcTemplate.queryForList(sql,"男");
System.out.println(stu);
}
参数中的RowMapper需要实现接口RowMapper
private class StudentRowMapper implements RowMapper<Student>{
public Student mapRow(ResultSet resultSet, int i) throws SQLException {
Student stu = new Student();
stu.setId(resultSet.getInt("id"));
stu.setName(resultSet.getString("name"));
stu.setSex(resultSet.getString("sex"));
stu.setBorn(resultSet.getDate("born"));
return stu;
}
}
获取一条数据
T queryForObject(String sql, RowMapper<T> mapper)
T queryForObject(String sql, Object[] args ,RowMapper<T> mapper)
T queryForObject(String sql, RowMapper<T> mapper, Object.. args)
重载三
public void testQueryEntity1(){
String sql = "select * from student where id = ?";
Student stu = jdbcTemplate.queryForObject(sql, new StudentRowMapper(), 1004);
System.out.println(stu);
}
获取多条数据
List<T> query(String sql, RowMapper<T> mapper)
List<T> query(String sql, Object[] args ,RowMapper<T> mapper)
List<T> query(String sql, RowMapper<T> mapper, Object.. args)
重载三
public void testQueryEntity2(){
String sql = "select * from student";
List<Student> stus = jdbcTemplate.query(sql,new StudentRowMapper());
System.out.println(stus);
}
public class Course {
private int id;
private String name;
private int score;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getScore() {
return score;
}
public void setScore(int score) {
this.score = score;
}
}
注入JdbcTemplate
不要忘记开启包扫描
<context:component-scan base-package="com.tonited.sc"/>
声明RowMapper
@Repository
public class SelectionDaoImpl implements SelectionDao {
@Autowired
private JdbcTemplate jdbcTemplate;
public void insert(List<Selection> seles) {
String sql = "insert into selection values(?,?,?,?)";
List<Object[]> list = new ArrayList<Object[]>();
for(Selection sel:seles){
Object[] args = new Object[4];
args[0] = sel.getSid();
args[1]=sel.getCid();
args[2] = sel.getSelTime();
args[3] =sel.getScore();
list.add(args);
}
jdbcTemplate.batchUpdate(sql,list);
}
public void delete(int sid,int cid) {
String sql = "delete from selection where student=? and course=?";
jdbcTemplate.update(sql,sid,cid);
}
public List<Map<String, Object>> selectByStudent(int sid) {
String sql = "select se.*,stu.name sname,cou.name cname from selection se " +
"left join student stu on se.student=stu.id " +
"left join course cou on se.course=cou.id" +
"where student=?";
return jdbcTemplate.queryForList(sql,sid);
}
public List<Map<String, Object>> selectByCourse(int cid) {
String sql = "select se.*,stu.name sname,cou.name cname from selection se " +
"left join student stu on se.student=stu.id " +
"left join course cou on se.course=cou.id" +
"where course=?";
return jdbcTemplate.queryForList(sql,cid);
}
}
JDBC Template是Spring框架对JDBC操作的封装,简单、灵活,但是不够强大。
因此实际应用中还需要和其他ORM框架(Hibernate、MyBatis等)结合使用。
以下参考:
理解事务之前,先要理解日常生活中的一个常做的事:取钱
你去ATM机取1000块钱,大体有两个步骤银行卡扣除1000元、ATM出1000元
这两个步骤必须要么都执行、要么都不执行
所以,如果一个步骤成功另一个步骤却失败,对双方都不是好事
如果其中哪一个步骤出了问题可以取消所有操作,回退到未作任何操作的状态的话,这对双方都有利。
事务就是用来解决类似问题的。事务是一系列操作,他们综合在一起才是一个完整的工作单元,这些动作必须全部完成,如果其中有任何失败的操作,事务就会回滚到最开始的状态。
在企业级应用程序开发中,事务管理必不可少,用来确保数据的一致性与完整性
特性 | 含义 |
---|---|
原子性(Atomicity) | 事务是一个原子操作,由一系列动作组成。事务的原子性确保动作要么全部完成,要么不起作用 |
一致性(Consistency) | 事务一旦完成,不管成功还是失败,系统必须确保它所建模的业务处于一致的状态,而不会是部分完成部分失败。在现实中的数据不应该被破坏 |
隔离性(Isolation) | 可能有许多事务会同时处理相同的数据,因此每个事务都应该与其他事务隔离开,防止数据破坏 |
持久性(Durability) | 一旦事务完成,无论发生什么系统错误,他的结果都不应该受到影响,这样就能从任何系统崩溃中恢复过来。通常情况下,事务的结果被写进持久化存储器中 |
Connection
对象控制的手动模式和自动模式Spring并不管理事务,而是提供了多种事务管理器,他们将事务管理的职责委托给Hibernate或者JTA等持久化机制所提供的相关平台框架的事务来实现
Spring事务管理器的接口是 org.springframework.transaction.PlatformTransactionManager,通过这个接口,Spring为各个平台如JDBC、Hibernate等都提供了对应的事务管理器,但是具体的实现就是平台自己的事情了
接口代码如下
Public interface PlatformTransactionManager()..{
// 由TransactionDefinition得到TransactionStatus对象
TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;
// 提交
Void commit(TransactionStatus status) throws TransactionException;
// 回滚
Void rollback(TransactionStatus status) throws TransactionException;
}
从代码中可以看出
如果应用程序中直接使用JDBC来进行持久化,DataSourceTransitionManager会为你处理事务边界(提交、回滚)
为了使用DateSourceTransactionManager,需要使用如下XMl装配到应用程序上下文定义中
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
实际上,DataSourceTransitionManager是通过调用java.sql.Connection来管理事务,而java.sql.Connection是通过DataSource获得的
通过调用连接的commit()方法来提交事务,同样,事务失败则通过调用rollback()方法回滚
如果应用程序的持久化是通过Hibernate实现,那么需要使用HibernateTransactionManager
对于Hibernate3,需要在Spring上下文定义中添加如下的<bean>
声明:
<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
sessionFactory属性需要装配一个Hibernate的session工厂
HibernateTransitionManager的实现细节是它将事务管理的职责委托给org.hibernate.Transition
对象,而后者是从HibernateSessioin
中获取到的
当事务成功完成时,HibernateTransitionManager
将会调用Transition
对象的commite()
方法,反之调用rollback()
方法
JPA: JPA 是一个基于O/R映射的标准规范。所谓规范即只定义标准规则(如注解、接口),不提供实现,软件提供商可以按照标准规范来实现,而使用者只需按照规范中定义的方式来使用,而不用和软件提供商的实现打交道
Hibernate多年来一直是事实上的Java持久化标准,但是现在Java持久化API作为真正的Java持久化标准进入大家的视野
JPA和Hibernate之间的关系
如果计划使用JPA,需要使用Spring的JpaTransactionManager
来处理事务
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
JpaTransactionManager
只需要装配一个JPA实体管理工厂(javax.persistence.EntityManagerFactory
接口的任意实现)
JpaTransictionManager
将与由工厂所产生的JPA EntityManager
合作构建事务
如果没有使用以上所述的事务管理,或者是跨越了多个事务管理源(比如两个或者多个不同的数据源),就需要使用JtaTransactionManager
<bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager">
<property name="transactionManagerName" value="java:/TransactionManager" />
</bean>
JtaTransictionManager
将事务管理的责任委托给javax.transaction.UserTransaction
和javax.transaction.TransactionManager
对象
UserTransaction.commit()
方法提交UserTransaction.rollback()
方法回滚事务管理器接口PlatformTransactionManager
通过getTransaction(TransactionDefinition definition)
方法来得到事务,这个方法里面的参数是TransactionDefinition
类
TransactionDefinition
类
TransactionDefinition
接口代码
public interface TransactionDefinition {
int getPropagationBehavior(); // 返回事务的传播行为
int getIsolationLevel(); // 返回事务的隔离级别,事务管理器根据它来控制另外一个事务可以看到本事务内的哪些数据
int getTimeout(); // 返回事务必须在多少秒内完成
boolean isReadOnly(); // 事务是否只读,事务管理器能够根据这个返回值进行优化,确保事务是只读的
}
传播行为 | 含义 |
---|---|
PROPAGATION_REQUIRED | 表示当前方法必须运行在事务中。如果当前事务存在,方法将会在该事务中运行。否则,会启动一个新的事务 |
PROPAGATION_SUPPORTS | 表示当前方法不需要事务上下文,但是如果存在当前事务的话,那么该方法会在这个事务中运行 |
PROPAGATION_MANDATORY | 表示该方法必须在事务中运行,如果当前事务不存在,则会抛出一个异常 |
PROPAGATION_REQUIRED_NEW | 表示当前方法必须运行在它自己的事务中。一个新的事务将被启动。如果存在当前事务,在该方法执行期间,当前事务会被挂起。如果使用JTATransactionManager的话,则需要访问TransactionManager |
PROPAGATION_NOT_SUPPORTED | 表示该方法不应该运行在事务中。如果存在当前事务,在该方法运行期间,当前事务将被挂起。如果使用JTATransactionManager的话,则需要访问TransactionManager |
PROPAGATION_NEVER | 表示当前方法不应该运行在事务上下文中。如果当前正有一个事务在运行,则会抛出异常 |
PROPAGATION_NESTED | 表示如果当前已经存在一个事务,那么该方法将会在嵌套事务中运行。嵌套的事务可以独立于当前事务进行单独地提交或回滚。如果当前事务不存在,那么其行为与PROPAGATION_REQUIRED一样。注意各厂商对这种传播行为的支持是有所差异的。可以参考资源管理器的文档来确认它们是否支持嵌套事务 |
注:以下具体讲解传播行为的内容参考自Spring事务机制详解
//事务属性 PROPAGATION_REQUIRED
methodA{
……
methodB();
……
}
//事务属性 PROPAGATION_REQUIRED
methodB{
……
}
使用spring声明式事务,spring使用AOP来支持声明式事务,会根据事务属性,自动在方法调用之前决定是否开启一个事务,并在方法执行之后决定事务提交或回滚事务
单独调用methodB方法
Main{ methodB(); }
相当于
Main{
Connection con=null;
try{
con = getConnection();
con.setAutoCommit(false);
//方法调用
methodB();
//提交事务
con.commit();
} Catch(RuntimeException ex) {
//回滚事务
con.rollback();
} finally {
//释放资源
closeCon();
}
}
methodB
方法中所有的调用都或得到相同的连接methodB
时没有事务存在,根据传播行为PROPAGATION_REQUIRED
的含义,它获得了一个新的连接,开启了一个新的事务单独调用methodA
时,methodA
内部会调用mehodB
执行效果相当于
Main{
Connection con = null;
try{
con = getConnection();
methodA();
con.commit();
} catch(RuntimeException ex) {
con.rollback();
} finally {
closeCon();
}
}
methodA
时,环境中没有事务,所以开启一个新的事务,当在methodA
中调用methodB
时,环境中已经有了刚刚由于methodA
创建的事务,所以把methodB
加入那个事务中//事务属性 PROPAGATION_REQUIRED
methodA(){
methodB();
}
//事务属性 PROPAGATION_SUPPORTS
methodB(){
……
}
methodB
时,methodB
方法非事务的执行methodA
时,methodB
则加入methodA
活动的事务中,并事务的执行//事务属性 PROPAGATION_REQUIRED
methodA(){
methodB();
}
//事务属性 PROPAGATION_MANDATORY
methodB(){
……
}
methodB
时,因为无活动事务,则会抛出异常throw new IllegalTransactionStateException(“Transaction propagation ‘mandatory’ but no existing transaction found”);
methodA
时,methodB
则加入到methodA
的事务中,事务的执行PROPAGATION_REQUIRES_NEW
,需要使用JtaTransactionManager
作为事务管理器//事务属性 PROPAGATION_REQUIRED
methodA(){
doSomeThingA();
methodB();
doSomeThingB();
}
//事务属性 PROPAGATION_REQUIRES_NEW
methodB(){
……
}
调用methodA
相当于
main(){
TransactionManager tm = null;
try{
//获得一个JTA事务管理器
tm = getTransactionManager();
tm.begin();//开启一个新的事务
Transaction ts1 = tm.getTransaction();
doSomeThing();
tm.suspend();//挂起当前事务
try{
tm.begin();//重新开启第二个事务
Transaction ts2 = tm.getTransaction();
methodB();
ts2.commit();//提交第二个事务
} Catch(RunTimeException ex) {
ts2.rollback();//回滚第二个事务
} finally {
//释放资源
}
//methodB执行完后,恢复第一个事务
tm.resume(ts1);
doSomeThingB();
ts1.commit();//提交第一个事务
} catch(RunTimeException ex) {
ts1.rollback();//回滚第一个事务
} finally {
//释放资源
}
}
这里把ts1
成为外层事务,ts2
称为内层事务,ts2
与ts1
时两个独立的事务,互不相干。ts2
是否成功并不依赖于ts1
methodA
方法在调用methodB
方法之后的doSomeThingB
方法失败了,而methodB
方法所做的结果依然被提交,除了methodB
之外的其他代码导致的结果却被回滚了使用PROPAGATION_REQUIRES_NEW
,需要使用JtaTransactionManager
作为事务管理器
PROPAGATION_NOT_SUPPORTED
,也需要JtaTransactionManager
作为事务管理器(代码示例同上)TransactionDefinition.PROPAGATION_REQUIRED
属性执行DataSourceTransactionManager
作为事务管理器
java.sql.Savepoint
类PROPAGATION_NESTED
,还需要把PlatformTransactionManager
的nestedTransactionAllowed
属性设为true
;而 nestedTransactionAllowed
属性值默认为false
。//事务属性 PROPAGATION_REQUIRED
methodA(){
doSomeThingA();
methodB();
doSomeThingB();
}
//事务属性 PROPAGATION_NESTED
methodB(){
……
}
如果单独调用methodB
方法,则按照REQUIRED
属性执行
如果调用methodA
方法,相当于下面效果
Main(){
Connection con = null;
Savepoint savepoint = null;
try{
con = getConnection();
con.setAutoCommit(false);
doSomeThingA();
savepoint = con2.setSavepoint();
try{
methodB();
} catch(RuntimeException ex) {
con.rollback(savepoint);
} finally {
//释放资源
}
doSomeThingB();
con.commit();
} catch(RuntimeException ex) {
con.rollback();
} finally {
//释放资源
}
}
methodB
方法调用之前,调用setSavePoint
方法,保存当前的状态到savepoint
methodB
方法调用失败,则恢复到之前保存的状态,值得注意的是:这时的事务并没有提交,如果后续的代码(doSomeThingB()
方法)调用失败,则回滚包括methodB
方法的所有操作PROPAGATION_NESTED 与PROPAGATION_REQUIRES_NEW的异同:
使用PROPAGATION_REQUIRES_NEW时 | 使用PROPAGATION_REQUIRES_NEW时 | |
---|---|---|
内外事务回滚关系 | 内层事务和外层事务就像两个独立的事务一样,一旦内层事务进行提交之后外层事务不能对其进行回滚 | 外层事务的回滚可以引起内层事务的回滚,内层事务的回滚不会导致外层事务的回滚 |
是否为真正的嵌套事务 | 否 | 是 |
支持 | 它需要JTA事务管理器的支持 | DataSourceTransactionManager使用savepoint支持。需要JDBC 3.0以上驱动及1.4以上的JDK版本支持。其它的JTA TrasactionManager实现可能有不同的支持方式。 |
PROPAGATION_REQUIRES_NEW
创建一个新的、不依赖于环境的“内部”事务。
commited
或 rolled back
而不是依赖于外部事务,它拥有自己的隔离范围、自己的锁等等PROPAGATION_NESTED
开始一个“嵌套”事务,他是已经存在事务的真正的子事务
savepoint
,如果这个嵌套事务执行失败,我们将回滚到此savepoint
PROPAGATION_REQUIRES_NEW
和PROPAGATION_NESTED
的最大区别在于
commit
,嵌套事务也会会被commit
,这个规则同样适用于 roll back
问题 | 英文 | 含义 |
---|---|---|
脏读 | Dirty reads | 脏读发生在一个事物读取了另一个事务“改写但尚未提交的数据”时,如果改写在稍后被回滚了,那么第一个事务获取的数据就是无效的 |
不可重复读 | Norepeatable read | 不可重复读发生在“一个事务执行相同的查询两次或两次以上,但是每次都得到不同的数据”时。这通常是因为另一个并发事务在两次查询期间进行了更新 |
幻读 | Phantom read | 幻读与不可重复度类似,它发生在一个事务(T1)读取了几行数据,接着另一个并发事务(T2)插入了一些数据时。在随后的查询中,第一个事务(T1)就会发现多了一些原本不存在的记录 |
不可重复读的重点是修改
幻读的重点是插入删除数据
不可重复读
例如:在事务1种,Mary读取了自己的工资为1000,操作并没有完成
con1 = getConnection();
select salary from employee empid="Mary";
在事务2中,这时财务人员修改了Mary的工资为2000,并提交了事务
con2 = getConnection();
update employee set salary=2000;
con2.commit();
在事务1中,Mary再次读取自己的工资时,工资变为了2000
// con1
select salary from employee empId="Mary";
在一个事务中前后两次读取的结果并不一致,导致的不可重复读
幻读
同样的条件,第一次第二次读出来的记录个数不一样
例如:目前工资为1000的员工有10人。事务1,读取所有工资为1000的员工,读取结果为10条记录
con1 = getConnection();
select * from employee where salary=1000;
这时另一个事务向employee表中插入了一条员工记录,工资也为1000
con2 = getConnection();
insert into employee(empid,salary) values("Lili",1000);
con2.commit();
事务1再次读取所有工资为1000的员工,这时读取到的记录数量是11,这就产生了幻读
//con1
select * from employee where salary=1000;
不同角度看待不可重复读与幻读的区别
隔离级别 | 含义 | 个人见解 |
---|---|---|
ISOLATION_DEFAULT | 使用后端数据库默认的隔离级别 | |
ISOLATION_READ_UNCOMMITTED | 最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读 | 查询时可以有任何事务对数据操作,可以查询没有提交的数据 |
ISOLATION_READ_COMMITTED | 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生 | 查询时只能查询已经提交的数据 |
ISOLATION_REPEATABLE_READ | 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生 | 在上面的基础上,查询时不能有修改被查询数据的事务对被查询数据操作 |
ISOLATION_SERIALIZABLE | 最高的隔离级别,完全服从ACID的隔离级别,确保阻止脏读、不可重复读以及幻读,也是最慢的事务隔离级别,因为它通常是通过完全锁定事务相关的数据库表来实现的 | 在上面的基础上,查询时不能有插入删除被查询数据的事务对被查询数据操作 |
readOnly
不起作用,不影响其增删改查readOnly
为true
时只能查,增删改操作会抛出异常上面讲到的调用PlatformTransactionManager
接口的getTransaction()
的方法得到的是TransactionStatus
接口的一个实现,这个接口如下
public interface TransactionStatus{
boolean isNewTransaction(); // 是否是新的事物
boolean hasSavepoint(); // 是否有恢复点
void setRollbackOnly(); // 设置为只回滚
boolean isRollbackOnly(); // 是否为只回滚
boolean isCompleted; // 是否已完成
}
TransactionTemplate
此为Spring官方团队推荐的编程式事务管理方式
主要工具类为JdbcTemplate
类
采用TransactionTemplate
和采用其他Spring框架,如JdbcTemplate
和Hibernate
是一样的方法
它使用回调函数(“TransactionCallback
类或TransactionCallbackWithoutResult
类”中的doTransaction
函数),把应用程序从处理取得和释放资源中解脱出来
如同其他模板,TransactionTemplate
是线程安全的
实现方法
TransactionTemplate
)对象TransactionTemplate tt = new TransactionTemplate(); // 新建一个TransactionTemplate
// 可设置事务属性,如隔离级别、超时时间等,如:
// tt.setIsolationLevel(TransactionDefinition.ISOLATION_READ_UNCOMMITTED);
Object result = tt.execute(
new TransactionCallback(){
public Object doTransaction(TransactionStatus status){
updateOperation();
// 数据库操作1
// JdbcTemplate jdbcTemplate = new JdbcTemplate(数据源);
// jdbcTemplate.execute(sql);
// 数据库操作2:简单模板化新增数据
//SimpleJdbcInsert simpleInsert = new getSimpleJdbcTemplate(数据源);
//simpleInsert.withTableName("books").usingColumns("isbn", "name", "price", "pubdate");
//Map<String, Object> parameters = new HashMap<String, Object>();
//parameters.put("isbn", book.getIsbn());
//parameters.put("name", book.getName());
//parameters.put("price", book.getPrice());
//parameters.put("pubdate", book.getPubdate());
//simpleInsert.execute(parameters);
//System.out.println("新增数据成功!");
// 数据库操作3;DAO数据操作模式:
// BookDAO.save(book);
return resultOfUpdateOperation();
}
}); // 执行execute方法进行事务管理
TransactionCallback
可以返回一个值,使用TransactionCallbackWithoutResult
无返回值PlatformTransactionManager
类似于JTA UserTransaction API
方式,但异常处理更简洁
辅助类为TransactionDefinition
和TransactionStatus
实现方法
步骤
实现代码片段
// 编程式事务管理:事务管理器PlatformTransactionManager方式实现
public void updateBookByIsbn(Book book) {
//第一步:获取JDBC事务管理器
DataSourceTransactionManager dtm = new DataSourceTransactionManager(数据源);
// 第二步:创建事务管理器属性对象
DefaultTransactionDefinition transDef = new DefaultTransactionDefinition(); // 定义事务属性
// 根据需要,设置事务管理器的相关属性
// 如设置传播行为属性
transDef.setPropagationBehavior(DefaultTransactionDefinition.PROPAGATION_REQUIRED);
// 第三步:获得事务状态对象(getTransaction会自动开启事务并返回该事务的状态对象)
TransactionStatus ts = dtm.getTransaction(transDef);
// 第四步:基于当前事务管理器,获取数据源,创建操作数据库的JDBC模板对象
JdbcTemplate jt = new JdbcTemplate(dtm.getDataSource());
try {//第五步:业务操作
jt.update("update books set price="+book.getPrice()+",name='"+book.getName()
+"' where isbn='"+book.getIsbn()+"' ");
// 其它数据操作如增删
//第六步:提交事务
dtm.commit(ts); // 如果不commit,则更新无效果
} catch (Exception e) {
dtm.rollback(ts);
e.printStackTrace();
}
}
DataSourceTransactionManager.getTransaction()
方法会自动开启事务并返回该事务的状态对象TransactionTemplate
)PlatefromTransactionManager
)JdbcTemplate
完成业务处理根据代理机制不同,有以下五种Spring事务配置方式
独立代理:每个Bean都有一个代理
共享代理:所有Bean共享一个代理基类
拦截器
tx拦截器
全注释:完全依靠注解(@Transactional)设置事务,配置文件中不做事务配置
本小节配置均为Hibernate示例
sessionFactory
<?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-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">
<!-- (Hibernate声明式的事务,如果是JDBC需要配数据源) -->
<bean id="sessionFactory"
class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<property name="configLocation" value="classpath:hibernate.cfg.xml(数据库连接的相关配置文件)" />
<property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" />
</bean>
<!-- 定义事务管理器(Hibernate声明式的事务) -->
<bean id="transactionManager"
class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
<!-- 定义事务管理器(JDBC声明式的事务) -->
<!--
<bean id="transactionManagerr"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
-->
<!-- 配置DAO -->
<bean id="userDaoTarget" class="com.bluesky.spring.dao.UserDaoImpl">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
<bean id="userDao"
class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<!-- 配置事务管理器 -->
<property name="transactionManager" ref="transactionManager" />
<!--target属性指向被代理的对象-->
<property name="target" ref="userDaoTarget" />
<property name="proxyInterfaces" value="com.bluesky.spring.dao.GeneratorDao" />
<!-- 配置事务属性 -->
<property name="transactionAttributes">
<props>
<!-- *对于所有方法 -->
<prop key="*">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>
</beans>
DaoTarget
)<?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-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">
<bean id="sessionFactory"
class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<property name="configLocation" value="classpath:hibernate.cfg.xml(数据库连接的相关配置文件)" />
<property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" />
</bean>
<!-- 定义事务管理器(声明式的事务) -->
<bean id="transactionManager"
class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
<bean id="transactionBase"
class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"
lazy-init="true" abstract="true">
<!-- 配置事务管理器 -->
<property name="transactionManager" ref="transactionManager" />
<!-- 配置事务属性 -->
<property name="transactionAttributes">
<props>
<!--*设置所有方法都为REQUIRED-->
<prop key="*">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>
<!-- 配置DAO -->
<bean id="userDaoTarget" class="com.bluesky.spring.dao.UserDaoImpl">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
<bean id="userDao" parent="transactionBase" >
<property name="target" ref="userDaoTarget" />
</bean>
</beans>
DaoTarget
)创造一个代理(Dao
),这个代理继承公共代理(transactionBase
),完成事务管理器和事务属性的配置在公共代理中配置,而继承的代理只要指定代理目标和修改自身代理的特殊部分即可<?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-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">
<!--配置Hibernate需要使用的sessionFactory-->
<bean id="sessionFactory"
class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<property name="configLocation" value="classpath:hibernate.cfg.xml(数据库连接的相关配置文件)" />
<property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" />
</bean>
<!-- 定义事务管理器(HIbernate声明式的事务) -->
<bean id="transactionManager"
class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
<!--配置事务拦截-->
<bean id="transactionInterceptor"
class="org.springframework.transaction.interceptor.TransactionInterceptor">
<property name="transactionManager" ref="transactionManager" />
<!-- 配置事务属性 -->
<property name="transactionAttributes">
<props>
<prop key="*">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>
<!--根据名称自动代理-->
<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<property name="beanNames">
<list>
<!--自动代理所有以“Dao”结尾的Bean-->
<value>*Dao</value>
</list>
</property>
<property name="interceptorNames">
<list>
<value>transactionInterceptor</value>
</list>
</property>
</bean>
<!-- 配置DAO -->
<bean id="userDao" class="com.bluesky.spring.dao.UserDaoImpl">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
</beans>
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">
<!--开启注解-->
<context:annotation-config />
<!--开启包扫描,用于自动注入-->
<context:component-scan base-package="com.bluesky" />
<bean id="sessionFactory"
class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<property name="configLocation" value="classpath:hibernate.cfg.xml" />
<property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" />
</bean>
<!-- 定义事务管理器(声明式的事务) -->
<bean id="transactionManager"
class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
<!--配置通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="*" propagation="REQUIRED" />
</tx:attributes>
</tx:advice>
<aop:config>
<!--配置切点-->
<aop:pointcut id="interceptorPointCuts"
expression="execution(* com.bluesky.spring.dao.*.*(.))" />
<!--切点表达式-->
<!--配置切面-->
<aop:advisor advice-ref="txAdvice"
pointcut-ref="interceptorPointCuts" />
</aop:config>
</beans>
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">
<!--开启注解-->
<context:annotation-config />
<!--开启包扫描-->
<context:component-scan base-package="com.bluesky" />
<!--开启tx注解(@Transactional)-->
<tx:annotation-driven transaction-manager="transactionManager"/>
<bean id="sessionFactory"
class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<property name="configLocation" value="classpath:hibernate.cfg.xml" />
<property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" />
</bean>
<!-- 定义事务管理器(声明式的事务) -->
<bean id="transactionManager"
class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
</beans>
@Transactional
@Component("userDao")
public class UserDaoImpl extends HibernateDaoSupport implements UserDao {
public List<User> listUsers() {
return this.getSession().createQuery("from User").list();
}
}
@Transaction是全注解方式使用的事务配置
用来注解是事务的方法
可以设置事务属性
属性 | 含义 |
---|---|
propagation | 指定事务的传播行为,即当前的事务方法被另外一个事务方法调用时如何使用事务 * 默认取值为REQUIRED,即使用调用方法的事务 *也经常使用REQUIRES_NEW:使用自己的事务,调用的事务方法的事务被挂起 |
isolation | 指定事务的隔离级别,最常用的取值为READ_COMMITTED |
noRollbackFor | 默认情况下 Spring 的声明式事务对所有的运行时异常进行回滚,也可以通过对应的属性进行设置。通常情况下,默认值即可 |
readOnly | 指定事务是否为只读。 表示这个事务只读取数据但不更新数据,这样可以帮助数据库引擎优化事务。若真的是一个只读取数据库值的方法,应设置readOnly=true |
timeOut | 指定强制回滚之前事务可以占用的时间 |
@Transactional(propagation=Propagation.REQUIRES_NEW,
isolation=Isolation.READ_COMMITTED,
noRollbackFor={UserAccountException.class},
readOnly=true,
timeout=3)
public void purchase() {
someOperate();
}
注:该实例参考自Spring中的事务管理实例详解
首先是数据库表
book(isbn, book_name, price)
account(username, balance)
book_stock(isbn, stock)
XML配置_全注释
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">
<import resource="applicationContext-db.xml" />
<context:component-scan
base-package="com.springinaction.transaction">
</context:component-scan>
<tx:annotation-driven transaction-manager="txManager"/>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
</beans>
使用的类
BookShopDao
public interface BookShopDao {
// 根据书号获取书的单价
public int findBookPriceByIsbn(String isbn);
// 更新书的库存,使书号对应的库存-1
public void updateBookStock(String isbn);
// 更新用户的账户余额:account的balance-price
public void updateUserAccount(String username, int price);
}
BookShopDaoImpl
@Repository("bookShopDao")
public class BookShopDaoImpl implements BookShopDao {
@Autowired
private JdbcTemplate JdbcTemplate;
@Override
public int findBookPriceByIsbn(String isbn) {
String sql = "SELECT price FROM book WHERE isbn = ?";
return JdbcTemplate.queryForObject(sql, Integer.class, isbn);
}
@Override
public void updateBookStock(String isbn) {
//检查书的库存是否足够,若不够,则抛出异常
String sql2 = "SELECT stock FROM book_stock WHERE isbn = ?";
int stock = JdbcTemplate.queryForObject(sql2, Integer.class, isbn);
if (stock == 0) {
throw new BookStockException("库存不足!");
}
String sql = "UPDATE book_stock SET stock = stock - 1 WHERE isbn = ?";
JdbcTemplate.update(sql, isbn);
}
@Override
public void updateUserAccount(String username, int price) {
//检查余额是否不足,若不足,则抛出异常
String sql2 = "SELECT balance FROM account WHERE username = ?";
int balance = JdbcTemplate.queryForObject(sql2, Integer.class, username);
if (balance < price) {
throw new UserAccountException("余额不足!");
}
String sql = "UPDATE account SET balance = balance - ? WHERE username = ?";
JdbcTemplate.update(sql, price, username);
}
}
BookShopService
public interface BookShopService {
public void purchase(String username, String isbn);
}
BookShopServiceImpl
@Service("bookShopService")
public class BookShopServiceImpl implements BookShopService {
@Autowired
private BookShopDao bookShopDao;
/**
* 1.添加事务注解
* 使用propagation 指定事务的传播行为,即当前的事务方法被另外一个事务方法调用时如何使用事务。
* 默认取值为REQUIRED,即使用调用方法的事务
* REQUIRES_NEW:使用自己的事务,调用的事务方法的事务被挂起。
*
* 2.使用isolation 指定事务的隔离级别,最常用的取值为READ_COMMITTED
* 3.默认情况下 Spring 的声明式事务对所有的运行时异常进行回滚,也可以通过对应的属性进行设置。通常情况下,默认值即可。
* 4.使用readOnly 指定事务是否为只读。 表示这个事务只读取数据但不更新数据,这样可以帮助数据库引擎优化事务。若真的是一个只读取数据库值的方法,应设置readOnly=true
* 5.使用timeOut 指定强制回滚之前事务可以占用的时间。
*/
@Transactional(propagation=Propagation.REQUIRES_NEW,
isolation=Isolation.READ_COMMITTED,
noRollbackFor={UserAccountException.class},
readOnly=true, timeout=3)
@Override
public void purchase(String username, String isbn) {
//1.获取书的单价
int price = bookShopDao.findBookPriceByIsbn(isbn);
//2.更新书的库存
bookShopDao.updateBookStock(isbn);
//3.更新用户余额
bookShopDao.updateUserAccount(username, price);
}
}
Cashier
public interface Cashier {
public void checkout(String username, List<String>isbns);
}
CashierImpl:CashierImpl.checkout和bookShopService.purchase联合测试了事务的传播行为
@Service("cashier")
public class CashierImpl implements Cashier {
@Autowired
private BookShopService bookShopService;
@Transactional
@Override
public void checkout(String username, List<String> isbns) {
for(String isbn : isbns) {
bookShopService.purchase(username, isbn);
}
}
}
BookStockException(继承RuntimeException
)
UserAccountException(继承RuntimeException
)
测试(Test)类
关于IOC的理解
在Spring中,已知Student类的定义,以下属于无参数构造器的方式实例化Bean
<bean id="bean1" class="com.tonited.demo.Student">
关于事务隔离的说法
ISOLATION_SERIALIZABLE
是最高的隔离级别,可以防止脏读、不可重复读、幻读,也是最慢的事务隔离级别在Spring中,Bean在配置时,初始化和销毁方法配置在<bean ./>
中的标签名是init-method
和destory-method
关于事务的说法
Spring能够实现各个组件之间的解耦,涉及到的解耦原理是
事务在处理时具有不可分割性,要么全部被执行、要么全部不执行,这一特性是事务的原子性
以关于Spring IOC定义的说法
已下载Spring开发版本,其中Spring的开发规范和API文档存在于docs
目录下
Spring事务的三个核心接口是
TransactionDefinition
TransactionStatus
PlatformTransactionManager
关于Spring的说法
在SpringBean注入中,如果需要对Map进行数据注入,需要用到的标签
<entry>
<map>
<property>
在Spring中,如果bean没有配置scope属性的值,那么默认的作用域是singleton
关于Bean的和id的name说法
关于事务的说法
关于JDBC事务的说法
事务属性范围包括
在Spring中,下列关于依赖注入的说法
关于事务传播行为的说法
Spring IOC的底层是用反射模式和工厂模式来完成的
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。