JDBC 基础


JDBC(Java DataBase Connectivity,java数据库连接)是一种用于执行SQL语句的Java API,可以为多种关系数据库提供统一访问,它由一组用Java语言编写的类和接口组成。

一、JDBC使用的流程

1、流程与环境
  1. 搭建开发环境(数据库、jar包)
  2. 在程序中加载数据库驱动
  3. 建立连接
  4. 创建用于向数据库发送SQL的Statement对象
  5. 从代表结果集的ResultSet中取出数据
  6. 断开与数据库的连接,并释放相关资源

jar包:mysql-connector-java-5.1.7-bin.jar

-- 准备数据环境
CREATE DATABASE jdbctest;
USE jdbctest;
CREATE TABLE user(
	uid INT PRIMARY KEY AUTO_INCREMENT,
    username VARCHAR(20),
    password VARCHAR(20),
    name VARCHAR(20)
);
INSERT INTO user VALUES(NULL, 'aaa', '111', '小明');
INSERT INTO user VALUES(NULL, 'bbb', '222', '小红');
INSERT INTO user VALUES(NULL, 'ccc', '333', '小华');
2、JDBC操作基本实例
Connection conn = null;
Statement statement = null;
ResultSet resultSet = null;
try {
    // 1. 加载驱动
    // 下面的方法会导致驱动注册两次,不采用
    //DriverManager.registerDriver(new Driver());
    // 采用字节码对象注册,只注册一次
    Class.forName("com.mysql.jdbc.Driver");
    // 2. 获得连接
    //  数据库连接对象(主协议+子协议+主机名+端口号+数据库名、用户名、密码)
    conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbctest", "root", "root");
    // 3. 创建执行sql语句的对象
    String sql = "SELECT * FROM user";
    statement = conn.createStatement();
    // 4. 执行sql语句
    resultSet = statement.executeQuery(sql);
    // 5. 遍历查询结果
    while (resultSet.next()) {
        int uid = resultSet.getInt("uid");
        String username = resultSet.getString("username");
        String password = resultSet.getString("password");
        String name = resultSet.getString("name");

        System.out.println("uid:" + uid + "  username:" + username + "  password:" + password + "  name:" + name);
    }
} catch (SQLException e) {
    e.printStackTrace();
} catch (ClassNotFoundException e) {
    e.printStackTrace();
} finally {
    // 6. 释放资源
    // 释放结果集
    if (resultSet != null) {
        try {
            resultSet.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        resultSet = null;
    }
    // 释放查询对象
    if (statement != null) {
        try {
            statement.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        statement = null;
    }
    // 释放连接
    if (conn != null) {
        try {
            conn.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        conn = null;
    }
}
3、JDBC的资源释放

​     Jdbc程序运行完后,切记要释放程序在运行过程中,创建的那些与数据库进行交互的对象,这些对象通常是ResultSet,Statement和Connection对象。
    特别是Connection对象,它是非常稀有的资源,用完后必须马上释放,如果Connection不能及时、正确的关闭,极易导致系统宕机。
    Connection的使用原则是尽量晚创建,尽量早的释放。

二、 JDBC API

1、DriverManager
  1. 注册驱动

    • Class.forName(“com.mysql.jdbc.Driver”);
  2. 获得连接

    • Connection getConnection(String url, String username, String password);

    url写法: jdbc:mysql://localhost:3306/test

    • jdbc:协议
    • mysql:子协议
    • localhost:主机名
    • 3306:端口号
    • test:数据库名

    本机默认端口的数据库可简写:

    jdbc:mysql:///test

// 会注册驱动两次
DriverManager.registerDriver(new Driver());

// 原因分析
// 1. 加载Driver.class文件时,执行里的静态代码块会注册一次
static {
	try {
		DriverManager.registerDriver(new Driver());
	} catch (SQLException var1) {
		throw new RuntimeException("Can't register driver!");
	}
}
// 2. 执行DriverManager的registerDriver()方法,注册一次
// 正确的做法:采用字节码对象进行注册
Class.forName("com.mysql.jdbc.Driver");
2、Connection
  1. 创建执行SQL语句对象
方法描述
Statement createStatement()创建SQL语句对象(存在SQL注入的漏洞)
PreparedStatement prepareStatement(String sql)预编译SQL语句
CallableStatement prepareCall(String sql)执行SQL中的存储过程
  1. 进行事物的管理
方法描述
setAutoCommit(boolean autoCommit)设置事务是否自动提交
commit()事务提交
rollback()事务回滚
3、StateMent
  1. 执行SQL语句

    方法描述
    boolean execute(String sql)执行SQL语句,执行SELECT语句返回true,否则返回false
    Result executeQuery(String sql)执行SQL中的SELECT语句
    int executeUpdate(String sql)执行SQL中的INSERT / UPDATE / DELETE 语句,返回的int值代表影响的行数
  2. 执行批处理操作

方法描述
addBatch(String sql)添加到批处理
executeBatch()执行批处理
clearBatch()清空批处理
3、ResultSet 结果集

结果集:是对查询语句(SELECT)查询结果的封装。

方法描述
next()下一行,没有时为null
getObject()以Object的类型获得结果集的数据
getXXX()以指定的类型获得结果集的数据

三、 JDBC的CRUD操作

1、向数据库中保存记录
// 编写SQL
String sql = "INSERT INTO user VALUES(null, 'eee', '555', '小亮')";
// 执行SQL:
int i = statement.executeUpdate(sql);
if (i > 0) {
	System.out.println("保存成功!");
}
2、修改数据库中的记录
sql = "UPDATE user SET username='qqq', password='456', name='James' WHERE uid=4 ";
i = statement.executeUpdate(sql);
if (i > 0) {
	System.out.println("修改成功啦!");
}
3、删除数据库中的记录
sql = "DELETE FROM user WHERE uid = 4";
i = statement.executeUpdate(sql);
if (i > 0) {
    System.out.println("删除成功啦");
}
4、查询数据库中的记录
// 查询多条记录==>while   单条记录==>if 
sql = "select * from user";
resultSet = statement.executeQuery(sql);
while (resultSet.next()) {
    System.out.println(("uid:" + resultSet.getInt("uid") + "  username:" + resultSet.getString("username") + "  password:" + resultSet.getString("password") + "  name:" + resultSet.getString("name")));
}

四、JDBC的工具类的抽取

    为了简化JDBC的开发,可以将一些重复的代码进行提取,并将需要配置的参数写入配置文件,利用Properities类进行获取。

  • jdbc.properities配置文件(src目录下)
driverClass=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/jdbctest
username=root
password=102
  • 抽取JDBC工具类
public class JdbcUtils {
    private static final String driverClass;
    private static final String url;
    private static final String username;
    private static final String password;

    static {
        // 加载属性文件并解析
        Properties properties = new Properties();
        // 属性文件的输入流:使用类的加载器方式进行获取
        InputStream is = JdbcUtils.class.getClassLoader().getResourceAsStream("jdbc.properties");
        try {
            properties.load(is);
        } catch (IOException e) {
            e.printStackTrace();
        }

        driverClass = properties.getProperty("driverClass");
        url = properties.getProperty("url");
        username = properties.getProperty("username");
        password  = properties.getProperty("password");
    }

    // 注册驱动的方法
    public static void loadDriver() throws ClassNotFoundException {
        Class.forName(driverClass);
    }

    // 获得连接的方法
    public static Connection getConnection() throws SQLException, ClassNotFoundException {
        loadDriver();
        Connection conn = DriverManager.getConnection(url, username, password);
        return conn;
    }

    // 资源的释放
    public static void release(Statement statement, Connection connection) {
        if (statement != null) {
            try {
                statement.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
            statement = null;
        }
        if (connection != null) {
            try {
                connection.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
            connection = null;
        }
    }
    
    // 资源的释放 重载
    public static void release(Statement statement, Connection connection, ResultSet resultSet) {
        release(statement, connection);
        if (resultSet != null) {
            try {
                resultSet.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
            resultSet = null;
        }
    }
}

五、SQL注入漏洞及解决

1、SQL注入漏洞演示
public static boolean login(String username, String password) {
    Connection conn = null;
    Statement statement = null;
    ResultSet resultSet = null;
    try {
        conn = JdbcUtils.getConnection();
        statement = conn.createStatement();
        String sql = "SELECT * FROM user WHERE username ='" + username + "' AND password = '" + password + "'";
        
        resultSet = statement.executeQuery(sql);
        if (resultSet.next()) {
            return true;
        }
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        JdbcUtils.release(statement, conn, resultSet);
    }
    return false;
}

    上面就是模拟服务器从网页获取到用户登录的用户名和密码,利用字符串拼接组成SQL语句,在进行数据库查询,以判断用户信息是否合法。

  • 恶意注入

    如果用户写入的用户名为:【abc’ OR ‘2=2】或 【abc’ – 】密码:任意

    这样最终交由MySQL执行的SQL语句:

SELECT * FROM user WHERE username=‘abc’ OR ‘2=2’ AND password=‘xxx’;
SELECT * FROM user WHERE username=‘abc’ - - ’ AND password='xxx’

    这样无论密码什么,Mysql都会返回数据,引起误判断(-- 为MySQL的注释)。

2、SQL注入漏洞的解决

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

  1. PreperedStatement 可以避免SQL注入的问题。
  2. Statement会使数据库频繁编译SQL,可能造成数据库缓冲区溢出。
    PreparedStatement 可对SQL进行预编译,从而提高数据库的执行效率。
  3. PreperedStatement对于sql中的参数,使用占位符的形式进行替换,简化sql语句的编写。
3、PreparedStatement的使用
  1. 保存数据
String sql = "INSERT INTO user VALUES (NULL,?,?,? )";
preparedStatement = conn.prepareStatement(sql);
preparedStatement.setString(1, "qqq");
preparedStatement.setString(2, "123");
preparedStatement.setString(3, "王五");
int count = preparedStatement.executeUpdate();
if (count > 0) {
    System.out.println("保存成功!");
}
  1. 修改数据
String sql = "UPDATE user set username = ?, password = ?, name = ? WHERE uid = ?";
preparedStatement = conn.prepareStatement(sql);
preparedStatement.setString(1, "xmlx");
preparedStatement.setString(2, "5452");
preparedStatement.setString(3, "张金");
preparedStatement.setInt(4, 5);

int count = preparedStatement.executeUpdate();
if (count > 0) {
    System.out.println("修改成功!");
}
  1. 删除数据
String sql = "DELETE FROM user WHERE uid = ?";
preparedStatement = conn.prepareStatement(sql);
preparedStatement.setInt(1, 11);

int count = preparedStatement.executeUpdate();
if (count > 0) {
    System.out.println("删除成功!");
}
  1. 查询数据
String sql = "SELECT * FROM user";
preparedStatement = conn.prepareStatement(sql);
resultSet = preparedStatement.executeQuery();

while (resultSet.next()) {
    System.out.println(("uid:" + resultSet.getInt("uid") + "  username:" + resultSet.getString("username") + "  password:" + resultSet.getString("password") + "  name:" + resultSet.getString("name")));
}

六、数据库连接池 C3P0

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

1、特点
  • 应用程序直接获取连接的缺点

    用户每次请求都需要向数据库获得连接,而数据库创建连接通常需要消耗相对较大的资源,创建时间也较长,极大的浪费数据库的资源,并且极易造成数据库服务器内存溢出。

  • C3PO连接池

    引入jar: c3p0-0.9.1.2.jar

2、C3P0连接池的使用
  • 连接池配置文件c3p0-config.xml(src目录下)
<?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">root</property>  
	<property name="initialPoolSize">5</property>  <!-- 初始化连接数 -->
	<property name="maxPoolSize">20</property>     <!-- 最大连接数 -->
  </default-config>
</c3p0-config>
  • 使用连接池的JDBC工具类
public class JdbcUtils2 {
    // 连接池对象
    private static ComboPooledDataSource dataSource = new ComboPooledDataSource();
    // 获得连接的方法
    public static Connection getConnection() throws SQLException, ClassNotFoundException {
        Connection conn = dataSource.getConnection();
        return conn;
    }

    // 资源的释放
    public static void release(Statement statement, Connection connection) {
        if (statement != null) {
            try {
                statement.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
            statement = null;
        }
        if (connection != null) {
            try {
                connection.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
            connection = null;
        }
    }
    // 资源的释放 重载
    public static void release(Statement statement, Connection connection, ResultSet resultSet) {
        release(statement, connection);
        if (resultSet != null) {
            try {
                resultSet.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
            resultSet = null;
        }
    }
}
  • 使用连接池
public static void demo2() {
    Connection conn = null;
    PreparedStatement preparedStatement = null;
    ResultSet resultSet = null;
    try {
        // 获得连接
        conn = JdbcUtils2.getConnection();
        // 编写Sql
        String sql = "SELECT * FROM user";
        // 预编译SQL
        preparedStatement = conn.prepareStatement(sql);
        // 设置参数
        // 执行SQL
        resultSet = preparedStatement.executeQuery();
        while (resultSet.next()) {
            System.out.println(("uid:" + resultSet.getInt("uid") + "  username:" + resultSet.getString("username") + "  password:" + resultSet.getString("password") + "  name:" + resultSet.getString("name")));
        }
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        JdbcUtils.release(preparedStatement, conn, resultSet);
    }
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值