Spring学习笔记四(声明式事务)

Spring的声明式事务

在web开发中,免不了要与数据库打交道,一旦与数据库联系上了就免不了要写事务,保证业务逻辑的正常运行。在以前写事务,我们需要自己通过connection开启事务setAutoCommit(false),然后正常则提交commit,异常就回滚rollback,通过事务能够保证数据的一致性,如果有许多业务都需要开启事务,这样就会出现许多重复的代码,使得代码臃肿,编写也比较繁琐,因此spring为我们提供了一个声明式事务,即我们不用再自己编写事务,只需要在配置文件中配置一事务管理器,然后通过Spring的aop对需要开启事务的业务进行动态插入,提高了代码的灵活性,降低了代码量。

JdbcTemplate的使用

在进行声明式事务的操作之前,需要先了解下spring是如何对数据库操作的,在spring中也对jdbc进行了封装,也就是jdbctemplate,其api的使用方式与dbutils的极其相似。
在这里插入图片描述

要使用JdbcTemplate,除了spring的基本包和数据库连接池的包,还需要导入以下jar包:
spring-jdbc-4.2.4.RELEASE.jar
spring-tx-4.2.4.RELEASE.jar

  • 配置文件示例
<?xml version="1.0" encoding="UTF-8"?>
<!-- beans的约束文件 -->
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:p="http://www.springframework.org/schema/p"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/tx 
        http://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop 
        http://www.springframework.org/schema/aop/spring-aop.xsd">
        
        <!-- 加载数据库连接池的配置文件 -->
        <context:property-placeholder location="classpath:jdbc.properties"/>
        
        <!-- 配置C3p0 -->
        <bean id="c3p0" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        	<property name="driverClass" value="${driverClass}"></property>
        	<property name="jdbcUrl" value="${jdbcUrl}"></property>
        	<property name="user" value="${user}"></property>
        	<property name="password" value="${password}"></property>
        </bean>
        
        <!-- 配置jdbcTemplate的bean,因为创建时需要传入连接池,并且在其类中有setDateSource()方法
        		这里可以使用set方式依赖注入c3p0连接池
         -->
        <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        	<property name="dataSource" ref="c3p0"></property>
        </bean> 

        <!-- 配置AccountDao的bean,用set的方式依赖注入jdbcTemplate对象 -->
        <bean id="accountDao" class="com.wzm.dao.AccountDao">
        	<property name="jdbcTemplate" ref="jdbcTemplate"></property>
        </bean>
        
        <!-- 配置AccountService,用set方式依赖注入AccountDao对象 -->
        <bean id="accountService" class="com.wzm.service.AccountService">
        	<property name="accountDao" ref="accountDao"></property>
        </bean>
</beans>
  • Dao层示例
package com.wzm.dao;

import java.util.List;

import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import com.wzm.entity.Account;

/*
 * Dao层
 */
public class AccountDao{
	
	//定义JdbcTemplate对象和其set方法,然后使用依赖注入获取对象
	private JdbcTemplate jdbcTemplate;
	public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
		this.jdbcTemplate = jdbcTemplate;
	}

	//保存数据
	public void save() {
		String sql = "insert into account values(null,?,?)";
		//增删改的使用方式与dbutils类似都是使用update方法,
		//第一个参数为sql语句,后边的参数为占位符对应的参数
		jdbcTemplate.update(sql, "jerry",1000);
	}
	//删除数据
	public void delete() {
		String sql = "delete from account where name=?";
		jdbcTemplate.update(sql, "jerry");
	}
	//更新数据
	public void update() {
		String sql = "update account set money=500 where name=?";
		jdbcTemplate.update(sql,"jerry");
	}
	//查询所有
	public List<Account> findAll(){
		String sql ="select * from account";
		//与dbutils类似,在dbutils使用new beanListHandler<>().这里使用new BeanPropertyRowMapper<>()
		List<Account> list = jdbcTemplate.query(sql, new BeanPropertyRowMapper<Account>(Account.class));
		return list;
	}
	//查询单个对象
	public Account findOne(){
		String sql ="select * from account where money=?";
		//查询单个对象还是用new BeanPropertyRowMapper<>(),只不过方法不是用query,而是queryForObject
		Account account = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<Account>(Account.class),500);
		return account;
	}
	//聚合查询
	public Integer getCount() {
		String sql ="select count(*) from account";
		//查询总记录数,使用queryForObject,第二个参数为返回的类型class对象
		Long count = jdbcTemplate.queryForObject(sql, Long.class);
		return count.intValue();
	}
}

  • Service层示例
