Java核心技术卷II(第8版) – 读书笔记 – 第4章

本章主要记录Java操作数据库

1、JDBC是Java操作数据库的接口,现在的版本已经是JDBC 4了。

2、JDBC的设计理念就是提供一套基本统一,纯Java的API数据库访问接口。JDBC实际是驱动管理器,各个厂家提供JDBC规范的驱动,注册到管理器中。这样,开发者使用JDBC API,而数据库厂商使用JDBC驱动API。

3、JDBC驱动的分类:
(1)JDBC/ODBC桥:将JDBC翻译成ODBC,然后实际调用ODBC来完成数据库操作。
(2)JDBC+本地代码:用JNI调用其他语言的驱动,封装成JDBC。
(3)纯Java客户端,使用协议无关的请求发送给服务器。
(4)纯Java客户端,JDBC客户端直接翻译成特定的C/S请求,发到服务器端。

4、JDBC实现了一个很好的目标:
(1)程序员使用几乎一致的JDBC API操作各种类型的数据库
(2)厂商自己维护JDBC驱动。

5、传统的Java数据库系统:

Client(JDBC) -> 数据库服务器

现在一半都会用三层分离:客户端 -> 中间件 -> 数据库

这样的话,JDBC主要运行在中间件这层,而客户端与中间件可以用HTTP、或者RPC、RMI等完成调用。

6、在关系模型中,我们将数据分配到多个表中,使得不会有冗余信息的出现,以防止更新时出现不一致的问题。

7、一些SQL语句

查询:

select * from books

select ISBN, price, title from books

select title from books where price <= 10.00

select title from books where id <> 1  #注意<>才是不等于,!=是不标准的!

select title from books where id = 1  #注意=而不是==

注意上述查询中,<>才是不等于,而=而不是==表示等于。

模糊查询用like,%表示0~N个字符,下划线_表示单个字符。

select title from books where title like '%n_x%'  #可以匹配标题linux或者unix

select * from books, publishers where books.publisher_id = publisher.publisher_id

注意上面的交叉表查询,实际是迪卡尔乘积的子集,它只返回有意义的并表(ID一致)。

更新或者删除操作:

update books set price = price - 5.00 where title like '%c++%'  #全部的c++书减价5块

delete from books where title like '%c++%'  #删除所有c++的书

插入数据:

insert into books values('A guide to java', '0-33-4-22-5555', 0, 45.5)  #必须逐项对应books的每个字段

创建表:

create table books
(
  title CHAR(50),
  ISBN CHAR(50),
  id int(10),
  price decimal(10,2)
)

8、除了选择MySQL、Oracle等之外,我们其实可以用Apache Derby,它已经成为了JDK6的一部分。

9、启动数据库:

cd lib
java -jar ./derbyrun.jar server start

关闭数据库:

java -jar ./derbyrun.jar server shutdown

10、用Derby自带客户端连接:

先创建配置文件,指定驱动,数据库路径等:

ij.driver=org.apache.derby.jdbc.ClientDriver
ij.protocol=jdbc:derby://localhost:1527/
ij.database=LHY;create=true

然后是启动客户端,用配置文件启动:

#ij命令是开启客户端
java -jar ./derbyrun.jar ij -p ./ij.properties

创建数据库:

ij> create TABLE msg(content char(20));
已插入/更新/删除 0 行

插入数据:

ij> insert into msg values('Hello, derby!');
已插入/更新/删除 1 行

选择:

ij> select * from msg ;
CONTENT
--------------------
Hello, derby!       

已选择 1 行

删除:

ij> drop table msg;
已插入/更新/删除 0 行

11、原来有的JDBC驱动是可以自动注册的……(有Jar包就能被注册)。但是这种情况太罕见了,我们掠过,即我们对于所有驱动都手动注册。

方法1:

Class.forName("org.apache.derby.jdbc.ClientDriver");

方法2:

java -Djdbc.drivers=org.apache.derby.jdbc.ClientDriver

方法3:

System.setProperty("jdbc.drivers","org.apache.derby.jdbc.ClientDriver;xxxxx");

如上面所示,这种方法可同时支持多种驱动

12、有了JDBC驱动之后,我们就可以连接到数据库了。

主要是通过DriverManager.getConnection(url)来获取Connection。

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;

