本章主要记录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的访问接口。
本章结束。
感觉第四章的数据库表是不是不完整啊? 每次运行代码都报错啊