JDBC介绍及编程流程&Statement与PreparedStatement对比&SQL注入攻击

JDBC编程

JDBC简介:
JDBC(Java Data Base Connection):Java数据库连接。是一种标准Java应用编程接口(Java API),将数据库和Java程序连接起来,是开发人员可以通过Java程序访问数据库。

常见的JDBC组件(JDBC开发中主要用到的类):

  • DriverManager :这个类管理一系列数据库驱动程序。
    匹配连接使用通信子协议从 JAVA 应用程序中请求合适的数据库驱动程序。
    识别 JDBC 下某个子协议的第一驱动程序将被用于建立数据库连接。
  • Driver : 这个接口处理与数据库服务器的通信。你将很少直接与驱动程序互动。
    相反,你使用 DriverManager 中的对象,它管理此类型的对象。
    它也抽象与驱动程序对象工作相关的详细信息。
  • Connection : 此接口具有接触数据库的所有方法。该连接对象表示通信上下文,
    即:所有与数据库的通信仅通过这个连接对象进行。
  • Statement : 使用创建于这个接口的对象将 SQL 语句提交到数据库。
    除了执行存储过程以外,一些派生的接口也接受参数。
  • ResultSet : 在你使用语句对象执行 SQL 查询后,这些对象保存从数据库获得的数据。
    是一个迭代器,方便遍历得到的结果集。
  • SQLException : 这个类处理发生在数据库应用程序的任何错误。

JDBC编程步骤:

  1. 加载数据库驱动,通过反射Class.forName(String driverName);加载。
  2. 通过Connection connection=DriverManager.getConnection(url,user,password);来获得数据库连接。
  • url:数据库地址,MySQL中为jdbc:mysql://localhost:3306/数据库名 localhost是本地主机,也就是说这个位置放的是要连接的数据库所在的主机信息;3306是数据库的端口号;后再跟上要访问的数据库名。
  • user:安装数据库时设置的用户名。
  • password:用户对应的密码。
  1. 可能需要设置隔离级别
    connection.setTransactionIsolation(Connection.TRANSACTION_NONE);
  2. 创建Statemebt对象(常用的有两种)
  • Statement statement = connection.createStatement();
  • PreparedStatement prepareStatement = connection.prepareStatement();
  1. 通过statement/prepareStatement对象来执行SQL语句,并拿到结果集ResultSet(如果是查询多条语句).
    5.如果有结果集则迭代遍历结果集。
  2. 关闭连接资源。结果集、statement对象、connection都要关闭。
import java.io.FileInputStream;
import java.io.IOException;
import java.sql.*;
import java.util.Properties;

