首页 > 其他分享 >JDBC的执行流程

JDBC的执行流程

时间:2022-09-01 23:15:16浏览次数:53  
标签:语句 JDBC String 流程 SQL ResultSet sql 执行 数据库

目录

一、JDBC的层次结构

总体而言,JDBC包含以下几大角色 : Driver、DriverManager、Connection、Statement、ResultSet。这几大角色之间的层次关系如下图所示:

Connection:Driver 或者 DriverManager根据连接的url 和参数信息创建Connection实例,用来维持和数据库的数据通信,如果没有销毁或者调用close()对象,此对象和数据库的对象会一直保持连接;

Statement:Connection创建Statement对象,表示需要执行的sql语句或者存储过程;

ResultSet: 表示Statement执行完SQL语句后返回的结果集。

Connection角色

Connection表示与特定数据库的连接,可以获取到数据库的一些信息,这些信息包括:其表信息,应该支持的SQL语法,数据库内有什么存储过程,此链接功能的信息等等。

在一般实际使用情况下,我们关注的Connection的功能有以下几点:

1.创建可以执行sql语句或者存储过程的对象statement,用来和数据库进行交互;

比如,以下代码创建了几种不同类型的Statement:

			//加载Oracle数据库驱动
			Class.forName("oracle.jdbc.driver.OracleDriver");
			
			//根据特定的URL,返回可以接受此URL的数据库驱动对象
			Driver driver = DriverManager.getDriver(URL);
			
			//使用数据库驱动创建数据库连接Connection会话
			Connection connection = driver.connect(URL, props);
		    
			//创建静态的sql语句  Statement 对象来将 SQL 语句发送到数据库。
			Statement staticStatement= connection.createStatement();
 
			//创建CallableStatement 对象来调用数据库存储过程。
			CallableStatement callableStatement = connection.prepareCall(sqlString);
			
			//创建参数化的Statement对象
			PreparedStatement preparedStatement = connection.prepareStatement(sqlString);

2. 控制sql语句的事务;

Connection默认情况下,对于创建的statement执行的sql语句都是自动提交的,即在statement语句执行完后,自动执行commit操作,将结果影响到物理数据库。为了满足更好地事务控制需求,我们也可以手动地控制事务,手动地对statement 的sql语句执行进行提交(commit)或者回滚(rollback)。

下面通过一个简单的例子演示connection的事务控制:

			String sqlString="insert into tableName(column1,column2) values(value1,value2)";
			//加载Oracle数据库驱动
			Class.forName("oracle.jdbc.driver.OracleDriver");
			
			//根据特定的URL,返回可以接受此URL的数据库驱动对象
			Driver driver = DriverManager.getDriver(URL);
			
			//使用数据库驱动创建数据库连接Connection会话
		    connection = driver.connect(URL, props);
		    //使用自定义的事务,要设置connection不自动提交
		    connection.setAutoCommit(false);
		    
			//创建静态的sql语句  Statement 对象来将 SQL 语句发送到数据库。
			Statement staticStatement= connection.createStatement();
			try{
				//执行插入操作
				staticStatement.execute(sqlString);
                 // 和上面的connection等价,statement只有一个创建自身的connection的引用
				staticStatement.getConnection().commit();
			}catch(Exception e)
			{
				//有异常,则rollback
				staticStatement.getConnection().rollback();
			}

3.获取数据库连接的元数据,即数据库的整体综合信息。

具体DatabaseMetaData内包含了什么信息,请查看 JDK 的API对DatabaseMetaData的描述。

三、Statement

Statement 的功能在于根据传入的sql语句,将传入sql经过整理组合成数据库能够识别的sql语句。

(对于静态的sql语句,不需要整理组合;而对于预编译sql语句和批量语句,则需要整理),然后传递sql请求,之后会得到返回的结果。

也就是说Statement用来操作sql语句(增删改查),并返回相应结果对象。

对于查询sql,结果会以ResultSet的形式返回。

SQL分类

SQL语句可以分为增删改查(CRUD,Create,Read,Update,Delete)四种形式,JDBC 从对数据更新与否的角度上看,将上面的四种形式分为两类:查询类别和更新类别。即:

