第15章 JDBC

2019独角兽企业重金招聘Python工程师标准>>> hot3.png

数据持久化

  • 持久化(persistence):把数据保存到可断电的存储设备中以供之后使用。大多数情况下,特别是企业级应用,数据持久化意味着将内存中的数据保存到硬盘上加以”固化”,而持久化的实现过程大多通过各种关系型数据库来完成。
  • 持久化的主要应用是将内存中的数据存储在关系型数据库中,当然也可以存储在磁盘文件、XML数据文件中。

第15章 JDBC_第1张图片

Java中的数据存储技术

在Java中,数据库存储技术可分为如下几类:

  • JDBC直接访问数据库。
  • JDO技术。
  • 第三方O/R Mapping工具,如Hibernate、MyBatis等。

JDBC是Java访问数据库的基石。Hibernate、MyBatis只是更好的封装了JDBC。

JDBC基础

  • JDBC(Java Database Connectivity)是一个独立于特定数据库管理系统、通用的SQL数据库存取和操作的公共接口(一组API),定义了用来访问数据库的标准Java类库,使用这个类库可以以一种标准的方法方便地访问数据库资源。
  • JDBC为访问不同的数据库提供了一种统一的途径,为开发者屏蔽了一些细节问题。
  • JDBC的目标是使Java程序员使用JDBC可以连接任何提供了JDBC驱动程序的数据库系统,这样就使得程序员无需对特定的数据库系统的特点有过多的了解,从而极大简化和加快了开发过程。

第15章 JDBC_第2张图片

第15章 JDBC_第3张图片

第15章 JDBC_第4张图片

JDBC体系结构

JDBC接口(API)包括两个层次:

  • 面向应用的API:Java API,抽象接口,供应用程序开发人员使用(连接数据库,执行SQL语句,获得结果)。
  • 面向数据库的API:Java Driver API,供开发商开发数据库驱动程序用。

第15章 JDBC_第5张图片

JDBC驱动程序分类(了解)

  • JDBC驱动程序:各个数据库厂商根据JDBC的规范制作的JDBC实现类的类库。
  • JDBC驱动程序总共有四种类型: 1、JDBC-ODBC桥。 2、部分本地API部分Java的驱动程序。 3、JDBC网络纯Java驱动程序。 4、本地协议的纯Java驱动程序

ODBC

  • 早期对数据库的访问,都是调用数据库厂商提供的专有的API。为了在 Windows平台下提供统一的访问方式,微软推出了 ODBC(Open Database Connectivity,开放式数据库连接),并提供了 ODBC API,使用者在程序中只需要调用 ODBC API,由 ODBC 驱动程序将调用转换成为对特定的数据库的调用请求。
  • 一个基于ODBC的应用程序对数据库的操作不依赖任何DBMS(database manager system),不直接与DBMS打交道,所有的数据库操作由对应的DBMS的ODBC驱动程序完成。也就是说,不论是MySQL还是Oracle数据库,均可用ODBC API进行访问。由此可见,ODBC的最大优点是能以统一的方式处理所有的数据库。
  • JDBC-ODBC 桥本身也是一个驱动,利用这个驱动,可以使用 JDBC-API 通过ODBC 去访问数据库。这种机制实际上是把标准的 JDBC 调用转换成相应的 ODBC 调用,并通过 ODBC 访问数据库。
  • 因为需要通过多层调用,所以利用 JDBC-ODBC 桥访问数据库的效率较低
  • 在JDK中提供了JDBC-ODBC 桥的实现类sun.jdbc.odbc.JdbcOdbcDriver

第15章 JDBC_第6张图片

部分本地API部分Java的驱动程序

  • 这种类型的JDBC驱动程序使用Java编写,调用数据库厂商提供的本地API。
  • 通过这种类型的JDBC驱动程序访问数据库减少了ODBC的调用环节,提高了数据库访问的效率。
  • 在这种方式下需要在客户的机器上安装本地JDBC驱动程序和特定厂商的本地API。

第15章 JDBC_第7张图片

JDBC网络纯Java驱动程序

  • 这种驱动利用中间件的应用服务器来访问数据库。应用服务器作为一个到多个数据库的网关,客户端通过它可以连接到不同的数据库服务器。
  • 应用服务器通常有自己的网络协议,Java 用户程序通过 JDBC 驱动程序将 JDBC 调用发送给应用服务器,应用服务器使用本地程序驱动访问数据库,从而完成请求。

第15章 JDBC_第8张图片

