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>