数据库Java学习笔记JAVA

JDBC学习笔记

2015-10-02  本文已影响1862人  usopp

JDBC概述

在Java中,数据库存取技术可分为如下几类:JDBC直接访问数据库、JDO技术、第三方O/R工具,如Hibernate, ibatis 等。
JDBC是java访问数据库的基石,JDO, Hibernate等只是更好的封装了JDBC。
JDBC(Java Database Connectivity)是一个独立于特定数据库管理系统、通用的SQL数据库存取和操作的公共接口(一组API),定义了用来访问数据库的标准Java类库,使用这个类库可以以一种标准的方法、方便地访问数据库资源。
JDBC为访问不同的数据库提供了一种统一的途径,为开发者屏蔽了一些细节问题。
JDBC的目标是使Java程序员使用JDBC可以连接任何提供了JDBC驱动程序的数据库系统,这样就使得程序员无需对特定的数据库系统的特点有过多的了解,从而大大简化和加快了开发过程。

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

加载与注册 JDBC 驱动

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

加载 JDBC 驱动需调用 Class 类的静态方法 forName(),需要向其传递要加载的 JDBC 驱动的类名,并提供相关驱动。例如:加入 mysql 驱动:

  1. 解压 mysql-connector-java-5.1.7.zip
  2. 在当前项目下新建 lib 目录
  3. 把 mysql-connector-java-5.1.7-bin.jar 复制到 lib 目录下
  4. 右键 build-path , add to buildpath 加入到类路径下.

DriverManager 类是驱动程序管理器类,负责管理驱动程序通常不用显式调用 DriverManager 类的 registerDriver() 方法来注册驱动程序类的实例,因为 Driver 接口的驱动程序类都包含了静态代码块,在这个静态代码块中,会调用 DriverManager.registerDriver() 方法来注册自身的一个实例。

建立连接

可以调用 DriverManager 类的 getConnection() 方法建立到数据库的连接。
JDBC URL 用于标识一个被注册的驱动程序,驱动程序管理器通过这个 URL 选择正确的驱动程序,从而建立到数据库的连接。
JDBC URL的标准由三部分组成,各部分间用冒号分隔。
jdbc:<子协议>:<子名称>
协议:JDBC URL中的协议总是jdbc
子协议:子协议用于标识一个数据库驱动程序
子名称:一种标识数据库的方法。子名称可以依不同的子协议而变化,用子名称的目的是为了定位数据库提供足够的信息

//oracle数据库驱动的完整类名,mysql的为com.mysql.jdbc.Driver
String driver = "oracle.jdbc.driver.OracleDriver";
// 通过反射得到驱动对象,加载数据库驱动程序(对应的 Driver 实现类中有注册驱动的静态代码块.)
Class.forName(driver);
// 通过 DriverManager 的 getConnection() 方法获取数据库连接.
Connection connection = DriverManager.getConnection(jdbcUrl, user, password);

访问数据库

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

Statement

通过调用 Connection 对象的 createStatement 方法创建该对象,该对象用于执行静态的 SQL 语句,并且返回执行结果,Statement 接口中定义了下列方法用于执行 SQL 语句:
ResultSet excuteQuery(String sql) 传入的sql语句应为SELECT,ResultSet是结果集. 封装了使用 JDBC 进行查询的结果.
int excuteUpdate(String sql) 传入的sql语句可以为INSRET、UPDATE 或 DELETE这些不返回结果的语句,也可以是一些sql DDL语句。返回值是一个整数,指示受影响的行数(即更新计数)。对于 CREATE TABLE 或 DROP TABLE等不操作行的语句,executeUpdate 的返回值总为零。

ResultSet

通过调用 Statement 对象的 excuteQuery() 方法创建该对象,ResultSet 对象以逻辑表格的形式封装了执行数据库操作的结果集 ,ResultSet 接口由数据库厂商实现,ResultSet 对象维护了一个指向当前数据行的游标,初始的时候,游标在第一行之前, 可以通过 ResultSet 对象的 next() 方法移动到下一行。当指针对位到一行时, 可以通过调用 getXxx(index) 或 getXxx(columnName)获取每一列的值. 例如: getInt(1), getString("name")。