本地协议的纯 Java 驱动程序

  • 大多数数据库厂商已经支持允许客户程序通过网络直接与数据库通信的网络协议
  • 这种类型的驱动程序完全使用 Java 编写,通过与数据库建立的 Socket 连接,采用具体与厂商的网络协议把 JDBC 调用转换为直接连接的网络调用。

第15章 JDBC_第9张图片

JDBC API

JDBC API 是一系列的接口,它使得应用程序能够进行数据库连接,执行SQL语句,并且得到返回结果。

第15章 JDBC_第10张图片

Driver接口

  • java.sql.Driver接口是所有 JDBC 驱动程序需要实现的接口。这个接口是提供给数据库厂商使用的,不同数据库厂商提供不同的实现。
  • 在程序中不需要直接去访问实现了 Driver 接口的类,而是由驱动程序管理器类java.sql.DriverManager去调用这些Driver实现。

加载与注册 JDBC 驱动

  • 加载 JDBC 驱动需调用java.lang.Class的静态方法 forName(),向其传递要加载的 JDBC 驱动的类名。
  • DriverManager 类是驱动程序管理器类,负责管理驱动程序。
  • 通常不用显式调用java.sql.DriverManager类的registerDriver()方法来注册驱动程序类的实。因为java.sql.Driver接口的驱动程序类都包含了静态代码块,在这个静态代码块中,会调用java.sql.DriverManager.registerDriver() 方法来注册自身的一个实例。

建立连接

  • 可以调用java.sql.DriverManager类的getConnection()方法建立到数据库的连接。
  • JDBC URL 用于标识一个被注册的驱动程序,驱动程序管理器通过这个 URL 选择正确的驱动程序,从而建立到数据库的连接。
  • JDBC URL的标准由三部分组成,各部分间用冒号分隔。

jdbc:<子协议>:<子名称> 协议:JDBC URL中的协议总是jdbc。 子协议:子协议用于标识一个数据库驱动程序。 子名称:一种标识数据库的方法。其可以依不同的子协议而变化,用其的目的是为了定位数据库提供足够的信息。

几种常用数据库的JDBC URL

第15章 JDBC_第11张图片

  • 对于 Oracle 数据库连接,采用如下形式: jdbc:oracle:thin:@localhost:1521:sid
  • 对于 MYSQL 数据库连接,采用如下形式:
    jdbc:mysql://localhost:3306/sid

访问数据库

  • 数据库连接被用于向数据库服务器发送命令和 SQL 语句,在连接建立后,需要对数据库进行访问,执行 sql 语句。
  • 在 java.sql 包中有 3 个接口分别定义了对数据库的调用的不同方式: java.sql.Statement java.sql.PrepatedStatement java.sql.CallableStatement

Statement接口

  • 通过调用java.sql.Connection对象的createStatement()方法创建该对象。
  • 该对象用于执行静态的 SQL 语句,并且返回执行结果。
  • Statement 接口中定义了下列方法用于执行 SQL 语句: ResultSet excuteQuery(String sql) int excuteUpdate(String sql)

ResultSet接口

  • 通过调用java.sql.Statement对象的excuteQuery()方法创建该对象。
  • ResultSet 对象以逻辑表格的形式封装了执行数据库操作的结果集,ResultSet 接口由数据库厂商实现。
  • ResultSet 对象维护了一个指向当前数据行的游标。初始的时候,游标在第一行之前,可以通过java.sql.ResultSet对象的next()方法移动到下一行。
  • ResultSet 接口的常用方法: boolean next() getString() getInt() getLong() getDouble() getBoolean() ...

第15章 JDBC_第12张图片

数据类型转换表

第15章 JDBC_第13张图片

练习

创立数据库表 examstudent,表结构如下:

第15章 JDBC_第14张图片

drop database if exists java ;
create database java default character set utf8 ;
use java;

create table examstudent(
  flowId int auto_increment comment'流水号',
  type int comment '四级/六级',
  idCard varchar(18) comment'身份证号',
  examCard varchar(15)comment'准考证号',
  studentName varchar(20)comment'学生姓名',
  location varchar(20)comment'区域',
  grade int comment'成绩',
  primary key(flowId)
);

向数据库中添加如下数据 :

第15章 JDBC_第15张图片

insert into examstudent(type,idCard,examCard,studentName,location,grade)
values(4,'412824195263214584','200523164754000','张锋','郑州',85);
insert into examstudent(type,idCard,examCard,studentName,location,grade)
values(4,'222224195263214584','200523164754001','孙朋','大连',56);
insert into examstudent(type,idCard,examCard,studentName,location,grade)
values(6,'342824195263214584','200523164754002','刘明','沈阳',72);
insert into examstudent(type,idCard,examCard,studentName,location,grade)
values(6,'100824195263214584','200523164754003','赵虎','哈尔滨',95);
insert into examstudent(type,idCard,examCard,studentName,location,grade)
values(4,'454524195263214584','200523164754004','杨丽','北京',64);
insert into examstudent(type,idCard,examCard,studentName,location,grade)
values(4,'854524195263214584','200523164754005','王小红','太原',60);