package com.wzm.service;

import java.util.List;

import com.wzm.dao.AccountDao;
import com.wzm.entity.Account;

/*
 * service层
 */
public class AccountService {
	//定义AccountDao对象和set方法,使用依赖注入
	private AccountDao accountDao;
	public void setAccountDao(AccountDao accountDao) {
		this.accountDao = accountDao;
	}
	//保存
	public void save() {
		accountDao.save();
	}
	//删除
	public void delete() {
		accountDao.delete();
	}
	//更新
	public void update() {
		accountDao.update();
	}
	//查询所有
	public List<Account> findAll() {
		return accountDao.findAll();
	}
	//查询单个
	public Account findOne() {
		return accountDao.findOne();
	}
	//聚合查询
	public Integer getCount() {
		return accountDao.getCount();
	}
}

  • 测试示例
package com.wzm.test;

import java.util.List;

import javax.annotation.Resource;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import com.wzm.entity.Account;
import com.wzm.service.AccountService;
import com.wzm.service.AccountServiceImpl;

/*
 * 使用整合junit的方式做测试
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(value="classpath:applicationContext.xml")
public class TxTest {
	
	//注解方式注入AccountService
	@Resource(name="accountService")
	private AccountService accountService;

	@Resource(name="accountServiceImpl")
	private AccountServiceImpl accountServiceImpl;
	
	@Test
	public void test1() {
		accountService.save();
	}
	
	@Test
	public void test2() {
		accountService.delete();
	}
	
	@Test
	public void test3() {
		accountService.update();
	}
	
	@Test
	public void test4() {
		List<Account> list = accountService.findAll();
		for (Account account : list) {
			System.out.println(account);
		}
	}
	
	@Test
	public void test5() {
		Account account = accountService.findOne();
		System.out.println(account);
	}
	
	@Test 
	public void test6() {
		Integer count = accountService.getCount();
		System.out.println(count);
	}
}

#声明式事务的使用-xml方式

##Spring的事务控制API

spring的声明式事务原理上都是基本编码式的事务,因此需要先了解下spring关于事务控制的API。

  • Spring的事务控制API
  1. PlatformTransactionManager

Spring的事务管理器,这是一个接口,接口中提供了事务操作的基本方法
commit()——事务提交
rollBack()——事务回滚
PlatformTransactionManager的具体实现类
DataSourceTransactionManager
如果是使用Spring的Jdbc方式就使用该类
HibernateTransactionManager
如果使用的是整合了Hibernate的jdbc就使用这个类

  1. TransactionDefinition

获取事务参数的一个类
获取事务的隔离级别
Read_uncommitted——读未提交
Read_commited——读已提交 Oracle默认级别,解决脏读问题
Repeatable_read——可重复读 MySQL默认级别,解决脏读+不可重复读问题
Serializable——可序列读 最高级别,解决脏读+不可重复读+幻读问题
获取事务的传播方式
假设a方法调用b方法,b方法有事务
REQUIRED—如果a方法有事务,就直接使用a的事务,如果没事务,就为a创建事务
SUPPORTS—如果a方法有事务,就使用a的事务,如果没事务,就使用无事务方式
MANDATORY—a方法有事务就直接用,没有就抛出异常
REQUERS_NEW—a方法没有事务就创建事务,如果a有事务也不使用,直接再创建一个新的事务。
获取事务是否只读
只读—只有在查询时才会开启事务
非只读—所有操作都开启事务
获取事务的名称
获取事务的超时时间

  1. TransactionStatus

这是一个获取事务状态的接口
获取事务是否是新事务
获取事务是否回滚
获取事务是否完成

在spring中要使用声明式事务需要有aop的支持,因此要需要导入aop相关的jar包,同样还需要导入:
spring-jdbc-4.2.4.RELEASE.jar
spring-tx-4.2.4.RELEASE.jar

  • 配置文件示例
<?xml version="1.0" encoding="UTF-8"?>
<!-- beans的约束文件 -->
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:p="http://www.springframework.org/schema/p"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/tx 
        http://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop 
        http://www.springframework.org/schema/aop/spring-aop.xsd">
        
        <!-- 加载数据库连接池的配置文件 -->
        <context:property-placeholder location="classpath:jdbc.properties"/>
        
        <!-- 配置C3p0 -->
        <bean id="c3p0" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        	<property name="driverClass" value="${driverClass}"></property>
        	<property name="jdbcUrl" value="${jdbcUrl}"></property>
        	<property name="user" value="${user}"></property>
        	<property name="password" value="${password}"></property>
        </bean>
        
        <!-- 配置jdbcTemplate的bean,因为创建时需要传入连接池,并且在其类中有setDateSource()方法
        		这里可以使用set方式依赖注入c3p0连接池
         -->
        <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        	<property name="dataSource" ref="c3p0"></property>
        </bean> 
        
        <!-- 配置accountDaoImpl的bean,使用依赖注入jdbcTemplate对象 -->
        <bean id="accountDaoImpl" class="com.wzm.dao.AccountDaoImpl">
        	<property name="jdbcTemplate" ref="jdbcTemplate"></property>
        </bean>
        
        <!-- 配置accountServiceImpl的bean,使用依赖注入accountDaoImpl对象 -->
        <bean id="accountServiceImpl" class="com.wzm.service.AccountServiceImpl">
        	<property name="accountDaoImpl" ref="accountDaoImpl"></property>
        </bean>
        
        <!-- 配置spring的事务管理器的bean,作为一个切面类,执行事务开启或回滚操作
        		其类内部也需要传入数据库连接池,这里使用依赖注入c3p0
         -->
        <bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        	<property name="dataSource" ref="c3p0"></property>
        </bean>
        
		<!-- 配置声明式事务
				transaction-manager指定作为切面类的事务管理器
		 -->
        <tx:advice id="txAdvice" transaction-manager="dataSourceTransactionManager">
        	<tx:attributes>
        		<!-- tx:method设置要开启事务的方法及其事务的参数
        				name为目标类中的切入点的方法名
        				timeout超时时间,-1永不超时
        				read-only设置事务是否只读
        				propagation设置事务传播方式
        		 -->
        		<tx:method name="transfer" timeout="-1" read-only="false" propagation="REQUIRED"/>
        	</tx:attributes>
        </tx:advice>
        
        <!-- 声明式事务需要与AOP配合 -->
        <aop:config>
        	<!-- 设置事务的切入点 -->
        	<aop:pointcut expression="execution(* com.wzm.service.AccountServiceImpl.transfer(..))" id="transfer"/>
        	<!-- aop:advisor专门用来提供声明式事务的aop标签
        			advice-ref即tx:advice标签配置的id值
        			pointcut-ref即切入点的id值
        	 -->
        	<aop:advisor advice-ref="txAdvice" pointcut-ref="transfer"/>
        </aop:config>
</beans>
  • Dap层示例
package com.wzm.dao;

import org.springframework.jdbc.core.JdbcTemplate;

/*
 * Dao层
 */
