Spring 中的 JDBC 事务

Spring 对 JDBC 的支持

JdbcTemplate 简介

•为了使 JDBC 更加易于使用, Spring 在 JDBC API 上定义了一个抽象层, 以此建立一个 JDBC 存取框架.

•作为 Spring JDBC 框架的核心, JDBC 模板的设计目的是为不同类型的 JDBC 操作提供模板方法. 每个模板方法都能控制整个过程, 并允许覆盖过程中的特定任务. 通过这种方式, 可以在尽可能保留灵活性的情况下, 将数据库存取的工作量降到最低.

使用 JdbcTemplate 更新数据库

 1     <!-- 导入资源文件 -->
 2     <context:property-placeholder location="classpath:db.properties"/>
 3     
 4     <!-- 配置 C3P0 数据源 -->
 5     <bean id="dataSource"
 6         class="com.mchange.v2.c3p0.ComboPooledDataSource">
 7         <property name="user" value="${jdbc.user}"></property>
 8         <property name="password" value="${jdbc.password}"></property>
 9         <property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property>
10         <property name="driverClass" value="${jdbc.driverClass}"></property>
11 
12         <property name="initialPoolSize" value="${jdbc.initPoolSize}"></property>
13         <property name="maxPoolSize" value="${jdbc.maxPoolSize}"></property>
14     </bean>
15     
16     <!-- 配置 Spirng 的 JdbcTemplate -->
17     <bean id="jdbcTemplate" 
18         class="org.springframework.jdbc.core.JdbcTemplate">
19         <property name="dataSource" ref="dataSource"></property>
20     </bean>
applicationContext.xml
 1     /**
 2      * 获取单个列的值, 或做统计查询
 3      * 使用 queryForObject(String sql, Class<Long> requiredType) 
 4      */
 5     @Test
 6     public void testQueryForObject2(){
 7         String sql = "SELECT count(id) FROM employees";
 8         long count = jdbcTemplate.queryForObject(sql, Long.class);
 9         
10         System.out.println(count);
11     }
12     
13     /**
14      * 查到实体类的集合
15      * 注意调用的不是 queryForList 方法
16      */
17     @Test
18     public void testQueryForList(){
19         String sql = "SELECT id, last_name lastName, email FROM employees WHERE id > ?";
20         RowMapper<Employee> rowMapper = new BeanPropertyRowMapper<>(Employee.class);
21         List<Employee> employees = jdbcTemplate.query(sql, rowMapper,5);
22         
23         System.out.println(employees);
24     }
25     
26     /**
27      * 从数据库中获取一条记录, 实际得到对应的一个对象
28      * 注意不是调用 queryForObject(String sql, Class<Employee> requiredType, Object... args) 方法!
29      * 而需要调用 queryForObject(String sql, RowMapper<Employee> rowMapper, Object... args)
30      * 1. 其中的 RowMapper 指定如何去映射结果集的行, 常用的实现类为 BeanPropertyRowMapper
31      * 2. 使用 SQL 中列的别名完成列名和类的属性名的映射. 例如 last_name lastName
32      * 3. 不支持级联属性. JdbcTemplate 到底是一个 JDBC 的小工具, 而不是 ORM 框架
33      */
34     @Test
35     public void testQueryForObject(){
36         String sql = "SELECT id, last_name lastName, email, dept_id as \"department.id\" FROM employees WHERE id = ?";
37         RowMapper<Employee> rowMapper = new BeanPropertyRowMapper<>(Employee.class);
38         Employee employee = jdbcTemplate.queryForObject(sql, rowMapper, 1);
39         
40         System.out.println(employee);
41     }
42     
43     /**
44      * 执行批量更新: 批量的 INSERT, UPDATE, DELETE
45      * 最后一个参数是 Object[] 的 List 类型: 因为修改一条记录需要一个 Object 的数组, 那么多条不就需要多个 Object 的数组吗
46      */
47     @Test
48     public void testBatchUpdate(){
49         String sql = "INSERT INTO employees(last_name, email, dept_id) VALUES(?,?,?)";
50         
51         List<Object[]> batchArgs = new ArrayList<>();
52         
53         batchArgs.add(new Object[]{"AA", "aa@atguigu.com", 1});
54         batchArgs.add(new Object[]{"BB", "bb@atguigu.com", 2});
55         batchArgs.add(new Object[]{"CC", "cc@atguigu.com", 3});
56         batchArgs.add(new Object[]{"DD", "dd@atguigu.com", 3});
57         batchArgs.add(new Object[]{"EE", "ee@atguigu.com", 2});
58         
59         jdbcTemplate.batchUpdate(sql, batchArgs);
60     }
61     
62     /**
63      * 执行 INSERT, UPDATE, DELETE
64      */
65     @Test
66     public void testUpdate(){
67         String sql = "UPDATE employees SET last_name = ? WHERE id = ?";
68         jdbcTemplate.update(sql, "Jack", 5);
69     }
JDBCTest