插入一个新的 student 信息。

第15章 JDBC_第16张图片

public class Test {
    public static void main(String[] args) throws Exception {

        Scanner input = new Scanner(System.in);
        System.out.println("请输入考生的详细信息");

        System.out.print("Type:");
        int type = input.nextInt();

        System.out.print("IDCard:");
        String idCard = input.next();

        System.out.print("ExamCard:");
        String examCard = input.next();

        System.out.print("StudentName:");
        String studentName = input.next();

        System.out.print("Location:");
        String location = input.next();

        System.out.print("Grade:");
        int grade = input.nextInt();

        String url = "jdbc:mysql://localhost:3306/java";
        String user = "root";
        String password = "root";
        
        Connection conn = DriverManager.getConnection(url, user, password);
        Statement stmt = conn.createStatement();
        String sql = "insert into examstudent(type,idCard,examCard,studentName,location,grade)" +
                "values(" + type + ",'" + idCard + "','" + examCard + "','" + studentName + "','" + location + "'," + grade + ");";
        int count = stmt.executeUpdate(sql);
        if (count > 0) {
            System.out.println("信息录入成功!");
        }
        stmt.close();
        conn.close();
    }
}

在IDE中建立Java程序:输入身份证号或准考证号可以查询到学生的基本信息。 结果如下:

第15章 JDBC_第17张图片

public class Test2 {
    public static void main(String[] args) throws Exception {

        Scanner input = new Scanner(System.in);
        System.out.println("请输入要查询的类型:");
        System.out.println("a.准考证号");
        System.out.println("b.身份证号");
        String type = input.next();

        if ("a".equals(type)) {
            System.out.println("请输入准考证号:");
            String examCard = input.next();
            String sql = "select * from examstudent where examCard='"+examCard+"'";
            showInfo(sql);
        } else if ("b".equals(type)) {
            System.out.println("请输入身份证号:");
            String idCard = input.next();
            String sql = "select * from examstudent where idCard='"+idCard+"'";
            showInfo(sql);
        } else {
            System.out.println("您的输入有误!请重新进入程序...");
        }
    }


    private static void showInfo(String sql) throws Exception {
        String url = "jdbc:mysql://localhost:3306/java";
        String user = "root";
        String password = "root";

        Connection conn = DriverManager.getConnection(url, user, password);
        Statement stmt = conn.createStatement();
        ResultSet rs = stmt.executeQuery(sql);
        if (rs.next()) {
            System.out.println("===查询结果===");
            System.out.println("流水号:" + rs.getInt("flowId"));
            System.out.println("四级/六级:" + rs.getInt("type"));
            System.out.println("身份证号:" + rs.getString("idCard"));
            System.out.println("准考证号:" + rs.getString("examCard"));
            System.out.println("学生姓名:" + rs.getString("studentName"));
            System.out.println("区域:" + rs.getString("location"));
            System.out.println("成绩:" + rs.getInt("grade"));
        } else {
            System.out.println("查无此人!请重新进入程序...");
        }
        rs.close();
        stmt.close();
        conn.close();
    }
}

完成学生信息的删除功能。

第15章 JDBC_第18张图片

public class Test3 {
    public static void main(String[] args) throws SQLException {
        Scanner input = new Scanner(System.in);
        System.out.println("请输入考生的准考证号:");
        String examCard = input.next();

        String url = "jdbc:mysql://localhost:3306/java";
        String user = "root";
        String password = "root";

        Connection conn = DriverManager.getConnection(url, user, password);
        Statement stmt = conn.createStatement();
        String sql = "select * from examstudent where examCard='"+examCard+"'";
        ResultSet rs = stmt.executeQuery(sql);
        if (rs.next()) {
            sql="delete from examstudent where examCard='"+examCard+"'";
            int count = stmt.executeUpdate(sql);
            if(count>0){
                System.out.println("删除成功!");
            }
        } else {
            System.out.println("查无此人!请重新进入程序...");
        }
        rs.close();
        stmt.close();
        conn.close();
    }
}

SQL注入

  • SQL注入是利用某些系统没有对用户输入的数据进行充分的检查,而在用户输入数据中注入非法的SQL语句段或命令,从而利用系统的SQL引擎完成恶意行为的做法。
  • 对于Java而言,要防范 SQL 注入,只要用 PreparedStatement 取代 Statement 就可以了。

