----------------------------JDBC 概述 -------------------------------
JDBC--------Java Data Base Connectivity , java 数据库连接
J2SE 的一部分,是由一些类和接口构成的 API ,由 java.sql 和 javax.sql 包组成
连接数据库的步骤 ( 连接 Mysql 数据库,使用的是连接 mysql 的方式 )
1. 注册驱动
2. 建立连接( Connection )
3. 创建执行 SQL 的语句( Statement 或 PreparedStatement )
4. 执行语句
5. 处理执行结果( ResultSet )
6. 释放资源
----------------------- 学习 JDBC 需要了解的类及接口的含义 -----------------
1. DriverManager 是管理一组 JDBC 驱动程序的基本服务
2. Connection 是与特定数据库的连接(会话)。在连接上下文中执行 SQL 语句并返回结果
3. Statement 是一个接口类,用于执行静态 SQL 语句并返回它所生成结果的对象
在默认情况下,同一时间每个 Statement 对象在只能打开一个 ResultSet 对象。因此,如果读取一个 ResultSet 对象与读取另一个交叉,则这两个对象必须是由不同的 Statement 对象生成的。如果存在某个语句的打开的当前 ResultSet 对象,则 Statement 接口中的所有执行方法都会隐式关闭它
4. PreparedStatement 表示预编译的 SQL 语句的对象, SQL 语句被预编译并存储在 PreparedStatement 对象中。然后可以使用此对象多次高效地执行该语句。
PreparedStatement 比 Statement 的执行速度快,用 System.currentTimeMillis() 方法测试出来,原因是在数据库的整个操作中,获取数据库的连接 con.getConnection(url,user,password) 是最耗时的,就如同两个城市之间要搭建一个桥梁一样,而发送一条 SQL 语句,就犹如汽车过桥一样,就很快了,获取连接底层使用的是 TCP/IP 协议,也就是 socket 协议,跟服务器连接上就要进行三次握手才能建立 TCP 的连接,建立连接以后,它要把用户和密码发送给数据库,然后数据库去校验一下用户名和密码有没有权限。 PreparedStatement 会在执行 SQL 语句前先对其进行预编译,而 Statement 会使数据库频繁编译 SQL ,可能造成数据库缓冲区溢出
PraparedStatement 还可以解决 Statement 中存在的 SQL 注入的问题
例如在一条查询语句中, select * from jdbctest where name=?
如果在问号处,输入一个数据库中存在的姓名,就会显示出来有关这个姓名的全部信息
但是也可以输入这样的一类字符
0 or 0 0 or 1 1 or 1
0 and 1
0 和 1 就代表了 C 语言中的 true 和 false 1 or 1 如果写成 9 or 9 52 or 53 也正确
这就是属于 statement 中的一个 bug
5. ResultSet 表示数据库结果集的数据表,通常通过执行查询数据库的语句生成
ResultSet 对象具有指向其当前数据行的光标。最初,光标被置于第一行之前。 next 方法将光标移动到下一行;因为该方法在 ResultSet 对象没有下一行时返回 false ,所以可以在 while 循环中使用它来迭代结果集。
默认的 ResultSet 对象不可更新,仅有一个向前移动的光标。因此,只能迭代它一次,并且只能按从第一行到最后一行的顺序进行。可以生成可滚动和 / 或可更新的 ResultSet 对象。
要注意的相关问题
1. 在 Connection 连接中,有一个 getConnection 方法,功能是获取一个数据库的 url 连接,每一种数据库的 url 都不相同,但是都有一定的规律存在
Connection conn=DriverManager.getConnection(url,user,password);
格式为: JDBC: 子协议 : 子名称 // 主机名 : 端口号 / 数据库名 ? 属性名 = 属性值 &...
user,password 可以用 ” 属性名 = 属性值 ” 方式告诉数据库
其他参数如: useUnicode=true&characterEncoding=GBK
2. 数据库连接 Connection 是非常稀有的资源,用完后必须马上释放,如果 Connection 不能及时正确的关闭将导致系统瘫痪, Connection 的使用原则是尽量晚创建,尽量早的释放,减少用户与数据库所建立的 TCP/IP 连接的时间,这样可以减轻数据库的负担
创建过多的连接数量,会占用太多的系统资源,就像连接 netmeeting 一样,一台机器做源连接过多的用户,这台机器就会越来越卡,最后很容易死机
3. CRUD 称为数据库的基本操作,也就是增删改查, C----Create 增, R----Read 查,
U----Update 改, D----Delete 删
插入,更新和删除数据返回的是 有多少条记录被影响的个数
查询数据返回的是 ResultSet 结果集
------------------------ 处理一些特殊类型的数据 -------------------------
1. 日期型数据,在一个插入方法中,其中一个参数 Date 类型的 birthday 是由外界传进来的,是一个 Util 类型的 Date ,
而在方法内部使用的 Date 类型的对象应该是 java.sql.Date 类型的, sql 包中的 Date 是 Util 包中的 Date 的子类,所以必须向下转型
public int create(java.util.Date birthday) throws SQLException{
con =DBConnection.getInstance ().getConnection ( url , user , password );
String sql= "insert into birthday(birthday) values (?)" ;
ps = con .prepareStatement(sql);
ps .setDate(1, new java.sql.Date(birthday.getTime()));
int len= ps .executeUpdate();
return len;
}
} 在读取的时候,又要将 sql 包中的 Date 类型对象转化为 Util 包中的 Date 类型对象,传递值的时候,会默认的进行向上转型,不用刻意的进行转换
public void read() throws SQLException{
con =DBConnection.getInstance ().getConnection ( url , user , password );
String sql= "select id,birthday from birthday" ;
ps = con .prepareStatement(sql);
rs = ps .executeQuery();
while ( rs .next()){
System. out .println( rs .getInt( "id" )+ "/t" +
rs .getDate( "birthday" ));
}
2. 大字符集类型,可以保存大容量的文本内容,在 SQLServer 和 Oracle 中为 CLOB 类型, MySQL 中为 TEXT 类型,在 Java 中使用字符流 Reader 和 Writer
用输入流指定一个文件,将文件的内容存放到数据库中,在从数据库中读取文件的内容,使用输出流写入到某个文件中
ResultSet 结果集中的内容可以存放到 Clob 类型的对象中,也可以存放到 String 类型的对象中
将文件中的大量字符写入数据库的方法:
public int create() throws SQLException, FileNotFoundException{
try {
con =DBConnection.getInstance ().getConnection( url , user , password );
String sql= "insert into clob(big_text) values(?)" ;
ps = con .prepareStatement(sql);
File file= new File( "Java/Lesson/JDBC/DBConnection.java" );
Reader reader= new FileReader(file);
BufferedReader br= new BufferedReader(reader);
ps .setCharacterStream(1,br,( int )file.length());
// 将给定参数设置为给定 Reader 对象,后者是给定的字符数长度
/*parameterIndex-SQL 语句中?的位置,第一个参数是 1 ,第二个参数是 2 ,依此类推。
br- 包含 Unicode 数据的 java.io.Reader 对象 , 也可以是 BufferedReader 缓冲输入流对象
length- 流中的字符数 */
int len= ps .executeUpdate();
return len;
}
finally {
DBConnection.getInstance ().free( rs , ps , con );
}
}
将数据库中的大量字符写入文件的方法: 有两种
运用 Clob 接收结果集的方法:
public void ClobRead() throws SQLException, IOException{
try {
con =DBConnection.getInstance ().getConnection( url , user , password );
String sql = "select big_text from clob" ;
ps = con .prepareStatement(sql );
rs = ps .executeQuery();
while ( rs .next()){
Clob clob= rs .getClob(1);
// 构造一个 Clob 类型,以 Clob 对象的形式获取此 ResultSet 对象的当前行中指定列的值
Reader reader=clob.getCharacterStream();
// 用字符输入流 获取 clob 对象中指定的 CLOB 值
File file= new File( "Java/Lesson/JDBC/Target.txt" );
// 指定要写入的文件路径
Writer writer= new FileWriter(file);
// 字符输出流
BufferedWriter bw= new BufferedWriter(writer);
// 缓冲字符输出流
char [] buf= new char [1024];
// 构造一个字符数组
for ( int i=0;(i=reader.read(buf))>0;){
//read 方法返回读取的字符数,如果已到达流的末尾,则返回 -1 ,不大于 0 就退出循环了
writer.write(buf, 0, i);
// 三个参数, cbuf- 字符数组 ,off- 开始写入字符处的偏移量 ,len- 要写入的字符数
/* 写入文件的内容,由于是从数据库中读取出来的,所以会有乱码问题,如何将 char[] 中的内容转码 ,
仍然是一个问题 */
}
bw.close();
reader.close();
}
}
finally {
DBConnection.getInstance ().free( rs , ps , con );
}
}
用 String 接收结果集的方法:
public void Stringread() throws SQLException, IOException{
try {
con =DBConnection.getInstance ().getConnection( url , user , password );
String sql= "select big_text from clob" ;
ps = con .prepareStatement(sql);
rs = ps .executeQuery();
while ( rs .next()){
String str= rs .getString(1);
// 用 String 类型来代替 Clob ,接受 rs 结果集中的内容,直接用 String 来做,就不需要 reader 输入流了
str= new String(str.getBytes( "ISO-8859-1" ), "GBK" );
// 对数据库中读取的内容进行转码
File file= new File( "Java/Lesson/JDBC/Target.txt" );
Writer writer= new FileWriter(file);
BufferedWriter bw= new BufferedWriter(writer);
bw.write(str, 0, str.length());
// 写入字符串的某一部分 ,str- 要写入的字符串 ,off- 开始读取字符处的偏移量 ,len- 要写入的字符数
bw.close();
}
}
finally {
DBConnection.getInstance ().free( rs , ps , con );
}
}
3. 二进制的类型,可以存储一个 jar 文件,一个图片等,转化成二进制存放在数据库中
在 SQLServer 中,二进制用 binary 类型来存储,图片用 image 来存储,对于二进制不同的应用是分开的,在 Oracle 和 MySQL 中为 BLOB 类型,在 Java 中使用字节流 InputStream 和 OutputStream
如果用 BLOB 类型来存放图片,默认情况下,图片的大小不能超过 64KB ,否则就要用 longBlog 类型
更加要注意的是,在 MySQL 中创建的表名称一定不能与 MySQL 的关键字或数据类型相同,否则会引起冲突,并难以发现问题所在
将图片转化成二进制写入数据库的方法
public int create() throws SQLException, IOException{
try {
con =DBConnection.getInstance ().getConnection( url , user , password );
String sql= "insert into blob_test(big_bit) values(?)" ;
ps = con .prepareStatement(sql);
File file= new File( "Java/Lesson/JDBC/Source.jpg" );
InputStream in= new FileInputStream(file);
BufferedInputStream bis= new BufferedInputStream(in);
ps .setBinaryStream(1,bis,( int )file.length());
// 将给定参数设置为给定 InputStream 对象,后者是给定的字符数长度
/*parameterIndex-SQL 语句中?的位置,第一个参数是 1 ,第二个参数是 2 ,依此类推。
br- 包含 Unicode 数据的 java.io.InputStream 对象 , 也可以是 BufferedInputStream 缓冲输入流对象
length- 流中的字符数 */
int len= ps .executeUpdate();
bis.close();
return len;
}
finally {
DBConnection.getInstance ().free( rs , ps , con );
}
}
将数据库中的二进制写入文件的方法
public void Read() throws SQLException, IOException{
try {
con =DBConnection.getInstance ().getConnection( url , user , password );
String sql= "select big_bit from blob_test" ;
ps = con .prepareStatement(sql);
rs = ps .executeQuery();
while ( rs .next()){
Blob blob= rs .getBlob(1);
// 构造一个 Clob 类型,以 Clob 对象的形式获取此 ResultSet 对象的当前行中指定列的值
InputStream in=blob.getBinaryStream();
// 用字节输入流 获取 blob 对象中指定的 BLOB 值
File file= new File( "Java/Lesson/JDBC/Target.jpg" );
// 指定要写入的文件路径
OutputStream out= new FileOutputStream(file);
// 字节输出流
BufferedOutputStream bos= new BufferedOutputStream(out);
// 缓冲字节输出流
byte [] buf= new byte [1024];
// 构造一个字符数组
for ( int i=0;(i=in.read(buf))>0;){
//read 方法返回读取的字符数,如果已到达流的末尾,则返回 -1 ,不大于 0 就退出循环了
bos.write(buf, 0, i);
// 三个参数, cbuf- 字符数组 ,off- 开始写入字符处的偏移量 ,len- 要写入的字符数
}
bos.close();
in.close();
}
}
finally {
DBConnection.getInstance ().free( rs , ps , con );
}
}
-------------------------------- 三层体系结构 ---------------------------
三层结构用接口隔离,用 Domain( 领域对象 ) 或 DTO 传递数据,好处是业务逻辑层不依赖数据访问层的具体实现,业务逻辑层不需要做任何修改,就可以把数据访问层替换掉
例如在教学这个领域里边,会有老师,学生或者课程这个对象,这就称为领域对象
表示层:
1. 基于 web 的 JSP , Servlet , Struts , Webwork , Spring WEB MVC 等
2. 基于富客户端的 Swing , SWT 等 (rmi , iiop)
业务逻辑层:
Pojo(Service , Manager) , Domain , Session EJB , Spring
数据访问层
JDBC , Ibatis , Hibernate , JDO , Entity Bean
-------------------------RuntimeException---------------------------- 建立一个异常类,继承 RuntimeException 类,以后有异常,都 throw 给异常类
----------------------JDBC 的 Factory 设计模式 ---------------------------
利用 properties 配置文件来保存一个 DAOImpl 类的信息,通过加载这个配置文件来构造一个工厂模式,目的是为了更深层次的解耦和
---------------------------- 事务处理 ---------------------------------
1. 普通的事务处理,将 id 为 1 的账户 money-10 ,转给 id 为 2 的账户,如果 id 为 2 的账户 money>200, 就认为错误,抛出一个异常,并回滚其转账的操作
public void test() throws SQLException {
try {
conn = DBConnection.getInstance ().getConnection();
conn .setAutoCommit( false );
/* 将此连接的自动提交模式设置为给定状态。
如果连接处于自动提交模式下,则将执行其所有 SQL 语句,并将这些语句作为单独的事务提交。
如果设置为 false 状态,则所有对表更改内容的操作,增删改等均不执行 */
conn .setTransactionIsolation(Connection. TRANSACTION_READ_COMMITTED );
// 试图将此 Connection 对象的事务隔离级别更改为给定的级别。
//TRANSACTION_READ_COMMITTED 常量表示 指示防止发生脏读的常量;不可重复读和虚读有可能发生
st = conn .createStatement();
String sql = "update user set money=money-10 where id=1" ;
st .executeUpdate(sql);
sql = "select money from user where id=2" ;
rs = st .executeQuery(sql);
float money = 0.0f;
if ( rs .next()) {
money = rs .getFloat( "money" );
}
if (money > 200)
throw new RuntimeException( " 已经超过最大值! " ); // 用指定的详细消息构造一个新的运行时异常。
sql = "update user set money=money+10 where id=2" ;
st .executeUpdate(sql);
conn .commit();
// 使自从上一次提交 / 回滚以来进行的所有更改成为持久更改,并释放此 Connection 对象当前保存的所有数据库锁定。
System. out .println( " 转账成功 " );
} catch (SQLException e) {
if ( conn != null )
conn .rollback();
throw e;
} finally {
DBConnection.getInstance ().free( rs , st , conn );
}
}
2. 带有保存点的事务处理,先定义一个保存点,将 id 为 1 的账户 money-10 ,设置保存点,在将 id 为 3 的账户 money-10 ,转给 id 为 2 的账户,如果 id 为 2 的账户 money>200, 就认为错误,抛出一个异常,并回滚其转账的操作,但只撤销到保存点之后的操作,而保存点之前的操作会被提交执行
这个颜色区域中的字符 ,表示与上边程序的区别部分,程序大体上相同,只是增加了保存点
private Savepoint sp = null ;
public void test() throws SQLException {
try {
conn = DBConnection.getInstance ().getConnection();
conn .setAutoCommit( false );
/* 将此连接的自动提交模式设置为给定状态。
如果连接处于自动提交模式下,则将执行其所有 SQL 语句,并将这些语句作为单独的事务提交。
如果设置为 false 状态,则所有对表更改内容的操作,增删改等均不执行 */
conn .setTransactionIsolation(Connection. TRANSACTION_READ_COMMITTED );
// 试图将此 Connection 对象的事务隔离级别更改为给定的级别。
//TRANSACTION_READ_COMMITTED 常量表示 指示防止发生脏读的常量;不可重复读和虚读有可能发生
st = conn .createStatement();
String sql = "update user set money=money-10 where id=1" ;
st .executeUpdate(sql);
sp = conn .setSavepoint(); // 定义一个保存点
/* 保存点是可以从 Connection.rollback 方法引用的当前事务中的点。
将事务回滚到保存点时,在该保存点之后所作的全部更改都将被撤消。 */
sql = "update user set money=money-10 where id=3" ;
st .executeUpdate(sql);
sql = "select money from user where id=2" ;
rs = st .executeQuery(sql);
float money = 0.0f;
if ( rs .next()) {
money = rs .getFloat( "money" );
}
if (money > 200)
throw new RuntimeException( " 已经超过最大值! " ); // 用指定的详细消息构造一个新的运行时异常。
sql = "update user set money=money+10 where id=2" ;
st .executeUpdate(sql);
conn .commit();
// 使自从上一次提交 / 回滚以来进行的所有更改成为持久更改,并释放此 Connection 对象当前保存的所有数据库锁定。
System. out .println( " 转账成功 " );
} catch (RuntimeException e) {
if ( conn != null && sp != null ) {
conn .rollback( sp ); // 将保存点之后,对表的更改操作全部撤销
conn .commit();
}
throw e;
} catch (SQLException e) {
if ( conn != null )
conn .rollback();
throw e;
} finally {
DBConnection.getInstance ().free( rs , st , conn );
}
}
3. JTA 分布式事务
跨越多个数据源的事务,使用 JTA 容器实现事务。
分成两阶段提交。
javax.transaction.UserTransactiontx= (UserTransaction)ctx.lookup(“jndiName”);
tx.begin();
//connection1 connection2 ( 可能来自不同的数据库 )…
tx.commit();//tx.rollback();
4. 隔离级别 多线程并发读取数据时的正确性
connection.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
V: 可能出现, X: 不会出现
隔离级别 脏读 不可重复读 虚读
读未提交( Read uncommitted ) V V V
读已提交( Read committed ) X V V
可重复读( Repeatable read ) X X V
可串行化( Serializable ) X X X
脏读 :当事务读取还未被提交的数据时,就会发生这种事件。用户 2 修改了某行数据,但尚未提交。此时用户 1 查询该数据,那么当用户 2 作回滚操作时,用户 1 所读取的数据就可以看作是从未存在过的。
不可重复的读 :当事务两次读取同一行数据,但每次得到的数据都不一样时,就会发生这种事件。用户 1 读取一行数据,然后用户 2 修改或删除该数据并提交,那么当 用户 1 试图重新读取该行时,它就会得到不同的数据值或发现该行已被删除。
虚读 ( 幻读 ) :如果符合搜索条件的一行数据在后面的读取操作中出现,但该行数据却不属于最初的数据,就会发生这种事件。用户 1 按照特定条件查询,当用户 2 插入了符合用户 1 查询条件的数据时,如果用户 1 重新执行上一次的查询,就会得到不同的行,也就是得到了具有用户 2 所插入记录的新数据。
可串行化( Serializable )的隔离级别在处理虚读的时候,如果用户 1 操作某个数据库并处于事务开启状态,那么用户 2 如果想对用户 1 所使用的数据库的表进行 insert 等操作,就会自动处于停止状态,无响应,也就是线程阻塞了,当用户 1 作 commit 提交时,用户 2 的操作就会立刻响应。所以,可串行化的隔离级别只允许除用户 1 以外的其他用户读取数据,而不能修改
------------------------- 使用 JDBC 调用存储过程 -------------------------
想要调用存储过程,就不能使用 Statement 和 PreparedStatement 了,要使用 CallableStatement ,它是从 PreperedStatement 扩展的。
CallableStatemen 是用于执行 SQL 存储过程的接口。
private ResultSet rs = null ;
public void ps() throws SQLException {
try {
// 2. 建立连接
conn = DBConnection.getInstance ().getConnection();
// conn = JdbcUtilsSing.getInstance().getConnection();
// 3. 创建语句
String sql = "{ call addUser(?,?,?,?) } " ;
cs = conn .prepareCall(sql);
// 创建一个 CallableStatement 对象来调用数据库存储过程。
cs .registerOutParameter(4, Types. INTEGER );
// 按存储过程中参数的排序位置将 OUT 参数注册为 JDBC 类型的 sqlType
cs .setString(1, "ps name" );
cs .setDate(2, new java.sql.Date(System.currentTimeMillis ()));
cs .setFloat(3, 100f);
cs .executeUpdate();
int id = cs .getInt(4);
System. out .println( "id=" + id);
} finally {
DBConnection.getInstance ().free( rs , cs , conn );
}
}
--------------------------JDBC 的部分 API-----------------------------
1. 获取刚刚插入的记录的 ID ,不使用数据库所提供的系统函数,如 MySQL 中的 last_insert_id() ,而使用 Java 中的通用方法而不受数据库的限制
public int create() throws SQLException {
try {
conn = DBConnection.getInstance ().getConnection();
String sql = "insert into user(name,birthday, money) values ('name2 gk','1987-01-01',100)" ;
ps = conn .prepareStatement(sql, Statement. RETURN_GENERATED_KEYS );
/* 创建一个默认 PreparedStatement 对象,该对象能检索自动生成的键。
给定常量告知驱动程序是否应该使自动生成的键可用于检索。如果该 SQL 语句不是一条 INSERT 语句,则忽略此参数。
RETURN_GENERATED_KEYS 该常量指示生成的键应该可用于检索。 */
ps .executeUpdate();
rs = ps .getGeneratedKeys();
// 因为指示该插入操作可以用于检索,所以可能会有返回值,因此可以用 rs 结果集来接收,而不用执行 executeQuery 来查询
// 检索由于执行此 Statement 对象而创建的所有自动生成的键。
// 当手动地把 id 添加到数据库中时,不能被检索出来,必须是由数据库自动生成。
int id = 0;
if ( rs .next())
id = rs .getInt(1);
return id;
} finally {
DBConnection.getInstance ().free( rs , ps , conn );
}
}
2. 批处理
批处理,可以大幅度提升大量增、删、改的速度。
需要用到 PreparedStatement 对象的两个函数
PreparedStatement.addBatch();
PreparedStatement.executeBatch();
public void create( int i) throws SQLException {
try {
conn = DBConnection.getInstance ().getConnection();
String sql = "insert into user(name,birthday, money) values (?, ?, ?) " ;
ps = conn .prepareStatement(sql, Statement. RETURN_GENERATED_KEYS );
ps .setString(1, "no batch" + i);
ps .setDate(2, new Date(System. currentTimeMillis ()));
ps .setFloat(3, 100f + i);
ps .executeUpdate();
} finally {
DBConnection.getInstance ().free( rs , ps , conn );
}
}
public void createBatch() throws SQLException {
Connection conn = null ;
PreparedStatement ps = null ;
ResultSet rs = null ;
try {
conn = DBConnection.getInstance ().getConnection();
String sql = "insert into user(name,birthday, money) values (?, ?, ?) " ;
ps = conn.prepareStatement(sql, Statement. RETURN_GENERATED_KEYS );
for ( int i = 0; i < 100; i++) {
ps.setString(1, "batch" + i);
ps.setDate(2, new Date(System.currentTimeMillis ()));
ps.setFloat(3, 100f + i);
ps.addBatch(); // 将一组参数添加到此 PreparedStatement 对象的批处理命令中。
}
int [] is = ps.executeBatch();
// 将一批命令提交给数据库来执行,如果全部命令执行成功,则返回更新的记录数。
} finally {
DBConnection.getInstance ().free(rs, ps, conn);
}
}
public static void main(String[] args) throws SQLException {
BatchTest bt= new BatchTest();
// 普通的大量更新记录,用外界的一个循环来控制,如果要插入 100 条记录,就会产生 100 个插入数据的请求,效率低,占用时间长
long start = System.currentTimeMillis ();
for ( int i = 0; i < 100; i++)
bt.create(i);
long end = System.currentTimeMillis ();
System. out .println( " 普通循环更新时间 :" + (end - start));
/* 利用批处理更新记录,在方法内部每插入一个记录,就放入包中,直到要插入的记录都放入包中为止,
且只会产生一次请求,一次性的将包内的数据全部插入,效率高,占用时间短
*/
start = System.currentTimeMillis ();
bt.createBatch();
end = System.currentTimeMillis ();
System. out .println( " 批量更新时间 :" + (end - start));
}
3. 可滚动结果集(游标)
// 创建一个 Statement 对象,该对象将生成具有给定类型和并发性的 ResultSet 对象。
Statement st =
connection.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE);
/*ResultSet.TYPE_SCROLL_SENSITIVE 该常量指示可滚动并且通常受其他的更改影响的 ResultSet 对象的类型。
ResultSet.CONCUR_UPDATABLE 该常量指示可以更新的 ResultSet 对象的并发模式。 */
第一个参数可以设置为让结果集正向或反向读取记录,例如:
FETCH_FORWARD 该常量指示将按正向(即从第一个到最后一个)处理结果集中的行。
FETCH_REVERSE 该常量指示将按反向(即从最后一个到第一个)处理结果集中的行处理。
常用的可滚动结果集的函数
在结果集中,默认指针指在了第一行之前或是最后一行之后
rs.beforeFirst(); // 将指针移动到此 ResultSet 对象的开头,正好位于第一行之前。
rs.afterLast(); // 将指针移动到此 ResultSet 对象的末尾,正好位于最后一行之后。
rs.first(); // 将指针移动到此 ResultSet 对象的第一行。
rs.isFirst(); // 检索指针是否位于此 ResultSet 对象的第一行。
rs.last(); // 将指针移动到此 ResultSet 对象的最后一行。
rs.isLast(); // 检索指针是否位于此 ResultSet 对象的最后一行。
rs.absolute(9); // 将指针移动到此 ResultSet 对象的给定行编号。
rs.moveToInsertRow(); // 将指针移动到插入行。
4. 可更新结果集
是指在查询过程中,是可以对数据进行修改的
String name= rs .getString( "name" );
if ( "a" .equals(name)){
rs .updateFloat( "money" ,300); // 用 float 值更新指定列。
rs .updateRow(); // 用此 ResultSet 对象的当前行的新内容更新底层数据库。
}
5. 数据库的元数据信息 DatabaseMetaData 类
为了查看使用的某个数据库是否支持某种特性,可以使用元数据信息来查看
Connection conn = DBConnection.getInstance().getConnection();
DatabaseMetaData meta = conn.getMetaData();
/* DatabaseMetaData 用于表示数据库的整体综合信息。是一个接口。
此接口由驱动程序供应商实现,让用户了解 Database Management System (DBMS) 在与驱动程序(基于与其一起使用的 JDBCTM 技术( “JDBC 驱动程序 ” ))相结合时的能力。不同的关系 DBMS 常常支持不同的功能,以不同方式实现这些功能,并使用不同的数据类型
getMetaData() 方法用于获取 DatabaseMetaData 对象,该对象包含关于 Connection 对象连接到的数据库的元数据。 */
System. out .println(meta.getDatabaseProductName());
// 检索此数据库产品的名称。
通过 DatabaseMetaData 类对象 meta 的 getXXX() 方法可以获得数据库相关的信息如:数据库版本、数据库名、数据库厂商信息、是否支持事务、是否支持某种事务隔离级别,是否支持滚动结果集等。
6. 参数的元数据信息 ParameterMetaData 类
用于查看 PreparedStatement 对象中所包含的 sql 查询语句的参数的信息
例如:
select * from user where name=? and money > ?
可以获取到占位符 ?( 参数 ) 所代表的 Java 中的类型名称,数据库中的类型名称,宽度,
public void read(String sql, Object[] params) throws SQLException {
try {
conn = DBConnection.getInstance ().getConnection();
ps = conn .prepareStatement( "select * from user where name=? and money = ?" );
ParameterMetaData pmd = ps .getParameterMetaData();
// 用于获取 PreparedStatement 对象中参数的类型和属性信息的对象。是一个接口。
//getParameterMetaData() 方法 检索此 PreparedStatement 对象的参数的编号、类型和属性。
int count = pmd.getParameterCount();
// 检索 PreparedStatement 对象中的参数的数量,此 ParameterMetaData 对象包含了该对象的信息。
for ( int i = 1; i <= count; i++) {
System. out .print(pmd.getParameterClassName(i) + "/t" );
// 检索 Java 类的完全限定名称,该类的实例应该传递给 PreparedStatement.setObject 方法。
System. out .print(pmd.getParameterType(i) + "/t" );
// 检索指定参数的 SQL 类型。
System. out .println(pmd.getParameterTypeName(i));
// 检索指定参数的特定于数据库的类型名称。
ps .setObject(i, params[i - 1]);
}
rs = ps .executeQuery();
while ( rs .next()) {
System. out .println( rs .getInt( "id" ) + "/t"
+ rs .getString( "name" ) + "/t" + rs .getDate( "birthday" )
+ "/t" + rs .getFloat( "money" ));
}
} finally {
DBConnection.getInstance ().free( rs , ps , conn );
}
}
public static void main(String[] args) throws SQLException {
Object[] params = new Object[] { "a" , 100f };
ParameterMetaDataTest pmdt= new ParameterMetaDataTest();
pmdt.read( "select * from user where name=? and money = ?" , null );
}
7. 结果集的元数据信息 ResultSetMetaData
可以获得 ResultSet 结果集有几列、各列列名、各列别名、各列类型等。
public List<Map<String, Object>> read(String sql) throws SQLException {
//List 的泛型类型定义为 Map 类型,并将此 Map 类型也带有泛型类型, key 为 String , value 为 Object
// 用 map 类型的 key 和 value 分别保存结果集中的列名和值,可以保存一行数据,用 List 来保存 map ,可以保存多行
Connection conn = null ;
PreparedStatement ps = null ;
ResultSet rs = null ;
try {
conn = DBConnection.getInstance ().getConnection();
ps = conn.prepareStatement(sql);
rs = ps.executeQuery();
ResultSetMetaData rsmd = rs.getMetaData();
/* 可用于获取关于 ResultSet 对象中列的类型和属性信息的对象。
以下代码片段创建 ResultSet 对象 rs ,创建 ResultSetMetaData 对象 rsmd , 并使用 rsmd 查找 rs 有多少列,以及 rs 中的第一列是否可以在 WHERE 子句中使用。 ResultSet rs = stmt.executeQuery("SELECT a, b, c FROM TABLE2");
ResultSetMetaData rsmd = rs.getMetaData();
int numberOfColumns = rsmd.getColumnCount();
boolean b = rsmd.isSearchable(1); */
int count = rsmd.getColumnCount();
// 返回此 ResultSet 对象中的列数。
String[] colNames = new String[count];
// 创建一个与列数相同的数组,用于存储结果集中的列名
for ( int i = 1; i <= count; i++) {
System. out .print(rsmd.getColumnClassName(i) + "/t" );
// 返回该列值在 Java 中的类型名 例如 java.lang.String
System. out .print(rsmd.getColumnName(i) + "/t" );
// 获取指定列的名称。
System. out .println(rsmd.getColumnLabel(i));
// 获取用于打印输出和显示的数据库列的别名 例如 select money as salary from user,salary 就是别名
colNames[i - 1] = rsmd.getColumnLabel(i); // 将列名存储在数组中
}
List<Map<String, Object>> datas = new ArrayList<Map<String, Object>>();
// 创建一个 List 集合对象,用来保存 Map 对象,也就是来保存多行数据
while (rs.next()) {
Map<String, Object> data = new HashMap<String, Object>();
for ( int i = 0; i < colNames. length ; i++) { // 通过列来循环这一行数据
data.put(colNames[i], rs.getObject(colNames[i]));
// 从 rs.getObject(colNames[i]) 中根据列名来获取结果集中的数据,存入列名 key 所对应的 value 中
}
datas.add(data); // 将 map 对象 ( 一行数据 ) 添加到 list 中
}
return datas;
} finally {
DBConnection.getInstance ().free(rs, ps, conn);
}
}
public void main(String[] args) throws SQLException {
List<Map<String, Object>> datas = read( "select id, name as n from user where id < 5" );
System. out .println(datas);
}
--------- 利用 Java 反射技术将查询结果用 setXXX 封装为 JavaBean 对象 ----------
用反射 ResultSetMetaData 将查询结果读入对象中(简单的 O/RMapping )
1) 让 SQL 语句中列别名和要读入的对象属性名一样;
2) 通过 ResultSetMetaData 获得结果列数和列别名;
3) 通过反射将对象的所有 setXxx 方法找到 ;
4) 将 3 )找到的方法 setXxx 和 2 )找到的列别名进行匹配(即方法中的 xxx 于列别名相等);
5) 由上一步找到的方法和列别名对应关系进行赋值
Method.invoke(obj, rs.getObject(columnAliasName));
private String[] getColNames (ResultSet rs) throws SQLException {
ResultSetMetaData rsmd = rs.getMetaData(); // 结果集的元数据信息
int count = rsmd.getColumnCount(); // 获取列的个数
String[] colNames = new String[count];
for ( int i = 1; i <= count; i++) {
colNames[i - 1] = rsmd.getColumnLabel(i); // 将列名的别名依次保存到数组中
}
return colNames;
}
public Object setObject(String sql, Class clazz) throws SQLException,
Exception, IllegalAccessException, InvocationTargetException {
Connection conn = null ;
PreparedStatement ps = null ;
ResultSet rs = null ;
try {
conn = DBConnection.getInstance ().getConnection();
ps = conn.prepareStatement(sql);
rs = ps.executeQuery();
String[] colNames = getColNames(rs); // 根据结果集获取保存有列名的数组
Object object = null ;
Method[] ms = clazz.getMethods(); // 反射出来提供的类的所有公有方法,但不包括私有和静态方法
if (rs.next()) {
object = clazz.newInstance(); // 创建所提供的默认实例,也就是不带参数的构造方法
for ( int i = 0; i < colNames. length ; i++) { // 循环迭代所有列名
String colName = colNames[i]; // 获取列名
String methodName = "set" + colName; // 将列名前加上 set 为的是与 javabean 的 setXXX 方法名相对应
for (Method m : ms) { // 迭代所有的方法
if (methodName.equals(m.getName())) {
m.invoke(object, rs.getObject(colName)); // 调用该方法,参数为该类对象和数据库中的值,此操作相当于 object.setXXX(rs.getObject(colName));
break ;
}
}
}
}
return object;
} finally {
DBConnection.getInstance ().free(rs, ps, conn);
}
}
public static void main(String[] args) throws SQLException,
IllegalAccessException, InvocationTargetException, Exception {
ORMTest ot= new ORMTest();
User user = (User) ot.setObject(
"select id as Id, name as Name, birthday as Birthday, money as Money from user where id=1" ,
User. class );
System. out .println(user);
Bean b = (Bean) ot.setObject(
"select id as Id, name as Name, birthday as Birthday, money as Money from user where id=1" ,
Bean. class );
System. out .println(b);
}
--------------------- 模拟一个连接池来实现连接的复用 ---------------------
初始化多个 Connection 连接,放入 LinkedList 集合中来等待多个用户使用
避免了每一个用户要操作数据库都要重新连接一次,这样会花费大量的时间
所以先构造好许多连接,等待客户使用,使用一个,就从集合中删除这一个,不用的时候,释放掉,就再添加回集合中,等待其他用户使用
一共用到两个类, MyDataSource .java DBConnection.java
MyDataSource 类实现用集合对连接的添加和移除
DBConnection 类实现用单例设计模式来获取和释放连接
public class MyDataSource {
private String url = "jdbc:mysql://localhost:3306/curd" ;
private String user = "root" ;
private String password = "root" ;
private LinkedList<Connection> connectionsPool = new LinkedList<Connection>();
/* 构造一个集合,考虑 ArrayList 和 LinkedList ,根据各自的优点来选择
*ArrayList 类似于栈结构, 读取速度快,但添加或移除速度慢
*LinkedList 类似于链表结构,读取速度慢,但添加或移除速度快
* 需要重复多次的添加或移除,所以选择 LinkedList
* 将连接添加到集合的末尾,取的时候是取表头的连接,也就是一个先入先出的链表算法 */
private static int initCount =5;
private static int maxCount =10;
private int currentCount =0;
public MyDataSource() {
for ( int i=0;i< initCount ;i++){
try {
this . connectionsPool .addLast( this .createConnection());
this . currentCount ++;
} catch (SQLException e) {
throw new ExceptionInInitializerError();
}
}
}
public Connection getConnection() throws SQLException{ // 让用户得到一个连接,就从集合中取走一个,也就是从集合中删除一个,并给用户使用
synchronized ( connectionsPool ) {
if ( this . connectionsPool .size()>0)
return this . connectionsPool .removeFirst();
if ( this . currentCount < this . maxCount ){
this . currentCount ++;
return this .createConnection();
}
throw new SQLException( " 已没有连接 " );
}
}
public void free(Connection conn){ // 用户不操作数据库了,释放该连接,将该连接再次添加到集合的末尾
this . connectionsPool .addLast(conn);
}
public Connection createConnection() throws SQLException{
return DriverManager.getConnection ( url , user , password );
}
}
public class DBConnection {
private static MyDataSource myDataSource = null ;
// 创建私有的构造方法,一个私有的实例和一个获取类实例的方法,使用单例设计模式,类中不能出现静态的方法
private DBConnection(){
}
private static DBConnection con = new DBConnection();
public static DBConnection getInstance(){
return con ;
}
static {
try {
Class.forName ( "com.mysql.jdbc.Driver" );
myDataSource = new MyDataSource();
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public Connection getConnection() throws SQLException{
//return DriverManager.getConnection(url,user,password);
return myDataSource .getConnection();
}
public void free(ResultSet rs,Statement st,Connection con){
try {
if (rs!= null )
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
finally {
try {
if (st!= null )
st.close();
} catch (SQLException e) {
e.printStackTrace();
}
finally {
try {
if (con!= null )
//con.close();
myDataSource .free(con);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
------------------------- 常用的开源实现 DBCP---------------------------
1) 使用 DBCP 必须用的三个包:
commons-dbcp-1.2.1.jar
commons-pool-1.2.jar
commons-collections-3.1.jar 。
2) 配置参数 , 或者是配置 .properties 配置文件
3)Java API: BasicDataSourceFactory.createDataSource(properties);
--------------------------MySQL 高级应用 ------------------------------
1. 查看当前表所使用的引擎
show create table user;
结果为:
user CREATE TABLE `user` (
`id` int(11) NOT NULL auto_increment,
`name` varchar(10) default NULL,
`birthday` date default NULL,
`money` float default NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=gbk
表的引擎使用的是 InnoDB ,这种引擎支持事务,主键外键关联都支持, MyISAM 存储引擎不支持事务,也不支持外键关联,因为不检查主外键关系,所以查询速度快,但支持数据库的很多特性
2. 查看和更改当前数据库所使用的隔离级别
这个隔离级别的信息存储在了一个全局变量中,名称为 @@tx_isolation
select @@tx_isolation;
也可以在 MySQL 的客户端中自己来设置隔离级别,每种隔离级别的名称均不相同
set transaction isolation level read uncommitted;
set transaction isolation level repeatable read;
3. 打开事务
Start transaction
4. Insert 插入日期类数据时,可以插入当前的系统日期
insert into user values(5,'e',now(),500);
now() 就表示当前的系统日期
5. MySQL 提供一个函数来返回当前线程最后一次插入记录的 id 值
该函数为: last_insert_id()
Insert into user(id,name,birthday,money) values(5,'e',now(),500);
select last_insert_id();
当手动地把 id 添加到数据库中时, LAST_INSERT_ID 是不能计算出 id 地,必须自动生成。
在 MYSQL 中, last_insert_id() 这个函数,用来取得最后一次执行插入操作时的自增长的 ID 。必须紧跟在插入语句之后使用。不然查出来的 ID 为 NULL 。
6. MySQL 自带的分页方法
Select id,name,birthday,money from user limit 150,10
从第 150 条开始取,取 10 条