spring 基本学习

第一章 Spring 概述

1.1 spring特点

spring就是一个java框架,使用java语言开发的, 轻量级的, 开源的框架。 可以在j2se、j2ee项目中都可以使用。
spring又叫做:容器, spring容器装的是java对象。 可以让spring创建java对象, 给属性赋值。管理对象的生命周期。

spring核心技术: ioc, aop

spring作用: 实现解耦合, 解决java对象之间的耦合, 解决模块之间的耦合。

tomcat也是容器:管理的是servlet, filter、 listener 等对象。

1.2 spring的地址

https://spring.io

1.3 Spring优点

(1) 轻量
Spring 框架使用的 jar 都比较小,一般在 1M 以下或者几百 kb。Spring核心功能的所需的 jar 总共在 3M 左右。
Spring 框架运行占用的资源少,运行效率高。不依赖其他 jar

(2) 面向接口编程,解耦合
Spring 提供了 Ioc 控制反转,由容器管理对象和对象间的依赖关系。原来在程序代码中的对象创建方式,现在由容器完成。对象之间的依赖解耦合。

(3) AOP 支持
通过 Spring 提供的 AOP 功能,方便进行面向切面的编程,许多不容易用传统 OOP 实现的功能可以通过 AOP 轻松应付在 Spring 中,开发人员可以从繁杂的事务管理代码中解脱出来,通过声明式方式灵活地进行事务的管理,提高开发效率和质量。

(4) 方便集成各种优秀框架
Spring 不排斥各种优秀的开源框架,相反 Spring 可以降低各种框架的使用难度,Spring 提供了对各种优秀框架(如 Struts,Hibernate、MyBatis)等的直接支持。简化框架的使用。Spring 像插线板一样,其他框架是插头,可以容易的组合到一起。需要使用哪个框架,就把这个插头放入插线板。不需要可以轻易的移除。

第二章 IoC 控制反转

2.1 IoC 概念

IoC: 控制反转, 是一个指导思想。 指导开发人员如何使用、管理对象的。 把 对象的创建,属性赋值,生命周期都交给容器管理。

1) IoC分为 控制和反转

​ 控制: 对象创建,属性赋值, 声明周期管理

​ 反转:把对象的权限转移给了容器。 由容器完成对象的管理。

​ 正转:开发人员在代码中, 使用 new 构造方法创建对象。 开发人员有对 对象 全部控制权。

2) IoC的技术实现

DI ( 依赖注入) :Dependency Injection。 DI 是IoC的一种技术实现。 程序只需要提供要使用的对象的名称就可以了, 对象如何创建由容器内部自己实现。

3) Spring框架使用的DI实现IoC.

通过spring框架, 只需要提供要使用的对象名词就可以了。 从容器中获取名称对应的对象。

spring底层使用的 反射机制, 通过反射创建对象,赋予属性。

2.2 Spring的配置文件

spring的xml配置文件可以直接在resources上右击新建spring xml配置文件,这样我们就不用自己写文件头这些了,
一般起名为 applicationContext.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">
	定义beans...
</beans>

spring标准的配置文件
1)根标签是 beans
2) beans 后面的是约束文件说明
3)beans里面是bean声明。
4)什么是bean: bean就是java对象
xmlns=“http://www.springframework.org/schema/beans” 指定beans命名空间值
xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance” 指定xsi的命名空间
beans标签和它的子标签都是在 http://www.springframework.org/schema/beans/spring-beans.xsd"定义和说明的

2.3 spring容器创建对象的特点

  1. 容器对象ApplicationContext:接口 ,一般用其实现类ClassPathXmlApplicationContext,我们在resources目录下的配置文件applicationContext.xml在编译后会在类路径 target/classes 下面,而上面那个类会直接在类路径下找同名称配置文件,并返回容器对象ApplicationContext。
    通过ApplicationContext对象,获取要使用的 其他java对象, 执行getBean(“的id”)

  2. spring默认是调用类的无参数构造方法创建对象,对应于属性的set注入。

  3. spring读取配置文件, 创建容器对象的时候一次创建好所有的java对象, 都放到map中,spring容器对象是通过map来管理java对象的。

2.4 DI:给属性赋值。

spring可以通过 xml配置文件注解@ 两种方式注入对象。
而对象java对象 属性的注入 可以采用set注入构造注入

2.4.1 基于xml的DI:

在xml配置文件中使用标签和属性,完成对象创建,属性赋值。

1) set注入

set注入: spring调用类中的set方法,在set方法中可以完成属性赋值。
调用的是类的无参构造和对应属性的set方法, 若没有无参构造或set方法会报错。
推荐使用。