简化 JDBC 模板查询

•每次使用都创建一个 JdbcTemplate 的新实例, 这种做法效率很低下.
•JdbcTemplate 类被设计成为线程安全的, 所以可以再 IOC 容器中声明它的单个实例, 并将这个实例注入到所有的 DAO 实例中.
 1 @Repository
 2 public class EmployeeDao {
 3     
 4     @Autowired
 5     private JdbcTemplate jdbcTemplate;
 6     
 7     public Employee get(Integer id){
 8         String sql = "SELECT id, last_name lastName, email FROM employees WHERE id = ?";
 9         RowMapper<Employee> rowMapper = new BeanPropertyRowMapper<>(Employee.class);
10         Employee employee = jdbcTemplate.queryForObject(sql, rowMapper, id);
11         
12         return employee;
13     }
14 }
EmployeeDao
•Spring JDBC 框架还提供了一个 JdbcDaoSupport 类来简化 DAO 实现. 该类声明了 jdbcTemplate 属性, 它可以从 IOC 容器中注入, 或者自动从数据源中创建.
 1 @Repository
 2 public class DepartmentDao extends JdbcDaoSupport{
 3 
 4     @Autowired
 5     public void setDataSource2(DataSource dataSource){
 6         setDataSource(dataSource);
 7     }
 8 
 9     public Department get(Integer id){
10         String sql = "SELECT id, dept_name name FROM departments WHERE id = ?";
11         RowMapper<Department> rowMapper = new BeanPropertyRowMapper<>(Department.class);
12         return getJdbcTemplate().queryForObject(sql, rowMapper, id);
13     }
14     
15 }
DepartmentDao

在 JDBC 模板中使用具名参数

•在 Spring JDBC 框架中, 绑定 SQL 参数的另一种选择是使用具名参数(named parameter).
•具名参数: SQL 按名称(以冒号开头)而不是按位置进行指定. 具名参数更易于维护, 也提升了可读性. 具名参数由框架类在运行时用占位符取代
•具名参数只在 NamedParameterJdbcTemplate 中得到支持
	<!-- 配置 NamedParameterJdbcTemplate, 该对象可以使用具名参数, 其没有无参数的构造器, 所以必须为其构造器指定参数 -->
	<bean id="namedParameterJdbcTemplate"
		class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate">
		<constructor-arg ref="dataSource"></constructor-arg>	
	</bean>
 1     /**
 2      * 使用具名参数时, 可以使用 update(String sql, SqlParameterSource paramSource) 方法进行更新操作
 3      * 1. SQL 语句中的参数名和类的属性一致!
 4      * 2. 使用 SqlParameterSource 的 BeanPropertySqlParameterSource 实现类作为参数. 
 5      */
 6     @Test
 7     public void testNamedParameterJdbcTemplate2(){
 8         String sql = "INSERT INTO employees(last_name, email, dept_id) "
 9                 + "VALUES(:lastName,:email,:dpetId)";
10         
11         Employee employee = new Employee();
12         employee.setLastName("XYZ");
13         employee.setEmail("xyz@sina.com");
14         employee.setDpetId(3);
15         
16         SqlParameterSource paramSource = new BeanPropertySqlParameterSource(employee);
17         namedParameterJdbcTemplate.update(sql, paramSource);
18     }
19     
20     /**
21      * 可以为参数起名字. 
22      * 1. 好处: 若有多个参数, 则不用再去对应位置, 直接对应参数名, 便于维护
23      * 2. 缺点: 较为麻烦. 
24      */
25     @Test
26     public void testNamedParameterJdbcTemplate(){
27         String sql = "INSERT INTO employees(last_name, email, dept_id) VALUES(:ln,:email,:deptid)";
28         
29         Map<String, Object> paramMap = new HashMap<>();
30         paramMap.put("ln", "FF");
31         paramMap.put("email", "ff@atguigu.com");
32         paramMap.put("deptid", 2);
33         
34         namedParameterJdbcTemplate.update(sql, paramMap);
35     }
TestNamedParameterJdbctemplate

Spring  中的事务管理

