jdbc核心之PrepareStatement
来源:互联网 发布:傲剑九阴真经数据 编辑:程序博客网 时间:2024/06/06 07:38
介绍PrepareStatement之前,先用前面一直用的Statement来模拟一个登录的例子。
数据库中有表userinfo,字段为
- id:用户id,主键
- username:用户名
- password:密码
- account:账户余额
- email:邮箱
有条用户记录 1 jack 123456 5000 jack@qq.com
需求:输入用户名和密码进行登录
public class JDBCDemo1 { public static void main(String[] args) { Connection connection = null; Statement statement = null; ResultSet resultSet = null; try { //连接数据库 connection = DBUtil.getConnection(); Scanner scanner = new Scanner(System.in); System.out.println("输入用户名:"); String username = scanner.nextLine(); System.out.println("输入密码"); String password = scanner.nextLine(); //创建statement对象 statement = connection.createStatement(); //创建sql String sql = "SELECT * FROM USERINFO " + "WHERE username='"+username+"' AND password='"+password+"'"; //执行查询 resultSet = statement.executeQuery(sql); if(resultSet.next()){ //取余额 int account = resultSet.getInt("account"); System.out.println("登录成功,账户余额为:"+account); }else { System.out.println("账号或密码错误!"); } } catch (Exception e) { e.printStackTrace(); }finally { DBUtil.closeConnection(connection, statement, resultSet); } }}
注:DBUtil是自己写第一个工具类,能获取连接和关闭连接。代码如下:
public class DBUtil { private static String driver; private static String url; private static String username; private static String password; private static String maxactive; private static String maxwait; //数据库连接池 private static BasicDataSource dateSource; /** * ThreadLocal内部是一个Map * key是某个线程,而value是要保存的值。 * 这样可以将某个值绑定到一个线程上。随时通过 * 该线程获取这个值。 */ private static ThreadLocal<Connection> tl; static{ try { tl = new ThreadLocal<>(); //加载配置文件 Properties properties = new Properties(); properties.load(new FileInputStream(new File("D:\\测试\\jdbc\\src\\config.properties"))); //读取配置文件,初始化成员变量 driver = properties.getProperty("driver"); url = properties.getProperty("url"); username = properties.getProperty("username"); password = properties.getProperty("password"); maxactive = properties.getProperty("maxactive"); maxwait = properties.getProperty("maxwait"); //测试初始化是否成功 System.out.println(driver); System.out.println(url); System.out.println(username); System.out.println(password); System.out.println(maxactive); System.out.println(maxwait); dateSource = new BasicDataSource(); dateSource.setDriverClassName(driver); dateSource.setUrl(url); dateSource.setUsername(username); dateSource.setPassword(password); dateSource.setMaxActive(new Integer(maxactive)); dateSource.setMaxWait(new Integer(maxwait)); } catch (Exception e) { //静态方法块里面是不能抛出异常的 e.printStackTrace(); } } /* * 从连接池获取数据库连接 */ public static Connection getConnection() throws Exception{ /** * 哪个线程调用ThreadLocal的get方法,就 * 用这个线程作为key去threadlocal内部的 * Map中取出对应的value */ Connection connection = dateSource.getConnection(); tl.set(connection); return connection; } public static void closeConnection(){ Connection conn=tl.get(); if(conn!=null){ try { //这个时候用的是连接池的close,其他方法是oracle的, //连接池只是起了代理的做用 conn.close(); tl.remove(); } catch (SQLException e) { e.printStackTrace(); } } } /* * 关闭连接 */ public static void closeConnection(Connection connection,Statement statement,ResultSet resultSet){ if(resultSet != null){ try { resultSet.close(); } catch (SQLException e) { e.printStackTrace(); } } if(statement != null){ try { statement.close(); } catch (SQLException e) { e.printStackTrace(); } } if (connection != null) { try { connection.close(); } catch (SQLException e) { e.printStackTrace(); } } }}
属性配置文件如下
driver = oracle.jdbc.driver.OracleDriverurl = jdbc:oracle:thin:@localhost:1521:orclusername = SCOTTpassword = 123456maxactive = 5maxwait = 5000
当用户输入jack 123456的时候会登录成功显示余额(进行相关操作),但是其实你乱输入一个用户名,密码输入1’ OR ‘1’=’1结果还是会出来(会出来所有结果),因为OR的优先级低于and。1=1恒成立,这就出现安全问题,所以有些字符串不允许输入。这个就称为SQL注入攻击。
解决办法:使用PrepareStatement
两者比较:
- Statement通常用来执行静态SQL(SQL内容不含任何动态信息)语句。
Statement执行非静态SQL有两个问题:
1:若SQL语句是动态拼接的,那么会造成SQL注入攻击,如上面的情况。
2:若仅是数值变化,而SQL语义并没有变化时,批量执行多条SQL语句对数据库性能开销大。PreparedStatement专门用来执行含有动态内容的SQL语句,解决了Statement执行动态SQL的两个不足。
1:比如解决SQL注入问题:它会严格地将?表示的参数作为一个整体的参数传给DBMS,而不会发生SQL嵌套,也就是说?表示的仅仅是参数。对于上面也就是说1’ OR ‘1’=’1会当成一整个串(密码)来处理。
2:执行的是预编译SQL,将动态信息以”?”的形式占位。数据库在接收预编译SQL后,会生成执行计划,我们接下来的工作就是传入动态信息即可,多次执行也无非就是多次传入动态信息。但可以重用同一个计划了(这就好像java程序中定义方法,动态信息被定义为参数,多次调用方法传入不同参数即可,但是方法不用每次都重新定义)。从而解决了第二个问题。
PrepareStatement
用PrepareStatement重现上面的例子
public class JDBCDemo2 { public static void main(String[] args) { Connection connection = null; Statement statement = null; ResultSet resultSet = null; try { //连接数据库 connection = DBUtil.getConnection(); Scanner scanner = new Scanner(System.in); System.out.println("输入用户名:"); String username = scanner.nextLine(); System.out.println("输入密码"); String password = scanner.nextLine(); //1、创建sql String sql = "SELECT * FROM USERINFO " + "WHERE username= ? AND password= ?"; /** * 在创建 PS的同时需要预编译SQL语句传入,并通过 * Connection 传递给数据库了,这时候数据库会将 * 进行计划生成,但是并不会执行该计划,因为语义 * 虽然确定了,但是还缺少具体的数据。 */ //2、创建PrepareStatement PreparedStatement preparedStatement = connection.prepareStatement(sql); //3、设置参数 preparedStatement.setString(1, username); preparedStatement.setString(2, password); //4、执行查询 resultSet = preparedStatement.executeQuery(); if (resultSet.next()) { //取余额 int account = resultSet.getInt("account"); System.out.println("登录成功,账户余额为:"+account); }else { System.out.println("账号或密码错误!"); } } catch (Exception e) { e.printStackTrace(); }finally { DBUtil.closeConnection(connection, statement, resultSet); } }}
测试的时候,发现再输入jack 1’ OR ‘1’=’1就会提示用户名或者密码错误。
使用Statement和PrepareStatement进行批量数据处理比较。
/* * 使用prepareStatement批量插入数据 */public class JDBCDemo3 { public static void main(String[] args) { Connection connection = null; Statement statement = null; try { connection = DBUtil.getConnection(); //向userinfo中插入一万条数据 /** * 使用Statement */ /*statement=connection.createStatement(); System.out.println("开始插入:"+new Date()); for(int i=0;i<10000;i++){ String sql= "INSERT INTO userinfo " + "VALUES " + "(seq_userinfo_id.NEXTVAL,'"+i+"','1234','"+i+"','jdbc@qq.com')"; //每次执行,数据库都会生成一个执行计划! statement.executeUpdate(sql); } System.out.println("插入完毕:"+new Date());*/ //花了5秒钟 /* * 使用prepareStatement,不要像上面一样进行循环创建ps,超过200会出现递归 SQL 级别 1 出现错误 * ORA-01000: 超出打开游标的最大数,Oracle数据库中打开的游标最大数为 * 一定值,默认情况下是300,当代码到第二步时, 循环中一个PreparedStatement占用了一个数据库游标,执行的循环超过这个数时就会产生游标数目溢出错误 *第二步循环中每次执行完PreparedStatement, *都将PreparedStatement.close()下,释放掉这个资源就好了 */ System.out.println("prepareStatement开始插入:"+new Date()); String sql1= "INSERT INTO userinfo " + "VALUES " + "(seq_userinfo_id.NEXTVAL,?,'1234',?,'jdbc@qq.com')"; //只会生成一个执行计划 PreparedStatement preparedStatement = connection.prepareStatement(sql1); for(int i=0;i<10000;i++){ preparedStatement.setString(1, "scu"+i); preparedStatement.setString(2, "100"+i); //更新会自动提交事务,每次插入都提交会消耗时间 preparedStatement.executeUpdate(); } System.out.println("prepareStatement插入完毕:"+new Date());//3秒钟 } catch (Exception e) { e.printStackTrace(); }finally { DBUtil.closeConnection(); } }}
分别用Statement和PrepareStatement插入一万条数据。发现前者用了5秒后者用了3秒。所以在批量数据处理的时候PrepareStatement是能节约性能的。
元数据的获取
如果要获取查询结果的元数据,jdbc也是提供了相应的方法。如:查到多少个字段,字段叫什么名字等信息。
public class JDBCDemo6 { public static void main(String[] args) { Connection connection = null; Statement statement = null; ResultSet resultSet =null; try { connection=DBUtil.getConnection(); statement=connection.createStatement(); String sql="SELECT id,username FROM userinfo"; resultSet=statement.executeQuery(sql); //获取结果集中元数据对象 ResultSetMetaData metaData = resultSet.getMetaData(); //获取查询字段数 int columnCount = metaData.getColumnCount(); System.out.println("查询的自段数为:"+columnCount); //查看每个字段的名字,数据库下标从1开始 for(int i=1;i<=columnCount;i++){ String colName=metaData.getColumnName(i); System.out.println(colName); } } catch (Exception e) { e.printStackTrace(); }finally { DBUtil.closeConnection(); } }}
输出结果:查询的自段数为:2IDUSERNAME
- jdbc核心之PrepareStatement
- jdbc---------prepareStatement
- jdbc核心之事务
- JDBC-PrepareStatement批处理
- JDBC的PrepareStatement
- JDBC连接数据库 prepareStatement
- jdbc prepareStatement的运用
- JDBC常用接口PrepareStatement
- JDBC--PrepareStatement 接口
- JDBC PrepareStatement 和 Statement
- jdbc prepareStatement 操作
- JDBC--PrepareStatement对象-程序
- JDBC——preparestatement
- jdbc Statement和PrepareStatement操作
- jdbc----preparestatement的简单使用
- jdbc大量插入数据(prepareStatement)
- JDBC中PrepareStatement与Statement的区别
- JDBC Statement 和 PrepareStatement的用法
- 开始记录我Web前端自学之路了,希望与大家分享交流
- Unity01
- form表单初始化FormData对象方式上传文件
- HandlerMapping和BeanNameUrlHandlerMapping的使用
- 交叉编译zeroMQ与pyzmq
- jdbc核心之PrepareStatement
- UIXX-UITableView制作简单表格
- hdu1158枚举PD
- 《失控》之六--自然之流变
- 有力量的话
- fgetc函数
- LINUX CENTOS7下安装PYTHON3.5.4
- mysql5.7.17安装配置图文教程
- 【机器学习入门】Andrew NG《Machine Learning》课程笔记之三:多元线性回归的梯度下降、特征缩放和正规方程