<!--简单类型的set注入,使用value-->
<bean id="mySchool" class="com.bjpowernode.ba02.School">
    <property name="name" value="北京大学"/>
    <property name="address" value="北京的海淀区"/>
</bean>

<!--引用类型set注入,使用ref-->
<bean id="myStudent" class="com.bjpowernode.ba02.Student">
    <property name="name" value="李四"/>
    <property name="age" value="22" />
    <!--引用类型的赋值-->
    <property name="school" ref="mySchool" /><!--setSchool(mySchool)-->
</bean>

2) 构造注入

构造注入:spring调用类中的有参数构造方法, 在创建对象的同时,给属性赋值
使用构造输入得有有参构造,并且配置文件要和构造方法参数匹配。

<!--构造注入,使用name属性-->
<bean id="myStudent" class="com.bjpowernode.ba03.Student">
    <constructor-arg name="myage" value="22" />
    <constructor-arg name="myname" value="李四"/>
    <constructor-arg name="mySchool" ref="mySchool"/>
</bean>
<!--构造注入,使用index,参数的位置,构造方法参数从左往右位置是0,1,2-->
<bean id="myStudent2" class="com.bjpowernode.ba03.Student">
    <constructor-arg index="1" value="28"/>
    <constructor-arg index="0" value="张三"/>
    <constructor-arg index="2" ref="mySchool" />
</bean>
<!--构造注入,省略index属性-->
<bean id="myStudent3" class="com.bjpowernode.ba03.Student">
    <constructor-arg  value="张峰"/>
    <constructor-arg  value="28"/>
    <constructor-arg  ref="mySchool" />
</bean>

3)引用类型的自动注入

概念: spring可以根据某些规则给引用类型完成赋值。 只对引用类型有效。 规则 byName, byType.

①:byName(按名称注入): java类中引用类型属性名称和spring容器中bean的id名称一样的,且数据类型也是一样的,这些bean能够赋值给引用类型。

<!-- byName 自动注入,对象的引用属性自动和容器中其他符合的对象关联起来 -->
<bean id="myStudent" class="com.bjpowernode.ba04.Student" autowire="byName">
    <property name="name" value="李四"/>
    <property name="age" value="22" />
    <!--引用类型的赋值-->
    <!--<property name="school" ref="mySchool" />-->
</bean>

②:byType(按类型注入):java类中引用类型的数据类型和spring容器中bean的class值是同源关系的,这样的bean赋值给引用类型。但容器中超过一个同源对象便无法给引用赋值而报错。

同源关系:
1: 父引用指向子类对象
2: 接口指向实现类对象
3: 同类引用指向其对象
 <!-- byType 自动注入 -->
<bean id="myStudent" class="com.bjpowernode.ba05.Student" autowire="byType">
    <property name="name" value="张三"/>
    <property name="age" value="26" />
    <!--引用类型的赋值-->
    <!--<property name="school" ref="mySchool" />-->
</bean>

4) 项目中使用多个spring配置文件

分多个配置文件的方式: 1)按功能模块分,一个模块一个配置文件。 2) 按类的功能分,数据库操作相关的类在一个文件, service类在一个配置文件, 配置redis,事务等等的一个配置文件。

spring管理多个配置文件: 常用的是包含关系的配置文件。 项目中有一个总的文件, 里面是有impor标签包含其他的多个配置文件。

语法:

总的文件(applicationContext.xml)

<import resource="其他的文件的路径1"/>
<import resource="其他的文件的路径2"/>

关键字“classpath:”:表示类路径, 也就是类文件(target/class文件)所在的目录。 spring到类路径中加载文件
                   至于什么时候使用classpath: 在一个文件中要使用其他的文件, 需要使用classpath.

在这里插入图片描述
当我们有这样一个文件目录的时候,总的配置文件该怎样写:

<?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">
       
	<import resource="classpath:/ba06/spring-school.xml"/>
	<import resource="classpath:/ba06/spring-student.xml"/>
	
	<!--包含关系的配置文件亦可以用通配符 *,但总配置文件不能包含在通配符路径中 -->
    <import resource="classpath:/ba06/spring-*.xml"/>

	<!--意思是当前配置文件包含了另外两个配置文件,配置文件中的bean声明不分顺序-->
</beans>

2.4.2 基于注解的DI

使用注解进行依耐注入时要在配置文件中开启包扫描,使注解生效,该包及其子包都会生效。

<context:component-scan base-package="com.company.beandemo"/>