关闭资源

Connection、Statement、resultSet 都是应用程序和数据库服务器的连接资源. 使用后一定要关闭.
需要在 finally 中关闭 Connection 和 Statement 、resultSet对象,关闭的顺序是: 先关闭后获取的. 即先关闭 resultSet、Statement 后关闭 Connection。

Statement statement = connection.createStatement();
String sql = "SELECT id, name, email, birth FROM customers";

// 执行查询, 得到 ResultSet
ResultSet rs = statement.executeQuery(sql);
// 处理 ResultSet
while(rs.next()){
    int id = rs.getInt(1);
    String name = rs.getString("name");
    String email = rs.getString(3);
    Date birth = rs.getDate(4);
    
    System.out.println(id);
    System.out.println(name);
    System.out.println(email);
    System.out.println(birth);
}
//finally{...}最后释放资源
java类型 SQL类型
boolean BIT
byte TINYINT
short SMALLINT
int INTEGER
long BIGINT
String CHAR,VARCHAR,LONGVARCHAR
byte array BINARY , VAR BINARY
java.sql.Date DATE
java.sql.Time TIME
java.sql.Timestamp TIMESTAMP
JAVA数据类型和MYSQL数据类型对应关系表

SQL注入攻击

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

String username = "a' or password = ";
String password = "or '1'='1";
String sql = "select * from users where username ='"+username+"'and password ='"+password+"'";
sql = "select * from users where username = 'a' or password ='and password =' or '1'='1'";

PreparedStatement

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

String sql = "insert into table values(?,?)";
PreparedStatement preparedStatement = connection.preparedStatement(sql);
preparedStatement.setInt(1,5);
preparedStatement.setString(2,"Hello");

PreparedStatement和Statement相比:

DatabaseMetaData类

Java 通过JDBC获得连接以后,得到一个Connection 对象,可以从这个对象获得有关数据库管理系统的各种信息,包括数据库中的各个表,表中的各个列,数据类型,触发器,存储过程等各方面的信息。根据这些信息,JDBC可以访问一个实现事先并不了解的数据库。获取这些信息的方法都是在DatabaseMetaData类的对象上实现的,而DataBaseMetaData对象是Connection对象上调用getMetaData()获得的。DatabaseMetaData 类中提供了许多方法用于获得数据源的各种信息,通过这些方法可以非常详细的了解数据库的信息:

            getURL():返回一个String类对象,代表数据库的URL。
            getUserName():返回连接当前数据库管理系统的用户名。
            isReadOnly():返回一个boolean值,指示数据库是否只允许读操作。
            getDatabaseProductName():返回数据库的产品名称。
            getDatabaseProductVersion():返回数据库的版本号。
            getDriverName():返回驱动驱动程序的名称。
            getDriverVersion():返回驱动程序的版本号。

ResultSetMetaData 类

当有一个结果集rsultSet,但是不知道里面有多少列和每列的名字时,可以使用ResultSetMetaData。还能用于编写通用的查询方法。
获取ResultSetMetaData对象:ResultSetMetaData rsmd = rsultSet.getMetaData();
一些常用的方法:

            getColumnName(int column):获取指定列的名称
            getColumnLabel(int count):获取指定列的别名,其索引从1开始
            getColumnCount():返回当前 ResultSet 对象中的列数。 
            getColumnTypeName(int column):检索指定列的数据库特定的类型名称。 
            getColumnDisplaySize(int column):指示指定列的最大标准宽度,以字符为单位。 
            isNullable(int column):指示指定列中的值是否可以为 null。 
            isAutoIncrement(int column):指示是否自动为指定列进行编号,这样这些列仍然是只读的。 

DAO(DATA ACCESS OBJECT)设计模式

实现功能模块化。更有利于代码的维护和升级。
访问数据信息的类,包含了对数据的CRUD(create,read,update,delete)。而不包含任何业务相关的信息。
使用JDBC编写DAO可能会包含的方法:

