JDBC概述

JDBC的全称为:Java DataBase Connectivity (Java数据库连接),是Java语言中用来规范客户端程序如何来访问数据库的应用程序接口,你可以认为这是一套Java连接数据库的规范。在以前没有JDBC的时候,Java程序员想要连接数据库就必须要了解该数据库的具体的驱动程序,如图示例:



后来,Sun 公司提供了JDBC的接口规范——JDBC API 后,数据库厂商就需要根据该接口规范来实现自己的驱动程序,我们在进行数据库开发的时候,只需要使用JDBC接口提供的方法即可:


JDBC的API

JDBC入门程序

首先创建数据库,数据表以及插入记录,sql语句如下:

CREATE DATABASE IF NOT EXISTS jdbctest DEFAULT CHARACTER SET UTF8;
USE jdbctest;
CREATE TABLE goods(
    id INT NOT NULL AUTO_INCREMENT KEY,
    name VARCHAR(20) NOT NULL,
    price FLOAT(6,1) NOT NULL,
    desp VARCHAR(30) NOT NULL
);

INSERT goods(name,price,desp) VALUES('手机',2000.0,'黑色,存储容量32G'),
('冰箱',1500.0,'银色,对开门'),
('洗衣机',3000.0,'滚筒'),
('空调',4000,'变频空调');

现在我们的需求是在我们的程序中查询并打印出价格在3500以下的商品信息。


首先我们需要先下载mysql connector 的 jar包。在IDEA上面导入jar包,具体步骤为:
ctrl + alt + shift + s 打开Project Structure,接下来按照图片的顺序进行配置即可

该图片截取自CSDN博客上面末尾带空格的bearBaby的文章IDEA使用JDBC连接MySQL数据库详细教程

示例程序如下:

import org.junit.Test;

import java.sql.*;