•作为企业级应用程序框架, Spring 在不同的事务管理 API 之上定义了一个抽象层. 而应用程序开发人员不必了解底层的事务管理 API, 就可以使用 Spring 的事务管理机制.
•Spring 既支持编程式事务管理, 也支持声明式的事务管理.
编程式事务管理: 将事务管理代码嵌入到业务方法中来控制事务的提交和回滚. 在编程式管理事务时, 必须在每个事务操作中包含额外的事务管理代码.
声明式事务管理: 大多数情况下比编程式事务管理更好用. 它将事务管理代码从业务方法中分离出来, 以声明的方式来实现事务管理. 事务管理作为一种横切关注点, 可以通过 AOP 方法模块化. Spring 通过 Spring AOP 框架支持声明式事务管理.

用事务通知声明式地管理事务

•事务管理是一种横切关注点
•为了在 Spring 2.x 中启用声明式事务管理, 可以通过 tx Schema 中定义的 <tx:advice> 元素声明事务通知, 为此必须事先将这个 Schema 定义添加到 <beans> 根元素中去.
•声明了事务通知后, 就需要将它与切入点关联起来. 由于事务通知是在 <aop:config> 元素外部声明的, 所以它无法直接与切入点产生关联. 所以必须在 <aop:config> 元素中声明一个增强器通知与切入点关联起来.
 1     <!-- 配置 bean -->
 2     <bean id="bookShopDao" class="com.atguigu.spring.tx.xml.BookShopDaoImpl">
 3         <property name="jdbcTemplate" ref="jdbcTemplate"></property>
 4     </bean>
 5     
 6     <bean id="bookShopService" class="com.atguigu.spring.tx.xml.service.impl.BookShopServiceImpl">
 7         <property name="bookShopDao" ref="bookShopDao"></property>
 8     </bean>
 9     
10     <bean id="cashier" class="com.atguigu.spring.tx.xml.service.impl.CashierImpl">
11         <property name="bookShopService" ref="bookShopService"></property>
12     </bean>
13     
14     <!-- 1. 配置事务管理器 -->
15     <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
16         <property name="dataSource" ref="dataSource"></property>
17     </bean>
18     
19     <!-- 2. 配置事务属性 -->
20     <tx:advice id="txAdvice" transaction-manager="transactionManager">
21         <tx:attributes>
22             <!-- 根据方法名指定事务的属性 -->
23             <tx:method name="purchase" propagation="REQUIRES_NEW"/>
24             <tx:method name="get*" read-only="true"/>
25             <tx:method name="find*" read-only="true"/>
26             <tx:method name="*"/>
27         </tx:attributes>
28     </tx:advice>
29     
30     <!-- 3. 配置事务切入点, 以及把事务切入点和事务属性关联起来 -->
31     <aop:config>
32         <aop:pointcut expression="execution(* com.atguigu.spring.tx.xml.service.*.*(..))" 
33             id="txPointCut"/>
34         <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>    
35     </aop:config>
config

用 @Transactional 注解声明式地管理事务

•为了将方法定义为支持事务处理的, 可以为方法添加 @Transactional 注解. 根据 Spring AOP 基于代理机制, 只能标注公有方法.

•可以在方法或者类级别上添加 @Transactional 注解. 当把这个注解应用到类上时, 这个类中的所有公共方法都会被定义成支持事务处理的.

•在 Bean 配置文件中只需要启用 <tx:annotation-driven> 元素, 并为之指定事务管理器就可以了.

如果事务处理器的名称是 transactionManager, 就可以在<tx:annotation-driven> 元素中省略 transaction-manager 属性. 这个元素会自动检测该名称的事务处理器.

	<!-- 配置事务管理器 -->
	<bean id="transactionManager" 
		class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<property name="dataSource" ref="dataSource"></property>
	</bean>
	
	<!-- 启用事务注解 -->
	<tx:annotation-driven transaction-manager="transactionManager"/>
	//添加事务注解
	//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)
	@Override
	public void purchase(String username, String isbn) {
		//1. 获取书的单价
		int price = bookShopDao.findBookPriceByIsbn(isbn);
		//2. 更新数的库存
		bookShopDao.updateBookStock(isbn);
		//3. 更新用户余额
		bookShopDao.updateUserAccount(username, price);
	}
}

 事务传播属性

•当事务方法被另一个事务方法调用时, 必须指定事务应该如何传播. 例如: 方法可能继续在现有事务中运行, 也可能开启一个新事务, 并在自己的事务中运行.

•事务的传播行为可以由传播属性指定. Spring 定义了 7  种类传播行为.第一和第二种最常用.

  
posted @ 2016-09-04 11:19  岳灵珊  阅读(276)  评论(0编辑  收藏  举报