案例结构
pom.xml
<packaging>jarpackaging> <dependencies> <dependency> <groupId>org.springframeworkgroupId> <artifactId>spring-contextartifactId> <version>5.0.2.RELEASEversion> dependency> <dependency> <groupId>org.springframeworkgroupId> <artifactId>spring-testartifactId> <version>5.0.2.RELEASEversion> dependency> <dependency> <groupId>commons-dbutilsgroupId> <artifactId>commons-dbutilsartifactId> <version>1.4version> dependency> <dependency> <groupId>mysqlgroupId> <artifactId>mysql-connector-javaartifactId> <version>8.0.15version> dependency> <dependency> <groupId>c3p0groupId> <artifactId>c3p0artifactId> <version>0.9.1.2version> dependency> <dependency> <groupId>junitgroupId> <artifactId>junitartifactId> <version>4.12version> dependency> dependencies>
Account实体类
import java.io.Serializable;/** * 账户的实体类 */public class Account implements Serializable { private Integer id; private String name; private Float money; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Float getMoney() { return money; } public void setMoney(Float money) { this.money = money; } @Override public String toString() { return "Account{" + "id=" + id + ", name='" + name + '\'' + ", money=" + money + '}'; }}
import com.itheima.domain.Account;import java.util.List;/** * 账户的持久层接口 */public interface IAccountDao { /** * 查询所有 * @return */ ListfindAllAccount(); /** * 查询一个 * @return */ Account findAccountById(Integer accountId); /** * 保存 * @param account */ void saveAccount(Account account); /** * 更新 * @param account */ void updateAccount(Account account); /** * 删除 * @param accountId */ void deleteAccount(Integer accountId); /** * 根据名称查询账户 * @param accountName * @return 如果有唯一的一个结果就返回,如果没有结果就返回null * 如果结果集超过一个就抛出异常 */ Account findAccountByName(String accountName);}
/** * 账户的持久层实现类 */public class AccountDaoImpl implements IAccountDao { private QueryRunner runner; private ConnectionUtils connectionUtils; public void setConnectionUtils(ConnectionUtils connectionUtils) { this.connectionUtils = connectionUtils; } public void setRunner(QueryRunner runner) { this.runner = runner; } public ListfindAllAccount() { try { return runner.query(connectionUtils.getThreadConnection(),"select * from account", new BeanListHandler(Account.class)); }catch (Exception e){ throw new RuntimeException(e); } } public Account findAccountById(Integer accountId) { try { return runner.query(connectionUtils.getThreadConnection(),"select * from account where id = ? ", new BeanHandler(Account.class),accountId); }catch (Exception e){ throw new RuntimeException(e); } } public void saveAccount(Account account) { try { runner.update(connectionUtils.getThreadConnection(),"insert into account(name,money)values(?,?)", account.getName(),account.getMoney()); }catch (Exception e){ throw new RuntimeException(e); } } public void updateAccount(Account account) { try { runner.update(connectionUtils.getThreadConnection(),"update account set name=?,money=? where id=?", account.getName(),account.getMoney(),account.getId()); }catch (Exception e){ throw new RuntimeException(e); } } public void deleteAccount(Integer accountId) { try { runner.update(connectionUtils.getThreadConnection(),"delete from account where id =?",accountId); }catch (Exception e){ throw new RuntimeException(e); } } public Account findAccountByName(String accountName) { try { List accounts = runner.query(connectionUtils.getThreadConnection(),"select * from account where name = ? ", new BeanListHandler(Account.class),accountName); if(accounts == null || accounts.size() == 0){ return null; } if(accounts.size() > 1){ throw new RuntimeException("结果集不唯一,数据有问题"); } return accounts.get(0); }catch (Exception e){ throw new RuntimeException(e); }}}
补充内容:Dbutils的使用和详解
DbUtils 是一个jdbc的工具,使用的范围内非常广,主要是为了简化jdbc的代码。核心类:
QueryRunner
ResultSetHandler(是一个接口,主要是完成ORM映射,把结果转化成我们需要的java对象)
核心方法:
update();用来执行DDL(DDL:create alert,drop;);
query();用来执行DML(DML:insert update delete;);
batch(); 用来执行批处理;
调用本方法之前,需要先创建对象,代码如下
QueryRunner runner = new QueryRunner(JDBCUtils.getDataSource());
当使用的是无参的构造器时,可以不提供连接池对象,但是在接下来的调用方法是,必须为方法提供Connection对象。
QueryRunner类:
该类简单化了 SQL 查询,它与 ResultSetHandler(接口 后面将会介绍) 组合在一起使用可以完成大部分的数据库操作,能够大大减少编码量。
构造函数:
QueryRunner()
QueryRunner(Datasource ds)
A:query(Connectionconn, String sql, Object[] params, ResultSetHandler rsh)方法:这一方法执行一个带参数的选择查询,在这个查询中,对象阵列的值被用来作为查询的置换参数。这一方法内在地处理 PreparedStatement 和ResultSet 的创建和关闭。ResultSetHandler对象把从 ResultSet得来的数据转变成一个更容易的或是应用程序特定的格式来使用。
B:query(Stringsql, Object[] params, ResultSetHandler rsh)方法:这几乎与第一种方法一样;唯一的不同在于它不将数据库连接提供给方法,并且它是从提供给构造器的数据源(DataSource) 或使用的setDataSource 方法中重新获得的。
C:query(Connectionconn, String sql, ResultSetHandler rsh)方法:执行一个带参数的选择查询。
D:update(Connectionconn, String sql, Object[] params)方法:这一方法被用来执行一个带参数的插入、更新或删除操作。对象阵列为声明保存着置换参数。
E:update(Stringsql, Object[] params)方法: 这几乎与上一种种方法一样;唯一的不同在于它不将数据库连接提供给方法,并且它是从提供给构造器的数据源(DataSource) 或使用的setDataSource 方法中重新获得的。
F:update(Connectionconn, String sql)方法:执行一个带参数的插入、更新或删除操作。
ResultSetHandler接口:
正如它的名字所示,这一接口执行处理一个java.sql.ResultSet,将数据转变并处理为任何一种形式,这样有益于其应用而且使用起来更容易。这一组件提供了:ArrayHandler :将ResultSet中第一行的数据转化成对象数组。ArrayListHandler:将ResultSet中所有的数据转化成List,List中存放的是Object[]。BeanHandler :将ResultSet中第一行的数据转化成类对象。BeanListHandler :将ResultSet中所有的数据转化成List,List中存放的是类对象。ColumnListHandler :将ResultSet中某一列的数据存成List,List中存放的是Object对象。KeyedHandler :将ResultSet中存成映射,key为某一列对应为Map。Map中存放的是数据。MapHandler :将ResultSet中第一行的数据存成Map映射MapListHandler :将ResultSet中所有的数据存成List。List中存放的是Map。ScalarHandler :将ResultSet中一条记录的其中某一列的数据存成Object等转化类。ResultSetHandler接口提供了一个单独的方法:Object handle(java.sql.ResultSet .rs)。因此任何ResultSetHandler 的执行需要一个结果集(ResultSet)作为参数传入,然后才能处理这个结果集,再返回一个对象。因为返回类型是java.lang.Object,所以除了不能返回一个原始的Java类型之外,其它的返回类型并没有什么限制。如果你发现这七个执行程序中没有任何一个提供了你想要的服务,你可以自己写执行程序并使用它。
import com.itheima.domain.Account;import java.util.List;/** * 账户的业务层接口 */public interface IAccountService { /** * 查询所有 * @return */ ListfindAllAccount(); /** * 查询一个 * @return */ Account findAccountById(Integer accountId); /** * 保存 * @param account */ void saveAccount(Account account); /** * 更新 * @param account */ void updateAccount(Account account); /** * 删除 * @param accountId */ void deleteAccount(Integer accountId); /** * 转账 * @param sourceName 转出账户名称 * @param targetName 转入账户名称 * @param money 转账金额 */ void transfer(String sourceName,String targetName,Float money);}
事务控制应该都在业务层
/** * 账户的业务层实现类 * * 事务控制应该都是在业务层 */public class AccountServiceImpl implements IAccountService { private IAccountDao accountDao; public void setAccountDao(IAccountDao accountDao) { this.accountDao = accountDao; } public List findAllAccount() { return accountDao.findAllAccount(); } public Account findAccountById(Integer accountId) { return accountDao.findAccountById(accountId); } public void saveAccount(Account account) { accountDao.saveAccount(account); } public void updateAccount(Account account) { accountDao.updateAccount(account); } public void deleteAccount(Integer accountId) { accountDao.deleteAccount(accountId); } public void transfer(String sourceName, String targetName, Float money) { System.out.println("transfer....."); //1.根据名称查询转出账户 Account source = accountDao.findAccountByName(sourceName); //2.根据名称查询转入账户 Account target = accountDao.findAccountByName(targetName); //3.转出账户减钱 source.setMoney(source.getMoney()-money); //4.转入账户加钱 target.setMoney(target.getMoney()+money); //5.更新转出账户 accountDao.updateAccount(source); int i = 1/0; //不满足事务的一致性,减钱的事务提交了,加钱的事务没有提交成功 //6.更新转入账户 accountDao.updateAccount(target); }}
分析transfer()方法中的问题
int i = 1/0; //不满足事务的一致性,减钱的事务提交了,加钱的事务没有提交成功
解释:
代码中一共获取四个连接,前三个事务都成功了所以都提交了,但第四个事务前发生了除零异常,所以没有提交。所以我们解决以上问题的思路就是:将transfer中的操作全放到一个连接中,所有操作要么全成功,要么全失败。方法:使用ThreadLocal对象把Connection和当前线程绑定,从而使一个线程中只有一个能控制事务的对象。注意:事务的控制应该都是在业务层。
ConnectionUtils:
import javax.sql.DataSource;import java.sql.Connection;/** * 连接的工具类,它用于从数据源中获取一个连接,并且实现和线程的绑定 */public class ConnectionUtils { private ThreadLocal tl = new ThreadLocal(); private DataSource dataSource; public void setDataSource(DataSource dataSource) { this.dataSource = dataSource; } /** * 获取当前线程上的连接 * @return */ public Connection getThreadConnection(){ /* //1.先从ThreadLocal上获取 Connection conn = tl.get();*/ try { //1.先从ThreadLocal上获取 Connection conn = tl.get(); //2.判断当前线程上是否有连接 if (conn == null) { //3.从数据源中获取一个连接,并且存入ThreadLocal中 conn = dataSource.getConnection(); tl.set(conn); } //4.返回当前线程上的连接 return conn; }catch (Exception e){ throw new RuntimeException(e); } } /** * 将用完的线程与连接解绑 * 把连接和线程解绑 */ public void removeConnection(){ tl.remove(); }}
TransactionManager和事务相关的工具类:
/** * 和事务管理相关的工具类,它包含了:开启事务、提交事务、回滚事务和释放连接 */public class TransactionManager { private ConnectionUtils connectionUtils; public void setConnectionUtils(ConnectionUtils connectionUtils) { this.connectionUtils = connectionUtils; } /** * 开启事务 */ public void beginTransaction(){ try { connectionUtils.getThreadConnection().setAutoCommit(false); }catch (Exception e){ e.printStackTrace(); } } /** * 提交事务 */ public void commit(){ try { connectionUtils.getThreadConnection().commit(); }catch (Exception e){ e.printStackTrace(); } } /** * 回滚事务 */ public void rollback(){ try { connectionUtils.getThreadConnection().rollback(); }catch (Exception e){ e.printStackTrace(); } } /** * 释放连接 */ public void release(){ try { connectionUtils.getThreadConnection().close();//还回连接池中 connectionUtils.removeConnection(); }catch (Exception e){ e.printStackTrace(); } }}
改进后的业务层实现类AccountServiceImpl_OLD:
/** * 账户的业务层实现类 * * 事务控制应该都是在业务层 */public class AccountServiceImpl_OLD implements IAccountService { private IAccountDao accountDao; private TransactionManager txManager; public void setTxManager(TransactionManager txManager) { this.txManager = txManager; } public void setAccountDao(IAccountDao accountDao) { this.accountDao = accountDao; } public ListfindAllAccount() { try{ //1.开启事务 txManager.beginTransaction(); //2.执行操作 List accounts = accountDao.findAllAccount(); //3.提交事务 txManager.commit(); //4.返回结果 return accounts; }catch (Exception e){ //5.回滚操作 txManager.rollback(); throw new RuntimeException(e); //若产生异常,程序不再执行 }finally { //6.释放连接 txManager.release(); } // return accountDao.findAllAccount(); } public Account findAccountById(Integer accountId) { try{ //1.开启事务 txManager.beginTransaction(); //2.执行操作 Account account = accountDao.findAccountById(accountId); //3.提交事务 txManager.commit(); //4.返回结果 return account; }catch (Exception e){ //5.回滚操作 txManager.rollback(); throw new RuntimeException(e); }finally { //6.释放连接 txManager.release(); } // return accountDao.findAccountById(accountId); } public void saveAccount(Account account) { try{ //1.开启事务 txManager.beginTransaction(); //2.执行操作 accountDao.saveAccount(account); //3.提交事务 txManager.commit(); }catch (Exception e){ //4.回滚操作 txManager.rollback(); }finally { //5.释放连接 txManager.release(); } // accountDao.saveAccount(account); } public void updateAccount(Account account) { try{ //1.开启事务 txManager.beginTransaction(); //2.执行操作 accountDao.updateAccount(account); //3.提交事务 txManager.commit(); //4.返回结果 }catch (Exception e){ //5.回滚操作 txManager.rollback(); }finally { //6.释放连接 txManager.release(); } //accountDao.updateAccount(account); } public void deleteAccount(Integer accountId) { try{ //1.开启事务 txManager.beginTransaction(); //2.执行操作 accountDao.deleteAccount(accountId); //3.提交事务 txManager.commit(); }catch (Exception e){ //4.回滚操作 txManager.rollback(); }finally { //5.释放连接 txManager.release(); } //accountDao.deleteAccount(accountId); } public void transfer(String sourceName, String targetName, Float money) { try{ //1.开启事务 txManager.beginTransaction(); //2.执行操作 //2.1根据名称查询转出账户 Account source = accountDao.findAccountByName(sourceName); //2.2根据名称查询转入账户 Account target = accountDao.findAccountByName(targetName); //2.3转出账户减钱 source.setMoney(source.getMoney()-money); //2.4转入账户加钱 target.setMoney(target.getMoney()+money); //2.5更新转出账户 accountDao.updateAccount(source); // int i = 1/0; 加异常测试事务是否回回滚 //2.6更新转入账户 accountDao.updateAccount(target); //3.提交事务 txManager.commit(); }catch (Exception e){ //4.回滚操作 txManager.rollback(); e.printStackTrace(); }finally { //5.释放连接 txManager.release(); } }}
创建Service代理工厂的对象BeanFactory
/** * 用于创建Service的代理对象的工厂 */public class BeanFactory { private IAccountService accountService; private TransactionManager txManager; public void setTxManager(TransactionManager txManager) { this.txManager = txManager; } public final void setAccountService(IAccountService accountService) { this.accountService = accountService; } /** * 获取Service代理对象 * * @return */ public IAccountService getAccountService() { return (IAccountService) Proxy.newProxyInstance(accountService.getClass().getClassLoader(), accountService.getClass().getInterfaces(), new InvocationHandler() { /** * 添加事务的支持 * @param proxy * @param method * @param args * @return * @throws Throwable */ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if("test".equals(method.getName())){ return method.invoke(accountService,args); } Object rtValue = null; try { //1.开启事务 txManager.beginTransaction(); //2.执行操作 rtValue = method.invoke(accountService, args); //3.提交事务 txManager.commit(); //4.返回结果 return rtValue; } catch (Exception e) { //5.回滚操作 txManager.rollback(); throw new RuntimeException(e); } finally { //6.释放连接 txManager.release(); } } }); }}
bean.xml
<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="proxyAccountService" factory-bean="beanfactory" factory-method="getAccountService">bean> <bean id="beanfactory" class="com.itheima.factory.BeanFactory"> <property name="accountService" ref="accountService">property> <property name="txManager" ref="txManager">property> bean> <bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"> <property name="accountDao" ref="accountDao">property> <property name="txManager" ref="txManager">property> bean> <bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl"> <property name="runner" ref="runner">property> <property name="connectionUtils" ref="connectionUtils">property> bean> <bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype"> bean> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="com.mysql.cj.jdbc.Driver">property> <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/eesy_spring?serverTimezone=UTC">property> <property name="user" value="root">property> <property name="password" value="qy914924771314b">property> bean> <bean id="connectionUtils" class="com.itheima.utils.ConnectionUtils"> <property name="dataSource" ref="dataSource">property> bean> <bean id="txManager" class="com.itheima.utils.TransactionManager"> <property name="connectionUtils" ref="connectionUtils">property> bean>beans>