Spring

Posted dear_diary

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring相关的知识,希望对你有一定的参考价值。

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)

示例:

<!--
	配置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>

以上是关于Spring的主要内容,如果未能解决你的问题,请参考以下文章

Spring boot:thymeleaf 没有正确渲染片段

What's the difference between @Component, @Repository & @Service annotations in Spring?(代码片段

spring练习,在Eclipse搭建的Spring开发环境中,使用set注入方式,实现对象的依赖关系,通过ClassPathXmlApplicationContext实体类获取Bean对象(代码片段

Spring Rest 文档。片段生成时 UTF-8 中间字节无效 [重复]

解决spring-boot启动中碰到的问题:Cannot determine embedded database driver class for database type NONE(转)(代码片段

一张图帮你记忆,Spring Boot 应用在启动阶段执行代码的几种方式