事务的定义:
事务(Transaction):是并发控制的单元,是用户定义的一个操作序列。这些操作要么都做,要么都不做,是一个不可分割的工作单位。通过事务,sql server 能将逻辑相关的一组操作绑定在一起,以便服务器保持数据的完整性。
事务通常是以begin transaction开始,以commit或rollback结束:
Commint表示提交,即提交事务的所有操作。具体地说就是将事务中所有对数据的更新写回到磁盘上的物理数据库中去,事务正常结束。
Rollback表示回滚,即在事务运行的过程中发生了某种故障,事务不能继续进行,系统将事务中对数据库的所有已完成的操作全部撤消,滚回到事务开始的状态。
======================
要实现的事 : 批量更新商品库存,当库存为负数时,事务回滚!
个人实践总结 :
-
mysql中的字段不能为负值 : 在你定义的数字变量前 加上UNSIGNED 就不可以放负值了 ;
-
针对单一对象的update(bean)方法,我是采用[在ps.execute();提交操作前]抛出一个自定义异常,从而利用mysql的特性实现:如果提交前 报错 自动回滚;(直接return;掉也可以,前置打印错误说明;)
if (0 > bean.getStock())
throw new ProductDaoStockMinusException(bean.getId() + "库存不能为负数");
- 如果是多对象update(beans)方法,我采用事务回滚的机制;复杂的代码测试如下 :
private static void method3() {
ProductDAO dao = new ProductDAO();
Product bean = dao.get(93);
String sql = "update product set name= ?, subTitle=?, originalPrice=?,promotePrice=?,stock=?, cid = ?, createDate=? where id = ?";
try (Connection c = DBUtil.getConnection(); PreparedStatement ps = c.prepareStatement(sql)) {
c.setAutoCommit(false); // 取消自动提交
try {
// 案例一
bean.setStock(500);
if (0 > bean.getStock())
throw new ProductDaoStockMinusException(bean.getId() + "库存不能为负数");
ps.setString(1, bean.getName());
ps.setString(2, bean.getSubTitle());
ps.setInt(3, bean.getOriginalPrice());
ps.setInt(4, bean.getPromotePrice());
ps.setInt(5, bean.getStock());
ps.setInt(6, bean.getCategory().getId());
ps.setTimestamp(7, DateUtil.d2t(bean.getCreateDate()));
ps.setInt(8, bean.getId());
ps.execute(); // 执行
// 案例二
bean.setStock(-1550);
if (0 > bean.getStock())
throw new ProductDaoStockMinusException(bean.getId() + "库存不能为负数");
ps.setString(1, bean.getName());
ps.setString(2, bean.getSubTitle());
ps.setInt(3, bean.getOriginalPrice());
ps.setInt(4, bean.getPromotePrice());
ps.setInt(5, bean.getStock());
ps.setInt(6, bean.getCategory().getId());
ps.setTimestamp(7, DateUtil.d2t(bean.getCreateDate()));
ps.setInt(8, bean.getId());
ps.execute(); // 执行
// 案例三
bean.setStock(5500);
if (0 > bean.getStock())
throw new ProductDaoStockMinusException(bean.getId() + "库存不能为负数");
ps.setString(1, bean.getName());
ps.setString(2, bean.getSubTitle());
ps.setInt(3, bean.getOriginalPrice());
ps.setInt(4, bean.getPromotePrice());
ps.setInt(5, bean.getStock());
ps.setInt(6, bean.getCategory().getId());
ps.setTimestamp(7, DateUtil.d2t(bean.getCreateDate()));
ps.setInt(8, bean.getId());
ps.execute(); // 执行
} catch (ProductDaoStockMinusException e) {
c.rollback();// 回滚
System.out.println("出错的原因 :" + e.getMessage());
}
c.commit(); // 提交
} catch (SQLException e) {
e.printStackTrace();
}
}
======================
当然,对于事务的编写,也是要遵守一定的顺序的:
-
首先,设置事务的提交方式为非自动提交:conn.setAutoCommit(false);
-
接下来,将需要添加事务的代码放入try catch块中。
-
然后,在try块内添加事务的提交操作,表示操作无异常,提交事务。conn.commit();
-
尤其不要忘记,在catch块内添加回滚事务,表示操作出现异常,撤销事务:conn.rollback();
-
最后,设置事务提交方式为自动提交:conn.setAutoCommit(true);
-
这样,通过简单的几步,我们就可以完成对事务处理的编写了。
参考代码1:
private Connection conn = null;
private PreparedStatement ps = null;
try {
conn.setAutoCommit(false); //将自动提交设置为false
ps.executeUpdate("修改SQL"); //执行修改操作
ps.executeQuery("查询SQL"); //执行查询操作
conn.commit(); //当两个操作成功后手动提交
} catch (Exception e) {
conn.rollback(); //一旦其中一个操作出错都将回滚,使两个操作都不成功
e.printStackTrace();
}
参考代码2:
//从properties文件中加载数据库配置
Properties properties = new Properties();
InputStream inputStream =Class.forName("test.Test").getResourceAsStream("/mysql.properties");
properties.load(inputStream);
String driver = properties.getProperty("driver");
String url = properties.getProperty("url");
String user = properties.getProperty("user");
String pwd=properties.getProperty("password");
Class.forName(driver);
Connection connection = DriverManager.getConnection(url, user, pwd);
connection.setAutoCommit(false); //关闭自动提交,此句代码会自动开启事务。默认为true,自动提交。
String sql1 = "insert into student_tb (name,age,score) values (?,?,?)";
PreparedStatement preparedStatement1 = connection.prepareStatement(sql1);
preparedStatement1.setString(1,"chy");
preparedStatement1.setInt(2,20);
preparedStatement1.setInt(3,100);
preparedStatement1.executeUpdate(); //放置到队列中
String sql2 = "update student_tb set name=? where id=?";
PreparedStatement preparedStatement2 = connection.prepareStatement(sql2);
preparedStatement2.setString(1,"CoCo");
preparedStatement2.setInt(2,10);
preparedStatement2.executeUpdate(); //放置到队列中
try{
connection.commit(); //提交事务
}catch (SQLException e){
connection.rollback(); //失败就回滚
} finally {
preparedStatement1.close();
preparedStatement2.close();
connection.close();
}
参考代码3:
//例:定义了一个事务方法并在方法内实现了语句之间的一致性操作
Connection con =null;
Statement st=null;
ResultSet rs=null;
PreparedStatement ps=null;
public void startTransaction(){
con = DBCManager.getConnect();//获取连接对象
try {
//设置事务的提交方式为非自动提交:
con.setAutoCommit(false);
//将需要添加事务的代码一同放入try,catch块中
//创建执行语句
String sql ="delete from me where id = 7";
String sql1 = "update me set name ='chengong',age ='34' where id =4";
//分别执行事务
ps = con.prepareStatement(sql);
ps.executeUpdate();
ps = con.prepareStatement(sql1);
ps.executeUpdate();
//在try块内添加事务的提交操作,表示操作无异常,提交事务。
con.commit();
} catch (SQLException e) {
try {
//在catch块内添加回滚事务,表示操作出现异常,撤销事务:
con.rollback();
} catch (SQLException e1) {
e1.printStackTrace();
}
e.printStackTrace();
}finally{
try {
//设置事务提交方式为自动提交:
con.setAutoCommit(true);
} catch (SQLException e) {
e.printStackTrace();
}
DBCManager.release(rs, ps, con);
}
}