1.看图
2.实体类Account:
package com.domin; 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 + '}'; } }
3.业务层接口IAccountService
package com.service; import com.domin.Account; import java.util.List; //账户的业务层接口 public interface IAccountService { /*查询所有*/ List<Account> findAllAccount(); /*查询一个*/ Account findAccountById(Integer accountId); /*保存操作*/ void saveAccount(Account account); /*更新操作*/ void updateAccout(Account account); /*删除操作*/ void delectAccout(Integer accountId); /*事务的解说*/ /*转账操作 * 转账源头名称 * 转账目的名称 * 转账金额 * */ /*被表现层调用,业务层写好参数:功能描述*/ void transfer(String sourceName , String targetName , Float money); }
4.业务层实现类:AccountServiceIpm
(包含事务的一整套操作)
package com.service.Ipm; import com.dao.IAccountDao; import com.domin.Account; import com.service.IAccountService; import com.utils.TransactionManager; import java.util.List; /*账户的实现类*/ public class AccountServiceIpm implements IAccountService { private IAccountDao accountDao; /*使用事务管理:那就注入实例对象,来使用*/ private TransactionManager txManager; //accountDao的set方法 public void setAccountDao(IAccountDao accountDao) { this.accountDao = accountDao; } /*注入灵魂*/ public void setTxManager(TransactionManager txManager) { this.txManager = txManager; } @Override public List<Account> findAllAccount() { try{ //1.开启事务(都是持久层写的方法) txManager.beginTransaction(); //2.执行操作 List<Account> accounts = accountDao.findAllAccount(); //3.提交事物 txManager.commit(); //4.返回结果 return accounts; }catch (Exception e){ //5.回滚操作 txManager.rollback(); throw new RuntimeException(e); }finally { //6.释放连接 txManager.release(); } } @Override 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(); } } @Override 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(); } } @Override public void updateAccout(Account account) { try{ //1.开启事务 txManager.beginTransaction(); //2.执行操作 accountDao.updateAccout(account); //3.提交事物 txManager.commit(); }catch (Exception e){ //4.回滚操作 txManager.rollback(); }finally { //5.释放连接 txManager.release(); } } @Override public void delectAccout(Integer accountId) { try{ //1.开启事务 txManager.beginTransaction(); //2.执行操作 accountDao.delectAccout(accountId); //3.提交事物 txManager.commit(); }catch (Exception e){ //4.回滚操作 txManager.rollback(); }finally { //5.释放连接 txManager.release(); } } /*转账*/ @Override 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); int i = 1/0; //2.5.更新转出账户 accountDao.updateAccout(source); //2.6.更新转入账户 accountDao.updateAccout(target); //3.提交事物 txManager.commit(); }catch (Exception e){ //4.回滚操作 txManager.rollback(); /*打出异常*/ e.printStackTrace(); }finally { //5.释放连接 txManager.release(); } } }
5.持久层实现类AccountDaoImp:
package com.dao.Ipm; import com.dao.IAccountDao; import com.domin.Account; import com.utils.ConnectionUtils; import org.apache.commons.dbutils.QueryRunner; import org.apache.commons.dbutils.handlers.BeanHandler; import org.apache.commons.dbutils.handlers.BeanListHandler; import java.util.List; /*账户的持久层实现类*/ public class AccountDaoImp implements IAccountDao { private QueryRunner runner; /*让spring容器提供的set方法*/ /*使用runner对象*/ private ConnectionUtils connectionUtils; public void setRunner(QueryRunner runner) { this.runner = runner; } /*注入ConnectionUtils对象,脱离直接使用的dataSource,而是从绑定的线程取*/ public void setConnectionUtils(ConnectionUtils connectionUtils) { this.connectionUtils = connectionUtils; } @Override public List<Account> findAllAccount() { /*泛型使用字解码创建对象*/ try{ return runner.query(connectionUtils.getThreadConnection(),"select * from account",new BeanListHandler<Account>(Account.class)); }catch (Exception e){ throw new RuntimeException(e); } } @Override public Account findAccountById(Integer accountId) { try{ return runner.query(connectionUtils.getThreadConnection(),"select * from account where id = ?",new BeanHandler<Account>(Account.class),accountId); }catch (Exception e){ throw new RuntimeException(e); } } @Override public void saveAccount(Account account) { try{ runner.update(connectionUtils.getThreadConnection(),"insert into account(name,money)value(?,?)",account.getName(),account.getMoney()); }catch (Exception e){ throw new RuntimeException(e); } } @Override public void updateAccout(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); } } @Override public void delectAccout(Integer accountId) { try{ runner.update(connectionUtils.getThreadConnection(),"delete from account where id=?",accountId); }catch (Exception e){ throw new RuntimeException(e); } } @Override public Account findAccountByName(String accountName) { try{ List<Account> accounts = runner.query(connectionUtils.getThreadConnection(),"select * from account 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); } } /* * 此时加入数据源之后:我们的dao已经有connect连接数据源的支持, * 同时它不再通过QueryRunner进行连接(注入)数据源 * 同时我们也通过工具类的方式将线程和连接进行绑定,并且编写了事务的管理 * * * * */ }
6.连接工具类ConnectionUtils:
package com.utils; /* * 连接的工具类:它用于从数据源中获取一个连接,并且实现和线程的绑定(只有统一调度才不会发生改变:转账一个变一个不变) * * * */ import javax.sql.DataSource; import java.sql.Connection; public class ConnectionUtils { private ThreadLocal<Connection> ts = new ThreadLocal<Connection>(); /*万一没有数据源,注入一个再说*/ private DataSource dataSource; public void setDataSource(DataSource dataSource) { this.dataSource = dataSource; } /* * 获取当前线程上的连接(写的一个方法) * 线程上有没有,没有就从数据源中取一个存进去,并且绑定 * */ public Connection getThreadConnection(){ try { //1.先从ThreadLocal上获取 Connection conn = ts.get(); //2.判断是否当前线程上有连接 if (conn == null) { //3.从数据源中获取一个连接,并且存入ThreadLocal中(也就是绑定线程) conn = dataSource.getConnection(); } //4.返回当前线程上的连接 return conn; }catch (Exception e){ throw new RuntimeException(e); } } /*连接之后不用了,就进行线程的解绑,线程还回池中*/ public void removeConnection(){ ts.remove(); } }
7.事务管理TransactionManager:
package com.utils; /* * 和事务管理相关的工具类,它包含了,开启事物,提交事物,回滚事物和释放连接 * * * */ /*怎么执行事务的整体呢??*/ /*注入连接ConnectionUtils事务的连接工具类*/ 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();//在命令行打印异常信息在程序中出错的位置及原因 } } /* * 自己理解:各种池:也就是说,当服务器启动的时候,如果需要使用该线程的时候,那么程序就会从线程池当中调出一个线程出来使用,这就叫做池 * 例如:数据库连接池、线程池 *线程池:当服务器tomcat一启动的时候,会初始化一堆的线程,也就是操作系统的线程管理,然后放到一个容器当中,接下来我们每次访问时,它都会从线程池中拿出一个线程提供给我们使用(如果关闭线程的时候并不是真正的关闭,而是把线程还回池中) *2.事务的控制都写完了,那么就可以去改造业务层的代码,使他们都在统一的线程下执行,以免发生操作不一样 * * */ }
8.配置文件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"> <!-- 配置IAccountService的实例--> <bean id="accountService" class="com.service.Ipm.AccountServiceIpm"> <!--注入类的实例accountDao,so创建持久层类的实例就不用写了--> <!--这时候还没有accountDao的创建实体类,这时候他会报错,所以引入一下创建accountDao对象,然后这里注入的地方就是实现了调用--> <property name="accountDao" ref="accountDao"></property> <!--注入是位于管理器txManager 它使用了了--> <property name="txManager" ref="txManager"></property> </bean> <bean id="accountDao" class="com.dao.Ipm.AccountDaoImp"> <property name="runner" ref="runner"></property> <!--因为他在使用--> <!--注入connectionUtils工具类--> <property name="connectionUtils" ref="connectionUtils"></property> </bean> <bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype"> <!--注入数据源ds--> <!-- <constructor-arg name="ds" ref="dataSource"></constructor-arg>--> </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://127.0.0.1:3306/eesy"></property> <property name="user" value="root"></property> <property name="password" value="123456"></property> </bean> <!--配置工具类ConnectionUtils--> <!--在连接事务中注入dataSource,以方便有时不用,没有时拿出一个使用--> <bean id="connectionUtils" class="com.utils.ConnectionUtils"> <property name="dataSource" ref="dataSource"></property> </bean> <!--配置管理事物的工具类--> <bean id="txManager" class="com.utils.TransactionManager"> <!--里面使用到了connectionUtils--> <property name="connectionUtils" ref="connectionUtils"></property> </bean> </beans>
9.依赖pom.xml
<!--依赖添加--> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.0.2.RELEASE</version> </dependency> <dependency> <groupId>commons-dbutils</groupId> <artifactId>commons-dbutils</artifactId> <version>1.6</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> <!--测试类--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.0.2.RELEASE</version> <scope>test</scope> </dependency> </dependencies>
10.测试TestCode:
package com.itheima;
import com.domin.Account;
import com.service.IAccountService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.util.List;
@RunWith(SpringJUnit4ClassRunner.class)
/*说明位置*/
@ContextConfiguration(locations ="classpath:bean.xml")
/*pom.xml加上spring-test依赖*/
public class TestCode {
@Autowired
private IAccountService as;
@Test
public void testTransfer(){
as.transfer("qi","hello",1000f);
// as.transfer("qi","hello",(float)1000.0);
// 两种强转一样
}
}
总结:初步理解:理解事务之前,先讲一个你日常生活中最常干的事:取钱。
比如你去取1000块钱,大体有:你去取钱,取1000,银行卡扣掉1000元钱;然后机子出1000元钱。这两个步骤必须是要么都执行要么都不执行。如果银行卡扣除了1000块但是ATM出钱失败的话,你将会损失1000元;如果银行卡扣钱失败但是机子却出了1000块,那么银行将损失1000元。所以,如果一个步骤成功另一个步骤失败对双方都不是好事,如果不管哪一个步骤失败了以后,整个取钱过程都能回滚,也就是完全取消所有操作的话,这对双方都是极好的。
事务就是用来解决类似问题的。事务是一系列的动作,它们综合在一起才是一个完整的工作单元,这些动作必须全部完成,如果有一个失败的话,那么事务就会回滚到最开始的状态,仿佛什么都没发生过一样。 在企业级应用程序开发中,事务管理必不可少的技术,用来确保数据的完整性和一致性。
程序步骤:
1.连接工具类的编写:如果没有连接,取一个线程来连接and关闭连接(扔回池中)
2.事务的管理类编写:使用private ConnectionUtils connectionUtils;注入连接(绑定)到线程,实现事务的操作
3.先在业务层接口写入:public void transfer(String sourceName , String targetName , Float money);
4.业务层实现类:通过注入一个事务的对象来管理每一个操作,使它是同一个线程中的事务private TransactionManager txManager;
5.持久层接口:/*被业务层调用。持久层应写好方法*/ Account findAccountByName(String accountName);
6.持久层实现类:这时候已经不再往QueryRunner中注入了,避免事务每次都从该数据池中获取,所以在bean.xml中,取消<constructor-arg name="ds" ref="dataSource"></constructor-arg>的注入,而改成在工具类中注入
<!--配置工具类ConnectionUtils--> <!--在连接事务中注入dataSource,以方便有时不用,没有时拿出一个使用--> <bean id="connectionUtils" class="com.utils.ConnectionUtils"> <property name="dataSource" ref="dataSource"></property> </bean>
7.配置文件的注入:
<!--注入是位于管理器txManager 它使用了了--> <property name="txManager" ref="txManager"></property>注入在业务层:因为在业务层中使用了
<!--注入connectionUtils工具类--> <property name="connectionUtils" ref="connectionUtils"></property>在持久层使用了连接工具类
工具类的注入:
<!--配置工具类ConnectionUtils--> <!--在连接事务中注入dataSource,以方便有时不用,没有时拿出一个使用--> <bean id="connectionUtils" class="com.utils.ConnectionUtils"> <property name="dataSource" ref="dataSource"></property> </bean> <!--配置管理事物的工具类--> <bean id="txManager" class="com.utils.TransactionManager"> <!--里面使用到了connectionUtils--> <property name="connectionUtils" ref="connectionUtils"></property> </bean>
注:本文章只供自己学习,有上面错误的地方,请多多指教。其次代码过于臃肿,后面继续优化,只是理解一下事务的基本概念和使用