jdbc 接口 api(java 语言定义的规范)--------> jdbc驱动(数据库厂商按照规范实现接口)-------> 将驱动加载到jvm中并将匹配的驱动类的实例的句柄赋值给Connection 类型的变量。
通过前面的学习我们或多或少学到了一些东西也有一些自己的理解下面就总结一下:
一、jdbc 使用的流程:
1、驱动的加载
Class.forName("com.mysql.jdbc.Driver");
了解java反射的话看到这个就明白了,就是将指定的类添加到jvm内存中,这里也就是将com.mysql.jdbc.Driver 这个类添加到jvm内存中。当类初始化时会执行类的static{}方法,而static{} 方法中调用了DriverManager 类的register() 方法来注册Driver 实例。
2、获取链接
Connection con = DriverManager.getConnection(url,username,password);
先说说其实第一步中当Driver类被加载到jvm内存当中时会执行static方法而 static方法的执行会将这个驱动类注册到DriverManager这个类中,具体细节可以看之前源码。
获取链接就是指定的url匹配到的当前驱动类的链接实例赋值给jdbc api 这个接口类型。
其实connection 在底层就是进行了指定ip 和port的socket 的获取,这个获取过程不仅慢而且占用了有限的物理资源,所以这也是jdbc链接中比较耗时耗资源的过程。
3、Statement PreparedStatement
当我们获取了链接后要进行查询,而查询的载体就是Statement 和PreparedStatement 两个对象,这两个对象是基于Connection ,如果没有Connection则不会有这些对象
创建Statement 和PreparedStatement对象的时候我们可以设置结果集类型等这样可以有更多的方式来操作结果集。
Statement 和PreparedStatement 向数据库服务器发送sql 语句,并且调用方法可以获取执行sql的结果集。
还有在前面说了Statement 和PreparedStatement 就是一个sql载体,所以这些对象可以重复使用,这样可以减少内存的浪费和时间的浪费。但是有个前提那就是他之前发送的sql的结果集ResultSet 已经处理完,不然就会被关闭这样的话之前的资源就没法访问了。
下面看具体的列子:
正确:
PreparedStatement pre= con.prepareStatement(sql);
ResultSet res1= pre.executeQuery();
int i=0;
while(res1.next()){
++i;
System.out.println(i);
}
// 先执行完结果集res1再获取res2
ResultSet res2= pre.executeQuery();
int j=0;
while(res2.next()){
++j;
System.out.println(j);
}错误:
PreparedStatement pre= con.prepareStatement(sql);
//获取结果集res1
ResultSet res1= pre.executeQuery();
//获取结果集res2,但是这时res1就被关闭了。所以在下面获取值时会报已关闭异常。
ResultSet res2= pre.executeQuery();
int i=0;
while(res1.next()){
++i;
System.out.println(i);
}
int j=0;
while(res2.next()){
++j;
System.out.println(j);
}4、ResultSet
结果集,当Statement 执行sql获取了结果集后我们可以处理数据了,但是注意这里获取的是结果集而不是具体的数据,也就是ResultSet只是记录了sql语句执行的结果集如有多少列,每一列的名称是什么,每一列的类型是什么以及总共有多少行等这些信息。
当我们调用ResultSet 对象的方法去获取数据的时候其实就是根据ResultSet 对象记录的行列 类型等各种信息到数据库服务器中去取对应的行,列的数据。
其实ResultSet 也是基于Connection的,如果Connection断开或者是资源释放了ResultSet都没法调用器方法获取数据。
5、释放资源
通过上面的四步我们可以获取需要的数据但是我们要注意,在前面的四步中我们占用了很多系统,物理资源多以当我们不用的时候应该释放这些被占用的资源
注意释放资源的代码写在 finally 代码块中,这样的话不管出现什么异常,资源都能正常释放。
我们经常能看到很多人会问释放资源的时候释放了statement 的资源不是也会释放Connection 链接的资源吗,为什么平时大家写的时候会释放三次即
ResultSet.colose();
Statement.close();
Connection.close();
当我们看到ResultSet 和Statement释放资源的源码的时候确实是把他们相关的connection的资源给释放了,源码如下:
close 方法中调用了realClose() 这个方法释放资源
/**
* Closes this ResultSet and releases resources.
*
* @param calledExplicitly
* was realClose called by the standard ResultSet.close() method, or was it closed internally by the
* driver?
*
* @throws SQLException
* if an error occurs
*/
public void realClose(boolean calledExplicitly) throws SQLException {
MySQLConnection locallyScopedConn = this.connection;
if (locallyScopedConn == null) {
return; // already closed
}
synchronized (locallyScopedConn.getConnectionMutex()) {
// additional check in case ResultSet was closed
// while current thread was waiting for lock
if (this.isClosed) {
return;
}
try {
if (this.useUsageAdvisor) {
// Report on result set closed by driver instead of application
if (!calledExplicitly) {
this.eventSink.consumeEvent(new ProfilerEvent(ProfilerEvent.TYPE_WARN, "", (this.owningStatement == null) ? "N/A"
: this.owningStatement.currentCatalog, this.connectionId, (this.owningStatement == null) ? (-1) : this.owningStatement.getId(),
this.resultId, System.currentTimeMillis(), 0, Constants.MILLIS_I18N, null, this.pointOfOrigin, Messages
.getString("ResultSet.ResultSet_implicitly_closed_by_driver")));
}
if (this.rowData instanceof RowDataStatic) {
// Report on possibly too-large result sets
if (this.rowData.size() > this.connection.getResultSetSizeThreshold()) {
this.eventSink.consumeEvent(new ProfilerEvent(ProfilerEvent.TYPE_WARN, "", (this.owningStatement == null) ? Messages
.getString("ResultSet.N/A_159") : this.owningStatement.currentCatalog, this.connectionId,
(this.owningStatement == null) ? (-1) : this.owningStatement.getId(), this.resultId, System.currentTimeMillis(), 0,
Constants.MILLIS_I18N, null, this.pointOfOrigin, Messages.getString("ResultSet.Too_Large_Result_Set", new Object[] {
Integer.valueOf(this.rowData.size()), Integer.valueOf(this.connection.getResultSetSizeThreshold()) })));
}
if (!isLast() && !isAfterLast() && (this.rowData.size() != 0)) {
this.eventSink.consumeEvent(new ProfilerEvent(ProfilerEvent.TYPE_WARN, "", (this.owningStatement == null) ? Messages
.getString("ResultSet.N/A_159") : this.owningStatement.currentCatalog, this.connectionId,
(this.owningStatement == null) ? (-1) : this.owningStatement.getId(), this.resultId, System.currentTimeMillis(), 0,
Constants.MILLIS_I18N, null, this.pointOfOrigin, Messages.getString(
"ResultSet.Possible_incomplete_traversal_of_result_set",
new Object[] { Integer.valueOf(getRow()), Integer.valueOf(this.rowData.size()) })));
}
}
//
// Report on any columns that were selected but not referenced
//
if (this.columnUsed.length > 0 && !this.rowData.wasEmpty()) {
StringBuilder buf = new StringBuilder(Messages.getString("ResultSet.The_following_columns_were_never_referenced"));
boolean issueWarn = false;
for (int i = 0; i < this.columnUsed.length; i++) {
if (!this.columnUsed[i]) {
if (!issueWarn) {
issueWarn = true;
} else {
buf.append(", ");
}
buf.append(this.fields[i].getFullName());
}
}
if (issueWarn) {
this.eventSink.consumeEvent(new ProfilerEvent(ProfilerEvent.TYPE_WARN, "", (this.owningStatement == null) ? "N/A"
: this.owningStatement.currentCatalog, this.connectionId, (this.owningStatement == null) ? (-1) : this.owningStatement
.getId(), 0, System.currentTimeMillis(), 0, Constants.MILLIS_I18N, null, this.pointOfOrigin, buf.toString()));
}
}
}
} finally {
if (this.owningStatement != null && calledExplicitly) {
this.owningStatement.removeOpenResultSet(this);
}
SQLException exceptionDuringClose = null;
if (this.rowData != null) {
try {
this.rowData.close();
} catch (SQLException sqlEx) {
exceptionDuringClose = sqlEx;
}
}
if (this.statementUsedForFetchingRows != null) {
try {
this.statementUsedForFetchingRows.realClose(true, false);
} catch (SQLException sqlEx) {
if (exceptionDuringClose != null) {
exceptionDuringClose.setNextException(sqlEx);
} else {
exceptionDuringClose = sqlEx;
}
}
}
this.rowData = null;
this.fields = null;
this.columnLabelToIndex = null;
this.fullColumnNameToIndex = null;
this.columnToIndexCache = null;
this.eventSink = null;
this.warningChain = null;
if (!this.retainOwningStatement) {
this.owningStatement = null;
}
this.catalog = null;
this.serverInfo = null;
this.thisRow = null;
this.fastDefaultCal = null;
this.fastClientCal = null;
this.connection = null;
this.isClosed = true;
if (exceptionDuringClose != null) {
throw exceptionDuringClose;
}
}
}
}从注释和适代码我们都可以看到,他是释放了它本身和相关的所有资源,如果在看Statement的clolse 方法调用的realClose() 方法来释放资源和这个一样。但是当我们调用了close() 方法释放了资源自来打印对象的时候你会发现对象是存在的:
那我们该怎么释放资源那,个人感觉这样比较好:
res.colose();
res=null;
sta.close();
sta=null;
con.close();
con=null;
这样能快速释放资源也能间接加快jvm垃圾回收,但还有一个问题要注意,那就是释放资源的顺序,一定要按照从小到大的顺序来,也就是ResultSet Statement Connection 这样的顺序来。
二、jdbc 优化
我们看到上面有五个步骤,但是个人感觉第一步骤就没办法优化,下面看看剩下的四个步骤:
1、获取链接Connection即Socket
这个过程是链接中最耗时间和资源的,那我们怎么来优化那。
我们知道Connection就是获取Socket链接这个链接很耗时而且占用物理资源,所以在没必要的时候我们要尽量少的获取链接。
应为他的链接占用着物理资源,所以在使用完了之后我们要马上释放资源,一边能更好的使用物理资源。
当获取多个链接的时候我们可以使用链接池。
使用链接池有什么好处那:
(1)、他会创建多个Connection这样我们在使用的时候就可以使用不用等待和数据库服务的链接这样能加快速度
(2)、链接占用了物理资源,如果一直这样占用着则,其他程序没法使用这个物理资源,所以数据库链接池会时时的释放这些物理资源这样能更好的使用物理资源
(3)、我们也知道,在使用的少的时候数据库链接池会释放哪些多余的资源这样能合理的使用资源。而又能加快链接的获取的速度。
所以在合适的时候使用数据库链接池能带来性能的提升。
2、Statement PreparedStatemetn
其实选择使用Statement 还是PreparedStatement 在不用的场合下对性能影响还是很大的,我们知道Statement对象只发送一次请求但是每次的sql语句会进行编译在执行,而PreparedStatement则不同,他会发送两次请求,第一次的sql 中没有具体的参数而是使用了占位符“?” 来替代参数并对这个sql语句进行预编译,而之后输入的参数只会替代原来的占位符尽心查询不会进行再次编译就直接开始执行了。
还有这里注意: 使用PreparedStatement 的批处理能提升很大的性能,因为我们没执行一次都是向数据库服务器发送请求,每次都要经过网络传输,所以很慢,而使用批处理就是将这些查询sql 语句放在驱动换粗中,当我们调用执行方法的时候才会把这些sql 一次性发给数据库服务,这样就减少了网络间传递的次数,如果数据量很少而循环的次数很多,则使用批量操作方法则性能能提升很多。
3、ResultSet
结果集的获取,当我们调用Statement 还是PreparedStatement 类的方法的执行了sql语句后我们能得到一个执行的结果集,而返回的这个结果集不是所有查询到的数据而只是查询到的数据的相关信息如:执行sql 查询到的数据有多少个列 列名称 列的类型以及有多少行,指向第一行之前的位置等
当我们平时些的时候获取数据调用的方法都是 Res.getString("name") Res.getInt("age") 等这样的方法,但是在后台的程序中还是转变调用了Res.getString(column) Res.getInt(column) 这个方法。还是那个句话你做的多了计算机干的就少了。
还有一个就是尽量获取有用的数据,不要查询获取不需要的数据,因为他们的获取都要通过 磁盘的I/O 以及网络的传输,如果查询并获取了一些没用的数据则会加重这个过程的负担。
4、释放资源
当我们查询数据结束后我们应该要释放资源,释放资源的方式和顺序在前面已经说了,一定要按照那个步骤来。那样能间接加快jvm内存释放
三、个人看法
在工作中可能会有这样的需求,那就是从一个数据库查询数据,对这些数据进行各种处理在将数据导入到另一个数据库表中去,这样的话那我们该怎么做那。
现在个人感觉有两种处理方式:一条一条处理和批量处理,下面描述我的这两种想法:
1、一条一条的处理:
(1)、一个sql查询获取ResultSet 结果集对象
(2)、循环结果集,并将结果集组装成对象,在按照要求进行各中处理。
(3)、处理后的组装的那个对象导入另一个数据库表中
(4)、接着下一条数据重复(2)、(3) 过程这样就可以了。
这种方式还可以修改一下那就是我们循环遍历 ResultSet 结果集并将其组装成对象放到Collection 集合中这样就可以提前释放ResultSet 对象了。
2、批量处理
(1)、循环遍历组装成对象放到集合中,在循环遍历批量处理各种条件,并将处理后的对象设置添加到批量添加中。
(2)、执行批量添加,这样的话速度会比第一种快很多。
第二种方式比第一种方式快的原因就是他进行了批量的入库,减少了网络连接的次数。但是第二种有个隐藏的危险那就是如果你查询的数据量过大过多,将其组装成对象这样是不是会出现内存溢出。第一种方式速度慢但是内存溢出的可能性不大,因为当你处理完一条后你可以释放他,这样在jvm内存吃紧的时候最起码可以回收之前创建的并释放了的那些对象。
不管是批量处理还是单条处理,最好不要一次性查询太多这样的话,数据库链接时间很长,占用资源时间很长,而且组装对象的时候很有可能出现内存溢出的情况。

1014

被折叠的 条评论
为什么被折叠?