1.创建对象的注解

@Component      普通java对象,(默认单例模式)

@Respository    持久层对象, 表示对象能访问数据库。

@Service        业务层对象, 处理业务逻辑,具有事务能力

@Controller     控制器对象, 接收请求,显示请求的处理结果。 视图层对象

后三个都是基于 @Component的注解

@Configuration + @Bean通过配置类注入对象

//创建一个class配置文件
@Configuration
public class MyConfiguration{
	//将一个Bean交由Spring进行管理
       @Bean
       public MyBean myBean(){
           return new MyBean();
       }
}

2.简单类型属性赋值

@Value("${user.name}")美元符号在springboot中可以直接在配置文件中取值

3.引用类型赋值

@Autowired:   spring提供的注解 . 支持byName, byType,主使用 @autowired

​      @Autowired:   默认就是byType

​      @Autowired  +  @Qualifier : 使用byName


@Resource : 来自jdk中的注解,给引用类型赋值的。默认是byName

​      @Resource: 先使用byName,没有符合条件的再byType

​      @Resource(name="bean的名称") :只使用byName注入

2.5 IoC 总结

IoC:管理对象的声明周期,把对象放在容器中,创建,赋值,管理依赖关系。

IoC:通过管理对象,实现解耦合。

spring作为容器适合管理什么对象 ?

1)service对象 ,dao对象。

2)工具类对象。

不适合交给spring的对象 ?

1)实体类。

2)servlet ,filter, listener 等web中的对象。他们是tomcat创建和管理的。

第三章 AOP 面向切面编程

3.1 增加功能,导致的问题

在源代码中, 业务方法中增加常用的的复用功能,比如打印日志,事务管理, 权限检查,参数检查。

1)源代码可能改动的比较多。

2)重复代码比较多。

3)代码难于维护。

3.1 什么是AOP

Aspect : 给业务方法增加的功能,叫做切面。 切面一般都是非业务功能, 而且切面功能一般都是可以复用的。 例如 日志功能, 事务功能, 权限检查,参数检查, 统计信息等等。

我们面向切面编程时需要关注切面、切入点等信息。

3.2 AOP作用

1)让切面功能复用

2)让开发人员专注业务逻辑。 提高开发效率

3)实现业务功能和其他非业务功能解耦合。

3.3 AOP中术语

1)Aspect:切面, 给业务方法增加的复用功能。

2)JoinPoint:连接点, 连接切面的业务方法。 在这个业务方法执行时,会同时执行切面的功能。

3)Pointcut: 切入点, 是连接点集合。 表示这些方法执行时,都能增加切面的功能。切入点是一个集合,可以通配 表达式表示, 比如“execution=…”。

4)Advice:通知(增强),表示切面的执行时间。 在目标方法之前执行切面,还是目标方法之后执行切面。

AOP中重要的三个要素: Aspect, Pointcut , Advice. 这个概念的理解是: 在Advice的时间,在Pointcut的位置, 执行Aspect

AOP是一个动态的思想。 在程序运行期间,创建代理(ServcieProxy),在代理对象中增加切面的功能。这个代理对象是存在内存中的。我们使用 applicationContext.get(beanname) 获取对象发现对象类型变成了代理类。

3.4 什么时候你想用AOP

你要给某些方法 增加相同的一些功能。 源代码不能改。 给业务方法增加常用的非业务功能,也可以使用AOP

3.5 AOP技术思想的实现

使用框架实现AOP。 实现AOP的框架有很多。 有名的两个

1) Spring : Spring框架实现AOP的操作比较繁琐,笨重。只有spring自己用。

2) Aspectj : 独立的框架,专门是AOP。 属于Eclipse ,简单易用。

3.6 使用AspectJ框架实现AOP

AspectJ框架可以使用 注解(推荐)和 xml配置文件 两种方式 实现 AOP

3.6.1 通知

Aspectj中表示切面执行时间,用的通知(Advice)。 这个通知可以使用注解表示。

通知Advice包含5个注解, 表示切面的5个执行时间, 这些注解叫做通知注解。

@Before : 	前置通知

@AfterRetunring: 	后置通知

@Around: 			环绕通知

@AfterThrowing:		异常通知

@After:			最终通知
3.6.2. Pointcut 位置

Pointcut 用来表示切面执行的位置,即增加切面的业务方法。 使用Aspectj中切入点表达式。

切入点表达式语法: execution(访问权限 方法返回值 方法声明(参数) 异常类型)