//insert,update,delete操作都可以包含在其中
void update(Connection connection,String str,Object ... args);
//查询一条记录,返回相应的对象
<T> T get(Class<T> clazz,String sql,Object ... args);
//查询多条记录,返回对应的集合
<T> List<T> get(Class<T> clazz,String sql,Object ... args);
//返回某条记录的某一个字段的值或一个统计的值(一共有多少条记录等)
<E> E getForValue(String sql,Object ... args);

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

通过Connection重载的prepareStatement(sql,flag)方法来获得包含了主键值的preparedStatement对象,然后在用preparedStatement对象的getGeneratedKeys()获取包含了主键的resultSet对象,在resultSet对象中只有一列GRNERATED_KEY,用于存放生成的主键值。
Oracle是通过序列生成的主键值,不能获取

Connection conn = JdbcUtil.getConnection();

String sql = "insert into user(name,password,email,birthday) 
            values('abc','123','abc@sina.com','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));

使用JDBC插入大对象

Oracle LOB

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

使用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数据了。

步骤

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

MySQL BLOB

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

类型 大小
TinyBlob 255
Blob 65K
MidiumBlob 16M
LongBlob 4G

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

//插入 BLOB 类型的数据必须使用 PreparedStatement:因为 BLOB 类型的数据时无法使用字符串拼写的。
//调用 setBlob(int index, InputStream inputStream)
String sql = "INSERT INTO customers(name, email, birth, picture) VALUES(?,?,?,?)";
preparedStatement = connection.prepareStatement(sql);
preparedStatement.setString(1, "ABCDE");
preparedStatement.setString(2, "abcde@atguigu.com");
preparedStatement.setDate(3,new Date(new java.util.Date().getTime()));

InputStream inputStream = new FileInputStream("Hydrangeas.jpg");
preparedStatement.setBlob(4, inputStream);

preparedStatement.executeUpdate();
//读取 blob 数据:使用 getBlob 方法读取到 Blob 对象
//调用 Blob 的 getBinaryStream() 方法得到输入流。再使用 IO 操作即可. 
connection = JDBCTools.getConnection();
String sql = "SELECT id, name customerName, email, birth, picture FROM customers WHERE id = 13";
preparedStatement = connection.prepareStatement(sql);
resultSet = preparedStatement.executeQuery();

if(resultSet.next()){
    int id = resultSet.getInt(1);
    String name = resultSet.getString(2);
    String email = resultSet.getString(3);
    
    System.out.println(id + ", " + name  + ", " + email);
    Blob picture = resultSet.getBlob(5);
    
    InputStream in = picture.getBinaryStream();
    System.out.println(in.available()); 
    
    OutputStream out = new FileOutputStream("flower.jpg");
    
    byte [] buffer = new byte[1024];
    int len = 0;
    while((len = in.read(buffer)) != -1){
        out.write(buffer, 0, len);
    }

数据库事务

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

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

如果多个操作, 每个操作使用的是自己的单独的连接, 则无法保证事务.。
具体步骤:
1). 事务操作开始前, 开始事务:取消 Connection 的默认提交行为。connection.setAutoCommit(false);
2). 如果事务的操作都成功,则提交事务: connection.commit();
3). 回滚事务: 若出现异常, 则在 catch 块中回滚事务:

try {
    //设置取消默认提交
    connection.setAutoCommit(false);
    ...
    // 提交事务
    connection.commit();
} catch (Exception e) {
    e.printStackTrace();
    // 回滚事务
    try {
        connection.rollback();
    } catch (SQLException e1) {
        e1.printStackTrace();
    }
} finally {...}

数据库的隔离级别

对于同时运行的多个事务, 当这些事务访问数据库中相同的数据时, 如果没有采取必要的隔离机制, 就会导致各种并发问题:

数据库事务的隔离性: 数据库系统必须具有隔离并发运行各个事务的能力, 使它们不会相互影响, 避免各种并发问题。
一个事务与其他事务隔离的程度称为隔离级别. 数据库规定了多种事务隔离级别, 不同隔离级别对应不同的干扰程度, 隔离级别越高, 数据一致性就越好, 但并发性越弱。

