美文网首页
Spring4.0笔记

Spring4.0笔记

作者: PC_Repair | 来源:发表于2019-01-27 23:04 被阅读41次

    Spring4.0

    Spring是一个开源框架

    Spring为简化企业级英语开发而生。使用Spring可以使简单的JavaBean实现以前只有EJB才能实现的功能

    Spring是一个IOC(DI)和AOP容器框架

    具体描述Spring:

    • 轻量级:Spring是非入侵的-基于Spring开发的应用中的对象可以不依赖Spring的API
    • 依赖注入(DI -- dependency injection、IOC)
    • 面向切面编程(AOP)
    • 容器:Spring是一个容器,因为它包含并且管理应用对象的生命周期
    • 框架:Spring实现了使用简单的组件配置组合成一个复杂的应用。在Spring中可以实现XML和Java注解组合这些对象
    • 一站式:在IOC和AOP的基础上可以整合各种企业应用的开源框架和优秀的第三方类库(实际上Spring自身也提供了展现层的SpringMVC和持久层的Spring JDBC)
    image.png

    示例:

    <!--
        配置bean
        class: bean 的全类名,通过反射的方式在IOC容器中创建Bean,所以要求Bean中必须有无参数的构造器
        id: 标识容器中的bean,id 唯一
    -->
    
    <!-- 通过全类名的方式来配置bean -->
    <bean id="helloWorld" class="com.ljf.spring.beans.HelloWorld">
        <property name="name2" value="Spring"> </property>
    </bean>
    
    • 创建Spring的IOC容器对象,在创建这个容器的时候会调用类的构造器对配置文件(xml)中的Bean初始化,同时调用set方法对属性就行赋值
    // 1.创建Spring的IOC容器对象,在创建这个容器的时候会调用类的构造器对配置文件(xml)中的Bean初始化,同时调用set方法对属性就行赋值
    // ApplicationContext 代表IOC容器
    // ClassPathXmlApplicationContext:是ApplicationContext接口的实现类,该实现类从类路径下加载配置文件
    ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
    // 2.从IOC容器中获取Bean实例
    // 利用id定位到IOC容器中的bean
    HelloWorld helloworld = (HelloWorld) ctx.getBean("helloWorld");
    // 利用类型返回IOC容器中的Bean,但要求IOC容器中必须只能有一个该类型的Bean
    //HelloWorld helloWorld = ctx.getBean(HelloWorld.class);
    // 3.调用hello方法
    helloWorld.hello();
    

    IOC & DI概述

    配置Bean

    • 配置形式:基于XML文件的方式;基于注解的方式
    • Bean的配置方式:通过全类名(反射)、通过工厂方法(静态工厂方法 & 实例工厂方法)、FactoryBean
    • IOC容器 BeanFactory & ApplicationContext概述
    • 依赖注入的方式:属性注入;构造器注入
    • 注入属性值细节
    • 自动转配
    • bean之间的关系:继承;依赖
    • bean的作用域:singleton;prototype;WEB环境作用域
    • 使用外部属性文件
    • spEL
    • IOC容器中的Bean生命周期
    • Spring 4.x新特性:泛型依赖注入

    IOC和DI

    IOC(Inversion of Control):其思想是反转资源获取的方向。传统的资源查找方式要求组件向容器发起请求查找资源。作为回应,容器适时的返回资源。而应用了IOC之后,则是容器主动地将资源推送给它所管理的组件,组件所要做的仅是选择一种合适的方式来接受资源。这种行为也被称为查找的被动形式。

    DI(Dependency Injection)--IOC的另一种表述方式:即组件以一些预先定义好的方式(例如:setter方法)接受来自如容器的资源注入。相对于IOC而言,这种表述更直接。

    <!--
        配置bean
        class: bean 的全类名,通过反射的方式在IOC容器中创建Bean,所以要求Bean中必须有无参数的构造器
        id: 标识容器中的bean,id 唯一
    -->
    

    Spring容器

    • 在Spring IOC容器读取Bean配置创建Bean实例之前,必须对它进行实例化。只有在容器实例化后,才可以从IOC容器里获取Bean实例并使用。
    • Spring提供了两种类型的IOC容器实现
      • BeanFactory:IOC容器的基本实现
      • ApplicationContext:提供了更多的高级特性,是BeanFactory的子接口
      • BeanFactory是Spring框架的基础设施,面向Spring本身;ApplicationContext面向使用Spring框架的开发者,几乎所有的应用场合都直接使用ApplicationContext而非底层的BeanFactory
      • 无论使用何种方式,配置文件是相同的

    ApplicationContext:

    • ApplicationContext的主要实现类:

      • ClassPathXmlApplicationContext:从类路径下加载配置文件
      • FileSystemXmlApplicationContext:从文件系统中加载配置文件
    • ConfigurableApplicationContext扩展于ApplicationContext,新增加两个主要方法:refresh()和close(),让ApplicationContext具有启动、刷新和关闭上下文的能力

    • ApplicationContext在初始化上下文时就实例化所有单例的Bean

    • WebApplicationContext是专门为WEB应用而准备的,它允许从相对于WEB根目录的路径中完成初始化工作。

    依赖注入的方式:Spring支持三种依赖注入的方式

    • 属性注入:

      • 属性注入即通过setter方法注入Bean的属性值或依赖的对象
      • 属性注入使用<property>元素,使用name属性指定Bean的属性名称,value属性或<value>子节点指定属性
      • 属性注入是实际应用中最常见的注入方式
      <!-- 通过全类名的方式来配置bean -->
      <bean id="helloWorld" class="com.ljf.spring.beans.HelloWorld">
        <property name="name2" value="Spring"> </property>
      </bean>
      
    • 构造方法注入:

      • 通过构造方法注入Bean的属性值或依赖的对象,它保证了Bean实例在实例化后就可以使用
      • 构造器注入在<constructor-arg>元素里声明属性,<constructor-arg>中没有name属性
    <!-- 
        通过构造方法来配置bean的属性 
        使用构造器注入属性值可以指定参数的位置和参数的类型,以区分重载的构造器
    -->
    
    <bean id="car" class="com.ljf.spring.beans.Car">
        <constructor-arg value="Audi" index="0"></constructor-arg>
        <constructor-arg value="ShangHai" index="1"></constructor-arg>
        <constructor-arg value="300000" type="double"></constructor-arg>
    </bean>
    

    字面值:

    • 字面值:可用字符串表示的值,可以通过<value>元素标签或value属性进行注入
    • 基本数据类型及其封装类、String等类型都可以采取字面值注入的方式
    • 若字面值中包含特殊字符,可以使用<![CDATA[]]>把字面值包裹起来

    引用其他Bean

    • 组成应用程序的Bean经常需要相互协作以完成应用程序的功能,要使Bean能够相互访问,就必须在Bean配置文件中指定对Bean的引用
    • 在Bean的配置文件中,可以通过<ref>元素或ref属性为Bean的属性或构造器参数指定对Bean的引用
    • 也可以在属性或构造器里包含Bean的声明,这样的Bean称为内部Bean
    <bean id="person" class="com.ljf.spring.beans.Person">
        <property name="name" value="Jack"> </property>
        <property age="age" value="24"> </property>
        <!-- 可以使用property的ref属性建立bean之间的引用联系  -->
        <property name="car" ref="car2"> </property>
        <!-- 内部bean -->
    </bean>
    

    XML配置里的Bean自动装配

    • Spring IOC容器可以自动装配Bean,需要做的仅仅是在<bean>的autowire属性里指定自动装配的模式
    • byType(根据类型自动装配):若IOC容器中有多个与目标Bean类型一致的Bean。在这种情况下,Spring将无法判断哪个Bean最合适该属性,所以不能执行自动装配。
    • byName(根据名称自动装配):必须将目标Bean的名称和属性名设置的完全相同。
    • constructor(通过构造器自动装配):当Bean中存在多个构造器时,此种自动装配方式将会很复杂,不推介使用。

    继承Bean配置

    • SPring允许继承bean的配置,被继承的bean称为父bean,继承这个父bean的bean称为子bean
    • 子bean从父bean中继承配置,包括bean的属性配置
    • 子bean也可以覆盖父bean继承过来的配置
    • 父bean可以作为配置模板,也可以作为bean实例。若只想把父bean作为模板,可以设置<bean>的abstract属性为true,这样Spring将不会实例化这个bean
    • 也可以忽略父bean的class属性,让子bean指定自己的类,而贡献相同的属性配置。但此时abstract必须设为true

    依赖Bean配置

    • SPring允许用户通过depends-on属性设定Bean前置依赖的Bean,前置依赖的Bean会在Bean实例化之前创建好
    • 如果前置依赖于多个Bean,则可以通过逗号、空号的方式配置Bean的名称

    bean的作用域:singleton;prototype;WEB环境作用域

    使用bean的 scope 属性来配置bean的作用域

    • singleton:默认值,容器初始时创建bean实例,在整个容器的生命周期内只创建这一个bean,单例的
    • prototype:原型的,容器初始化时不创建bean实例,而在每次请求时都创建一个新的Bean实例,并返回。
    • request
    • session

    使用外部属性文件

    • 在配置文件里配置Bean时,有时需要在Bean的配置里混入系统部署的细节信息(例如:文件路径,数据源配置信息等)。而这些部署细节实际上需要和Bean配置相分离
    • Spring提供了一个PropertyPlaceholderConfigurer的BeanFactory后置处理器,这个处理器允许用户将Bean配置的部分内容外移到属性文件中。可以在Bean配置文件里使用形式为${var}的变量,PropertyPlaceholderConfigurer从属性文件里加载属性,并使用这些属性来替换变量
    • Spring还允许在属性文件中使用${propName},以实现属性之间的相互引用。
        <!-- 导入属性文件,配置数据库相关参数properties的属性:${url} -->
        <context:property-placeholder location="classpath:jdbc.properties"/>
    
        <!-- 数据库连接池 -->
        <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
            <!-- 使用外部化属性文件的属性 -->
            <property name="driverClass" value="${jdbc.driver}"/>
            <property name="jdbcUrl" value="${jdbc.url}"/>
            <property name="user" value="${jdbc.username}"/>
            <property name="password" value="${jdbc.password}"/>
        </bean>
    

    Spring表达式语言:SpEL

    • SpEL是一个支持运行时查询和操作对象图的强大的表达式语言
    • 语法类似于EL:SpEL使用#{...}作为定界符,所有在大大括号中的字符都将被认为是SpEL
    • SpEL为bean的属性进行动态赋值提供了便利
    • 通过SpEL可以实现:
      • 通过bean的id对bean进行引用
      • 调用方法以及引用对象中的属性
      • 计算表达式的值
      • 正则表达式的匹配

    IOC容器中的Bean的生命周期

    • Spring IOC 容器可以管理Bean的生命周期,Spring允许在Bean生命周期的特定点执行定制的任务。
    • Spring IOC容器对Bean的生命周期管理的过程:
      • 通过构造器或工厂方法创建Bean实例
      • 为Bean的属性设置值和对其他Bean的引用
      • 调用Bean的初始化方法
      • Bean可以使用了
      • 当容器关闭时,调用Bean的销毁方法
    • 在Bean的声明里设置init-method 和 destroy-method属性,为Bean指定初始化和销毁方法

    配置Bean的后置处理器

    <!--
        实现BeanPostProcessor接口,并具体提供
        Object postProcessBeforeInitialization(Object bean, String beanName): init-method 之前被调用
        Object postProcessAfterInitialization(Object bean, String beanName): init-method 之后被调用
        
        bean: bean 实例本身
        beanName: IOC 容器配置的bean的名字
        返回值:是实际上返回给用户的那个Bean,注意:可以在以上两个方法中修改返回的bean,甚至返回一个新的bean
    -->
    
    <!-- 配置bean的后置处理器:不需要配置id,IOC容器自动识别是一个BeanPostProcessor -->
    <bean class="com.ljf.MyBeanPostProcessor"></bean>
    

    通过静态工厂方法来配置 bean

    public class StaticCarFactory {
        private static Map<String, Car> cars = new HashMap<String, Car>();
        static {
            cars.put("audi", new Car("audi", 30000));
            cars.put("ford", new Car("ford"m 40000));
        }
        // 静态工厂方法
        public static Car getCar(String name) {
            return cars.get(name);
        }
    }
    
    <!-- 通过静态工厂方法来配置 bean。注意补水配置静态工厂方法实例,而是配置bean实例 -->
    <!-- 
        class 属性:指向静态工厂方法的全类名
        factory-method: 指向静态工厂方法的名字
        constructor-arg: 如果工厂方法需要传入参数,则使用constructor-arg 来配置参数
    -->
    <bean id="car1"
        class="com.ljf.beans.StaticCarFactory"
        factory-method="getCar">
        <constructor-arg value="audi"></constructor-arg>
    </bean>
    

    通过实例工厂方法配置bean

    FactoryBean

    <!--
        通过FactoryBean 来配置Bean的实例
        class: 指向FactoryBean的全类名
        property: 配置FactoryBean的getObject() 方法返回的实例
    
        但实际返回的实例是 FactoryBean的getObject() 方法返回的实例
    -->
    <bean id="car" class="com.ljf.bean.factorybean.CarFactoryBean">
        <property name="brand" value="BMW"></property>
    </bean>
    
    public class CarfactoyBean implements FactoryBean<Car> {
        ...
        Override 3 个方法
    }
    

    基于注解的方式

    在classpath中扫描组件

    • 组件扫描(component scanning):Spring能够从classpath下自动扫描,侦测和实例化具有特定注解的组件。

    • 特定组件包括:

      • @Component:基本注解,标识了一个受Spring管理的组件
      • @Respository:标识持久层组件
      • @Service:标识服务层(业务层)组件
      • @Controller:标识表现层组件
    • 对于扫描到的组件,Spring有默认的命名策略:使用非限定类名,第一个字母小写;也可以在注解中通过value属性值标识组件的名称

    • 当在组件类上使用了特定的注解之后,还需要在Spring的配置文件中声明<context:component-scan>

      • base-package 属性指定一个需要扫描的基类包,Spring容器将会扫描这个基类包里及其子包中的所有类
      • 当需要扫描多个包时,可以使用逗号分隔
      • 如果仅希望扫描特定的类而非基包下的所有类,可使用resource-pattern属性过滤特定的类,示例:
      <context:component-scan 
        base-package="com.ljf.spring.beans"
          resource-pattern="autowire/*.class"/>
      
      • <context:include-filter> 子节点表示要包含的目标类
      • <context:exclude-filter> 子节点表示要排除在外的目标类
      • <context:component-scan> 下可以拥有若干个<context:include-filter> 和<context:exclude-filter>的子节点

    组件装配

    • <context:component-scan>元素还会自动注册AutowiredAnnotationBeanPostProcessor实例,该实例可以自动装配具有@autowired、@Resource 和 @Inject 注解的属性

    使用@Autowired自动装配Bean

    • @Autowired注解自动装配具有兼容类型的单个Bean属性
      • 构造器,普通字段(即使是非public),一切具有参数的方法都可以应用@Autowired注解
      • 默认情况下,所有使用@Autowired注解的属性都需要被配置。当Spring找不到匹配的Bean装配属性时,会抛出异常,若某一属性允许不被设置,可以设置@Autowired注解的required属性为false
      • 默认情况下,当IOC容器里存在多个类型兼容的Bean时,通过类型的自动装配将无法工作。此时可以在@Qualifier注解里提供Bean的名称。Spring允许对方法的入参标注@Qualifier已指定注入Bean的名称
      • @Autowired 注解有可以应用在数组类型的属性上,此时Spring将会把所有匹配的Bean进行自动装配
      • @Autowired 注解有可以应用在集合属性上,此时Spring读取该集合的类型信息,然后自动装配所有与之兼容的Bean
      • @Autowired 注解应用在 java.util.Map上时,若该Map的键值为String,那么Spring将自动装配与之Map值类型兼容的Bean,此时Bean的名称作为键值

    @Resource 注解要求提供一个Bean名称的属性,若该属性为空,则自动采用标注处的变量或方法名作为Bean的名称

    @Inject 和 @Autowired 注解一样也是按类型匹配注入的Bean,但没有required属性

    Spring 4.x新特性:泛型依赖注入

    AOP

    问题:

    • 代码混乱:越来越多的非业务需求(日志和验证等)加入后,原有的业务方法急剧膨胀。每个方法在处理核心逻辑的同时还必须兼顾其他多个关注点。
    • 代码分散:以日志需求为例,只是为了满足这个单一需求,就不得不在多个模块(方法)里多次重复相同的日志代码。如果日志需求发生变化,必须修改所有模块。
    public class ArithmeticCalculatorLoggingProxy {
        // 要代理的对象
        private ArithmeticCalculator target;
        private ArithmeticCalculator getLoggingProxy() {
            ArithmeticCalculator proxy = null;
            
            // 代理对象由哪一个类加载器负责加载
            ClassLoader loader = target.getClass().getClassLoader();
            // 代理对象的类型,即其中有哪些方法
            Class[] interfaces = new Class[]{ArithmeticCalculator.class};
            // 当调用代理对象其中的方法时,该执行的代码
            InvocationHandler h = new InvocationHandler() {
                /**
                 * proxy: 正在返回的那个代理对象,一般情况下,在invoke方法中都不使用该对象
                 * method: 正在被调用的方法
                 * args: 调用方法时,传入的参数
                 */
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    String methodName = method.getName();
                    // 日志
                    System.out.println("ATGUIGU-> The method " + methodName + " bengins with " + Arrays.asList(args));
                    // 执行方法
                    Object result = method.invoke(target, args);
                    // 日志
                    return result;
                }
            };
            proxy = (ArithmeticCalculator) Proxy.newProxyInstance(loader, interfaces, h);
            return proxy;
        }
    }
    

    AOP术语:

    • 切面(Aspect):横切关注点(跨越应用程序多个模块的功能)被模块化的特殊对象
    • 通知(Advice):切面必须要完成的工作
    • 目标(Target):被通知的对象
    • 代理(Proxy):向目标对象引用通知之后创建的对象
    • 连接点(Joinpoint):程序执行的某个特定位置:如类某个方法调用前、调用后、方法抛出异常后等
    • 切点(PointCut):每个类都拥有多个连接点:例如ArithmethicCalculator的所有方法实际上都是连接点,即连接点是程序类中客观存在的事务。AOP通过切点定位到特定的连接点。切点和连接点不是一对一的关系,一个切点匹配多个连接点,切点通过org.springframework.aop.Pointcut接口进行描述,它使用类和方法作为连接点的查询条件。

    在Spring中启用AspectJ注解支持

    • 要在Spring应用中使用AspectJ注解,必须在classpath下包含AspectJ类库:aopalliance.jar、aspectj.weaver.jar 和 spring-aspects.jar
    • 将aop Schema添加到<beans>根元素中
    • 要在Spring IOC 容器中启用AspectJ 注解支持,只要在Bean配置文件中定义一个空的XML元素<aop:aspectj-autoproxy>
    • 当Spring IOC容器侦测到Bean配置文件中的<aop:aspectj-autoproxy>元素时,会自动为与AspectJ切面匹配的Bean创建代理

    步骤:

    1)加入jar包

    2)在配置文件中加入aop命名空间,配置自动扫描包

    3)基于注解的方式

    • 在配置文件中加入如下配置:
    <context:component-scan base-package="com.ljf.spring.aop.impl"> </context:component-scan>
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
    
    • 把横切关注点的代码抽象到切面的类中
      • 切面首先是一个IOC中的bean,即加入@Component注解
      • 切面还需要加入@Aspect注解
    • 在类中声明通知
      • 声明一个方法
      • 在方法前加入@Before注解
    • 可以在通知方法中声明一个类型为JoinPoint的参数,然后就能访问链接细节,如方法名称和参数值
    @Aspect
    @Component
    public class LoggingAspect {
        // 声明该方法是一个前置通知:在目标方法开始之前执行
        @Before("execution(* com.ljf.spring.aop.impl.*.*(int, int))")
        public void beforeMethod(JoinPoint joinPoint) {
            String methodName = JoinPoint.getSignature().getName();
            List<Object> args = Arrays.asList(JoinPoint.getArgs());
            System.out.println("The method " + methodName + " begins with " + args);
        }
        
        // 后置通知:在目标方法执行后(无论是否发生异常),执行通知
        // 在后置通知中还不能访问目标方法执行的结果
        @After("execution(* com.ljf.spring.aop.impl.*.*(int, int))")
        public void beforeMethod(JoinPoint joinPoint) {
            String methodName = JoinPoint.getSignature().getName();
            System.out.println("The method " + methodName + " ends.");
        }
        
        // 在方法正常结束后执行的代码
        // 返回通知是可以访问到方法的返回值
        @AfterReturning(value="execution(* com.ljf.spring.aop.impl.*.*(..))", returning="result")
        public void AfterReturning(JoinPoint joinPoint, Object result) {
            String methodName = JoinPoint.getSignature().getName();
            System.out.println("The method " + methodName + " ends with." + result);
        }
        
        // 在目标方法出现异常时执行的代码
        // 可以访问到异常对象;且可以指定在出现特定异常时再执行通知代码
        @AfterThrowing(value="execution(* com.ljf.spring.aop.impl.*.*(..))", throwing="ex")
        public void AfterThrowing(JoinPoint joinPoint, Exception ex) {
            String methodName = JoinPoint.getSignature().getName();
            System.out.println("The method " + methodName + " occurs excetion: " + ex);
        }
        
        // 环绕通知需要携带 ProceedingJoinPoint 类型的参数
        // 环绕通知类似于动态代理的全过程:ProceedingJoinPoint 类型的参数可以决定是否执行目标方法
        // 且环绕通知必须由返回值,返回值即为目标方法的返回值
        @Around(value="execution(* com.ljf.spring.aop.impl.*.*(..))")
        public void AroundMethod(ProceedingJoinPoint pjd, Exception ex) {
            System.out.println("aroundMethod");
            return 100;
        }
    }
    

    @Order(x)指定切面的优先级,x为值,值越小,优先级越高

    重用切点表达式

    • 定义一个方法,用于声明切入点表达式
    @Aspect
    @Component
    public class LoggingAspect {
        @Pointcut("execution(* com.ljf.spring.aop.impl.*.*(..))"")
        public void declareJointPointExpression(){}
        
        // 声明该方法是一个前置通知:在目标方法开始之前执行
        @Before("declareJointPointExpression()")
        public void beforeMethod(JoinPoint joinPoint) {
            String methodName = JoinPoint.getSignature().getName();
            List<Object> args = Arrays.asList(JoinPoint.getArgs());
            System.out.println("The method " + methodName + " begins with " + args);
        }
        
        // 后置通知:在目标方法执行后(无论是否发生异常),执行通知
        // 在后置通知中还不能访问目标方法执行的结果
        @After("declareJointPointExpression()")
        public void beforeMethod(JoinPoint joinPoint) {
            String methodName = JoinPoint.getSignature().getName();
            System.out.println("The method " + methodName + " ends.");
        }
        ...
    }
    

    基于配置文件的AOP配置

    <!-- 配置切面的bean -->
    <bean id="LoggingAspect"
          class="com.ljf.spring.aop.xml.LoggingAspect"></bean>
    <!-- 配置AOP -->
    <aop:config>
        <!-- 配置切点表达式 -->
        <aop:pointcut expression="execution(* com.ljf.spring.aop.impl.*.*(..))" id="pointcut"/>
        <!-- 配置切面及通知 -->
        <aop:aspect ref="LoggingAspect" order="2">
            <aop:before method="beforeMethod" pointcut-ref="pointcut"/>
            <aop:after method="afterMethod" pointcut-ref="pointcut"/>
            <aop:after-throwing method="afterThrowing" pointcut-ref="pointcut" throwing="e"/>
            <aop:after-returning method="afterReturning" pointcut-ref="pointcut" returning="result"/>
        </aop:aspect>
        <aop:aspect ref="vlidationAspect" order="1">
            <aop:around method="aroundMethod" pointcut-ref="pointcut"/>
        </aop:aspect>
    </aop:config>
    

    JDBCTemplate

    JDBCTemplate 简介:

    • 为了使 JDBC 更加易于使用,Spring 在 JDBC API 上定义了一个抽象层,以此建立一个 JDBC 存取框架。
    • 作为 Spring JDBC 框架的核心,JDBC 模板的设计目的是为不同类型的 JDBC 操作提供模板方法。每个模板方法都能控制整个过程,并允许覆盖过程中的特定任务。通过这种方式,可以在尽可能保留灵活性的情况下,将数据库存取的工作量降到最低。
        <!-- 配置数据库相关参数properties的属性:${url} -->
        <context:property-placeholder location="classpath:jdbc.properties"/>
    
        <!-- 数据库连接池 -->
        <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
            <property name="driverClass" value="${jdbc.driver}"/>
            <property name="jdbcUrl" value="${jdbc.url}"/>
            <property name="user" value="${jdbc.username}"/>
            <property name="password" value="${jdbc.password}"/>
            <property name="maxPoolSize" value="${c3p0.maxPoolSize}"/>
            <property name="minPoolSize" value="${c3p0.minPoolSize}"/>
            <property name="autoCommitOnClose" value="${c3p0.autoCommitOnClose}"/>
            <property name="checkoutTimeout" value="${c3p0.checkoutTimeout}"/>
            <property name="acquireRetryAttempts" value="${c3p0.acquireRetryAttempts}"/>
        </bean>
    
        <!-- 配置 Spring 的 JdbcTemplate -->
        <bean id="jdbcTemplate"
              class="org.springframework.jdbc.core.JdbcTemplate">
            <property name="dataSource" ref="dataSource"></property>
        </bean>
    
    public class JDBCTest {
        private ApplicationContext ctx = null;
        private JdbcTemplate jdbcTemplate;
        
        {
            ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
            jdbcTemplate = (JdbcTemplate) ctx.getBean("jdbcTemplate");
        }
        
        /**
         * 调用queryForObject(String sql, RowMapper<Employee> rowMapper, Object... args)
         * 1.其中的RowMapper指定如何去映射结果集的行,常用的实例类为BeanPropertyRowMapper
         * 2.使用SQL中列的别名完成列名和类的属性名的映射,例如 last_name  lastName
         * 3.不支持级联属性(如department.id),JdbcTemplate 到底是一个JDBC的小工具,而不是ORM框架
         */
        @Test  // 查找一个对象
        public void testQueryForObject() {
            String sql = "SELECT id, last_name lastName, email, dept_id as \"department.id\" FROM employees WHERE id = ?";
            RowMapper<Employee> rowMapper = new BeanPropertyRowMapper<>(Employee.class);
            Employee employee = jdbcTemplate.queryForObject(sql, rowMapper, 1);
            System.out.println(employee);
        }
        
        /**
         * 查找实体类的集合
         */
        @Test  // 查找一组对象
        public void testQueryForList() {
            String sql = "SELECT id, last_name lastName, email FROM employees WHERE id > ?";
            RowMapper<Employee> rowMapper = new BeanPropertyRowMapper<>(Employee.class);
            List<Employee> employees = jdbcTemplate.query(sql, rowMapper, 5);
            System.out.println(employees);
        }
        
        /**
         * 获取单个列的值,或做统计查询
         */
        @Test
        public void testQueryForObject2() {
            String sql = "SELECT count(id) FROM employees";
            long count = jdbcTemplate.queryForObject(sql, Long.class);
            System.out.println(count);
        }
        
        
        /**
         * 批量执行 INSERT, UPDATE, DELETE
         */
        @Test
        public void testBatchUpdate() {
            String sql = "INSERT INTO employees(last_name, email, dept_id) VALUES(?,?,?)";
            List<Object[]> batchArgs = new ArrayList<>();
            batchArgs.add(new Object[]{"AA", "aa@163.com", 1});
            batchArgs.add(new Object[]{"BB", "bb@163.com", 2});
            batchArgs.add(new Object[]{"CC", "cc@163.com", 3});
            batchArgs.add(new Object[]{"DD", "dd@163.com", 3});
            batchArgs.add(new Object[]{"EE", "ee@163.com", 2});
            jdbcTemplate.batchUpdate(sql, batchArgs);
        }
    }
    

    Spring中的事务管理

    事务管理是企业级应用程序开发中必不可少的技术,用来确保数据的完整性和一致性。

    • 作为企业级应用程序框架,Spring在不同的事务管理API之上定义了一个抽象层。而应用程序开发人员不必了解底层的事务管理API,就可以使用Spring的事务管理机制。
    • Spring既支持编程式事务管理,也支持声明式的事务管理。
      • 编程式事务管理:将事务管理代码嵌入到业务方法中来控制事务的提交和回滚。在编程式管理事务时,必须在每个事务操作中包含额外的事务管理代码。
      • 声明式事务管理:大多数情况下比编程式事务管理更好用。它将事务管理代码从业务方法中分离出来,以声明的方式来实现事务管理。事务管理作为一种横切关注点,可以通过AOP方法模块化。Spring通过Spring AOP框架支持声明式事务管理。
    <!-- 配置事务管理器 -->
    <bean id="transactionManager"
          class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    
    <!-- 启用事务注解 -->
    <tx:annotation-driven transaction-manager="transactionManager"/>
    

    在对应方法上加上@Transactional注解

    REQUIRED传播行为

    • 当bookService的purchase()方法被另一个事务方法checkout()调用时,它默认会在现有的事务内运行,这个默认的传播行为就是REQUIRED。因此在checkout()方法的开始和终止边界内只有一个事务。这个事务只在checkout()方法结束的时候被提交,结果用户一本书都买不了。
    • 事务传播属性可以在@Transactional注解的propagation属性中定义

    REQUIRES_NEW传播行为

    • 另一种常见的传播行为是REQUIRES_NEW,它表示该方法必须启动一个新事务,并在自己的事务内运行,如果有事务在运行,就应该先挂起它。

    并发事务所导致的问题:

    • 脏读:对于两个事务T1、T2,T1读取了已经被T2更新但还没有被提交的字段,之后,如T2回滚,T1读取的内容就是临时且无效的。
    • 不可重复读:对于两个事务T1、T2,T1读取了一个字段,然后T2更新了该字段,之后,T1再次读取同一个字段,值就不同了。
    • 幻读:对于两个事务T1、T2,从一个表中读取了一个字段,然后T2在该表中插入了一些新的行,之后,如果T1再次读取同一个表,就会多出几行。
    // 添加事务注解
    // 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,
                  readOnly=false,
                  timeout=3)
    

    使用xml配置Spring事务

    <!-- 1.配置事务管理器 -->
    <bean id="transactionManager"
          class="rg.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!-- 注入数据库连接池 -->
        <property name="dataSource" ref="dataSource"/>
    </bean>
    
    <!-- 2.配置事务属性 -->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <!-- 传播行为 -->
            <tx:method name="save*" propagation="REQUIRED" />
            <tx:method name="insert*" propagation="REQUIRED" />
            <tx:method name="add*" propagation="REQUIRED" />
            <tx:method name="create*" propagation="REQUIRED" />
            <tx:method name="delete*" propagation="REQUIRED" />
            <tx:method name="update*" propagation="REQUIRED" />
            <tx:method name="find*" propagation="SUPPORTS" read-only="true" />
            <tx:method name="select*" propagation="SUPPORTS" read-only="true" />
            <tx:method name="get*" propagation="SUPPORTS" read-only="true" />
        </tx:attributes>
    </tx:advice>
    
    <!-- 3.配置事务切入点,以及把事务切入点和事务属性关联起来 -->
    <aop:config>
        <aop:advisor advice-ref="txAdvice"
                     pointcut="execution(* com.bank.service.*.*(..))" />
    </aop:config>
    

    相关文章

      网友评论

          本文标题:Spring4.0笔记

          本文链接:https://www.haomeiwen.com/subject/buqojqtx.html