本案例为基于接口的动态代理:
被代理类为AccountServiceImpl,被代理对象为容器中id为accountService的Bean对象,代理对象为容器中id为ProxyAccountService的Bean对象。
1、 创建maven的jar工程,添加依赖jar包
<dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.0.2.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.0.2.RELEASE</version> </dependency> <dependency> <groupId>commons-dbutils</groupId> <artifactId>commons-dbutils</artifactId> <version>1.4</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.6</version> </dependency> <dependency> <groupId>c3p0</groupId> <artifactId>c3p0</artifactId> <version>0.9.1.2</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> </dependencies>
2、创建数据库eesy下的account1表
3、创建Account实体类
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 + '}'; } }
4、写业务层接口IAccountService
public interface IAccountService { void transfer(String sourceName,String targetName,Float money); void updateAccount(Account account); }
5、 写业务层接口的实现类,在类中添加set方法以XML的方式注入依赖
public class AccountServiceImpl implements IAccountService{ private IAccountDao accountDao; public void setAccountDao(IAccountDao accountDao) { this.accountDao = accountDao; } @Override public void updateAccount(Account account) { accountDao.updateAccount(account); } @Override public void transfer(String sourceName, String targetName, Float money) { System.out.println("transfer...."); //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); } }
service实现类中不再需要TransactionManager,这样事务控制和业务层的方法进行了真正的分离,也解除了方法之间的依赖。
6、创建持久层接口IAccountDao
public interface IAccountDao { Account findAccountByName(String accountName); void updateAccount(Account account); }
7、创建持久层实现类AccountDaoImpl
public class AccountDaoImpl implements IAccountDao { private QueryRunner runner; public void setRunner(QueryRunner runner) { this.runner = runner; } private ConnectionUtils connectionUtils; public void setConnectionUtils(ConnectionUtils connectionUtils) { this.connectionUtils = connectionUtils; } @Override public Account findAccountByName(String accountName) { try{ List<Account> accounts = runner.query(connectionUtils.getThreadConnection(), "select * from account1 where name = ? ", new BeanListHandler<Account>(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); } } @Override public void updateAccount(Account account) { try{ runner.update(connectionUtils.getThreadConnection(),"update account1 set name=?,money=? where id=?",account.getName(),
account.getMoney(),
account.getId()); }catch (Exception e) { throw new RuntimeException(e); } } }
四次与数据库的交互由同一个Connection来控制,即只有一个Connection对象,要成功则这些操作一起成功,要失败则一起失败。
8、创建ConnectionUtils工具类(先从数据源中获取一个连接,并且把连接存入ThreadLocal中从而实现连接与线程的绑定)
public class ConnectionUtils { private ThreadLocal<Connection> tl = new ThreadLocal<Connection>(); private DataSource dataSource; public void setDataSource(DataSource dataSource) { this.dataSource = dataSource; } // 获取当前线程上的连接 public Connection getThreadConnection() { try{ //1.先从ThreadLocal上获取连接 Connection conn = tl.get(); //2.判断当前线程上是否有连接 if (conn == null) { //3.从数据源(连接池)中获取一个连接, conn = dataSource.getConnection(); //并且存入ThreadLocal中 tl.set(conn); } //4.返回当前线程上的连接 return conn; }catch (Exception e){ throw new RuntimeException(e); } } //把连接和线程解绑 public void removeConnection(){ tl.remove(); } }
9、创建事务管理相关的工具类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(); } } }
10、创建BeanFactory类
通过BeanFactory类的普通方法来创建service的代理对象proxyAccountService。然后proxyAccountService代理对象调用IAccountService接口的任何方法如transfer方法,都会被拦截并进行增强,从而实现控制事务。service实现类中不再需要TransactionManager,这样事务控制和业务层的方法进行了真正的分离,也解除了方法之间的依赖。
被代理类为AccountServiceImpl,被代理对象为容器中id为accountService的Bean对象,代理对象为容器中id为ProxyAccountService的Bean对象。
现在我们模拟一个工厂类,类中提供了一个方法,通过这个方法我们可以得到一个有事务支持的proxyAccountService,
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; } public IAccountService getAccountService() {//获取Service代理对象 return (IAccountService)Proxy.newProxyInstance(accountService.getClass().getClassLoader(), accountService.getClass().getInterfaces(), new InvocationHandler() { // 添加事务的支持 @Override 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 { txManager.beginTransaction();//1.开启事务 rtValue = method.invoke(accountService, args);//2.执行操作 txManager.commit();//3.提交事务 return rtValue;//4.返回结果 } catch (Exception e) { txManager.rollback();//5.回滚操作 throw new RuntimeException(e); } finally { txManager.release();//6.释放连接 } } }); } }
当匿名内部类访问外部成员变量时,外部成员要求是final修饰的。
11、创建bean.xml文件,导入约束,配置bean对象
使用BeanFactory类中的普通方法创建代理对象,并存入spring容器。即先反射创建BeanFactory类的对象,再通过该对象调用类中的方法来创建proxyAccountService对象。
<?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"> <!--配置代理的service--> <bean id="proxyAccountService" factory-bean="beanFactory" factory-method="getAccountService"></bean> <!--配置beanfactory--> <bean id="beanFactory" class="com.itheima.factory.BeanFactory"> <!-- 注入service --> <property name="accountService" ref="accountService"></property> <!-- 注入事务管理器 --> <property name="txManager" ref="txManager"></property> </bean> <!--配置Dao对象--> <bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl"> <!-- 注入QueryRunner --> <property name="runner" ref="runner"></property> <!-- 注入ConnectionUtils --> <property name="connectionUtils" ref="connectionUtils"></property> </bean> <!--配置QueryRunner--> <bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype"></bean> <!-- 配置Connection的工具类 ConnectionUtils --> <bean id="connectionUtils" class="com.itheima.utils.ConnectionUtils"> <!-- 注入数据源--> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 配置事务管理器--> <bean id="txManager" class="com.itheima.utils.TransactionManager"> <!-- 注入ConnectionUtils --> <property name="connectionUtils" ref="connectionUtils"></property> </bean> <!-- 配置Service --> <bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"> <!-- 注入dao --> <property name="accountDao" ref="accountDao"></property> </bean> <!-- 配置数据源 --> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <!--连接数据库的必备信息--> <property name="driverClass" value="com.mysql.jdbc.Driver"></property> <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/eesy"></property> <property name="user" value="root"></property> <property name="password" value="123456"></property> </bean> </beans>
poxyAccountService与accountService是有区别的,accountService就是一个service对象,没有事务支持,而poxyAccountService是有事务支持的。现在容器中有两个AccountService类型的对象,他们两个都实现了IAccountService接口,一个通过动态代理实现的,一个本就是实现类。
12、使用Junit单元测试,测试我们的配置
@RunWith(SpringJUnit4ClassRunner.class)//替换成能创建容器的main方法 @ContextConfiguration(locations = "classpath:bean.xml")//通过XML方式创建容器 public class AccountServiceTest { @Qualifier("proxyAccountService") @Autowired private IAccountService as; @Test public void testTransfer(){ as.transfer("aaa","bbb",100f); } }
由于IOC容器中有两个IAccountService类型的对象,故需要使用@Qualifier注解。
结果:
当没有添加int i=1/0时,正常转账。 如果在更新转出账户之后出现了异常,则转出账户的钱没少100,而转入账户的钱未增加100.保证了事务的一致性,
总结:
使用了动态代理之后,重复代码都消失了,方法之间的依赖也解除了,同时提高了开发的效率。
本案例不仅保证了多条sql语句共用同一个事务,保证了事务的一致性,而且通过动态代理(创建BeanFactory类)实现了业务逻辑与事务控制代码的分离。