几个切入点表达式的例子, *表示该项任意, ..表示通配符
execution( public * *(..))
指定切入点:任意公共方法
execution( * set*(..))
指定切入点:任意以set开始的方法
execution( * com.xyz.service.*.*(..))
指定切入点:定义在service包里的任意类的任意方法。
execution( * com.xyz.service..*.*(..))
指定切入点:定义在service包或子包里的任意类的任意方法。
3.6.3 @Before前置通知

前置通知@Before:在目标方法前执行,不影响目标方法的执行,不修改目标方法的执行结果.

/**
 * 切面类中的通知方法,可以有参数
 * JoinPoint必须是他。
 *
 * JoinPoint: (连接点)表示正在执行的业务方法。 相当于反射中 Method
 *   使用要求:必须是参数列表的第一个
 *   作用:获取业务方法执行时的信息,例如方法名称,参数集合等。
 */

@Component   //将切面类交于spring
@Aspect		//定义aop切面类
public class MyAspect{

	@Before(value = "execution(* *..SomeServiceImpl.do*(..) )")
	public void myBefore2(JoinPoint jp){
	
	    //获取方法的定义
	    System.out.println("前置通知中,获取目标方法的定义:"+jp.getSignature());
	    System.out.println("前置通知中,获取方法名称="+jp.getSignature().getName());
	    //获取方法执行时参数
	    Object args []= jp.getArgs();// 数组中存放的是 方法的所有参数
	    for(Object obj:args){
	        System.out.println("前置通知,获取方法的参数:"+obj);
	    }
		//根据methodName 决定做什么
	    String methodName = jp.getSignature().getName();
	    if("doSome".equals(methodName)){
	        //切面的代码。
	        System.out.println("doSome输出日志=====在目标方法之前先执行==:"+ new Date());
	    } else if("doOther".equals(methodName)){
	        System.out.println("doOther前置通知,作为方法名称,参数的记录。");
	    }
	}
}

使用aspectj的aop时需要在spring配置文件中开启aop自动代理

<!-- 开启注解自动扫描注入bean -- >
<context:component-scan base-package="com.company.beandemo"/>

<!-- 开启aspectj  aop自动代理生成器 -- >
<aop:aspectj-autoproxy />

<!-- 调用sapectj框架功能,寻找spring容器中所有目标对象(切入点),把每个对象加入切面类中的功能,生成代理。
这个代理是修改内存中的目标对象,通过容器获得的对象是代理对象! (com.sun.proxy.$Proxy8)-- >
3.6.4 @AfterReturning后置通知

@AfterReturning: 在目标方法之后执行的

/* 特点:
     *  1.在目标方法之后,执行的。
     *  2.能获取到目标方法的执行结果。
     *  3.不会影响目标方法的执行
     *
     * 方法的参数:
     *   JoinPoint 连接点即目标方法,必须在第一个。
     *   Object res: 表示目标方法的返回值,使用res接收doOther的调用结果。
     *
     *  后置通知的执行顺序
     *  代理对象Proxy.doOther(){
	 *  		Object res = SomeServiceImpl.doOther(..); 
     * 	 		myAfterReturning(res);	//后置通知不修改基本类型的res,引用类型的不行。
     *			return res;
     * 		}
     *  思考:
     *    1 doOther方法返回是String ,Integer ,Long等基本类型,
     *      在后置通知中,修改返回值, 是不会影响目标方法的最后调用结果的。
     *    2 doOther返回的结果是对象类型,例如Student。
     *      在后置通知方法中,修改这个Student对象的属性值,会不会影响最后调用结果?
     */
    @AfterReturning(value = "execution(* *..SomeServiceImpl.doOther(..))",
                    returning = "res")
    public void myAfterReturning(JoinPoint jp, Object res){

        //修改目标方法的返回值
        if(res != null){
            res = "HELLO Aspectj";
        }
        System.out.println("后置通知,在目标方法之后,执行的。能拿到执行结果:"+res);
        
        //Object res有什么用
        if("abcd".equals(res)){
            System.out.println("根据返回值的不同,做不同的增强功能");
        } else if("add".equals(res)){
           System.out.println("doOther做了添加数据库, 我做了备份数据");
        }
    }

public static void main(String[] args){
	ApplicationContext ioc=new ClassPathXmlApplicationContext(applicationContext.xml);
	Service service = ioc.getBean(someServiceImpl);
	String res=service.doOther();
	Sytem.out.println(res);  
 
    --结果为"abcd"而不是"HELLO Aspectj"--
	//即在代理的后置通知里修改目标方法的res,对于基本类型的res是不会影响最终获得的res的。
} 
3.6.5 @Around 环绕通知