PreparedStatement

  • 可以通过调用java.sql.Connection对象的preparedStatement()方法获取PreparedStatement对象。
  • PreparedStatement接口是java.sql.Statement的子接口,它表示一条预编译过的 SQL 语句。
  • PreparedStatement 对象所代表的 SQL 语句中的参数用问号(?)来表示,调用 PreparedStatement 对象的 setXXX() 方法来设置这些参数。setXXX()方法有两个参数,第一个参数是要设置的SQL语句中的参数的索引(从 1 开始),第二个是设置的 SQL语句中的参数的值。

PreparedStatement VS Statement

  • 代码的可读性和可维护性。
  • PreparedStatement 能最大可能提高性能:

DBServer会对预编译语句提供性能优化。因为预编译语句有可能被重复调用,所以SQL语句在被DBServer的编译器编译后的执行代码被缓存下来,那么下次调用时只要是相同的预编译语句就不需要编译,只要将参数直接传入编译过的语句执行代码中就会得到执行。

在statement语句中,即使是相同操作但因为数据内容不一样,所以整个语句本身不能匹配,没有缓存语句的意义。事实是没有数据库会对普通语句编译后的执行代码缓存。这样每执行一次都要对传入的语句编译一次。

  • PreparedStatement可以防止SQL注入 。

JDBC调用存储过程(了解)

步骤:

  • 通过java.sql.Connection对象的prepareCall()方法创建 一个java.sql.CallableStatement对象的实例。在使用java.sql.Connection对象的preparedCall()方法时,需要传入一个String类型的字符串,该字符串用于指明如何调用存储过程。

  • 通过java.sql.CallableStatement对象的reisterOutParameter()方法注册OUT参数。
  • 通过java.sql.CallableStatement对象的setXxx()方法设定IN或*IN OUT参数。若想将参数默认值设为null,可以使用setNull()方法。
  • 通过java.sql.CallableStatement对象的execute()方法执行存储过程。
  • 如果所调用的是带返回参数的存储过程,还需要通过java.sql.CallableStatement 对象的getXxx()方法获取其返回值。

注:通过数据字典查看存储过程或函数的定义。

select text 
from user_source 
where lower(name) = 'add_sal_procedure';

调用函数: 第15章 JDBC_第19张图片

调用过程: 第15章 JDBC_第20张图片

使用JDBC驱动程序处理元数据(了解)

第15章 JDBC_第21张图片

第15章 JDBC_第22张图片

  • Java通过JDBC获得连接以后,得到一个java.sql.Connection对象,可以从这个对象获得有关数据库管理系统的各种信息。包括数据库中的各个表,表中的各个列,数据类型,触发器,存储过程等各方面的信息。根据这些信息,JDBC可以访问一个实现事先并不了解的数据库。
  • 获取这些信息的方法都是在java.sql.DatabaseMetaData类的对象上实现的,而java.sql.DatabaseMetaData对象是在java.sql.Connection对象上获得的。

DatabaseMetaData类

java.sql.DatabaseMetaData类中提供了许多方法用于获得数据源的各种信息,通过这些方法可以非常详细的了解数据库的信息: getURL():返回一个String类对象,代表数据库的URL。 getUserName():返回连接当前数据库管理系统的用户名。 isReadOnly():返回一个boolean值,指示数据库是否只允许读操作。 getDatabaseProductName():返回数据库的产品名称。 getDatabaseProductVersion():返回数据库的版本号。 getDriverName():返回驱动驱动程序的名称。 getDriverVersion():返回驱动程序的版本号。

ResultSetMetaData类

java.sql.ResultSetMetaData可用于获取关于java.sql.ResultSet对象中列的类型和属性信息的对象: getColumnName(int column):获取指定列的名称。 getColumnCount():返回当前 ResultSet 对象中的列数。 getColumnTypeName(int column):检索指定列的数据库特定的类型名称。 getColumnDisplaySize(int column):指示指定列的最大标准宽度,以字符为单位。 isNullable(int column):指定列中的值是否可以为null。 isAutoIncrement(int column):是否自动为指定列进行编号,这样这些列仍然是只读的。

获取数据库自动生成的主键

示例:

Connection conn = JdbcUtil.getConnection();
String sql = "insert into user(name,password,email,birthday) values('abc','123','[email protected]','1978-08-08')";
PreparedStatement st = conn.prepareStatement(sql,Statement.RETURN_GENERATED_KEYS );
st.executeUpdate();
ResultSet rs = st.getGeneratedKeys();  //取得主键
if(rs.next()){
	System.out.println(rs.getObject(1));
}

