Spring基础知识
一、简介
1. IOC 和 DI
- IOC(Inversion of Control):反转控制。反转资源获取的方向,容器主动将资源推送给它所管理的组件,组件所要做的仅是选择一种合适的方式来接收资源。
- DI(Dependency Injection):依赖注入。IOC 的另一种表述方式。组件以一些预定义好的方式接受来自如容器的资源注入。
2. Spring提供了两种 IOC 容器的实现
在 SpringIOC 容器读取 Bean 配置创建 Bean 实例之前,必须对它进行实例化。只有在容器实例化后,才可以从 IOC 容器里获取 Bean 实例。
- BeanFactory:IOC 容器的基本实现,是 Spring 框架的基础设施,面向 Spring 本身。
- ApplicationContext:提供了更多的高级特性。是 BeanFactory 的子接口。面向使用 Spring 框架的开发者。(几乎所有应用场合都直接使用 ApplicationContext 而非底层的 BeanFactory)
3. ApplicationContext 的主要实现类
ApplicationContext 在初始化上下文时就实例化所有的单例
- ClassPathXmlApplicationContext:从类路径下加载配置文件
- FileSystemXmlApplicationContext:从文件系统中加载配置文件
二、通过 XML 文件配置 Bean
1. 通过全类名(反射)
Spring支持3种依赖注入的方式
- 属性注入
- 构造器注入
- 工厂方法注入(不推荐)
1)属性注入
主程序:Main.java
public class Main { public static void main(String[] args) {
//1. 创建 Spring 的 IOC 容器 ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml"); //2. 从 IOC 容器中获取 bean 的实例 HelloWorld helloWorld = (HelloWorld) ctx.getBean("helloWorld"); //根据类型来获取 bean 的实例: 要求在 IOC 容器中只有一个与之类型匹配的 bean, 若有多个则会抛出异常. //一般情况下, 该方法可用, 因为一般情况下, 在一个 IOC 容器中一个类型对应的 bean 也只有一个. //HelloWorld helloWorld1 = ctx.getBean(HelloWorld.class); //3. 使用 bean helloWorld.hello(); } }
JavaBean:HelloWorld.java
public class HelloWorld { private String user; public HelloWorld() { }
public HelloWorld(String user) {
this.user = user;
}
public void setUser(String user) { System.out.println("setUser:" + user); this.user = user; }
public void hello(){ System.out.println("Hello: " + user); } }
配置文件:beas.xml
<!-- 配置一个 bean --> <bean id="helloWorld" class="com.spring.helloworld.HelloWorld"> <!-- 为属性赋值 --> <!-- 通过属性注入: 通过 setter 方法注入属性值 --> <property name="user" value="Tom"></property> </bean>
2)构造器注入
JavaBean:Car.java
public class Car { private String company; private String brand; private int maxSpeed; private float price; public Car(String company, String brand, float price) { super(); this.company = company; this.brand = brand; this.price = price; } }
JavaBean:User.java(拥有Car类对象的集合)
public class User { private String userName;
private Car car; private List<Car> cars;
private Map<String, Car> cars2 private Properties properties;
public User() {
} ......
//一系列get和set方法 }
配置文件:bean.xml
<!-- 若一个 bean 有多个构造器,可以根据 index 和 type 进行更加精确的定位。 --> <bean id="car" class="com.spring.helloworld.Car"> <constructor-arg value="KUGA" index="1"></constructor-arg> <constructor-arg value="ChangAnFord" index="0"></constructor-arg> <!-- 或<constructor-arg index="0"><value>ChangAnFord</value> </constructor-arg> --> <!--若字面值中包含特殊字符, 则可以使用 CDATA 来进行赋值。 <constructor-arg index="0"> <value><![CDATA[<ChangAnFord>]]></value> </constructor-arg> --> <constructor-arg value="250000" type="float"></constructor-arg> </bean> <!-- 装配集合属性 --> <bean id="user" class="com.spring.helloworld.User"> <property name="userName" value="Jack"></property> <property name="car" ref="car"></property><!-- 引用外部bean。car即为上个bean的id --> <!-- 也可以写为 <property name="car"><ref bean="car" /></property> --> <!-- 也可以用内部 bean, 类似于匿名内部类对象. 不能被外部的 bean 来引用, 也没有必要设置 id 属性 <bean class="com.spring.helloworld.Car"> <constructor-arg value="KUGA" index="1"></constructor-arg> ... </bean> --> <!-- 为级联属性赋值,需定义setPrice方法(给car的price属性赋值) --> <property name="car.price" value="300"></property> <!-- 使用 list 元素来装配集合属性 --> <property name="cars"> <!-- 该list只能在此使用,若想在其他地方也能使用可以声明一个集合类型的bean --> <list> <ref bean="car" /> <ref bean="car2" /><!-- Car的其他对象,同上一个bean节点中的内容,这里没有写出 --> </list> </property> <!-- 声明集合类型的 bean,以供多个bean进行引用,需要导入util命名空间 <util:list id="carslist"> <ref bean="car"/> <ref bean="car2"/> </util:list> 引用外部声明的 list <property name="cars" ref="carslist"></property> --> <!-- 使用 map 元素来装配集合属性 --> <property name="cars2"> <map> <entry key="AA" value-ref="car"> <entry key="BB" value-ref="car2"> </map> </property> <!-- 配置Properties属性值 --> <property name="properties"> <prop key="user">root</prop> <prop key="password">123</prop> </property> </bean> <!-- 通过 p 命名空间为 bean 的属性赋值,需要先导入 p 命名空间。比传统配置简洁 --> <bean id="user2" class="com.spring.helloworld.User" p:cars-ref="cars" p:userName="Titannic" />
2. 通过工厂方法
1)静态工厂方法
静态工厂类
public class StaticCarFactory{ private static Map<String, Car> cars = new HashMap<String, Car>(); static{ cars.put("audi", new Car("audi", 300000)); cars.put("ford", new Car("ford", 400000)); } public static Car getCar(String name){ return cars.get(name); } }
配置文件
<!-- class 属性:指向静态工厂方法的全类名 factory-method:指向静态工厂方法的名字 constructor-arg:如果工厂方法需要传入参数,则使用 constructor-arg 来配置参数 --> <bean id="car1" class="com.spring.beans.factory.StaticCarFactory" factory-method="getCar"> <constructor-arg value="audi"></constructor-arg> </bean>
2)实例工厂方法
工厂类
public class InstanceCarFactory{ private Map<String, Car> cars = null; public InstanceCarFactory(){ cars = new HashMap<String, Car>(); cars.put("audi", new Car("audi", 300000)); cars.put("ford", new Car("ford", 300000)); } public Car getCar(String brand){ return cars.get(brand); } }
配置文件
<!-- 配置工厂的实例 --> <bean id="carFactory" class="com.spring.beans.factory.InstanceCarFactory"></bean> <!-- 通过实例工厂方法来配置 Bean -->
<!-- factory-bean:指向实例工厂方法的 Bean factory-method:指向静态工厂方法的名字 constructor-arg:如果工厂方法需要传入参数,则使用 constructor-arg 来配置参数 --> <bean id="car2" factory-bean="carFactory" factory-method="getCar"> <constructor-arg value="ford"></constructor-arg> </bean>
3)实现 FactoryBean 接口
工厂类
public class CarFactoryBean implements FactoryBean<Car>{ private String brand; public void setBrand(String brand){ this.brand = brand; } @Override public Car getObject() throws Exception{ return new Car(brand,500000); } @Override public Class<?> getObjectType(){ return Car.class; } @Override puiblic boolean isSingleton(){ return true; } }
配置文件
<!-- 通过 FactoryBean 来配置 Bean 的实例 class:指向 FactoryBean 的全类名 property:配置 FactoryBean 的属性 返回 Car 的实例 --> <bean id="car" class="com.spring.beans.factory.CarFactoryBean"> <property name="brand" value="BMW"></property> </bean>
三、通过注解的方式配置 Bean
组件扫描:Spring 能够从 classpath 下自动扫描,侦测和实例化具有特定注解的组件,并自动为其命名为:首字母小写的类名。
组件:
- @Component:基本注解,标识了一个受 Spring 管理的组件
- @Respository:标识持久层组件
- @Service:标识服务层(业务层)组件
- @Controller:标识表现层组件
main.java
ApplicationContext ctx = new new ClassPathXmlApplicationContext("bean-annotation"); UserController userController = (UserController)ctx.getBean("userController"); UserRepository userRepository = (UserRepository)ctx.getBean("userRepository");
相关类
@Autowired 自动装配:在IOC容器中寻找与该类型兼容的 Bean。 @Autowired(required=false) //即使该类不在IOC中也可以被装配
@Qualifier("xxx"):指定装配xxx
//这种情况自动命名为 testObject @Component public class TestObject{ //... } @Controller public class UserController{ @Autowired
private UserService userService;
//... } @Service public class UserService{ @Autowired
@Qualifier("userRepositoryImpl")
private UserRepository userRepository;
//... } public interface UserRepository{ void save(); } //手动命名 @Repository("userRepository") public class UserRepositoryImpl{ @Override public void save(){ //... } }
配置文件
<!-- 配置自动扫描的包: 需要导入 context 命名空间。 Spring会扫描该包和其子包下具有特定注解的组件。 --> <!-- 1. 可以通过 resource-pattern 指定扫描的资源。 --> <context:component-scan base-package="com.spring.annotation" resource-pattern="*.class"> </context:component-scan> <!-- 2. context:exclude-filter 子节点指定排除哪些表达式的组件。 --> <context:component-scan base-package="com.spring.annotation"> <!-- 排除指定组件 --> <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Repository"> <!-- 排除指定类 --> <context:exclude-filter type="assignable" expression="com.spring.beans.annotation.UserRepository"> </context:component-scan> <!-- 3. context:include-filter 子节点指定包含哪些表达式的组件,该子节点需要 use-default-filters="false" 配合使用。 use-default-filters="false" 不使用默认的过滤器。 --> <context:component-scan base-package="com.spring.annotation" use-default-filters="false"> <!-- 只包含指定组件 --> <context:include-filter type="annotation" expression="org.springframework.stereotype.Repository"> <!-- 只包含指定类 --> <context:include-filter type="assignable" expression="com.spring.beans.annotation.UserRepositoryrg"> </context:component-scan>
四、自动装配
方式:在<bean>的autowire属性里指定自动装配的模式
分类:
- byType(根据类型):根据 Bean 的类型和当前 Bean 的属性的类型进行自动装配。若 IOC 容器中有多个与目标 Bean 类型一致的 Bean 。这种情况下不能自动装配。
- byName(根据名称):根据 Bean 的 id 和当前 Bean 中的 setter 风格的属性名称进行匹配。必须将目标 Bean 的名称和属性名设置的完全相同。
- constructor(通过构造器):当 Bean 中存在多个构造器时,这种自动装配会很复杂(不推荐)
使用:
<bean id="person" class="com.spring.autowire.Person" p:name="Tom"
autowire="byName" />
五、Bean之间的关系
1. 继承
- 使用 Bean 的 parent 属性指定继承哪个 Bean 的配置。
- 子 Bean 可覆盖从父 Bean 继承过来的配置。
- 若想把父 Bean 只作为模板可设置 abstract 属性为 true,那么Spring不会实例化这个 Bean。这样父 Bean 的 class 属性可忽略,让子 Bean 指定自己的类。
<bean id="address" class="com.spring.beans.Address" p:city="AAA" p:street="BBB" /> <bean id="address2" p:street="CCC" parent="address" />
2. 依赖
- 前置依赖的 Bean 必须存在,不然报错
- 前置依赖的 Bean 会在本 Bean 实例化之前创建好
- 如果前置依赖多个 Bean,则可以通过逗号,空格的方式配置 Bean 的名称
<bean id="car" class="com.spring.beans.Car" p:brand="Audi" p:price="300000" /> <bean id="person" class="com.spring.beans.Person" p:name="Tom" depends-on="car" />
六、Bean的作用域
使用 scope 属性设置作用域,默认为 singleton(单例)。
种类:
- singleton(单例):容器初始时创建 Bean 实例。在整个容器的生命周期内只创建这一个 Bean。所以每次向容器获取 Bean都为同一个 Bean。
- prototype(原型):容器初始时不会创建 Bean 实例。每次向容器请求获取 Bean 时,都会创建一个新的 Bean 实例返回。
<bean id="car" class="com.spring.beans.Car" scope="prototype" p:brand="Audi" p:price="300000" />
七、使用外部属性文件
需求:实际项目开发中,要是想对某一 Bean 的属性进行修改时我们在其配置文件中找到对应的 Bean 才能为其属性赋值。要是项目中有过多的 Bean ,查找起来就越困难。这时我们可以使用外部属性文件让这些部署细节与 Bean 配置相分离。
准备:导入context命名空间
配置文件
<context:property-placeholder location="classpath:db.properties" /> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <!-- 使用外部化属性文件的属性 --> <property name="user" value="${user}"></property> <property name="password" value="${password}"></property> <property name="driverClass" value="${driverClass}"></property> <property name="jdbcurl" value="${jdbcurl}"></property> </bean>
db.properties
jdbc.user=root jdbc.password=1230 jdbc.driverClass=com.mysql.jdbc.Driver jdbc.jdbcUrl=jdbc:mysql:///test
八、管理 Bean 的生命周期
SpringIOC 容器可以管理 Bean 的生命周期。
使用:在 Bean 的声明里设置 init-method 和 destroy-method 属性,为 Bean 指定初始化和销毁方法。
生命周期管理过程:
- 实例化 Bean 对象——调用其构造器
- 为 Bean 对象设置属性——调用其 setter 方法
- 初始化——调用 init-method 属性指定的初始化方法
- 使用 Bean 对象
- 销毁——调用 destroy-method 属性指定的销毁方法
<!-- Car类中定义了 myInit() 和 myDestroy() 方法 --> <bean id="car" class="com.spring.beans.Car" init-method="myInit" destroy-method="myDestroy"> <property name="brand" value="Audi"></property> </bean>
添加 Bean 后置处理器
作用:在调用每个 Bean 的初始化方法前后处理程序。
注意:需实现 BeanPostProcessor 接口。
添加后置处理器后的生命周期管理过程:
- 创建 Bean 实例
- 为 Bean 实例的属性赋值
- 将 Bean 实例传递给 Bean 后置处理器的 postProcessBeforeInitialization 方法
- 调用 Bean 的初始化方法
- 将 Bean 实例传递给 Bean 后置处理器的 postProcessAfterInitialization 方法
- 使用 Bean
- 容器关闭时,调用 Bean 的销毁方法
MyBeanPostProcessor.java
public class MyBeanPostProcessor implements BeanPostProcessor{ @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException{ System.out.println("postProcessBeforeInitialization"); if("car".equals(beanName)){ //.... } return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException{ System.out.println("postProcessAfterInitialization"); return bean; } }
配置文件
<!-- 配置 Bean 的后置处理器:不需要 id,IOC 容器自动识别是一个 BeanPostProcessor -->
<bean class="com.spring.beans.MyBeanPostProcessor"></bean>
九、AOP
1. 简介
简介:AOP(Aspect-Oriented Programming,面向切面编程),是对OOP的补充。主要变成对象是切面。一个方法中有很多步骤,可以把每个方法中统一存在的步骤看成一个切面。比如像每个方法被调用时需记录日志的这一步骤可以被看成一个切面,而AOP就是在不改变源码的情况下使关注点转向这些切面。
作用:可以通过预编译方式和运行期动态代理实现在不修改源代码的情况下给程序动态统一添加功能。
术语:
- 通知(Advice):切面必须完成的工作
- 目标(Target):被通知的对象
- 代理(Proxy):向目标对象应用通知之后创建的对象
- 连接点(Joinpoint):程序执行的某个特定位置。某个方法调用前、调用后、方法抛出异常等。
- 切点(Pointcut):AOP通过切点定位到特定的连接点。一个切点可以匹配多个连接点
需要 jar 包:
- com.springsource.org.aopalliance
- com.springsource.org.aspectj.weaver
- commons-logging
- spring-aop
- spring-aspects
2. 通知
通知注解:
- @Before:前置通知,在方法执行之前执行
- @After:后置通知,在方法执行之后执行
- @AfterRunning:返回通知,在方法返回结果之后执行
- @AfterThrowing:异常通知,在方法抛出异常之后执行
- @Around:环绕通知,环绕着方法执行
切面类
//通过添加 @Aspect 注解声明一个 bean 是一个切面 @Aspect
//注:目标方法所在的类也需要加上 @Component 注解 @Component public class LoggingAspect { /** * 定义一个方法,用于声明切入点表达式。 一般地, 该方法中再不需要添入其他的代码。 * 使用 @Pointcut 来声明切入点表达式。 * 后面的其他通知直接使用方法名来引用当前的切入点表达式。 */ @Pointcut("execution(public int com.atguigu.spring.aop.ArithmeticCalculator.*(..))") public void declareJointPointExpression(){} //声明该方法是一个前置通知:在目标方法开始之前执行 @Before("execution(public int com.spring.aop.ArithmeticCalculator.*(int, int))") public void beforeMethod(JoinPoint joinPoint){ String methodName = joinPoint.getSignature().getName(); Object [] args = joinPoint.getArgs(); System.out.println("The method " + methodName + " begins with " + Arrays.asList(args)); } //声明该方法是一个后置通知:在目标方法执行之后(无论是否发生异常)执行。在后置通知中还不能访问目标方法执行的结果 @After("execution(* com.spring.aop.*.*(..))") //被通知的对象(目标)为 com.spring.aop 下的所有方法 public void afterMethod(JoinPoint joinPoint){ String methodName = joinPoint.getSignature().getName(); System.out.println("The method " + methodName + " ends"); } /** * 在方法法正常结束受执行的代码 * 返回通知是可以访问到方法的返回值的!
*
* 使用了切点表达式,等同于 value="execution(public int com.atguigu.spring.aop.ArithmeticCalculator.*(..))" */ @AfterReturning(value="declareJointPointExpression()", 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="declareJointPointExpression()", throwing="e") public void afterThrowing(JoinPoint joinPoint, Exception e){ String methodName = joinPoint.getSignature().getName(); System.out.println("The method " + methodName + " occurs excetion:" + e); }
/** * 环绕通知需要携带 ProceedingJoinPoint 类型的参数。 * 环绕通知类似于动态代理的全过程:ProceedingJoinPoint 类型的参数可以决定是否执行目标方法。 * 且环绕通知必须有返回值, 返回值即为目标方法的返回值 */ @Around("execution(public int com.atguigu.spring.aop.ArithmeticCalculator.*(..))") public Object aroundMethod(ProceedingJoinPoint pjd){ Object result = null; String methodName = pjd.getSignature().getName(); try { //前置通知 System.out.println("The method " + methodName + " begins with " + Arrays.asList(pjd.getArgs())); //执行目标方法 result = pjd.proceed(); //返回通知 System.out.println("The method " + methodName + " ends with " + result); } catch (Throwable e) { //异常通知 System.out.println("The method " + methodName + " occurs exception:" + e); throw new RuntimeException(e); } //后置通知 System.out.println("The method " + methodName + " ends"); return result; } }
applicationContext.xml
<!-- 自动扫描的包 --> <context:component-scan base-package="com.atguigu.spring.aop"></context:component-scan> <!-- 使 AspectJ 的注解起作用:自动为匹配的类生成代理对象 --> <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
3. 优先级
可以用 @Order(num) 来指定切面的优先级,且值越小优先级越高
@Order(1) @Aspect @Component public class VlidationAspect { @Before("com.spring.aop.LoggingAspect.declareJointPointExpression()") public void validateArgs(JoinPoint joinPoint){ System.out.println("-->validate:" + Arrays.asList(joinPoint.getArgs())); } }
4. Xml方式
每个 aop:aspect 节点为一个切面
<!-- 配置 目标 bean -->
<bean id="arithmeticCalculator" class="com.atguigu.spring.aop.xml.ArithmeticCalculatorImpl"></bean>
<!-- 配置切面的 bean. -->
<bean id="loggingAspect" class="com.atguigu.spring.aop.xml.LoggingAspect"></bean>
<bean id="vlidationAspect" class="com.atguigu.spring.aop.xml.VlidationAspect"></bean>
<!-- 配置 AOP -->
<aop:config>
<!-- 配置切点表达式 -->
<aop:pointcut expression="execution(* com.atguigu.spring.aop.xml.ArithmeticCalculator.*(int, int))" 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:around method="aroundMethod" pointcut-ref="pointcut"/>
-->
</aop:aspect>
<aop:aspect ref="vlidationAspect" order="1">
<aop:before method="validateArgs" pointcut-ref="pointcut"/>
</aop:aspect>
</aop:config>
十、Spring 对 JDBC 的支持
1. 基本操作
Spring 提供了 JdbcTemplate 类。
方法签名:
- int JdbcTemplate.update(String sql, Object... args) —— //执行更新(INSERT、UPDATE、DALETE Sql语句)。
- int[] JdbcTemplate.batchUpdate(String sql, List<Object[]> batchArgs) —— //执行批量更新。
- <T> T JdbcTemplate.queryForObject(String sql, RowMapper<T> rowMapper, Object... args) —— //从数据库中获取一条记录, 实际得到对应的一个对象。
- <T> List<T> JdbcTemplate.query(String sql, RowMapper<T> rowMapper, Object... args) —— //查到实体类的集合。
- <T> T JdbcTemplate.queryForObject(String sql, Class<T> requiredType) —— //获取单个列的值, 或做统计查询。
- int NamedParameterJdbcTemplate.update(String sql, Map<String, ?> paramMap) —— //执行更新(但可以使用具名参数)。
- int NamedParameterJdbcTemplate.update(String sql, SqlParameterSource paramSource) —— //执行更新(可以传入实体类实例,以实例中的属性作为具名参数)。
public class JDBCTest { private ApplicationContext ctx = null; private JdbcTemplate jdbcTemplate; private NamedParameterJdbcTemplate namedParameterJdbcTemplate; { ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); jdbcTemplate = (JdbcTemplate) ctx.getBean("jdbcTemplate"); namedParameterJdbcTemplate = ctx.getBean(NamedParameterJdbcTemplate.class); } /** * 1. 执行 INSERT, UPDATE, DELETE */ @Test public void testUpdate(){ String sql = "UPDATE employees SET last_name = ? WHERE id = ?"; jdbcTemplate.update(sql, "Jack", 5); } /** * 2. 执行批量更新: 批量的 INSERT, UPDATE, DELETE * 最后一个参数是 Object[] 的 List 类型: 因为修改一条记录需要一个 Object 的数组 */ @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@atguigu.com", 1}); batchArgs.add(new Object[]{"BB", "bb@atguigu.com", 2}); batchArgs.add(new Object[]{"CC", "cc@atguigu.com", 3}); batchArgs.add(new Object[]{"DD", "dd@atguigu.com", 3}); batchArgs.add(new Object[]{"EE", "ee@atguigu.com", 2}); jdbcTemplate.batchUpdate(sql, batchArgs); } /** * 3. 从数据库中获取一条记录, 实际得到对应的一个对象 * 1)RowMapper:指定如何去映射结果集的行, 常用的实现类为 BeanPropertyRowMapper * 2)使用 SQL 中列的别名完成列名和类的属性名的映射. 例如 last_name lastName * 3)不支持级联属性. JdbcTemplate 只是一个 JDBC 的小工具, 而不是 ORM 框架 */ @Test public void testQueryForObject(){ String sql = "SELECT id, last_name lastName, email FROM employees WHERE id = ?"; RowMapper<Employee> rowMapper = new BeanPropertyRowMapper<>(Employee.class); Employee employee = jdbcTemplate.queryForObject(sql, rowMapper, 1); } /** * 4. 查到实体类的集合 * 注意调用的不是 queryForList 方法 */ @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); } /** * 5. 获取单个列的值, 或做统计查询 */ @Test public void testQueryForObject2(){ String sql = "SELECT count(id) FROM employees"; long count = jdbcTemplate.queryForObject(sql, Long.class); System.out.println(count); } /** * 6. 更新(可以为参数起名字). * 若有多个参数, 则不用再去对应位置, 直接对应参数名, 便于维护 */ @Test public void testNamedParameterJdbcTemplate(){ String sql = "INSERT INTO employees(last_name, email, dept_id) VALUES(:ln,:email,:deptid)"; Map<String, Object> paramMap = new HashMap<>(); paramMap.put("ln", "FF"); paramMap.put("email", "ff@atguigu.com"); paramMap.put("deptid", 2); namedParameterJdbcTemplate.update(sql, paramMap); } /** * 7. 使用具名参数时, 可以使用 update(String sql, SqlParameterSource paramSource) 方法进行更新操作 * 1)SQL 语句中的参数名和类的属性一致! * 2)使用 SqlParameterSource 的 BeanPropertySqlParameterSource 实现类作为参数 */ @Test public void testNamedParameterJdbcTemplate2(){ String sql = "INSERT INTO employees(last_name, email, dept_id) VALUES(:lastName,:email,:deptId)"; Employee employee = new Employee(); employee.setLastName("XYZ"); employee.setEmail("xyz@sina.com"); employee.setDeptId(3); SqlParameterSource paramSource = new BeanPropertySqlParameterSource(employee); namedParameterJdbcTemplate.update(sql, paramSource); } }
applicationContext.xml
<!-- 导入资源文件 --> <context:property-placeholder location="classpath:db.properties"/> <!-- 配置 C3P0 数据源 --> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="user" value="${jdbc.user}"></property> <property name="password" value="${jdbc.password}"></property> <property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property> <property name="driverClass" value="${jdbc.driverClass}"></property> <property name="initialPoolSize" value="${jdbc.initPoolSize}"></property> <property name="maxPoolSize" value="${jdbc.maxPoolSize}"></property> </bean> <!-- 配置 Spirng 的 JdbcTemplate --> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 配置 NamedParameterJdbcTemplate, 该对象可以使用具名参数, 其没有无参数的构造器, 所以必须为其构造器指定参数 --> <bean id="namedParameterJdbcTemplate" class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate"> <constructor-arg ref="dataSource"></constructor-arg> </bean>
db.properties
jdbc.user=root jdbc.password=1230 jdbc.driverClass=com.mysql.jdbc.Driver jdbc.jdbcUrl=jdbc:mysql:///spring jdbc.initPoolSize=5 jdbc.maxPoolSize=10
2. 事务
编程式事务管理:将事务管理代码嵌入到业务方法中来控制事务的提交和回滚。
声明式事务管理:将事务管理代码从业务方法中分离出来,以声明的方式来实现事务管理。
事务传播属性:当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。
传播行为:
- PROPAGATION_REQUIRED -- 支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。
- PROPAGATION_SUPPORTS -- 支持当前事务,如果当前没有事务,就以非事务方式执行。
- PROPAGATION_MANDATORY -- 支持当前事务,如果当前没有事务,就抛出异常。
- PROPAGATION_REQUIRES_NEW -- 新建事务,如果当前存在事务,把当前事务挂起。
- PROPAGATION_NOT_SUPPORTED -- 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
- PROPAGATION_NEVER -- 以非事务方式执行,如果当前存在事务,则抛出异常。
- PROPAGATION_NESTED -- 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则进行与PROPAGATION_REQUIRED类似的操作。
1)使用注解的方式声明事务
@Autowired private BookShopDao bookShopDao; //添加事务注解 //1.使用 propagation 指定事务的传播行为。//2.使用 isolation 指定事务的隔离级别, 最常用的取值为 READ_COMMITTED。 //3.默认情况下 Spring 的声明式事务对所有的运行时异常进行回滚. 也可以通过对应的属性进行设置. 通常情况下去默认值即可。 //4.使用 readOnly 指定事务是否为只读. 表示这个事务只读取数据但不更新数据。
//5.使用 timeout 指定强制回滚之前事务可以占用的时间。若超过此时间则强制回滚。 @Transactional(propagation=Propagation.REQUIRES_NEW, isolation=Isolation.READ_COMMITTED,
noRollbackFor={UserAccountException.class},//对 UserAccount 异常不进行回滚 readOnly=false, timeout=3) @Override public void purchase(String username, String isbn) { try { Thread.sleep(5000); } catch (InterruptedException e) {} //1. 获取书的单价 int price = bookShopDao.findBookPriceByIsbn(isbn); //2. 更新数的库存 bookShopDao.updateBookStock(isbn); //3. 更新用户余额 bookShopDao.updateUserAccount(username, price); }
applicationContext-tx.xml
<!-- 配置事务管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 启用事务注解 需导入 tx 命名空间 --> <tx:annotation-driven transaction-manager="transactionManager"/>
2)使用Xml的方式声明事务
applicationComtext-tx-xml.xml
<!-- 1. 配置事务管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 2. 配置事务属性 --> <tx:advice id="txAdvice" transaction-manager="transactionManager"> <tx:attributes> <!-- 根据方法名指定事务的属性 --> <tx:method name="purchase" propagation="REQUIRES_NEW"/> <tx:method name="get*" read-only="true"/> <tx:method name="find*" read-only="true"/> <tx:method name="*"/> </tx:attributes> </tx:advice> <!-- 3. 配置事务切入点, 以及把事务切入点和事务属性关联起来 --> <aop:config>
<!-- 作用于哪些方法上 --> <aop:pointcut expression="execution(* com.spring.tx.xml.service.*.*(..))" id="txPointCut"/>
<!-- 把切入点和属性相关联 --> <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/> </aop:config>