@Around(value=“切入点表达式”)

使用环绕通知: 就是调用 切面类中的通知方法,在切面类中决定是否调用目标方法和修改返回结果。

特点:

  • 1.在目标方法的前和后都能增强功能
  • 2.控制目标方法是否执行
  • 3.修改目标方法的执行结果。
/**
 * @Around:环绕通知
 *    属性:value 切入点表达式
 *
 * 返回值:Object ,表示调用目标方法希望得到执行结果(不一定是目标方法自己的返回值)
 * 参数:  ProceedingJoinPoint, 相当于反射中 Method。
 *        作用:执行目标方法的,等于Method.invoke()
 *
 *        public interface ProceedingJoinPoint extends JoinPoint {}
 */
@Around("execution(* *..SomeServiceImpl.doFirst(..))")
public Object myAround(ProceedingJoinPoint pjp) throws Throwable {

    //获取方法执行时的参数值
    String name = "";
    Object args [] = pjp.getArgs();
    if( args != null && args.length > 0){
        Object arg = args[0];
        if(arg !=null){
            name=(String)arg;
        }
    }

    Object methodReturn = null;

    System.out.println("执行了环绕通知,在目标方法之前,输出日志时间=="+ new Date());
    //执行目标方法  ProceedingJoinPoint,表示doFirst

    if("lisi".equals(name)){
        methodReturn = pjp.proceed();//method.invoke(),表示执行doFirst()方法本身
    }
    
    if( methodReturn != null){
        methodReturn ="环绕通知中,修改目标方法原来的执行结果";
    }

    System.out.println("环绕通知,在目标方法之后,增加了事务提交功能");

    //return "HelloAround,不是目标方法的执行结果";
    //返回目标方法执行结果。没有修改的。
    return methodReturn;
}
3.6.6 @AfterThrowing 异常通知

语法@AfterThrowing(value=“切入点表达式”,throwing=“自定义变量”)

/**
 * @AfterThrowing:异常通知
 *     属性: value 切入点表达式
 *           throwing 自定义变量,表示目标方法抛出的异常。
 *                    变量名必须和通知方法的形参名一样  
 * 特点:
 *  1. 在目标方法抛出异常后执行的, 没有异常不执行
 *  2. 能获取到目标方法的异常信息。
 *  3. 不是异常处理程序。可以得到发生异常的通知, 可以发送邮件,短信通知开发人员。
 *      看做是目标方法的监控程序。
 *
 *  代理对象中异常通知的执行
 *  try{
 *      SomeServiceImpl.doSecond(..)
 *  }catch(Exceptoin e){
 *      myAfterThrowing(e);
 *  }
 */
@AfterThrowing(value = "execution(* *..SomeServiceImpl.doSecond(..))",throwing = "ex")
public void myAfterThrowing(Exception ex){
    System.out.println("异常通知,在目标方法抛出异常时执行的,异常原因是:"+ex.getMessage());
    /*
       异常发生可以做:
       1.记录异常的时间,位置,等信息。
       2.发送邮件,短信,通知开发人员
     */
}
3.6.7 @After 最终通知

语法: @After(value=“切入点表达式”),在代理对象中相当于是 try-finally 结构。

@After(value = "mypt()")
public void myAfter(){
    System.out.println("最终通知,总是会被执行的");
}
/**
 * @Pointcut: 定义和管理切入点,不是通知注解。
 *     属性: value 切入点表达式
 *     位置: 在一个自定义方法的上面, 这个方法看做是切入点表达式的别名。
 *           其他的通知注解中,可以使用方法名称,就表示使用这个切入点表达式了
 */
@Pointcut("execution(* *..SomeServiceImpl.doThird(..))")
private void mypt(){
    //无需代码
}

第四章 Spring集成MyBatis

4.1 集成思路

spring能集成很多的框架,是spring一个优势功能。 通过集成功能,让开发人员使用其他框架更方便。

集成使用的是spring ioc 核心技术。spring集合mybatis主要是将 datasource、sqlSessionFactory、dao接口代理对象这些对象注入到spring容器中,最终进行curd的便是dao接口代理对象(mapper)。由mybatis框架生成。

4.2未集成时

mybatis没有经spring集成之前,也可以生成dao代理对象进行crud,只不过我们得自己创建datasource、sqlSessionFactory、dao接口代理对象。下面是没有spring时的mybatis配置文件,datasource和每一个mapper.xml(或者mapper所在的包)都得在mybatis核心配置文件中注册。

