Bootstrap

Spring JDBC 和 事务控制——(2)

3. 持久层账户模块操作(操作)

当完成 Spring Jdbc 环境集成后,这⾥使⽤ spring jdbc 完成账户单表 crud 操作 .

3.1. 账户接⼝⽅法定义

3.1.1. 定义实体类

Account.java

package com.xxxx.entity;

import java.util.Date;

/**

 * ⽤户账户类

 */

public class Account {

 private Integer accountId; // 账户ID,主键

 private String accountName; // 账户名称

 private String accountType; // 账户类型
 private Double money; // 账户⾦额

 private String remark; // 账户备注

 private Integer userId; // ⽤户ID,账户所属⽤户

 private Date createTime; // 创建时间

 private Date updateTime; // 修改时间

 public Account() {

 }

 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;

 }

 @Override

 public String toString() {

 return "Account{" +

 "accountId=" + accountId +

 ", accountName='" + accountName + '\'' +

 ", accountType='" + accountType + '\'' +

 ", money=" + money +

 ", remark='" + remark + '\'' +

 ", userId=" + userId +

 ", createTime=" + createTime +

 ", updateTime=" + updateTime +

 '}';

 }

 public Integer getAccountId() {

 return accountId;

 }

 public void setAccountId(Integer accountId) {

 this.accountId = accountId;

 }

 public String getAccountName() {

 return accountName;

 }

 public void setAccountName(String accountName) {

 this.accountName = accountName;

 }

 public String getAccountType() {

 return accountType;

 }

 public void setAccountType(String accountType) {

 this.accountType = accountType;

 }

 public Double getMoney() {

 return money;

 }

 public void setMoney(Double money) {

 this.money = money;

 }

 public String getRemark() {

 return remark;

 }

 public void setRemark(String remark) {

 this.remark = remark;

 }

 public Integer getUserId() {

 return userId;

 }

 public void setUserId(Integer userId) {

 this.userId = userId;

 }

 public Date getCreateTime() {

 return createTime;

 }

 public void setCreateTime(Date createTime) {

 this.createTime = createTime;

 }

 public Date getUpdateTime() {

 return updateTime;

 }

 public void setUpdateTime(Date updateTime) {

 this.updateTime = updateTime;

 }

}

3.1.2. 定义接⼝类

IAccountDao.java

package com.xxxx.dao;
import com.xxxx.entity.Account;

import java.util.List;

/**

 * ⽤户模块 接⼝定义

 *      1. 添加账户

 *          添加账户记录,返回受影响的⾏数

 *          添加账户记录,返回记录的主键

 *          批量添加账户记录,返回受影响的⾏数

 *      2. 查询账户

 *          查询指定⽤户的账户总记录数,返回记录数

 *          查询指定账户记录详情,返回账户对象

 *          多条件查询指定⽤户的账户列表,返回账户集合

 *      3. 更新账户

 *          更新账户记录,返回受影响的⾏数

 *          批量更新账户记录,返回受影响的⾏数

 *      4. 删除账户

 *          删除账户记录,返回受影响的⾏数

 *          批量删除账户记录,返回受影响的⾏数

 */

public interface IAccountDao {

 /**

 * 添加账户

 *      添加账户记录,返回受影响的⾏数

 * @param account

 * @return

 */

 public int addAccount(Account account) ;

 /**

 * 添加账户

 *      添加账户记录,返回记录的主键

 * @param account

 * @return

 */

 public int addAccountHasKey(Account account);

 /**

 * 添加账户

 *      批量添加账户记录,返回受影响的⾏数

 * @param accounts

 * @return

 */
 public int addAccountBatch(List<Account> accounts);

 /**

 * 查询账户

 *      查询指定⽤户的账户总记录数,返回记录数

 * @param userId

 * @return

 */
 /**

 * 查询账户

 *      查询指定账户记录详情,返回账户对象

 * @param accountId

 * @return

 */
 public Account queryAccountById(Integer accountId);

 /**

 * 查询账户

 *      多条件查询指定⽤户的账户列表,返回账户集合

 * @param userId

 * @param accountName

 * @param accountType

 * @param createTime

 * @return

 */
 public List<Account> queryAccountsByParams(Integer userId, String accountName, String accountType, String createTime);

