JDBC(Java Database Connectivity)
JDBC API:JDBC API是Java语言中用于连接各种数据库的API。
主要的包和相应的接口、类:
java.sql.*
: class DriverManager; interface Connection, Statement, ResultSet, PreparedStatement, CallableStatement
javax.sql.*
: interface DataSource
使用JDBC的6个关键步骤(面试可能让手写!!):
加载驱动
Class.forName("com.mysql.jdbc.Driver");
建立连接
connection = DriverManager.getConnection(url, user, passwd);
创建Statement
statement = connection.createStatement();
执行查询
String sql = "show databases";
resultSet = statement.executeQuery(sql);
处理结果
while (resultSet.next()) {
String database = resultSet.getString("Database");
System.out.println("Query: " + database);
}
关闭连接(放到finally子句)
finally {
//6.关闭连接
try {
resultSet.close();
statement.close();
connection.close();
} catch (NullPointerException e) {
System.out.println("数据库连接未建立或查询操作有误!");
} catch (SQLException e) {
e.printStackTrace();
}
}
(完整代码请转至我的Github主页,X-ration/Practice_JavaJDBC,第一次Commit下的Main.java文件,或者点这里下载源代码)
数据库操作也是I/O过程。
PreparedStatement
优点:防止SQL注入,有相应的setInt()等方法。
基本使用:
preparedStatement = connection.prepareStatement("select ename from emp where ename like ?");
preparedStatement.setString(1,"%A%");
resultSet = JdbcUtil.executeQueryPrepared();
事务控制
设置是否自动提交:Connection.setAutoCommit(Boolean)
Connection.commit()
手动回滚:Connection.rollback()
ResultSet滚动结果集
常用API():
next()
absolute(int row)
创建Statement时可以使用带参的方法createStatement(int resultSetType, int resultSetConcurrency),例如:
Statement st = conn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATE);
第一个参数可选值:
第二个参数可选值:
ResultSet.CONCUR_UPDATABLE, 指示可以更新的ResultSet并发模式
获取元数据
ParameterMetaData:参数的元数据
ParameterMetaData parameterMetaData = preparedStatement.getParameterMetaData();
System.out.println("ParameterMetaData测试,参数个数:" + parameterMetaData.getParameterCount());
ResultSetMetaData resultSetMetaData = resultSet.getMetaData();
System.out.println("ResultSetMetaData测试,结果集列数:" + resultSetMetaData.getColumnCount());
DatabaseMetaData databaseMetaData = connection.getMetaData();
System.out.println("DatabaseMetaData测试,用户名:" + databaseMetaData.getUserName());
Statement批处理操作
批处理操作的好处显而易见。实例测试:使用JDBC向Oracle数据表中插入600条数据,分别使用普通的Statement逐条插入和使用批处理插入。实测结果:逐条插入耗时4563ms,而批处理插入耗时20ms。
And:批处理插入8400条记录,共耗时116ms;批处理插入90000条数据,共耗时1847ms。(太快了吧也!!简直不在一个量级上!!)
Statement.addBatch(String sql);
PreparedStatement.addBatch();
(每次将参数值改变后执行,相当于批量执行PreparedStatement)
PreparedStatement.addBatch(String sql);
(没有测试过)
Statement.executeBatch();
PreparedStatement.executeBatch();
这两个方法的返回值是int[]类型,其中数组中的每个值代表对应语句影响到的行数。
正好在这里记录一个小问题。看到有博客上说调用executeBatch()方法执行成功后会自动调用clearBatch()方法,我感觉这种说法是不合适的,要想自己验证当然可以去找相应的源代码实现。查看相关源代码发现,java.sql.Statement是一个接口,只是规定了Statement有executeBatch()方法,所以我猜想,真正的实现要依赖于具体的Driver,那就看你用的是Mysql的Driver还是Oracle的Driver或者是其他的Driver了。我的使用情境中Driver用的是OracleDriver,查看OracleStatement的源代码发现有这么一句比较关键的:
} finally {
this.clearBatchItems();
...
}
其中clearBatchItems()方法的实现为this.m_batchItems.removeAllElements();
,而这里的m_batchItems(注:Vector类型)恰好是OracleStatement内部对批处理指令存储的实现。那么executeBatch()方法会不会执行到这一句呢?看上面的关键代码,这一句被写在finally里面,finally是try-catch-finally的最后一步,即不管发生什么异常都会执行的意思,所以这一句八成是要调的,但也不能这么绝对,还得先分析下代码结构。经过我的分析发现executeBatch()的代码结构如下:
//some useless code(这里的没用指的是对分析过程没用,即不起决定性作用)
synchronized(this.connnection){
//some useless code
if(this.getBatchSize()<=0){
return new int[0];
} else {
//some useless code
try {
//some useless code
} catch (SomeException e){
//some useless code
} finally {
this.clearBatchItems();
//some useless code
}
}
}
这里我唯一感到不明白的是synchronized关键字的含义,所以我认为在Oracle Driver实现中executeBatch()方法最后调用clearBatch()方法是正确的,判断正确的把握是95%。(所以如果有大佬研究的比较深的,还是请再指点一下啊~)
Statement.clearBatch();
PreparedStatement.clearBatch();
贴一段Statement.clearBatch()方法的官方说明:(大意是:如果数据库连接过程中出现错误(error),这个方法(clearBatch)会在关闭Statement时调用,或者当前驱动根本不支持批处理操作。)
/**
* ... if a database access error occurs,
* this method is called on a closed Statement or the
* driver does not support batch updates
*/
使用JDBC时的几点注意
地址:不同的Driver要求的URL地址不同,通常我们用的mysql地址示例:jdbc:mysql://localhost:3306/some_database?useUnicode=true&characterEncoding=UTF8
Oracle地址示例:jdbc:oracle:thin:@localhost:1521:orcl
Idea中的步骤是:
创建一个resources文件夹,并在Project Structure中设置该文件夹为resource dir
在resources文件夹中创建Resource Bundle(后缀.properties),例如jdbc.properties
在jdbc.properties中以每行一个key=value的形式设定数据库相关配置,例如
driver=oracle.jdbc.driver.OracleDriver
在程序中使用如下语句获取相应key的值,如获取driver:
String driver = ResourceBundle.getBundle("jdbc").getString("driver");
PreparedStatement preparedStatement = JdbcUtil.createPreparedStatement("select * from ?");
preparedStatement.setString(1,"user_tables");
resultSet = JdbcUtil.executeQueryPrepared();
这里的代码是在对Oracle数据库进行操作的,运行时报错java.sql.SQLSyntaxErrorException: ORA-00903: invalid table name。贴一段来自StackOverflow上大神Jon Skeet的解答:
I believe that
PreparedStatement
parameters are only for values - not for parts of the SQL query such as tables. There may be some databases which support what you’re trying to achieve, but I don’t believe Oracle is one of them. You’ll need to include the table name directly - and cautiously, of course.
这启示我们,至少在Oracle数据库中,不能把表名当做查询参数传给PreparedStatement。
执行executeBatch()操作,返回值是一个整型数组,数组中的每个数字对应一条命令影响到的行数。但在Oracle的驱动中没有实现该功能,即提交成功后不能返回影响行数。
在JDBC的规范中
Statement.SUCCESS_NO_INFO(-2)
代表:执行成功,受影响行数不确定
获取PreparedStatement的元数据ParameterMetaData时,不能调用getParameterClassName(int num)这个方法,在我调用时报错”Unsupported feature”,当时就在心里骂着这破Oracle咋这么多限制(虽然我知道还是要用它,哎)。我的测试场景是PreparedStatement中含有一个待定参数,调用这个方法试了传0或1结果都是报这个错,应该就是Oracle Driver自己的问题吧。