public class JdbcDemo {
    private String jdbcDriver;//数据库驱动名
    private String jdbcUrl;//访问的数据库所在主机url
    private String user;//数据据的用户名
    private String password;//数据库的密码
    //以上信息我可以放在.properties文件中,该文件是Java中的配置文件,支持key-value的存储方式,这里定义一个流来访问该文件,在java.util包路径下
    //这样做以后要改上面的信息就不用改代码,而是改文件
    private Properties properties;
    private String dbpro;//db.properties的路径
    private Connection connection;//先定义一个connection对象,注意在java.sql包路径下
    private Statement statement;//定义一个Statement接口的对象statement
    private PreparedStatement ps;//定义一个PreparedStatement接口的对象ps
    //构造方法初始化成员变量
    public JdbcDemo(){
        properties=new Properties();//实例化流
        try {
            properties.load(new FileInputStream("D:\\Java\\servlet_demo\\src\\main\\resources\\db.properties"));//加载db.properties文件
            jdbcDriver=properties.getProperty("jdbcDriver");//从文件中读取key=jdbcDriver对应的value值
            jdbcUrl=properties.getProperty("jdbcUrl");
            user=properties.getProperty("user");
            password=properties.getProperty("password");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    //获取数据库连接,封装在这个方法中
    public void getConnection(){
        try {
            Class.forName(jdbcDriver);//1.加载数据库驱动
            connection=DriverManager.getConnection(jdbcUrl,user,password);//2.获取数据库连接
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
    //下面我把对数据库的操作都封装在每个方法中
    //查询Student表中的所有数据(通过statement执行SQL获取结果集)
    public void selectAll1(){
        try {
            statement=connection.createStatement();//3.创建statement对象
            String sql="select * from Student";
            ResultSet resultSet = statement.executeQuery(sql);//4.执行sql,获取结果集
            while(resultSet.next()){//5.处理结果集:迭代遍历结果集
                System.out.println("SID"+resultSet.getString("SID")//可以根据在表中字段名获取对应字段
                        +" Sname"+resultSet.getString("Sname")
                        +" Sage"+resultSet.getString(3)//也可以根据在表中第几列(从0开始)获取对应字段
                        +" Ssex"+resultSet.getString(4)
                );
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
    //查询Student表中的所有数据(通过PreparedStatement执行SQL获取结果集)注意和Statement对比
    public void selectAll2(){
        try {
            String sql="select * from Student";
            ps=connection.prepareStatement(sql);//3.创建PreparedStatement,预编译sql并保存在ps
            ResultSet resultSet = ps.executeQuery();//4.执行sql并获取结果集
            while(resultSet.next()){//5.处理结果集:迭代遍历结果集
                System.out.println("SID"+resultSet.getString("SID")//可以根据在表中字段名获取对应字段
                        +" Sname"+resultSet.getString("Sname")
                        +" Sage"+resultSet.getString(3)//也可以根据在表中第几列(从0开始)获取对应字段
                        +" Ssex"+resultSet.getString(4)
                );
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
    //根据指定SID来查询Student表中的对应信息(通过Statement执行SQL获取结果集)
    public void selectById1(String id){
        try {
            String sql="select * from Student where SID="+id;//只能通过字符串拼接传递参数
            statement=connection.createStatement();//3.创建Statemnet对象
            ResultSet resultSet = statement.executeQuery(sql);//4.执行sql并获取结果集
            while(resultSet.next()){//5.通过迭代遍历结果集
                System.out.println("SID"+resultSet.getString("SID")//可以根据在表中字段名获取对应字段
                        +" Sname"+resultSet.getString("Sname")
                        +" Sage"+resultSet.getString(3)//也可以根据在表中第几列(从0开始)获取对应字段
                        +" Ssex"+resultSet.getString(4)
                );
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
    //根据指定SID来查询Student表中的对应信息(通过PreparedStatement执行SQL获取结果集)
    public void selectById2(String id){
        try {
            String sql="select * from Student where SID=?";
            ps=connection.prepareStatement(sql);//3.创建PreparedStatemnet对象,预编译sql并保存在ps
            ps.setString(1,id);//4.将形参传给底层,数字1指sql中的第几个?
            ResultSet resultSet=ps.executeQuery();//5.执行sql并获取结果集
            while(resultSet.next()){//6.通过迭代遍历结果集
                System.out.println("SID"+resultSet.getString("SID")//可以根据在表中字段名获取对应字段
                        +" Sname"+resultSet.getString("Sname")
                        +" Sage"+resultSet.getString(3)//也可以根据在表中第几列(从0开始)获取对应字段
                        +" Ssex"+resultSet.getString(4)
                );
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
    //给Student表插入一条数据(通过Statement执行SQL获取结果集)
    public void insert(String id,String name){
        try {
            String sql="insert into Student (SID,Sname) values('"+id+"','"+name+"')";//看到这,总该知道statement非常烦人的一点了吧
            System.out.println(sql);
            statement=connection.createStatement();//3.创建statement对象
            //4.执行sql语句并返回结果(插入操作返回int值代表更改的行数/记录数),对数据的更新(insert、delete、update)调用executeUpdate()
            int i = statement.executeUpdate(sql);
            //上面的操作也可以这样实现
//            String sql="insert into Student (SID,Sname) values(?,?)";//不用字符串拼接啊,使用?占位符 好爽!
//            ps=connection.prepareStatement(sql);//3.创建PreparedStatement对象,预编译sql并保存在ps
//            ps.setString(1,id);
//            ps.setString(2,name);
//            int i1 = ps.executeUpdate();
            System.out.println("插入了"+i+"条数据");
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
    //更改Student表中SID对应的姓名
    public void update(String id,String name){
        try {
            String sql="update Student set Sname=? where SID=?";
            System.out.println(sql);
            ps=connection.prepareStatement(sql);
            ps.setString(1,name);
            ps.setString(2,id);
            //对数据的更新(insert、delete、update)也可以调用execute(),
            //返回true表示MySQL给我们返回了一个ResultSet对象(查询操作),返回false表示MySQL没有返回信息或返回了int(影响行数)
            boolean execute = ps.execute();
            System.out.println(execute);//这里打印为false
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
    //删除Student表中SID对应的数据
    public void delete(String id){
        try {
            String sql="delete from Student where SID=?";
            System.out.println(sql);//打印sql是为了对比字符串拼接和占位符的效果
            ps=connection.prepareStatement(sql);
            ps.setString(1,id);
            int i = ps.executeUpdate();
            System.out.println("删除了"+i+"条数据");
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
    //关闭所有资源
    public void close(){
            try { 
                if(connection!=null){ //不等于null即没有关闭
                    connection.close();//关闭connection
                    connection=null;//将其致为null,是为了防止内存泄漏,方便GC(jvm的垃圾回收机制)回收
                }
                if(statement!=null){//同上
                    statement.close();
                    statement=null;
                }
                if(ps!=null){//同上
                    ps.close();
                    ps=null;
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
    }
    public static void main(String[] args) {
        JdbcDemo jdbcDemo=null;
        try {
            jdbcDemo = new JdbcDemo();
            jdbcDemo.getConnection();//调用该方法来获取数据库连接
            jdbcDemo.selectAll1();//进行查询操作
            System.out.println("==========================");
            jdbcDemo.selectById1("02");//和selectById2("02")执行结果相同,但底层的执行过程差异很大
            System.out.println("==========================");
            jdbcDemo.selectById2("02");
            System.out.println("==========================");
            jdbcDemo.insert("09", "大神");//执行一次表中就有这条数据了,可不敢再插入一次同样的数据
            jdbcDemo.selectAll1();//全表查询看是否打印成功,注意:因为我没有设置是否自动提交,所以默认自动提交(commit),即不支持事务
            jdbcDemo.update("09", "大仙");
            jdbcDemo.selectAll1();//看一下更改效果
            System.out.println("==========================");
            jdbcDemo.delete("09");
            jdbcDemo.selectAll1();//看一下删除效果
        }finally {//程序执行到最后会执行finally中关闭资源的代码
            if(jdbcDemo!=null){//不为null则没有关闭
                jdbcDemo.close();//关闭jdbcDemo
                jdbcDemo=null;//方便GC(垃圾回收机制)清理内存,防止内存泄漏
            }
        }
    }
}

执行结果:
在这里插入图片描述
在这里插入图片描述
常用的方法都在里面使用且对比了效果,认真跟一下代码就能掌握。

Statement和PreparedStatement对比

  • 都是接口,且PreparedStatement继承了Statement
    在这里插入图片描述
    在这里插入图片描述
  • PreparedStatement的sql可以使用占位符,是预编译的,预编译后将带有占位符的SQL语句传给数据库服务器,再根据 ps.setString();把传占位符对应的值传给数据库。
    int parameterIndex是第几个占位符(从0开始数),String x是要传的值:
    在这里插入图片描述
    要传其他类型的数据也可以使用对应的方法
    在这里插入图片描述
  • Statement必须使用字符串拼接来实现SQL语句,过程繁琐容易出错,且会出现SQL注入攻击。
  • PreparedStatement可以使用SQL缓存区,效率更高。

总结:

  • 安全性:PreparedStatement可以防止依赖SQL注入,安全性高;
  • 运行效率:PreparedStatement可以使用SQL缓存区,运行效率高;
  • 语法不同:PreparedStatement可以使用预编译的SQL,而Statement只能使用静态SQL;
  • 代码美感:PreparedStatement使用占位符,不需要字符串拼接,编写方便且代码优雅。

SQL注入攻击:
先来看一段代码:

public class SQLTest {
    public static void main(String[] args) {
        Connection connection=null;
        Statement statement=null;
        try {
            String name="张三' or 1=1";
            String password="djndaed' or 1=1";
            String sql = "select * from User where name='"+name+" and password='"+password;
            Class.forName("com.mysql.jdbc.Driver");
            connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/test_sql", "root", "123456");
             statement = connection.createStatement();
            ResultSet resultSet = statement.executeQuery(sql);
            while (resultSet.next()){
                System.out.println("登陆成功");
                System.out.println(sql);
                return;//如果登陆成功,下面代码不执行
            }
            System.out.println("登陆失败");
            System.out.println(sql);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            try {
                if(connection!=null){
                    connection.close();
                    connection=null;
                }
                if(statement!=null){
                    statement.close();
                    statement=null;
                }
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
}

执行结果:
在这里插入图片描述
注意!name=‘张三’ or 1=1这个表达式判断下来永真(永远不会假),因为or就是有真则真;password=‘’djndaed‘ or 1=1一样是永真。这样无论我id和password传什么,只要保证可以字符串拼接出永真式。当进行登陆操作时,这样判断name和password是否在User表中存在的结果一定是存在,所以会登录成功,还可以返回表中的用户信息,造成安全性问题。这就是SQL注入攻击

来看看PreparedStatement:

public class SQLTest {
    public static void main(String[] args) {
        Connection connection=null;
        PreparedStatement ps=null;
        try {
            String name="张三 or 1=1";
            String password="123 or 1=1";
            //        String sql = "select * from User where id="+id+" and password="+password;
            String sql = "select * from User where name=? and password=?";
            Class.forName("com.mysql.jdbc.Driver");
            connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/test_sql", "root", "123456");
            ps = connection.prepareStatement(sql);
            ps.setString(1,name);
            ps.setString(2,password);
            ResultSet resultSet = ps.executeQuery();
            while (resultSet.next()){
                System.out.println("登陆成功");
                System.out.println(resultSet.getString("name")+" "+resultSet.getString(password));
                return;//如果登陆成功,下面代码不执行
            }
            System.out.println("登陆失败");
            System.out.println(sql);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            try {
                if(connection!=null){
                    connection.close();
                    connection=null;
                }
                if(ps!=null){
                    ps.close();
                    ps=null;
                }
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
}

执行结果:
在这里插入图片描述
可见PreparedStatement中将id和password传过去,形成的SQL应该是:select * from User where name='张三 or 1=1' and password='123 or 1=1',这样就算or前面的信息正确,MySQL也会把id和password的内容当成整体,不会像Statement那样拼接成字符串而变成永真式。

发布了19 篇原创文章 · 获赞 9 · 访问量 2196

猜你喜欢

转载自blog.csdn.net/weixin_44480874/article/details/99762798