在这里插入图片描述
通过SqlSessionFactoryBuilder(mybatis配置文件)便可创建sqlSessionFactory对象,我们可以把sqlSessionFactory看成是数据库连接池,一个项目中应该只有一个,sqlSessionFactory.openSession()便可得到sqlSession,他直接和数据库打交道,相当于连接池的连接器,每个线程都应该有自己的sqlSession实例,sqlSession不是线程安全的,因此不能被共享,因此我们每当要crud时当场打开一个sqlSession,用完后就给关闭,避免占用sqlsessionFactory的资源。
在这里插入图片描述
dao接口 proxy对象 = sqlSession.getMapper( dao接口.class )
这样便会根据 (dao接口和对应的mapper.xml) 创建dao接口代理对象,通过dao代理对象直接执行mapper.xml中对象的sql。经检测发现代理的 proxy对象.getClass() 结果是 org.apache.ibatis.binding.MapperProxy.

4.3 spring集成mybatis

使用mybatis,需要创mybatis框架中的某些对象, 使用这些对象,就能使用mybatis提供的功能了。

分析: mybatis执行sql语句,需要使用那些对象

  1. 需要有Dao接口的代理对象, 例如StudentDao接口, 需要一个它的代理对象

    使用 SqlSession.getMapper(StudentDao.class),得到dao代理对象

  2. 需要有SqlSessionFactory, 创建SqlSessionFactory对象,才能使用openSession()得到SqlSession对象。

  3. 数据源DataSource对象, 使用一个更强大,功能更多的连接池对象(druid)代替mybatis自己的
    PooledDataSource,况且 SqlSessionFactory 需要 dataSource。

集成 maybatis 时 spring 配置文件

<?xml version="1.0" encoding="UTF-8"?>
-<beans 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" 
xmlns:context="http://www.springframework.org/schema/context" 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
xmlns="http://www.springframework.org/schema/beans">

<!--声明数据源DataSource为druid替换mybatis自带的PolledDataSource-->
<!--druid不需要driver-class-name,它可以根据url自动判断驱动类名称-->
<bean destroy-method="close" init-method="init" class="com.alibaba.druid.pool.DruidDataSource" id="myDataSource">
	<property value="jdbc:mysql://localhost:3306/springdb" name="url"/>
	<property value="root" name="username"/>
	<property value="555888" name="password"/>
</bean>

<!--声明SqlSessionFactoryBean,在这个类的内部,创建SqlSessionFactory-->
-<bean id="factory" class="org.mybatis.spring.SqlSessionFactoryBean" >
	<!--指定数据源-->
	<property name="dataSource" ref="myDataSource"/>
	<!--指定mybatis核心配置文件 这里可以不要了,dataSource足够创建SqlSessionFactory -->
	<property name="configLocation" value="classpath:mybatis.xml" />
</bean>

//对应于没集成时的这句话
SqlSessionFactory factory = new SqlSessonFactoryBuider.build(classpath:mybatis.xml)

<!--mybatis的 mapperScanner,使用sqlSessionFactory为dao包下的所有dao接口生成代理并注入容器-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
	<!--指定SqlSessionFactory对象的名称-->
	<property value="factory" name="sqlSessionFactoryBeanName"/>
	<!--指定基本包,dao接口所在的包名-->
	<property value="com.bjpowernode.dao" name="basePackage"/>
</bean>

</beans>

MapperScannerConfigurer 作用是:循环basePackage所表示的包,把包中的每个接口都找到,调用SqlSession.getMapper 把每个dao接口都创建出dao代理对象 ,并把代理对象放到spring容器中。

	ApplicationContext ctx = ....					//获取容器对象
	SqlSessionFactory sqlSessionFactory = ctx.getBean("factory");		//获取sqlSessionFactory
	SqlSession session = sqlSessionFactory.openSession();				//获取SqlSession
	for(接口: com.bjpowernode.dao){ 
		dao接口 proxy对象 = session.getMapper(接口)			//执行sqlSession.getMapper便会生成代理对象
		springMap.put(dao接口名的首字母小写, 对象)			//把代理对象注入容器
	}

第五章 Spring 事务

5.1 事务的概念

什么事务? 事务是一些sql序列的集合, 是多条sql, 作为一个整体执行。

5.2 在程序中事务在哪说明

事务:加在业务类的 public方法上面,表示业务方法执行时,需要事务的支持。

public class AccountService{
    
   private AccountDao dao; 
   private MoneyDao dao2;
   // 在service(业务类)的public方法上面,需要说明事务。该方法需要事务支持。
   public void trans(String a, String b, Integer money){
       dao.updateA();
       dao.updateB();
       dao2.insertA();
   }
}
public class AccountDao{ 
    public void updateA(){}
    public void updateB(){} 
}

