Spring(五) JdbcTemplate基本使用与事务控制
1. Spring JdbcTemplate基本使用
1.1 JdbcTemplate概述
它是spring框架中提供的一个对象,是对原始繁琐的Jdbc API对象的简单封装。spring框架为我们提供了很多的操作模板类
例如:操作关系型数据的JdbcTemplate和HibernateTemplate,操作nosql数据库的RedisTemplate操 作消息队列的JmsTemplate等等。
1.2 JdbcTemplate开发步骤/快速入门
1.2.1 导入spring-jdbc和spring-tx坐标
1 | <dependency> |
1.2.2 创建数据库表和实体
运行SQL脚本 和 Generate POJOs.groovy即可
1.2.3 创建JdbcTemplate对象
1 | //1、创建数据源对象 |
1.2.4执行数据库操作
1 | String sql = "insert into account values(?,?)"; |
1.3 Spring产生JdbcTemplate对象
我们可以将JdbcTemplate的创建权交给Spring
将数据源DataSource的创建权也交给Spring
注意:applicationContext.xml加载jdbc.properties配置文件获得连接信息时别忘了 引入context命名空间和约束路径
在Spring容器内部将 数据源DataSource注入到JdbcTemplate模版对象中,配置如下:
1 | <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> |
1 | ApplicationContext app =new ClassPathXmlApplicationContext("applicationContext.xml"); |
1.4 JdbcTemplate的常用操作
执行DML语句。增、删、改语句
修改
1
2
3String sql = "update emp set salary = 10000 where id = 1001";
int count = template.update(sql)增加
1
2
3String sql = "insert into emp(id,ename,dept_id) values(?,?,?)";
int count = template.update(sql, 1015, "郭靖", 10);删除
1
2
3String sql = "delete from emp where id = ?";
int count = template.update(sql, 1015);
查询结果将结果集封装为map集合
将列名作为 key,将值作为value
将这条记录封装为一个map 集合
注意:这个方法查询的结果集长度只能是1
1 | String sql = "select * from emp where id = ? or id = ?"; |
查询结果将结果集封装为list集合
注意:将每一条记录封装为一个Map集合,再将 Map集合装载到List集合中
1 | String sql = "select * from emp"; |
查询结果,将结果封装为JavaBean对象 封装给对象
用封装类 不然 数值类型 不能被赋null
new BeanPropertyRowMapper<类型>(类型. class)
1
2
3String sql = "select * from emp";
List<Emp> list = template.query(sql, new BeanPropertyRowMapper<Emp>(Emp.class));
for (Emp emp : list) { System.out.println(emp); }new RowMapper<类型>
我们使用BeanPropertyRowMapper实现类
可 以完成数据到JavaBean的自动封装
不推荐)手动 设置 要接收的值的set与get
1
2
3
4
5
6
7
8
9
10
11
12
13
14String sql = "select * from emp";
List<Emp> list = template.query(sql, new
RowMapper<Emp>() {
public Emp mapRow(ResultSet rs, int i) throws
SQLException {
Emp emp = new Emp();
int id = rs.getInt("id");
String ename = rs.getString("ename");
emp.setId(id);
emp.setEname(ename);
return emp; }
}
);
聚合函数的查询 查询有多少记录
还可以用主键代替要用主键
1
2String sql = "select count(*) from emp";
Long total = template.queryForObject(sql,Long.class);返回对象
1
2Book book = jdbcTemplate.queryForObject(
sql, new BeanPropertyRowMapper<Book>(Book.class), id);
2. 事务控制
2.1 编程式事务控制相关对象
2.1.1 PlatformTransactionManager 事务管理器
PlatformTransactionManager 接口是 spring 的事务管理器
它里面提供了我们常用的操作事务的方法
| 方法 | 说明 |
|---|---|
| TransactionStatus getTransaction(TransactionDefination defination) | 获取事务的状态信息 |
| void commit(TransactionStatus status) | 提交事务 |
| void rollback(TransactionStatus status) | 回滚事务 |
注意:
PlatformTransactionManager 是接口类型,不同的 Dao 层技术则有不同的实现类
例如:Dao 层技术是jdbc 或 mybatis 时:org.springframework.jdbc.datasource.DataSourceTransactionManager
Dao 层技术是hibernate时:
org.springframework.orm.hibernate5.HibernateTransactionManager
2.1.2 TransactionDefinition 事务的定义信息对象
TransactionDefinition 是事务的定义信息对象,里面有如下方法:
| 方法 | 说明 |
|---|---|
| int getIsolationLevel() | 获得事务的隔离级别 |
| int getPropogationBehavior() | 获得事务的传播行为 |
| int getTimeout() | 获得超时时间 |
| boolean isReadOnly() | 是否只读 |
事务隔离级别
设置隔离级别,可以解决事务并发产生的问题,如脏读、不可重复读和虚读
数据库是要被广大客户所共享访问的,那么在数据库操作过程中很可能出现以下几种不确定情况。
两个事务都同时更新一行数据,一个事务对数据的更新把另一个事务对数据的更新覆盖了
比如 CMS系统中,两个同时打开一篇文章进行修改,一个人先保存,另一个人后保存,后保存的就覆盖了先保存的那个人的内容,这就造成更新丢失
这是因为系统没有执行任何的锁操作,因此并发事务并没有被隔离开来
在并发事务处理带来的问题中,“更新丢失”通常应该是完全避免的
但防止更新丢失,并不能单靠数据库事务控制器来解决,需要应用程序对要更新的数据加必要的锁来解决,因此,防止更新丢失应该是应用的责任。
一个事务读取到了另一个事务未提交的数据操作结果
这是相当危险的,因为很可能所有的操作都被回滚。
一个事务对同一行数据重复读取两次,但是却得到了不同的结果
比如事务T1读取某一数据后,事务T2对其做了修改,当事务T1再次读该数据时得到与前一次不同的值。又叫虚读。
事务在操作过程中进行两次查询,第二次查询的结果包含了第一次查询中未出现的数据或者缺少了第一次查询中出现的数据(这里并不要求两次查询的SQL语句相同)
这是因为在两次查询过程中有另外一个事务插入数据造成的。
不可重复读的重点是修改某个记录字段,幻读的重点在于新增或者删除记录。
对于前者, 只需要锁住满足条件的记录。对于后者, 要锁住满足条件及其相近的记录。
“脏读”、“不可重复读”和“幻读”,其实都是数据库读一致性问题,必须由数据库提供一定的事务隔离机制来解决。
| 隔离级别 | 读数据一致性 | 脏读 | 不可重复读 | 幻读 |
|---|---|---|---|---|
| ISOLATION_DEFAULT(默认) | 默认 | 默认 | 默认 | 默认 |
| ISOLATION_READ_UNCOMMITTED(未提交读) | 最低级别,只能保证不读取物理上损坏的数据 | √ | √ | √ |
| ISOLATION_READ_COMMITTED(已提交读) | 语句级 | × | √ | √ |
| ISOLATION_REPEATABLE_READ(可重复读) | 事务级 | × | × | √ |
| ISOLATION_SERIALIZABLE(可序列化) | 最高级别,事务级 | × | × | × |
Spring中一个标识:ISOLATION_DEFAULT
表示使用后端数据库默认的隔离级别
大多数数据库默认的事务隔离级别是Read committed,比如Sql Server , Oracle
MySQL的默认隔离级别是Repeatable read
Spring标识:ISOLATION_READ_UNCOMMITTED
允许脏读取,但不允许更新丢失
如果一个事务已经开始写数据,则另外一个事务则不允许同时进行写操作,但允许其他事务读此行数据。
该隔离级别可以通过“排他写锁”实现。
Spring标识:ISOLATION_READ_COMMITTED
允许不可重复读取,但不允许脏读取
这可以通过“瞬间共享读锁”和“排他写锁”实现
读取数据的事务允许其他事务继续访问该行数据,但是未提交的写事务将会禁止其他事务访问该行。
Spring标识:ISOLATION_REPEATABLE_READ
禁止不可重复读取和脏读取,但是有时可能出现幻读数据
这可以通过“共享读锁”和“排他写锁”实现
读取数据的事务将会禁止写事务(但允许读事务),写事务则禁止任何其他事务。
Spring标识:ISOLATION_SERIALIZABLE
提供严格的事务隔离
它要求事务序列化执行,事务只能一个接着一个地执行,不能并发执行
仅仅通过“行级锁”是无法实现事务序列化的,必须通过其他机制保证新插入的数据不会被刚执行查询操作的事务访问到
隔离级别越高,越能保证数据的完整性和一致性,但是对并发性能的影响也越大
对于多数应用程序,可以优先考虑把数据库系统的隔离级别设为Read Committed
它能够避免脏读取,而且具有较好的并发性能
尽管它会导致不可重复读、幻读和第二类丢失更新这些并发问题,在可能出现这类问题的个别场合,可以由应用程序采用悲观锁或乐观锁来控制
事务传播行为
| 名词 | 解释 |
|---|---|
REQUIRED |
如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。一般的选择(默认值) |
SUPPORTS |
支持当前事务,如果当前没有事务,就以非事务方式执行(没有事务) |
| MANDATORY | 使用当前的事务,如果当前没有事务,就抛出异常 |
| REQUERS_NEW | 新建事务,如果当前在事务中,把当前事务挂起 |
| NOT_SUPPORTED | 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起 |
| NEVER | 以非事务方式运行,如果当前存在事务,抛出异常 |
| NESTED | 如果当前存在事务,则在嵌套事务内执行如果当前没有事务,则执行 REQUIRED 类似的操作 |
| 超时时间 | 默认值是-1,没有超时限制。如果有,以秒为单位进行设置 |
| 是否只读 | 建议查询时设置为只读 |
2.1.3 TransactionStatus 事务具体的运行状态
TransactionStatus 接口提供的是事务具体的运行状态,方法介绍如下
| 方法 | 说明 |
|---|---|
| boolean hasSavepoint() | 是否存储回滚点 |
| boolean isCompleted() | 事务是否完成 |
| boolean isNewTransaction() | 是否是新事务 |
| boolean isRollbackOnly() | 事务是否回滚 |
2.2 基于 XML 的声明式事务控制
2.2.1 什么是声明式事务控
Spring 的声明式事务顾名思义就是采用声明的方式来处理事务
这里所说的声明,就是指在配置文件中声明 ,用在 Spring 配置文件中声明式的处理事务来代替代码式的处理事务
声明式事务处理的作用
事务管理不侵入开发的组件
具体来说,业务逻辑对象就不会意识到正在事务管理之中,事实上也应该如此,因为事务管理是属于系统层面的服务,而不是业务逻辑的一部分,如果想要改变事务管理策划的话, 也只需要在定义文件中重新配置即可
在不需要事务管理的时候,只要在设定文件上修改一下,即可移去事务管理服务,无需改变代码重新编译 ,这样维护起来极其方便
注意:Spring 声明式事务控制底层就是AOP
2.2.2 声明式事务控制的实现
声明式事务控制明确事项:
- 谁是切点?
- 谁是通知?
- 配置切面?
引入tx命名空间
1
2
3xmlns:aop="http://www.springframework.org/schema/tx"
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd配置事务增强
1
2
3
4
5
6
7
8
9
10
11
12<!--平台事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--事务增强配置-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<!--设置事务的信息-->
<tx:attributes>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>其中, < tx:method >代表切点方法的事务参数的配置,例如:
<tx:method name=”transfer” isolation=”REPEATABLE_READ” propagation=”REQUIRED” timeout=”-1” read-only=”false”/>
- name:切点方法名称
- isolation:事务的隔离级别
- propogation:事务的传播行为
- timeout:超时时间
- read-only:是否只读
配置事务 AOP 织入
1
2
3
4
5
6<!--事务的aop增强-->
<aop:config>
<aop:pointcut id="myPointcut" expression="execution(*
cn.jyw.service.impl.*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="myPointcut"/>
</aop:config>
2.3 使用注解配置声明式事务控制
2.3.1 使用注解配置声明式事务控制
编写 AccoutDao
1
2
3
4
5
6
7
8
9
10
11
public class AccountDaoImpl implements AccountDao {
private JdbcTemplate jdbcTemplate;
public void out(String outMan, double money) {
jdbcTemplate.update("update account set money=money-? where name=?",money,outMan);
}
public void in(String inMan, double money) {
jdbcTemplate.update("update account set money=money+? where name=?",money,inMan);
}
}编写 AccoutService
1
2
3
4
5
6
7
8
9
10
11
12
public class AccountServiceImpl implements AccountService {
private AccountDao accountDao;
public void transfer(String outMan, String inMan, double money) {
accountDao.out(outMan,money);
int i = 1/0;
accountDao.in(inMan,money);
}
}编写 applicationContext.xml 配置文件
1
2
3
4
5<!—之前省略datsSource、jdbcTemplate、平台事务管理器的配置-->
<!--组件扫描-->
<context:component-scan base-package="cn.jyw"/>
<!--事务的注解驱动-->
<tx:annotation-driven/>
2.3.2 注解配置声明式事务控制解析
- 使用 @Transactional 在需要进行事务控制的类或是方法上修饰,注解可用的属性同 xml 配置方式,例如隔离 级别、传播行为等
- 注解使用在类上,那么该类下的所有方法都使用同一套注解参数配置
- 使用在方法上,不同的方法可以采用不同的事务参数配置
- Xml配置文件中要开启事务的注解驱动<tx:annotation-driven />

