一、JDBC简介
- JDBC(Java数据基础连接,Java Database Connectivity)
是标准的Java访问数据库的API。
JDBC定义了数据库的连接,SQL语句的执行以及查询结果集的遍历等。
JDBC把这些操作定义为接口,位于包java.sql下面。
如java.sql.Connection、java.sql.Statement、java.sql.ResultSet等。
各个数据库提供商在自己的JDBC驱动中实现了这些接口。 - 查询实例:列出人员信息
Connection conne = null;
Statement stmt = null;
ResultSet rs = null;
try {
DriverManager.registerDriver(new com.mysql.jdbc.Driver());
conn = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/databaseWeb", "root", "admin");
stmt = conn.createStatement();
rs = stmt.executeQuery("select * from tb");
while(rs.next()) {
int id = rs.getInt("id");
String name = rs.getString("name");
}
} catch(SQLException e) {
e.printStackTrace();
} finally {
if (rs != null) {
rs.close();
}
if (stmt != null) {
stmt.close();
}
if (conn != null) {
conn.close();
}
} - 各种数据库的连接
MySQL jdbc:mysql://localhost:3306/db
Oracle jdbc:oracle:thin:@localhost:1521:db
DB2 jdbc:db2://localhost:6789/db
PostgreSQL jdbc:postgresql:/localhost:5432/db
Sysbase jdbc:jtds:sysbase://localhost:2638/db
SQLServer jdbc:microsoft:sqlserver://localhost:1433;databaseName=db
SQLServer 2005 jdbc:sqlserver://localhost:1433;databaseName=db
二、MySQL的乱码解决
- MySQL的乱码解决
常用的中文编码方式有GB2312、GBK、GB18030、UTF-8等。
GB2312是针对中文的编码方式,每个汉字均占两个字节,解析比较简单,
但是仅能编码中文字符。中文网站往往采用GB2312编码,例如BAIDU。
GBK类似于GB2312,但是比GB2312编码更多的中文以及中文字符。
GB18030又比GBK广泛,不仅可以编码中文,还可以编码少数民族的语言。
UTF-8能够编码目前为止的任意语言,国际化的网站往往采用UTF-8编码,
例如Google。但是UTF-8编码比较浪费空间,
有的汉字编码后能占到三个字符,解析也比较复杂。 - 从控制台修改编码
从控制台输入命令:
ALTER DATABASE database_name CHARACTER SET utf8;
查看当前数据库编码方式的命令为:
show variables like 'character_set_database';
注意修改数据库编码不会影响已经存在的表的编码方式。
如果表的编码方式原来为latin1,还需要单独修改表的编码方式。
修改某个数据表的编码方式的SQL为:
ALTER TABLE table_name CHARACTER SET utf8;
也可以在创建数据库的时候指定编码方式,例如:
CREATE DATABASE databse_name CHARACTER SET utf8; - 从配置文件修改编码
修改配置文件。用记事本打开MySQL目录下的my.ini文件
(C:\Program Files\MySQL\MySQL Server 5.0\my.ini),
找到下面的一句话:
default-chcharacter-set=latin1
应该有两行这样的代码。将编码方式latin1都修改为utf8即可。
注意修改该参数只会影响以后创建的数据库、表,
但不会影响已经存在的数据库,表。 - 利用图形界面工具修改
- URL中指定编码方式
指定JDBC连接的编码方式,方法是修改连接URL,例如:
Connection conn = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/database_name?
unicode=true&characterEncoding=utf-8",
"root", "admin")
三、JDBC基本操作:CRUD
- 数据库程序常被称为CRUD程序,因为它包括数据的创建Create、
读取查询Read、更新Update、删除Delete等操作,取首字母缩写便是CRUD。 - 查询数据库遵循固定的流程:
注册MySQL驱动、获取Connection、创建Statement、
查询数据库返回ResultSet对象、遍历ResultSet输出数据、
关闭ResultSet、关闭Statement、关闭Connection。 - 插入人员信息
Java程序也可以执行INSERT语句往数据库插入数据,方法仍然是使用Statement对象,
不过执行INSERT语句时要使用executeUpdate(String sql)方法
而不是executeQuery(String sql)方法。
executeUpdate()方法用于执行INSERT、UPDATE、DELETE等,
返回数据库中影响的行数,返回int类型。
而executeQuery()方法用于执行SELECT,
返回查询到的结果集,返回ResultSet类型。 - 注册数据库驱动
代码中注册驱动使用的是显示的注册,直接调用DriverManager的方法注册:
DriverManager.registerDriver(new com.mysql.jdbc.Driver());
对于MySQL来说,还可以使用下面的人一种方式:
new com.mysql.jdbc.Driver();
Class.forName("com.mysql.jdbc.Driver").newInstance();
MySQL驱动的构造函数中会调用DriverManager的注册方法。 - 获取自动插入的ID
Statement提供的getGenerateKeys()方法以ResultSet方法
返回所有自动生成的键值,遍历该ResultSet对象便可得到插入行的id。
rs = stmt.getGeneratedKeys();
rs.next();
rs.getInt(1); - 删除人员信息
删除数据使用Statement的executeUpdate(String sql)方法执行DELETE语句。
与INSERT不同的是,DELETE必须使用WHERE条件指定删除哪一行数据,
否则将删除所有数据。 - 修改人员数据
修改数据需要先把原来的数据呈现出来,客户做出修改后再保存数据。
需要注意的是修改信息的使用一定要保存id信息与action信息,
否则保存信息时不知道是修改的哪条数据,是保存还是添加。 - 使用PreparedStatement
除了使用Statement,还可以使用PreparedStatement。
PreparedStatement接口继承自Statement接口,是Statement的子类,
因此拥有Statement接口的所有方法。
PreparedStatement与Statement的最大区别是PreparedStatement可以使用参数,
也就是(?)号。PreparedStatement允许使用不完整的SQL语句,
空缺的部分使用问号(?)代替,直到执行前的时候设置进去。
PreparedStatement preStmt = null;
preStmt = conn.prepareStatement(sql);
preStmt.setInt(1, id);
preStmt.setString(2, name);
int result = preStmt.executeUpdate(sql);
由于PreparedStatement使用问号代替了SQL中所有可变的部分,
剩下了不变的部分,因此JDBC将PreparedStatement的SQL中
不变的部分进行预编译,下次执行时直接高效率执行。
Statement与PreparedStatement的区别:
1、Statement不预编译,执行效率相对较低。
PreparedStatement预编译,执行效率相对较高。
2、Statement不支持复杂类型。需要调用数据库函数转化。
PreparedStatement支持。通过响应的set方法可以设置。例如setDate。
3、Statement不支持大文本(Clob)类型字段。
有些数据库中SQL语句有长度限制。
PreparedStatement支持。大文本字段要通过setClob方法设置。
4、Statement不支持二进制(Blob)数据类型。
PreparedStatement支持。二进制数据要通过setBlob方法设置。
5、Statement支持批处理。不支持设置参数。
PreparedStatement支持。可以设置参数。 - Statement批处理SQL
Statement与PreparedStatement都能够批处理SQL语句,
也就是同时执行多条SQL语句。这些SQL语句必须是INSERT、UPDATE、
DELETE等SQL语句,它们执行后都返回一个int类型数,表示影响的行数。
Statement与PreparedStatement通过addBatch()方法添加一条SQL语句,
通过executeBatch()方法批量执行SQL语句,返回一个int数组,
代表各句SQL的返回值。
stmt = conn.createStatement();
stmt.addBatch(sql);
int[] result = stmt.executeBatch(); - PreparedStatement批处理SQL
Statement的批处理SQL必须为完整的SQL语句。而PreparedStatement就灵活得多,
既可以用完整的SQL(因为它继承自Statement),
又可以用带参数的不完整的SQL。
preStmt = conn.prepareStatement("");
preStmt.setInt(1, id);
preStmt.setString(2, name);
preStmt.addBatch();
int[] result = preStmt.executeBatch();
四、处理结果集
- 查询多个结果集
进行第一次查询时,stmt会返回一个ResultSet对象。
进行第二次查询时,stmt会返回一个全新的对象。
中间不需要执行rs.close()关闭第一个对象。
这是因为Statement与PreparedStatement有一个特性,
在返回ResultSet结果集时,会自动关闭上一次查询的结果集。 - 可以滚动的结果集
如果后面还有记录,next()会返回true,
同时自动向下滚动一条记录,否则返回false。
previous()方法刚好相反,如果前面还有记录,previous()会返回true,
同时自动向上滚动一条记录,否则返回false。
first()是滚动到第一条记录,如果第一条记录存在的话。
last()是滚动到最后一条记录。
为了效率,Statement默认返回的ResultSet是只可往后滚动的,
因此只有next()、last()方法时刻用的。
要想使用previous()、first()方法,可以这样创建Statement:
stmt = conn.createStatement(
ResultSet.TYPE_SCROLL_INSENSITIVE,
ResultSet.CONCUR_UPDATABLE);
rs = stmt.executeQuery("");
第一个参数指定Statement创建的ResultSet可以自由滚动,
第二个参数指定Statement创建的ResultSet可以直接修改。 - Pagination分页显示
1、代码1
String url = pageUrl.contains("?") ? pageUrl : pageUrl + "?";
if (!url.endsWith("?") && !url.endsWith("&")) {
url += "&";
}
StringBuffer buffer = new StringBuffer();
buffer.append("第 " + pageNum + "/" + pageCount + " 页 " +
"共 " + recordCount + " 记录 ");
buffer.append(pageNum == 1 ? "第一页 " :
"<a href='" + url + "pageNum=1'>第一页</a> ");
buffer.append(pageNum == 1 ? "上一页 " :
"<a href='" + url + "pageNum=" + (pageNum-1) + "'>上一页</a> ");
buffer.append(pageNum == pageCount ? "下一页 " :
"<a href='" + url + "pageNum=" + (pageNum + 1) + "'>下一页</a> ");
buffer.append(pageNum == pageCount ? "最后一页 " :
"<a href='" + url + "pageNum=" + pageCount + "'>下一页</a> ");
return buffer.toString();
2、代码2
final int pageSize = 10;
int pageNum = Integer.parseInt(request.getParameter("pageNum"));
int recordCount = DbManager.getCount(sql);
int pageCount = (recordCount + pageSize - 1) / pageSize;
int startRecord = (pageNum - 1) * pageSize;
sql = "SELECT * FROM table_name LIMIT ?, ? ";
DbManager.setParams(preStmt, startRecord, pageSize);
rs = preStmt.executeQuery(); - 带条件的查询
实用的查询页面能够设置复杂的查询条件。其基本原理是把查询条件
转化为对应的WHERE子句,然后使用包含该WHERE子句的SQL查询数据库。 - ResultSetMetaData元数据
JDBC可以直接获取Result对象的列名。
根据获得到的列名,可以动态的显示查询的各列内容。
ResultSet对象的列名可由ResultSetMetaData元数据获得。
ResultSet.getMetaData可以返回元数据,
遍历元数据即可获知ResultSet中有哪些列,每一列是什么类型。
ResultSetMetaData meta = rs.getMetaData();
int columnCount = meta.getColumnCount();
String[] columns = new String[columnCount];
for (int i = 1; i <= columnCount; i++) {
columns[i-1] = meta.getCountName(i);
}
for (String column : columns) {
rs.getString(column);
}
ResultSetMetaData元数据中还包括其他信息,
例如列名称、列类型、列长度等,可根据需要获取相应的信息。 - 直接显示中文列名
MySQL的驱动在处理中文列名时会有乱码。这时候可能需要对列名进行编码转化。
String columnName = rs.getMetaData().getColumnName(1);
columnName = new String(columnName.getBytes("latin1"), "utf-8");
五、JDBC高级应用
- DAO模式与Java Bean
DAO(数据库操作对象,Database Access Object)是JDBC下常用的模式,
保存数据时将Java Bean的属性拆分成正确的SQL语句,并保存到数据库中;
读取数据时将数据从数据库中读取出来,并通过setter方法设置到Java Bean中。 - 事务实例:转账
数据库是具有事务性的,这是数据库不同于其他存储方式的区别之一。一个事务内
可以执行多个操作,这些操作要么全部执行成功,要么全部执行失败。
事务有两个结果,提交(commit)与回滚(Rollback)。
如果两步转账都没有错误,则可以提交,转账结果保存进数据库。
如果其中任何一步出错了,则必须回滚,数据库不会有任何的改动。
conn.setAutoCommit(false);
conn.commit();
conn.rollback(); - 抛出异常自动回滚
设置自动提交为false后,事务的提交必须执行conn.commit(),而事务的回滚
不一定要显示的执行conn.rollback()。如果程序最后没有执行conn.commit(),
事务也会回滚。一般是直接抛出异常,终止本段程序的正常运行。 - 存储二进制数据
MySQL提供了BLOB类型字段用于存储二进制数据。用户上传后,
可以将文件保存到硬盘上,只把文件名、路径等保存进数据库,
也可以把文件内容直接存储到数据库中。
preStmt.setBinaryStream(
4, fileItem.getInputStream(), (int)fileItem.getSize());
preStmt.executeUpdate(); - 读取二进制数据
文件写入数据库中,还需要专门的程序将数据读出来。
读取文件内容同样要使用Stream。
InputStream ins = rs.getBinaryStream("content");
OutputStream ous = response.getOutputStream();
byte[] b = new byte[1024];
int len = 0;
while((len = ins.read(b)) != -1) {
out.write(b, 0, len); - 数据源(连接池)
先前的JDBC编程中,每操作一次数据库,都会经过下面的过程:创建Connection、
创建Statement对象、获取ResultSet、销毁ResultSet、销毁Statement、
断开Connection。即每操作一次数据库,都会创建连接、断开连接。
在实际的使用中,创建与断开Connection都会销毁一定的时间、IO资源,
在大量的并发访问时尤其明显。为了避免频繁地创建、断开数据库连接,
工程师们提出了数据源技术(Data Source),
也称为连接池(DBCP,Database Connection Pool)。
要操作数据库时,程序并不是直接创建Connection,
而是向连接池“申请”一个Connection。如果连接池中有空闲的Connection,
则返回该Connection,否则创建新的Connection。
使用完毕,然后会“释放”该Connection。连接池会将该Connection回收,
并交付其他的线程使用,达到减少创建、断开连接次数的目的。
Tomcat使用Jakarta-Commons Database Connection Pool作为数据源实现,
使用时只需按照Tomcat文档配置即可。
数据源可以配置在tomcat/conf/server.xml中,
也可以配置在tomcat/conf/context.xml中。
注意此时要把MySQL驱动放在Tomcat全局lib里下面,
而不能放在本web应用下面。
<Context cookies="true">
<Resource name="jdbc/databaseWeb"
auth="Container"
type="javax.sql.DataSource"
maxActive="100" maxIdle="30" maxWait="100000"
username="root" password="admin"
driverClassName="com.mysql.jdbc.Driver"
url="jdbc:mysql://localhost:3306/databaseWeb?
characterEncoding=utf-8"/>
</Context>
然后要在Web程序的Web.xml中配置数据源的引用。
<web-app>
<resource-ref>
<description>DB Connection</description>
<res-ref-name>jdbc/databaseWeb</res-ref-name>
<res-type>javax.sql.DataSource</res-type>
<res-auth>Container</res-auth>
</resource-ref>
</web-app>
使用Java代码查找JNDI:
Context initContext = new InitialContext();
Context envContext =
(Context) initContext.loopup("java:/comp/env");
DataSource ds =
(DataSource)envContext.loopup("jdbc/databaseWeb");
Connection conn = ds.getConnection();