 /**

 * 更新账户

 *      更新账户记录,返回受影响的⾏数

 * @param account

 * @return

 */

 public int updateAccountById(Account account);

 /**

 * 更新账户

 *      批量更新账户记录,返回受影响的⾏数

 * @param accounts

 * @return

 */
 public int updateAccountBatch(List<Account> accounts);

 /**

 * 删除账户

 *      删除账户记录,返回受影响的⾏数

 * @param accountId

 * @return

 */
 public Integer deleteAccoutById(Integer accountId);

 /**

 * 删除⽤户

 *      批量删除账户记录,返回受影响的⾏数

 * @param ids



 * @return
 */

 public int deleteAccountBatch(Integer[] ids);

}

3.1.3. 定义接⼝实现类

package com.xxxx.dao.impl;

import com.xxxx.dao.IAccountDao;

import com.xxxx.entity.Account;

import org.springframework.jdbc.core.JdbcTemplate;

import org.springframework.stereotype.Component;

import javax.annotation.Resource;

import java.util.List;

/**

 * 账户模块接⼝实现类

 */

@Repository

public class AccountDaoImpl implements IAccountDao {

 // JdbcTemplate 模板类注⼊

 @Resource

 private JdbcTemplate jdbcTemplate;

 @Override

 public int addAccount(Account account) {

 return 0;

 }

 @Override

 public int addAccountHasKey(Account account) {

 return 0;

 }

 @Override

 public int addAccountBatch(List<Account> accounts) {

 return 0;

 }

 @Override

 public int queryAccountCount(Integer userId) {

 return 0;

 }

 @Override

 public Account queryAccountById(Integer accountId) {

 return null;

 }

 @Override



 public List<Account> queryAccountsByParams(Integer userId, String accountName, String accountType, String createTime) {

 return null;

 }

 @Override

 public int updateAccountById(Account account) {

 return 0;

 }

 @Override
 public int updateAccountBatch(List<Account> accounts) {  return 0;

 }

 @Override

 public Integer deleteAccoutById(Integer accountId) {

 return null;

 }

 @Override

 public int deleteAccountBatch(Integer[] ids) {

 return 0;

 }

}

3.2. 账户记录添加实现

        在企业项⽬开发时,对于记录的添加可能涉及到多种添加⽅式,⽐如添加单条记录,批量添加多条记录等情况。这⾥对于账户记录添加⽅式分为三种⽅式:添加单条记录返回受影响⾏数、添加单条记录返回主键、批量添加多条记录。

3.2.1. 添加账户记录
/**

 * 添加单条记录,返回受影响的⾏数

 * @param account

 * @return

 */

@Override

public int addAccount(Account account) {
 String sql = "insert into tb_account(account_name,account_type,money,remark," +  "user_id,create_time,update_time) values (?,?,?,?,?,now(),now())";
 Object[] objs = {account.getAccountName(),account.getAccountType(),

 account.getMoney(),account.getRemark(),account.getUserId()};

 return jdbcTemplate.update(sql,objs);

}

测试⽅法

/**

 * 添加账户记录,得到受影响的⾏数

 */

@Test

public void testAddAccount() {

 // 准备要添加的数据
 Account account = new Account("张三","建设银⾏",100.0,"零花钱",1);

 // 调⽤对象的添加⽅法,返回受影响的⾏数

 int row = accountDao.addAccount(account);

 System.out.println("添加账户受影响的⾏数:" + row);

}
3.2.2. 添加记录返回主键
/**

 * 添加单条记录,返回主键

 * @param account

 * @return

 */

@Override