public class MoneyDao{   
    public void insertA(){}
    public void deleteB(){}
}

5.3 事务管理器

5.3.1 不同的数据库访问技术 jdbc、mybatis、hibrnate等,事务处理方案 是不同的。

1)使用jdbc访问数据库, 事务处理。

public void updateAccount(){
    Connection connection = ...
    connection .setAutoCommit(false);
    statement.insert()
    statement.update();
    connection .commit();
    connection .setAutoCommit(true)
}
  1. mybatis访问数据库,事务处理。
public void updateAccount(){
    SqlSession session = SqlSession.openSession(false);		//关闭自动提交
    try{
        session.insert("insert into student...");
    	session.update("update school ...");
    	session.commit(); 
    }catch(Exception e){
        session.rollback();
    } 
}

5.3. 2 spring统一管理事务, 把不同数据库访问技术的事务处理统一起来。

​ 开发人员只需要掌握spring的事务处理一个方案, 就可以实现使用不同数据库访问技术的事务管理。

​ 开发人员管理事务面向的是spring, 由spring管理事务,统一事务管理器接口。

5.3.3 Spring事务管理器

Spring框架使用事务管理器对象管理所有的事务。

事务管理器接口: PlatformTransactionManager 定义了事务的操作, 主要是commit(), rollback()

package org.springframework.transaction;
import org.springframework.lang.Nullable;

public interface PlatformTransactionManager extends TransactionManager {
    TransactionStatus getTransaction(@Nullable TransactionDefinition var1) throws TransactionException;
    void commit(TransactionStatus var1) throws TransactionException;
    void rollback(TransactionStatus var1) throws TransactionException;
}

spring事务管理器接口有很多实现类:各数据库的访问技术有一个实现类。 由各实现类完成具体的事务提交,回滚。

 jdbc或者 mybatis(算是jdbc的高级封装) 持久层技术的事务管理器实现类 : DataSourceTranactionManager

 hibernate框架的事务管理器实现类: HibernateTransactionManager.

spring统一事务管理器接口,各持久层框架实现类完成提交、回滚。

在这里插入图片描述

5.3.4 事务的提交和回顾的时机

什么时候提交事务,回滚事务 ?

当你的业务方法正常执行时,没有异常,事务是提交的。 如果你的业务方法抛出了运行时异常, 事务是回滚的。

异常分类:

Error: 严重错误。 回滚事务。

Exception : 异常类,可以出来的异常情况

运行时异常: RuntimeException和他的子类都是运行时异常, 在程序执行过程中抛出的异常。 常见的运行时异常:
			 NullPoinerException ,  NumberFormatException , ArithmeticException, IndexOutOfBoundsException.

受查异常 : 编写java代码的时候,必须出来的异常。 例如IOException ,  SQLException , FileNotFoundException ,须在代码里处理

怎么记忆:

方法中抛出了运行时异常, 事务回滚,其他情况(正常执行方法,受查异常)就是提交事务。

5.3.5 spring事务管理底层使用的AOP的环绕通知

环绕通知:可以在目标方法的前和后都能增强功能,不需要修改代码代码

spring在执行业务方法时,增加上事务切面功能,或者说spring给有 @Transaction注解 的类生成代理,在环绕通知中实现事务功能。
@Around("execution(*  所有的业务类中的方法)")
public Object myAround(ProceedingJoinPoint pjp) {
    try{
       PlatformTransactionManager.beginTransaction();  //使用spring的事务管理器,开启事务
       pjp.proceed();     //执行目标方法 //doSome()
       PlatformTransactionManager.commit();   //业务方法正常执行,提交事务
    }catch(Exception e){
        PlatformTransactionManager.rollback();//业务方法正常执行,回滚事务
    } 
}

5.4 事务定义接口TransactionDefinition

TransactionDefinition接口。定义了三类静态常量, 定义了有关事务控制的属性。(TransactionDefinition只是定义 一些常量)

事务的属性:1) 隔离级别 2)传播行为 3)事务的超时

给业务方法说明事务属性。和ACID不一样。

5.4.1 隔离级别

隔离级别:控制事务之间影响的程度。

5个值,只有四个隔离级别

1)DEFAULT : 采 用 DB 默 认 的 事 务 隔 离 级 别 。 MySql 的 默 认 为 REPEATABLE_READ;

​ Oracle 默认为 READ_COMMITTED。

