10.5 执行SQL语句的方式
一、使用executeLargeUpdate方法执行DDL和DML语句
statement提供了三个方法执行SQL语句,executeQuery()用于执行查询语句,executeLargeUpdate(或executeUpdate())用于执行DDL和DML语句,区别在于执行DDL语句后返回0,执行DML语句后的返回值为受影响的记录条数。
1.1 执行DDL语句,executeUpdate()返回0
下面程序示范了executeUpdate()方法创建数据表。该示例并没有把数据库连接信息写在程序中,而是使用一个mysql.ini文件(就是一个properties文件)来保存数据库的连接信息,这是比较成熟做法——当需要把程序从开发环境移植到生产环境时,无须修改源代码,只需要修改mysql.ini配置文件即可:
package section4; import java.util.*; import java.io.*; import java.sql.*; public class ExecuteDDL { private String driver;//数据库驱动 private String url;//数据库url private String user;//用户名 private String pass;//数据库密码 void initParam(String paramFile) //用于加载配置文件属性 throws Exception { //使用Properties类加载文件属性 Properties props=new Properties(); props.load(new FileInputStream(paramFile)); this.driver=props.getProperty("driver"); this.url=props.getProperty("url"); this.user=props.getProperty("user"); this.pass=props.getProperty("pass"); } int createTable(String sql) throws Exception { int n; //加载驱动 Class.forName(driver); try( //获取数据库连接 Connection conn = DriverManager.getConnection(url,user,pass); //创建Statement对象 Statement stmt=conn.createStatement()) { //执行DDL语句 n=stmt.executeUpdate(sql); } return n; } public static void main(String[] args) throws Exception { var exeDDL=new ExecuteDDL(); exeDDL.initParam("src\\mysql.ini"); int n= exeDDL.createTable( "create table jdbc_test(" + "jdbc_id int auto_increment primary key," + "jdbc_name varchar(255)," + "jdbc_desc text);"); System.out.println(n);//执行DDL语句,executeUpadte()返回0 System.out.println("建表成功"); } }
现在查看数据库select_test是否存在一个名叫jdbc的表格:
1.2 执行DML语句,返回受影响的记录条数
下面程序将在上面创建的jdbc_test表中插入几条记录,Mysql的扩展语法中,insert into可以插入多条记录:
package section4; import java.util.*; import java.io.*; import java.sql.*; public class ExecuteDML { private String driver; private String url; private String user; private String pass; public void initParam(String paramFile) throws Exception { //使用properties加载属性文件 var porps=new Properties(); porps.load(new FileInputStream(paramFile)); driver=porps.getProperty("driver"); url=porps.getProperty("url"); user=porps.getProperty("user"); pass=porps.getProperty("pass"); } public int insertData(String sql) throws Exception { //加载驱动 Class.forName(driver); try( //try()在代码块接受后会自行关闭打开的资源 //获取数据库连接 Connection conn=DriverManager.getConnection(url,user,pass); //创建Statement对象 Statement stmt =conn.createStatement() ) { //执行DML语句,返回受影响的记录条数 return stmt.executeUpdate(sql); } } public static void main(String[] args) { var exeDML=new ExecuteDML(); try { exeDML.initParam("src\\mysql.ini"); int result=exeDML.insertData("insert into jdbc_test(jdbc_name,jdbc_desc)" + "select s.student_name,t.teacher_name " + "from student_table s,teacher_table t " + "where s.java_teacher=t.teacher_id;"); System.out.println("-----系统中一个有" + result + "条记录受影响-----"); } catch (Exception e) { e.printStackTrace(); } } } 输出:-----系统中一个有6条记录受影响-----
看一下jdbc_test表是不是预期那样:
{{uploading-image-545228.png(uploading...)}}
二、使用execute方法执行SQL语句
Statement的execute()方法可以执行任何SQL语句,当它执行SQL语句时比较麻烦,通常没有必要使用execute()方法来执行SQL语句,使用executeQuery()或executeUpdata()方法更简单。但如果不清楚SQL语句的类型,则只能使用execute()方法来执行SQL语句。
使用execute()方法执行SQL语句的返回值只是Boolean值,它表明执行该SQL语句是否返回了ResultSet对象。Statement提供两个如下方法来获取执行结果:
(1)getResultSet():获取该Statement执行查询语句所返回的Statement对象。
(2)getUpdata():获取该Statement()执行DML语句所影响的记录条数。执行DDL语句返回0.
下面程序示范了使用Statement的execute()方法执行任意SQL语句,执行不同的SQL语句时,产生不同的输出。
package section4; import java.io.*; import java.sql.*; import java.util.Properties; public class ExecuteSQL { private String driver; private String url; private String user; private String pass; public void initParam(String paramFile) throws Exception { //使用properties加载属性文件 Properties props=new Properties(); props.load(new FileInputStream(paramFile)); driver=props.getProperty("driver"); url=props.getProperty("url"); user=props.getProperty("user"); pass=props.getProperty("pass"); } public void executesql(String sql) throws Exception { //加载驱动 Class.forName(driver); try( //获取数据库连接 Connection conn= DriverManager.getConnection(url,user,pass); //创建Statement来创建数据库连接 Statement stmt=conn.createStatement(); ) { //执行SQL语句,返回boolean值是否包含ResultSet Boolean hasResultSet=stmt.execute(sql); //如何执行结果有ResultSet结果集 if(hasResultSet) { try( ResultSet rs=stmt.getResultSet(); ) { //ResultSetMetaData是用于分析结果集的元数据接口 ResultSetMetaData rsmd=rs.getMetaData(); int columnCount=rsmd.getColumnCount(); //迭代输出ResultSet对象 while(rs.next()) { //依次输出每条记录 for(var i=0;i<columnCount;i++) { System.out.print(rs.getString(i+1)+"\t"); } System.out.print("\n"); } } } else { System.out.println("该语句受影响的记录条数为" +stmt.getUpdateCount()+"条"); } } } public static void main(String[] args) throws Exception { var es=new ExecuteSQL(); es.initParam("src\\mysql.ini"); System.out.println("-----执行删除表的DDL语句-------"); es.executesql("drop table if exists my_test"); System.out.println("----执行建表的DDL语句-------"); es.executesql("create table if not exists my_test(" + "test_id int auto_increment primary key," + "test_name varchar(255));"); System.out.println("-----执行插入数据的DML语句-----"); es.executesql("insert into my_test(test_name) " + "select student_name from student_table"); System.out.println("-----执行查询语句----"); es.executesql("select *from my_test"); } } -----执行删除表的DDL语句------- 该语句受影响的记录条数为0条 ----执行建表的DDL语句------- 该语句受影响的记录条数为0条 -----执行插入数据的DML语句----- 该语句受影响的记录条数为7条 -----执行查询语句---- 1 张三 2 张三 3 李四 4 王五 5 _王五 6 null 7 赵六 Process finished with exit code 0
三、使用PreparedStatement执行SQL语句
如果经常需要反复执行一条结构相似的SQL语句,例如下面两条SQL语句:
insert into student_table values(null,‘张三‘,1); insert into student_table values(null,‘李四‘,1);
对于这种情况可以使用占位符(?)参数的SQL语句来代表它:
insert into student_table values(null,?,?);
为了满足这种功能,JDBC提供了PreparedStatement接口,它是Statement接口的子接口,它可以预编译SQL语句,预编译后的SQL语句被存储在PrepareStatement对象中,然后可以使用该对象多次高效地执行语句。简而言之,使用PrepareStatement比使用Statement效率高。
创建PreparedStatement对象使用Connection的prepareStatement()方法,该方法需要传入一个SQL语句,该SQL字符串可以包含占位符参数:
//创建一个PreparedStatement() pstmt=conn.preparedStatement("insert into student_table values(null,?,?)");
PreparedStatement也提供了executeQuery()、executeUpdata()、execute()方法来执行SQL语句,只是这三个方法无须再传入SQL语句,因为PreparedStatement已存储了预编译的SQL语句。但是这些预编译语句使用了占位符,所以必须为其传入参数值,PreparedStatement提供了一系列的setXxx(int index,Xxx value)方法来传入参数值。
提示:如果已知预编译SQL语句各参数的类型,则使用相应的setXxx()方法传入参数;如果不清楚则使用setObject()方法传入参数,由PreparedStatement对象来负责类型转换。
下面示范使用Statement和PreparedStatement分别插入100条记录对比:
package section4; import java.io.FileInputStream; import java.sql.Connection; import java.sql.*; import java.util.Properties; public class PreparedStatementTest { private String driver; private String url; private String user; private String pass; public void initParam(String paramFile) throws Exception { Properties props=new Properties(); props.load(new FileInputStream(paramFile)); driver=props.getProperty("driver"); url=props.getProperty("url"); user=props.getProperty("user"); pass=props.getProperty("pass"); //加载驱动 Class.forName(driver); } public void insertUseStatement() throws Exception { long star=System.currentTimeMillis(); try( //获取数据连接 Connection conn= DriverManager.getConnection(url,user,pass); //使用Connection对象创建一个Statement对象 Statement stmt=conn.createStatement() ) { for(var i=0;i<100;i++) { stmt.executeUpdate("insert into student_table values(" + "null,‘姓名"+i+"‘ ,1)"); } System.out.println("使用Statement耗时:"+ (System.currentTimeMillis()-star)); } } public void insertUsePrepare() throws Exception { long star=System.currentTimeMillis(); try( Connection conn=DriverManager.getConnection(url,user,pass); PreparedStatement pstmt=conn.prepareStatement("insert into student_table values(null,?,1)"); ) { for(var i=0;i<100;i++) { pstmt.setString(1,"姓名"+i); pstmt.executeUpdate(); } System.out.println("使用PreparedStatement耗时:"+ (System.currentTimeMillis()-star)); } } public static void main(String[] args) throws Exception { var pt=new PreparedStatementTest(); pt.initParam("src\\mysql.ini"); pt.insertUseStatement(); pt.insertUsePrepare(); } } 使用Statement耗时:12136 使用PreparedStatement耗时:11881 Process finished with exit code 0
四、使用CallableStatement调用存储过程
4.1 首先在MySQL数据库创建一个简单的存储过程add_pro:
delimeter// create procedure add_pro(a int,b int,out sum int) begin set sum=a+b; end; //
上面的SQL语句将MySQL的结束符改为双斜线(//)。这样可以在创建存储过程中使用分隔号(MySQL默认使用分号作为语句结束的分隔符)。上面创建了名为add_pro的存储过程,该存储过程包含三个系数:a,b是传入参数,而sum使用out修饰。是传出参数。
4.2 创建CallableStatement对象
调用CallableStatement,可以通过Connection的prepareCall()方法创建CallableStatement对象,创建该对象需要传入调用存储过程的SQL语句。调用存储过程的SQL语句总是这样的格式:{call 过程吗(?,?,?...)},其中?作为存储过程参数作为占位符。例如下面创建了调用上面存储过程的CallableStatement对象。
//使用Connection来创建一个CallableStatement对象 cstmt=conn.prepareCall("{call add_pro(?,?,?)}");
4.3设置传入传出参数
所谓传入参数就是Java程序必须为这些参数传入值,可以通过CallableStatement的setXxx()方法为传入参数设置值;所谓传出参数就是Java程序可以通过该参数获取存储过程里的值,CallableStatement需要调用registerOutParameter()方法来注册该参数。
//注册CallableStatement的第三个参数是int类型 xstmt.registerOutParameter(3,Types.INTEGER);
经过上面步骤后,就可以调用CallableStatement的execute()方法来执行那个存储过程,执行结束后通过CallableStatement对象的getXxx(int index)方法来获取指定传出参数的值。下面程序示范了如何调用存储过程的值:
package section4; import java.io.FileInputStream; import java.sql.CallableStatement; import java.sql.Connection; import java.sql.DriverManager; import java.sql.Types; import java.util.Properties; public class CallableStatementTest { private String driver; private String url; private String user; private String pass; public void initParam(String paramFile) throws Exception { //使用Properties类加载文件属性 Properties props=new Properties(); props.load(new FileInputStream(paramFile)); driver=props.getProperty("driver"); url=props.getProperty("url"); user=props.getProperty("user"); pass=props.getProperty("pass"); } public void callProcedure() throws Exception { //加载驱动 Class.forName(driver); try( //创建连接 Connection conn=DriverManager.getConnection(url,user,pass); //获取Callable CallableStatement cstmt=conn.prepareCall("{call add_pro(?,?,?)}") ) { cstmt.setInt(1,4); cstmt.setInt(2,5); cstmt.registerOutParameter(3, Types.INTEGER); //执行存储过程 cstmt.execute(); //获取并存储过程传出参数的值 System.out.println("执行结果:"+cstmt.getInt(3)); } } public static void main(String[] args) throws Exception { var ct=new CallableStatementTest(); ct.initParam("src\\mysql.ini"); ct.callProcedure(); } }
上面程序中可以看到这个简单存储过程的执行结果,传入参数分别为4,5,执行方法后传出总和为9。