public class AccountDaoImpl {
	
	//使用set方式依赖注入JdbcTemplate对象
	private JdbcTemplate jdbcTemplate;
	public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
		this.jdbcTemplate = jdbcTemplate;
	}

	//收款方加钱操作
	public void addMoney(String name, Double money) {
		String sql = "update account set money=money+? where name=?";
		jdbcTemplate.update(sql,money,name);
	}

	//转账方减钱操作
	public void reduceMoney(String name, Double money) {
		String sql = "update account set money=money-? where name=?";
		jdbcTemplate.update(sql,money,name);
	}
}

  • Service层示例
package com.wzm.service;

import com.wzm.dao.AccountDaoImpl;

/*
 * service层
 */
public class AccountServiceImpl {
	//使用set方式依赖注入AccountDaoImpl对象
	private AccountDaoImpl accountDaoImpl;
	public void setAccountDaoImpl(AccountDaoImpl accountDaoImpl) {
		this.accountDaoImpl = accountDaoImpl;
	}
	/*	事务的切入点
	 * fromname	转账方
	 * toname	收款方
	 * money	转账金额
	 */
	public void transfer(String fromname,String toname,Double money) {
		//转账方减钱
		accountDaoImpl.reduceMoney(fromname, money);
		//制造异常
		int i = 1/0;
		//收款方加钱
		accountDaoImpl.addMoney(toname, money);
	}
}

  • 测试
package com.wzm.test;

import java.util.List;

import javax.annotation.Resource;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import com.wzm.entity.Account;
import com.wzm.service.AccountService;
import com.wzm.service.AccountServiceImpl;

