写在开头:本文是学习Java后端开发的个人笔记,便于自己复习。如果只是想快速学会使用jdbc的朋友可以直接跳转至JDBC(三)的二三四部分。文章内容引用了尚硅谷的jdbc教学,有兴趣的朋友可以上B站搜索学习。
JDBC(二)
Ⅰ 操作BLOB类型
-
MySQL BLOB类型
MySQL中,BLOB是一个二进制大型对象,是一个可以存储大量数据的容器,它能容纳不同大小的数据。
插入BLOB类型的数据必须使用PreparedStatement,因为BLOB类型的数据无法使用字符串拼接写的。
MySQL的四种BLOB类型(除了在存储的最大信息量上不同外,他们是等同的)类型 大小(单位/字节) TinyBlob 最大 255 Blob 最大 65K MediumBlob 最大 16M LongBlob 最大 4G 实际使用中根据需要存入的数据大小定义不同的BLOB类型。
需要注意的是:如果存储的文件过大,数据库的性能会下降。
如果在指定了相关的Blob类型以后,还报错:xxx too large,那么在mysql的安装目录下,找my.ini文件加上如下的配置参数: max_allowed_packet=16M。同时注意:修改了my.ini文件之后,需要重新启动mysql服务 -
向数据表中插入大数据类型
下边的例子中会用到users2表,其create语句如下:create table users2( id int primary key auto_increment, name varchar(10) not null, age int not null, photo Blob )
// 向数据表users2中插入Blob类型的字段 public void testInsert(){ Connection conn = null; PreparedStatement psmt = null; try { conn = JDBCutils.getConnection(); String sql = "insert into users2(name, age, photo)value(?,?,?);"; psmt = conn.prepareStatement(sql); psmt.setObject(1,"雷姆"); psmt.setObject(2,18); FileInputStream is = new FileInputStream(new File("leimu.jpg")); psmt.setBlob(3, is); psmt.execute(); } catch (Exception e) { e.printStackTrace(); } finally { JDBCutils.closeResource(conn, psmt); } }
-
修改数据表中的Blob类型字段
// 修改数据表users2中Blob类型的字段 public void testUpdate(){ Connection conn = null; PreparedStatement psmt = null; FileInputStream fis = null' try { Connection conn = JDBCUtils.getConnection(); String sql = "update users set photo = ? where id = ?"; PreparedStatement psmt = conn.prepareStatement(sql); fis = new FileInputStream("lamu.jpg"); psmt.setBlob(1, fis); psmt.setInt(2, 1); psmt.execute(); } catch (Exception e) { e.printStackTrace(); } finally { try{ if(fis != null) fis.close; } catch (IOException e){ e.printStackTrace(); } JDBCUtils.closeResource(conn, ps); } }
-
从数据表中读取大数据类型
// 从数据表users2中读取Blob类型的字段 public static void testQuery(){ Connection conn = null; PreparedStatement psmt = null; ResultSet rs = null; InputStream is = null; FileOutputStream fos = null; try { conn = JDBCutils.getConnection(); String sql = "select * from users2 where id = ?;"; psmt = conn.prepareStatement(sql); psmt.setInt(1, 1); rs = psmt.executeQuery(); if(rs.next()){ int id = rs.getInt(1); String name = rs.getString(2); int age = rs.getInt(3); Users users2 = new Users(id, name, age); System.out.println(users2); // 将Blob类型的字段下载下来,以文件的方式保存在本地 Blob photo = rs.getBlob(4); is = photo.getBinaryStream(); fos = new FileOutputStream("leimu2.jpg"); byte[] buffer = new byte[1024]; int len; while((len = is.read(buffer)) != -1){ fos.write(buffer, 0, len); } } } catch (Exception e) { e.printStackTrace(); }finally { try { if(is != null) is.close(); } catch (IOException e) { e.printStackTrace(); } try { if (fos != null) fos.close(); } catch (IOException e) { e.printStackTrace(); } JDBCutils.closeResource(conn, psmt, rs); } }
Ⅱ 批量注入
-
批量执行SQL语句
当需要成批插入或者更新记录时,可以采用Java的批量更新机制,这一机制允许多条语句一次性提交给数据库批量处理。通常情况下比单独提交处理更有效率
JDBC的批量处理语句包括下面三个方法:
- addBatch(String):添加需要批量处理的SQL语句或是参数
- executeBatch():执行批量处理语句
- clearBatch():清空缓存的数据
通常我们会遇到两种批量执行SQL语句的情况:
- 多条SQL语句的批量处理
- 一个SQL语句的批量传参 -
高效的批量插入
举例:向数据表users中插入20000条数据;
(1)实现层次一// 批量插入的方式一:使用PrepareStatement public void testInsert1(){ Connection conn = null; PreparedStatement psmt = null; try { conn = JDBCutils.getConnection(); String sql = "insert into users(name, age)values(?,?);"; psmt = conn.prepareStatement(sql); for(int i=0; i<=20; i++){ psmt.setObject(1, "name_" + i); psmt.setObject(2, 18); psmt.execute(); } } catch (Exception e) { e.printStackTrace(); } finally { JDBCutils.closeResource(conn, psmt); } }
(2)实现层次二
/* * 批量插入的方式二: * 1.addBatch() / executeBatch() / clearBatch() * 2.mysql服务器默认是关闭批处理的,我们需要通过一个参数,让mysql开启批处理的支持。 * ?rewriteBatchedStatements=true 写在配置文件的url后面 * 3.使用更新的mysql 驱动:mysql-connector-java-5.1.37-bin.jar 及以上 */ public void testInsert2(){ Connection conn = null; PreparedStatement psmt = null; try { conn = JDBCutils.getConnection(); String sql = "insert into users(name, age)values(?,?);"; psmt = conn.prepareStatement(sql); for(int i=0; i<=20; i++){ psmt.setObject(1, "name_" + i); psmt.setObject(2, 18); // 1."攒"sql psmt.addBatch(); if(i % 500 == 0){ //2.执行batch psmt.executeBatch(); //3.清空batch psmt.clearBatch(); } } } catch (Exception e) { e.printStackTrace(); } finally { JDBCutils.closeResource(conn, psmt); } }
(3)实现层次三
/* * 层次三 :在层次二的基础上操作 * 使用Connection 的 setAutoCommit(false) / commit() */ public static void testInsert3(){ Connection conn = null; PreparedStatement psmt = null; try { conn = JDBCutils.getConnection(); // 设置不允许自动提交数据 conn.setAutoCommit(false); String sql = "insert into users(name, age)values(?,?);"; psmt = conn.prepareStatement(sql); for(int i=0; i<=20; i++){ psmt.setObject(1, "name_" + i); psmt.setObject(2, 18); // 1."攒"sql psmt.addBatch(); if(i % 500 == 0){ //2.执行batch psmt.executeBatch(); //3.清空batch psmt.clearBatch(); } } // 提交数据 conn.commit(); } catch (Exception e) { e.printStackTrace(); } finally { JDBCutils.closeResource(conn, psmt); } }
Ⅲ 数据库事务
-
数据库事务介绍
- 什么叫数据库事务?
事务:一组逻辑操作单元(一个或多个DML操作),使数据从一种状态变到另一种状态。
举个例子,比如在下边的这张表中,将AA的账户余额转100到B的账户上。这样要经过两个update操作,使得balances由原来的1000、1000转变为900、1100,这就是数据库事务。
- 事务处理的原则
保证所有事务都作为一个工作单元来执行,即使出现了故障,都不能改变这种执行方式。当在一个事务中执行多个操作时,要么所有的事务都被提交(commit),那么这些修改就永久地保存下来;要么数据库管理系统将放弃所作的所有修改,整个事务回滚(rollback)到最初状态。
还是用上边那个例子理解,也就是说A与B的账户余额要么同时发生变化,要么都不变,不能出现一个变了另一个没变的情况。- 为确保数据库中数据的一致性,数据的操纵应当是离散的成组的逻辑单元:当它全部完成时,数据的一致性可以保持,而当这个单元中的一部分操作失败,整个事务应全部视为错误,所有从起始点以后的操作应全部回退
到开始状态。 -
JDBC事务处理
- 数据一旦提交,就不可回滚。
- 数据什么时候意味着提交?
+ 当一个连接对象被创建时,默认情况下是自动提交事务:每次执行一个 SQL 语句时,如果执行成功,就会向数据库自动提交,而不能回滚。
+ 关闭数据库连接,数据就会自动的提交。如果多个操作,每个操作使用的是自己单独的连接,则无法保证事务。即同一个事务的多个操作必须在同一个连接下。
- JDBC程序中为了让多个 SQL 语句作为一个事务执行:
+ 调用 Connection 对象的 setAutoCommit(false);以取消自动提交事务
+ 在所有的 SQL 语句都成功执行后,调用commit(); 方法提交事务
+ 在出现异常时,调用 rollback(); 方法回滚事务若此时 Connection 没有被关闭,还可能被重复使用,则需要恢复其自动提交状态
setAutoCommit(true)。尤其是在使用数据库连接池技术时,执行close()方法 前,建议恢复自动提交状态。- 示例:用户AA向用户BB转账100
public void testUadateWithTx(){ Connection conn = null; try { // 1.获取数据库连接 conn = JDBCutils.getConnection(); // 2.取消数据的自动提供功能,开始事务 conn.setAutoCommit(false); // 3.进行数据库操作 String sql1 = "update users3 set balances = balances - 100 where name = ?"; update(conn, sql1, "AA"); // 模拟网络异常 // System.out.println(10/0); String sql2 = "update users3 set balances = balances + 100 where name = ?"; update(conn, sql2, "BB"); System.out.println("转账成功"); // 4.若没有异常,则提交事务 conn.commit(); } catch (Exception e) { e.printStackTrace(); // 5.若有异常,则回滚数据 System.out.println("转账失败"); try { conn.rollback(); } catch (SQLException throwables) { throwables.printStackTrace(); } } finally { // 6.恢复每次DML操作的自动提交功能 try { conn.setAutoCommit(true); } catch (SQLException e) { e.printStackTrace(); } // 7.关闭连接 JDBCutils.closeResource(conn, null, null); } }
其中,对数据库操作的方法为
/* 通用的增删改操作 --- 考虑数据库事务 */ // sql中占位符的个数与可变形参的长度相同 public static void update(Connection conn, String sql, Object ...args){ PreparedStatement psmt= null; try { //1、预编译sql语句,返回PreparedStatment的实例 psmt = conn.prepareStatement(sql); //2、填充占位符 for(int i=0; i< args.length; i++){ psmt.setObject(i+1, args[i]); } //3、执行 psmt.execute(); } catch (Exception e) { e.printStackTrace(); }finally { //4、关闭资源 JDBCutils.closeResource(null, psmt); } }
-
事务的ACID属性
(1)事物的四个属性
- 原子性(Atomicity) 原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。
- 一致性(Consistency) 事务必须使数据库从一个一致性状态变换到另外一个一致性状态。
- 隔离性(Isolation) 事务的隔离性是指一个事务的执行不能被其他事务干扰,即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。
- 持久性(Durability) 持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来的其他操作和数据库故障不应该对其有任何影响。(2)数据的并发问题
对于同时运行的多个事务, 当这些事务访问数据库中相同的数据时, 如果没有采取必要的隔离机制, 就会导致各种并发问题:
- 脏读: 对于两个事务 T1, T2, T1 读取了已经被 T2 更新但还没有被提交的字段。之后, 若 T2 回滚, T1读取的内容就是临时且无效的。
- 不可重复读: 对于两个事务T1, T2, T1 读取了一个字段, 然后 T2 更新了该字段。之后, T1再次读取同一个字
段, 值就不同了。
- 幻读: 对于两个事务T1, T2, T1 从一个表中读取了一个字段, 然后 T2 在该表中插入了一些新的行。之后, 如 果 T1 再次读取同一个表, 就会多出几行。
数据库事务的隔离性: 数据库系统必须具有隔离并发运行各个事务的能力, 使它们不会相互影响, 避免各种并发问题
一个事务与其他事务隔离的程度称为隔离级别。数据库规定了多种事务隔离级别, 不同隔离级别对应不同的干扰程度, 隔离级别越高, 数据一致性就越好, 但并发性越弱。(3)四种隔离级别
数据库提供的4种事务隔离级别:隔离级别 描述 READ UNCOMMITTED(读未提交数据) 允许事务读取未被其他事务提交的变更;脏读,不可重复度和幻读的问题都会出现 READ COMMITED(读已提交数据) 只允许事务读取已经被其他事务提交的变更;可以避免脏读,但不可重复读和幻读的问题仍然可能出现 REPEATABLE READ(可重复读) 确保事务可以多次从一个字段中读取相同的值;在这个事务持续期间,禁止其他事务对这个字段进行更新,可以避免脏读和不可重复读,但幻读的问题仍然存在 SERIALIZABLE(串行化) 确保事务可以从一个表中读取相同的行;在这个事务持续期间,禁止其他事务对该表进行插入,更新和删除操作;所有并发问题都可以避免,但性能十分低下 Oracle 支持的 2 种事务隔离级别:READ COMMITED,SERIALIZABLE; Oracle 默认的事务隔离级别为:READ COMMITED
Mysql 支持 4 种事务隔离级别。Mysql 默认的事务隔离级别为: REPEATABLE READ
在JDBC中设置mysql数据库的隔离级别语句为:conn.setTransactionIsolation(Connection.TRANSACTION_REPEATABLE_READ);