文章目录
Spring JDBC与事务操作一、Spring整合JDBC的环境(一)添加依赖坐标(二)添加jdbc.properties的配置(三)创建Spring的配置文件(四)配置数据源(五)JDBC测试
二、持久层账户模块操作(例)(一)账户接口方法定义1. 定义账户实体类2.定义账户的接口类3.定义接口的实现类
(二)、账户记录添加实现1. 添加账户记录2.添加账户记录返回主键3.批量添加账户,返回受影响的行数
(三)账户记录查询的实现1. 查询用户的账户记录总数2.查询指定账户记录详情3. 多条件查询用户账户记录
(四)账户记录的更新操作1.更新账户记录2.批量更新账户记录
(五)账户记录删除实现1.删除账户记录2.批量删除账户记录
三、事务的概念(一)事务的四大特性(ACID)1. 原子性(Atomicity)2. 一致性(Consistency)3.隔离性(Isolation)4. 持久性(Durability)
(二)Spring事务核心接口1. JDBC事务2. Hibernate事务3. Java持久化API事务(JPA)4. Java原生API事务
(三)Spring事务控制的配置1. XML配置(1)添加命名空间(2)设置AOP代理(3)配置事务管理器(4)配置事务的相关通知(5)配置AOP
2. 注解配置(1)配置事务管理器(2)配置注解支持(3)加入事务注解
(四)Spring事务控制——模拟转账的实现1.接口方法定义2.接口实现3.业务层方法4.测试
Spring JDBC与事务操作
一、Spring整合JDBC的环境
Spring框架除了提供了IOC和AOP的核心功能之外,同样提供了基于JDBC的数据访问功能,使得访问持久层数据更加方便。使用Spring JDBC环境,首先需要一套Spring整合JDBC的环境。
(一)添加依赖坐标
(二)添加jdbc.properties的配置
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost/spring_jdbc?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&useSSL=false
jdbc.username = root
jdbc.password = root
jdbc.maxActive= 50
initialPoolSize=20
maxPoolSize=100
minPoolSize=10
maxIdleTime=600
acquireIncrement=5
maxStatements=5
idleConnectionTestPeriod=60
(三)创建Spring的配置文件
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd ">
需要添加相应的命名空间,并且开启自动化扫描组件,使用context的property-placeholder属性,并指定location属性值为指定的配置文件。
(四)配置数据源
由于建立数据库连接是一个非常耗时耗资源的行为,所以通过连接池预先同数据库建立一些连接,放在内存中,应用程序需要建立数据库连接时,直接到连接池中申请一个即可。用完再放回去。
C3P0是一个开源的JDBC连接池,它实现了数据源,支持JDBC3规范和JDBC2的标准扩展,目前使用它的开源项目有Hibernate,Spring等,C3P0具有自动回收空闲连接的功能。
C3P0数据源配置
模板类配置
Spring把JDBC中的重复操作组合成了一个模板类org.springframework.jdbc.core.JdbcTemplate
(五)JDBC测试
创建数据库表
创建tb_account表,并插入相应的数据支持。
USE MyBatis;
CREATE TABLE tb_account(
account_id int(11) NOT NULL PRIMARY KEY AUTO_INCREMENT,
account_name varchar(20) NOT NULL,
account_type varchar(20) NOT NULL,
money double NOT NULL ,
remark varchar(50),
create_time datetime NOT NULL,
update_time datetime NOT NULL ,
user_id int(11) NOT NULL
);
INSERT INTO tb_account (account_id, account_name, account_type, money, remark, create_time, update_time, user_id)
VALUES (1,'账户1','建设银行',1000,'零花钱','2020-03-19 02:42:42','2020-03-19 02:42:56',1);
INSERT INTO tb_account(account_id, account_name, account_type, money, remark, create_time, update_time, user_id) VALUES
(2,'账户2','招商银行',500,'兼职费','2020-03-20 02:43:56','2020-03-20 02:46:57',1);
使用Junit测试
@Test
public void testTemplate1(){
//获取Spring的上下文环境
ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
JdbcTemplate jdbcTemplate = context.getBean("jdbcTemplate", JdbcTemplate.class);
//定义sql语句
String totalUsers="SELECT count(1) FROM tb_account";
Integer total = jdbcTemplate.queryForObject(totalUsers, Integer.class);
System.out.println("共有"+total+"条数据");
}
@Test
public void testJdbcTemplate2(){
//获取Spring的上下文环境
ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
JdbcTemplate jdbcTemplate = context.getBean("jdbcTemplate", JdbcTemplate.class);
String sql="SELECT count(1) FROM tb_account WHERE user_id=?";
Integer integer = jdbcTemplate.queryForObject(sql, Integer.class,2);
System.out.println("记录总数:"+integer);
}
junit测试改进
首先创建基类测试类
@RunWith(SpringJUnit4ClassRunner.class)//将测试运行在Spring的测试环境中
@ContextConfiguration(locations = {"classpath:spring.xml"})//设置要加载的配置文件,使用注解进行配置。
public class BaseTest {
}
需要注意的是基类测试类不需要书写任何内容,只需要在类上添加两个注解
@RunWith(SpringJUnit4ClassRunner.class):此处表示将原先的junit测试添加到Spring的测试环境之中去。
@ContextConfiguration(locations={xxxx,xxxx}):此处表示设置要加载的配置文件,locations表示配置文件的数组名,之后添加一个数组,数组的内容是需要使用到的xml配置文件。
创建基本的测试类
public class SpringJDBCTest extends BaseTest{
/**使用注解来注入*/
@Resource
private JdbcTemplate jdbcTemplate;
@Test
public void testTemplate1(){
//定义sql语句
String totalUsers="SELECT count(1) FROM tb_account";
Integer total = jdbcTemplate.queryForObject(totalUsers, Integer.class);
System.out.println("共有"+total+"条数据");
}
@Test
public void testJdbcTemplate2(){
String sql="SELECT count(1) FROM tb_account WHERE user_id=?";
Integer integer = jdbcTemplate.queryForObject(sql, Integer.class,2);
System.out.println("记录总数:"+integer);
}
}
此处需要注意的是将jdbcTemplate类对象作为属性注入到测试类中,并通过@Resource注解完成属性的注入。并且具体的测试类只需要继承父类BaseTest即可完成对配置文件的加载及junit环境添加到Spring环境之中去的操作。
二、持久层账户模块操作(例)
此处使用Spring JDBC完成增删改查操作
(一)账户接口方法定义
1. 定义账户实体类
Account.java类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Account {
private Integer accountId;
private String accountName;
private String accountType;
private Double money;
private String remark;
private Date createTime;
private Date updateTime;
private Integer userId;
public Account(String accountName, String accountType, Double money, String remark, Integer userId) {
this.accountName = accountName;
this.accountType = accountType;
this.money = money;
this.remark = remark;
this.userId = userId;
}
}
2.定义账户的接口类
IAccountDao.java
/** * @author lambda * 添加、修改、删除、查询账户
* 1. 添加账户
* 添加账户记录,返回受影响的行数
* 添加账户记录,返回主键 *
* 批量添加账户记录,返回受影响的行数
* 2. 修改账户
* 修改账户记录,返回受影响的行数
* 批量修改账户记录,返回受影响的行数
*3.删除账户
* 删除账户记录,返回受影响的行数
*批量删除账户记录,返回受影响的行数
* 4. 查询账户
* 查询指定用户账户的总记录数
* 查询指定账户的账户详情,返回账户对象
* *多条件查询指定用户的账户列表,返回一个账户集合 *
* */public interface IAccountDao {
* /** * 添加账户记录,返回受影响的行数
* * @param account
* * @return
* */
* int addAccount(Account account);
* /**
* * 添加账户记录,返回主键
* * @param account
* * @return
* */
* int addAccountPrimaryKey(Account account);
* /**
* * 批量添加账户记录,返回受影响的行数
* * @param accounts
* * @return
* */
* int addAccountBatch(List
* /**
* * 查询指定用户账户的总记录数
* * @param userId
* * @return
* */
* int queryAccountCount(int userId);
* /**
* * 查询指定账户的账户详情,返回账户对象
* * @param accountId
* * @return
* */
* Account queryAccountByAccountId(int accountId);
* /**
* * 多条件查询指定用户的账户列表,返回一个账户集合
* * @param userId
* * @param accountName
* * @param accountType
* * @param createTime
* * @return
* */
* List
* /**
* * 修改账户记录,返回受影响的行数,传入整个账户对象
* * @param account
* * @return
* */
* int updateAccount(Account account);
* /**
* * 批量修改账户记录,返回受影响的行数
* * @param accounts
* * @return
* */
* int updateAccountBatch(List
* /**
* * 删除账户记录,返回受影响的行数
* * @param accountId
* * @return
* */
* int deleteAccount(int accountId);
* /**
* * 批量删除账户记录,返回受影响的行数
* * @param ids
* * @return
* */
* int deleteAccountBatch(int[] ids);}
3.定义接口的实现类
@Repository
public class AccountDaoImpl implements IAccountDao {
/**由于操作数据库需要使用Spring-JDBC,因此需要传入jdbcTemplate模板对象, * 并完成注入到当前类中*/
@Resource
private JdbcTemplate jdbcTemplate;
@Override
public int addAccount(Account account) {
return 0;
}
@Override
public int addAccountPrimaryKey(Account account) {
return 0;
}
@Override
public int addAccountBatch(List
return 0;
}
@Override
public int queryAccountCount(int userId) {
return 0;
}
@Override
public Account queryAccountByAccountId(int accountId) {
return null;
}
@Override
public List
return null;
}
@Override
public int updateAccount(Account account) {
return 0;
}
@Override
public int updateAccountBatch(List
return 0;
}
@Override
public int deleteAccount(int accountId) {
return 0;
}
@Override
public int deleteAccountBatch(int[] ids) {
return 0;
}
}
由于操作数据库需要使用Spring-JDBC,因此需要传入jdbcTemplate模板对象
(二)、账户记录添加实现
在企业项目开发时,对于记录的添加可能涉及到多种添加方式,比如添加单条记录,批量添加多条记录等情况,这里对于账户记录添加方式为三种,添加单条记录,返回受影响的行数,添加单条记录,返回主键、批量添加多条记录。
1. 添加账户记录
添加账户记录返回首影响的行数
@Override
public int addAccount(Account account) {
String sql="INSERT INTO tb_account(account_name,account_type,money,remark,create_time" + ",update_time,user_id) VALUES(?,?,?,?,now(),now(),?)";
int update = jdbcTemplate.update(sql, account.getAccountName(), account.getAccountType(), account.getMoney(), account.getRemark(), account.getUserId());
return update;
}
@Test
public void addAccount(){
int i = accountDao.addAccount(new Account("账户3", "工商银行", 200.0, "奖金", 1));
if (i>0){
System.out.println("插入成功!");
}
}
jdbcTemplate.update方法需要传入对应的sql语句,并传递相应的参数。参数以可变参数的形式添加到对应的表中。
2.添加账户记录返回主键
/**
* 添加记录返回主键
* @param account
* @return
*/
@Override
public int addAccountPrimaryKey(Account account) {
String sql="INSERT INTO tb_account(account_name,account_type,money,remark,create_time" +",update_time,user_id)VALUES(?,?,?,?,now(),now(),?)";
//定义该对象,用于获取我们记录的主键
GeneratedKeyHolder keyHolder = new GeneratedKeyHolder();
//需要传入两个参数,一个是PreparedStatementCreator,一个是keyHolder
jdbcTemplate.update(con -> {
//此处需要利用lambda表达式去预编译sql语句,并设置返回主键(tatement.RETURN_GENERATED_KEYS)
PreparedStatement ps=con.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
//设置相应的参数;
ps.setString(1,account.getAccountName());
ps.setString(2,account.getAccountType());
ps.setDouble(3,account.getMoney());
ps.setString(4,account.getRemark());
ps.setInt(5,account.getUserId());
//返回预编译对象
return ps;
}, keyHolder);
//执行完之后可以通过keyHolder获取主键
return keyHolder.getKey().intValue();
}
@Test
public void addAccountPrimaryKey(){
Account account = new Account("账户4", "中国银行", 300.0, "绩效奖", 4); int key = accountDao.addAccountPrimaryKey(account);
System.out.println("主键为:"+key);
}
GeneratedKeyHolder keyHolder = new GeneratedKeyHolder();此处表示获取键值对象,后续可以用来获取主键 jdbcTemplate.update(PreparedStatementCreator pcs,KeyHolder keyHolder)此处表示执行update方法,并且传入的参数有经过处理的预处理语句(使用lambda表达式来进行设置sql语句的相关参数并返回预处理语句对象ps)和键对象最后通过keyHolder.getKey().intValue();来获取新添加的对象的主键值。
3.批量添加账户,返回受影响的行数
/**
* 批量添加账户,返回受影响的行数
* @param accounts
* @return
*/
@Override
public int addAccountBatch(List
String sql="INSERT INTO tb_account(account_name,account_type,money,remark,create_time" +
",update_time,user_id)VALUES(?,?,?,?,now(),now(),?)";
//调用batchUpdate批量添加,需要传入sql语句和BatchPreparedStatementSetter pss对象
int rows= jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {
@Override
public void setValues(PreparedStatement ps, int i) throws SQLException {
//此处由于传入的是集合,所以需要通过i来获取指定的Account对象(会自动循环)
Account account=accounts.get(i);
//设置参数
ps.setString(1,account.getAccountName());
ps.setString(2,account.getAccountType());
ps.setDouble(3,account.getMoney());
ps.setString(4,account.getRemark());
ps.setInt(5,account.getUserId());
}
@Override
public int getBatchSize() {
//直接返回集合的大小,即受影响的行数
return accounts.size();
}
}).length;
//jdbcTemplate.batchUpdate返回的是数组,通过length属性可以获取数组的大小,即受影响的行数
return rows;
}
@Test
public void addAccountBatch(){
List
accounts.add(new Account("账户5", "广东发展银行", 400.0, "出勤奖", 2));
accounts.add(new Account("账户6", "浦发银行", 600.0, "年终奖", 2));
int i = accountDao.addAccountBatch(accounts);
System.out.println("添加了"+i+"条记录");
}
jdbcTemplate.batchUpdate表示执行批量添加操作,主要参数有要执行的sql语句和BatchPreparedStatementSetter pss对象。由于需要传入BatchPreparedStatementSetter对象。所以new一个对象,并实现其中的两个方法setValues和getBatchSizesetValues是使用预处理语句来对相应的sql语句进行预处理,并且通过循环取出集合中每一个值进行相应的赋值操作。getBatchSize最终返回的是经过预处理的集合的大小。最终返回数组的大小。
(三)账户记录查询的实现
账户记录查询提供了三种查询的方式,查询指定用户所有账户记录数,查询单条账户记录详情,多条件查询指定用户账户记录。
1. 查询用户的账户记录总数
@Override
public int queryAccountCount(int userId) {
//定义sql语句(其中的count中的括号可以填写*表示全部,可以添加1,表示第一个字段的总数,也就是总数)
String sql="SELECT COUNT(1) FROM tb_account WHERE user_id=?";
//调用查询方法(其中的参数依次表示为sql语句,返回的类型,以及参数值)
Integer integer = jdbcTemplate.queryForObject(sql, Integer.class, userId);
return integer;
}
@Test
public void queryAccountCount(){
int i = accountDao.queryAccountCount(1);
System.out.println("1号用户的账户一共有:"+i+"条");
}
jdbcTemplate.queryForObject用来执行查询操作,参数为sql语句,返回值的类型,以及方法的参数值。最终将返回的结果返回即可获取指定用户的总账户数。
2.查询指定账户记录详情
/**
* 查询指定账户记录详情,返回账户对象
* @param accountId
* @return
*/
@Override
public Account queryAccountByAccountId(int accountId) {
String sql="SELECT * FROM tb_account WHERE account_id=?";
//调用查询对象的方法,此处方法的第二个参数是一个函数式接口对象,lambda表达式,返回account对象
Account account= jdbcTemplate.queryForObject(sql,(rs,rowNum)->{ //在内存中创建一个账户对象来设置相应的信息以便后续返回
Account acc=new Account();
acc.setAccountId(accountId);
acc.setAccountName(rs.getString("account_name"));
acc.setAccountType(rs.getString("account_type"));
acc.setMoney(rs.getDouble("money"));
acc.setRemark(rs.getString("remark"));
acc.setCreateTime(rs.getDate("create_time"));
acc.setUpdateTime(rs.getDate("update_time"));
acc.setUserId(rs.getInt("user_id"));
return acc;
},accountId);
return account;
}
@Test
public void queryAccountByAccountId(){
Account account = accountDao.queryAccountByAccountId(1);
System.out.println(account);
}
此处的queryForObject方法主要有3个参数,一个是sql,一个是RowMapper对象,一个是对应的可变参数RowMaper参数是一个函数式接口,可以使用lambda表达式,该接口中需要传入行集和行数,需要接收返回的Account(Account的各个信息由行集中获取并返回。)最终返回为一个Account对象
3. 多条件查询用户账户记录
@Override
public List
//首先定义sql语句
String sql="SELECT * FROM tb_account WHERE user_id=?";
//定义一个参数集合,因为是多条件查询
List
params.add(userId);
//判断字符串是否为空,如果不为空,则拼接sql语句
if (StringUtils.isNotBlank(accountName)){
//如果账户名称不为空,则进行拼接
sql+="and account_name Like contact('%',?,'%')";
params.add(accountName);
}
if (StringUtils.isNotBlank(accountType)){
//如果不为空
sql+="AND account_type=?";
params.add(accountType);
}
if (StringUtils.isNotBlank(createTime)){
//如果创建时间不为空
sql+="AND create_time";
params.add(createTime);
}
//将集合转换为数组
Object[] objects = params.toArray();
List
//在内存中创建一个账户对象来设置相应的信息以便后续返回
Account acc = new Account();
acc.setAccountId(rs.getInt("account_id"));
acc.setAccountName(rs.getString("account_name"));
acc.setAccountType(rs.getString("account_type"));
acc.setMoney(rs.getDouble("money"));
acc.setRemark(rs.getString("remark"));
acc.setCreateTime(rs.getDate("create_time"));
acc.setUpdateTime(rs.getDate("update_time"));
acc.setUserId(rs.getInt("user_id"));
return acc;
});
return query;
}
@Test
public void queryAccountByParams(){
List
System.out.println(accounts.toString());
}
此处多条件查询,需要涉及到AND条件的拼接,注意点在于AND关键字之间需要空格。jdbcTemplate.query方法需要传入三个参数,一个是sql、一个是参数数组,一个是RowMapper
sql语句需要查询出所有的所需要的数据参数数组:需要判断里面的参数是否为空,不为空,则添加到参数数组中,并且对sql语句进行相应的拼接操作。RowMapper对象需要使用lambda表达式依次对创建的对象进行相应的赋值, 最终返回查询到的对象在测试的时候需要使用SimpleDateFormat类来对此进行格式化时间。
(四)账户记录的更新操作
1.更新账户记录
/**
* 更新账户,返回受影响的行数
* @param account
* @return
*/
@Override
public int updateAccount(Account account) {
String sql="UPDATE tb_account SET account_name=?, account_type=?,money=?,remark=?," +
"update_time=now() ,user_id=? WHERE account_id=?";
int update = jdbcTemplate.update(sql, account.getAccountName(), account.getAccountType(),
account.getMoney(), account.getRemark(), account.getUpdateTime(), account.getUserId()
, account.getAccountId());
return update;
}
@Test
public void updateAccount(){
int i = accountDao.updateAccount(new Account());
System.out.println("更新了"+i+"条数据.");
}
2.批量更新账户记录
/**
*批量更新账户记录
* @param accounts
* @return
*/
@Override
public int updateAccountBatch(List
String sql="UPDATE tb_account SET account_name=?, account_type=?,money=?,remark=?," +
"update_time=now() ,user_id=? WHERE account_id=?";
int length = jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {
@Override
public void setValues(PreparedStatement ps, int i) throws SQLException {
//此处由于传入的是集合,所以需要通过i来获取指定的Account对象(会自动循环)
Account account=accounts.get(i);
//设置参数
ps.setString(1,account.getAccountName());
ps.setString(2,account.getAccountType());
ps.setDouble(3,account.getMoney());
ps.setString(4,account.getRemark());
ps.setInt(5,account.getUserId());
ps.setInt(6,account.getAccountId());
}
@Override
public int getBatchSize() {
return accounts.size();
}
}).length;
@Test
public void testUpdateBatch(){
List
accounts.add(new Account());
accounts.add(new Account());
int i = accountDao.updateAccountBatch(accounts);
System.out.println("更新了"+i+"条数据");
}
(五)账户记录删除实现
1.删除账户记录
/**
*删除账户记录,返回受影响的行数
* @param accountId
* @return
*/
@Override
public int deleteAccount(int accountId) {
String sql="DELETE FROM tb_account WHERE account_id=?";
int update = jdbcTemplate.update(sql, accountId);
return update;
}
@Test
public void deleteAccountByAccountId(){
int i = accountDao.deleteAccount(1);
System.out.println("受影响的行数"+i);
}
对于jdbcTemplate而言,删除使用的是update语句
2.批量删除账户记录
/**
*批量删除用户记录,返回受影响的行数
* @param ids
* @return
*/
@Override
public int deleteAccountBatch(int[] ids) {
String sql="DELETE FROM tb_account WHERE account_id=?";
int length = jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {
@Override
public void setValues(PreparedStatement ps, int i) throws SQLException {
ps.setInt(1, ids[i]);
//i表示行数
}
@Override
public int getBatchSize() {
return ids.length;
}
}).length;
return length;
}
}
@Test
public void deleteAccountBatch(){
int[] ids={1,2,3};
int i = accountDao.deleteAccountBatch(ids);
System.out.println("受影响的行数有:"+ids);
}
批量删除使用batchupdate语句,传入sql语句与BatchPreparedStatementSetter对象setValues是预处理语句循环赋值,因为是批处理,所以需要通过循环不断地为对应的处理语句赋值,最后返回参数数组的长度,即参数有多少个,赋了多少值。
三、事务的概念
(一)事务的四大特性(ACID)
1. 原子性(Atomicity)
要么全部成功,要么全部失败,指 一个操作是不可中断的,要么全部执行成功要么全部执行失败,有着"同生共死"的感觉
2. 一致性(Consistency)
事务在执行前后,数据库中的数据要保持一致性状态(如转账过程前后需要保持数据的一致性),即只有改变前的状态和改变后的状态,没有所谓的中间状态。
3.隔离性(Isolation)
事务与事务之间的执行应当是相互隔离互不影响的。(多个角色对于统一记录进行操作必须保证没有任何干扰)。没有任何影响显然是不可能的,为了让影响级别降到最低,通过隔离级别加以限制
READ_UNCOMMITED(读未提交),隔离级别最低的一种事务级别,在这种隔离级别下,会引发脏读、不可重复读和幻读READ_COMMITED(读已提交),读到的是别人提交后的值,在这种隔离级别下,会引发不可重复读和幻读,但是避免了脏读。REPEATABLE_READ(可重复读),在这种隔离级别下,会引发幻读,但是避免了脏读和不可重复读SERIALIZABLE(串行化),最严格的隔离级别,在serializable隔离级别下,所有事务按照次序依次进行,脏读不可重复读和幻读都不会出现。
4. 持久性(Durability)
事务提交完毕后,数据库中的数据的改变是永久的。
(二)Spring事务核心接口
Spring事务管理的实现有很多细节,如果对整个接口框架有个大体的了解会非常有利于我们理解事务。
Spring并不是直接管理事务,而是提供了多种事务管理器,他们将事务的管理职责委托给Hibernate或者JTA等持久化机制所提供的相关平台框架的事务来实现的。
Spring事务管理器的接口是org.springframework.transaction.PlatformTranscationManager,通过这个接口,Spring为各个平台如JDBC、Hibernate等都通过供了对应的事务管理器,但是具体的实现可以依据各个平台自己的实际来定。接口内容如下:
public interface PlatformTransactionManager extends TransactionManager {
//得到事务对象的方法
TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException;
//提交事务
void commit(TransactionStatus status) throws TransactionException;
//回滚事务
void rollback(TransactionStatus status) throws TransactionException;
}
从这里可知具体的事务管理机制对Spring来说是透明的,它并不关心具体的事务管理机制,哪些是对应各个平台需要关心的。所以Spring事务管理的一个优点就是为不同的事务API提供一个统一的编程模型,如JTA、JDBC、HIbernate、JPA等。
1. JDBC事务
如果应用程序中直接使用JDBC来进行持久化,此时使用DataSourceTransactionManager来处理事务边界,为了使用DataSourceTransactionManager,需要使用如下的xml信息将其装配到程序的上下文定义中去。(MyBatis也是使用DataSourceTransactionManager)
事实上,DataSourceTransactionManager是通过调用java.sql.connection来管理事务,后者是通过DataSource获取的,通过调用连接的commit方法来提交事务,同样,如果事务失败,则通过调用rollback方法进行回滚。
2. Hibernate事务
使用HIbernate事务需要首先导入Hibernate的依赖坐标
如果应用程序的持久化是通过HIbernate来实现的话,需要使用HibernateTransactionManager,对于HIbernate3,需要在spring的上下文中定义如下声明:
sessionFactory属性需要装配一个HIbernate的session工厂,HibernateTransactionManager的实现细节是它将事务管理的职责委托给org.hibernate.Transaction对象,而后者是从Hibernate session中获取到的。当事务成功完成时,HibernateTransactionManager将会调用Transaction对象的commit方法,反之,则会调用rollback方法。
3. Java持久化API事务(JPA)
使用JPA需要导入坐标依赖
Java持久化API作为真正的Java持久化标准进入我们的视野,使用JPA的话,需要使用Spring的JpaTransactionManager来处理事务,可以在xml文件中进入如下配置
JpaTransactionManager只需要装配一个JPA实体管理工厂(javax.persistence.EntityManagerFactory接口的任意实现),JpaTransactionManager将由与工厂所产生的JPA EntityManager合作构建事务。
4. Java原生API事务
如果应用程序跨越了多个事务管理器(比如两个或者多个不同的数据源)。此时需要使用JtaTransactionManager:
JtaTransactionManager将事务管理责任委托给javax.transaction.UserTransaction和javax.transaction.TransactionManager对象。其中事务成功完成通过UserTransaction.commit()方法提交,事务失败,则通过UserTransaction.rollback()方法回滚。
(三)Spring事务控制的配置
通过jdbc持久化事务,对于事务配置实现由两种方式来实现,即xml配置和注解配置
1. XML配置
(1)添加命名空间
事务
xmlns:tx="http://www.springframework.org/schema/tx"
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
AOP
xmlns:aop="http://www.springframework.org/schema/aop"
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
配置如下:
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://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 "> (2)设置AOP代理 (3)配置事务管理器 (4)配置事务的相关通知 一般来说增删改使用propagation="REQUIRED",查询一般是使用read-only="true"设置为只读。 配置事务的通知: transaction-manager属性表示这个事务通知是哪个事务管理器的tx:method属性 name:是必须的,表示与事务属性关联的方法名(业务方法名),对切入点进行细化。通配符(*)可以用来指定一批关联到相同的事务属性的方法。如get*、handle*、on*Event等propagation:不是必须的,默认值是REQUIRED,表示事务传播行为,包括REQUIRED、SUPPORTS、MANDATORY、NEVER、REQUIRES_NEW\NOT_SUPPORTED\NESTEDisolation:不是必须的,默认值是DEFAULT,表示事务的隔离级别(数据库的隔离级别)timeout:不是必须的,默认值为-1(永不超时),表示事务的超时时间,以秒为单位。read-only:不是必须的,默认值false不是只读的,表示事务是否为只读。rollback-for:不是必须的,表示将被触发进行回滚的Exception(s),以逗号分开。no-rollback-for:不是必须的,表示不被触发进行回滚的Exception(s),以逗号分开。 事务传播行为的介绍 对应的属性值对应的意义@Transactional(propagation=Propagation.REQUIRED)如果有事务,那么加入事务,如果或没有事务,默认情况下新建一个。@Transactional(propagation=Propagation.NOT_SUPPORTED)容器不为这个方法开启事务@Transactional(propagation=Propagation.REQUIRES_NEW)不管是否存在事务,都创建一个新的事务,原来的挂起,新的执行完毕,再继续执行老的事务。@Transactional(propagation=Propagation.MANDATORY)必须在一个已有的事务中执行,否则会抛出异常@Transactional(propagation=Propagation.NEVER)必须在一个没有的事务中执行,否则抛出异常(与MANDATORY相反)@Transactional(propagation=Propagation.SUPPORTS)如果其他的bean调用这个方法,在其他bean中声明事务,那就用事务。如果在其他bean中没有声明事务,那就不用事务。@Transactional(propagation=Propagation.NESTED)支持当前事务,如果当前事务存在,则执行一个嵌套事务,如果当前没有事务,就新建一个事务。 (5)配置AOP 2. 注解配置 (1)配置事务管理器 (2)配置注解支持 (3)加入事务注解 在需要使用事务的方法上添加事务的注解 @Override @Transactional(propagation=Propagation.REQUIRED) public void saverUser(){ xxxxx } (四)Spring事务控制——模拟转账的实现 1.接口方法定义 /** * 支出方法 * @param accountId * @param money * @return */ int outAccount(int accountId,Double money); /** * 表示收入方法 * @param accountId * @param money * @return */ int inAccount(int accountId,Double money); 2.接口实现 /** * 表示支出 * @param accountId * @param money * @return */ @Override public int outAccount(int accountId, Double money) { String sql="UPDATE tb_account SET money=money-? WHERE account_id=?"; return jdbcTemplate.update(sql,money,accountId); } /** *表示收入 * @param accountId * @param money * @return */ @Override public int inAccount(int accountId, Double money) { String sql="UPDATE tb_account SET money=money+? WHERE account_id=?"; return jdbcTemplate.update(sql,money,accountId); } 3.业务层方法 @Service @Transactional(propagation = Propagation.REQUIRED) public class AccountService { @Autowired private IAccountDao accountDao; /** * 转账的业务操作 * @param outId * @param inId * @param money * @return */ public int updateAccountByTransfer(Integer outId,Integer inId,Double money){ //code表示状态码,0表示失败,1表示成功 int code=0; //支出方法 int outRow=accountDao.outAccount(outId,money); //收入方法 int inRow=accountDao.inAccount(inId,money); if (inRow==1 && outRow==1){ code=1; } return code; } } @Transactional(propagation = Propagation.REQUIRED)表示用注解对该方法进行事务支持。 4.测试 public class TestTransfer extends BaseTest{ @Autowired private AccountService accountService; @Test public void testTransfer(){ int i = accountService.updateAccountByTransfer(1, 2, 100.0); if (i==1){ System.out.println("转账成功"); } } } 相关链接
发表评论