查询类别:select 语句

更新类别:Insert 、update、delete语句

执行SQL方法分类

ResultSet  executeQuery(String sql) 根据查询语句返回结果集。只能执行**select**语句。

int executeUpdate(String sql) 根据执行的DML(insert update delete)语句,返回受影响的行数。

boolean execute(String sql)  此方法可以执行任意sql语句。返回boolean值. 

	true:  执行select有查询的结果

	false:  执行insert, delete,update, 执行select没有查询的结果

对应地,Statement执行sql的几种形式:

1、对sql语句类型不进行区分,执行sql语句的方法

//执行给定的 SQL 语句,该语句可能返回多个结果。
execute(String sql)

如果是执行的sql是查询类型的select语句,此方法会返回true,需要自己再调用 statement.getResultSet() 方法来获取Resultset结果集

如果是执行的更新类的sql语句如 update,delete,insert语句,此方法会返回false,自己调用statement.getUpdateCount() 返回sql语句影响的行数

2、对查询类型的sql语句的执行方法

statement提供了executeQuery(String sql)方法支持此形式,定义如下

// 执行给定的 SQL 语句,该语句返回单个 ResultSet 对象。
executeQuery(String sql)          

3. 对更新类的sql语句 的执行方法

statement提供了executeUpdate(String sql)方法支持此形式,定义如下:

// 执行给定 SQL 语句,该语句可能为 INSERT、UPDATE 或 DELETE 语句,或者不返回任何内容的 SQL 语句(如 SQL DDL 语句)。
executeUpdate(String sql)          

4.批量sql的执行方法

有时候需要将一些sql语句一起提交给数据库,批量执行,statement提供了一些方法,对批量sql的支持:

# 将给定的 SQL 命令添加到此 Statement 对象的当前命令列表中。
addBatch(String sql)          
# 将一批命令提交给数据库来执行,如果全部命令执行成功,则返回更新计数组成的数组。
executeBatch()          

这里只讨论一般性的Statement,不包含其子接口PreparedStatement和CallableStatement

ParameterMetaData元数据信息

ParameterMetaData是由preparedStatement对象通过getParameterMetaData方法获取而来,ParameterMetaData可用于获取有关PreparedStatement对象和其预编译sql语句 中的一些信息. eg:参数个数,获取指定位置占位符的SQL类型

获得ParameterMetaData:

ParameterMetaData parameterMetaData =  preparedStatement.getParameterMetaData ()

ParameterMetaData相关的API

  • int getParameterCount(); 获得参数个数
  • int getParameterType(int param) 获取指定参数的SQL类型。 (注:MySQL不支持获取参数类型)
public class ParameterMetaTest {
    public static void main(String[] args) throws SQLException {
        String url = "jdbc:mysql://localhost:3306/jdbc";
        java.util.Properties info = new java.util.Properties();
        info.setProperty("user","root");
        info.setProperty("password","root");
        Connection connection = DriverManager.getConnection(url, info);
        String sql  = "select * from account where id = ? and name = ?";
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        preparedStatement.setInt(1,2);
        preparedStatement.setString(2,"ls");
        ParameterMetaData parameterMetaData = preparedStatement.getParameterMetaData();
        int parameterCount = parameterMetaData.getParameterCount();
        // 2
        System.out.println("获取得到预编译中的参数个数:"+parameterCount);
        String parameterClassName = parameterMetaData.getParameterClassName(1);
        // 下面的会报错
        System.out.println("获取得到参数类型:"+parameterClassName);
    }
}

控制台输出:

获取得到预编译中的参数个数:2
Exception in thread "main" java.sql.SQLException: Parameter metadata not available for the given statement

Statement分类

在使用数据库连接来创建得到对应的statement时,这个时候需要注意的是

如果是下面这种创建方式;

Statement statement = connection.createStatement();

那么就会出现SQL注入问题。

举个例子:

Connection connection = JdbcUtils.getConnection();
Statement statement = connection.createStatement();
String sql = "SELECT * FROM user WHERE username = '"+username+"' AND password = '"+password+"'";
ResultSet resultSet = statement.executeQuery(sql);
User user = null;

