Java 核心技术 卷II (4章)
---数据库编程
1、jdbc设计
1)Jdbc是java能够通过SQL访问数据库的一套javaAPI;
2)Jdbc是一套能与多种关系数据库提供统一访问Java接口方法;
2、核心API方法介绍
创建执行对象:
Stattement stat = conn.createStatement();
stat.executeUpdate():返回受SQL命令影响的行数,或者对不返回行数的语句(DDL语句)返回0
ResultSet rs = stat.executeQuery(String SQL)
结果集使用:
while(rs.next()){ . . . . . .}
如何取值?不同的参数类型有不同的访问器,可以通过下标和类名获取
rs.getDouble (1);
rs.getDouble(“price”)
说明:
1)数据库索引与数组的索引不同,数据库的列序号从1开始计算的。
2)使用get方法的类型与列的数据类型不一致,get方法会尝试合理的类型转换,如rs.getString(“price”),会将price列的浮点值转换为字符串。
3)做通用话数据封装时,可以使用getObject进行通用取。
如何管理连接??
使用完ResultSet、Statement或Connection对象,应立即使用close方法关闭,调用顺序从里到外(ResultSet、Statement、Connection)
如何解析SQL异常
SQLWarning w = stat.getWarning();
while(w != null){
do something with w
w = w.nextWarning();
}
说明:还有其他的很多方法,见书!!
相关其他方法:
Connection :
Statement createStatement()
voidcolse()
Statement
ResultSetexecuteQuery(String sql)
intexecuteUpdate(String sql)
booleanexecute(String sql)
ResultSetgetResultSet()
intgetUpdateCount()
voidclose()
booleanisColsed()
void coseOncompleion()
ResultSet
booleannext()
XxxgetXxx(int columnNumber)
XxxgetXxx(String columnName)
<T> TgetObject(int columnNumber,Class<T> type)
<T> T getObject(intcolumnName,Class<T> type)
int findColumn(String columnName)
void close()
boolean isClosed();
3、不同的执行对象
3.1预备语句(预编译语句)
如何进行参数化查询??
PreparedStatement stat = conn.prepareStatement(“select *from user where id = ?”);
stat.serInt(1,12);
ResultSet rs = stat.executeQuery();
许多数据库通常都会有自动缓存预备语句,如果相同的查询被预备两次,数据库通常会直接重用查询策略。
不使用Statement:
1)使用Statement,若是进行参数化查询,每次查询都会建立新的查询语句,消耗查询性能;
2)使用Statement,如果进行参数化查询,需要拼接SQL,容易造成SQL注入。
使用statement:
如果进行的查询是不带参数的,使用Statement比PrepredStatement更高校,
相关其他方法:
Connection
PreparedStatementprepareStatement(String sql)
PreparedStatement
voidsetXxx(int n, Xxx x);
voidclearParameters();
ResultSetexecuteQuery()
intexecuteUpdate() 【如果执行的是数据定义语句(DDL),如CREATE TABLE,则返回0】
3.2 CallableStatement执行存储过程
n CALL pro_findById2(5,@NAME);
(1)存储过程
mysql> DELIMITER $
mysql> CREATE PROCEDURE pro_findById2(IN eidINT,OUT vname VARCHAR(20))
-> BEGIN
-> SELECT empname INTO vname FROM employeeWHERE id = eid;
-> END $;
(2)测试
String sql = "CALL pro_findById2(?,?)"; //第一个?是输入参数,第二个?是输出参数
cstmt =conn.prepareCall(sql);
//设置输入参数
cstmt.setInt(1, 6);
//设置输出参数(注册输出参数)
cstmt.registerOutParameter(2,java.sql.Types.VARCHAR);
//返回结果到输出参数中
cstmt.executeQuery();
//从输出参数的索引中,获取结果
String result = cstmt.getString(2);
4、其他
4.1读写Lob
Lob分两种:一种字符型大对象Clob,二进制大对象Blob
Blob:
存入数据库:
String sql = " insert into test(img)values(?)";
// 连接
con = JdbcUtil.getConnection();
// pstmt 对象
pstmt = con.prepareStatement(sql);
// 获取图片流
InputStream in = App_text.class.getResourceAsStream("7.jpg");
pstmt.setBinaryStream(1, in);
// 执行保存图片
pstmt.execute();
从数据库读取:
String sql = "select img from test where id=2;";
// 连接
con = JdbcUtil.getConnection();
pstmt = con.prepareStatement(sql);
rs = pstmt.executeQuery();
if (rs.next()) {
// 获取图片流
InputStream in =rs.getBinaryStream("img");
// 图片输出流
FileOutputStream out = new FileOutputStream(new File("c://1.jpg"));
int len = -1;
byte b[] = new byte[1024];
while ((len = in.read(b)) != -1) {
out.write(b, 0,len);
}
}
Clob:
存入数据库:
String sql = "insert into test(content)values(?)";
con = JdbcUtil.getConnection();
pstmt = con.prepareStatement(sql);
// 设置参数
// 先获取文件路径
String path = App_text.class.getResource("tips.txt").getPath();
FileReader reader = new FileReader(new File(path));
pstmt.setCharacterStream(1,reader);
// 执行sql
pstmt.executeUpdate();
从数据库读取:
String sql = "select * from test;";
con = JdbcUtil.getConnection();
pstmt = con.prepareStatement(sql);
rs = pstmt.executeQuery();
if (rs.next()) {
// 获取长文本数据,方式1:
//Reader r = rs.getCharacterStream("content");
// 获取长文本数据,方式2:
System.out.print(rs.getString("content"));
}
4.2获取主键
String sql = "insert into articlevalues(null,?,now(),?)";
//执行语句的同时返回一个主键集合
PreparedStatement pst =DB.prepareStmt(conn, sql,Statement.RETURN_GENERATED_KEYS);
pst.setInt(1,0);
pst.setInt(2, 0);
pst.executeUpdate();
//得到主键集合
ResultSet rsKey =pst.getGeneratedKeys();
rsKey.next();
rootId = rsKey.getInt(1);
4.3多结果集
若是执行存储过程或者在单个查询中提交了多个select语句,那么一个数据库能返回多个结果集。
关键点:重复调用getMoreResult方法移动到下一项结果集。
CREATE PROCEDURE proc_test()
BEGIN
select * from person;
select * from person;
END;
Connection conn = getConn();
String sql = "{call proc_test()}";
CallableStatement ctmt = conn.prepareCall(sql);
boolean hadResults = ctmt.execute();
int i=0;
ResultSet rs = null;
while (hadResults) {
System.out.println("result No:----"+(++i));
rs = ctmt.getResultSet();
while (rs !=null && rs.next()){
int id1 = rs.getInt(1);
String name1 = rs.getString(2);
System.out.println(id1 +":" + name1);
}
hadResults = ctmt.getMoreResults(); //检查是否存在更多结果集
}
4.4可滚动&可更新结果集
可滚动结果集:
Connection conn = getConn();
/*
*conn.createStatement(type,concurrency)
*/
// TYPE_SCROLL_SENSITIVE:结果集能滚动,对数据库变化敏感
// CONCUR_UPDATABLE:结果集可以用于更新数据库
Statement stat = conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE,ResultSet.CONCUR_UPDATABLE);
ResultSet rs = stat.executeQuery("select * from user");
// 移动游标方法
rs.next();
rs.relative(3);
rs.previous();
rs.relative(-1);
rs.absolute(5);
// 特殊位置
rs.first();
rs.last();
rs.beforeFirst();
rs.afterLast();
ps:因为在操作过程中,一直连接着数据库,可滚动结果集,消耗数据库资源巨大。
可更新结果集:
Connection conn = getConn();
// TYPE_SCROLL_SENSITIVE:结果集能滚动,对数据库变化敏感
// CONCUR_UPDATABLE:结果集可以用于更新数据库
Statement stat = conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE,ResultSet.CONCUR_UPDATABLE);
ResultSet rs = stat.executeQuery("select * from user");
rs.next();
rs.updateString("name","u-2");
rs.updateInt("age", 22);
rs.updateRow();
rs.insertRow();
rs.deleteRow();
ps:
①更新数据库行,必须要updateRow(),这样可以将变化同步到数据库;插入数据库一行,使用updateXxx,之后要使用insertRow(),rs.moveToCurrentRow();deleteRow()直接将当前行删除。
②如果查询语句涉及到多个表的连接操作,那么它所产生的结果集将是不可更新的;若是利用主键连接表的,那么它还是可更新结果集。
4.5行集
RowSet接口扩展自ResultSet接口,却无需始终保持与数据库连接,可以完美解决可滚动结果集的,数据库资源消耗问题。
行集分几种:
CachedRowSet:缓存的行集。
WebRowSet:缓存的行集,可保存为XML文件,该文件可移动到Web应用。
FilteredRowSet和JoinRowSet:支持对行集的轻量级操作。
JdbcRowSet:是ResultSet接口的瘦包装器。
行集中的数据来源??
使用JDBC驱动程序从数据库检索的数据
从其他数据源获得的数据,如文件数据
行集(Row Set)的优点??
1)可以断开数据库连接操作数据;
2)可以在分布式系统中的不同组件之间传递;
3)默认可更新,可滚动,可序列化,可以方便的在网络间传输;
4)可以方便的使数据在行集与JavaBean对象之间进行转换。行集中的一行数据可以封装为一个JavaBean对象。
Connection conn = getConn();
PreparedStatement pst=conn.prepareStatement("select * from user");
//必须设置非自动提交
conn.setAutoCommit(false);
ResultSet rs=pst.executeQuery();
//创建行集实例
CachedRowSetImpl rowset=newCachedRowSetImpl();
//填充
rowset.populate(rs);
//这里已经关闭结果集了!!!
rs.close();
pst.close();
// 在关闭连接之前进行更新操作
rowset.absolute(5);
rowset.updateInt("English", 55);
rowset.updateRow(); //更新
rowset.acceptChanges(conn); //提交
//输出结果集之前,关闭连接
conn.close();
//输出行集数据
while(rowset.next()){
System.out.print(rowset.getInt("id")+"\t");
System.out.print(rowset.getInt("Chinese")+"\t");
System.out.print(rowset.getInt("English")+"\t");
System.out.println(rowset.getInt("history"));
}
5、元数据
元数据:描述数据库或者其组成部分的数据。
元数据分为:
关于数据库的元数据(DatabaseMetaData)
关于结果集的元数据(ResultSetMetaData)
关于预备语句参数的元数据(ParameterMetaData)
数据库元数据
// 获取连接
Connection conn = JdbcUtil.getConnection();
// 获取数据库元数据
DatabaseMetaData metaData = conn.getMetaData();//alt + shift + L 快速获取方法返回值
System.out.println(metaData.getUserName());
System.out.println(metaData.getURL());
System.out.println(metaData.getDatabaseProductName());
参数元数据
// 获取连接
Connection conn = JdbcUtil.getConnection();
// SQL
String sql = "select * from dept wheredeptid=? and deptName=?";
// Object[] values = {"tom","888"};
PreparedStatement pstmt = conn.prepareStatement(sql);
// 参数元数据
ParameterMetaData p_metaDate = pstmt.getParameterMetaData();
// 获取参数的个数
int count = p_metaDate.getParameterCount();
// 测试
System.out.println(count);
结果集元数据
Connection conn = JdbcUtil.getConnection();
PreparedStatement pstmt = conn.prepareStatement("select * from dept ");
ResultSet rs = pstmt.executeQuery();
// 得到结果集元数据(目标:通过结果集元数据,得到列的名称)
ResultSetMetaData rs_metaData = rs.getMetaData();
// 迭代每一行结果
while (rs.next()) {
// 1. 获取列的个数
int count = rs_metaData.getColumnCount();
// 2. 遍历,获取每一列的列的名称
for (int i=0; i<count; i++) {
// 得到列的名称
String columnName= rs_metaData.getColumnName(i + 1);
// 获取每一行的每一列的值
ObjectcolumnValue = rs.getObject(columnName);
// 测试
System.out.print(columnName +"=" + columnValue + ",");
}
System.out.println();
}
说明:getObject可以拿到任意类型行的值
6、事务
事务:将一组语句构建成一个事务,当所有语句都顺利执行,事务可以被提交。否则,如果其中某个语句遇到错误,那么事务将被回滚。
事务:确保数据库万整性。
默认情况下,数据库连接处于自动提交模式。
Connection con;
PreparedStatement pstmt;
String sql_zs = "UPDATE account SETmoney=money-1000 WHERE accountName='张三';";
String sql_ls = "UPDATE1 account SETmoney=money+1000 WHERE accountName='李四';";
try {
con = JdbcUtil.getConnection();// 默认开启的隐士事务
// 一、设置事务为手动提交
con.setAutoCommit(false);
/*** 第一次执行SQL ***/
pstmt = con.prepareStatement(sql_zs);
pstmt.executeUpdate();
/*** 第二次执行SQL ***/
pstmt = con.prepareStatement(sql_ls);
pstmt.executeUpdate();
} catch(Exception e) {
try {
// 二、出现异常,需要回滚事务
con.rollback();
} catch (SQLException e1) {
e.printStackTrace();
}
} finally {
try {
// 三、所有的操作执行成功,提交事务
con.commit();
JdbcUtil.closeAll(con,pstmt, null);
} catch (SQLException e) {
}
}
保存点:使用保存点可以更细粒度的控制回滚操作
. . .
try {
con = JdbcUtil.getConnection();// 默认开启的隐士事务
// 一、设置事务为手动提交
con.setAutoCommit(false);
/*** 第一次执行SQL ***/
pstmt = con.prepareStatement(sql_zs);
pstmt.executeUpdate();
Savepoint svpt = pstmt.setSavepoint();
/*** 第二次执行SQL ***/
pstmt = con.prepareStatement(sql_ls);
pstmt.executeUpdate();
} catch(Exception e) {
// 二、出现异常,需要回滚到回滚点
con.rollback(svpt);
. . .
} finally {
. . .
}
Ps:当不需要回滚点时,可以释放回滚点:使用conn.releaseSavepoint(svpt)。
7、批量操作
当程序需要执行多条语句时,可以使用批量操作,如执行上万条insert语句,可以使用批量更新来提高程序性能。
// SQL
String sql = "INSERT INTOadmin(userName,pwd) values(?,?)";
try {
// 获取连接
con = JdbcUtil.getConnection();
// 创建stmt
pstmt = con.prepareStatement(sql); // 【预编译SQL语句】
for (int i=0; i<list.size(); i++) {
Admin admin =list.get(i);
// 设置参数
pstmt.setString(1,admin.getUserName());
pstmt.setString(2, admin.getPwd());
// 添加批处理
pstmt.addBatch(); // 【不需要传入SQL】
// 测试:每5条执行一次批处理
if (i % 5 == 0) {
// 批量执行
pstmt.executeBatch();
// 清空批处理
pstmt.clearBatch();
}
}
// 批量执行
pstmt.executeBatch();
// 清空批处理
pstmt.clearBatch();
} catch(Exception e) {
e.printStackTrace();
} finally {
JdbcUtil.closeAll(con, pstmt, rs);
}