public int addAccountHasKey(Account account) {
 String sql = "insert into tb_account(account_name,account_type,money,remark," +  "user_id,create_time,update_time) values (?,?,?,?,?,now(),now())";
 // 定义keyHolder 对象  获取记录主键值

 KeyHolder keyHolder = new GeneratedKeyHolder();

 jdbcTemplate.update(connection ->  {

 // 预编译sql语句,并设置返回主键

 PreparedStatement ps = 
 connection.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);

 // 得到返回的主键

 Integer key = keyHolder.getKey().intValue();

 return key;

}

测试⽅法

/**

 * 添加账户记录,返回主键

 */

@Test

public void testAddAccountHasKey() {

 // 准备要添加的数据
 Account account = new Account("李四","招商银⾏",200.0,"兼职费",2);

 // 调⽤对象的添加⽅法,返回主键

 int key = accountDao.addAccountHasKey(account);

 System.out.println("添加账户返回的主键:" + key);

}
3.2.3. 批量添加账户记录

/**

 * 添加多条记录,返回受影响的⾏数

 * @param accounts

 * @return

 */

@Override
public int addAccountBatch(final List<Account> accounts) {
 String sql = "insert into tb_account(account_name,account_type,money,remark," +  "user_id,create_time,update_time) values (?,?,?,?,?,now(),now())";
 int rows = jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {  @Override
 public void setValues(PreparedStatement preparedStatement, int i)
 throws SQLException {

 // 设置参数
 preparedStatement.setString(1,accounts.get(i).getAccountName());  preparedStatement.setString(2,accounts.get(i).getAccountType());  preparedStatement.setDouble(3,accounts.get(i).getMoney());  preparedStatement.setString(4,accounts.get(i).getRemark());  preparedStatement.setInt(5,accounts.get(i).getUserId());
 }

 @Override

 public int getBatchSize() {

 return accounts.size();

 }

 }).length;

 return rows;

}

测试方法

/**

 * 批量添加数据,返回受影响的⾏数

 */

@Test

public void testAddAccountBatch() {

 // 准备要添加的数据
 Account account  = new Account("王五","农业银⾏",2000.0,"⼯资",3); Account account2 = new Account("赵六","中国银⾏",280.0,"奖⾦",3); Account account3 = new Account("⽥七","⼯商银⾏",800.0,"零花钱",3); List<Account> accountList = new ArrayList<>();

 accountList.add(account);

 accountList.add(account2);

 accountList.add(account3);

 // 调⽤对象的添加⽅法,返回主键

 int rows = accountDao.addAccountBatch(accountList);

 System.out.println("批量添加账户受影响的⾏数:" + rows);

}

3.3. 账户记录查询实现

  账户记录查询这⾥提供了三种查询⽅式,查询指定⽤户所有账户记录数,查询单条账户记录详情,多条件查询指定⽤户账户记录。

3.3.1. 查询⽤户的账户总记录数
/**

 * 查询指定⽤户的账户总记录数,返回记录数

 * @param userId

 * @return

 */

@Override

public int queryAccountCount(Integer userId) {
 String sql = "select count(1) from tb_account where user_id = ?"; int count = jdbcTemplate.queryForObject(sql,Integer.class,userId); return count;

}

测试⽅法

/**

 * 查询⽤户的账户总记录数,返回总记录数

 */

@Test

public void testQueryAccountCount(){

 // 查询ID为1的⽤户的账户总记录数

 int total = accountDao.queryAccountCount(1);

 System.out.println("总记录数:" + total);

}
3.3.2. 查询指定账户记录详情

/**

 * 查询某个账户记录详情,返回账户对象

 * @param accountId

 * @return



 */

@Override

public Account queryAccountById(Integer accountId) {

 String sql = "select * from tb_account where account_id = ?";
 Account account = jdbcTemplate.queryForObject(sql, new Object[]{accountId},

(resultSet, i) -> {

 Account acc = new Account();

 acc.setAccountId(resultSet.getInt("account_id"));

 acc.setMoney(resultSet.getDouble("money"));
 acc.setAccountName(resultSet.getString("account_name")); acc.setAccountType(resultSet.getString("account_type")); acc.setRemark(resultSet.getString("remark"));
 acc.setCreateTime(resultSet.getDate("create_time")); acc.setUpdateTime(resultSet.getDate("update_time")); acc.setUserId(resultSet.getInt("user_id"));

 return acc;

 });

 return account;

 }

测试⽅法

/**

 * 查询指定账户的记录详情,返回账户对象

 */

@Test