while (resultSet.next()){
    user  = new User(
        resultSet.getInt("id"),
        resultSet.getString("username"),
        resultSet.getString("password"),
        resultSet.getString("nickname")
    );
}

当输入的密码 ' or '' = ' , 发现永远登录成功。

所以应该来避免这种SQL注入问题。

所以有了PreparedStatement,这个statement对象的创建如下:

PreparedStatement preparedStatement = connection.prepareStatement(sql);

具体的实现是:

//b.创建预编译的SQL语句对象(SQL参数需要使用?占位)
String sql = "SELECT * FROM user WHERE username  = ? AND password = ?";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
//c.设置参数, 执行(还是executeQuery()和executeQUpdate(), 但是不需要再传入SQL语句, 上面已经传入了)
preparedStatement.setString(1,username);
preparedStatement.setString(2,password);
ResultSet resultSet = preparedStatement.executeQuery();

这样子就能够避免SQL注入问题了。现在用的都是这种方式来进行操作的。

四、ResultSet

当Statement查询sql执行后,会得到ResultSet对象,ResultSet对象是sql语句查询的结果,作为数据库结果的映射,其映射关系如下图所示。

ResultSet对从数据库返回的结果进行了封装,使用迭代器的模式逐条取出结果集中的记录。其遍历结果集的基本形式如下:

while(resultSet.next())
{
    //传入列名或者列索引获取记录中对应列的值
    resultSet.getXXX(param);
}

ResultSet游标的移动和定位

Resultset 提供了很多游标定位的方法,部分方法已经在下面列出:

# 将光标移动到此 ResultSet 对象的给定行编号。
absolute(int row)          
#  将光标移动到此 ResultSet 对象的末尾,正好位于最后一行之后。
afterLast()         
# 将光标移动到此 ResultSet 对象的开头,正好位于第一行之前。
beforeFirst()         
# 将光标移动到此 ResultSet 对象的第一行。
first()
# 获取当前行编号
getRow()
# 获取光标是否位于此 ResultSet 对象的最后一行之后
isAfterLast()
# 获取光标是否位于此 ResultSet 对象的第一行之前
isBeforeFirst()
#  获取光标是否位于此 ResultSet 对象的第一行
isFirst()
# 获取光标是否位于此 ResultSet 对象的最后一行
isLast()
# 将光标移动到此 ResultSet 对象的最后一行
last()
#  将光标从当前位置向前移一行
next()
# 将光标移动到此 ResultSet 对象的上一行
previous()
# 按相对行数(或正或负)移动光标
relative(int rows)

ResultSet结果集的元数据信息

元信息是指关于 ResultSet 对象中列的类型和属性信息的对象。

元数据: 描述数据的数据. mysql元数据: 用来定义数据库, 表 ,列信息的 eg: 参数的个数, 列的个数, 列的类型...

ResultSetMetaData是由ResultSet对象通过getMetaData方法获取而来,ResultSetMetaData可用于获取有关ResultSet对象中列的类型和属性的信息。

获得ResultSetMetaData:

ResultSetMetaData resultSetMetaData =  resultSet.getMetaData()

相关API展示:

  • getColumnCount(); 获取结果集中列项目的个数
  • getColumnName(int column); 获得数据指定列的列名
  • getColumnTypeName();获取指定列的SQL类型
  • getColumnClassName();获取指定列SQL类型对应于Java的类型

写个代码来进行演示:

public class ResultSetMetaTest {
    public static void main(String[] args) throws SQLException {
        String url = "jdbc:mysql://localhost:3306/jdbc";
        java.util.Properties info = new java.util.Properties();
        info.setProperty("user","root");
        info.setProperty("password","root");
        Connection connection = DriverManager.getConnection(url, info);
        PreparedStatement preparedStatement = connection.prepareStatement("select * from account");
        ResultSet resultSet = preparedStatement.executeQuery();
        ResultSetMetaData metaData = resultSet.getMetaData();
        System.out.println("列的个数:" + metaData.getColumnCount());
        for (int i = 0; i < metaData.getColumnCount(); i++) {
            String columnTypeName = metaData.getColumnTypeName(i + 1);
            String columnName = metaData.getColumnName(i + 1);
            int columnType = metaData.getColumnType(i + 1);
            String columnClassName = metaData.getColumnClassName(i + 1);
            System.out.println("对应的列的SQL类型是:"+columnTypeName);
            System.out.println("对应的列名是:"+columnName);
            System.out.println("列对应的sql类型:"+columnType);
            System.out.println("列对应的java类型是:"+columnClassName);
            System.out.println("---------------------------");
        }
        System.out.println("第1列的名字:"+metaData.getColumnName(1));
        System.out.println("第2列的数据库类型:"+metaData.getColumnTypeName(2));
        System.out.println("第2列的java类型:"+metaData.getColumnClassName(2));
        connection.close();
    }
}

