事务示例之转账
1.创建数据库,填充数据:
数据库名称db_account,表名tb_account,插入几条数据。
2.配置数据库信息
在项目的WebContent/META-INF目录下创建一个context.xml文件。如图:
在context.xml文件中配置:
<?xml version="1.0" encoding="UTF-8"?>
<Context>
<Resource name="jdbc/account" auth="Container" type="javax.sql.DataSource"
maxActive="100" maxIdle="30" maxWait="10000"
username="root" password="root" driverClassName="com.mysql.jdbc.Driver"
url="jdbc:mysql://localhost:3306/db_account"/>
</Context>
这里采用的是Javaweb自带的DBCP配置,详细参考Javaweb配置常用的数据源配置
3.创建项目结构
如图:
AccountDao
public interface AccountDao { /** * 更新账户 * @param account */ public boolean updateAccount(Account account) throws SQLException; /** * 查找账户 * @param account * @return */ public Account findAccountByName(String account); }
AccountDaoImpl
public class AccountDaoImpl implements AccountDao{ public AccountDaoImpl() { } @Override public boolean updateAccount(Account account) { Connection conn = ManagerThreadLocal.getConn(); if(conn==null){ throw new NullPointerException("Connection is null"); } PreparedStatement ps = null; try { ps = conn.prepareStatement("update tb_account set money=? where name=?"); if(ps==null){ throw new NullPointerException("PreparedStatement is null"); } ps.setDouble(1, account.getMoney()); ps.setString(2, account.getName()); int i = ps.executeUpdate(); return i>0; } catch (SQLException e) { e.printStackTrace(); } return false; } @Override public Account findAccountByName(String account) { Connection conn = ManagerThreadLocal.getConn(); if(conn==null){ throw new NullPointerException("Connection is null"); } Account a = null; PreparedStatement ps = null; ResultSet rs = null; try { ps = conn.prepareStatement("select name,money from tb_account where name=?"); if(ps==null){ throw new NullPointerException("PreparedStatement is null"); } ps.setString(1, account); rs = ps.executeQuery(); if(rs==null){ throw new NullPointerException("ResultSet is null"); } if(rs.next()){ a = new Account(); a.setName(rs.getString(1)); a.setMoney(rs.getDouble(2)); return a; } } catch (SQLException e) { e.printStackTrace(); } return a; } }
Account
public class Account { private int id; private String name; private double money; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public double getMoney() { return money; } public void setMoney(double money) { this.money = money; } @Override public String toString() { return "Account [id=" + id + ", name=" + name + ", money=" + money + "]"; } }
AccountService
public interface AccountService { /** * 转账 * @param fromName * @param toName * @param money */ public void transfer(String fromName,String toName,double money); }
AccountServiceImpl
public class AccountServiceImpl implements AccountService{ @Override public void transfer(String fromName, String toName, double money) { AccountDao accountDao = new AccountDaoImpl(); Account fromAccount = accountDao.findAccountByName(fromName); if(fromAccount==null){ throw new RuntimeException("你的账户不存在"); } if(money > fromAccount.getMoney()){ throw new RuntimeException("你的账户的钱不够"); } fromAccount.setMoney(fromAccount.getMoney() - money); Account toAccount = accountDao.findAccountByName(toName); if(toAccount==null){ throw new RuntimeException("对方的账户不存在"); } toAccount.setMoney(toAccount.getMoney() + money); //开启事务 ManagerThreadLocal.startTransaction(); try { accountDao.updateAccount(fromAccount); //int i = 10/0; //测试事务代码 accountDao.updateAccount(toAccount); //提交事务 ManagerThreadLocal.commit(); } catch (SQLException e) { //事务回滚 ManagerThreadLocal.rollback(); e.printStackTrace(); }finally { ManagerThreadLocal.close(); } } }
TransferServlet
@WebServlet({ "/TransferServlet", "/transfer" }) public class TransferServlet extends HttpServlet { private static final long serialVersionUID = 1L; public TransferServlet() { super(); } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { AccountService accountService = new AccountServiceImpl(); accountService.transfer("10010", "10000", 100); } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } }
ManagerThreadLocal
public class ManagerThreadLocal { private static ThreadLocal<Connection> tl = new ThreadLocal<>(); private static Connection getConnection(){ Connection conn = null; try { Context context = new InitialContext(); DataSource dataSource = (DataSource) context.lookup("java:/comp/env/jdbc/account"); conn = (Connection) dataSource.getConnection(); } catch (SQLException e) { e.printStackTrace(); } catch (NamingException e) { e.printStackTrace(); } return conn; } public static Connection getConn(){ Connection conn = null; conn = tl.get(); if(conn==null){ conn = getConnection(); tl.set(conn); } return conn; } public static void startTransaction(){ try { getConn().setAutoCommit(false); } catch (SQLException e) { e.printStackTrace(); } } public static void commit(){ try { getConn().commit(); } catch (SQLException e) { e.printStackTrace(); } } public static void rollback(){ try { getConn().rollback(); } catch (SQLException e) { e.printStackTrace(); } } public static void close(){ try { getConn().close(); tl.remove(); } catch (SQLException e) { e.printStackTrace(); } } }
ManagerThreadLocal的作用是通过ThreadLocal类(内部是由Map实现)缓存一个Connection对象,保证连接的conn对象是同一个,将Connection对象解耦出来,实现事务操作(保证了事务的conn对象是同一个)
执行结果:注释AccountServiceImpl类中transfer方法中的如下代码,数据库数据同时改变,注释打开,数据保持不变。
//int i = 10/0; //测试事务代码