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;}@Overridepublic 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;/*** 账户模块接⼝实现类*/@Repositorypublic class AccountDaoImpl implements IAccountDao {// JdbcTemplate 模板类注⼊@Resourceprivate JdbcTemplate jdbcTemplate;@Overridepublic int addAccount(Account account) {return 0;}@Overridepublic int addAccountHasKey(Account account) {return 0;}@Overridepublic int addAccountBatch(List<Account> accounts) {return 0;}@Overridepublic int queryAccountCount(Integer userId) {return 0;}@Overridepublic Account queryAccountById(Integer accountId) {return null;}@Overridepublic List<Account> queryAccountsByParams(Integer userId, String accountName, String accountType, String createTime) {return null;}@Overridepublic int updateAccountById(Account account) {return 0;}@Overridepublic int updateAccountBatch(List<Account> accounts) {  return 0;}@Overridepublic Integer deleteAccoutById(Integer accountId) {return null;}@Overridepublic int deleteAccountBatch(Integer[] ids) {return 0;}}

3.2. 账户记录添加实现

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

3.2.1. 添加账户记录
/*** 添加单条记录,返回受影响的⾏数* @param account* @return*/@Overridepublic 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);}

测试⽅法

/*** 添加账户记录,得到受影响的⾏数*/@Testpublic void testAddAccount() {// 准备要添加的数据Account account = new Account("张三","建设银⾏",100.0,"零花钱",1);// 调⽤对象的添加⽅法,返回受影响的⾏数int row = accountDao.addAccount(account);System.out.println("添加账户受影响的⾏数:" + row);}
3.2.2. 添加记录返回主键
/*** 添加单条记录,返回主键* @param account* @return*/@Overridepublic 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;}

测试⽅法

/*** 添加账户记录,返回主键*/@Testpublic 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() {  @Overridepublic 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());}@Overridepublic int getBatchSize() {return accounts.size();}}).length;return rows;}

测试方法

/*** 批量添加数据,返回受影响的⾏数*/@Testpublic 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*/@Overridepublic 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;}测试⽅法/*** 查询⽤户的账户总记录数,返回总记录数*/@Testpublic void testQueryAccountCount(){// 查询ID为1的⽤户的账户总记录数int total = accountDao.queryAccountCount(1);System.out.println("总记录数:" + total);}
3.3.2. 查询指定账户记录详情

/*** 查询某个账户记录详情,返回账户对象* @param accountId* @return*/@Overridepublic 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;}测试⽅法/*** 查询指定账户的记录详情,返回账户对象*/@Testpublic 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;}

测试⽅法

/*** 多条件查询⽤户的账户记录,返回账户集合*/@Testpublic 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*/@Overridepublic 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);}

测试⽅法

/*** 批量更新账户记录,返回受影响的⾏数*/@Testpublic 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); }

测试⽅法

/*** 更新指定账户记录,返回受影响的⾏数*/@Testpublic 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() {  @Overridepublic 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());}@Overridepublic int getBatchSize() {return accounts.size();}}).length;return rows;}

3.5. 账户记录删除实现

3.5.1. 删除账户记录
/*** 删除账户记录,返回受影响的⾏数* @param accountId* @return*/@Overridepublic Integer deleteAccoutById(Integer accountId) {String sql = "delete from tb_account where account_id= ? "; Object[] objs = {accountId};return jdbcTemplate.update(sql,objs);}

测试⽅法

/*** 删除账户记录,返回受影响的⾏数*/@Testpublic void testDeleteAccount(){// 删除ID为1的账户记录int row = accountDao.deleteAccoutById(1);System.out.println("删除账户记录返回受影响的⾏数:" + row); }
3.5.2. 批量删除账户记录
 /*** 批量删除账户记录,返回受影响的⾏数* @param ids* @return*/@Overridepublic int deleteAccountBatch(Integer[] ids) {String sql = "delete from tb_account where account_id = ?";int row = jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {  @Overridepublic void setValues(PreparedStatement ps, int i) throws SQLException {  ps.setInt(1,ids[i]);}@Overridepublic int getBatchSize() {return ids.length;}}).length;return row;}