LOB(了解)

  • LOB,即Large Objects(大对象),是用来存储大量的二进制和文本数据的一种数据类型(一个LOB字段可存储可多达4GB的数据)。
  • LOB 分为两种类型:内部LOB 和 外部LOB。

内部LOB将数据以字节流的形式存储在数据库的内部。因而,内部LOB的操作可以参与事务,也可以像处理普通数据一样对其进行备份和恢复操作。

Oracle LOB

Oracle支持三种类型的内部LOB:

  • BLOB(二进制数据)  
  • CLOB(单字节字符数据) 
  • NCLOB(多字节字符数据)

BLOB字段适用于存储大量的二进制数据,如图片、音频、视频、文件等。 CLOB和NCLOB类型适用于存储超长的文本数据

目前只支持一种外部LOB类型,即BFILE类型。在数据库内,该类型仅存储数据在操作系统中的位置信息,而数据的实体以外部文件的形式存在于操作系统的文件系统中。因而,该类型所表示的数据是只读的,不参与事务。该类型可帮助用户管理大量的由外部程序访问的文件。

MySQL BLOB

  • MySQL中,BLOB是一个二进制大型对象,是一个可以存储大量数据的容器,它能容纳不同大小的数据。
  • MySQL的四种BLOB类型(除了在存储的最大信息量上不同外,他们是等同的)。

第15章 JDBC_第23张图片

  • 实际使用中根据需要存入的数据大小定义不同的BLOB类型。如果存储的文件过大,数据库的性能会下降

使用JDBC来写入Blob型数据到Oracle中

  • Oracle的Blob字段比long字段的性能要好,可以用来保存图片之类的二进制数据。
  • Oracle的BLOB字段由两部分组成:数据(值)和指向数据的指针(定位器)。尽管值与表自身一起存储,但是一个BLOB列并不包含值,仅有它的定位指针。为了使用大对象,程序必须声明定位器类型的本地变量。
  • 当Oracle内部LOB被创建时,定位器被存放在列中,值被存放在LOB段中,LOB段是在数据库内部表的一部分
  • 因为Blob自身有一个cursor(游标),当写入Blob字段必须使用指针(定位器)对Blob进行操作,因而在写入Blob之前,必须获得指针(定位器)才能进行写入
  • 如何获得Blob的指针(定位器):需要先插入一个empty的blob,这将创建一个blob的指针,然后再把这个empty的blob的指针查询出来,这样通过两步操作,就获得了blob的指针,可以真正的写入blob数据了。

步骤

  • 插入空blob insert into javatest(name,content) values(?,empty_blob());
  • 获得blob的cursor select content from javatest where name= ? for update; 注意: 须加for update,锁定该行,直至该行被修改完毕,保证不产生并发冲突。
  • 利用IO和获取到的cursor往数据库写入数据流。

数据库事务

  • 所谓数据库事务是指一组逻辑操作单元,使数据从一种状态变换到另一种状态
  • 为确保数据库中数据的一致性,数据的操纵应当是离散的成组的逻辑单元:当它全部完成时,数据的一致性可以保持,而当这个单元中的一部分操作失败,整个事务应全部视为错误,所有从起始点以后的操作应全部回退到开始状态。
  • 事务的操作:先定义开始一个事务,然后对数据作修改操作,这时如果提交(COMMIT),这些修改就永久地保存下来;如果回滚(ROLLBACK),数据库将放弃前面所作的所有修改而回到事务开始时的状态。

事务的ACID属性

  • **原子性(Atomicity)**是指事务是一个不可分割的工作单位。事务中的操作要么都发生,要么都不发生。 
  • **一致性(Consistency)**是指事务必须使数据库从一个一致性状态变换到另一个一致性状态。
  • 隔离性(Isolation)是指一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的。并发执行的各个事务之间不能互相干扰。
  • 持久性(Durability)是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的。接下来的其他操作和数据库故障都不能对其有任何影响。

事务的使用

以第一个 DML 语句的执行作为开始,以下面的其中之一作为结束:

  • COMMIT 或 ROLLBACK 语句。
  • DDL 或 DCL 语句(自动提交)。
  • 用户会话正常结束。
  • 系统异常终了。

DDL(Data Definition Language)数据定义语言(用来定义数据库结构):

create table; 
alter table; 
drop table; 

create index; 
drop index;
...

DCL(Data Control Language)数据控制语言(用来控制数据库的访问):

grant; 
revoke;
commit;
rollback;
lock;

DML(Data Manipulation Language)数据操纵语言(用来查询与更新记录):

insert; 
update; 
delete;

COMMIT和ROLLBACK语句的优点

  • 确保数据完整性
  • 数据改变被提交之前预览。
  • 将逻辑上相关的操作分组。