public void testQueryAccountById(){

 // 查询ID为1的账户记录的详情

 Account account = accountDao.queryAccountById(1);  System.out.println("账户详情:" + account.toString()); }

3.3.3. 多条件查询⽤户账户记录

/**

 * 多条件查询指定⽤户的账户列表,返回账户集合

 * @param userId ⽤户Id

 * @param accountName 账户名称 (模糊查询)

 * @param accountType 账户类型

 * @param createTime  账户创建时间

 * @return

 */

@Override
public List<Account> queryAccountsByParams(Integer userId, String accountName, String accountType,

 String createTime) {

 String sql = "select * from tb_account where user_id = ? "; List<Object> params = new ArrayList<>();

 params.add(userId);

 // 判断是否有条件查询

 // 如果账户名称不为空,通过账户名称模糊匹配



 if (StringUtils.isNotBlank(accountName)) {
 sql += " and  account_name like concat('%',?,'%') "; 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[] objs = params.toArray();

 List<Account> accountList = jdbcTemplate.query(sql, objs, (resultSet, rowNum) ->  {

 Account acc = new Account();

 acc.setAccountId(resultSet.getInt("account_id"));

 acc.setMoney(resultSet.getDouble("money"));
 acc.setAccountName(resultSet.getString("account_name")); acc.setAccountType(resultSet.getString("account_type")); acc.setRemark(resultSet.getString("remark"));
 acc.setCreateTime(resultSet.getDate("create_time")); acc.setUpdateTime(resultSet.getDate("update_time")); acc.setUserId(resultSet.getInt("user_id"));

 return acc;

 });

 return accountList;

}

测试⽅法

/**

 * 多条件查询⽤户的账户记录,返回账户集合

 */

@Test

public void testQueryAccountByParams(){

 // 查询⽤户的账户列表
 List<Account> accountList = accountDao.queryAccountsByParams(3,null,null,null); // 通过指定条件查询⽤户的账户列表
 List<Account> accountList02 = accountDao.queryAccountsByParams(3,"张",null,null);

 System.out.println(accountList.toString());

 System.out.println(accountList02.toString());

}

3.4. 账户记录更新实现

3.4.1. 更新账户记录

/**

 * 更新指定账户记录,返回受影响的⾏数

 * @param account

 * @return

 */

@Override

public int updateAccountById(Account account) {
 String sql = "update tb_account set account_name = ?, account_type = ?, " +

 " money = ? ,remark = ?,user_id = ? ,update_time = now() " + " where account_id = ? ";

 Object[] objs = {account.getAccountName(),account.getAccountType(),

 account.getMoney(), account.getRemark(),account.getUserId(), account.getAccountId()};

 return jdbcTemplate.update(sql,objs);

}

测试⽅法

/**

 * 批量更新账户记录,返回受影响的⾏数

 */

@Test

public void testUpdateAccountBatch(){

 // 准备要修改的数据
 Account account = new Account("a3","建设银⾏3",300.0,"零花钱加倍3",3); account.setAccountId(3);
 Account account2 = new Account("a4","建设银⾏4",400.0,"零花钱加倍4",3); account2.setAccountId(4);

 List<Account> accountList = new ArrayList<>();

 accountList.add(account);

 accountList.add(account2);

 int rows = accountDao.updateAccountBatch(accountList);  System.out.println("批量修改账户记录返回受影响的⾏数:" + rows); }

测试⽅法

/**

 * 更新指定账户记录,返回受影响的⾏数

 */

@Test

public void testUpdateAccount(){

 // 准备要修改的数据
 Account account = new Account("张三1","建设银⾏1",500.0,"零花钱加倍",1); account.setAccountId(1);

 int row = accountDao.updateAccountById(account);

 System.out.println("修改账户返回受影响的⾏数:" + row);

}
3.4.2. 批量更新账户记录
/**

 * 批量新账户记录,返回受影响的⾏数

 * @param accounts

 * @return

 */

@Override
public int updateAccountBatch(List<Account> accounts) {
 String sql = "update tb_account set account_name = ?, account_type = ?, " +

 " money = ? ,remark = ?,user_id = ? ,update_time = now() " + " where account_id = ? ";

 int rows = jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {  @Override
 public void setValues(PreparedStatement ps, int i) throws SQLException {  // 设置参数

 ps.setString(1,accounts.get(i).getAccountName());

 ps.setString(2,accounts.get(i).getAccountType());



 ps.setDouble(3,accounts.get(i).getMoney());

 ps.setString(4,accounts.get(i).getRemark());

 ps.setInt(5,accounts.get(i).getUserId());

 ps.setInt(6,accounts.get(i).getAccountId());

 }

 @Override

 public int getBatchSize() {

 return accounts.size();

 }

 }).length;

 return rows;

}

