天天看点

JDBC规范四-Statement详解

作者:小雨在进步

本节我们来学习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类型之间的对应关系如表所示。

JDBC规范四-Statement详解

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();
        }
    }           
JDBC规范四-Statement详解

继续阅读