在前面的关于JDBC的叙述中,着重说明了工具类的创建办法,接下来我将就我之前没有提到的查询的方式加以说明,在说明查询之前我先引出一个新的概念就是结果集这个类,及他的相关的一些方法
1.结果集
之前有提到过发送SQL的Statement这个类,他的主要作用就是发送SQL到数据库,对于增,删,改,使用的方法是Statement.executeUpdata();注意他的返回值是一个整形,也就是返回的是数据库受影响的行数,而在进行查找的SQL时,要调用的方法是Statement.executeQuery();
这个方法将返回一个结果集,也就是说你要查询的结果将以集合的方式返还回来
以下是一段测试代码:
1 @Test 2 public void test1(){ 3 //假设传入的员工id是 4 int id=7369; 5 Connection con=null; 6 //创建连接 7 try { 8 String sql="select * from emp_jiawenzhe " 9 + "where empno="+id;10 con=DBUtil.getConnection();11 Statement smt=con.createStatement();12 ResultSet rs=smt.executeQuery(sql);13 //结果集中封装了多行数据,需要遍历14 while(rs.next()){15 //rs.get类型(字段名)16 //rs.get类型(字段索引)17 System.out.println(rs.getInt("empno"));18 System.out.println(rs.getString("ename"));19 }20 } catch (SQLException e) {21 //记录日志22 e.printStackTrace();23 //能处理则自己处理,处理不了向上抛给调用者24 throw new RuntimeException("查询员工失败",e);25 }finally{26 //归还连接27 DBUtil.close(con);28 }29 }
根据SQL语句可以看出,是要从一张员工表里面找到在指定id的员工,rs是结果集的实例化,rs.next()方法就是去遍历每一个字段,当字段遍历到最后为空的时候返回null
而rs.get方法后面跟的类型是要以你的数据库字段类型为标准,比如我要得到员工号,员工号肯定是整数,所以用getInt,以此类推这段测试代码执行后的结果为:
7369SMITH
得到了员工号为7369,名字为SMITH
2.PreparedStatement
从上面的示例可以看到,我们进行SQL查询是传入了一条写好的固定好了的SQL语句,比如我们现在需要进行下一条查询,必须在重新写一个SQL语句,并且重新编译一次。这样看来效率很差,Statement只适合在静态SQL中使用,为此Java引进了PreparedStatement来做出动态SQL查询的效果。
关于PreparedStatement的原理解释有很多很多,下面我直接引入一段代码直观简介的阐明
1 @Test 2 public void test2(){ 3 //假设查询的工资 4 double salary=4500.0; 5 Connection conn=null; 6 try { 7 conn=DBUtil.getConnection(); 8 String sql="select *from emp_jiawenzhe " 9 + "where sal>=?";10 //创建PrepareStatement对象11 //发送SQL并建立执行计划12 PreparedStatement ps=conn.prepareStatement(sql);13 //设置参数14 //ps.set类型(?的索引,?的值)15 ps.setDouble(1, salary);16 ResultSet rs=ps.executeQuery();17 while (rs.next()) {18 System.out.println(rs.getInt("empno"));19 System.out.println(rs.getString("ename"));20 21 }22 } catch (SQLException e) {23 24 e.printStackTrace();25 throw new RuntimeException("查询员工失败",e);26 }finally{27 DBUtil.close(conn);28 }29 }
在上面的SQL语句中,可以看懂他的意思就是要查询工资大于某个范围的员工信息,但是这个范围我们并没有写死,而是用"?"作为占位符,也就是说,这个问号的具体值将由后面传入,和Statement的方法一样,我们同样要拿到PreparedStatement的接口的连接对象,调用他的一个set()方法将参数传入,第一个参数是索引值,也就是问号的位置,第二个参数就是实际的值,将实际的值存放到变量之中,这样在修改的时候只要改动就可以了,而不用再去改动SQL语句。
接下来的这个例子,你会看到SQL使用了更多的占位符,更加体现了这种方法的有点
1 @Test 2 public void test03(){ 3 //假设用户传入了如下要添加的数据 4 String ename="唐僧"; 5 String job="领导"; 6 int mgr=0; 7 Date date=new Date(System.currentTimeMillis()); 8 double sal=9000; 9 double comm=3000;10 int deptno=2;11 Connection conn=null;12 try {13 conn=DBUtil.getConnection();14 String sql="insert into emp_jiawenzhe(empno,ename,JOB,MGR,HIREDATE,SAL,COMM,DEPTNO) "15 + "values(emp_jiawenzhe_j.nextval,?,?,?,?,?,?,?)";16 PreparedStatement ps=conn.prepareStatement(sql);17 ps.setString(1, ename);18 ps.setString(2, job);19 ps.setInt(3, mgr);20 ps.setDate(4, date);21 ps.setDouble(5, sal);22 ps.setDouble(6, comm);23 ps.setInt(7,deptno);24 ps.executeQuery();25 } catch (SQLException e) {26 27 e.printStackTrace();28 throw new RuntimeException("增加员工失败",e);29 }finally{30 DBUtil.close(conn);31 }32 }
下面是一条修改的示例:
1 @Test 2 public void test4(){ 3 //假设用户传入了如下要修改的数据 4 int empno=104; 5 String ename="悟空"; 6 String job="保镖"; 7 int mgr=8; 8 Date date =new Date(System.currentTimeMillis()); 9 double sal=5000.0;10 double comm=0;11 int deptno=2;12 Connection conn=null;13 try {14 conn=DBUtil.getConnection();15 String sql="update emp_jiawenzhe set "16 + "ename=?,"17 + "job=?,"18 + "mgr=?,"19 + "hiredate=?,"20 + "sal=?,"21 + "comm=?,"22 + "deptno=? "23 + "where empno=?";24 PreparedStatement ps=conn.prepareStatement(sql);25 ps.setString(1, ename);26 ps.setString(2, job);27 ps.setInt(3, mgr);28 ps.setDate(4, date);29 ps.setDouble(5, sal);30 ps.setDouble(6, comm);31 ps.setInt(7, deptno);32 ps.setInt(8, empno);33 ps.executeQuery();34 } catch (SQLException e) {35 36 e.printStackTrace();37 throw new RuntimeException("修改错误",e);38 }finally{39 DBUtil.close(conn);40 }41 }
set方法后面跟的类型和我上面说的get方法是完全一样的
下面是一个删除的示例:
1 @Test 2 public void test5(){ 3 Connection conn=null; 4 int empno=104; 5 try { 6 conn=DBUtil.getConnection(); 7 String sql="delete from emp_jiawenzhe " 8 + "where empno=? "; 9 PreparedStatement ps=conn.prepareStatement(sql);10 ps.setInt(1, empno);11 ps.executeQuery();12 } catch (SQLException e) {13 14 e.printStackTrace();15 throw new RuntimeException("删除错误",e);16 }finally{17 DBUtil.close(conn);18 }19 }
注意一下这里面的DBUtil就是我之前写的工具类3
3.SQL注入
所谓的SQL注入其实就是程序的一个bug,更准确的说是在使用Statement时产生的这样一个bug,我举个例子加以说明一下,比如你在输入密码的时候,其实你是不知道密码的,你输入的密码是:123 or ‘a’=‘a’;这样输入就是利用了SQL里面的关键字or,因为or后面的表达式是恒成立的,所以这个密码整体就是恒正确的,这样你就会在完全不知道密码的情况下登陆系统
为了解决这个办法,必须使用参数化的SQL,即使用?作为占位符,这样就可以有效避免SQL注入
示例代码:
1 @Test 2 public void test6(){ 3 //假设用户输入的数据如下 4 String username ="admin"; 5 String password="'a' or 'b'='b'"; 6 Connection conn=null; 7 try { 8 conn=DBUtil.getConnection(); 9 String sql="select*from user_jiawenzhe "10 + "where username=? "11 + "and password=? ";12 PreparedStatement ps=conn.prepareStatement(sql);13 ps.setString(1, username);14 ps.setString(2, password);15 ResultSet rs= ps.executeQuery();16 while (rs.next()) {17 System.out.println("登陆成功");18 19 }20 } catch (SQLException e) {21 22 e.printStackTrace();23 throw new RuntimeException("查询用户失败",e);24 }finally{25 DBUtil.close(conn);26 }27 }
这样系统将不会把or理解为SQL的关键字而仅仅当成普通的字符串。
未完待续!