在web开发中,免不了要与数据库打交道,一旦与数据库联系上了就免不了要写事务,保证业务逻辑的正常运行。在以前写事务,我们需要自己通过connection开启事务setAutoCommit(false),然后正常则提交commit,异常就回滚rollback,通过事务能够保证数据的一致性,如果有许多业务都需要开启事务,这样就会出现许多重复的代码,使得代码臃肿,编写也比较繁琐,因此spring为我们提供了一个声明式事务,即我们不用再自己编写事务,只需要在配置文件中配置一事务管理器,然后通过Spring的aop对需要开启事务的业务进行动态插入,提高了代码的灵活性,降低了代码量。
在进行声明式事务的操作之前,需要先了解下spring是如何对数据库操作的,在spring中也对jdbc进行了封装,也就是jdbctemplate,其api的使用方式与dbutils的极其相似。
要使用JdbcTemplate,除了spring的基本包和数据库连接池的包,还需要导入以下jar包:
spring-jdbc-4.2.4.RELEASE.jar
spring-tx-4.2.4.RELEASE.jar
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 findAll(){
String sql ="select * from account";
//与dbutils类似,在dbutils使用new beanListHandler<>().这里使用new BeanPropertyRowMapper<>()
List list = jdbcTemplate.query(sql, new BeanPropertyRowMapper(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.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();
}
}
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 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 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的事务管理器,这是一个接口,接口中提供了事务操作的基本方法
commit()——事务提交
rollBack()——事务回滚
PlatformTransactionManager的具体实现类
DataSourceTransactionManager
如果是使用Spring的Jdbc方式就使用该类
HibernateTransactionManager
如果使用的是整合了Hibernate的jdbc就使用这个类
获取事务参数的一个类
获取事务的隔离级别
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有事务也不使用,直接再创建一个新的事务。
获取事务是否只读
只读—只有在查询时才会开启事务
非只读—所有操作都开启事务
获取事务的名称
获取事务的超时时间
这是一个获取事务状态的接口
获取事务是否是新事务
获取事务是否回滚
获取事务是否完成
在spring中要使用声明式事务需要有aop的支持,因此要需要导入aop相关的jar包,同样还需要导入:
spring-jdbc-4.2.4.RELEASE.jar
spring-tx-4.2.4.RELEASE.jar
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);
}
}
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方式中,配置文件的配置比较复杂,但是如果使用注解,一切就会变得非常简便。
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);
}
}
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);
}
}