控制台输出:

列的个数:3
对应的列的SQL类型是:INT
对应的列名是:id
列对应的sql类型:4
列对应的java类型是:java.lang.Integer
---------------------------
对应的列的SQL类型是:VARCHAR
对应的列名是:name
列对应的sql类型:12
列对应的java类型是:java.lang.String
---------------------------
对应的列的SQL类型是:DOUBLE
对应的列名是:money
列对应的sql类型:8
列对应的java类型是:java.lang.Double
---------------------------
第1列的名字:id
第2列的数据库类型:VARCHAR
第2列的java类型:java.lang.String

ResultSet.getXXX(param) 、ResultSet.updateXXX()的XXX问题

ResultSet接口常用API

  • boolean next();将光标从当前位置向下移动一行
  • int getInt(int colIndex)以int形式获取ResultSet结果集当前行指定列号值
  • int getInt(String colLabel)以int形式获取ResultSet结果集当前行指定列名值
  • float getFloat(int colIndex)以float形式获取ResultSet结果集当前行指定列号值
  • float getFloat(String colLabel)以float形式获取ResultSet结果集当前行指定列名值
  • String getString(int colIndex)以String 形式获取ResultSet结果集当前行指定列号值
  • String getString(String colLabel)以String形式获取ResultSet结果集当前行指定列名值
  • Date getDate(int columnIndex); 以Date 形式获取ResultSet结果集当前行指定列号值
  • Date getDate(String columnName);以Date形式获取ResultSet结果集当前行指定列名值
  • void close()关闭ResultSet 对象

因为在ResultSet中提供了大量的API设计,所以MySQL为了满足这种条件,也应该来提供对应的一些转换器来进行设置值。

JDBC中定义了数据库中的数据类型和java数据类型的映射,用于数据库和Java数据类型之间的转换。

在使用ResultSet去记录中的某一列值的时候,用户要根据数据库对应列的数据类型地应的java数据类型,否则的话有可能抛出异常。

下图定义了数据库和Java类型之间的映射:


五、JDBC工作的基本流程

一个基本的JDBC工作流程,分为以下几步:

1.加载特定数据库驱动器实现类,并注册驱动器(Driver会注册到DriverManager中);
  1. 根据特定的URL,返回可以接受此URL的数据库驱动对象Driver;
  2. 使用数据库驱动 Driver 创建数据库连接Connection会话;
  3. 使用 Connection对象创建 用于操作sql的Statement对象;
  4. statement对象执行 sql语句,返回结果ResultSet 对象;
  5. 处理ResultSet中的结果;
  6. 关闭连接,释放资源。

以下是一个简单的案例:

public class DBConnection {
 
	static final String  URL = "jdbc:oracle:thin:@127.0.0.1:1521:xe";
	static final String USER_NAME ="louluan";
	static final String PASSWORD = "123456";
	
	public static void main(String[] args) {
		connectionTest();
	}
	
