JDBC简单入门以及SQL注入代码教程
import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.Statement; import org.junit.Test; public class JdbcDemo_01 { @Test public void demo01() throws Exception{ // 查询所有的分类信息 // 注意:使用JDBC规范,采用都是 java.sql包下的内容 //1 注册驱动 Class.forName("com.mysql.jdbc.Driver"); //2 获得连接 Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/webdb_4", "root", "root"); //3获得语句执行者 Statement st = conn.createStatement(); //4执行SQL语句 ResultSet rs = st.executeQuery("select * from category"); //5处理结果集 while(rs.next()){ // 获得一行数据 Integer cid = rs.getInt("cid"); String cname = rs.getString("cname"); System.out.println(cid + " , " + cname); } //6释放资源 rs.close(); st.close(); conn.close(); } } 解释说明: 获得驱动 1. JDBC规范规定,如果需要连接数据库,必须提供驱动接口的实现类 驱动接口:java.sql.Driver 每一个数据库提供驱动jar 都实现该接口 2. MySQL 提供实现类:com.mysql.jdbc.Driver 源码:public class com.mysql.jdbc.Driver implements java.sql.Driver { 3. JDBC规范提供了,注册实现方式 DriverManager.registerDriver( new com.mysql.jdbc.Driver() ); 但,如果遵循上面语句,Java代码与 mysql实现类耦合(直接关系),之后切换数据库将不能进行。 希望提供方案时,只要切换数据驱动,就可以切换使用数据库 4. 通常注册驱动使用标准写法 Class.forName("com.mysql.jdbc.Driver") 1) 使用反射的方式加载指定的类 2) 具体加载的类以字符串(类的全限定名称)体现,内容就可以存放到配置文件中,通过修改配置文件方便切换数据库 3) 一个类被加载到内存,静态代码块将执行,static{ ... } 4) com.mysql.jdbc.Driver 源码分析 static{ java.sql.DriverManager.registerDriver(new Driver()); } 注册驱动注意事项 手动注册驱动,驱动注册了几次? DriverManager.registerDriver( new com.mysql.jdbc.Driver() ); 注册了2次 第一次:new Driver() 时,Driver类加载,静态代码块执行,注册一次 第二次:手动注册 //结论:注册驱动 Class.forName("com.mysql.jdbc.Driver"); JDBC提供工具类 DriverManager(驱动管理器) getConnection() 通过设置具体参数向不同的数据库创建新的连接 参数1:url ,数据访问路径 参数2:user , 数据库用户名 参数3:password , 数据库密码 url访问路径 格式 jdbc:mysql://ip地址:端口号/数据库名称 例如 jdbc:mysql://localhost:3306/webdb_4 jdbc固定 mysql 表示mysql数据库,一般情况会根据数据库不同而不同 localhost:3306 表示数据具体地址,为默认值,及可以省略 webdb_4 表示数据名称 路径简化版 jdbc:mysql:///webdb_4 通过Connection就可以获得针对不同数据库sql语句的执行对象,常用 createStatement() 通过Statement对象可以执行任意的SQL语句 st.executeUpdate(sql) 执行DML语句(增删改 insert、delete、update) ,返回为整形,表示影响行数。 st.executeQuery(sql) 执行DQL语句(查询 select) ,返回ResultSet结果集对象(查询所有数据) st.execute(sql) 了解,可以执行任意sql语句。返回为boolean true,表示执行DQL语句,需要通过ts.getResultSet() 获得查询结果 false,表示DML语句,需要通过 ts.getUpdateCount() 获得影响行数。 移动游标 rs.next(); 下一个 rs.previous(); 上一个 获得指定列数据 rs.getXxx(String) ,通过字段名称获得内容 rs.getXxx(Integer) ,通过字段索引号获得内容 例如 : rs.getString("cname") 获得指定名称 rs.getDouble(1) 获得第二列 什么是SQL注入: SQL注入:用户输入的内容作为了SQL语句语法的一部分,改变了原有SQL真正的意义。 假设有登录案例SQL语句如下: SELECT * FROM 用户表 WHERE NAME = 用户输入的用户名 AND PASSWORD = 用户输的密码; 此时,当用户输入正确的账号与密码后,查询到了信息则让用户登录。但是当用户输入的账号为XXX 密码为:XXX’ OR ‘a’=’a时,则真正执行的代码变为: SELECT * FROM 用户表 WHERE NAME = ‘XXX’ AND PASSWORD =’ XXX’ OR ’a’=’a’; 此时,上述查询语句时永远可以查询出结果的。那么用户就直接登录成功了,显然我们不希望看到这样的结果,这便是SQL注入问题。 为此,我们使用PreparedStatement来解决对应的问题。 PreparedStatement预处理对象,处理的每条sql语句中所有的实际参数,都必须使用占位符?替换。 String sql = "select * from user where username = ? and password = ?"; PreparedStatement使用,需要通过以下3步骤完成: 1. PreparedStatement预处理对象代码: #获得预处理对象,需要提供已经使用占位符处理后的SQL语句 PreparedStatement psmt = conn.prepareStatement(sql) 2. 设置实际参数 void setXxx(int index, Xxx xx) 将指定参数设置指定类型的值 参数1:index 实际参数序列号,从1开始。 参数2:xxx 实际参数值,xxx表示具体的类型。 例如: setString(2, "1234") 把SQL语句中第2个位置的占位符?替换成实际参数 "1234" 3. 执行SQL语句: int executeUpdate(); --执行insert update delete语句. ResultSet executeQuery(); --执行select语句. boolean execute(); --执行select返回true 执行其他的语句返回false. 下面演示SQL注入例子 @Test public void demo02(){ //#演示SQL注入 String username = "jack' #"; String password = "12345"; Connection conn = null; Statement st = null; ResultSet rs = null; try { conn = JdbcUtils.getConnection(); st = conn.createStatement(); String sql = "select * from user where username='"+username+"' and password='"+password+"'"; System.out.println(sql); rs = st.executeQuery(sql); if(rs.next()){ System.out.println("用户登录"); } else { System.out.println("不能登录"); } } catch (Exception e) { throw new RuntimeException(e); } finally{ JdbcUtils.closeResource(conn, st, rs); } } 输出结果是: @Test public void demo02(){ //#解决SQL注入 //String username = "jack' #"; String username = "jack"; String password = "1234"; Connection conn = null; PreparedStatement psmt = null; ResultSet rs = null; try { conn = JdbcUtils.getConnection(); //1 准备sql语句 String sql = "select * from user where username=? and password=?"; //2 获得预处理对象 psmt = conn.prepareStatement(sql); //3设置实际参数 psmt.setString(1, username); psmt.setString(2, password); //4执行sql语句 , 注意:没有实际参数 rs = psmt.executeQuery(); if(rs.next()){ System.out.println("用户登录"); } else { System.out.println("不能登录"); } } catch (Exception e) { throw new RuntimeException(e); } finally{ JdbcUtils.closeResource(conn, psmt, rs); } } JdbcUtils工具类: import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; public class JdbcUtils { private static String driver = "com.mysql.jdbc.Driver"; private static String url = "jdbc:mysql://localhost:3306/webdb_4"; private static String user = "root"; private static String password = "root"; static{ try { //注册驱动 Class.forName(driver); } catch (Exception e) { throw new RuntimeException(e); } } /** * 获得连接 * @return * @throws SQLException */ public static Connection getConnection() throws SQLException{ //获得连接 Connection conn = DriverManager.getConnection(url, user, password); return conn; } /** * 释放资源 * @param conn * @param st * @param rs */ public static void closeResource(Connection conn , Statement st , ResultSet rs){ if(rs != null){ try { rs.close(); } catch (SQLException e) { } } if(st != null){ try { st.close(); } catch (SQLException e) { } } if(conn != null){ try { conn.close(); } catch (SQLException e) { } } } }