数据完整性:存储在数据库中的所有数据值均处于正确的状态。 如果数据库中存储有不正确的数据值,则该数据库称为已丧失数据完整性。 数据库采用多种方法来保证数据完整性,包括外键、束约、规则和触发器。

提交或回滚前的数据状态

  • 改变前的数据状态是可以恢复的。
  • 执行 DML 操作的用户可以通过SELECT语句查询提交或回滚之前的修正。
  • 其他用户不能看到当前用户所做的改变,直到当前用户结束事务。
  • DML语句所涉及到的行被锁定,其他用户不能操作。

提交后的数据状态

  • 数据的改变已经被保存到数据库中。
  • 改变前的数据已经丢失。
  • 所有用户可以看到结果。
  • 锁被释放,其他用户可以操作涉及到的数据。

提交数据

  • 改变数据

第15章 JDBC_第24张图片

  • 提交改变

数据回滚后的状态

使用 ROLLBACK 语句可使数据变化失效:

  • 数据改变被取消。
  • 修改前的数据状态可以被恢复。

JDBC的事务处理

  • 事务:指构成单个逻辑工作单元的操作集合。
  • 事务处理:保证所有事务都作为一个工作单元来执行。即使出现了故障,都不能改变这种执行方式。当在一个事务中执行多个操作时,要么所有的事务都被提交commit,要么整个事务回滚(rollback)到最初状态。
  • 当一个连接对象被创建时,默认情况下是自动提交事务:每次执行一个 SQL 语句时,如果执行成功,就会向数据库自动提交,而不能回滚。
  • 为了让多个 SQL 语句作为一个事务执行:

调用java.sql.Connection对象的setAutoCommit(false)以取消自动提交事务。 在所有的 SQL 语句都成功执行后,调用commit()方法提交事务。 在出现异常时,调用rollback()方法回滚事务。 若此时java.sql.Connection没有被关闭, 则需要恢复其自动提交状态。

事务的隔离级别

  • 对于同时运行的多个事务, 当这些事务访问数据库中相同的数据时, 如果没有采取必要的隔离机制, 就会导致各种并发问题脏读:对于两个事务T1、T2,T1读取了已经被T2更新但还没有被提交的数据。之后若T2回滚,T1读取的内容就是临时且无效的。 不可重复读:对于两个事务T1、T2,T1读取了一个字段,然后T2更新了该字段。之后T1再次读取同一个字段,值就不同了。 幻读:对于两个事务T1、T2,T1从一个表中读取了一个字段,然后T2 在该表中插入了一些新的行。之后如果T1再次读取同一个表,就会多出几行。
  • 数据库事务的隔离性: 数据库系统必须具有隔离并发运行各个事务的能力,使它们不会相互影响,避免各种并发问题。
  • 一个事务与其他事务隔离的程度称为隔离级别。 数据库规定了多种事务隔离级别,不同隔离级别对应不同的干扰程度,隔离级别越高,数据一致性就越好,但并发性越弱
  • 数据库提供的 4 种事务隔离级别:

第15章 JDBC_第25张图片

  • Oracle支持的2种事务隔离级别:READ COMMITEDSERIALIZABLE。 所以Oracle不支持脏读。默认的是:READ COMMITED

  • MySQL支持4中事务隔离级别。 默认的是: REPEATABLE READ

在MySQL中设置隔离级别

  • 每启动一个mysql程序,就会获得一个单独的数据库连接。每个数据库连接都有一个全局变量@@tx_isolation,表示当前的事务隔离级别。MySQL 默认的隔离级别为REPEATABLE READ
  • 查看当前的隔离级别:select @@tx_isolation ;
  • 设置当前MySQL连接的隔离级别: set transaction isolation level read committed ;
  • 设置数据库系统的全局的隔离级别: set global transaction isolation level read committed ;

批量处理

  • 当需要成批插入或者更新记录时。可以采用Java的批量更新机制,这一机制允许多条语句一次性提交给数据库批量处理。通常情况下比单独提交更有效率
  • JDBC的批量处理语句包括下面两个方法: addBatch(String):添加需要批量处理的SQL语句或是参数。 executeBatch():执行批量处理语句。
  • 通常我们会遇到两种批量执行SQL语句的情况: 多条SQL语句的批量处理。 一个SQL语句的批量传参。

多条SQL语句的批量处理

第15章 JDBC_第26张图片

一个SQL语句的批量传参

情景 第15章 JDBC_第27张图片

解决 第15章 JDBC_第28张图片

数据库连接池

