事务的提交与回滚

一.事务的概念

事务就是某一组操作,要么都执行,要么都不执行。

比如你要去银行给朋友转钱,必然存在着从你的账户里扣除一定的金额和向你朋友账户里增加相等的金额这两个操作,这两个操作是不可分割的,无论是哪一个操作的失败,成功的操作也要恢复至最初的状态才能使银行和用户双方都满意,而这样的行为就牵扯到了事务的回滚。

事务的特性:
原子性:一个事务是不可分割的最小单位。
一致性:一个事务在执行之前和执行之后都必须处于一致性状态(比如说转账,前后两个账户的总金额是不会改变的)。
隔离性:多个并发事务之间的操作不会互相干扰。

持久性:提交的事务会使得修改的数据是永久的。

二.事务的实例分析

例子:向员工表中添加一个员工的记录。

1.首先需要一个员工实体:

publicclassEmp{privateintempId;privateStringename;privateStringsex;privateDatebirthday;publicintgetEmpId(){returnempId;}publicvoidsetEmpId(intempId){this.empId=empId;}publicStringgetEname(){returnename;}publicvoidsetEname(Stringename){this.ename=ename;}publicStringgetSex(){returnsex;}publicvoidsetSex(Stringsex){this.sex=sex;}publicDategetBirthday(){returnbirthday;}publicvoidsetBirthday(Datebirthday){this.birthday=birthday;}publicEmp(intempId,Stringename,Stringsex,Datebirthday){super();this.empId=empId;this.ename=ename;this.sex=sex;this.birthday=birthday;}publicEmp(){super();}@OverridepublicStringtoString(){return"Emp[empId="+empId+",ename="+ename+",sex="+sex+",birthday="+birthday+"]";}publicEmp(Stringename,Stringsex,Datebirthday){super();this.ename=ename;this.sex=sex;this.birthday=birthday;}}

2.还需要封装一些数据库的连接:

