JDBC学习笔记(3):批处理、事务

241 阅读3分钟

批处理

在之前的版本中,要对数据库进行多次增加操作,例如增加2万条数据,代码如下:

public void InsertTest() throws Exception {
	Connection connection = JDBCUtils.getConnection();
	String sql = "insert into booklist (username,email)values(?,?)";
	PreparedStatement ps = connection.prepareStatement(sql);
	for(int i=1;i<=20000;i++) {
		ps.setObject(1, "name_"+i);
		ps.setObject(2, "name_"+i+"@163.com");
		ps.execute();
	}
	JDBCUtils.close(connection, ps);
}

数据量2万,运行时间约47s

每读入一条数据,都会执行一次execute();由于MySQL默认DML操作执行后自动提交数据,而数据提交的过程是程序调用数据库进行操作,频繁的数据提交会导致程序运行的效率低下。为了减少数据提交的次数,可以通过setAutoCommit(false)修改为不允许自动提交数据,等到所有数据操作执行完毕后进行一次数据提交,代码如下:

public void InsertTest() throws Exception {
	Connection connection = JDBCUtils.getConnection();
	connection.setAutoCommit(false);
	String sql = "insert into booklist (username,email)values(?,?)";
	PreparedStatement ps = connection.prepareStatement(sql);
	for(int i=1;i<=1000000;i++) {
		ps.setObject(1, "name_"+i);
		ps.setObject(2, "name_"+i+"@163.com");
		ps.execute();
	}
	connection.commit();
	JDBCUtils.close(connection, ps);
}

数据量设置为100万,运行时间约6.3s

上述代码仍存在改进的空间,因为execute();仍要被执行100万次,通过批处理可以减少其执行的次数,代码如下:

为了实现批量操作,JDBC连接URL字符串中需要新增一个参数:rewriteBatchedStatements=true

public void InsertTest3() throws Exception {
	Connection connection = JDBCUtils.getConnection();
	connection.setAutoCommit(false);
	String sql = "insert into booklist (username,email)values(?,?)";
	PreparedStatement ps = connection.prepareStatement(sql);
	for(int i=1;i<=1000000;i++) {
		ps.setObject(1, "name_"+i);
		ps.setObject(2, "name_"+i+"@163.com");
		ps.addBatch();
		if(i%50000==0) {
			ps.executeBatch();
			ps.clearBatch();
		}
	}
	connection.commit();
	JDBCUtils.close(connection, ps);
}

数据量100万,运行时间约6.8s

好尴尬啊,我不知道为什么这个运行时间还没有上面那一个短。
=。=
可能是数据量还不够大,差异不明显,推荐使用最后一种批处理的方法。

事务

原子性(Atomicity) 原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。

一致性(Consistency) 事务必须使数据库从一个一致性状态变换到另外一个一致性状态。

隔离性(Isolation) 事务的隔离性是指一个事务的执行不能被其他事务干扰,即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。

持久性(Durability) 持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来的其他操作和数据库故障不应该对其有任何影响。

以AA用户向BB用户转账为例,如果在两个update操作之间发生了一次网络异常,可能会导致AA余额数字减少了,但BB余额没有增加的情况,这不符合事务的要求。回滚操作rollback()只能返回到上次执行过提交commit()的位置,因此要控制数据提交的位置。除了通过setAutoCommit(false)外,也要注意连接一旦关闭自动提交数据,所以一个事务内的所有操作应使用同一个连接。代码如下:

public void testUpdate() {
	//创建一个连接,该事务内的操作共用
	Connection connection = JDBCUtils.getConnection();
	try {
		connection.setAutoCommit(false);
		//AA余额减少100
		String sql1 = "update info set balance = balance-100 where user=?";
		update(connection, sql1, "AA");
		//模拟异常
		int i = 10/0;
		//BB余额增加100
		String sql2 = "update info set balance = balance+100 where user=?";
		update(connection, sql2, "BB");
		//事务结束再提交数据
		connection.commit();
	} catch (SQLException e) {
		e.printStackTrace();
		try {
			//出现异常回滚数据
			connection.rollback();
		} catch (SQLException e1) {
			e1.printStackTrace();
		}
	} finally {
		try {
			//这里可以不用写这行代码,但是如果使用的是数据库连接池,要恢复默认连接设置,为之后使用这条连接的程序考虑
			connection.setAutoCommit(true);
		} catch (SQLException e) {
			e.printStackTrace();
		}
		JDBCUtils.close(connection, null);
	}
}