public class DerbyTest {
	public static void main(String[] args) {
		// Load JDBC Driver
		try {
			Class.forName("org.apache.derby.jdbc.ClientDriver");
		} catch (Exception e) {
			e.printStackTrace();
			return;
		}
		System.out.println("Succ load JDBC Driver.");
		// Make db url and connect
		String url = "jdbc:derby://localhost:1527/lhy;create=true;user=lhy;password=lhy";
		Connection conn = null;
		try {
			// conn
			conn = DriverManager.getConnection(url);
			System.out.println("Succ connect to derby database.");
			// create stmt
			Statement stmt = conn.createStatement();
			// create db
			stmt.execute("create table msg(msg char(20))");
			System.out.println("Succ create table.");
			// insert db
			stmt.execute("insert into msg values('HAHA')");
			System.out.println("Succ insert row.");
			stmt.close();
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			return;
		} finally {
			try {
				conn.close();
			} catch (Exception e) {

			}
		}
	}
}

13、JDBC数据库操作基本步骤
(1)Connection conn = DriverManager.getConnection(xxx,xx,xx)
(2)Statement stmt = conn.createStatement();
(3)stmt.executeUpdate(什么都不返回,适合Update/Insert等) ,stmt.executeQuery()返回ResultSet
(4)如果有ResultSet,遍历每条返回的记录:

while(rs.next()) {
    rs.getString("Title"); //使用名字做为当前row的column的key
    rs.getDouble(2); //使用下标做为取回的row的column的key
}

注意这个一定要rs.next()即使你只第一条,也一定要.next()一下才能移动游标到第一条之前,犯了很多这个错误了,切记!

14、 你也可以直接在Statement上直接调用getResultSet(),此外,stmt还提供了:

getUpdateCount(),如果你执行的是update的sql语句,将返回收到影响的row总数(例如实际被更新的行数)。

15、ResultSet除了getXXX(String/int)  xxx为类型名字的accessor外,还提供了column域名到下标的转换:

int col = rs.findColumn(String)

rs.getMetaData()可以获取“元数据”,例如你select的每一列头是什么名字。

16、每个Connection可以创建若干个Statement,但是同一个Statement只能打开一个ResultSet。这三者都会占用一定资源,因此当不用任何一者时,要主动调用close()方法关闭他们。

17、如果在Statement上调用close,将关闭其上打开的所有ResultSet。同理,如果Connection上close,将关闭所有相关资源。

18、JDK6之后对SqlException进行了改造,使得它可以链式传递错误。

当抛出一个异常后,我们可以如下简单的处理:

for(Throwable t: sqlException)
{
    //do sth
}

19、stat.getWarning()可获取不致命但是数据库驱动认为不太正常的警告错误。

20、有的时候,我们的业务逻辑(sql语句模式)基本是固定的,但是某一个参数必须到运行时才能决定,例如查询某一个作者的所有书:

select * from books where author = ?

这里的?必须等着用户输入才能决定,但是其他部分都是固定的,如果我们每次都拼sql串得多浪费啊。

Java提供了“动态绑定”的sql语句,PreparedStatement。

String sql = "seelct * from books where id=?"
PrepareteStatement pstmt = conn.prepareStatement(sql);
for(int i=0; i<100; i++) {
    pstmt.setInt(1, i); // 1指第一个动态绑定参数,88是值。
    ResultSet rs = pstmt.executeQuert();
    .....处理rs.....
}

如上所述,这种动态绑定可以多次运行~使用PreparedStatement还有另外一个好处:避免sql注入。

如果不是select这种会返回RS的,可以用PreparedStatement的executeUpdate,同理的了。

PreparedStatement.clearParameters()可以清除所有的动态绑定的参数。

21、一般的数据库都可以存放大对象,大二进制称为BLOB,大字符对象称为CLOB。

读取BLOB:

String sql = "select image from books where id=?";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.set(1, id);
Result rs = pstmt.executeQuery();
if(rs.next())
{
    Blob bimg = result.getBlob(1);
    Image img = ImageIO.read(bimg.getInputStream());
}

写入BLOB:

Blob bimg = conn.createBlob();
int off = 0;
OutputStream out = bimg.setBinaryStream(off);
ImageIO.write(bimg, "PNG", out);
PreparedStatement pstmt = conn.prepareStatement("insert into books values(?, ?)");
pstmt.set(1, id);
stmt.set(2, bimg);
stmt.executeUpdate();

22、有时sql语句需要转义,以防止解析错误。

