-
JDBC(Java Database Connectivity)
-
JDBC API:JDBC API是Java语言中用于连接各种数据库的API。
-
主要的包和相应的接口、类:
java.sql.*
: class DriverManager; interface Connection, Statement, ResultSet, PreparedStatement, CallableStatementjavax.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:
preparedStatement = connection.prepareStatement("select ename from emp where ename like ?");
-
设置参数值(注意,这里的columnIndex是从1开始的)
preparedStatement.setString(1,"%A%");
-
执行查询
resultSet = JdbcUtil.executeQueryPrepared();
-
关闭PreparedStatement:调用close方法。
-
-
-
事务控制
- 设置是否自动提交:
Connection.setAutoCommit(Boolean)
- 手动提交:
Connection.commit()
- 手动回滚:
Connection.rollback()
- 设置是否自动提交:
-
ResultSet滚动结果集
-
常用API():
- next()
- previous()
- absolute(int row)
-
创建Statement时可以使用带参的方法createStatement(int resultSetType, int resultSetConcurrency),例如:
Statement st = conn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATE);
第一个参数可选值:
- ResultSet.TYPE_FORWARD_ONLY, 指示光标只能往前移动;
- ResultSet.TYPE_SCROLL_INSENSITIVE, 指示光标可滚动但通常不受底层数据变动的影响;
- ResultSet.TYPE_SCROLL_SENSITIVE,指示光标可滚动并且可实时得到底层数据变化后的最新数据。
第二个参数可选值:
- ResultSet.CONCUR_READ_ONLY,指示不可以更新的ResultSet并发模式
- ResultSet.CONCUR_UPDATABLE, 指示可以更新的ResultSet并发模式
-
-
获取元数据
-
ParameterMetaData:参数的元数据
ParameterMetaData parameterMetaData = preparedStatement.getParameterMetaData(); System.out.println("ParameterMetaData测试,参数个数:" + parameterMetaData.getParameterCount());
-
ResultSetMetaData:结果集的元数据
ResultSetMetaData resultSetMetaData = resultSet.getMetaData(); System.out.println("ResultSetMetaData测试,结果集列数:" + resultSetMetaData.getColumnCount());
-
DatabaseMetaData:数据库的元数据
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
-
可以通过读取Resource Bundle的方式让程序从配置文件中读取数据库url用户名等信息。
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时,通常设定的是"value"。例如下面这个例子执行是有问题的:
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。
-
对Oracle数据库进行executeBatch()操作返回-2
执行executeBatch()操作,返回值是一个整型数组,数组中的每个数字对应一条命令影响到的行数。但在Oracle的驱动中没有实现该功能,即提交成功后不能返回影响行数。
在JDBC的规范中
Statement.SUCCESS_NO_INFO(-2)
代表:执行成功,受影响行数不确定 -
再记录一个关于使用Oracle Driver的weird problem!!
获取PreparedStatement的元数据ParameterMetaData时,不能调用getParameterClassName(int num)这个方法,在我调用时报错"Unsupported feature",当时就在心里骂着这破Oracle咋这么多限制(虽然我知道还是要用它,哎)。我的测试场景是PreparedStatement中含有一个待定参数,调用这个方法试了传0或1结果都是报这个错,应该就是Oracle Driver自己的问题吧。
-