3.5. 账户记录删除实现

3.5.1. 删除账户记录
/**

 * 删除账户记录,返回受影响的⾏数

 * @param accountId

 * @return

 */

@Override

public Integer deleteAccoutById(Integer accountId) {
 String sql = "delete from tb_account where account_id= ? "; Object[] objs = {accountId};

 return jdbcTemplate.update(sql,objs);

}

测试⽅法

/**

 * 删除账户记录,返回受影响的⾏数

 */

@Test

public void testDeleteAccount(){

 // 删除ID为1的账户记录

 int row = accountDao.deleteAccoutById(1);
 System.out.println("删除账户记录返回受影响的⾏数:" + row); }
3.5.2. 批量删除账户记录
 /**

 * 批量删除账户记录,返回受影响的⾏数

 * @param ids

 * @return

 */

@Override

public int deleteAccountBatch(Integer[] ids) {
 String sql = "delete from tb_account where account_id = ?";
 int row = jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {  @Override
 public void setValues(PreparedStatement ps, int i) throws SQLException {  ps.setInt(1,ids[i]);

 }

 @Override

 public int getBatchSize() {

 return ids.length;

 }

 }).length;

 return row;

}

测试⽅法

/**

 * 批量删除账户记录,返回受影响的⾏数

 */

@Test

public void testDeleteAccountBatch(){

 // 删除多个id的账户记录

 Integer[] ids = new Integer[]{2,3};

 int rows = accountDao.deleteAccountBatch(ids);
 System.out.println("批量删除账户记录返回受影响的⾏数:" + rows); }

4. Spring 事务控制

4.1. 转账场景模拟实现(操作)

4.1.1. 接⼝⽅法定义
/**

 * 收⼊

 * @param tarAid 收⼊⾦额的账户ID

 * @param money 收⼊⾦额

 * @return

 */

public int inAccount(Integer tarAid, Double money);

/**

 * ⽀出

 * @param outAid ⽀出⾦额的账户ID

 * @param money  ⽀出⾦额

 * @return

 */

public int outAccount(Integer outAid, Double money);
4.1.2. 实现对应接⼝

  对于转账涉及到双⽅账户以及对应转账⾦额,所以有⼊账和出账两个⽅法。

/**

 * 账户收⼊

 * @param tarAid 账户ID

 * @param money 收⼊⾦额

 * @return

 */

@Override

public int inAccount(Integer tarAid, Double money) {

 // 修改指定ID的⾦额 (加上⾦额)
 String sql = "update tb_account set money = money + ? where account_id = ? "; Object[] objs = {money, tarAid};

 return jdbcTemplate.update(sql,objs);

}

/**

 * 账户⽀出

 * @param outAid 账户ID

 * @param money  ⽀出⾦额

 * @return

 */

@Override
public int outAccount(Integer outAid, Double money) {
 // 修改指定ID的⾦额 (减去⾦额)
 String sql = "update tb_account set money = money - ? where account_id = ? ";  Object[] objs = {money, outAid};

 return jdbcTemplate.update(sql,objs);

}
4.1.3. 转账⽅法实现
package com.xxxx.service;

import com.xxxx.dao.IAccountDao;

import org.springframework.stereotype.Service;

import javax.annotation.Resource;

@Service

public class AccountService {

 @Resource

 private IAccountDao accountDao;