2)READ_UNCOMMITTED:读未提交。未解决任何并发问题。
3)READ_COMMITTED:读已提交。解决脏读,存在不可重复读与幻读。
4)REPEATABLE_READ:可重复读。解决脏读、不可重复读,存在幻读
5)SERIALIZABLE:串行化。不存在并发问题。

5.4.2 超时时间

超时时间,以秒为单位 。 整数值。 默认是 -1

超时时间:表示一个业务方法最长的执行时间,没有到达时间没有执行完毕,spring回滚事务。

5.4.3 传播行为

传播行为有7个值。

传播行为:业务方法在调用时,事务在方法之间的,传递和使用。

使用传播行为,标识方法有无事务。

PROPAGATION_REQUIRED
PROPAGATION_REQUIRES_NEW
PROPAGATION_SUPPORTS

以上三个需要掌握的。

PROPAGATION_MANDATORY
PROPAGATION_NESTED
PROPAGATION_NEVER
PROPAGATION_NOT_SUPPORTED

1) REQUIRED: spring默认传播行为, 方法在调用的时候,如果存在事务就是使用当前的事务,如果没有事务,则新建事务, 方法在新事务中执行。

2) SUPPORTS:支持, 方法有事务可以正常执行,没有事务也可以正常执行。

3) REQUIRES_NEW:方法需要一个新事务。 如果调用方法时,存在一个事务,则原来的事务暂停。 直到新事务执行完毕。 如果方法调用时,没有事务,则新建一个事务,在新事务执行代码。

5.5 Spring框架使用自己的注解@Transactional控制事务

@Transactional注解, 使用注解的属性控制事务(隔离级别,传播行为,超时)

​ 属性:

​     1.propagation : 	事务的传播行为, 他使用的 Propagation类的枚举值。例如 Propagation.REQUIRED

​     2.isolation : 	表示隔离级别, 使用Isolation类的枚举值,表示隔离级别。 默认 Isolation.DEFAULT

​     3.readOnly: 	boolean类型的值,表示数据库操作是不是只读的。默认是false

​     4.timeout: 	事务超时,默认是-1, 整数值,单位是秒。 例如 timeout=20

​     5.rollbackFor:	表示回滚的异常类列表, 他的值是一个数组,每个值是异常类型的class。 

​     6.rollbackForClassName:	表示回滚的异常类列表,他的值是异常类名称,是String类型的值

​     7.noRollbackFor: 	不需要回滚的异常类列表。 是class类型的。

​     8.noRollbackForClassName: 	不需要回滚的异常类列表,是String类型的值

注解的使用步骤:

1)在spring的配置文件,声明事务的内容(声明式事务)

​  声明事务管理器,说明使用哪个事务管理器对象,mybatis 使用 DataSourceTransactionManager

​  声明使用注解管理事务, 开启是事务注解驱动,即使 @Transactional 生效
<!--声明事务管理器-->
<bean id="transactionManager"  class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <!--指定数据源DataSource-->
    <property name="dataSource" ref="myDataSource" />
</bean>

<!--开启事务注解驱动: 告诉框架使用注解管理事务-->
<tx:annotation-driven transaction-manager="transactionManager" />

2)在类的源代码中,在public的业务方法上加入@Transactional。由于开启了事务注解驱动,事务便会生效,而且容器中有事务管理器,这样spring会给目标对象生成动态代理,增加事务切面功能,并且代理中一定有事务管理器引用,来进行提交、回滚。

//@Transactional 放在public方法的上面。表示方法有事务功能
    /*
    第一种设置方式
    @Transactional(
            propagation = Propagation.REQUIRED,
            isolation = Isolation.DEFAULT,
            readOnly = false, timeout = 20,
            rollbackFor = {NullPointerException.class,NotEnougthException.class})
    
    第二种设置方式
     @Transactional(
            propagation = Propagation.REQUIRED,
            isolation = Isolation.DEFAULT,
            readOnly = false, timeout = 20
     )

     解释 rollbackFor 的使用;
     1)框架首先检查方法抛出的异常是不是在 rollbackFor 的数组中, 如果在一定回滚。
     2)如果方法抛出的异常不在 rollbackFor 数组, 框架会继续检查 抛出的异常 是不是 RuntimeException.
        如果是RuntimeException, 一定回滚。

     例如 抛出 SQLException , IOException
     rollbackFor={SQLException.class, IOException.class}
     */

    //第三种方式: 使用默认值 REQUIRED , 发生运行时异常回滚。
    @Transactional
    @Override
    public void buy(Integer goodsId, Integer num) { }
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值