23、有时一个execute执行sql语句可能会返回多个结果集。此时可以用getMoreResults()来移动到下一个结果集。当返回false时候就移动到最后了。

如果是update也可能产生多个计数,此时getUpdateCount() 返回-1时表示结束了。

注意updatecnt可能和resultset是混合在一起的。

bool done = false;
boolean isResult = stmt.execute(sql);
while(!done)
{
    if(isResult)
    {
        ResultSet rs = stmt.getResultSet();
        ...
    }
    else
    {
        int ucnt = stmt.getUpdateCount();
        if(ucnt>=0)
        {
            ...
        }else
        {
            done = true;
        }
    }
    isResult = stmt.getMoreResults();
}

24、自动获取主键,不解释了,直接代码。

stmt.executeUpdate(sql, Statement.RETURN_GENERATED_KEYS);
ResultSet rs = stmt.getGeneratedKeys();
if(rs.next())
{
    int key = rs.getInt(1);
    ....
}

25、ResultSet的next()不仅可以遍历,还可以是可修改的,即一遍浏览,一遍就把需要update的给更新好了。

这时获取Statement需要加上另外一个参数,使用这个函数重载:

Statement createStatement(int resultSetType, int resultSetConcurrency);

这个resultSetType是决定是否可以前进后退什么的。

concurrency很自然就是 ResultSet.CONCUR_READ_ONLY (只读)ResultSet.CONCUR_UPDATABLE(可更新)。

如果设置了可前向,则可以rs.previous(),如果设置了相对,还可以rs.relative(n)。整数前移,负数后移。或者直接绝对定位rs.absolute(i)。

rs.getRow()获取行号。

26、回到更新的问题,如果需要更新

rs.updateDouble(xxxx)

等updateXXX即可。

之后一定要rs.updateRow()一下,否则是不保存更新的!!

也可以rs.deleteRow()来删除行。

27、行集RowSet比较与ResultSet的优点是,操作时无需保持与数据库的连接。

CachedRowSet允许在断开连接情况下操作。

WebRowSet是一端结果行集,可以转存为xml。

FilteredRowSet和JoinRowSet可以在RowSet基础上轻量操作,如SELECT和JOIN。

JdbcRowSet继承了JavaBeans中的get和set方法,可以把结果集转成bean。

28、下面以CachedRowSet为例:

ResultSet rs = ....
CachedRowSet crs = new com.sun.rowset.CachedRowSetImpl();
crs.populate(rs);
conn.close(); // RowSet can do without conn open

29、元数据metaData,对于数据库这层的在conn.getMetaData()获取。对于某次select的某个表的,在rs.getMetaData()获取。

ResultSetMetaData.getColumnLable(i),获取第i列名字

ResultSetMetaData.getColumnDisplaySize(i),获取第i列宽度

30、事务:可以将一组非原子操作组成一个事务(transaction)。当事务都成功执行后,可以提交(commit),如果有某个原子操作失败了,也可以回滚(rollback)。

默认情况,数据库连接是自动提交的,我们先要关掉它:

conn.setAutoCommit(false);

然后的每次Update后

stmt  = conn.createStatement();

stmt.executeUpdate(sql1);

。。。。

如果都成功了

stmt.commit()

否则可以

stmt.rollback();

31、如果要更细粒度的事务,可以用断电,conn.setSavePoint();之后conn.commit()或者conn.releaseSavePoint(svp)

32、当一次要执行很多insert、update等时,可以用批操作:

一定要先关闭自动commit!!

conn.setAutoCommit(false);
String sql = "insert ..."
while(...)
{
    String sql = xxxx
    stmt.addBatch(sql);
}
//积攒很多batch,最后一起提交
stmt.executeBatch();
conn.commit();
conn.setAutoCommit(true);

33、数据库对应类型和Java类型对应表见书243页。。

34、更多关于JDBC和SQL的,可以看《JDBC API Tutorial and Reference》

35、由于数据库资源很有限,一般都会用数据库连接池。它也做成一个DriverManager的样子,因此对程序员是透明的。

36、Java的javax.naming.ldap提供了对LDAP的访问接口。

本章结束。

 

 

 

One thought on “Java核心技术卷II(第8版) – 读书笔记 – 第4章

  1. is_i99.0@qq.com

    感觉第四章的数据库表是不是不完整啊? 每次运行代码都报错啊

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *