JDBC

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JDBC相关的知识,希望对你有一定的参考价值。

JDBC常用对象

DriverManager 驱动管理类

主要作用:

  • 注册驱动 实际开发中使用Class.forName("com.mysql,jdbc.Drive");这种方式,因为之前的方式会导致注册两次驱动
  • 获得连接 Connection getConnection(String url,String username,String password)
  • url写法:jdbc:mysql://localhost:3306/jdbctest
    • jdbc 协议
    • mysql 子协议
    • localhost 主机名
    • 3306 端口号
    • jdbctest 数据库名
    • 如果连接的是本机,可以简写为jdbc:mysql:///jbdctest

Connection 连接对象

主要作用:

  • 创建用来执行SQL语句的对象
    • Statement createStatement() 执行SQL语句,有SQL注入漏洞威胁
    • PrepareStatement prepareStatement(String sql) 预编译SQL语句,解决SQL注入
    • CallableStatement prepareCall(String sql) 执行SQL中的存储过程
  • 事务的管理
  • setAutoCommit(boolean autoCommit) 设置事务是否自动提交
  • commit() 事务提交
  • rollback() 事务回滚

Statement 执行SQL

主要作用:

  • 执行SQL语句
    • boolean execute(String sql) 执行SQL,执行查询语句返回true,否则返回false
    • ResultSet execute(String sql) 执行SQL中的查询语句
    • int executeUpdate(String sql) 执行SQL中的插入、更新、删除语句
  • 执行批处理操作
    • addBatch(String sql) 添加到批处理
    • executeBatch() 执行批处理
    • clearBatch() 清空批处理

ResultSet 结果集

主要作用:

  • 获取查询到的结果
    • next() 判断是否存在下一条记录
    • 针对不同类型的数据可以使用getXXX()获取数据
    • getObject() 通用获取数据,可以获取任何类型的数据

JDBC的SQL注入漏洞问题

package com.kernel.test;

import org.junit.Test;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;

/**
 * 演示JDBC注入漏洞
 */
public class JDBCDemo04 {
    @Test
    /**
     * 测试SQL注入
     */
    public void demo1() {
        //boolean result = login("aaa", "11");
        //boolean result = login("aaa‘ or ‘1=1", "5454545");
        boolean result = login("aaa‘ -- ", "sjjkhnjkhnk");
        System.out.println(result);
    }

    public boolean login(String username, String password) {
        Connection connection = null;
        Statement statement = null;
        ResultSet resultSet = null;
        try {
            connection = DBUtil.getConnection();
            statement = connection.createStatement();
            String sql = "select * from user where username=‘" + username + "‘ and password=‘" + password + "‘";
            resultSet = statement.executeQuery(sql);
            if (resultSet.next()) {
                return true;
            } else {
                return false;
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            DBUtil.release(resultSet, statement, connection);
        }
        return false;
    }
}
我们来看下这是什么原因,有这样一行代码
boolean result = login("aaa‘ or ‘1=1", "5454545");
这时,sql就变成了这样
sql = "select * from user where username=‘aaa‘ or ‘1=1‘ and password=‘545445‘";
SQL会首先判断and这个语句,and的结果为false,username=‘aaa‘,结果为true,true or false返回true
再来看第二条
sql "select * from user where username=‘aaa‘ -- and password=‘sjjkhnjkhnk‘
-- 是注释的意思,意思就是注释后面的password=xxx,肯定返回true

如何解决呢

PrepareStatement是Statement的子接口,它的实例对象可以通过调用Connection.prepareStatement(sql)方法获得,相对于Statement对象而言:

  • PrepareStatement可以避免SQL注入漏洞的问题
  • Statement会使数据库频繁的编译SQL,可能造成数据库缓冲区溢出,而PrepareStatement可以对SQL进行预编译,提高数据库执行效率
  • PrepareStatement允许使用占位符替换SQL中的参数,简化编写
package com.kernel.test;

import org.junit.Test;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.Statement;

/**
 * 演示JDBC注入漏洞
 */
public class JDBCDemo04 {
    @Test
    /**
     * 测试SQL注入
     */
    public void demo1() {
        //boolean result = login("aaa", "11");
        //boolean result = login("aaa‘ or ‘1=1", "5454545");
        boolean result = logon("aaa‘ -- ", "sjjkhnjkhnk");
        System.out.println(result);
    }

    /**
     * 避免SQL注入漏洞
     * @param username
     * @param password
     * @return
     */
    public boolean logon(String username, String password) {
        Connection connection = null;
        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;
        try {
            connection = DBUtil.getConnection();
            String sql = "select * from user where username=? and password=?";
            preparedStatement = connection.prepareStatement(sql);
            preparedStatement.setString(1, username);
            preparedStatement.setString(2, password);
            resultSet = preparedStatement.executeQuery();
            if (resultSet.next()) {
                return true;
            } else {
                return false;
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            DBUtil.release(resultSet, (Statement) preparedStatement, connection);
        }
        return false;
    }

    /**
     * 产生SQL注入漏洞
     *
     * @param username
     * @param password
     * @return
     */
    public boolean login(String username, String password) {
        Connection connection = null;
        Statement statement = null;
        ResultSet resultSet = null;
        try {
            connection = DBUtil.getConnection();
            statement = connection.createStatement();
            String sql = "select * from user where username=‘" + username + "‘ and password=‘" + password + "‘";
            resultSet = statement.executeQuery(sql);
            if (resultSet.next()) {
                return true;
            } else {
                return false;
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            DBUtil.release(resultSet, statement, connection);
        }
        return false;
    }
}
为什么使用它就可以避免SQL注入漏洞呢?那是因为创建对象的时候就将sql传递进去,并进行了预编译,即使后面通过变量传递了关键字进来,也会认为这是字符串

数据库连接池

连接池是创建和管理一个连接的缓冲池的技术,这些连接准备好被任何需要它们的线程使用

应用程序直接获得链接的缺点

用户每次请求都需要向数据库获得连接,而数据库创建连接通常需要消耗较大的资源,创建时消耗的事件也比较长,在高并发业务场景下,如果采用这种方式获得连接,极大浪费数据库的资源,并且容易造成数据库服务器内存溢出

那么连接池是怎么解决这个问题的呢

连接池中默认存放了若干个数据库连接对象,当用户请求与数据库进行连接时,直接从数据库中取出,当用户完成操作需要释放数据库连接资源时,销毁的连接回到连接池继续等待下一次用户的请求

C3P0的使用

package com.kernel.test;

import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.junit.Test;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;

/**
 * 演示连接池
 */
public class DataSourseDemo01 {
    @Test
    /**
     * 手动设置连接池
     */
    public void demo1() {
        //获得连接
        Connection connection = null;
        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;
        try {
            //创建连接池
            ComboPooledDataSource dataSource = new ComboPooledDataSource();
            //设置连接池参数
            dataSource.setDriverClass("com.mysql.jdbc.Driver");
            dataSource.setJdbcUrl("jdbc:mysql://127.0.0.1:3306/jdbctest");
            dataSource.setUser("root");
            dataSource.setPassword("123456");
            dataSource.setMaxPoolSize(20);
            connection = dataSource.getConnection();
            String sql = "select * from user";
            preparedStatement = connection.prepareStatement(sql);
            resultSet = preparedStatement.executeQuery();
            while (resultSet.next()) {
                System.out.println(resultSet.getInt("uid") + "	"
                        + resultSet.getString("username") + "	"
                        + resultSet.getString("password") + "	"
                        + resultSet.getString("name"));
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            DBUtil.release(resultSet, preparedStatement, connection);
        }
    }

    @Test
    /**
     * 读取配置文件设置连接池,默认读取src目录下的c3p0-config.xml文件
     */
    public void demo2() {
        //获得连接
        Connection connection = null;
        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;
        try {
            //创建连接池
            connection = DBUtil.getConnection();
            String sql = "select * from user";
            preparedStatement = connection.prepareStatement(sql);
            resultSet = preparedStatement.executeQuery();
            while (resultSet.next()) {
                System.out.println(resultSet.getInt("uid") + "	"
                        + resultSet.getString("username") + "	"
                        + resultSet.getString("password") + "	"
                        + resultSet.getString("name"));
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            DButil2.release(resultSet, preparedStatement, connection);
        }
    }
}

编写C3P0的配置文件

<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>

  <default-config>
    <property name="driverClass">com.mysql.jdbc.Driver</property>
    <property name="jdbcUrl">jdbc:mysql:///jdbctest</property>
    <property name="user">root</property>
    <property name="password">123456</property>
    <property name="initialPoolSize">5</property>
    <property name="maxPoolSize">20</property>
  </default-config>

</c3p0-config>

以上是关于JDBC的主要内容,如果未能解决你的问题,请参考以下文章

面试常用的代码片段

mysql jdbc源码分析片段 和 Tomcat's JDBC Pool

JDBC操作数据库之查询数据

如何在片段中填充列表视图?

在 myeclipse中进行连接sql server的测试

MYBATIS05_ifwherechoosewhentrimsetforEach标签sql片段