数据库连接池的必要性

  • 在开发基于数据库的Web程序时,传统模式基本是按以下步骤:   1、在主程序(如servlet、beans)中建立数据库连接。 2、进行SQL操作。 3、断开数据库连接。
  • 这种模式开发存在如下问题: 1、普通的JDBC数据库连接使用java.sql.DriverManager来获取数据库连接,每次向数据库建立连接的时候都要将java.sql.Connection对象加载到内存中,再验证用户名和密码。需要数据库连接的时候,就向数据库要求一个,执行完成后再断开连接。这样的方式将会消耗大量的资源和时间。数据库的连接资源并没有得到很好的重复利用。若同时有几百人甚至几千人在线,频繁的进行数据库连接操作将占用很多的系统资源,严重的甚至会造成服务器的崩溃。 2、对于每一次数据库连接,使用完后都得断开。否则,如果程序出现异常而未能关闭数据库连接,将会导致数据库系统中的内存泄漏,最终将导致重启数据库。 3、这种开发不能控制被创建的连接对象数,系统资源会被毫无顾及的分配出去。如连接过多,也可能导致内存泄漏,服务器崩溃

数据库连接池概念

  • 为解决传统开发中的数据库连接问题,可以采用数据库连接池技术。
  • 数据库连接池的基本思想就是为数据库连接建立一个“缓冲池”。预先在缓冲池中放入一定数量的连接,当需要建立数据库连接时,只需从缓冲池中取出一个,使用完毕之后再放回去。
  • 数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用现有的数据库连接,而不是重新建立一个。
  • 数据库连接池在初始化时将创建一定数量的数据库连接放到连接池中,这些数据库连接的数量是由最小数据库连接数来设定的。无论这些数据库连接是否被使用,连接池都将一直保证至少拥有这么多的连接数量。连接池的最大数据库连接数量限定了这个连接池能占有的最大连接数。当应用程序向连接池请求的连接数超过最大连接数量时,这些请求将被加入到等待队列中。

数据库连接池的工作原理

第15章 JDBC_第29张图片

数据库连接池技术的优点

  • 资源重用

由于数据库连接得以重用,避免了频繁创建、释放连接引起的大量性能开销。在减少系统消耗的基础上,也增加了系统运行环境的平稳性。

  • 更快的系统反应速度

数据库连接池在初始化过程中,往往已经创建了若干数据库连接置于连接池中备用。此时连接的初始化工作均已完成。对于业务请求处理而言,直接利用现有可用连接,避免了数据库连接初始化和释放过程的时间开销,从而减少了系统的响应时间。

  • 新的资源分配手段

对于多应用共享同一数据库的系统而言,可在应用层通过数据库连接池的配置,实现某一应用最大可用数据库连接数的限制,避免某一应用独占所有的数据库资源。

  • 统一的连接管理,避免数据库连接泄露

在较为完善的数据库连接池实现中,可根据预先的占用超时设定,强制回收被占用连接,从而避免了常规数据库连接操作中可能出现的资源泄露。

两种开源的数据库连接池

  • JDBC的数据库连接池使用javax.sql.DataSource来表示,javax.sql.DataSource只是一个接口,该接口通常由服务器(Weblogic、WebSphere、Tomcat)提供实现。也有一些开源组织提供实现,如:DBCP、C3P0。
  • DataSource 通常被称为数据源,它包含连接池连接池管理两个部分。习惯上也经常把 DataSource 称为连接池

DBCP 数据源

  • DBCP是Apache软件基金组织下的开源连接池实现。该连接池依赖该组织下的另一个开源系统:common-pool。如需使用该连接池实现,应在系统中增加如下两个 jar 文件: commons-dbcp.jar:连接池的实现。 commons-pool.jar:连接池实现的依赖库。
  • Tomcat的连接池正是采用该连接池来实现的。该数据库连接池既可以与应用服务器整合使用,也可由应用程序独立使用。
  • 数据源和数据库连接不同,数据源无需创建多个,它是产生数据库连接的工厂,因此整个应用只需要一个数据源即可
  • 当数据库访问结束后,程序还是像以前一样关闭数据库连接:conn.close()。 但上面的代码并没有关闭数据库的物理连接,它仅仅把数据库连接释放,归还给了数据库连接池

第15章 JDBC_第30张图片

C3P0 数据源

第15章 JDBC_第31张图片

数据库的分页语句

在编写Web应用程序等系统时,会涉及到与数据库的交互。如果数据库中数据量很大的话,一次检索所有的记录,会占用系统很大的资源,因此常常采用分页语句:需要多少数据就只从数据库中取多少条记录

以下是Oracle和MySQL的分页语句(从数据库表中的第M条数据开始取N条记录)。