 /**

 * 转账业务操作

 * @param outAid  ⽀出账户

 * @param inAid   收⼊账户

 * @param money   ⽀出⾦额/收⼊⾦额

 * @return

 */
 public int updateAccountByTransfer(Integer outAid, Integer inAid, Double money){  int row = 0;

 /**

 * 张三账户向李四账户转账100元

 *   张三账户的⾦额 - 100

 *   李四账户的⾦额 + 100

 */

 // ⽀出,修改⾦额返回受影响的⾏数

 int outRow = accountDao.outAccount(1,100.0);

 // 收⼊,修改⾦额返回受影响的⾏数

 int inRow = accountDao.inAccount(2,100.0);

 // 当两个操作都执⾏成功时,转账成功

 if (outRow == 1 && inRow == 1) {

 row = 1; // 成功

 }

 return row;

 }

}

  仔细思考代码会发现,在程序运⾏中⽆法保证 service 层业务代码不发⽣异常,如果通过 jdbc 的⽅式处理事务,此时需要⼿动⽅式控制事务,这样的话凡是涉及到事务控制的业务⽅法均需要开发⼈员⼿动来进⾏事务处理,⽆法满⾜⽣产的需要。

4.2. Spring 事务概念(了解

4.2.1. 事务的四⼤特性(ACID

原⼦性(Atomicity

  共⽣死,要么全部成功,要么全部失败!

⼀致性(Consistency
  事务在执⾏前后,数据库中数据要保持⼀致性状态。(如转账的过程 账户操作后数据必须保持⼀致)

隔离性(Isolation
  事务与事务之间的执⾏应当是相互隔离互不影响的。(多个⻆⾊对统⼀记录进⾏操作必须保证没有任何⼲扰),当然没有影响是不可能的,为了让影响级别降到最低,通过隔离级别加以限制:   1. READ_UNCOMMITTED (读未提交)
  隔离级别最低的⼀种事务级别。在这种隔离级别下,会引发脏读、不可重复读和幻读。

  2. READ_COMMITTED (读已提交)
  读到的都是别⼈提交后的值。这种隔离级别下,会引发不可重复读和幻读,但避免了脏读。

  3. REPEATABLE_READ (可重复读)

  这种隔离级别下,会引发幻读,但避免了脏读、不可重复读。

  4. SERIALIZABLE (串⾏化)
  最严格的隔离级别。在Serializable隔离级别下,所有事务按照次序依次执⾏。  脏读、不可重复读、幻读都不会出现。

持久性(Durability

  事务提交完毕后,数据库中的数据的改变是永久的。

4.2.2. Spring 事务核⼼接⼝

  Spring 事务管理的实现有许多细节,如果对整个接⼝框架有个⼤体了解会⾮常有利于我们理解事务,下⾯通过讲解 Spring 的事务接⼝来了解 Spring 实现事务的具体策略 

  Spring 并不直接管理事务,⽽是提供了多种事务管理器,他们将事务管理的职责委托给 Hibernate 或者 JTA 等持久化机制所提供的相关平台框架的事务来实现。

  Spring 事务管理器的接⼝是org.springframework.transaction.PlatformTransactionManager,通过这个接⼝,Spring 为各个平台如 JDBCHibernate 等都提供了对应的事务管理器,但是具体的实现就是各个平台⾃⼰的事情了。此接⼝的内容如下:

public interface PlatformTransactionManager(){
 //  TransactionDefinition 得到 TransactionStatus 对象 
 TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;

 // 提交 
 void commit(TransactionStatus status) throws TransactionException;  // 回滚 
 void rollback(TransactionStatus status) throws TransactionException; }

  从这⾥可知具体的具体的事务管理机制对 Spring 来说是透明的,它并不关⼼那些,那些是对应各个平台需要关⼼的,所以 Spring 事务管理的⼀个优点就是为不同的事务 API 提供⼀致的编程模型,如  JTAJDBCHibernateJPA。下⾯分别介绍各个平台框架实现事务管理的机制。

4.2.2.1. JDBC 事务

  如果应⽤程序中直接使⽤ JDBC 来进⾏持久化,此时使⽤ DataSourceTransactionManager 来处理事务边界。为了使⽤DataSourceTransactionManager,需要使⽤如下的 XML 将其装配到应⽤程序的上下⽂定义中:

<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">  <property name="dataSource" ref="dataSource" />

</bean>

  实际上,DataSourceTransactionManager 是通过调⽤ java.sql.Connection 来管理事务,⽽后者是通 DataSource 获取到的。通过调⽤连接的 commit() ⽅法来提交事务,同样,事务失败则通过调 rollback() ⽅法进⾏回滚。

4.2.2.2. Hibernate 事务

  如果应⽤程序的持久化是通过 Hibernate 实现的,那么你需要使⽤ HibernateTransactionManager。对 Hibernate3,需要在 Spring 上下⽂定义中添加如下的声明:

<bean id="transactionManager"
class="org.springframework.orm.hibernate3.HibernateTransactionManager">  <property name="sessionFactory" ref="sessionFactory" />
</bean>

  sessionFactory 属性需要装配⼀个 Hibernate  session ⼯⼚,HibernateTransactionManager 的实现细节是它将事务管理的职责委托给 org.hibernate.Transaction 对象,⽽后者是从 Hibernate Session 中获取到的。当事务成功完成时,HibernateTransactionManager 将会调⽤ Transaction 对象的 commit() ⽅法,反之,将会调⽤ rollback() ⽅法。

4.2.2.3. Java 持久化 API 事务(JPA

  Hibernate 多年来⼀直是 Java 持久化标准,但是现在 Java 持久化 API 作为真正的 Java 持久化标准进⼊⼤家的视野。如果你计划使⽤ JPA 的话,那你需要使⽤ Spring  JpaTransactionManager 来处理事务。

你需要在 Spring 中这样配置 JpaTransactionManager

<bean id="transactionManager"

class="org.springframework.orm.jpa.JpaTransactionManager">

 <property name="sessionFactory" ref="sessionFactory" />

</bean>

  JpaTransactionManager 只需要装配⼀个 JPA 实体管理⼯⼚(javax.persistence.EntityManagerFactory 接⼝的任意实现)。 JpaTransactionManager 将与由⼯⼚所产⽣的 JPA EntityManager 合作来构建事务。

4.2.2.4. Java 原⽣ API 事务

  如果应⽤程序没有使⽤以上所述的事务管理,或者是跨越了多个事务管理源(⽐如两个或者是多个不同的数据源),此时需要使⽤ JtaTransactionManager

<bean id="transactionManager"

class="org.springframework.transaction.jta.JtaTransactionManager">
 <property name="transactionManagerName" value="java:/TransactionManager" />

</bean>

  JtaTransactionManager 将事务管理的责任委托给 javax.transaction.UserTransaction 
javax.transaction.TransactionManager 对象,其中事务成功完成通过 UserTransaction.commit() ⽅法提交,事务失败通过 UserTransaction.rollback() ⽅法回滚 

4.3. Spring 事务控制配置(了解

  通过 jdbc 持久化事务,对于事务配置实现由两种⽅式即:Xml 配置,注解配置。

4.3.1. XML 配置
4.3.1.1. 添加命名空间

spring.xml配置⽂件的添加事务和aop的命名空间

事务

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

配置如下

<beans xmlns="http://www.springframework.org/schema/beans"
 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
 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">

4.3.1.2. 设置aop代理

<!-- 开启AOP代理 -->

<aop:aspectj-autoproxy />

4.3.1.3. 配置事务管理器

<!-- 事务管理器定义 -->

<bean id="txManager" 
 class="org.springframework.jdbc.datasource.DataSourceTransactionManager">  <!--数据源 -->
 <property name="dataSource" ref="dataSource"></property>
</bean>

4.3.1.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,NESTED

 isolation  

 不是必须的,默认值DEFAULT

 表示事务隔离级别(数据库的隔离级别)

 timeout

 不是必须的,默认值-1(永不超时)

 表示事务超时的时间(以秒为单位)

 read-only 

 不是必须的,默认值false不是只读的

 表示事务是否只读

 rollback-for

 不是必须的

 表示将被触发进⾏回滚的 Exception(s);以逗号分开。

 如:'com.foo.MyBusinessException,ServletException'

 no-rollback-for

 不是必须的

 表示不被触发进⾏回滚的 Exception(s);以逗号分开。

 如:'com.foo.MyBusinessException,ServletException'

 任何 RuntimeException 将触发事务回滚

 -->
<tx:advice id="txAdvice" transaction-manager="txManager">
 <!--对以add update delete query开头的所有⽅法进⾏事务处理-->  <tx:attributes>
 <!--定义什么⽅法需要使⽤事务  name代表的是⽅法名(或⽅法匹配)-->  <!-- 匹配以 add 开头的所有⽅法均加⼊事务 -->

 <tx:method name="add*" propagation="REQUIRED" />

 <!-- 匹配以 update 开头的所有⽅法均加⼊事务 -->

 <tx:method name="update*" propagation="REQUIRED" />

 <!-- 匹配以 delete 开头的所有⽅法均加⼊事务 -->

 <tx:method name="delete*" propagation="REQUIRED" />

 <!-- 匹配以 query 开头的所有⽅法均加⼊事务 -->

 <tx:method name="query*" read-only="true" />

 </tx:attributes>

</tx:advice>

事务传播⾏为介绍:

 @Transactional(propagation=Propagation.REQUIRED)

 如果有事务, 那么加⼊事务, 没有的话新建⼀个(默认情况下)
 @Transactional(propagation=Propagation.NOT_SUPPORTED)
 容器不为这个⽅法开启事务
 @Transactional(propagation=Propagation.REQUIRES_NEW)
 不管是否存在事务,都创建⼀个新的事务,原来的挂起,新的执⾏完毕,继续执⾏⽼的事务 @Transactional(propagation=Propagation.MANDATORY)

 必须在⼀个已有的事务中执⾏,否则抛出异常

 @Transactional(propagation=Propagation.NEVER)
 必须在⼀个没有的事务中执⾏,否则抛出异常( Propagation.MANDATORY 相反) @Transactional(propagation=Propagation.SUPPORTS)
 如果其他 bean 调⽤这个⽅法,在其他 bean 中声明事务,那就⽤事务.

 如果其他 bean 没有声明事务,那就不⽤事务.

 @Transactional(propagation=Propagation.NESTED)        
 ⽀持当前事务,如果当前事务存在,则执⾏⼀个嵌套事务,如果当前没有事务,就新建⼀个事务。

4.3.1.5. 配置aop

<!-- aop 切⾯定义 (切⼊点和通知) -->

<aop:config>

 <!-- 设置切⼊点 设置需要被拦截的⽅法 -->
 <aop:pointcut expression="execution(* com.xxxx.service..*.*(..) )" id="cut" />  <!-- 设置通知 事务通知 -->
 <aop:advisor advice-ref="txAdvice" pointcut-ref="cut"/>
<aop:adviso

4.3.2. 注解配置
4.3.2.1. 配置事务管理器

<!-- spring 注解式事务声明 -->

<!-- 事务管理器定义 -->

<bean id="txManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">  <property name="dataSource" ref="dataSource"></property>
</bean>

4.3.2.2. 配置注解⽀持

<tx:annotation-driven transaction-manager="txManager"/>

4.3.2.3. ⽅法上加⼊事务注解

Service ⽅法上在需要添加事务的⽅法上加⼊事务注解

@Override

@Transactional(propagation=Propagation.REQUIRED)

public void saveUser(String userName,String userPwd){

 User user1=new User();

 user1.setUserName(userName);

 user1.setUserPwd(userPwd);

 userDao.saveUser(user1);

 userDao.delUserById(2);

}

备注:默认 spring 事务只在发⽣未被捕获的 runtimeexcetpion 时才回滚。

spring aop 异常捕获原理:

被拦截的⽅法需显式抛出异常,并不能经任何处理,这样aop 代理才能捕获到⽅法的异常,才能进⾏回滚,默认情况下 aop 只捕获 runtimeexception 的异常,但可以通过配置来捕获特定的异常并回滚换句话说在 service 的⽅法中不使⽤ try catch 或者在 catch 中最后加上 throw new RunTimeexcetpion(),这样程序异常时才能被 aop 捕获进⽽回滚.

;