- 浏览: 110234 次
- 性别:
- 来自: 惠州
文章分类
最新评论
-
dw3799:
写个单列实现,只有阿里员工能写吗
枚举类型的单例模式(java) -
不再是小白:
错误太多!
枚举类型的单例模式(java) -
iljyh123:
1楼怎么猜到博主是阿里的员工
枚举类型的单例模式(java) -
limb99:
超赞。博主应该是阿里的员工吧
枚举类型的单例模式(java) -
yexinchen:
请问为什么呢?我也是这个问题!
java.lang.NoClassDefFoundError: Could not initialize class util.HibernateUtil
http://developer.51cto.com/art/200907/137823.htm
MySQL是一个中小型关系型数据库管理系统,目前使用的也比较广泛。为了对开发中间DAO层的问题能有更深的理解,在遇到问题的时候能够有更多的思路,于是研究了一下MySQL JDBC驱动的使用,并且在这过程中也发现了一直以来关于PreparedStatement常识理解上的错误,与大家分享。
下面是个最简单的使用jdbc取得数据的应用。在例子之后我将分成4步,分别是①取得连接,②创建PreparedStatement,③设置参数,④执行查询,来分步分析这个过程。除了设置参数那一步之外,其他的我都画了时序图,如果不想看文字的话,可以对着时序图 。文中的第4步是组装MySQL协议并发送数据包的关键,而且在这部分的(b)环节,我对于PreparedStatement的应用有详细的代码注释分析,建议大家关注一下。
- Java代码
- Java代码
- public class DBHelper {
- public
class DBHelper {
- public static Connection getConnection() {
- public static Connection getConnection() {
- Connection conn = null;
- Connection conn =
null;
- try {
- try {
- Class.forName("com.mysql.jdbc.Driver");
- Class.forName(
"com.mysql.jdbc.Driver");
- conn = DriverManager.getConnection("jdbc:mysql://localhost/ad?useUnicode=true&characterEncoding=GBK&jdbcCompliantTruncation=false",
- conn = DriverManager.getConnection(
"jdbc:mysql://localhost/ad?useUnicode=true&characterEncoding=GBK&jdbcCompliantTruncation=false",
- "root", "root");
- "root", "root");
- } catch (Exception e) {
- }
catch (Exception e) {
- e.printStackTrace();
- e.printStackTrace();
- }
- }
- return conn;
- return conn;
- }
- }
- }
- }
- /*dao中的方法*/
- /*dao中的方法*/
- public List getAllAdvs() {
- public List getAllAdvs() {
- Connection conn = null;
- Connection conn =
null;
- ResultSet rs = null;
- ResultSet rs =
null;
- PreparedStatement stmt = null;
- PreparedStatement stmt =
null;
- String sql = "select * from adv where id = ?";
- String sql =
"select * from adv where id = ?";
- List advs = new ArrayList();
- List advs =
new ArrayList();
- conn = DBHelper.getConnection();
- conn = DBHelper.getConnection();
- if (conn != null) {
- if (conn != null) {
- try {
- try {
- stmt = conn.prepareStatement(sql);
- stmt = conn.prepareStatement(sql);
- stmt.setInt(1, new Integer(1));
- stmt.setInt(
1, new Integer(1));
- rs = stmt.executeQuery();
- rs = stmt.executeQuery();
- if (rs != null) {
- if (rs != null) {
- while (rs.next()) {
- while (rs.next()) {
- Adv adv = new Adv();
- Adv adv =
new Adv();
- adv.setId(rs.getLong(1));
- adv.setId(rs.getLong(
1));
- adv.setName(rs.getString(2));
- adv.setName(rs.getString(
2));
- adv.setDesc(rs.getString(3));
- adv.setDesc(rs.getString(
3));
- adv.setPicUrl(rs.getString(4));
- adv.setPicUrl(rs.getString(
4));
- advs.add(adv);
- advs.add(adv);
- }
- }
- }
- }
- } catch (SQLException e) {
- }
catch (SQLException e) {
- e.printStackTrace();
- e.printStackTrace();
- } finally {
- }
finally {
- try {
- try {
- stmt.close();
- stmt.close();
- conn.close();
- conn.close();
- } catch (SQLException e) {
- }
catch (SQLException e) {
- e.printStackTrace();
- e.printStackTrace();
- }
- }
- }
- }
- }
- }
- return advs;
- return advs;
- }
- }
- public class DBHelper {
- public
class DBHelper {
- public static Connection getConnection() {
- public static Connection getConnection() {
- Connection conn = null;
- Connection conn =
null;
- try {
- try {
- Class.forName("com.mysql.jdbc.Driver");
- Class.forName(
"com.mysql.jdbc.Driver");
- conn = DriverManager.getConnection("jdbc:mysql://localhost/ad?useUnicode=true&characterEncoding=GBK&jdbcCompliantTruncation=false",
- conn = DriverManager.getConnection(
"jdbc:mysql://localhost/ad?useUnicode=true&characterEncoding=GBK&jdbcCompliantTruncation=false",
- "root", "root");
- "root", "root");
- } catch (Exception e) {
- }
catch (Exception e) {
- e.printStackTrace();
- e.printStackTrace();
- }
- }
- return conn;
- return conn;
- }
- }
- }
- }
- /*dao中的方法*/
- /*dao中的方法*/
- public List getAllAdvs() {
- public List getAllAdvs() {
- Connection conn = null;
- Connection conn =
null;
- ResultSet rs = null;
- ResultSet rs =
null;
- PreparedStatement stmt = null;
- PreparedStatement stmt =
null;
- String sql = "select * from adv where id = ?";
- String sql =
"select * from adv where id = ?";
- List advs = new ArrayList();
- List advs =
new ArrayList();
- conn = DBHelper.getConnection();
- conn = DBHelper.getConnection();
- if (conn != null) {
- if (conn != null) {
- try {
- try {
- stmt = conn.prepareStatement(sql);
- stmt = conn.prepareStatement(sql);
- stmt.setInt(1, new Integer(1));
- stmt.setInt(
1, new Integer(1));
- rs = stmt.executeQuery();
- rs = stmt.executeQuery();
- if (rs != null) {
- if (rs != null) {
- while (rs.next()) {
- while (rs.next()) {
- Adv adv = new Adv();
- Adv adv =
new Adv();
- adv.setId(rs.getLong(1));
- adv.setId(rs.getLong(
1));
- adv.setName(rs.getString(2));
- adv.setName(rs.getString(
2));
- adv.setDesc(rs.getString(3));
- adv.setDesc(rs.getString(
3));
- adv.setPicUrl(rs.getString(4));
- adv.setPicUrl(rs.getString(
4));
- advs.add(adv);
- advs.add(adv);
- }
- }
- }
- }
- } catch (SQLException e) {
- }
catch (SQLException e) {
- e.printStackTrace();
- e.printStackTrace();
- } finally {
- }
finally {
- try {
- try {
- stmt.close();
- stmt.close();
- conn.close();
- conn.close();
- } catch (SQLException e) {
- }
catch (SQLException e) {
- e.printStackTrace();
- e.printStackTrace();
- }
- }
- }
- }
- }
- }
- return advs;
- return advs;
- }
- }
- Java代码
- Java代码
- public class Driver extends NonRegisteringDriver implements java.sql.Driver {
- public
class Driver extends NonRegisteringDriver implements java.sql.Driver {
- static {
- static {
- try {
- try {
- java.sql.DriverManager.registerDriver(new Driver());
- java.sql.DriverManager.registerDriver(
new Driver());
- } catch (SQLException E) {
- }
catch (SQLException E) {
- throw new RuntimeException("Can't register driver!");
- throw new RuntimeException("Can't register driver!");
- }
- }
- }
- }
- public Driver() throws SQLException {
- public Driver() throws SQLException {
- }
- }
- }
- }
- public class Driver extends NonRegisteringDriver implements java.sql.Driver {
- public
class Driver extends NonRegisteringDriver implements java.sql.Driver {
- static {
- static {
- try {
- try {
- java.sql.DriverManager.registerDriver(new Driver());
- java.sql.DriverManager.registerDriver(
new Driver());
- } catch (SQLException E) {
- }
catch (SQLException E) {
- throw new RuntimeException("Can't register driver!");
- throw new RuntimeException("Can't register driver!");
- }
- }
- }
- }
- public Driver() throws SQLException {
- public Driver() throws SQLException {
- }
- }
- }
- }
- Java代码
- Java代码
- public static synchronized void registerDriver(java.sql.Driver driver)
- public static synchronized void registerDriver(java.sql.Driver driver)
- throws SQLException {
- throws
SQLException {
- if (!initialized) {
- if
(!initialized) {
- initialize();
- initialize();
- }
- }
- DriverInfo di = new DriverInfo();
- DriverInfo di =
new DriverInfo();
- /*把driver的信息封装一下,组成一个DriverInfo对象*/
- /*把driver的信息封装一下,组成一个DriverInfo对象*/
- di.driver = driver;
- di.driver = driver;
- di.driverClass = driver.getClass();
- di.driverClass = driver.getClass();
- di.driverClassName = di.driverClass.getName();
- di.driverClassName = di.driverClass.getName();
- writeDrivers.addElement(di);
- writeDrivers.addElement(di);
- println("registerDriver: " + di);
- println(
"registerDriver: " + di);
- readDrivers = (java.util.Vector) writeDrivers.clone();
- readDrivers = (java.util.Vector) writeDrivers.clone();
- }
- }
- public static synchronized void registerDriver(java.sql.Driver driver)
- public static synchronized void registerDriver(java.sql.Driver driver)
- throws SQLException {
- throws SQLException {
- if (!initialized) {
- if (!initialized) {
- initialize();
- initialize();
- }
- }
- DriverInfo di = new DriverInfo();
- DriverInfo di =
new DriverInfo();
- /*把driver的信息封装一下,组成一个DriverInfo对象*/
- /*把driver的信息封装一下,组成一个DriverInfo对象*/
- di.driver = driver;
- di.driver = driver;
- di.driverClass = driver.getClass();
- di.driverClass = driver.getClass();
- di.driverClassName = di.driverClass.getName();
- di.driverClassName = di.driverClass.getName();
- writeDrivers.addElement(di);
- writeDrivers.addElement(di);
- println("registerDriver: " + di);
- println(
"registerDriver: " + di);
- readDrivers = (java.util.Vector) writeDrivers.clone();
- readDrivers = (java.util.Vector) writeDrivers.clone();
- }
- }
至于驱动的集合writeDrivers和readDrivers,很有趣的是,无论是registerDriver还是deregisterDriver,都是先对writeDrivers中的数据进行添加或者删除,然后再把writeDrivers中的驱动都拷贝到readDrivers中,但每次取出driver却从来不从writeDrivers中取,都是通过readDrivers来获得。我认为可以这样理解,writeDrivers只负责注册driver与注销driver,而readDrivers只负责提供可用的driver,只有当writeDrivers中准备好了驱动,这些驱动才是可以使用的,所以才能被copy至readDrivers中以备使用。这样一来,对内的注册注销与对外的提供使用就分开来了。
- Java代码
- Java代码
- private static Connection getConnection(
- private static Connection getConnection(
- String url, java.util.Properties info, ClassLoader callerCL) throws SQLException {
- String url, java.util.Properties info, ClassLoader callerCL)
throws SQLException {
- java.util.Vector drivers = null;
- java.util.Vector drivers =
null;
- ...
- ...
- if (!initialized) {
- if
(!initialized) {
- initialize();
- initialize();
- }
- }
- /*取得连接使用的driver从readDrivers中取*/
- /*取得连接使用的driver从readDrivers中取*/
- synchronized (DriverManager.class){
- synchronized
(DriverManager.class){
- drivers = readDrivers;
- drivers = readDrivers;
- }
- }
- SQLException reason = null;
- SQLException reason =
null;
- for (int i = 0; i < drivers.size(); i++) {
- for
(int i = 0; i < drivers.size(); i++) {
- DriverInfo di = (DriverInfo)drivers.elementAt(i);
- DriverInfo di = (DriverInfo)drivers.elementAt(i);
- if ( getCallerClass(callerCL, di.driverClassName ) != di.driverClass ) {
- if ( getCallerClass(callerCL, di.driverClassName ) != di.driverClass ) {
- continue;
- continue;
- }
- }
- try {
- try {
- /*找到可供使用的驱动,连接数据库server*/
- /*找到可供使用的驱动,连接数据库server*/
- Connection result = di.driver.connect(url, info);
- Connection result = di.driver.connect(url, info);
- if (result != null) {
- if (result != null) {
- return (result);
- return (result);
- }
- }
- } catch (SQLException ex) {
- }
catch (SQLException ex) {
- if (reason == null) {
- if (reason == null) {
- reason = ex;
- reason = ex;
- }
- }
- }
- }
- }
- }
- if (reason != null) {
- if
(reason != null) {
- println("getConnection failed: " + reason);
- println(
"getConnection failed: " + reason);
- throw reason;
- throw reason;
- }
- }
- throw new SQLException("No suitable driver found for "+ url, "08001");
- throw
new SQLException("No suitable driver found for "+ url, "08001");
- }
- }
- private static Connection getConnection(
- private static Connection getConnection(
- String url, java.util.Properties info, ClassLoader callerCL) throws SQLException {
- String url, java.util.Properties info, ClassLoader callerCL)
throws SQLException {
- java.util.Vector drivers = null;
- java.util.Vector drivers =
null;
- ...
- ...
- if (!initialized) {
- if (!initialized) {
- initialize();
- initialize();
- }
- }
- /*取得连接使用的driver从readDrivers中取*/
- /*取得连接使用的driver从readDrivers中取*/
- synchronized (DriverManager.class){
- synchronized (DriverManager.class){
- drivers = readDrivers;
- drivers = readDrivers;
- }
- }
- SQLException reason = null;
- SQLException reason =
null;
- for (int i = 0; i < drivers.size(); i++) {
- for (int i = 0; i < drivers.size(); i++) {
- DriverInfo di = (DriverInfo)drivers.elementAt(i);
- DriverInfo di = (DriverInfo)drivers.elementAt(i);
- if ( getCallerClass(callerCL, di.driverClassName ) != di.driverClass ) {
- if ( getCallerClass(callerCL, di.driverClassName ) != di.driverClass ) {
- continue;
- continue;
- }
- }
- try {
- try {
- /*找到可供使用的驱动,连接数据库server*/
- /*找到可供使用的驱动,连接数据库server*/
- Connection result = di.driver.connect(url, info);
- Connection result = di.driver.connect(url, info);
- if (result != null) {
- if (result != null) {
- return (result);
- return (result);
- }
- }
- } catch (SQLException ex) {
- }
catch (SQLException ex) {
- if (reason == null) {
- if (reason == null) {
- reason = ex;
- reason = ex;
- }
- }
- }
- }
- }
- }
- if (reason != null) {
- if (reason != null) {
- println("getConnection failed: " + reason);
- println(
"getConnection failed: " + reason);
- throw reason;
- throw reason;
- }
- }
- throw new SQLException("No suitable driver found for "+ url, "08001");
- throw new SQLException("No suitable driver found for "+ url, "08001");
- }
- }
- Java代码
- Java代码
- public java.sql.Connection connect(String url, Properties info)
- public
java.sql.Connection connect(String url, Properties info)
- throws SQLException {
- throws SQLException {
- Properties props = null;
- Properties props =
null;
- if ((props = parseURL(url, info)) == null) {
- if ((props = parseURL(url, info)) == null) {
- return null;
- return null;
- }
- }
- try {
- try {
- Connection newConn = new com.mysql.jdbc.Connection(host(props),
- Connection newConn =
new com.mysql.jdbc.Connection(host(props),
- port(props), props, database(props), url);
- port(props), props, database(props), url);
- return newConn;
- return newConn;
- } catch (SQLException sqlEx) {
- }
catch (SQLException sqlEx) {
- throw sqlEx;
- throw sqlEx;
- } catch (Exception ex) {
- }
catch (Exception ex) {
- throw SQLError.createSQLException(...);
- throw SQLError.createSQLException(...);
- }
- }
- }
- }
- public java.sql.Connection connect(String url, Properties info)
- public java.sql.Connection connect(String url, Properties info)
- throws SQLException {
- throws SQLException {
- Properties props = null;
- Properties props =
null;
- if ((props = parseURL(url, info)) == null) {
- if ((props = parseURL(url, info)) == null) {
- return null;
- return null;
- }
- }
- try {
- try {
- Connection newConn = new com.mysql.jdbc.Connection(host(props),
- Connection newConn =
new com.mysql.jdbc.Connection(host(props),
- port(props), props, database(props), url);
- port(props), props, database(props), url);
- return newConn;
- return newConn;
- } catch (SQLException sqlEx) {
- }
catch (SQLException sqlEx) {
- throw sqlEx;
- throw sqlEx;
- } catch (Exception ex) {
- }
catch (Exception ex) {
- throw SQLError.createSQLException(...);
- throw SQLError.createSQLException(...);
- }
- }
- }
- }
2、PreparedStatement stmt = conn.prepareStatement(sql);使用得到的connection创建一个Statement。Statement有许多种,我们常用的就是PreparedStatement,用于执行预编译好的SQL语句,CallableStatement用于调用数据库的存储过程。它们的继承关系如下图所示。 Java代码 public java.sql.PreparedStatement prepareStatement(String sql) throws SQLException { return prepareStatement(sql, java.sql.ResultSet.TYPE_FORWARD_ONLY, java.sql.ResultSet.CONCUR_READ_ONLY); }
public java.sql.PreparedStatement prepareStatement(String sql,
int resultSetType, int resultSetConcurrency) throws SQLException;
public java.sql.PreparedStatement prepareStatement(String sql)
throws SQLException {
return prepareStatement(sql, java.sql.ResultSet.TYPE_FORWARD_ONLY,
java.sql.ResultSet.CONCUR_READ_ONLY);
} public java.sql.PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) throws SQLException; public java.sql.PreparedStatement prepareStatement(String sql) throws SQLException { return prepareStatement(sql, java.sql.ResultSet.TYPE_FORWARD_ONLY, java.sql.ResultSet.CONCUR_READ_ONLY); } int resultSetType, int resultSetConcurrency) throws SQLException; ResultSet中的参数常量主要有以下几种:
- TYPE_FORWARD_ONLY: ResultSet的游标只能向前移动。
- TYPE_FORWARD_ONLY: ResultSet的游标只能向前移动。
- TYPE_SCROLL_INSENSITIVE:ResultSet的游标可以滚动,但对于resultSet下的数据改变不敏感。
- TYPE_SCROLL_INSENSITIVE:ResultSet的游标可以滚动,但对于resultSet下的数据改变不敏感。
- TYPE_SCROLL_SENSITIVE:ResultSet的游标可以滚动,但对于resultSet下的数据改变是敏感的。
- TYPE_SCROLL_SENSITIVE:ResultSet的游标可以滚动,但对于resultSet下的数据改变是敏感的。
- CONCUR_READ_ONLY:不可以更新的ResultSet的并发模式。
- CONCUR_READ_ONLY:不可以更新的ResultSet的并发模式。
- CONCUR_UPDATABLE:可以更新的ResultSet的并发模式。
- CONCUR_UPDATABLE:可以更新的ResultSet的并发模式。
- FETCH_FORWARD:按正向(即从第一个到最后一个)处理结果集中的行。
- FETCH_FORWARD:按正向(即从第一个到最后一个)处理结果集中的行。
- FETCH_REVERSE:按反向(即从最后一个到第一个)处理结果集中的行处理。
- FETCH_REVERSE:按反向(即从最后一个到第一个)处理结果集中的行处理。
- FETCH_UNKNOWN:结果集中的行的处理顺序未知。
- FETCH_UNKNOWN:结果集中的行的处理顺序未知。
- CLOSE_CURSORS_AT_COMMIT:调用Connection.commit方法时应该关闭 ResultSet 对
- CLOSE_CURSORS_AT_COMMIT:调用Connection.commit方法时应该关闭 ResultSet 对
- HOLD_CURSORS_OVER_COMMIT:调用Connection.commit方法时不应关闭ResultSet对象。
- HOLD_CURSORS_OVER_COMMIT:调用Connection.commit方法时不应关闭ResultSet对象。
3、stmt.setInt(1, new Integer(1)); 4、ResultSet rs = stmt.executeQuery(); 一切准备就绪,开始执行查询罗!
- Java代码
- Java代码
- protected Buffer fillSendPacket(byte[][] batchedParameterStrings,
- protected Buffer fillSendPacket(byte[][] batchedParameterStrings,
- InputStream[] batchedParameterStreams, boolean[] batchedIsStream,
- InputStream[] batchedParameterStreams,
boolean[] batchedIsStream,
- int[] batchedStreamLengths) throws SQLException {
- int[] batchedStreamLengths) throws SQLException {
- // 从connection的IO中得到发送数据包,首先清空其中的数据
- // 从connection的IO中得到发送数据包,首先清空其中的数据
- Buffer sendPacket = this.connection.getIO().getSharedSendPacket();
- Buffer sendPacket =
this.connection.getIO().getSharedSendPacket();
- sendPacket.clear();
- sendPacket.clear();
- /* 数据包的第一位为一个操作标识符(MysqlDefs.QUERY),表示驱动向服务器发送的连接的操作信号,包括有QUERY, PING, RELOAD, SHUTDOWN, PROCESS_INFO, QUIT, SLEEP等等,这个操作信号并不是针对sql语句操作而言的CRUD操作,从提供的几种参数来看,这个操作是针对服务器的一个操作。一般而言,使用到的都是MysqlDefs.QUERY,表示发送的是要执行sql语句的操作。
- /* 数据包的第一位为一个操作标识符(MysqlDefs.QUERY),表示驱动向服务器发送的连接的操作信号,包括有QUERY, PING, RELOAD, SHUTDOWN, PROCESS_INFO, QUIT, SLEEP等等,这个操作信号并不是针对sql语句操作而言的CRUD操作,从提供的几种参数来看,这个操作是针对服务器的一个操作。一般而言,使用到的都是MysqlDefs.QUERY,表示发送的是要执行sql语句的操作。
- */
- */
- sendPacket.writeByte((byte) MysqlDefs.QUERY);
- sendPacket.writeByte((
byte) MysqlDefs.QUERY);
- boolean useStreamLengths = this.connection
- boolean useStreamLengths = this.connection
- .getUseStreamLengthsInPrepStmts();
- .getUseStreamLengthsInPrepStmts();
- int ensurePacketSize = 0;
- int ensurePacketSize = 0;
- for (int i = 0; i < batchedParameterStrings.length; i++) {
- for (int i = 0; i < batchedParameterStrings.length; i++) {
- if (batchedIsStream[i] && useStreamLengths) {
- if (batchedIsStream[i] && useStreamLengths) {
- ensurePacketSize += batchedStreamLengths[i];
- ensurePacketSize += batchedStreamLengths[i];
- }
- }
- }
- }
- /* 判断这个sendPacket的byte buffer够不够大,不够大的话,按照1.25倍来扩充buffer
- /* 判断这个sendPacket的byte buffer够不够大,不够大的话,按照1.25倍来扩充buffer
- */
- */
- if (ensurePacketSize != 0) {
- if (ensurePacketSize != 0) {
- sendPacket.ensureCapacity(ensurePacketSize);
- sendPacket.ensureCapacity(ensurePacketSize);
- }
- }
- /* 遍历所有的参数。在prepareStatement阶段的new ParseInfo()中,驱动曾经对sql语句进行过分割,如果含有以问号标识的参数占位符的话,就记录下这个占位符的位置,依据这个位置把sql分割成多段,放在了一个名为staticSql的字符串中。这里就开始把sql语句进行拼装,把staticSql和parameterValues进行组合,放在操作符的后面。
- /* 遍历所有的参数。在prepareStatement阶段的new ParseInfo()中,驱动曾经对sql语句进行过分割,如果含有以问号标识的参数占位符的话,就记录下这个占位符的位置,依据这个位置把sql分割成多段,放在了一个名为staticSql的字符串中。这里就开始把sql语句进行拼装,把staticSql和parameterValues进行组合,放在操作符的后面。
- */
- */
- for (int i = 0; i < batchedParameterStrings.length; i++) {
- for (int i = 0; i < batchedParameterStrings.length; i++) {
- /* batchedParameterStrings就是parameterValues,batchedParameterStreams就是parameterStreams,如果两者都为null,说明在参数的设置过程中出了错,立即抛出错误。
- /* batchedParameterStrings就是parameterValues,batchedParameterStreams就是parameterStreams,如果两者都为null,说明在参数的设置过程中出了错,立即抛出错误。
- */
- */
- if ((batchedParameterStrings[i] == null)
- if ((batchedParameterStrings[i] == null)
- && (batchedParameterStreams[i] == null)) {
- && (batchedParameterStreams[i] ==
null)) {
- throw SQLError.createSQLException(Messages
- throw SQLError.createSQLException(Messages
- .getString("PreparedStatement.40") //$NON-NLS-1$
- .getString(
"PreparedStatement.40") //$NON-NLS-1$
- + (i + 1), SQLError.SQL_STATE_WRONG_NO_OF_PARAMETERS);
- + (i +
1), SQLError.SQL_STATE_WRONG_NO_OF_PARAMETERS);
- }
- }
- /*在sendPacket中加入staticSql数组中的元素,就是分割出来的没有”?”的sql语句,并把字符串转换成byte。
- /*在sendPacket中加入staticSql数组中的元素,就是分割出来的没有”?”的sql语句,并把字符串转换成byte。
- */
- */
- sendPacket.writeBytesNoNull(this.staticSqlStrings[i]);
- sendPacket.writeBytesNoNull(
this.staticSqlStrings[i]);
- /* batchedIsStream就是isStream,如果参数是通过CallableStatement传递进来的话,batchedIsStream[i]==true,就用batchedParameterStreams中的值填充到问号占的参数位置中去。
- /* batchedIsStream就是isStream,如果参数是通过CallableStatement传递进来的话,batchedIsStream[i]==true,就用batchedParameterStreams中的值填充到问号占的参数位置中去。
- */
- */
- if (batchedIsStream[i]) {
- if (batchedIsStream[i]) {
- streamToBytes(sendPacket, batchedParameterStreams[i], true,
- streamToBytes(sendPacket, batchedParameterStreams[i],
true,
- batchedStreamLengths[i], useStreamLengths);
- batchedStreamLengths[i], useStreamLengths);
- } else {
- }
else {
- /*否则的话,就用batchedParameterStrings,也就是parameterValues来填充参数位置。在循环中,这个操作是跟在staticSql后面的,因此就把第i个参数加到了第i个staticSql段中。参考前面的staticSql的例子,发现当循环结束的时候,原始sql语句最后一个”?”之前的sql语句就拼成了正确的语句了。
- /*否则的话,就用batchedParameterStrings,也就是parameterValues来填充参数位置。在循环中,这个操作是跟在staticSql后面的,因此就把第i个参数加到了第i个staticSql段中。参考前面的staticSql的例子,发现当循环结束的时候,原始sql语句最后一个”?”之前的sql语句就拼成了正确的语句了。
- */
- */
- sendPacket.writeBytesNoNull(batchedParameterStrings[i]);
- sendPacket.writeBytesNoNull(batchedParameterStrings[i]);
- }
- }
- }
- }
- /*由于在原始的包含问号的sql语句中,在最后一个”?”后面可能还有order by等语句,因此staticSql数组中的元素个数一定比参数的个数多1,所以这里把staticSqlString中的最后一段sql语句放入sendPacket中。
- /*由于在原始的包含问号的sql语句中,在最后一个”?”后面可能还有order by等语句,因此staticSql数组中的元素个数一定比参数的个数多1,所以这里把staticSqlString中的最后一段sql语句放入sendPacket中。
- */
- */
- sendPacket
- sendPacket
- .writeBytesNoNull(this.staticSqlStrings[batchedParameterStrings.length]);
- .writeBytesNoNull(
this.staticSqlStrings[batchedParameterStrings.length]);
- return sendPacket;
- return sendPacket;
- }
- }
- protected Buffer fillSendPacket(byte[][] batchedParameterStrings,
- protected Buffer fillSendPacket(byte[][] batchedParameterStrings,
- InputStream[] batchedParameterStreams, boolean[] batchedIsStream,
- InputStream[] batchedParameterStreams,
boolean[] batchedIsStream,
- int[] batchedStreamLengths) throws SQLException {
- int[] batchedStreamLengths) throws SQLException {
- // 从connection的IO中得到发送数据包,首先清空其中的数据
- // 从connection的IO中得到发送数据包,首先清空其中的数据
- Buffer sendPacket = this.connection.getIO().getSharedSendPacket();
- Buffer sendPacket =
this.connection.getIO().getSharedSendPacket();
- sendPacket.clear();
- sendPacket.clear();
- /* 数据包的第一位为一个操作标识符(MysqlDefs.QUERY),表示驱动向服务器发送的连接的操作信号,包括有QUERY, PING, RELOAD, SHUTDOWN, PROCESS_INFO, QUIT, SLEEP等等,这个操作信号并不是针对sql语句操作而言的CRUD操作,从提供的几种参数来看,这个操作是针对服务器的一个操作。一般而言,使用到的都是MysqlDefs.QUERY,表示发送的是要执行sql语句的操作。
- /* 数据包的第一位为一个操作标识符(MysqlDefs.QUERY),表示驱动向服务器发送的连接的操作信号,包括有QUERY, PING, RELOAD, SHUTDOWN, PROCESS_INFO, QUIT, SLEEP等等,这个操作信号并不是针对sql语句操作而言的CRUD操作,从提供的几种参数来看,这个操作是针对服务器的一个操作。一般而言,使用到的都是MysqlDefs.QUERY,表示发送的是要执行sql语句的操作。
- */
- */
- sendPacket.writeByte((byte) MysqlDefs.QUERY);
- sendPacket.writeByte((
byte) MysqlDefs.QUERY);
- boolean useStreamLengths = this.connection
- boolean useStreamLengths = this.connection
- .getUseStreamLengthsInPrepStmts();
- .getUseStreamLengthsInPrepStmts();
- int ensurePacketSize = 0;
- int ensurePacketSize = 0;
- for (int i = 0; i < batchedParameterStrings.length; i++) {
- for (int i = 0; i < batchedParameterStrings.length; i++) {
- if (batchedIsStream[i] && useStreamLengths) {
- if (batchedIsStream[i] && useStreamLengths) {
- ensurePacketSize += batchedStreamLengths[i];
- ensurePacketSize += batchedStreamLengths[i];
- }
- }
- }
- }
- /* 判断这个sendPacket的byte buffer够不够大,不够大的话,按照1.25倍来扩充buffer
- /* 判断这个sendPacket的byte buffer够不够大,不够大的话,按照1.25倍来扩充buffer
- */
- */
- if (ensurePacketSize != 0) {
- if (ensurePacketSize != 0) {
- sendPacket.ensureCapacity(ensurePacketSize);
- sendPacket.ensureCapacity(ensurePacketSize);
- }
- }
- /* 遍历所有的参数。在prepareStatement阶段的new ParseInfo()中,驱动曾经对sql语句进行过分割,如果含有以问号标识的参数占位符的话,就记录下这个占位符的位置,依据这个位置把sql分割成多段,放在了一个名为staticSql的字符串中。这里就开始把sql语句进行拼装,把staticSql和parameterValues进行组合,放在操作符的后面。
- /* 遍历所有的参数。在prepareStatement阶段的new ParseInfo()中,驱动曾经对sql语句进行过分割,如果含有以问号标识的参数占位符的话,就记录下这个占位符的位置,依据这个位置把sql分割成多段,放在了一个名为staticSql的字符串中。这里就开始把sql语句进行拼装,把staticSql和parameterValues进行组合,放在操作符的后面。
- */
- */
- for (int i = 0; i < batchedParameterStrings.length; i++) {
- for (int i = 0; i < batchedParameterStrings.length; i++) {
- /* batchedParameterStrings就是parameterValues,batchedParameterStreams就是parameterStreams,如果两者都为null,说明在参数的设置过程中出了错,立即抛出错误。
- /* batchedParameterStrings就是parameterValues,batchedParameterStreams就是parameterStreams,如果两者都为null,说明在参数的设置过程中出了错,立即抛出错误。
- */
- */
- if ((batchedParameterStrings[i] == null)
- if ((batchedParameterStrings[i] == null)
- && (batchedParameterStreams[i] == null)) {
- && (batchedParameterStreams[i] ==
null)) {
- throw SQLError.createSQLException(Messages
- throw SQLError.createSQLException(Messages
- .getString("PreparedStatement.40") //$NON-NLS-1$
- .getString(
"PreparedStatement.40") //$NON-NLS-1$
- + (i + 1), SQLError.SQL_STATE_WRONG_NO_OF_PARAMETERS);
- + (i +
1), SQLError.SQL_STATE_WRONG_NO_OF_PARAMETERS);
- }
- }
- /*在sendPacket中加入staticSql数组中的元素,就是分割出来的没有”?”的sql语句,并把字符串转换成byte。
- /*在sendPacket中加入staticSql数组中的元素,就是分割出来的没有”?”的sql语句,并把字符串转换成byte。
- */
- */
- sendPacket.writeBytesNoNull(this.staticSqlStrings[i]);
- sendPacket.writeBytesNoNull(
this.staticSqlStrings[i]);
- /* batchedIsStream就是isStream,如果参数是通过CallableStatement传递进来的话,batchedIsStream[i]==true,就用batchedParameterStreams中的值填充到问号占的参数位置中去。
- /* batchedIsStream就是isStream,如果参数是通过CallableStatement传递进来的话,batchedIsStream[i]==true,就用batchedParameterStreams中的值填充到问号占的参数位置中去。
- */
- */
- if (batchedIsStream[i]) {
- if (batchedIsStream[i]) {
- streamToBytes(sendPacket, batchedParameterStreams[i], true,
- streamToBytes(sendPacket, batchedParameterStreams[i],
true,
- batchedStreamLengths[i], useStreamLengths);
- batchedStreamLengths[i], useStreamLengths);
- } else {
- }
else {
- /*否则的话,就用batchedParameterStrings,也就是parameterValues来填充参数位置。在循环中,这个操作是跟在staticSql后面的,因此就把第i个参数加到了第i个staticSql段中。参考前面的staticSql的例子,发现当循环结束的时候,原始sql语句最后一个”?”之前的sql语句就拼成了正确的语句了。
- /*否则的话,就用batchedParameterStrings,也就是parameterValues来填充参数位置。在循环中,这个操作是跟在staticSql后面的,因此就把第i个参数加到了第i个staticSql段中。参考前面的staticSql的例子,发现当循环结束的时候,原始sql语句最后一个”?”之前的sql语句就拼成了正确的语句了。
- */
- */
- sendPacket.writeBytesNoNull(batchedParameterStrings[i]);
- sendPacket.writeBytesNoNull(batchedParameterStrings[i]);
- }
- }
- }
- }
- /*由于在原始的包含问号的sql语句中,在最后一个”?”后面可能还有order by等语句,因此staticSql数组中的元素个数一定比参数的个数多1,所以这里把staticSqlString中的最后一段sql语句放入sendPacket中。
- /*由于在原始的包含问号的sql语句中,在最后一个”?”后面可能还有order by等语句,因此staticSql数组中的元素个数一定比参数的个数多1,所以这里把staticSqlString中的最后一段sql语句放入sendPacket中。
- */
- */
- sendPacket
- sendPacket
- .writeBytesNoNull(this.staticSqlStrings[batchedParameterStrings.length]);
- .writeBytesNoNull(
this.staticSqlStrings[batchedParameterStrings.length]);
- return sendPacket;
- return sendPacket;
- }
- }
但是并不是说PreparedStatement除了给我们带来高效率就没有其他作用了,它还有非常好的其他作用: i. 极大的提高了sql语句的安全性,可以防止sql注入 ii. 代码结构清晰,易于理解,便于维护。 i. 设置当前数据库连接,并调用connection的execSQL来执行查询.然后继续把要发送的查询包,就是之间组装完毕的sendPacket传递进入MysqlIO的sqlQueryDirect()。 ii. 接下来就要往server端发送我们的查询指令啦(sendCommand),说到发送数据,不禁要问,如果这个待发送的数据包超级大,难道每次都是一次性的发送吗?当然不是,如果数据包超过规定的最大值的话,就会把它分割一下,分成几个不超过最大值的数据包来发送。 所以可以肯定,在分割的过程中,除了最后一个数据包,其他数据包的大小都是一样的。那就这样的数据包直接切割了进行发送的话,假如现在被分成了三个数据包,发送给mysql server,服务器怎么知道那个包是第一个呢,它读数据该从什么地方开始读呢,这都是问题,所以,我们要给每个数据包的前面加上一点属性标志,这个标志一共占了4个byte。从代码①处开始就是头标识位的设置。第一位表示数据包的开始位置,就是数据存放的起始位置,一般都设置为0,就是从第一个位置开始。第二和第三个字节标识了这个数据包的大小,注意的是,这个大小是出去标识的4个字节的大小,对于非最后一个数据包来说,这个大小都是一样的,就是splitSize,也就是maxThreeBytes,它的值是255 * 255 * 255。 最后一个字节中存放的就是数据包的编号了,从0开始递增。 在标识位设置完毕之后,就可以把255 * 255 * 255大小的数据从我们准备好的待发送数据包中copy出来了,注意,前4位已经是标识位了,所以应该从第五个位置开始copy数据。 在数据包都装配完毕之后,就可以往socket的outputSteam中发送数据了。接下来的事情,就是由mysql服务器接收数据并解析,执行查询了。
- Java代码
- Java代码
- while (len >= this.maxThreeBytes) {
- while
(len >= this.maxThreeBytes) {
- this.packetSequence++;
- this.packetSequence++;
- /*设置包的开始位置*/
- /*设置包的开始位置*/
- headerPacket.setPosition(0);
- headerPacket.setPosition(
0);
- /*设置这个数据包的大小,splitSize=255 * 255 * 255*/
- /*设置这个数据包的大小,splitSize=255 * 255 * 255*/
- headerPacket.writeLongInt(splitSize);
- headerPacket.writeLongInt(splitSize);
- /*设置数据包的序号*/
- /*设置数据包的序号*/
- headerPacket.writeByte(this.packetSequence);
- headerPacket.writeByte(
this.packetSequence);
- /*origPacketBytes就是sendPacket,所以这里就是把sendPacket中大小为255 * 255 * 255的数据放入headPacket中,headerPacketBytes是headPacket的byte buffer*/
- /*origPacketBytes就是sendPacket,所以这里就是把sendPacket中大小为255 * 255 * 255的数据放入headPacket中,headerPacketBytes是headPacket的byte buffer*/
- System.arraycopy(origPacketBytes, originalPacketPos,
- System.arraycopy(origPacketBytes, originalPacketPos,
- headerPacketBytes, 4, splitSize);
- headerPacketBytes,
4, splitSize);
- int packetLen = splitSize + HEADER_LENGTH;
- int packetLen = splitSize + HEADER_LENGTH;
- if (!this.useCompression) {
- if (!this.useCompression) {
- this.mysqlOutput.write(headerPacketBytes, 0,
- this.mysqlOutput.write(headerPacketBytes, 0,
- splitSize + HEADER_LENGTH);
- splitSize + HEADER_LENGTH);
- this.mysqlOutput.flush();
- this.mysqlOutput.flush();
- } else {
- }
else {
- Buffer packetToSend;
- Buffer packetToSend;
- headerPacket.setPosition(0);
- headerPacket.setPosition(
0);
- packetToSend = compressPacket(headerPacket, HEADER_LENGTH,
- packetToSend = compressPacket(headerPacket, HEADER_LENGTH,
- splitSize, HEADER_LENGTH);
- splitSize, HEADER_LENGTH);
- packetLen = packetToSend.getPosition();
- packetLen = packetToSend.getPosition();
- /*往IO的output stream中写数据*/
- /*往IO的output stream中写数据*/
- this.mysqlOutput.write(packetToSend.getByteBuffer(), 0,
- this.mysqlOutput.write(packetToSend.getByteBuffer(), 0,
- packetLen);
- packetLen);
- this.mysqlOutput.flush();
- this.mysqlOutput.flush();
- }
- }
- originalPacketPos += splitSize;
- originalPacketPos += splitSize;
- len -= splitSize;
- len -= splitSize;
- }
- }
- while (len >= this.maxThreeBytes) {
- while (len >= this.maxThreeBytes) {
- this.packetSequence++;
- this.packetSequence++;
- /*设置包的开始位置*/
- /*设置包的开始位置*/
- ① headerPacket.setPosition(0);
- ① headerPacket.setPosition(
0);
- /*设置这个数据包的大小,splitSize=255 * 255 * 255*/
- /*设置这个数据包的大小,splitSize=255 * 255 * 255*/
- headerPacket.writeLongInt(splitSize);
- headerPacket.writeLongInt(splitSize);
- /*设置数据包的序号*/
- /*设置数据包的序号*/
- headerPacket.writeByte(this.packetSequence);
- headerPacket.writeByte(
this.packetSequence);
- /*origPacketBytes就是sendPacket,所以这里就是把sendPacket中大小为255 * 255 * 255的数据放入headPacket中,headerPacketBytes是headPacket的byte buffer*/
- /*origPacketBytes就是sendPacket,所以这里就是把sendPacket中大小为255 * 255 * 255的数据放入headPacket中,headerPacketBytes是headPacket的byte buffer*/
- System.arraycopy(origPacketBytes, originalPacketPos,
- System.arraycopy(origPacketBytes, originalPacketPos,
- headerPacketBytes, 4, splitSize);
- headerPacketBytes,
4, splitSize);
- int packetLen = splitSize + HEADER_LENGTH;
- int packetLen = splitSize + HEADER_LENGTH;
- if (!this.useCompression) {
- if (!this.useCompression) {
- this.mysqlOutput.write(headerPacketBytes, 0,
- this.mysqlOutput.write(headerPacketBytes, 0,
- splitSize + HEADER_LENGTH);
- splitSize + HEADER_LENGTH);
- this.mysqlOutput.flush();
- this.mysqlOutput.flush();
- } else {
- }
else {
- Buffer packetToSend;
- Buffer packetToSend;
- headerPacket.setPosition(0);
- headerPacket.setPosition(
0);
- packetToSend = compressPacket(headerPacket, HEADER_LENGTH,
- packetToSend = compressPacket(headerPacket, HEADER_LENGTH,
- splitSize, HEADER_LENGTH);
- splitSize, HEADER_LENGTH);
- packetLen = packetToSend.getPosition();
- packetLen = packetToSend.getPosition();
- /*往IO的output stream中写数据*/
- /*往IO的output stream中写数据*/
- this.mysqlOutput.write(packetToSend.getByteBuffer(), 0,
- this.mysqlOutput.write(packetToSend.getByteBuffer(), 0,
- packetLen);
- packetLen);
- this.mysqlOutput.flush();
- this.mysqlOutput.flush();
- }
- }
- originalPacketPos += splitSize;
- originalPacketPos += splitSize;
- len -= splitSize;
- len -= splitSize;
- }
- }
1、首先我们看到要的到一个数据库连接,得到数据库连接这部分放在DBHelper类中的getConnection方法中实现。Class.forName("com.mysql.jdbc.Driver");用来加载mysql的jdbc驱动。
Mysql的Driver类实现了java.sql.Driver接口,任何数据库提供商的驱动类都必须实现这个接口。在DriverManager类中使用的都是接口Driver类型的驱动,也就是说驱动的使用不依赖于具体的实现,这无疑给我们的使用带来很大的方便。如果需要换用其他的数据库的话,只需要把Class.forName()中的参数换掉就可以了,可以说是非常方便的。
在com.mysql.jdbc.Driver类中,除了构造方法,就是一个static的方法体,它调用了DriverManager的registerDriver()方法,这个方法会加载所有系统提供的驱动,并把它们都假如到具体的驱动类中,当然现在就是mysql的Driver。在这里我们第一次看到了DriverManager类,这个类中提供了jdbc连接的主要操作,创建连接就是在这里完成的,可以说这是一个管理驱动的工具类。
注册驱动首先就是初始化,然后把驱动的信息封装一下放进一个叫做DriverInfo的驱动信息类中,最后放入一个驱动的集合中。初始化工作主要是完成所有驱动的加载。
第二步就要根据url和用户名,密码来获得数据库的连接了。url一般都是这样的格式:jdbc:protocol://host_name:port/db_name?parameter_name=param_value。开头部分的protocal是对应于不同的数据库提供商的协议,例如mysql的就是mysql。
DriverManager中有重载了四个getConnection(),因为我们有用户名和密码,就把用户和密码存放在Properties中,最后进入终极getConnection(),如下:
Initialize()简直无所不在,DriverManager中只要使用driver之前,就要检查一下有没有初始化,非常小心。然后开始遍历所有驱动,直到找到一个可用的驱动,用这个驱动来取得一个数据库连接,最后返回这个连接。当然,这是正常的情况,从上面我们可以看到,程序中对异常的处理很仔细。如果连接失败,会记录抛出的第一个异常信息,如果没有找到合适的驱动,就抛出一个08001的错误。
现在重点就是假如一切正常,就应该从driver.connect()返回一个数据库连接,所以我们来看看如何通过url提供的数据库。
很简洁的写法,就是新建了一个mysql的connection,host, port, database给它传进入,让它去连接就对了,props里面是些什么东西呢,就是把url拆解一下,什么host,什么数据库名,然后url后面的一股脑的参数,再把用户跟密码也都放进入,反正就是所有的连接数据都放进入了。
在com.mysql.jdbc.Connection的构造方法里面,会先做一些连接的初始化操作,例如创建PreparedStatement的cache,创建日志等等。然后就进入createNewIO()来建立连接了。
从时序图中可以看到,createNewIO()就是新建了一个com.mysql.jdbc.MysqlIO,利用com.mysql.jdbc.StandardSocketFactory来创建一个socket。然后就由这个mySqlIO来与MySql服务器进行握手(doHandshake()),这个doHandshake主要用来初始化与Mysql server的连接,负责登陆服务器和处理连接错误。在其中会分析所连接的mysql server的版本,根据不同的版本以及是否使用SSL加密数据都有不同的处理方式,并把要传输给数据库server的数据都放在一个叫做packet的buffer中,调用send()方法往outputStream中写入要发送的数据。
一旦有了一个statement,就可以通过执行statement.executeQuery()并通过ResultSet对象读出查询结果(如果查询有返回结果的话)。
创建statement的方法一般都有重载,我们看下面的prepareStatement:
public java.sql.PreparedStatement prepareStatement(String sql,
如果没有指定resultSetType和resultSetConcurrency的话,会给它们默认设置一个值。
prepareStatement的创建如下图所示:
在new ParseInfo中,会对这个sql语句进行分析,例如看看这个sql是什么语句;有没有limit条件语句,还有一个重要的工作,如果使用的是PreparedStatement来准备sql语句的话,会在这里把sql语句进行分解。我们知道PreparedStatement对象在实例化创建时就被设置了一个sql语句,使用PreparedStatement对象执行的sql语句在首次发送到数据库时,sql语句就会被编译,这样当多次执行同一个sql语句时,mysql就不用每次都去编译sql语句了。
这个sql语句如果包含参数的话,可以用问号(”?”)来为参数进行占位,而不需要立即为参数赋值,而在语句执行之前,必须通过适当的set***()来为问号处的参数赋值。New ParseInfo()中,包含了参数的sql语句就会被分解为多段,放在staticSql中,以便需要设置参数时定位参数的位置。假如sql语句为“select * from adv where id = ? and name = ?”的话,那么staticSql中的元素就是3个,staticSql[3]={ ”select * from adv where id = ”, ” and name = ” , ””}。注意数组中最后一个元素,在这个例子中是””,因为我的例子里面最后一个就是”?”,如果sql语句是这样的“select * from adv where id = ? and name = ? order by id”的话,staticSql就变成是这样的{ ”select * from adv where id = ”, ” and name = ” , ” order by id”}。
设置sql语句中的参数值。
对于参数而言,PreparedStatement中一共有四个变量来储存它们,分别是
a) byte[][] parameterValues:参数转换为byte后的值。
b) InputStream[] parameterStreams:只有在调用存储过程batch(CallableStatement)的时候才会用到它,否则它的数组中的值设置为null。
c) boolean[] isStream:是否为stream的标志,如果调用的是preparedStatement,isStream数组中的值均为false,若调用的是CallableStatement,则均设置为true。
d) boolean[] isNull:标识参数是否为空,设置为false。
这四个变量的一维数组的大小都是一样的,sql语句中有几个待set的参数(几个问号),一维的元素个数就是多大。
a) 检查preparedStatement是否已关闭,如果已关闭,抛出一个SQLError.SQL_STATE_CONNECTION_NOT_OPEN的错误。
b) fillSendPacket:创建数据包,其中包含了要发送到服务器的查询。
这个sendPacket就是mysql驱动要发送给数据库服务器的协议数据。一般来说,协议的数据格式有两种,一种是二进制流的格式,还有一种是文本的格式。文本协议就是基本上人可以直接阅读的协议,一般是用ascii字符集,也有用utf8格式的,优点是便于理解,读起来方便,扩充容易,缺点就是解析的时候比较麻烦,而且占用的空间比较大,冗余的数据比较多。二进制格式话,就需要服务器与客户端协议规定固定的数据结构,哪个位置放什么数据,如果单独看协议内容的话,很难理解数据含义,优点就是数据量小,解析的时候只要根据固定位置的值就能知道具体标识什么意义。
在这里使用的是二进制流的格式,也就是说协议中的数据格式是固定的,而且都要转换成二进制。格式为第一个byte标识操作信号,后面开始就是完整的sql语句的二进制流,请看下面的代码分析。
假如sql语句为“select * from adv where id = ?”的话,这个sendPacket中第一个byte的值就是3(MysqlDefs.QUERY的int值),后面接着的就是填充了参数值的完整的sql语句字符串(例如:select * from adv where id = 1)转换成的byte格式。
于是,我们看到,好像sql语句在这里就已经不是带”?”的preparedStatement,而是在驱动里面把参数替代到”?”中,再把完整的sql语句发送给mysql server来编译,那么尽管只是参数改变,但对于mysql server来说,每次都是新的sql语句,都要进行编译的。这与我们之前一直理解的PreparedStatement完全不一样。照理来说,应该把带”?”的sql语句发送给数据库server,由mysql server来编译这个带”?”的sql语句,然后用实际的参数来替代”?”,这样才是实现了sql语句只编译一次的效果。sql语句预编译的功能取决于server端,oracle就是支持sql预编译的。
所以说,从mysql驱动的PreparedStatement里面,好像我们并没有看到mysql支持预编译功能的证据。(实际测试也表明,如果server没有预编译功能的话,PreparedStatement和Statement的效率几乎一样,甚至当使用次数不多的时候,PreparedStatement比Statement还要慢一些)。
2009-07-02增加(感谢gembler):其实,在mysql5上的版本是支持预编译sql功能的。我用的驱动是5.0.6的,在com.mysql.jdbc.Connection中有一个参数useServerPreparedStmts,表明是否使用预编译功能,所以如果把useServerPreparedStmts置为true的话,mysql驱动可以通过PreparedStatement的子类ServerPreparedStatement来实现真正的PreparedStatement的功能。在这个类的serverExecute方法里面,就负责告诉server,用现在提供的参数来动态绑定到编译好的sql语句上。所以说,ServerPreparedStatement才是真正实现了所谓prepare statement。
c) 设置当前的数据库名,并把之前的数据库名记录下来,在查询完成之后还要恢复原状。
d) 检查一下之前是否有缓存的数据,如果不久之前执行过这个查询,并且缓存了数据的话,就直接从缓存中取出。
e) 如果sql查询没有限制条件的话,为其设置默认的返回行数,若preparedStatement中已经设置了maxRows的话,就使用它。
f) executeInternal:执行查询。
iii. 通过readAllResults方法读取查询结果。这个读取的过程与发送过程相反,如果接收到的数据包有多个的话,通过IO不断读取,并根据第packet第4个位置上的序号来组装这些packet。然后把读到的数据组装成resultSet中的rowData,这个结果就是我们要的查询结果了。
结合下面的executeQuery的时序图再理一下思路就更清楚了。
至此,把resultSet一步步的返回给dao,接下来的过程,就是从resultSet中取出rowData,组合成我们自己需要的对象数据了。
总结一下,经过这次对mysql驱动的探索,我发现了更多关于mysql的底层细节,对于以后分析问题解决问题有很大帮助,当然,这里面还有很多细节文中没有写。另外一个就是对于PreparedStatement有了重新的认识,有些东西往往都是想当然得出来的结论,真相还是要靠实践来发现。
发表评论
-
关于在MyEclipse9中导入ExtJs校验报错的处理办法
2012-05-01 14:23 1181myeclipse9.0运行速度比之前的版本提高了少,用起 ... -
final finally finallize 区别
2012-04-27 17:28 1060final定义的变量的值不能改变,定义的方法不能被覆盖, ... -
23种设计模式的形象比喻
2012-04-21 14:42 10311、ABSTRACT FACTORY— ... -
MAP,SET,LIST,等JAVA中集合解析(了解)
2012-04-19 09:46 994在JAVA的util包中有两个所有集合的父接口Collecti ... -
关于数据库范式及关系的总结
2012-04-16 16:25 1153关于数据库范式及关系的总结 PowerDesi ... -
快速排序算法
2012-04-13 22:01 1040快速排序是目前使用可 ... -
Java 排序算法
2012-04-13 21:38 1244package Sort; /** * 排 ... -
java JDBC连接不同的数据库写法
2012-04-12 15:16 947一、DB2 Class.forName(& ... -
mysql命令
2012-04-06 19:28 788http://www.blogjava.net/JafeLee ... -
java枚举类型
2012-04-05 15:13 762public class TestEnum { /* ... -
枚举类型的单例模式(java)
2012-04-05 15:07 27451Inspired by Effective Java. ... -
struts2输入校验
2012-03-29 08:47 0一. 手动输入完成校验 1.普通的处理方式:只需要在actio ... -
MyEclipse6.5的速度性能优化大提速
2012-03-11 13:12 1043MyEclipse是Eclipse的插件,也是一款功能强 ... -
JUnit, HttpUnit, Castus, JMeter之间的区别
2011-12-20 16:24 1835·单元测试:JUnit (http://www.junit.o ... -
org.hibernate.exception.DataException: Could not execute JDBC batch update
2011-12-06 16:19 2974做项目时忘记写下这句了request.setCharacter ... -
java.lang.NoClassDefFoundError: Could not initialize class util.HibernateUtil
2011-12-05 23:04 21531java.lang.NoClassDefFoundErr ... -
Hibernate3.1插入中文乱码解决办法
2011-12-04 19:44 8551、修改my.ini,修改default-charac ... -
Exception in thread "main" java.lang.NoClassDefFoundError: javax/persistence/Cac
2011-12-04 19:14 962javax.persistence.Cacheable 是 ... -
抽象类和接口之间的区别
2011-11-13 07:30 864抽象类和接口之间的区别: 一个类可以实现任意多个接口 ... -
HtmlParser初步研究
2011-11-12 19:33 852HtmlParser初步研究 by l ...
相关推荐
在特定版本的MySQL JDBC驱动(如mysql-connector-java 8.0.12)中,存在一个反序列化漏洞,攻击者可以利用这个漏洞执行任意代码,从而对系统造成严重威胁。 #### JDBC简介 JDBC提供了一组接口和类,使得Java应用...
### JDBC驱动加载分析 #### 背景与概念 在Java开发中,JDBC (Java Database Connectivity) 是一种用于执行SQL语句的标准Java API,它由一组用Java语言编写的类和接口组成。JDBC提供了Java应用程序与各种类型的关系...
DBEAVER的MySQL 5.7驱动包含了与该版本兼容的JDBC驱动,JDBC(Java Database Connectivity)是Java语言连接数据库的标准接口,使得DBEAVER可以通过Java代码与MySQL数据库进行通信。 3. **驱动安装与配置**:解压...
"mysql java" 暗示内容可能涉及如何在Java程序中使用MySQL数据库,而"mysql jdbc" 明确指出是通过JDBC驱动来实现这一连接。 **描述分析:** 描述提到“这个适用于初学者”,意味着提供的内容适合对JDBC和MySQL...
在“mysql-driver:mysql jdbc驱动源码阅读”这个主题中,我们将深入探讨该驱动的工作原理、关键组件以及源码分析。 1. **JDBC驱动类型**: JDBC驱动分为四种类型:Type 1、Type 2、Type 3 和 Type 4。MySQL ...
本资源提供的是MySQL 8.0的免费安装包,允许用户在自己的计算机上搭建和管理MySQL服务器,无论你是开发人员、系统管理员还是数据分析师,都能从中受益。 MySQL 8.0带来了许多显著的改进和新功能,旨在提高性能、可...
本文将详细介绍MySQL JDBC驱动和Druid连接池的相关知识点。 首先,MySQL JDBC驱动是Java应用程序连接MySQL数据库的桥梁。JDBC(Java Database Connectivity)是Java语言中用来规范客户端程序如何访问数据库的应用...
### 常用JDBC驱动名字和URL列表 在Java应用程序与各种数据库之间建立连接时,JDBC(Java Database Connectivity)是一种广泛采用的标准技术。它允许开发者通过标准API与多种类型的数据库进行交互,从而实现数据的...
检查并更新任何可能与驱动版本相关的配置,例如`jdbc.url`、`jdbc.driverClassName`等。 6. **性能优化**: MySQL 8.x引入了许多性能改进和新特性,如InnoDB存储引擎的增强、JSON支持、窗口函数等。利用这些新功能...
MySQL Connector/J遵循JDBC(Java Database Connectivity)标准,使得Java开发者能够方便地在他们的应用中访问和操作MySQL数据库。5.1.13版是这个驱动的一个特定版本,可能包含了对当时MySQL 5.1服务器版本的兼容性...
Java使用jdbc连接MySQL数据库实例分析 Java语言是目前最流行的编程语言之一,广泛应用于Web开发、Android开发、企业级应用等领域。数据库是存储和管理大量数据的核心组件,而MySQL是最流行的开源关系数据库管理系统...
2. **mysql-connector-java-5.1.37.jar**:这是 MySQL 数据库的 JDBC 驱动程序,用于在 Java 应用程序中连接到 MySQL 数据库。在 Hive 中,如果要将数据导出到 MySQL 或从 MySQL 导入数据,这个驱动是必需的。版本 ...
MySQL的Java连接器(JDBC驱动)是连接Java应用程序与MySQL数据库的关键组件,它提供了Java程序员访问MySQL数据库的接口。在本案例中,我们关注的是"mysql-connector-java-5.1.10.rar",这是一个包含MySQL JDBC驱动的...
本资源包含了MySQL的JDBC驱动jar包——mysql-connector-java-5.1.37-bin.jar,以及相关的源码,这对于Java开发者来说是非常有价值的。 首先,让我们详细了解`mysql-connector-java-5.1.37-bin.jar`。这是一个Java ...
这种方式是将 MySQL 的 JDBC 驱动(如 mysql-connector-java-5.1.13-bin.jar)直接放入 Tomcat 安装目录下的 lib 文件夹中。例如: ``` C:\Program Files\Apache Software Foundation\Tomcat 7.0\lib\mysql-...
2. 在Java项目中引入MySQL的JDBC驱动包(例如mysql-connector-java-8.0.16.jar),创建相应的包结构。 3. 定义`Student`实体类,用于表示学生信息。 4. 设计`StudentDao`接口,定义数据库操作方法,如`...
这个驱动包中的MySQL驱动可能包含JDBC驱动类库(通常以.jar文件形式存在),如`mysql-connector-java.jar`,它是连接Kettle到MySQL数据库的关键。 接下来是Oracle驱动。Oracle数据库是全球最广泛使用的商业数据库之...
1. 加载驱动:使用`Class.forName()`方法加载MySQL JDBC驱动。 ```java Class.forName("com.mysql.cj.jdbc.Driver"); ``` 2. 创建连接:通过`DriverManager.getConnection()`方法创建数据库连接。 ```java String ...
3. **连接配置**:使用Informix JDBC驱动和DbVisualizer时,需要配置数据库连接参数,包括URL、用户名、密码、驱动类名等,以便DbVisualizer能正确连接到Informix实例。 4. **SQL查询与操作**:DbVisualizer的SQL...
通过分析和运行这个实例,初学者可以更好地理解JDBC的工作原理,并掌握如何在实际项目中应用这些概念。 总之,JDBC是Java开发人员与数据库交互的核心工具,理解和熟练使用JDBC对于任何Java开发者来说都是至关重要的...