JDBC规范四-Statement详解 jdbc规范四-statement详解解析
yuyutoo 2024-10-12 00:49 12 浏览 0 评论
本节我们来学习JDBC API中比较重要的部分—Statement接口及它的子接口PreparedStatement和CallableStatement。Statement接口中定义了执行SQL语句的方法,这些方法不支持参数输入,PreparedStatement接口中增加了设置SQL参数的方法,CallableStatement接口继承自PreparedStatement,在此基础上增加了调用存储过程以及检索存储过程调用结果的方法。
1.java.sql.Statement接口
Statement是JDBC API操作数据库的核心接口,具体的实现由JDBC驱动来完成。Statement对象的创建比较简单,需要调用Connection对象的createStatement()方法,例如:
// 获取Connection对象
Connection connection = DriverManager.getConnection("jdbc:hsqldb:mem:mybatis",
"sa", "");
Statement statement = connection.createStatement();
在应用程序中,每个Connection对象可以同时创建多个Statement对象,例如;
// 获取Connection对象
Connection connection = DriverManager.getConnection("jdbc:hsqldb:mem:mybatis",
"sa", "");
Statement statement1 = connection.createStatement();
Statement statement2 = connection.createStatement();
此外,Connection接口中还提供了几个重载的createStatement()方法,用于通过Statement对象指定ResultSet(结果集)的属性,例如:
Connection conn = dataSource.getConnection(user,passwd);Statement stmt = conn.createStatement(
ResultSet.TYPE SCROLL INSENSITIVEResultSet.CONCUR UPDATABLE
ResultSet.HOLD CURSORS OVER COMMIT):
上面的代码中,我们创建了一个Statement对象,通过参数指定该Statement对象创建的ResultSet对象是可滚动的,而且是可以修改的,当修改提交时ResultSet不会被关闭。关于ResultSet的更多细节会在后面的章节中介绍。
Statement的主要作用是与数据库进行交互,该接口中定义了一些数据库操作以及检索SQL执行结果相关的方法,具体如下:
#批量执行 SQL
void addBatch(String sgl)
void clearBatch()
int[] executeBatch()
#执行未知 SQL语句
boolean execute(String sgl)
execute(String sql,int autoGeneratedKeys)boolean
boolean execute(String sgl,int门 columnIndexes)
boolean execute(String sql, String[] columnNames)#执行查询语句
ResultSet executeQuery(String sql)#执行更新语句,包括UPDATE、DELETE、INSERT
intexecuteUpdate(String sql)
int executeUpdate(String sgl,int autoGeneratedKeys)int executeUpdate(String sgl,int[] columnIndexes)
int executeUpdate(String sgl,String[] columnIndexes)
int executeUpdate(String sgl,String[] columnNames)
#SOL 执行结果处理
long getLargeUpdateCount()
ResultSet getResultSet()
int getUpdateCount()
boolean getMoreResults()
boolean getMoreResults(int current)
ResultSet getGeneratedKeys()
#JDBC4.2新增,数据量大于 Integer.MAX VALUE 时使用
long[] executeLargeBatch()
long executeLargeUpdate(String sql
long executeLargeUpdate(String sql,int autoGeneratedKeys)
longexecuteLargeUpdate(Stringsq1,int[] columnIndexes)
long executeLargeUpdate(Stringsg1,String[] columnNames)
#取消 SOL 执行,需要数据库和驱动支持
void cancel()
#关闭statement对象
void close()
void closeOnCompletion()
Statement接口中提供的与数据库交互的方法比较多,具体调用哪个方法取决于SQL语句的类型。
如果使用Statement执行一条查询语句,并返回一个结果集(ResultSet对象),则可以调用executeQuery()方法。
如果SQL语句是一个返回更新数量的DML语句,则需要调用executeUpdate()方法,该方法有几个重载的方法,下详细介绍。
int executeUpdate(String sql):执行一个UPDATE、INSERT或者DELETE语句,返回更新数量。
int executeUpdate(String sql, int autoGeneratedKeys):执行一个UPDATE、INSERT或者DELETE语句。当SQL语句是INSERT语句时,autoGeneratedKeys参数用于指定自动生成的键是否能够被检索,取值为Statement.RETURN_GENERATED_KEYS或Statement.NO_GENERATED_KEYS。当参数值为Statement.RETURN_GENERATED_KEYS时,INSERT语句自动生成的键能够被检索。当我们向数据库中插入一条记录,希望获取这条记录的自增主键时,可以调用该方法,指定第二个参数值为Statement.RETURN_GENERATED_KEYS。
int executeUpdate(String sql, int[] columnIndexes):执行一个UPDATE、INSERT或者DELETE语句,通过columnIndexes参数告诉驱动程序哪些列中自动生成的键可以用于检索。columnIndexes数组用于指定目标表中列的索引,这些列中自动生成的键必须能够被检索。如果SQL语句不是INSERT语句,columnIndexes参数将会被忽略。
int executeUpdate(String sql, String[]columnNames):这个方法的作用和executeUpdate(String sql, int[]columnIndexes)相同,不同的是columnNames参数是一个String数组,通过字段名的方式指定哪些字段中自动生成的键能够被检索。如果SQL语句不是INSERT语句,columnNames参数就会被忽略。
注意如果数据库支持返回的更新数量大于Integer.MAX_VALUE,则需要调用executeLargeUpdate()方法。
当我们在执行数据库操作之前,若不确定SQL语句的类型,则可以调用excute()方法。该方法也有几个重载的方法,分别说明如下。
boolean execute(String sql):执行一个SQL语句,通过返回值判断SQL类型,当返回值为true时,说明SQL语句为SELECT语句,可以通过Statement接口中的getResultSet()方法获取查询结果集;否则为UPDATE、INSERT或者DELETE语句,可以通过Statement接口中的getUpdateCount()方法获取影响的行数。
boolean execute(String sql, int autoGeneratedKeys):该方法通过autoGeneratedKeys参数(只对INSERT语句有效)指定INSERT语句自动生成的键是否能够被检索。
boolean execute(String sql, String[]columnNames):columnNames参数是一个String数组,通过字段名的方式指定哪些字段中自动生成的键能够被检索。如果SQL语句不是INSERT语句,则columnNames参数会被忽略。
注意当数据库支持返回影响的行数大于Integer.MAX_VALUE时,需要使用getLargeUpdateCount()方法。
另外,execute()方法可能返回多个结果。我们可以通过Statement对象的getMoreResults()方法获取下一个结果,当getMoreResults()方法的返回值为true时,说明下一个结果为ResultSet对象;当返回值为false时,说明下一个结果为影响行数,或者没有更多结果。
默认情况下,每次调用getMoreResults()方法都会关闭上一次调用getResultSet()方法返回的ResultSet对象。但是,我们可以通过重载getMoreResults()方法的参数指定是否关闭ResultSet 对象。
Statement接口中定义了3个常量可以用作getMoreResults()的参数,具体如下。
CLOSE_CURRENT_RESULT:表明当返回下一个ResultSet对象时,当前ResultSet对象应该关闭。
KEEP_CURRENT_RESULT:表明当返回下一个ResultSet对象时,当前ResultSet对象不关闭。
CLOSE_ALL_RESULTS:表明当返回下一个ResultSet对象时,当前所有未关闭的ResultSet对象都关闭。
如果当前结果是影响行数,而不是ResultSet对象,则getMoreResults()方法的参数将会被忽略。为了确定JDBC驱动是否支持通过getMoreResults()方法获取下一个结果,我们可以调用DatabaseMetaData接口提供的supportsMultipleOpenResults()方法,DatabaseMetaData的相关细节将会在后面的章节中介绍。
除此之外,Statement接口中还提供了几个方法,用于批量执行SQL语句,分别为:
void addBatch(String sql):把一条SQL语句添加到批量执行的SQL列表中。
void clearBatch():清空批量执行的SQL列表。
int[]executeBatch():批量地执行SQL列表中的语句。
Statement接口中除了提供操作数据库相关的方法外,还提供了一系列属性相关的方法,这些方法用于设置或获取Statement相关的属性,代码如下:
#Statement 属性相关
Connection getConnection()
int getFetchDirection()
int getFetchSize()
ResultSet getGeneratedKeys()
int getMaxFieldSize()
int getMaxRows()
boolean getMoreResults()
boolean getMoreResults(int current)
int getQueryTimeout()
int getResultSetConcurrency()
int getResultSetHoldability()
int getResultSetType()
boolean isClosed()
booleanisCloseOnCompletion()
boolean isPoolable()
void setCursorName(String name)
void setEscapeProcessing(boolean enable)
void setFetchDirection(int direction)
void setFetchSize(int rows)
void setLargeMaxRows(long max)
void setMaxFieldSize(int max)
void setMaxRows(int max)
void setPoolable(boolean poolable)
void setQueryTimeout(int seconds)
2.java.sql.PreparedStatement接口
PreparedStatement接口继承自Statement接口,在Statement接口的基础上增加了参数占位符功能。PreparedStatement接口中增加了一些方法,可以为占位符设置值。PreparedStatement的实例表示可以被预编译的SQL语句,执行一次后,后续多次执行时效率会比较高。使用PreparedStatement实例执行SQL语句时,可以使用“?”作为参数占位符,然后使用PreparedStatement接口中提供的方法为占位符设置参数值。
PreparedStatement对象的创建比较简单,与Statement类似,只需要调用Connection对象的prepareStatement()方法。与创建Statement对象不同的是,prepareStatement()方法需要提供一个SQL语句作为参数,例如:
// 获取Connection对象
Connection connection = dataSource.getConnection();
PreparedStatement stmt = connection.prepareStatement("insert into " +
"user(create_time, name, password, phone, nick_name) " +
"values(?,?,?,?,?);");
stmt.setString(1,"2010-10-24 10:20:30");
stmt.setString(2,"User1");
stmt.setString(3,"test");
stmt.setString(4,"18700001111");
stmt.setString(5,"User1");
前面的章节中有提到过,使用createStatement()方法创建Statement对象时,可以通过参数指定ResultSet的特性。与createStatement()方法类似,prepareStatement()也可以通过重载的方法指定ResultSet的特性.
PreparedStatement接口中定义了一系列的Setter方法,用于为SQL语句中的占位符赋值,这些Setter方法名称遵循set<Type>格式,其中Type为数据类型。例如,setString()方法用于为参数占位符设置一个字符串类型的值。这些Setter方法一般都有两个参数,第一个参数为int类型,表示参数占位符的位置(从1开始);第二个参数为占位符指定的值。
需要注意的是,在使用PreparedStatement对象执行SQL语句之前必须为每个参数占位符设置对应的值,否则调用executeQuery()、executeUpdate()或execute()等方法时会抛出SQLException异常。
PreparedStatement对象设置的参数在执行后不能被重置,需要显式地调用clearParameters()方法清除先前设置的值,再为参数重新设置值即可。
注意 在使用PreparedStatement对象执行SQL时,JDBC驱动通过setAsciiStream()、setBinaryStream()、setCharacterStream()、setNCharacterStream()或setUnicodeStream()等方法读取参数占位符设置的值。这些参数值必须在下一次执行SQL时重置掉,否则将会抛出SQLException异常。
对于一个给定的Statement对象,在execute()、executeQuery()、executeUpdate()、executeBatch()或clearParameters()方法调用之前,如果占位符已经使用setXXX()方法设置值,应用程序不可以再次调用setXXX()方法修改已经设置的值。但是应用程序可以在execute()、executeQuery()、executeUpdate()、executeBatch()或clearParameters()方法调用后,再次调用setXXX()方法覆盖先前设置的值。不遵循这一约束可能会导致不可预知的结果。
我们在使用setXXX()方法为参数占位符设置值时存在一个数据转换过程。setXXX()方法的参数为Java数据类型,需要转换为JDBC类型(java.sql.Types中定义的SQL类型),这一过程由JDBC驱动来完成。Java类型与JDBC类型之间的对应关系如表所示。
PreparedStatement接口中提供了一个setObject()方法,可以将Java类型转换为JDBC类型。该方法可以接收三个参数,第一个参数为占位符位置,第二个参数为Java对象,第三个参数是要转换成的JDBC类型。如果Java对象与JDBC类型不兼容,就会抛出SQLException异常。
下面是使用setObject()方法将Java中的Integer类型转换为JDBC中的SHORT类型的案例,具体代码如下:
Integer value = new Integer(15);
ps.setObject(1, value, java.sql.Types.SHORT);
另外,setObject()方法可以只接收两个参数,不用指定JDBC类型。这种情况下,JDBC驱动会按照表2-2中的映射关系将Java类型隐式地转换为对应的JDBC类型,例如:
Integer value= new Integer(15);// Integer 类型会转换为java.sql.Types.INTEGER
ps.setObject(1, value);
PreparedStatement接口中提供了一个setNull()方法,可以将占位符参数设置为JDBC的NULL。该方法接收两个参数,第一个参数为占位符的位置,第二个参数为JDBC类型。该方法的语法格式如下:
ps.setNul1(2, java.sql.Types.VARCHAR)
如果接收Java对象的setXXX()方法参数为null,则该参数的占位符被设置为JDBC的NULL。
JDBC API中提供了一个ParameterMetaData接口,用于描述PreparedStatement对象的参数信息,包括参数个数、参数类型等。PreparedStatement接口中提供了一个getParameterMetaData()方法,用于获取ParameterMetaData实例。下面是使用ParameterMetaData获取参数信息的案例,代码如下:
public void testJdbc() {
try {
// 创建DataSource实例
DataSourceFactory dsf = new UnpooledDataSourceFactory();
Properties properties = new Properties();
InputStream configStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("database.properties");
properties.load(configStream);
dsf.setProperties(properties);
DataSource dataSource = dsf.getDataSource();
// 获取Connection对象
Connection connection = dataSource.getConnection();
PreparedStatement stmt = connection.prepareStatement("insert into " +
"user(create_time, name, password, phone, nick_name) " +
"values(?,?,?,?,?);");
stmt.setString(1,"2010-10-24 10:20:30");
stmt.setString(2,"User1");
stmt.setString(3,"test");
stmt.setString(4,"18700001111");
stmt.setString(5,"User1");
ParameterMetaData pmd = stmt.getParameterMetaData();
for(int i = 1; i <= pmd.getParameterCount(); i++) {
String typeName = pmd.getParameterTypeName(i);
String className = pmd.getParameterClassName(i);
System.out.println("第" + i + "个参数," + "typeName:" + typeName + ", className:" + className);
}
stmt.execute();
// 关闭连接
IOUtils.closeQuietly(stmt);
IOUtils.closeQuietly(connection);
} catch (Exception e) {
e.printStackTrace();
}
}
3.java.sql.CallableStatement接口
CallableStatement接口继承自PreparedStatement接口,在PreparedStatement的基础上增加了调用存储过程并检索调用结果的功能。与Statement、PreparedStatement一样,CallableStatement对象也是通过Connection对象创建的,我们只需要调用Connection对象的prepareCall()方法即可,例如:
CallableStatement cstmt = conn.prepareCall("call validate(?,?)")
CallableStatement对象可以使用3种类型的参数:IN、OUT和INOUT。可以将参数指定为序数参数或命名参数,必须为IN或INOUT参数的每个参数占位符设置一个值,必须为OUT或INOUT参数中的每个参数占位符调用registerOutParameter()方法。存储过程参数的数量、类型和属性可以使用DatabaseMetaData接口提供的getProcedureColumns()方法获取。需要注意的是,使用setXXX()方法为参数占位符设置值时,下标必须从1开始。语句中的字面量参数值不会增加参数占位符的序数值,例如:
CallableStatement cstmt = con.prepareCall("(CALL PROC(?"Literal Value",?)}");
cstmt.setString(1,"First");
cstmt.setString(2,"Third");
命名参数可以用来指定特定的参数,这在存储过程有多个给定默认值的参数时特别有用,命名参数可以用于为那些没有默认值的参数设置值,参数名称可以通过DatabaseMetaData对象的getProcedureColumns()方法返回的COLUMN_NAME字段获取。例如,在下面的案例中,COMPLEX_PROC存储过程可以接收10个参数,但是只有第1个和第5个参数(PARAM_1和PARAM_5)需要设置值。
CallableStatement cstmt = con.prepareCall("{CALL COMPLEX PROC(?,?)}");
cstmt.setString("PARAM 1","Price");
cstmt.setFloat("PARAM 5",150.25);
CallableStatement接口中新增了一些额外的方法允许参数通过名称注册和检索。
DatabaseMetaData接口中提供了supportsNamedParameters()方法,用于判断JDBC驱动是否支持指定命名参数。
对于IN参数的设置,调用CallableStatement接口中提供的setXXX()方法即可;但是对于OUT和INOUT参数,在CallableStatement执行之前,必须为每个参数调用CallableStatement接口中提供的registerOutParameter()方法,例如:
CallableStatement cstmt = conn.prepareCall("(CALL GET NAME AND NUMBER(?, ?))");
cstmt.registerOutParameter(1,java.sql.Types.STRING);
cstmt.registerOutParameter(2,java.sql.Types.FLOAT);
cstmt.execute() ;
//取 OUT 参数值
String name = cstmt.getString(1);
float number = cstmt.getFloat(2);
与Statement、PreparedStatement类似,CallableStatement也是使用executeQuery()、executeUpdate()、execute()等方法执行存储过程的调用,返回结果可能是ResultSet对象或者影响的行数,存储过程调用结果的处理与Statement对象执行SQL结果的处理过程类似,这里就不重复介绍了。
4.获取自增长的键值
目前大多数数据库都支持自增长主键,当向表中插入数据时,数据库引擎可以自动生成自增长主键。Statement接口中提供了getGeneratedKeys()方法,用于获取数据库自动生成的值,该方法返回一个ResultSet对象,我们可以从ResultSet对象中获取数据库中所有自增长的键值。
Statement接口中的execute()、executeUpdate()和Connection接口的prepareStatement()方法都可以接收一个可选的参数,该参数用于指定由数据库生成的值是否可以被检索,例如:
public void testJdbc() {
try {
Class.forName("org.hsqldb.jdbcDriver");
// 获取Connection对象
Connection conn = DriverManager.getConnection("jdbc:hsqldb:mem:mybatis",
"sa", "");
Statement stmt = conn.createStatement();
String sql = "insert into user(create_time, name, password, phone, nick_name) " +
"values('2010-10-24 10:20:30','User1','test','18700001111','User1');";
stmt.executeUpdate(sql, Statement.RETURN_GENERATED_KEYS);
ResultSet genKeys = stmt.getGeneratedKeys();
if(genKeys.next()) {
System.out.println("自增长主键:" + genKeys.getInt(1));
}
IOUtils.closeQuietly(stmt);
IOUtils.closeQuietly(conn);
} catch (Exception e) {
e.printStackTrace();
}
}
另外,Statement接口中还提供了execute()、executeUpdate()重载方法,能够通过下标或者字段名指定哪些字段中自动生成的值可以被检索,例如:
public void testJdbc2() {
try {
Class.forName("org.hsqldb.jdbcDriver");
// 获取Connection对象
Connection conn = DriverManager.getConnection("jdbc:hsqldb:mem:mybatis",
"sa", "");
Statement stmt = conn.createStatement();
// 指定主键
String[] columnNames = new String[]{"id"};
String sql = "insert into user(create_time, name, password, phone, nick_name) " +
"values('2010-10-24 10:20:30','User1','test','18700001111','User1');";
stmt.executeUpdate(sql);
sql = "insert into user(create_time, name, password, phone, nick_name) " +
"values('2010-10-24 10:20:30','User1','test','18700001111','User1');";
stmt.executeUpdate(sql, columnNames);
ResultSet genKeys = stmt.getGeneratedKeys();
if(genKeys.next()) {
System.out.println("自增长主键:" + genKeys.getInt(1));
}
IOUtils.closeQuietly(stmt);
IOUtils.closeQuietly(conn);
} catch (Exception e) {
e.printStackTrace();
}
}
相关推荐
- Python操作Word文档神器:python-docx库从入门到精通
-
Python操作Word文档神器:python-docx库从入门到精通动动小手,点击关注...
- Python 函数调用从入门到精通:超详细定义解析与实战指南 附案例
-
一、函数基础:定义与调用的核心逻辑定义:函数是将重复或相关的代码块封装成可复用的单元,通过函数名和参数实现特定功能。它是Python模块化编程的基础,能提高代码复用性和可读性。定义语法:...
- 等这么长时间Python背记手册终于来了,入门到精通(视频400集)
-
本文毫无套路!真诚分享!前言:无论是学习任何一门语言,基础知识一定要扎实,基础功非常的重要,找一个有丰富编程经验的老师或者师兄带着你会少走很多弯路,你的进步速度也会快很多,无论我们学习的目的是什么,...
- 图解Python编程:从入门到精通系列教程(附全套速查表)
-
引言本系列教程展开讲解Python编程语言,Python是一门开源免费、通用型的脚本编程语言,它上手简单,功能强大,它也是互联网最热门的编程语言之一。Python生态丰富,库(模块)极其丰富,这使...
- Python入门教程(非常详细)从零基础入门到精通,看完这一篇就够
-
本书是Python经典实例解析,采用基于实例的方法编写,每个实例都会解决具体的问题和难题。主要内容有:数字、字符串和元组,语句与语法,函数定义,列表、集、字典,用户输入和输出等内置数据结构,类和对象,...
- Python函数全解析:从入门到精通,一文搞定!
-
1.为什么要用函数?函数的作用:封装代码,提高复用性,减少重复,提高可读性。...
- Python中的单例模式:从入门到精通
-
Python中的单例模式:从入门到精通引言单例模式是一种常用的软件设计模式,它保证了一个类只有一个实例,并提供一个全局访问点。这种模式通常用于那些需要频繁创建和销毁的对象,比如日志对象、线程池、缓存等...
- 【Python王者归来】手把手教你,Python从入门到精通!
-
用800个程序实例、5万行代码手把手教你,Python从入门到精通!...
- Python从零基础入门到精通:一个月就够了
-
如果想从零基础到入门,能够全职学习(自学),那么一个月足够了。...
- Python 从入门到精通:一个月就够了
-
要知道,一个月是一段很长的时间。如果每天坚持用6-7小时来做一件事,你会有意想不到的收获。作为初学者,第一个月的月目标应该是这样的:熟悉基本概念(变量,条件,列表,循环,函数)练习超过30个编...
- Python零基础到精通,这8个入门技巧让你少走弯路,7天速通编程!
-
Python学习就像玩积木,从最基础的块开始,一步步搭建出复杂的作品。我记得刚开始学Python时也是一头雾水,走了不少弯路。现在回头看,其实掌握几个核心概念,就能快速入门这门编程语言。来聊聊怎么用最...
- 神仙级python入门教程(非常详细),从0到精通,从看这篇开始!
-
python入门虽然简单,很多新手依然卡在基础安装阶段,大部分教程对一些基础内容都是一带而过,好多新手朋友,对一些基础知识常常一知半解,需要在网上查询很久。...
- Python类从入门到精通,一篇就够!
-
一、Python类是什么?大家在生活中应该都见过汽车吧,每一辆真实存在、能在路上跑的汽车,都可以看作是一个“对象”。那这些汽车是怎么生产出来的呢?其实,在生产之前,汽车公司都会先设计一个详细的蓝图...
- 学习Python从入门到精通:30天足够了,这才是python基础的天花板
-
当年2w买的全套python教程用不着了,现在送给有缘人,不要钱,一个月教你从入门到精通1、本套视频共487集,本套视频共分4季...
- 30天Python 入门到精通(3天学会python)
-
以下是一个为期30天的Python入门到精通学习课程,专为零基础新手设计。课程从基础语法开始,逐步深入到面向对象编程、数据处理,最后实现运行简单的大语言模型(如基于HuggingFace...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- mybatis plus (70)
- scheduledtask (71)
- css滚动条 (60)
- java学生成绩管理系统 (59)
- 结构体数组 (69)
- databasemetadata (64)
- javastatic (68)
- jsp实用教程 (53)
- fontawesome (57)
- widget开发 (57)
- vb net教程 (62)
- hibernate 教程 (63)
- case语句 (57)
- svn连接 (74)
- directoryindex (69)
- session timeout (58)
- textbox换行 (67)
- extension_dir (64)
- linearlayout (58)
- vba高级教程 (75)
- iframe用法 (58)
- sqlparameter (59)
- trim函数 (59)
- flex布局 (63)
- contextloaderlistener (56)