1. JdbcTemplate
1.1 概述
为了使 JDBC 更加易于使用,Spring 在 JDBC API 上定义了一个抽象层,以此建立一个 JDBC 存取框架。
作为 Spring JDBC 框架的核心,JDBC 模板的设计目的是为不同类型的 JDBC 操作提供模板方法,通过这种方式,可以在尽可能保留灵活性的情况下,将数据库的存取的工作量降到最低。
可以将 Spring 的 JdbcTemplate 看作是一个小型的轻量级持久层框架,和我们之前使用过的 DBUtils 风格非常接近。
1.2 环境准备
1.2.1 导入 jar 包
1.2.2 DB 属性文件
db.properties
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql:///test
jdbc.username=root
jdbc.password=root
1.2.3 在配置文件中配置bean
jdbc.xml
<context:property-placeholder location="db.properties"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driver}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
1.3 持久化操作
1.3.1 增删改
- 单条增删改:
JdbcTemplate.update(String, Object ...)
- 批量增删改:
JdbcTemplate.batchUpdate(String, List<Object[]>)
- Object[] 封装了 SQL 语句每一次执行时所需要的参数
- List 集合封装了 SQL 语句多次执行时的所有参数
ApplicationContext ac = new ClassPathXmlApplicationContext("jdbc.xml");
JdbcTemplate template = ac.getBean("jdbcTemplate", JdbcTemplate.class);
// 批量添加
public void testBatchUpdate() {
String sql = "insert into emp value(null, ?, ?, ?)";
List<Object[]> batchArgs = new ArrayList<>();
batchArgs.add(new Object[] {"root", 30, "女"});
batchArgs.add(new Object[] {"shaw", 28, "女"});
template.batchUpdate(sql, batchArgs);
}
补充:
1.3.2 查询
- 查询单行
<T> T queryForObject(String sql, RowMapper<T> rowMapper, Object... args) <T> T queryForObject(String sql, Object[] args, RowMapper<T> rowMapper)
- 查询单一值
<T> T queryForObject(String sql, Class<T> requiredType) <T> T queryForObject(String sql, Class<T> requiredType, Object... args) <T> T queryForObject(String sql, Object[] args, Class<T> requiredType)
- 查询多行
<T> List<T> query(String sql, RowMapper<T> rowMapper) <T> List<T> query(String sql, RowMapper<T> rowMapper, Object... args) <T> List<T> query(String sql, Object[] args, RowMapper<T> rowMapper)
测试:
public void testQueryForObject() {
// template.queryForObject(sql, requiredType) 用来获取单个的值
// template.queryForObject(sql, rowMapper) 用来获取单条记录
String sql = "SELECT eid, ename, age, sex FROM emp WHERE eid = ?";
// 将 [列名] 和 [属性名] 进行映射
RowMapper<Emp> rowMapper = new BeanPropertyRowMapper<>(Emp.class);
Emp emp = template.queryForObject(sql, new Object[] {3}, rowMapper);
System.out.println(emp);
System.out.println("--------------------------------");
sql = "SELECT COUNT(*) FROM emp";
Integer count = template.queryForObject(sql, Integer.class);
System.out.println(count);
}
public void testQuery() {
String sql = "SELECT eid, ename, age, sex FROM emp";
RowMapper<Emp> rowMapper = new BeanPropertyRowMapper<>(Emp.class);
List<Emp> list = template.query(sql, rowMapper);
System.out.println(list);
}
2. 事务管理简述
2.1 事务
- 在 JavaEE 企业级开发的应用领域,为了保证数据的完整性和一致性,必须引入数据库事务的概念,所以事务管理是企业级应用程序开发中必不可少的技术
- 事务就是一组由于逻辑上紧密关联而合并成一个整体(工作单元) 的多个数据库操作,这些操作要么都执行,要么都不执行
- 事务的 4 个关键属性(ACID)
- 原子性(atomicity):“原子”的本意是“不可再分”,事务的原子性表现为一个事务中涉及到的多个操作在逻辑上缺一不可。事务的原子性要求事务中的所有操作要么都执行,要么都不执行
- 一致性(consistency):“一致”指的是数据的一致,具体是指:所有数据都处于满足业务规则的一致性状态。一致性原则要求:一个事务中不管涉及到多少个操作,都必须保证事务执行之前数据是正确的,事务执行之后数据仍然是正确的。如果一个事务在执行的过程中,其中某一个或某几个操作失败了,则必须将其他所有操作撤销,将数据恢复到事务执行之前的状态,这就是回滚
- 隔离性(isolation):在应用程序实际运行过程中,事务往往是并发执行的,所以很有可能有许多事务同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏。隔离性原则要求多个事务在并发执行过程中不会互相干扰
- 持久性(durability):持久性原则要求事务执行完成后,对数据的修改永久的保存下来,不会因各种系统错误或其他意外情况而受到影响。通常情况下,事务对数据的修改应该被写入到持久化存储器中
2.2 Spring 事务管理
2.2.1 编程式事务管理
使用原生的 JDBC API 进行事务管理:
- 获取数据库连接 Connection 对象
- 取消事务的自动提交
- 执行操作
- 正常完成操作时手动提交事务
- 执行失败时回滚事务
- 关闭相关资源
【评价】使用原生的 JDBC API 实现事务管理是所有事务管理方式的基石,同时也是最典型的编程式事务管理。编程式事务管理需要将事务管理代码嵌入到业务方法中来控制事务的提交和回滚。在使用编程的方式管理事务时,必须在每个事务操作中包含额外的事务管理代码。相对于核心业务而言,事务管理的代码显然属于非核心业务,如果多个模块都使用同样模式的代码进行事务管理,显然会造成较大程度的代码冗余。
2.2.2 声明式事务管理
大多数情况下声明式事务比编程式事务管理更好:它将事务管理代码从业务方法中分离出来,以声明的方式来实现事务管理。
事务管理代码的固定模式作为一种 [横切关注点],可以通过 AOP 方法模块化,进而借助 Spring AOP 框架实现声明式事务管理。
Spring 在不同的事务管理 API 之上定义了一个抽象层,通过配置的方式使其生效,从而让应用程序开发人员不必了解事务管理 API 的底层实现细节,就可以使用 Spring 的事务管理机制。
Spring 既支持编程式事务管理,也支持声明式的事务管理。
2.2.3 Spring 提供的事务管理器
Spring 从不同的事务管理 API 中抽象出了一整套事务管理机制,让事务管理代码从特定的事务技术中独立出来。开发人员通过配置的方式进行事务管理,而不必了解其底层是如何实现的。
Spring 的核心事务管理抽象是它为事务管理封装了一组独立于技术的方法。无论使用 Spring 的哪种事务管理策略(编程式或声明式),事务管理器都是必须的。
事务管理器可以以普通的 bean 的形式声明在 Spring IOC 容器中。
2.2.4 事务管理器的主要实现
-
DataSourceTransactionManager:在应用程序中只需要处理一个数据源,而且通过 JDBC 存取
-
JtaTransactionManager:在 JavaEE 应用服务器上用 JTA(Java Transaction API) 进行事务管理
-
HibernateTransactionManager:用 Hibernate 框架存取数据库
-
DataSourceTransactionManager:在应用程序中只需要处理一个数据源,而且通过 JDBC 存取
-
JtaTransactionManager:在 JavaEE 应用服务器上用 JTA(Java Transaction API) 进行事务管理
-
HibernateTransactionManager:用 Hibernate 框架存取数据库
3. 基于注解的声明式事务配置
3.1 以买书为例
- xml 中配置事务管理
<!-- 配置事务管理器 --> <bean id="transactionManager" class = "org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> <!-- 启用事务注解驱动 (引入 tx 命名空间),对事务相关注解进行扫描,解析含义并执行功能 --> <!-- 当事务管理器 id 为 transactionManager,则在如下配置中可省略 --> <tx:annotation-driven [transaction-manager="transactionManager"]/>
- 在需要进行事务控制的方法上加注解 @Transactional,表示将该方法中所有的操作作为一个事务进行管理;也可以加到类上,表示该类中所有方法都需要事务管理
@Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented public @interface Transactional { String value() default ""; Propagation propagation() default Propagation.REQUIRED; Isolation isolation() default Isolation.DEFAULT; int timeout() default TransactionDefinition.TIMEOUT_DEFAULT; boolean readOnly() default false; Class<? extends Throwable>[] rollbackFor() default {}; String[] rollbackForClassName() default {}; Class<? extends Throwable>[] noRollbackFor() default {}; String[] noRollbackForClassName() default {}; }
3.2 事务的传播行为
3.2.1 概述
当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。
【举例说明】设 Method-a 和 Method-b 都有事务,当 Method-a 内调用 Method-b 时,会将 Method-a 中的事务传播给 Method-b,而 Method-b 对于事务的处理方式就是事务的传播行为。比如,Method-b 可能会继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。
事务的传播行为可以由传播属性指定。Spring 定义了 7 种类传播行为,详见枚举 Propagation。
事务传播属性可以在 @Transactional 注解的 propagation 属性中定义。
3.2.2 REQUIRED
举例说明:
purchase()
买一本书;checkout()
买多本书,内部执行多次purchase()
操作- 当 bookService 的
purchase()
被另一个事务方法checkout()
调用时,它默认会在现有的事务内运行。这个默认的传播行为就是 REQUIRED。因此在checkout()
的开始和终止边界内只有一个事务。这个事务只在checkout()
结束的时候被提交,结果用户一本书都买不了。
3.2.3 REQUIRED_NEW
表示该方法必须启动一个新事务,并在自己的事务内运行。如果有事务在运行,就应该先挂起它。
3.3 事务的隔离级别
3.3.1 事务并发问题
假设现在有两个事务:Transaction01 和 Transaction02 并发执行。
3.3.2 隔离级别
数据库系统必须具有隔离并发运行各个事务的能力,使它们不会相互影响,避免各种并发问题。一个事务与其他事务隔离的程度称为 [隔离级别]。
SQL 标准中规定了多种事务隔离级别,不同隔离级别对应不同的干扰程度,隔离级别越高,数据一致性就越好,但并发性越弱。
各个隔离级别解决并发问题的能力见下表:
各种数据库产品对事务隔离级别的支持程度:
通过 isolation 属性可设置隔离级别。
3.4 触发事务回滚的异常
- 默认情况:捕获到 RuntimeException 或 Error 时回滚,而捕获到编译时异常不回滚。
- Transactional 相关属性
- rollbackFor 属性:指定遇到时必须进行回滚的异常类型,可以为多个
- noRollbackFor 属性:指定遇到时不回滚的异常类型,可以为多个
- 举例
3.5 事务的超时和只读属性
由于事务可以在行和表上获得锁,因此长事务会占用资源,并对整体性能产生影响。如果一个事务只读取数据但不做修改,数据库引擎可以对这个事务进行优化。
- 超时属性(timeout):事务在强制回滚之前可以保持多久。这样可以防止长期运行的事务占用资源。
- 只读属性(readOnly): 表示这个事务只读取数据但不更新数据, 这样可以帮助数据库引擎优化事务。
4. 基于XML的声明式事务配置
- 配置事务管理器,不管是用注解方式或者XML方式配置事务,一定要有 DataSourceTransactionManager 的支持
<!-- TransactionManager 就是一个切面 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="druidDataSource"></property> </bean>
- 配置切入点表达式(作用于谁)
<!-- 配置哪些类/方法需要被事务控制 --> <aop:config> <aop:pointcut expression="execution(* cn.edu.nuist.book_xml.service.impl.*.*(..))" id="pointCut"/> <!-- 将切入点表达式和事务属性配置关联到一起 --> <aop:advisor advice-ref="advice" pointcut-ref="pointCut"/> </aop:config>
- 配置事务通知(如何作用)
<!-- 需要得到 transactionManager(一个切面) 的支持 --> <tx:advice id="advice" transaction-manager="transactionManager"> <tx:attributes> <!-- 在设置好的切入点表达式下进一步确定具体方法的事务属性 --> <tx:method name="buyBook" isolation="READ_COMMITTED" timeout="10"/> <!-- 可使用 {通配符*} 进行配置,如以 select 开头的方法 --> <tx:method name="select*" read-only="true" /> </tx:attributes> </tx:advice>