1.1 Mysql数据库中的Blob类型数据
1.2 向数据库中插入一条含有blob类型字段的数据
@Test public void insertBlob() { Connection connection = null; PreparedStatement preparedStatement = null; try { //1.获取连接 connection = MyJDBCUtils.getConnection(); //2.提供sql语句 String sql = "insert into customers(name,email,birth,photo) values(?,?,?,?)"; //3.创建preparedStatement对象 preparedStatement = connection.prepareStatement(sql); //4.填充占位符 preparedStatement.setString(1,"张三"); preparedStatement.setString(2,"zhangsan@qq.com"); //这里涉及到了数据库的隐式转换:如果我们传入的字符串为xxxx-xx-xx类型,数据库会默认为date,否则需要我们将日期转成sql.date类型 preparedStatement.setString(3,"1998-10-05"); //涉及到blob类型的数据存储时,我们通常以流的形式来操作 //提供一个输入流用来读取图片 FileInputStream is = new FileInputStream("3271a1e269ffb5f84b57acc74c422a7d.jpeg"); preparedStatement.setBlob(4,is); //5.执行sql int i = preparedStatement.executeUpdate(); if (i > 0) System.out.println("插入成功!"); else System.out.println("插入失败!"); } catch (Exception e) { e.printStackTrace(); } finally { //6.关闭资源 //这里的io流是在preparedStatement中,所以直接关闭preparedStatement即可 MyJDBCUtils.closeConnection(connection,preparedStatement); } }
1.3 读出一条含有blob类型的数据并写出到本地文件
@Test public void queryBlob() throws Exception { Connection connection = null; PreparedStatement preparedStatement = null; ResultSet resultSet = null; InputStream stream = null; FileOutputStream outputStream = null; try { //1.获取数据库连接 connection = MyJDBCUtils.getConnection(); //2.提供sql语句 String sql = "select id,name,email,birth,photo from customers where id = ?"; //3.创建prepareStatement对象 preparedStatement = connection.prepareStatement(sql); //4.填充占位符并执行 preparedStatement.setInt(1,16); resultSet = preparedStatement.executeQuery(); //5.封装 if (resultSet.next()){ //获取查询出来的值,两种方式 //方式一:使用下标 // resultSet.getInt(1); // resultSet.getString(2); // resultSet.getString(3); // resultSet.getString(4); //方式二:使用别名 int id = resultSet.getInt("id"); String name = resultSet.getString("name"); String email = resultSet.getString("email"); Date birth = resultSet.getDate("birth"); Customers customers = new Customers(id,name,email,birth); //对于Blob类型的数据,使用文本的方式来存储 Blob photo = resultSet.getBlob("photo"); stream = photo.getBinaryStream(); //写出 outputStream = new FileOutputStream("zhuyin.jpeg"); byte[] bytes = new byte[1024]; int len; while((len = stream.read(bytes)) != -1){ outputStream.write(bytes,0,len); } } } catch (Exception e) { e.printStackTrace(); } finally { if(stream != null) stream.close(); if (outputStream != null) outputStream.close(); //6.关闭相关的流 MyJDBCUtils.closeConnection(connection,preparedStatement,resultSet); }
事务:一组逻辑操作单元(一行或者多行的DML操作),使数据从一种状态变换为另外一种状态。
事务处理(事务操作)原则: 保证所有事务都作为一个工作单元来执行,即使出现了故障也不能改变这种执行方式。当在一个事务中执行多个操作时,要么所有的事务都被提交,将数据永久的进行修改,要么就放弃所有修改,使事务被回滚到最初的状态
JDBC中的事务处理
1.数据一旦提交就不能回滚
2.会导致数据自动提交的一些操作
2.1 DDL语言,这个是默认自动提交的,而且不能修改
2.2 DML语言默认情况下是一旦执行就会自动提交,但是可以通过set autoCommit = false来关闭自动提交
2.3 默认在关闭数据库连接,数据会进行提交
3.在JDBC中为了让多个sql语句作为同一个事务来执行:
3.1 调用Connection对象的setAutoCommit(false);来关闭DML语言的自动提交功能(在关闭之前,需要修改回默认状态,主要体现在使用数据库连接池时)
3.2 在1所有的sql语句执行完之后,使用Connection对象的commit()方法进行统一的提交。
3.3 在出现异常时,调用callback()方法进行事务回滚。
事务的ACID属性:
1.原子性(Atomicity):原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生要么都不发生。
2.一致性(Consistency):事务必须使数据库从一个一致性状态变为另外一个一致性状态。
3.隔离性(Isolation):事务的隔离性是指一个事务的执行不能被其他事务所干扰。即一个事务内部的操作和所使用的数据对于其他并发的事务是隔离的,并发执行的各个事务之间不能互相干扰。
4.持久性(Durability):指的是一个事务一旦被提交,它对数据库的修改就是永久的,接下来的其他操作或者数据库故障不能对其有影响。
数据库的并发问题:
对于同时执行的事务来说,如果数据库没有采用隔离机制就会产生一些并发问题。
1. 脏读:对于两个事务而言,其中一个事务读取了另外一个事务更新但是还没有提交的数据。此时一旦更新没有被提交,这个时候读取到的数据就是临时且无效的。
2. 不可重复读:对于两个事务而言,事务A读取到了一个字段之后,事务B对该字段进行了更新,之后事务A再次读取该字段时值就发生了改变。
3. 幻读:对于两个事务来说,A事务读取了表中的一个字段之后,B事务向表中新插入了行,这样A事务再次读取同一个表就会发现多了几行。
数据库的事务隔离性:数据库系统必须具有隔离并发运行各个事务的能力,使他们之间不会发生影响,避免产生各种并发问题。一个事务与其他事务之间的隔离程度称为隔离级别,隔离级别越高,数据的一致性越好但是并发性越弱。
数据库的四种事务隔离级别:
1. READ UNCOMMITTED(读未提交数据):允许事务读取其他事务没有被提交的数据,此时脏读、不可重复读、幻读的问题都会出现。
2. READ COMMITED(读已提交数据):只允许事务读取已经别其他事务提交过之后的变更,解决了脏读,但是不可重复读、幻读的问题依然存在。
3. REPEATABLE READ(可重复读):确保事务可以多次从一个字段读取相同的值,在该事务还没有结束期间,不允许其他事务对该字段进行修改,避免了脏读和不可重复读,但是幻读问题依然存在。
4. SERIALIZABLE(串行化):确保事务可以多次从一个表中读取相同的行,在该事务还没有结束期间,不允许其他事务对该表进行增删改操作,解决了所有并发问题,但性能十分低下。
Oracle数据库支持两种个隔离级别:READ COMMITED 和 SERIALIZABLE 。默认的事务隔离级别为READ COMMITED。
MySQL数据库支持四种隔离级别,默认隔离级别为:PEREATABLE READ。
如何在MySQL中设置事务隔离级别:
每启动一个mysql程序就会获取一个单独的数据库连接,每一个单独的数据库连接就会有一个全局变量@@tx_ioslation来表示事务的隔离级别。
查看当前的隔离级别:select @@tx_ioslation.
设置当前mysql连接的隔离级别:set transaction ioslation level read commited.
设置数据库全局系统的隔离级别:set global transaction ioslation level read commited.
创建mysql的用户:create user xxx identified by 'password';
给创建的用户权限:
给通过网络方式登录的用户所有表的全部权限,密码设置为123456:grant all privileges on *.* to xxx@'%' identified by '123456';
给本地登陆的用户某个库下所有表的增删改查权限:grant select,uodate,delete,insert on 表名.* to xxx@localhost identified by '123456'
1. 考虑到事务之后的通用的增删改方法
public static void generalUpdate(Connection conn,String sql,Object ...args) { PreparedStatement preparedStatement = null; try { //1.创建preparedStatement对象 preparedStatement = conn.prepareStatement(sql); //2.填充占位符 for (int i = 0; i < args.length; i++) { preparedStatement.setObject(i+1,args[i]); } //3.执行操作 int i = preparedStatement.executeUpdate(); if (i > 0) System.out.println("此次操作成功!"); else System.out.println("此次操作失败!"); } catch (SQLException throwables) { throwables.printStackTrace(); } finally { //关闭statement,此处不要关闭connection,等数据全部提交之后再关闭 try { if (preparedStatement != null) preparedStatement.close(); } catch (SQLException throwables) { throwables.printStackTrace(); } } }
2.考虑到事务之后的查询单一表数据的方法
public static <T> T generalQuery(Class<T> clazz,Connection conn,String sql,Object ...args) { PreparedStatement preparedStatement = null; ResultSet resultSet = null; try { //1.创建对象 preparedStatement = conn.prepareStatement(sql); //2.填充占位符 for (int i = 0; i < args.length; i++) { preparedStatement.setObject(i+1,args[i]); } //3.执行操作 resultSet = preparedStatement.executeQuery(); //4.获取元数据 ResultSetMetaData metaData = resultSet.getMetaData(); //5.获取该条记录的列数 int columnCount = metaData.getColumnCount(); if (resultSet.next()){ //6.使用反射获取到当前bean的实例 T t = clazz.newInstance(); for (int i = 0; i < columnCount; i++) { //7.获取该条记录中每一列的别名 String columnLabel = metaData.getColumnLabel(i + 1); //8.获取该条记录中每一列的值 Object object = resultSet.getObject(i + 1); //9.使用反射获取到当前bean的属性 Field declaredField = clazz.getDeclaredField(columnLabel); declaredField.setAccessible(true); declaredField.set(t,object); } return t; } } catch (Exception e){ e.printStackTrace(); } finally { //10.关闭资源 try { if (resultSet != null) resultSet.close(); } catch (SQLException throwables) { throwables.printStackTrace(); } try { if (preparedStatement != null) preparedStatement.close(); } catch (SQLException throwables) { throwables.printStackTrace(); } } return null; }
3.考虑到事务之后的通用的查询多张表的方法
public static <T> List<T> generalQueryList(Class<T> clazz, Connection conn, String sql, Object ...args) { PreparedStatement preparedStatement = null; ResultSet resultSet = null; try { //1.创建对象 preparedStatement = conn.prepareStatement(sql); //2.填充占位符 for (int i = 0; i < args.length; i++) { preparedStatement.setObject(i+1,args[i]); } //3.执行操作 resultSet = preparedStatement.executeQuery(); //4.获取元数据 ResultSetMetaData metaData = resultSet.getMetaData(); //5.获取该条记录的列数 int columnCount = metaData.getColumnCount(); //6.创建list集合 ArrayList<T> list = new ArrayList<>(); if (resultSet.next()){ //6.使用反射获取到当前bean的实例 T t = clazz.newInstance(); for (int i = 0; i < columnCount; i++) { //7.获取该条记录中每一列的别名 String columnLabel = metaData.getColumnLabel(i + 1); //8.获取该条记录中每一列的值 Object object = resultSet.getObject(i + 1); //9.使用反射获取到当前bean的属性 Field declaredField = clazz.getDeclaredField(columnLabel); declaredField.setAccessible(true); declaredField.set(t,object); } list.add(t); } return list; } catch (Exception e){ e.printStackTrace(); } finally { //10.关闭资源 try { if (resultSet != null) resultSet.close(); } catch (SQLException throwables) { throwables.printStackTrace(); } try { if (preparedStatement != null) preparedStatement.close(); } catch (SQLException throwables) { throwables.printStackTrace(); } } return null; }