Oralce

从数据库表中第M条记录开始检索N条记录。

select * 
from 
    (select e.*,rownum rn 
    from (select * from 表名 order by 主键 desc) e 
    where rownum<=N)
where rn>M;

例:

从表employees(主键为employee_id)中从11条记录还是检索20条记录。

select * 
from 
    (select e.*,rownum rn 
    from (select * from 表名 order by employees desc) e 
    where rownum<=20)
where rn>11;

MySQL

MySQL数据库最简单,是利用MySQL的limit函数——limit [offset,] rows。 从数据库表中M条记录开始检索N条记录的语句为: select [列名列表] from 表名 limit M,N ;

例:

从表t_user中从10条记录还是检索20条记录。

select * from t_user limit 10,20 ;

Apache—DBUtils简介(了解)

  • commons-dbutils是Apache组织提供的一个开源。 JDBC工具类库,它是对JDBC的简单封装,学习成本极低,并且使用dbutils能极大简化jdbc编码的工作量,同时也不会影响程序的性能。

  • API介绍: org.apache.commons.dbutils.QueryRunner org.apache.commons.dbutils.ResultSetHandler org.apache.commons.dbutils.DbUtils

DbUtils类

提供如关闭连接、装载JDBC驱动程序等常规工作的工具类。 里面的所有方法都是静态的。 主要方法如下:

  • public static void close() throws SQLException DbUtils类提供了三个重载的关闭方法。这些方法检查所提供的参数是不是null。如果不是的话,它们就关闭java.sql.Connectionjava.sql.Statementjava.sql.ResultSet
  • public static void closeQuietly() 这一类方法不仅能在java.sql.Connectionjava.sql.Statementjava.sql.ResultSet为null情况下避免关闭,还能隐藏一些在程序中抛出的SQLEeception。
  • public static void commitAndCloseQuietly(Connection conn) 用来提交连接,然后关闭连接,并且在关闭连接时不抛出SQL异常。
  • public static boolean loadDriver(String driverClassName) 这一方装载并注册JDBC驱动程序,如果成功就返回true。使用该方法,你不需要捕捉这个异常ClassNotFoundException。

QueryRunner类

该类简单化了SQL查询,它与ResultSetHandler组合在一起使用可以完成大部分的数据库操作,能够大大减少编码量。

QueryRunner类提供了两个构造方法:

  • 默认的构造方法。
  • 需要一个javax.sql.DataSource来作参数的构造方法。

QueryRunner类的主要方法

  • public Object query(Connection conn, String sql, Object[] params, ResultSetHandler rsh) throws SQLException 执行一个查询操作,在这个查询中,对象数组中的每个元素值被用来作为查询语句的置换参数。该方法会自行处理java.sql.PreparedStatementjava.sql.ResultSet的创建和关闭。
  • public Object query(String sql, Object[] params, ResultSetHandler rsh) throws SQLException 几乎与第一种方法一样;唯一的不同在于它不将数据库连接提供给方法,并且它是从提供给构造方法的数据源(DataSource)或使用的setDataSource 方法中重新获得java.sql.Connection
  • public Object query(Connection conn, String sql, ResultSetHandler rsh) throws SQLException 执行一个不需要置换参数的查询操作。
  • public int update(Connection conn, String sql, Object[] params) throws SQLException 用来执行一个更新(插入、更新或删除)操作。
  • public int update(Connection conn, String sql) throws SQLException 用来执行一个不需要置换参数的更新操作。

ResultSetHandler接口

该接口用于处理java.sql.ResultSet,将数据按要求转换为另一种形式。 ResultSetHandler 接口提供了一个单独的方法: Object handle (java.sql.ResultSet rs)

ResultSetHandler接口的实现类

  • ArrayHandler:把结果集中的第一行数据转成对象数组。
  • ArrayListHandler:把结果集中的每一行数据都转成一个数组,再存放到List中。
  • BeanHandler:将结果集中的第一行数据封装到一个对应的JavaBean实例中。
  • BeanListHandler:将结果集中的每一行数据都封装到一个对应的JavaBean实例中,存放到List里。
  • ColumnListHandler:将结果集中某一列的数据存放到List中。
  • KeyedHandler(name):将结果集中的每一行数据都封装到一个Map里,再把这些map再存到一个map里,其key为指定的key。
  • MapHandler:将结果集中的第一行数据封装到一个Map里,key是列名,value就是对应的值。
  • MapListHandler:将结果集中的每一行数据都封装到一个Map里,然后再存放到List。

转载于:https://my.oschina.net/mondayer/blog/3030292

你可能感兴趣的:(第15章 JDBC)