/*
 * 使用整合junit的方式做测试
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(value="classpath:applicationContext.xml")
public class TxTest {
	
	//注解方式注入AccountService
	@Resource(name="accountService")
	private AccountService accountService;

	@Resource(name="accountServiceImpl")
	private AccountServiceImpl accountServiceImpl;
	
	@Test
	public void test7() {
		accountServiceImpl.transfer("jerry", "tom", 500.0);
	}
}

  • 实体类示例
package com.wzm.entity;

/*
 * Account类
 */
public class Account {
	String name;
	Double money;
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public Double getMoney() {
		return money;
	}
	public void setMoney(Double money) {
		this.money = money;
	}
	@Override
	public String toString() {
		return "Account [name=" + name + ", money=" + money + "]";
	}
	
}

#声明式事务的使用-注解方式

在xml方式中,配置文件的配置比较复杂,但是如果使用注解,一切就会变得非常简便。

  • 配置文件示例
<?xml version="1.0" encoding="UTF-8"?>
<!-- beans的约束文件 -->
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:p="http://www.springframework.org/schema/p"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop 
        http://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/tx 
        http://www.springframework.org/schema/tx/spring-tx.xsd">
        
        
        <!-- 别人的类 -->
        
         <!-- 加载数据库连接池的配置文件 -->
        <context:property-placeholder location="classpath:jdbc.properties"/>
        <!-- 配置C3p0 -->
        <bean id="c3p0" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        	<property name="driverClass" value="${driverClass}"></property>
        	<property name="jdbcUrl" value="${jdbcUrl}"></property>
        	<property name="user" value="${user}"></property>
        	<property name="password" value="${password}"></property>
        </bean>
        
        <!-- 配置jdbcTemplate的bean,因为创建时需要传入连接池,并且在其类中有setDateSource()方法
        		这里可以使用set方式依赖注入c3p0连接池
         -->
        <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        	<property name="dataSource" ref="c3p0"></property>
        </bean> 
        
         <!-- 配置spring的事务管理器的bean,作为一个切面类,执行事务开启或回滚操作
        		其类内部也需要传入数据库连接池,这里使用依赖注入c3p0
         -->
        <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        	<property name="dataSource" ref="c3p0"></property>
        </bean>
        
        <!-- 自己的类 -->
        <!-- 注解扫描器 -->
        <context:component-scan base-package="com.wzm"></context:component-scan>
        <!-- 开启事务注解 
        		transaction-manager指定事务管理器
        -->
        <tx:annotation-driven transaction-manager="transactionManager"/>
        
</beans>
  • Dap层示例
package com.wzm.dao;

import javax.annotation.Resource;

import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

/*
 * Dao层 --注解方式
 */
@Repository("accountDaoImpl")
public class AccountDaoImpl {
	
	//使用set方式依赖注入JdbcTemplate对象
	@Resource(name="jdbcTemplate")
	private JdbcTemplate jdbcTemplate;

	//收款方加钱操作
	public void addMoney(String name, Double money) {
		String sql = "update account set money=money+? where name=?";
		jdbcTemplate.update(sql,money,name);
	}

	//转账方减钱操作
	public void reduceMoney(String name, Double money) {
		String sql = "update account set money=money-? where name=?";
		jdbcTemplate.update(sql,money,name);
	}
}

  • Service层示例
package com.wzm.service;

import javax.annotation.Resource;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import com.wzm.dao.AccountDaoImpl;

/*
 * service层——注解方式
 */
@Service("accountServiceImpl")
public class AccountServiceImpl {
	//使用set方式依赖注入AccountDaoImpl对象
	@Resource(name="accountDaoImpl")
	private AccountDaoImpl accountDaoImpl;
	
	/*	事务的切入点
	 * fromname	转账方
	 * toname	收款方
	 * money	转账金额
	 * @Transactional指明在这个方法上开启事务
	 * 	也可以定义在类上,对所有方法都开启事务
	 */
	
	@Transactional(propagation=Propagation.REQUIRED,timeout=-1,readOnly=false)
	public void transfer(String fromname,String toname,Double money) {
		//转账方减钱
		accountDaoImpl.reduceMoney(fromname, money);
		//制造异常
		int i = 1/0;
		//收款方加钱
		accountDaoImpl.addMoney(toname, money);
	}
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值