1什么是事务
一件事情有n个组成单元,要么这n个组成单元同时成功,要么n个单元就同时失败,
就是将n个组成单元放到一个事务中。
银行转账例子:
表名account:
id |
账户名 |
存款 |
1 |
test1 |
1000 |
2 |
test2 |
5000 |
现在,test1向test2借钱4000,
银行转账:
第一步: update account set 存款=存款-4000 where id=2;
第一步做完,出现异常,则下面代码不执行:
第二步: update account set 存款=存款+4000 where id=1;
如果没有事务,就会发生test2减少了4000块,test1也没有收到4000块。4000块不翼而飞了。
2 mysql的事务
默认的事务:一条sql语句就是一个事务,默认就开启事务并提交事务
手动事务:
1)显示开启事务:start transaction
2)事务提交:commit代表从开启事务到事务提交,中间的所有的sql都认为有效,真正的更新数据库
3)事务回滚:rollback 代表事务的回滚,从开启事务到事务回滚中间的所有的sql操作都认为无效,数据库没有被更新
Tips:MySQL存储引擎:
主要存储引擎:MyISAM、InnoDB、MEMORY和MERGE
MySQL5.5以后默认使用InnoDB存储引擎,其中InnoDB和BDB提供事务安全表,其它存储引擎都是非事务安全表。
若要修改默认引擎,可以修改配置文件中的default-storage-engine。
命令show variables like 'default_storage_engine'; 查看当前数据库的默认引擎。
命令show engines和show variables like 'have%'; 可以列出当前数据库所支持的引擎。
所以,MySQL要安装5.5以后的版本才能使用事务。
例:
第一次查出的结果,没有真正进入数据库,只是在当前事务的缓存中。
3 JDBC事务操作
默认是自动事务:
执行sql语句:executeUpdate() ---- 每执行一次executeUpdate方法代表事务自动提交
通过jdbc的API手动事务:
开启事务:conn.setAutoComnmit(false);
提交事务:conn.commit();
回滚事务:conn.rollback();
注意:控制事务的connnection必须是同一个
执行sql的connection与开启事务的connnection必须是同一个才能对事务进行控制
例:
数据库中数据:
import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import java.sql.Statement; public class JDBCdemo { public static void main(String[] args){ Connection conn=null; try{ //1.注册驱动 Class.forName("com.mysql.jdbc.Driver"); //2.获得连接对象 String url="jdbc:mysql://localhost:3306/market0929?useUnicode=true&characterEncoding=UTF-8"; String username="root"; String password="123456"; conn=DriverManager.getConnection(url,username,password); //3.获得语句执行平台 Statement sta=conn.createStatement(); //4.开启事务 conn.setAutoCommit(false); //5.Sql语句 String sql="UPDATE account SET money=money-2000 WHERE aid=1"; sta.executeUpdate(sql); }catch(Exception ex){ //7.回滚事务 try { conn.rollback(); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } ex.printStackTrace(); }finally{ //6.提交事务 try { conn.commit(); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }
加一条错误代码:
数据没有被继续减少:
4 DBUtils事务操作
有参构造:QueryRunner runner = new QueryRunner(DataSource dataSource);
使用无Connection参数的方法操作数据库
无参构造:QueryRunner runner = new QueryRunner();
使用有Connection参数的方法操作数据库
5 ThreadLocal
ThreadLocal 实例通常是类中的 private static 字段,它们希望将状态与某一个线程(例如,用户 ID 或事务 ID)相关联。
Tips:
因为是单线程程序,可以让线程携带着连接对象,
这样就可以使service层和dao层用的是一个Connection对象了,
在这里,ThreadLocal 类似一个map,key是线程名,值是Connection对象。
6实例:建一个web项目Transfer实现转帐功能
transfer.jsp:
<form action="${pageContext.request.contextPath}/TransferServlet" method="post"> 转出账户:<input type="text" name="out"><br> 转入账户:<input type="text" name="in"><br> 金额:<input type="text" name="money"><br> <input type="submit" value="提交"><br> </form>
TransferServlet:
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("utf-8"); //获取转出、转入账户以及金额 String out=request.getParameter("out"); String in=request.getParameter("in"); String moneyStr=request.getParameter("money"); double money=Double.parseDouble(moneyStr); //字符串转换成数字 //调用Service层方法 boolean flag=accountService.transfer(out, in, money); response.setContentType("text/html;charset=utf-8"); if(flag){ response.getWriter().write("转账成功!"); }else{ response.getWriter().write("转账失败!"); } }
DBUtils工具类:
import java.sql.Connection; import java.sql.SQLException; import javax.sql.DataSource; import org.apache.commons.dbcp.BasicDataSource; public class DBUtils { public static final String DRIVER = "com.mysql.jdbc.Driver"; public static final String URL = "jdbc:mysql://127.0.0.1:3306/market0929?useUnicode=true&characterEncoding=UTF-8"; public static final String USERNAME = "root"; public static final String PASSWORD = "123456"; /* * 创建连接池BasicDataSource */ public static BasicDataSource dataSource = new BasicDataSource(); //创建ThreadLocal对象 private static ThreadLocal<Connection> tl=new ThreadLocal<Connection>(); //静态代码块 static { //对连接池对象 进行基本的配置 dataSource.setDriverClassName(DRIVER); // 这是要连接的数据库的驱动 dataSource.setUrl(URL); //指定要连接的数据库地址 dataSource.setUsername(USERNAME); //指定要连接数据的用户名 dataSource.setPassword(PASSWORD); //指定要连接数据的密码 } /* * 返回连接池对象 */ public static DataSource getDataSource(){ return dataSource; } //从连接池中取一个连接对象 public static Connection getConn(){ Connection conn=null; try { conn=dataSource.getConnection(); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } return conn; } //获取当前线程上绑定的Connection对象 public static Connection getCurrentConn(){ //获取线程绑定的conn对象 Connection conn=tl.get(); //判断线程是否有绑定的对象 if(conn==null){ conn=getConn(); tl.set(conn); } return conn; } //开启事务 public static void startTranscation(){ //获取当前线程绑定的conn对象 Connection conn=getCurrentConn(); //开启事务 try { conn.setAutoCommit(false); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } } //提交事务 public static void commit(){ //获取当前线程绑定的conn对象 Connection conn=getCurrentConn(); //提交事务 try { conn.commit(); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } } //回滚事务 public static void rollback(){ //获取当前线程绑定的conn对象 Connection conn=getCurrentConn(); //回滚事务 try { conn.rollback(); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
AccountDao:
import java.sql.Connection; import java.sql.SQLException; import org.apache.commons.dbutils.QueryRunner; import com.oracle.tools.DBUtils; public class AccountDao { //转出 public void substractMoney(String out,double money) throws SQLException{ Connection conn=DBUtils.getCurrentConn(); QueryRunner qr=new QueryRunner(); String sql="update account set money=money-? where aname=?"; qr.update(conn,sql,money,out); } //转入 public void addMoney(String in,double money) throws SQLException{ Connection conn=DBUtils.getCurrentConn(); QueryRunner qr=new QueryRunner(); String sql="update account set money=money+? where aname=?"; qr.update(conn,sql,money,in); } }
AccountService:
import com.oracle.dao.AccountDao; import com.oracle.tools.DBUtils; public class AccountService { private AccountDao accountDao=new AccountDao(); //转账 public boolean transfer(String out,String in,double money){ boolean flag=true; try { //开启事务 DBUtils.startTranscation(); accountDao.substractMoney(out, money); accountDao.addMoney(in, money); } catch (Exception e) { // TODO Auto-generated catch block flag=false; //事务回滚 DBUtils.rollback(); e.printStackTrace(); }finally{ //事务提交 DBUtils.commit(); } return flag; } }
加一条错误代码:
注意:
1)Dao层只写事务的组成单元
2)Service层不能出现Connection,因为与数据库的相关操作都要放在dao层
3)把事务开启、提交、回滚全部封装在工具类中,在Service层直接调用方法,就不会出现Connection相关内容了。
4)写好工具类后,dao层QueryRunner runner的就要用无参构造了,Connection参数从工具类中获取当前线程上的Connection对象
6事务的特性
原子性(Atomicity):原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。
一致性(Consistency):一个事务中,事务前后数据的完整性必须保持一致。
隔离性(Isolation):多个事务,事务的隔离性是指多个用户并发访问数据库时,一个用户的事务不能被其它用户的事务所干扰,多个并发事务之间数据要相互隔离。(用同步代码块实现)
持久性(Durability):持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响。