public class JDBCDemo {
    @Test
    public void test() {
        Connection connection = null;
        Statement statement = null;
        ResultSet resultSet = null;
        try {
            Class.forName("com.mysql.jdbc.Driver");
            connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbctest", "root", "root");
            statement = connection.createStatement();
            String sql = "SELECT * FROM goods\n" +
                    "WHERE price <= 3500;";
            resultSet = statement.executeQuery(sql);
            while (resultSet.next()) {
                int id = resultSet.getInt("id");
                String name = resultSet.getString("name");
                float price = resultSet.getFloat("price");
                String desp = resultSet.getString("desp");

                System.out.println(id + " " + name + " " + price + " " + desp);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            // JDBC 资源的释放
            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(connection != null){
                try {
                    connection.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
                connection = null;
            }
        }
    }
}

DriverManager

DriverManager是驱动管理类,它的主要作用有两个:

  1. 注册驱动
  2. 获得连接

注册驱动的两种方式:
第一种:

DriverManager.registerDriver(new Driver());

第二种:

Class.forName("com.mysql.jdbc.Driver");

在实际开发中,应该使用反射的写法,因为第一种方式会导致DriverManager类注册驱动两次,下面的程序是DriverManager类的源代码中的一部分,可以看到在静态代码块中,已经实现了注册驱动,我们只要使用反射加载DriverManager类即可达到注册驱动的目的。

static {
        try {
            DriverManager.registerDriver(new Driver());
        } catch (SQLException var1) {
            throw new RuntimeException("Can't register driver!");
        }
    }

DriverManager类的第二个主要的作用就是获得连接,使用如下程序:

DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbctest","root","root");

该程序会返回一个Connection对象。getConnection方法里要传入三个参数,第一个参数是url,第二个参数是连接数据库的username,第三个参数是password。对于第一个参数url的写法如下:

协议:子协议://主机名:端口号/数据库名称

url 的写法应该牢记!

Connection

调用DriverManager的getConnection方法就可以获得Connection对象。Connection对象的作用又是什么呢?
Connection对象的主要作用有如下几点:

  1. 创建用来执行SQL语句的对象
1. Statement createStatement()
2. PreparedStatement prepareStatement(String sql)
3. CallableStatement prepareCall(String sql)

createStatement方法可以执行SQL语句,prepareStatement可以进行预编译并执行。相比prepareStatement方法,createStatement方法无法防止SQL注入,prepareStatement方法解决了SQL注入的漏洞。prepareCall方法是用来执行SQL中的存储过程。

  1. 进行事物的管理
    Connection对象除了可以创建用来执行SQL语句的对象,还可以进行事务的管理。
setAutoCommit(boolean autoCommit)
commit()
rollback()

setAutoCommit方法中可以传入一个布尔值,该方法用来设置事务是否自动提交;commit方法是事务提交的方法;rollback()是进行事务的回滚。

Statement

Statement对象的主要作用有两个:

  1. 执行SQL语句
  2. 执行批处理操作

Statement对象主要的执行SQL语句的方法有:

boolean execute(String sql) 
ResultSet executeQuery(String sql)
int executeUpdate(String sql)

execute方法是用来执行SQL语句的;executeQuery则是用来执行SQL中的SELECT语句的,也就是执行SQL的查询操作,返回的ResultSet是查询的结果集;executeUpdate方法用来执行SQL中的insert/update/delete语句,也就是说executeUpdate方法主要执行SQL的增删改操作,其返回的结果是int类型,代表增删改操作影响的具体的行数。

除了执行SQL语句,Statement对象还可以执行批处理操作
主要方法如下:

addBatch(String sql)
executeBatch()
clearBatch()

addBatch方法可以将sql语句添加到批处理中;executeBatch方法可以将一群添加到批处理的sql语句执行;clearBatch方法则是将添加到批处理的sql语句清除。

ResultSet

ResultSet对象是结果集,它的作用很简单就是获取查询到的结果。什么是结果集?结果集其实就是查询语句(SELECT)查询出的结果的封装。ResultSet中的主要方法如下:

next()
getXXX()
getObject()

next方法用来判断是否有下一行记录,它的作用类似于迭代器,可以用来进行结果集的遍历;getXXX表示针对不同类型的数据可以使用可以使用getXXX来获取数据,比如你要获取String类型的数据则可以使用getString();你要获取int类型的数据则可以使用getInt()。而getObject()则是通用的方法,我们可以使用getObject()来获取任意类型的数据。

JDBC的资源的释放

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

// JDBC 资源的释放
if(connection != null){
    try {
        connection.close();
    } catch (SQLException e) {
        e.printStackTrace();
    }
    connection = null;
}

试想一下,为什么在close方法执行过后,还需要手动将connection置为null值呢?原因就在于,Connection对象是非常稀有的资源,用完需要马上释放。当Connection对象调用close方***释放此资源,但是还没有被JVM垃圾回收。如果在最后手动将Connection对象置为null,则会让垃圾回收机制更早地进行回收。

JDBC的CRUD操作

向数据库中插入记录

依然对数据表goods进行操作



现在向表中插入一条记录:

商品名称:耳机
商品价格:200.0
商民描述:蓝牙耳机

代码如下:

import org.junit.Test;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;

public class JDBCDemo2 {
    @Test
    public void test(){
        Connection connection = null;
        Statement statement = null;
        try {
            Class.forName("com.mysql.jdbc.Driver");
            connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbctest?useUnicode=true&characterEncoding=utf8","root","root");
            statement = connection.createStatement();
            String sql = "INSERT goods(name,price,desp) VALUES('耳机',200.0,'蓝牙耳机');";
            int result = statement.executeUpdate(sql);
            if(result > 0){
                System.out.println("insert succeed");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            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;
            }
        }
    }
}

这里注意:jdbc向mysql数据库中插入数据的时候,查询发现是出现乱码,只需要在getConnection的时候将url的后面加上?useUnicode=true&characterEncoding=utf8即可。
插入数据成功~

修改数据库中的记录

数据表现有数据如下:



将id = 2的记录修改为:

name:电视
price:3000.0
desp:4k高清液晶电视

代码如下:

import org.junit.Test;

import java.sql.*;

public class JDBCDemo3 {

    @Test
    public void test() {
        // 修改数据表的记录
        Connection connection = null;
        Statement statement = null;
        ResultSet resultSet = null;
        try {
            Class.forName("com.mysql.jdbc.Driver");
            connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbctest?useUnicode=true&characterEncoding=utf8", "root", "root");
            statement = connection.createStatement();
            String sql1 = "UPDATE goods SET name = '电视',price = 3000.0,desp = '4k高清液晶电视' WHERE id = 2";
            String sql2 = "SELECT * FROM goods";
            int result = statement.executeUpdate(sql1);
            if (result > 0) {
                System.out.println("update succeed");
                resultSet = statement.executeQuery(sql2);
                while (resultSet.next()) {
                    int id = resultSet.getInt("id");
                    String name = resultSet.getString("name");
                    float price = resultSet.getFloat("price");
                    String desp = resultSet.getString("desp");
                    System.out.println(id + " " + name + " " + price + " " + desp);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            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(connection != null){
                try {
                    connection.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
                connection = null;
            }
        }

    }
}

执行代码成功:

update succeed
1 手机 2000.0 黑色,存储容量32G
2 电视 3000.0 4k高清液晶电视
3 洗衣机 3000.0 滚筒
4 空调 4000.0 变频空调
6 耳机 200.0 蓝牙耳机

删除数据库中的记录

继续使用goods表



现在将id = 3的记录删除
代码如下:

import org.junit.Test;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;

public class JDBCDemo4 {
    @Test
    public void test(){
        // 删除数据表中的记录
        Connection connection = null;
        Statement statement = null;
        try {
            Class.forName("com.mysql.jdbc.Driver");
            connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbctest","root","root");
            statement = connection.createStatement();
            String sql = "DELETE FROM goods where id = 3";
            int result = statement.executeUpdate(sql);
            if(result > 0){
                System.out.println("delete succeed");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            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;
            }
        }
    }
}

执行代码成功,再次查询goods表显示如下:


查询数据库中的记录

还是对goods表进行操作,现在的需求是查询id = 4的这条记录。
代码如下:

import org.junit.Test;

import java.sql.*;

public class JDBCDemo5 {

    @Test
    public void test() {
        // 查询数据表中的数据
        Connection connection = null;
        Statement statement = null;
        ResultSet resultSet = null;

        try {
            Class.forName("com.mysql.jdbc.Driver");
            connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbctest", "root", "root");
            statement = connection.createStatement();
            String sql = "SELECT * FROM goods WHERE id = 4;";
            resultSet = statement.executeQuery(sql);
            while (resultSet.next()) {
                int id = resultSet.getInt("id");
                String name = resultSet.getString("name");
                float price = resultSet.getFloat("price");
                String desp = resultSet.getString("desp");
                System.out.println(id + " " + name + " " + price + " " + desp);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            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(connection != null){
                try {
                    connection.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
                connection = null;
            }
        }
    }
}

查询成果,返回结果:

4 空调 4000.0 变频空调

封装成工具类

像上述示例中的几个程序,我们都需要去写复杂的诸如url,mysql的登录名与密码等信息,而且对于释放资源的程序都是一样的,如果每次都去写则十分麻烦,最好的方法就是将代码封装成工具类,这样就可以非常简便地使用工具类代替我们去做这些重复的工作。
封装好的工具类如下:

JDBCUtils 类

import java.io.IOException;
import java.sql.*;
import java.util.Properties;

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 properties = new Properties();
        // 类加载器获取文件的输入流
        try {
            properties.load(JDBCUtils.class.getClassLoader().getResourceAsStream("jdbc.properties"));
        } catch (IOException e) {
            e.printStackTrace();
        }

        DRIVERCLASS = properties.getProperty("driverClass");
        URL = properties.getProperty("url");
        USERNAME = properties.getProperty("username");
        PASSWORD = properties.getProperty("password");
    }
    /**
     * 注册驱动
     */
    public static void registDriver() throws ClassNotFoundException {
        Class.forName(DRIVERCLASS);
    }

    /**
     * 获取连接
     */
    public static Connection getConnection() throws Exception {
        registDriver();
        return DriverManager.getConnection(URL, USERNAME, PASSWORD);
    }

    /**
     * 释放资源
     */
    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;
        }
    }
    // overload release
    public static void release(ResultSet resultSet,Statement statement,Connection connection){
        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(connection != null){
            try {
                connection.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
            connection = null;
        }
    }
}

jdbc.properties 文件配置

driverClass=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/jdbctest?useUnicode=true&characterEncoding=utf8
username=root
password=root

TestJDBCUtils 类

import org.junit.Test;

import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;

public class TestJDBCUtils {
    // 测试工具类
    @Test
    public void test(){
        Connection connection = null;
        Statement statement = null;
        try {
            connection = JDBCUtils.getConnection();
            statement = connection.createStatement();
            String sql = "INSERT goods VALUES(NULL,'烤箱',1000.0,'专门烤鸡胸肉');";
            int result = statement.executeUpdate(sql);
            if(result > 0){
                System.out.println("insert succeed");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            JDBCUtils.release(statement,connection);
        }
    }
}

SQL注入漏洞

什么是SQL注入漏洞

首先我们来看一段程序,该程序模拟的是用户登录情景,用到的数据表是user表:


import jdbcutils.JDBCUtils;
import org.junit.Test;

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

public class SQLInjectionVulnerabilityTest {

    @Test
    public void testSQLInjecton(){
        if(SQLInjectionVulnerabilityTest.login("aaa","111")){
            System.out.println("用户登录成功");
        }else{
            System.out.println("用户登录失败");
        }
    }

    public static boolean login(String username, String password) {
        Connection connection = null;
        Statement statement = null;
        ResultSet resultSet = null;
        boolean result = false;

        try {
            connection = JDBCUtils.getConnection();
            statement = connection.createStatement();
            String sql = "SELECT * FROM user where username = '" + username + "' AND password = '" + password + "';";
            resultSet = statement.executeQuery(sql);
            result = resultSet.next() ? true : false;
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            JDBCUtils.release(resultSet,statement,connection);
        }
        return result;
    }
}

因为数据表中有username为张三的用户,而且张三的密码为111,所以返回的结果自然是用户登录成功



那么,什么是SQL注入呢?在我们知道username的情况下,假设我们不知道用户的密码:

import jdbcutils.JDBCUtils;
import org.junit.Test;

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

public class SQLInjectionVulnerabilityTest {

    @Test
    public void testSQLInjecton(){
        if(SQLInjectionVulnerabilityTest.login("aaa ' or '1 = 1","xjbx")){
            System.out.println("用户登录成功");
        }else{
            System.out.println("用户登录失败");
        }

    }

    public static boolean login(String username, String password) {
        Connection connection = null;
        Statement statement = null;
        ResultSet resultSet = null;
        boolean result = false;

        try {
            connection = JDBCUtils.getConnection();
            statement = connection.createStatement();
            String sql = "SELECT * FROM user where username = '" + username + "' AND password = '" + password + "';";
            resultSet = statement.executeQuery(sql);
            result = resultSet.next() ? true : false;
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            JDBCUtils.release(resultSet,statement,connection);
        }
        return result;
    }
}

如果在登录用户名的输入框中,我们输入了aaa ' or '1 = 1这样的语句,那么我们需要查询的sql语句就会为:

SELECT  * FROM user where username = 'aaa' or '1 = 1' AND password = 'xjbx';

因为or运算的断路特性,这条语句必然会执行成功,所以即使密码输入错误,但是我们通过这种SQL注入的方式也可以返回登录成功


JDBC的SQL注入漏洞的解决

解决SQL注入的方法就是弃用Statement对象改用PreparedStatement。
PreparedStatement是Statement的子接口,它的实例对象可以通过调用Connection.preparedStatement(sql)方法来获得,相对于Statement对象而言:

  • PreparedStatement可以避免SQL注入的问题
  • Statement会使数据库频繁地编译SQL,可能造成数据库的缓冲区溢出。PreparedStatement则可以对SQL进行预编译,从而提高数据库的执行效率
  • PreparedStatement对于SQL中的参数,允许使用占位符的形式进行替换,从而简化SQL语句的编写,并且能够有效避免SQL注入。

示例程序如下:

import jdbcutils.JDBCUtils;
import org.junit.Test;

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

public class SQLInjectionVulnerabilityTest {

    @Test
    public void testSQLInjecton() {
        if (SQLInjectionVulnerabilityTest.loginProtectSQLInjection("aaa ' or '1 = 1", "xjbx")) {
            System.out.println("用户登录成功");
        } else {
            System.out.println("用户登录失败");
        }

    }

    public static boolean loginProtectSQLInjection(String username, String password) {
        Connection connection = null;
        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;
        boolean result = false;

        try {
            connection = JDBCUtils.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();
            result = resultSet.next() ? true : false;
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            JDBCUtils.release(resultSet,preparedStatement,connection);
        }
        return result;
    }
}

带有SQL注入的语句测试结果如下:



将username变为aaa,password变为111,即正确的用户格式,测试情况如下:


PreparedStatement

插入记录

依旧对user表进行操作:



向user表中插入一条数据:

username : ddd
password:444
name:王五

代码如下:

import jdbcutils.JDBCUtils;
import org.junit.Test;

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

public class PreparedStatementTest1 {
    @Test
    public void test(){
        Connection connection = null;
        PreparedStatement preparedStatement = null;

        try {
            connection = JDBCUtils.getConnection();
            String sql = "INSERT INTO user VALUES(NULL,?,?,?);";
            preparedStatement = connection.prepareStatement(sql);
            preparedStatement.setString(1,"ddd");
            preparedStatement.setString(2,"444");
            preparedStatement.setString(3,"王五");
            int res = preparedStatement.executeUpdate();

            if(res > 0){
                System.out.println("INSERT SUCCEED");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            JDBCUtils.release(preparedStatement,connection);
        }
    }
}

插入记录的结果成功。


更新与删除记录

依旧对user表进行操作,现在的需求是:将id = 4的用户的name字段的值更新为赵六;代码如下:

import jdbcutils.JDBCUtils;
import org.junit.Test;

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

public class PreparedStatementTest1 {

    @Test
    public void testUpdate(){
        Connection connection = null;
        PreparedStatement preparedStatement = null;

        try {
            connection = JDBCUtils.getConnection();
            String sql = "UPDATE user SET name = ? WHERE uid = ?;";
            preparedStatement = connection.prepareStatement(sql);
            preparedStatement.setString(1,"赵六");
            preparedStatement.setInt(2,4);
            int res = preparedStatement.executeUpdate();
            if(res > 0){
                System.out.println("UPDATE SUCCEED");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            JDBCUtils.release(preparedStatement,connection);
        }
    }

    @Test
    public void test(){
        Connection connection = null;
        PreparedStatement preparedStatement = null;

        try {
            connection = JDBCUtils.getConnection();
            String sql = "INSERT INTO user VALUES(NULL,?,?,?);";
            preparedStatement = connection.prepareStatement(sql);
            preparedStatement.setString(1,"ddd");
            preparedStatement.setString(2,"444");
            preparedStatement.setString(3,"王五");
            int res = preparedStatement.executeUpdate();

            if(res > 0){
                System.out.println("INSERT SUCCEED");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            JDBCUtils.release(preparedStatement,connection);
        }
    }
}

执行结果成功:



再来看删除记录的操作:
现在我们将uid = 4的记录删除:
代码如下:

    @Test
    public void testDelete(){
        Connection connection = null;
        PreparedStatement preparedStatement = null;

        try {
            connection = JDBCUtils.getConnection();
            String sql = "DELETE FROM user WHERE uid = ?;";
            preparedStatement = connection.prepareStatement(sql);
            preparedStatement.setInt(1,4);
            int res = preparedStatement.executeUpdate();
            if(res > 0){
                System.out.println("DELETE SUCCEED");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            JDBCUtils.release(preparedStatement,connection);
        }
    }

执行语句成功:


查询记录

还是以user表为例,查询uid = 3的记录
代码如下:

    @Test
    public void testQuery(){
        Connection connection = null;
        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;

        try {
            connection = JDBCUtils.getConnection();
            String sql = "SELECT * FROM user WHERE uid = ?;";
            preparedStatement = connection.prepareStatement(sql);
            preparedStatement.setInt(1,3);
            resultSet = preparedStatement.executeQuery();
            while(resultSet.next()){
                int id = resultSet.getInt("uid");
                String username = resultSet.getString("username");
                String password = resultSet.getString("password");
                String name = resultSet.getString("name");
                System.out.println(id + " " + username + " " + password + " " + name);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            JDBCUtils.release(resultSet,preparedStatement,connection);
        }
    }

查询成功:


数据库连接池C3P0

连接池概述

连接池是创建和管理一个连接的缓冲池技术,这些连接准备好被任何需要他们的线程使用。那么为什么要使用连接池呢?
如果没有连接池,直接由应用程序获取连接的示例图是这样的:


这样做的缺点就是:用户每次请求都需要向数据库获得连接,而数据库创建连接通常需要消耗相对较大的资源,而且创建时间也比较长。如果网站一天有十万的访问量,数据库就需要创建十万次连接,极大地浪费了数据库的资源,并且很容易造成数据库服务器内存溢出
而有了数据库连接池,则会变为这样:


数据库连接池是一块内存,这块内存中存者许多连接对象,请求不会直接面向数据库,而是会从连接池里面调用连接对象,使用之后再返还给连接池,这样我们就大大节省了数据库创建连接的消耗,达到了节省资源的作用。

C3P0 连接池的使用

手动创建连接池

我们可以直接手动创建连接池

    @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://localhost:3306/jdbctest?useUnicode=true&characterEncoding=utf8");
            dataSource.setUser("root");
            dataSource.setPassword("root");
            // 设置连接池最大的连接数量
            dataSource.setMaxPoolSize(20);
            // 设置连接池初始化的连接数量
            dataSource.setInitialPoolSize(5);
            connection = dataSource.getConnection();
            String sql = "SELECT * FROM user;";
            preparedStatement = connection.prepareStatement(sql);
            resultSet = preparedStatement.executeQuery();
            while(resultSet.next()){
                int id = resultSet.getInt("uid");
                String username = resultSet.getString("username");
                String password = resultSet.getString("password");

                System.out.println(id + " " + username + " " + password);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            JDBCUtils.release(resultSet,preparedStatement,connection);
        }
    }

对于上述程序的DriverClass或者是JdbcUrl,User,Password等参数,我们还可以进一步优化,将其写入到配置文件中。

使用配置文件的形式

首先我们需要下载C3P0的jar包并添加到Dependencies。然后配置好c3p0-config.xml文件,配置格式如下:

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

  <default-config>
    <property name="driverClass">com.mysql.jdbc.Driver</property>
    <property name="jdbcUrl">jdbc:mysql://localhost:3306/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>

然后封装好我们的工具类:

import com.mchange.v2.c3p0.ComboPooledDataSource;

import java.sql.*;


public class JDBCUtils2 {

    private static final ComboPooledDataSource combo = new ComboPooledDataSource();

    /**
     * 获取连接
     */
    public static Connection getConnection() throws Exception {
        return  combo.getConnection();
    }

    /**
     * 释放资源
     */
    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;
        }
    }
    // overload release
    public static void release(ResultSet resultSet,Statement statement,Connection connection){
        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(connection != null){
            try {
                connection.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
            connection = null;
        }
    }
}

我们的程序如下:

    @Test
    public void demo2(){
        Connection connection = null;
        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;

        try {
            connection = JDBCUtils2.getConnection();
            String sql = "SELECT * FROM user;";
            preparedStatement = connection.prepareStatement(sql);
            resultSet = preparedStatement.executeQuery();
            while(resultSet.next()){
                int id = resultSet.getInt("uid");
                String username = resultSet.getString("username");
                String password = resultSet.getString("password");

                System.out.println(id + " " + username + " " + password);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            JDBCUtils2.release(resultSet,preparedStatement,connection);
        }
    }

比起手动创建连接池,这样做的优点是,只有一个连接池对象,不需要每次都创建连接池。