数据库提供的 4 种事务隔离级别:

隔离级别 描述
READ UNCOMMITTED(读未提交数据) 允许事务读取未被其它事务提交的变更,脏读、不可重复读、幻读都会出现
READ COMMITTED(读已提交数据) 只允许读取事务已经提交的变更,可避免脏读,但不可重复读、幻读仍会出现
REPEATABLE READ(可重复读) 确保事务可以多次从一个字段中读取相同的值,但在这个事务的持续期间,禁止其它事务对这个字段进行更新,可避免脏读和不可重复读,但幻读问题仍存在
SERIALIZABLE(串行化) 确保事务可以从一个表中读取相同的行,在这个事务持续的期间,禁止其它事务对该表进行插入、更改和删除操作,所有的并发问题都可避免,但性能十分低下

Oracle 支持的 2 种事务隔离级别:READ COMMITED, SERIALIZABLE. Oracle 默认的事务隔离级别为: READ COMMITED

Mysql 支持 4 中事务隔离级别. Mysql 默认的事务隔离级别为: 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;

批量处理JDBC语句提高处理速度

当需要成批插入或者更新记录时。可以采用Java的批量更新机制,这一机制允许多条语句一次性提交给数据库批量处理。通常情况下比单独提交处理更有效率。
JDBC的批量处理语句包括下面两个方法:

通常我们会遇到两种批量执行SQL语句的情况:

多条SQL语句批量处理

...
Statement st = connection.createStatement();
st.addBatch(sql1);
st.addBatch(sql2);
...
st.excuteBatch();
...

一个SQL语句批量传参

...
PreparedStatement pst = connection.preparedStatement(sql);
for(int i = 0;i<n;i++){
    pst.setInt(1,i);
    ...
    pst.addBatch();
}
pst.excuteBatch();
...

JDBC数据库连接池的必要性

在使用开发基于数据库的web程序时,传统的模式基本是按以下步骤:

这种模式开发,存在的问题:

数据库连接池(connection pool)

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

数据库连接池技术的优点

两种开源的数据库连接池

DBCP 数据源

数据源和数据库连接不同,数据源无需创建多个,它是产生数据库连接的工厂,因此整个应用只需要一个数据源即可。
当数据库访问结束后,程序还是像以前一样关闭数据库连接:conn.close();

但上面的代码并没有关闭数据库的物理连接,它仅仅把数据库连接释放,归还给了数据库连接池。


C3P0 数据源


Apache—DBUtils简介

DbUtils类

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

QueryRunner类

ResultSetHandler接口

该接口用于处理 java.sql.ResultSet,将数据按要求转换为另一种形式。

ResultSetHandler 接口提供了一个单独的方法:Object handle (java.sql.ResultSet .rs)。

ResultSetHandler 接口的实现类:

数据库的分页语句

在编写Web应用程序等系统时,会涉及到与数据库的交互,如果数据库中数据量很大的话,一次检索所有的记录,会占用系统很大的资源,因此常常采用分页语句:需要多少数据就只从数据库中取多少条记录。以下是Sql Server,Oracle和MySQL的分页语句(从数据库表中的第M条数据开始取N条记录):

MySQL数据库

My sql数据库最简单,是利用mySQL的LIMIT函数,LIMIT [offset,] rows从数据库表中M条记录开始检索N条记录的语句为:
SELECT [列名列表] FROM 表名称 LIMIT M,N
例如从表Sys_option(主键为sys_id)中从10条记录还是检索20条记录,语句如下:
select * from sys_option limit 10,20

Oralce数据库

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

select * from(
    select ROWNUM rownum_,t1.* from(
        select * from table order by table.id desc
        ) row_
    where rownum <= ?
    )
where rownum_ > ?

例如从表employees(主键为employee_id)中从11条记录还是检索20条记录,语句如下:


上一篇 下一篇

猜你喜欢

热点阅读