测试⽅法

/*** 批量删除账户记录,返回受影响的⾏数*/@Testpublic 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*/@Overridepublic 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;@Servicepublic class AccountService {@Resourceprivate 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 捕获进⽽回滚.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/480542.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

搭建文件服务器并使用Qt实现文件上传和下载(带账号和密码)

文章目录 0 背景1 搭建文件服务器2 代码实现文件上传和下载2.1 在pro文件中添加网络支持2.2 创建网络管理类2.3 文件上传2.4 文件下载 3 扩展&#xff08;其他方法实现文件上传和下载&#xff09;3.1 python3.2 npm3.3 ftp服务器 4 完整的代码 0 背景 因为需要使程序具备在远程…

社交新零售模式下“2+1 链动模式 S2B2C 商城小程序”的创新实践与发展策略

摘要&#xff1a;随着实体商业与社交网络深度融合&#xff0c;社交新零售蓬勃兴起&#xff0c;“21 链动模式 S2B2C 商城小程序”作为其中创新典范&#xff0c;融合独特激励机制与数字化运营优势&#xff0c;重塑零售生态。本文剖析该模式架构、运作逻辑&#xff0c;探讨其在私…

【Git】Git 完全指南:从入门到精通

Git 完全指南&#xff1a;从入门到精通 Git 是现代软件开发中最重要的版本控制工具之一&#xff0c;它帮助开发者高效地管理项目&#xff0c;支持分布式协作和版本控制。无论是个人项目还是团队开发&#xff0c;Git 都能提供强大的功能来跟踪、管理代码变更&#xff0c;并保障…

华为E9000刀箱(HWE9000V2)服务器硬件监控指标解读

随着数据中心规模的不断扩大&#xff0c;服务器的稳定性和可靠性变得尤为重要。华为E9000刀箱&#xff08;HWE9000V2&#xff09;作为一款高性能的服务器设备&#xff0c;其硬件状态的实时监控对于保障业务的连续性和系统的稳定运行至关重要。 监控易作为一款专业的IT基础设施监…

Css—实现3D导航栏

一、背景 最近在其他的网页中看到了一个很有趣的3d效果&#xff0c;这个效果就是使用css3中的3D转换实现的&#xff0c;所以今天的内容就是3D的导航栏效果。那么话不多说&#xff0c;直接开始主要内容的讲解。 二、效果展示 三、思路解析 1、首先我们需要将这个导航使用一个大…

gitee:删除仓库

1、点击主页面设置 2、找到左侧导航栏-数据管理->仓库空间信息&#xff1b;找到需要删除的仓库->点击设置 3、点击左侧仓库设置->点击右侧删除仓库 4、输入提示内容->确认删除 5、输入密码验证 6、成功删除提示

探索 Python 任务自动化的新境界:Invoke 库揭秘

文章目录 探索 Python 任务自动化的新境界&#xff1a;Invoke 库揭秘背景&#xff1a;为何选择 Invoke&#xff1f;什么是 Invoke&#xff1f;如何安装 Invoke&#xff1f;5个简单的库函数使用方法1. 定义任务2. 带参数的任务3. 运行 Shell 命令4. 任务参数化5. 列出任务 场景应…

深入理解计算机系统,源码到可执行文件翻译过程:预处理、编译,汇编和链接

1.前言 从一个高级语言到可执行程序&#xff0c;要经过预处理、编译&#xff0c;汇编和链接四个过程。大家可以思考下&#xff0c;为什么要有这样的过程&#xff1f; 我们学习计算机之处&#xff0c;就应该了解到&#xff0c;计算机能够识别的只有二进制语言&#xff08;这是…

六通道串口服务器

型号&#xff1a;SG-TCP232-620 1.1 功能 1.1.1 基本功能 串口服务器是串口 RS232/422/485 和以太网之间的一个转换器&#xff0c;实现串口数 据和以太网数据的双向透明传输&#xff0c;可以让串口设备立即联网&#xff0c;典型应用拓扑如下&#xff1a; 1.1.2 特色功能…

Ubuntu 18.04 中安装 RDKit(针对 Python 2.7)

✅作者简介&#xff1a;2022年博客新星 第八。热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 &#x1f49e;当前专栏…

websocket前后端长连接之java部分

一共有4个类,第一个WebSocketConfig 配置类 Configuration EnableWebSocket public class WebSocketConfig implements WebSocketConfigurer {Autowiredprivate WebSocketHandler webSocketHandler;Autowiredprivate WebSocketInterceptor webSocketInterceptor;Overridepubli…

PyCharm中Python项目打包并运行到服务器的简明指南

目录 一、准备工作 二、创建并设置Python项目 创建新项目 配置项目依赖 安装PyInstaller 三、打包项目 打包为可执行文件 另一种打包方式&#xff08;使用setup.py&#xff09; 四、配置服务器环境 五、上传可执行文件到服务器 六、在服务器上运行项目 配置SSH解释…

【UE5 C++课程系列笔记】05——组件和碰撞

效果 可以看到我们可以实现的功能是 &#xff08;1&#xff09;可以通过鼠标旋转视角 &#xff08;2&#xff09;通过使用Pawn移动组件来控制Pawn移动 &#xff08;3&#xff09;Pawn碰到物体会被阻挡然后逐渐滑动 &#xff08;4&#xff09;通过空格切换激活/关闭粒子效果…

格网法计算平面点云面积(matlab版本)

1、原理介绍 格网法计算平面点云面积&#xff0c;其思想类似高中油膜法计算面积。其将点云投影到水平面&#xff0c;再将点云划分成尺寸相同的格网。最后&#xff0c;统计格网内包含点的数量number&#xff0c;那么可利用如下公式计算得到点云的面积&#xff1a; Aeranumber*L…

ZooKeeper 基础知识总结

先赞后看&#xff0c;Java进阶一大半 ZooKeeper 官网这样介绍道&#xff1a;ZooKeeper 是一种集中式服务&#xff0c;用于维护配置信息、命名、提供分布式同步和提供组服务。 各位hao&#xff0c;我是南哥&#xff0c;相信对你通关面试、拿下Offer有所帮助。 ⭐⭐⭐一份南哥编写…

2024年第十三届”认证杯“数学中国数学建模国际赛(小美赛)

↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓

ATTCK红队评估实战靶场(二)

http://vulnstack.qiyuanxuetang.net/vuln/?page2 描述&#xff1a;红队实战系列&#xff0c;主要以真实企业环境为实例搭建一系列靶场&#xff0c;通过练习、视频教程、博客三位一体学习。本次红队环境主要Access Token利用、WMI利用、域漏洞利用SMB relay&#xff0c;EWS re…

如何启用本机GPU硬件加速猿大师播放器网页同时播放多路RTSP H.265 1080P高清摄像头RTSP视频流?

目前市面上主流播放RTSP视频流的方式是用服务器转码方案&#xff0c;这种方案的好处是兼容性更强&#xff0c;可以用于不同的平台&#xff0c;比如&#xff1a;Windows、Linux或者手机端&#xff0c;但是缺点也很明显&#xff1a;延迟高、播放高清或者同时播放多路视频视频容易…

rocylinux9.4安装prometheus监控

一.上传软件包 具体的软件包如下&#xff0c;其中kubernetes-mixin是下载的监控kubernetes的一些监控规则、dashbaordd等。 二.Prometheus配置 1.promethes软件安装 #解压上传后的软件包 [rootlocalhost ] cd /opt [rootlocalhost opt]# tar xf prometheus-2.35.3.linux-amd…

第五课 Unity资源导入工作流效率优化(AssetGraph工具)

上期我们学习了简单的animation动画的优化&#xff0c;接下来我们继续资源导入效率的优化 工程目录 首先我们来学习一下工程目录结构及用途 Asset文件夹&#xff1a;用来储存和重用的项目资产 Library文件夹&#xff1a;用来储存项目内部资产数据信息的目录 Packages文件夹…