publicclassJDBCUtil{Connectionconn;Statementstm;ResultSetrs;privatestaticJDBCUtilinstance;privatestaticPropertiespro=newProperties();static{try{InputStreamin=JDBCUtil.class.getClassLoader().getResourceAsStream("jdbc.properties");pro.load(in);}catch(IOExceptione){//TODOAuto-generatedcatchblockthrownewRuntimeException("读取配置文件失败!");}}privateJDBCUtil(){}publicstaticJDBCUtilgetInstance(){if(instance==null){synchronized(JDBCUtil.class){if(instance==null){instance=newJDBCUtil();}}}returninstance;}publicConnectiongetConnection()throwsClassNotFoundException,SQLException{Class.forName(pro.getProperty("driver"));Connectionconn=DriverManager.getConnection(pro.getProperty("url"),pro.getProperty("username"),pro.getProperty("password"));returnconn;}publicvoidrelease(ResultSetrs,Statementst,Connectionconn){if(rs!=null){try{rs.close();}catch(SQLExceptione){//TODOAuto-generatedcatchblocke.printStackTrace();}finally{if(st!=null){try{st.close();}catch(SQLExceptione){//TODOAuto-generatedcatchblocke.printStackTrace();}finally{if(conn!=null){try{conn.close();}catch(SQLExceptione){//TODOAuto-generatedcatchblocke.printStackTrace();}}}}}}}}

3.配置文件:(在使用时注意更改为自己的数据库)

driver=com.mysql.jdbc.Driverurl=jdbc:mysql://127.0.0.1:3306/java?characterEncoding=utf8username=rootpassword=root

4.添加员工:

publicclassTestTransacion{Connectionconn;Connectionconn1;Connectionconn2;PreparedStatementpstm;PreparedStatementpstm1;ResultSetrs;publicvoidaddEmp(Empemp){try{conn=JDBCUtil.getInstance().getConnection();conn.setAutoCommit(false);//设置事务非自动提交Stringsql="insertintoemp(ename,sex,birthday)values(?,?,?)";pstm=conn.prepareStatement(sql,Statement.RETURN_GENERATED_KEYS);//在sql未执行之前,进行预编译pstm.setString(1,emp.getEname());pstm.setString(2,emp.getSex());pstm.setDate(3,newjava.sql.Date(emp.getBirthday().getTime()));pstm.executeUpdate();intid=0;rs=pstm.getGeneratedKeys();//获取由于执行此Statement对象而创建的所有自动生成的键if(rs.next()){id=rs.getInt(1);}System.out.println(id);conn1=JDBCUtil.getInstance().getConnection();sql="select*fromempwhereempid="+id;pstm1=conn1.prepareStatement(sql);rs=pstm1.executeQuery();if(rs.next()){System.out.println(rs.getString("ename")+"11");}conn.commit();conn2=JDBCUtil.getInstance().getConnection();sql="select*fromempwhereempid="+id;pstm1=conn2.prepareStatement(sql);rs=pstm1.executeQuery();if(rs.next()){System.out.println(rs.getString("ename")+"22");}}catch(ClassNotFoundExceptione){//TODOAuto-generatedcatchblocke.printStackTrace();}catch(SQLExceptione){//TODOAuto-generatedcatchblocke.printStackTrace();}finally{JDBCUtil.getInstance().release(rs,pstm,conn);if(pstm1!=null){try{pstm1.close();conn1.close();conn2.close();}catch(SQLExceptione){//TODOAuto-generatedcatchblocke.printStackTrace();}}}}publicstaticvoidmain(String[]args){TestTransacionttst=newTestTransacion();Empemp=newEmp("lucy","女");ttst.addEmp(emp);}}

在添加员工时共声明了三个Connection变量,在插入之前先设置事务为非自动提交,之后用到了Statement的方法getGenerateKeys()来获取刚刚插入的数据所自动生成的键值(即empId值)可用于下面的查找,下面共有两个查找语句,中间我们用commit提交了事务,一个是提交前的查找,一个是提交后的查找,当执行完时,我们会发现结果是lucy22,为什么呢?因为提交之前数据库里面还没有东西肯定是查不到的。当然切记需要用到不同的coon,因为如果查找时用的coon和插入时的一样的话,事务没有提交也是可以查到的,因为用到的对象是同一个,里面已经有数据了!!!!

三.事务的回滚

回滚:假设A给B转账,A的money--,B的money++,两者是一个整体。只有两个操作都成功完成了,事务才会提交,否则都会回滚到原来的状态。

在emp表里再添加一个salary属性,把员工编号为1的员工的工资更改为1000,并且判断员工编号为3的员工的工资是否大于5000,如果小于5000则加1000,这两个操作为一个事务,要想事务能够正常的提交的条件是员工编号为3的员工的工资小于5000,工资加了1000,此时员工编号为1的员工的工资更改为1000.如果员工编号为3的员工的工资大于5000则中断程序,抛出异常,即操作2没有顺利完成,事务不应提交,而应该回滚到最初的状态。

当然,不是所有的时候都需要恢复最初的状态的,所以有时候就会用到自己设置回滚点,上述程序中注释的部分就是自己添加了如果事务没有正常提交想要恢复到程序执行到哪个位置的回滚点。

publicclassTestTx{publicstaticvoidmain(String[]args)throwsSQLException{Connectionconn=null;PreparedStatementpstm=null;ResultSetrs=null;//Savepointsp=null;try{conn=JDBCUtil.getInstance().getConnection();conn.setAutoCommit(false);Stringsql="updateempsetsalary=1000whereempid=1";pstm=conn.prepareStatement(sql);pstm.executeUpdate();//sp=conn.setSavepoint();//设置回滚点sql="selectsalaryfromempwhereempid=4";pstm=conn.prepareStatement(sql);rs=pstm.executeQuery();if(rs.next()){doublesalary=rs.getDouble(1);if(salary>5000){System.out.println("薪资大于5000");thrownewRuntimeException("薪资大于5000");}}sql="updateempsetsalary=salary+1000whereempid=4";pstm=conn.prepareStatement(sql);pstm.executeUpdate();conn.commit();}catch(RuntimeExceptione){/*if(conn!=null&&sp!=null){conn.rollback(sp);}conn.commit();throwe;*/e.printStackTrace();}catch(ClassNotFoundExceptione){e.printStackTrace();}catch(SQLExceptione){if(conn!=null){conn.rollback();}throwe;}finally{JDBCUtil.getInstance().release(rs,pstm,conn);}}}