	public static void connectionTest(){
		
		Connection connection = null;
		Statement statement = null;
		ResultSet resultSet = null;
		
		try {
			//1.加载类,并注册驱动器(Driver会注册到DriverManager中)
			
			//加载Oracle数据库驱动
			Class.forName("oracle.jdbc.driver.OracleDriver").newInstance();
			
			//2.根据特定的URL,返回可以接受此URL的数据库驱动对象
			Driver driver = DriverManager.getDriver(URL);
			Properties props = new Properties();
			props.put("user", USER_NAME);
			props.put("password", PASSWORD);
			
			//3.使用数据库驱动创建数据库连接Connection会话
			connection = driver.connect(URL, props);
			
			//4.获得Statement对象
			statement = connection.createStatement();
			//5.执行 sql语句,返回结果
			resultSet = statement.executeQuery("select * from hr.employees");
			//6.处理结果,取出数据
			while(resultSet.next())
			{
				System.out.println(resultSet.getString(2));
			}
			
			//7.关闭链接,释放资源
		} catch (ClassNotFoundException e) {
			System.out.println("加载Oracle类失败!");
			e.printStackTrace();
		} catch (SQLException e) {
			e.printStackTrace();
		} catch (InstantiationException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}finally{
			    //使用完成后管理链接,释放资源,释放顺序应该是: ResultSet ->Statement ->Connection
				try {
					resultSet.close();
				} catch (SQLException e) {
					e.printStackTrace();
				}
				
				try {
					statement.close();
				} catch (SQLException e) {
					e.printStackTrace();
				}
				
				try {
					connection.close();
				} catch (SQLException e) {
					e.printStackTrace();
				}
		}
	}
}

JDBC工作时序图

上述几个对象之间:DriverManager或者Driver创建Connection、Connection创建Statement、Statement又获得ResultSet,它们之间的交互序列图如下所示:

标签:语句,JDBC,String,流程,SQL,ResultSet,sql,执行,数据库
From: https://www.cnblogs.com/likeguang/p/16648138.html

相关文章

  • 管理员身份之执行msi安装包
    方式一:以管理员身份运行CMD:在CMD中进入到.msi文件的目录下执行命令:msiexec/packageXXX(.msi文件名) ......
  • MySQL Explain执行计划key_len详解(特意针对date和datetime详细测试说明)
    MySQLExplain执行计划key_len详解(特意针对date和datetime详细测试说明)我们在使用Explain查看SQL执行计划时,其中有一列为key_kenkey_len表示使用的索引长度,那么key_len......
  • ent裸SQL执行
    ent快速上手一文中介绍了如何在ent框架内使用ORM思想操作数据库,本文讲述下如何在ent框架下执行裸SQL语句。ent通过引入sql/execquery的featureflag来支持裸SQL执行,这样可......
  • 【Java面试】面试如何让面试官面的很爽,看完这道面试题,finally块一定会执行吗?
    “finally块一定会执行吗?”这是最近一个工作3年的小伙伴去面试的时候遇到的问题。你遇到这个问题会怎么回答呢?大家好,我是Mic,一个工作了14年的Java程序员对于这个问题,......
  • 认证流程总结
    1.用户的新增密码的加密存储:一般使用不可逆加密mango使用的是BCrypt(是一种加盐的不可逆加密方法)@Overridepublicvoidadd(Adminadmin){Stringpassword=BCrypt......
  • java通过jdbc连接hive并实时获取日志(转)
    转载:https://blog.csdn.net/weixin_43455443/article/details/1153439954、通过java代码连接hiveonspark,使用hive-jdbc引入pom文件<dependency><groupId>org.apache.......
  • Java流程控制
    1.输出/输入Java提供的输出包括:System.out.println() [换行]/ print() / printf(),其中printf()可以格式化输出;格式化输出使用System.out.printf(),通过使用占位符%?,p......
  • JDBC---初识
    《基本介绍》  当面对不同的数据库,如果直接用java操作数据库会使得对于不同的数据库有不同的方法,不统一 ......
  • vue方法中的方法怎么同步顺序执行_vue方法同步(顺序)执行:async/await使用 , 使用async搭
    vue方法中的方法怎么同步顺序执行_vue方法同步(顺序)执行:async/await使用项目中有一个地方需要获取到接口返回值之后根据返回值确定之后执行的步骤,使用async搭配await实......
  • 【工具类】Cocos 分帧执行函数
    版本:2.4.10参考:卡顿优化之卡顿原理全解析与如何快速定位到卡顿问题 一为啥要分帧一个游戏帧率是60时,每帧分配的执行时间是1秒/60=0.016666秒=16毫秒。当这一帧......