jdbc 编程(二)及 事务
JDBC处理大文本,JDBC处理图片,JDBC进行批处理 数据库连接池
参考文献:https://www.cnblogs.com/shanheyongmu/p/5909539.html
参考文献:https://www.cnblogs.com/shanheyongmu/p/5909922.html
1. 事务(Transaction,简写为tx):
所谓事务是用户定义的一组操作,使数据从一种状态变换到另一种状态,要么同时成功,要么同时失败。
2. 事务的操作:
先定义开始一个事务,然后对数据作修改操作,这时如果提交(commit),这些修改就永久地保存下来,如果回退(rollback),数据库管理系统将放弃您所作的所有修改而回到开始事务时的状态。
3. 事务的ACID属性:
Atomic原子性、Consistency一致性、Isolation [ˌaɪsəˈleɪʃn] 隔离性和 Durability 持久性。
1、原子性(Atomicity)原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。
2、一致性(Consistency):事务必须使数据库从一个一致性状态变换到另外一个一致性状态。
3、隔离性(Isolation):事务的隔离性是指一个事务的执行不能被其他事务干扰,即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。
4、持久性(Durability):持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来的其他操作和数据库故障不应该对其有任何影响。)
4. 并发下事务会产生的问题
1、脏读
所谓脏读,就是指事务A读到了事务B还没有提交的数据,比如银行取钱,事务A开启事务,此时切换到事务B,事务B开启事务-->取走100元,此时切换回事务A,事务A读取的肯定是数据库里面的原始数据,因为事务B取走了100块钱,并没有提交,数据库里面的账务余额肯定还是原始余额,这就是脏读。
2、不可重复读
所谓不可重复读,就是指在一个事务里面读取了两次某个数据,读出来的数据不一致。还是以银行取钱为例,事务A开启事务-->查出银行卡余额为1000元,此时切换到事务B事务B开启事务-->事务B取走100元-->提交,数据库里面余额变为900元,此时切换回事务A,事务A再查一次查出账户余额为900元,这样对事务A而言,在同一个事务内两次读取账户余额数据不一致,这就是不可重复读。
3、幻读
所谓幻读,就是指在一个事务里面的操作中发现了未被操作的数据。比如学生信息,事务A开启事务-->修改所有学生当天签到状况为false,此时切换到事务B,事务B开启事务-->事务B插入了一条学生数据,此时切换回事务A,事务A提交的时候发现了一条自己没有修改过的数据,这就是幻读,就好像发生了幻觉一样。幻读出现的前提是并发的事务中有事务发生了插入、删除操作。
5. MySQL事务隔离级别
① Serializable (串行化):可避免脏读、不可重复读、幻读的发生。
② Repeatable read (可重复读):可避免脏读、不可重复读的发生。
③ Read committed (读已提交):可避免脏读的发生。
④ Read uncommitted (读未提交):最低级别,任何情况都无法保证。
以上四种隔离级别最高的是Serializable级别,最低的是Read uncommitted级别,当然级别越高,执行效率就越低。像Serializable这样的级别,就是以锁表的方式(类似于Java多线程中的锁)使得其他的线程只能在锁外等待,所以平时选用何种隔离级别应该根据实际情况。
在MySQL数据库中,支持上面四种隔离级别,默认的为Repeatable read (可重复读);而在Oracle数据库中,只支持Serializable (串行化)级别和Read committed (读已提交)这两种级别,其中默认的为Read committed级别。
1. 在MySQL数据库中查看当前事务的隔离级别: select @@tx_isolation;
2.在MySQL数据库中设置事务的隔离 级别: 记住:设置数据库的隔离级别一定要是在开启事务之前!
set [glogal | session] transaction isolation level 隔离级别名称;
set tx_isolation=’隔离级别名称;
注意: session 表示设置当前会话事务隔离级别,
比如MyBatis,getSqlSession()的时候,只针对这一次拿到的Session有效;比如CMD命令行,只对这一次的窗口有效。
glogal表示设置全局事务隔离级别:针对此后所有的会话有效,当前已经存在的会话不受影响。
3. 如果是使用JDBC对数据库的事务设置隔离级别的话,也应该是在调用Connection对象的setAutoCommit(false)方法之前。调用Connection对象的setTransactionIsolation(level)即可设置当前链接的隔离级别,至于参数level,可以使用Connection对象的字段:
在JDBC中设置隔离级别的部分代码:
后记:隔离级别的设置只对当前链接有效。对于使用MySQL命令窗口而言,一个窗口就相当于一个链接,当前窗口设置的隔离级别只对当前窗口中的事务有效;对于JDBC操作数据库来说,一个Connection对象相当于一个链接,而对于Connection对象设置的隔离级别只对该Connection对象有效,与其他链接Connection对象无关。
6. 如何在代码中去处理事务:
1.在JDBC中,事务是默认提交的. 必须先设置事务为手动提交.
connection对象.setAutoCommit(false);//设置事务为手动提交.
2.手动的提交事务.
connection对象.commit();
3.若出现异常必须回滚事务:
不回滚事务,总余额依然是正确的. 若不回滚事务,不会释放数据库资源.
connection对象.rollback();
7. 事务示例:
银行转帐功能: bank / money
过儿和姑姑: 过儿 : 10000块钱 姑姑 : 0块钱
转账:过儿要给姑姑转1000块钱
分析:转钱需要提供两条sql,但是程序员也会出错,比较代码写错了.
①update bank set money = money +1000 where name = '姑姑'
②代码出错, 会导致过儿账户钱少了,而姑姑账户没有收到钱,钱去哪了?
③update bank set money = money -1000 where name= '过儿'
解决办法 代码:
数据库:
CREATE TABLE `bank` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(255) DEFAULT NULL, `money` double DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
com.domain包下的 实体类 Bank
package com.domain; public class Bank { private String name; private double money; public Bank(String name, double money) { super(); this.name = name; this.money = money; } 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; } public Bank() { // TODO Auto-generated constructor stub } @Override public String toString() { return "Bank [name=" + name + ", money=" + money + "]"; } }
com.dao 包下的 IBankDao 接口
package com.dao; import com.domain.Bank; public interface IBankDao { void transMoney(Bank b1,Bank b2, double money); }
com.dao .impl 包 的 BankDaoImpl 实现类
package com.dao.impl; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import com.Util.JDBCUtil; import com.dao.IBankDao; import com.domain.Bank; public class BankDaoImpl implements IBankDao { // 单利模式获取 BankDaoImpl 类对象 private BankDaoImpl (){} private static BankDaoImpl instance = null; public static BankDaoImpl getInstance(){ if(instance == null){ return instance = new BankDaoImpl(); } return null; } @Override public void transMoney(Bank b1, Bank b2, double money) { Connection conn = null; PreparedStatement ps = null; try { conn = JDBCUtil.getInstance().getConnection(); //在JDBC中,事务是默认提交的true. 必须先设置事务为手动提交false conn.setAutoCommit(false); ps = conn.prepareStatement("update bank set money = money - ? where name = ? "); ps.setDouble(1, money); ps.setString(2, b1.getName() ); ps.executeUpdate(); // int a = 1/0; //错误代码 ps = conn.prepareStatement("update bank set money = money + ? where name = ?"); ps.setDouble(1, money); ps.setString(2, b2.getName()); ps.executeUpdate(); conn.commit(); //手动进行提交 } catch (Exception e) { try { conn.rollback(); //数据回滚 } catch (SQLException e1) { e1.printStackTrace(); } } JDBCUtil.getInstance().closeAll(null,ps,conn); } }
测试类
public class TestBank { @Test public void trans(){ Bank b1 = new Bank("过儿",10000); Bank b2 = new Bank("姑姑",0); BankDaoImpl.getInstance().transMoney(b1, b2, 1000); } }
8. 如何在JDBC中保存数据的时候获取自动生成的主键呢?
Statement方式:
int executeUpdate(String sql, int autoGeneratedKeys):执行SQL:
参数:autoGeneratedKeys,是否需要返回自动生成的主键.常量值:Statement.RETURN_GENERATED_KEYS.
ResultSet getGeneratedKeys():获取自动生成的主键
示列代码如下:
@Test public void statementTest() throws Exception { String sql = "insert into student (name, age) values ('zhangsan', 30)"; Connection conn = JdbcUtil.getConn(); Statement st = conn.createStatement(); // statement.executeUpdate(sql); st.executeUpdate(sql, Statement.RETURN_GENERATED_KEYS); ResultSet rs = st.getGeneratedKeys(); if (rs.next()) { Long id = rs.getLong(1); System.out.println(id); } JdbcUtil.close(conn, st, null); }
PreparedStatement方式:
PreparedStatement prepareStatement(String sql,int autoGeneratedKeys) :
创建PreparedStatement对象,病指定是否需要返回生成的主键. 常量值:Statement.RETURN_GENERATED_KEYS
示列代码:
public class PreparedStatementTest { @Test public void preparedStatement() throws Exception { String sql = "insert into student (name,age) values (?,?)"; Connection conn = JdbcUtil.getConn(); PreparedStatement ps = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS); ps.setString(1, "张三"); ps.setInt(2, 24); ps.executeUpdate(); ResultSet rs = ps.getGeneratedKeys(); if (rs.next()) { Long id = rs.getLong(1); System.out.println(id); } JdbcUtil.close(conn, ps, null); } }
部分类容参考:https://www.cnblogs.com/741162830qq/p/6733982.html