JDBC

一、概念

Java数据库连接,(Java Database Connectivity,简称JDBC)是Java语言中用来规范客户端程序如何来访问数据库的应用程序接口,提供了诸如查询和更新数据库中数据的方法。JDBC也是Sun Microsystems的商标。我们通常说的JDBC是面向关系型数据库的。

二、JDBC 的作用

2.1 JDBC 常用的包 & 接口
  • java.sql:JDBC 操作的时候,数据库相关的接口和类。
  • javax.sql:扩展包,可以提供额外的功能:连接池。
  • 驱动包:mysql-connector-java-5.1.48.jar 一般都是厂家提供,厂家针对。
  • JDBC 规范提供出来的接口,进行实现。都是写好的 Java 源码。
  • DriverManager:主要用来管理和注册驱动,获取数据库连接对象。
  • Connection 接口:负责与数据库进行连接。
  • Statement 接口:执行者对象,主要把 SQL 语句发送到数据库进行交互。
  • PreparedStatement 接口:执行者对象,Statement 的子类,更安全。
  • ResultSet 接口:用来封装从数据库中获得的数据,进而可以将数据封装到JavaBean。
2.2 加载和注册
  • 在使用的过程中需要注册和加载驱动
// 1)注册和加载驱动
Class.forName("com.mysql.jdbc.Driver");

使用了反射,只需要你指定类的名字,它会获取类的名字,也就是类所在的路径,然后自动去实例化它,加载到堆内存中。

在Driver.class中注册的源码:

public class Driver extends NonRegisteringDriver implements java.sql.Driver {
    //
    // Register ourselves with the DriverManager
    //
    static {
        try {
            java.sql.DriverManager.registerDriver(new Driver());
        } catch (SQLException E) {
            throw new RuntimeException("Can't register driver!");
        }
    }

但是如果使用 mysql8,驱动包也是 mysql8,在指定驱动名,需要改成

Class.forName("com.mysql.cj.jdbc.Driver");
2.3 DriverMananger

主要负责管理和注册驱动,还能获取 Connection 数据库连接对象。

Connection 接口里有如下方法:

static Connection   getConnection(String url)
                    尝试建立与给定数据库URL的连接。
static Connection   getConnection(String url, Properties info)
                    尝试建立与给定数据库URL的连接。
static Connection   getConnection(String url, String user, String password)
                    尝试建立与给定数据库URL的连接。
static Driver       getDriver(String url)
                    尝试查找了解给定URL的驱动程序。

语法格式:

  • 协议名:子协议名://服务器名:端口号/数据库名
  • jdbc:mysql://localhost:3306/java01
  • 如果连接的是本地的数据库,简约写法:jdbc:mysql:///java01

代码实现:

String url = "jdbc:mysql://localhost:3306/java01";
String user = "root";
String password = "1234";
Connection conn = DriverManager.getConnection(url,user, password);

正式开发的时候,建议不要把数据冗余到业务代码中,最好是把他们分开。可以使用配置文件:xml、properties、yml 文件。比如xxx.properties文件,里面包含连接信息。

jdbc.url="jdbc:mysql://localhost:3306/java01"
jdbc.user="root"
jdbc.password="1234"
2.4 Connection 连接接口
  • Connection 主要是数据库连接对象。
Statement   createStatement()
            创建一个 Statement对象,用于将SQL语句发送到数据库。
Statement   createStatement(int resultSetType, int resultSetConcurrency)
            创建一个 Statement对象,该对象将生成具有给定类型和并发性的 ResultSet对象。
Statement   createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability)
            创建一个 Statement对象,将产生 ResultSet对象具有给定类型,并发性和可保存性。
  • 调用 createStatement() 方法,可以获得执行者对象,发送 SQL 到数据库执行。
2.5 Statement 执行者接口
jdbc1.png

常用的方法:

ResultSet   executeQuery(String sql)
            执行给定的SQL语句,该语句返回单个 ResultSet对象。
int         executeUpdate(String sql)
            执行给定的SQL语句,这可能是 INSERT , UPDATE ,或 DELETE语句,或者不返回任何内容,如SQL DDL语句的SQL语句。
  • 整个流程如下:
    1)注册和加载驱动
    2)获取连接对象 Connection
    3)获取 Statement 执行者
    4)执行 SQL
    5)获取并处理 ResultSet
    6)释放资源

  • 关于释放资源:

释放的时候,特别要注意的是,资源释放的顺序:先开的后关,后开的先关掉。(也可以记成,从下往上关),否则会报空指针异常。
先关 ResultSet -> 再到 Statement 1 -> 最后 Connection

三、案例:

查询表格中所有的数据。

DB数据信息:


jdbc2.png

代码:

public class Demo1 {

    public static void main(String[] args) {

        Connection conn = null;
        Statement stmt = null;
        ResultSet rs = null;

        try {
            // 1)注册和加载驱动
            Class.forName("com.mysql.jdbc.Driver");

            // 2)获取连接对象 Connection
            String url = "jdbc:mysql:///java01";
            conn = DriverManager.getConnection(url, "root", "root");

            // 3)获取 Statement 执行者
            stmt = conn.createStatement();

            // 4)执行 SQL
            String sql = "select * from stu";
            // 所有的数据,都封装在结果集中
            rs = stmt.executeQuery(sql);

            // 5)获取并处理 ResultSet
            while (rs.next()) {
                // 操作数据
                System.out.println(rs.getInt("id") + "|" + rs.getString("name") + "|" + rs.getInt("sex") + "|"
                        + rs.getString("address"));
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally { // 一般释放资源的操作都建议放到 finally 块中
            // 因为哪怕上面出错了,报异常了,最终都会执行这里面的代码
            // 6)释放资源
            // 释放的原则:先判断,需要释放的资源,在不在
            if (rs != null) {
                try {
                    rs.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if (stmt != null) {
                try {
                    stmt.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if (conn != null) {
                try {
                    conn.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
    }

运行结果:


jdbc3.png

Insert 一条数据到表里:

// 1)注册和加载驱动
            Class.forName("com.mysql.jdbc.Driver");

            // 2)获取连接对象 Connection
            String url = "jdbc:mysql:///java01";
            conn = DriverManager.getConnection(url, "root", "root");

            // 3)获取 Statement 执行者
            stmt = conn.createStatement();

            // 4)执行 SQL
            String sql = "insert into stu values(null, 'dongdong','aa', 1, 'HAHA')";

            int flag = stmt.executeUpdate(sql);

            if (flag == 1) {
                System.out.println("执行成功了");
            }
jdbc4.png

update数据:

            // 4)执行 SQL
            String sql = "update stu set name = 'cuicui' where id = 2";
            
            int flag = stmt.executeUpdate(sql);
            
            if(flag == 1) {
                System.out.println("执行成功了");
            }

四、工具类

可以将上述的注册驱动、获取Connection连接对象、关闭资源等都封装进一个工具类里面,可以使得代码更简洁。

// JDBC 工具类
public class JdbcUtil {

    // 连接数据库的信息
    private static final String URL = "jdbc:mysql:///java01";
    private static final String USER = "root";
    private static final String PASSWORD = "root";
    private static final String DRIVER = "com.mysql.jdbc.Driver";

    // 注册驱动
    static {
        // 只需要注册一次即可,不用重复注册
        try {
            Class.forName(DRIVER);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // 获取数据库 Connection 连接对象
    public static Connection getConnection() throws SQLException {
        return DriverManager.getConnection(URL, USER, PASSWORD);
    }

    // 关闭资源
    public static void closeResource(Connection conn, Statement stmt, ResultSet rs) {
        if (rs != null) {
            try {
                rs.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }

        if (stmt != null) {
            try {
                stmt.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }

        if (conn != null) {
            try {
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }

    public static void closeResource(Connection conn, Statement stmt) {

        if (stmt != null) {
            try {
                stmt.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }

        if (conn != null) {
            try {
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }

五、PreparedStatement 执行者子类接口

5.1使用PreparedStatement的原因
  • 如果使用 Statement 来发送 SQL 的话,量大时,就会导致访问流量激增。Statement 对象执行每一条 SQL 语句的时候,都会先把 SQL 语句发送到数据库中,然后进行编译,最后才能执行。如果数据量非常巨大的情况下会造成性能的问题。

  • 而PreparedStatement是预编译的,对于批量处理可以大大提高效率. 也叫JDBC存储过程。如果使用了 PreparedStatement 类,首先会将你的 SQL 发送到数据库中进行预编译动作,然后会直接引用预编译之后的结果,如果你的 SQL 需要传递参数的话,也可以多次传入不同的参数给这个对象并执行。不管有多少条 SQL 插入语句,数据库只需要预编译一次即可,可以传入多次参数,并执行。这里主要是减少了预编译的次数,提高了执行的效率。

5.2 创建 PreparedStatement 对象
CallableStatement   prepareCall(String sql)
                    创建一个调用数据库存储过程的 CallableStatement对象。
CallableStatement   prepareCall(String sql, int resultSetType, int resultSetConcurrency)
                    创建一个 CallableStatement对象,该对象将生成具有给定类型和并发性的 ResultSet对象。
CallableStatement   prepareCall(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability)
                    创建一个 CallableStatement对象,该对象将生成具有给定类型和并发性的 ResultSet对象。
PreparedStatement   prepareStatement(String sql)
                    创建一个 PreparedStatement对象,用于将参数化的SQL语句发送到数据库。
PreparedStatement   prepareStatement(String sql, int autoGeneratedKeys)
                    创建一个默认的 PreparedStatement对象,该对象具有检索自动生成的密钥的能力。
PreparedStatement   prepareStatement(String sql, int[] columnIndexes)
                    创建一个默认的 PreparedStatement对象,能够返回给定数组指定的自动生成的键。
PreparedStatement   prepareStatement(String sql, int resultSetType, int resultSetConcurrency)
                    创建一个 PreparedStatement对象,该对象将生成具有给定类型和并发性的 ResultSet对象。
PreparedStatement   prepareStatement(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability)
                    创建一个 PreparedStatement对象,将产生 ResultSet对象具有给定类型,并发性和可保存性。
PreparedStatement   prepareStatement(String sql, String[] columnNames)
                    创建一个默认的 PreparedStatement对象,能够返回给定数组指定的自动生成的键。
5.3 PreparedStatement 常用方法
ResultSet   executeQuery()
            执行此 PreparedStatement对象中的SQL查询,并返回查询 PreparedStatement的 ResultSet对象。
int executeUpdate()
            执行在该SQL语句PreparedStatement对象,它必须是一个SQL数据操纵语言(DML)语句,如INSERT,UPDATE或DELETE ; 或不返回任何内容的SQL语句,例如DDL语句。
5.4 使用 PreparedStatement 的步骤
  • 因为支持预编译,所以在这里能使用占位符,将来动态传递参数
    1.准备 SQL 语句,如果有参数,则使用 ? 占位符
    2.创建 PreparedStatement 对象
    3.提供上面占位符需要的参数,使用 setXxx() 方法来设置
    4.执行 SQL 语句
    5.处理结果集
    6.关闭资源
    例:
public class Demo {

    public static void main(String[] args) {

        // 1. 使用 Scanner 类来接收用户输入的信息
        Scanner sc = new Scanner(System.in);

        // 用户名
        System.out.println("用户名:");
        String name = sc.nextLine();

        // 密码
        System.out.println("密码:");
        String pass = sc.nextLine();

        // 2. 将接收到的数据,传入登录方法中进行登录
        login(name, pass);

    }

    // 登录方法
    public static void login(String userName, String passWord) {

        // 1. 定义变量
        Connection conn = null;
        PreparedStatement ptmt = null;
        ResultSet rs = null;

        // 2.
        try {
            // 获取连接
            conn = JdbcUtil.getConnection();

            // SQL 语句
            String sql = "select * from stu where name = ? and pass = ?";

            // 执行者
            ptmt = conn.prepareStatement(sql);

            // 设置参数
            ptmt.setString(1, userName);
            ptmt.setString(2, passWord);

            // 执行 SQL 并且获取结果
            rs = ptmt.executeQuery();

            // 处理结果
            if (rs.next()) {
                System.out.println("登录成功!恭喜!");
            } else {
                System.out.println("登录失败!检查一下再来!");
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            // 关闭资源
            JdbcUtil.closeResource(conn, ptmt, rs);
        }
    }
}

运行结果:


jdbc5.png
5.4 封装数据
jdbc6.png
  1. 返回单个数据
    需要提供一个映射,所以要创建一个 JavaBean 类,比如 User、Student、Person。
    先建立Student类:
public class Student {
    
    private int id;
    private String name;
    private String pass;
    private int sex;
    private String address;
    
    // 构造器
    public Student() {
        super();
    }
    public Student(int id, String name, String pass, int sex, String address) {
        super();
        this.id = id;
        this.name = name;
        this.pass = pass;
        this.sex = sex;
        this.address = address;
    }
    
    // get & set 方法
    // toString 方法
}

Demo1:

public class Demo1 {

    public static void main(String[] args) throws SQLException {

        // 1. 创建 stu 对象
        Student stu = new Student();
        
        // 2. 获取 Connection 对象
        Connection conn = JdbcUtil.getConnection();
        
        // 3. 准备 SQL 语句
        String sql = "select * from stu where id = ?";
        
        // 4. 预编译
        PreparedStatement ptmt = conn.prepareStatement(sql);
        
        // 5. 设置参数,查询id=3的数据信息
        ptmt.setInt(1, 3);
        
        // 6. 执行并获取结果
        ResultSet rs = ptmt.executeQuery();
        
        // 7. 处理结果:查询数据表中的数据,然后封装到 Student 对象中
        if(rs.next()) {
            stu.setId(rs.getInt("id"));
            stu.setName(rs.getString("name"));
            stu.setPass(rs.getString("pass"));
            stu.setSex(rs.getInt("sex"));
            stu.setAddress(rs.getString("address"));
        }

        // 8. 释放资源
        JdbcUtil.closeResource(conn, ptmt, rs);
        
        // 9. 验证
        System.out.println(stu);
    }
}

结果如下:

Student [id=3, name=dongdong, pass=aa, sex=1, address=moon]
  1. 返回集合数据
public class Demo2 {

    public static void main(String[] args) throws SQLException {

        // 1. 创建 List 集合,用来接收所有数据
        ArrayList<Student> stuList = new ArrayList<>();
        
        // 2. 获取 Connection 对象
        Connection conn = JdbcUtil.getConnection();
        
        // 3. 准备 SQL 语句
        String sql = "select * from stu";
        
        // 4. 预编译
        PreparedStatement ptmt = conn.prepareStatement(sql);
        
        // 5. 执行并获取结果
        ResultSet rs = ptmt.executeQuery();
        
        // 6. 处理结果:查询数据表中的数据,然后封装到 Student 对象中
        while(rs.next()) {
            
            // 初始化 Student
            Student stu = new Student();
            
            stu.setId(rs.getInt("id"));
            stu.setName(rs.getString("name"));
            stu.setPass(rs.getString("pass"));
            stu.setSex(rs.getInt("sex"));
            stu.setAddress(rs.getString("address"));
            
            // 将 stu 保存到 stuList 集合中
            stuList.add(stu);
        }

        // 8. 释放资源
        JdbcUtil.closeResource(conn, ptmt, rs);
        
        // 9. 验证
        for(Student stu : stuList) {
            System.out.println(stu);
        }
    }
}

结果:

Student [id=2, name=慢慢, pass=fd, sex=0, address=山东]
Student [id=3, name=dongdong, pass=aa, sex=1, address=moon]
Student [id=4, name=lxl, pass=123, sex=1, address=北京]
Student [id=5, name=dongdong, pass=aa, sex=1, address=HAHA]

六、常见 DML 操作

  • nsert、update、delete
public class Demo3 {

    public static void main(String[] args) throws SQLException {

        // 调用方法
         //insertData();
         updateData();
        // deleteData();
    }
    
    // 插入数据
    private static void insertData() throws SQLException {
        
        Connection conn = JdbcUtil.getConnection();
        
        PreparedStatement ptmt = conn.prepareStatement("insert into stu values (null, ?, ?, ?, ?)");
        
        ptmt.setString(1, "laoliu");
        ptmt.setString(2, "1234");
        ptmt.setInt(3, 1);
        ptmt.setString(4, "流浪地球");
        
        int count = ptmt.executeUpdate();
        System.out.println("成功插入 " + count + " 条数据。");
        
        JdbcUtil.closeResource(conn, ptmt);
        
    }
    
    // 更新数据
    private static void updateData() throws SQLException {
        
        Connection conn = JdbcUtil.getConnection();
        
        PreparedStatement ptmt = conn.prepareStatement("update stu set name = ? where id = ?");
        
        ptmt.setString(1, "xiaoming");
        ptmt.setInt(2, 5);
        
        int count = ptmt.executeUpdate();
        System.out.println("成功更新 " + count + " 条数据。");
        
        JdbcUtil.closeResource(conn, ptmt);
        
    }
    
    // 删除数据
    private static void deleteData() throws SQLException {
        
        Connection conn = JdbcUtil.getConnection();
        
        PreparedStatement ptmt = conn.prepareStatement("delete from stu where id = ?");
        
        ptmt.setInt(1, 1);
        
        int count = ptmt.executeUpdate();
        System.out.println("成功删除  " + count + " 条数据。");
        
        JdbcUtil.closeResource(conn, ptmt);
        
    }
}

七、事务操作

7.1 新建表格并插入数据。
create table bankAccount (
    id int primary key auto_increment,
    name varchar(50),
    money double
);
jdbc7.png
7.2 常用的方法。
void    commit()
        使自上次提交/回滚以来所做的所有更改都将永久性,并释放此 Connection对象当前持有的任何数据库锁。
void    rollback()
        撤消在当前事务中所做的所有更改,并释放此 Connection对象当前持有的任何数据库锁。
void    rollback(Savepoint savepoint)
        撤消在给定的 Savepoint对象设置后进行的所有更改。
void    setAutoCommit(boolean autoCommit)
        将此连接的自动提交模式设置为给定状态。
7.3 实现步骤

1.获取数据库连接
2.开启事务
3.获取执行者对象,preparedStatement
4.使用 preparedStatement 完成转账,一个账户加钱,一个账户减钱,更新
5.如果没问题,则提交事务
6.如果有问题,则回滚事务
7.finally 块中,可以关闭资源

7.4 转账案例
public class Demo5 {

    public static void main(String[] args) throws SQLException {
        
        Connection conn = null;
        PreparedStatement ptmt = null;
        
        try {
            // 1. 获取数据库连接
            conn = JdbcUtil.getConnection();
            
            // 2. 开启事务
            // 如果不自动提交,就是开启事务(手动操作的事务)
            conn.setAutoCommit(false);
            
            // 3. 获取执行者对象,preparedStatement
            // 使用 preparedStatement 完成转账,一个账户加钱,一个账户减钱,更新
            // 支出方
            String sql = "update bankcount set money = money - ? where name = ?";
            ptmt = conn.prepareStatement(sql);
            
            ptmt.setDouble(1, 500);
            ptmt.setString(2, "cuihua");
            
            // 执行 SQL
            ptmt.executeUpdate();
            
            // 手动在这里,添加一个异常,承训就回到catch块里rollback回来。数据不会改变。
            //如果没有这个异常,那么程序会运行成功,提交事务。
             int i =1 / 0;
        
            // 收入方
            sql = "update bankcount set money = money + ? where name = ?";
            ptmt = conn.prepareStatement(sql);
            
            ptmt.setDouble(1, 500);
            ptmt.setString(2, "huahua");
            
            // 执行 SQL
            ptmt.executeUpdate();
            
            // 4. 如果没问题,则提交事务
            conn.commit();
            System.out.println("转账成功,谢谢使用,下次再来!");
            
        } catch (Exception e) {
        
            // 5. 如果有问题,则回滚事务
            conn.rollback();
            System.out.println("转账失败,再试一下!");
            
            e.printStackTrace();
            
        } finally {
            // 6. 关闭资源
            JdbcUtil.closeResource(conn, ptmt);
        }       
    }
}

八、使用 Properties 配置文件

8.1 添加配置文件:db.properties放在src下面
jdbc.URL=jdbc:mysql:///java01
jdbc.USER=root
jdbc.PASSWORD=root
jdbc.DRIVER=com.mysql.jdbc.Driver
8.2 使用 ResourceBundle 对象加载配置文件
public class JdbcUtil {

    // 连接数据库的信息
    // 建议,我们不要将数据硬编码到代码中,容易产生耦合,将来处理起来非常不方便
    private static String URL;
    private static String USER;
    private static String PASSWORD;
    private static String DRIVER;

    // 注册驱动
    static {
        
        // 读取 db.properties 配置文件中的数据,只需要读取一次即可
        // 此处,我们需要指定的是文件的名字,而不是整个【文件名+扩展名】,否则找不到
        ResourceBundle rb = ResourceBundle.getBundle("db");
        URL = rb.getString("jdbc.URL");
        USER = rb.getString("jdbc.USER");
        PASSWORD = rb.getString("jdbc.PASSWORD");
        DRIVER = rb.getString("jdbc.DRIVER");
        
        // 只需要注册一次即可,不用重复注册
        try {
            Class.forName(DRIVER);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
//其他的不变
}

九、JDBC连接池

  • 连接池主要是用来管理 Connection 对象,减少创建的时候消耗性能,然后还可以重复使用它。当你在使用的时候,如果说访问量激增时,有可能需要等待一定的时间,因为 cpu 会将空闲下来的 Connection 拿来使用,当你使用完了之后,还需要归还到连接池中,让下一个访问者来继续使用。


    JDBC8.png
  • Java 主要在数据连接池方向上,通过提供一套公共的接口:
    java.sql.DataSource。接着,不同的厂商就会根据这套规范,然后制定对应的产品,比如 MyBatis、Hibernate 等框架都有不同的连接池实现方案。这样,其实是方便了 Java 开发者,我们开发者其实只需要提供一套代码,就可以在不同的厂商产品之间进行切换,不需要针对每个不同的连接池产品去修改我们的代码。

  • 常用的连接池:DBCP、C3P0

9.1 自定义连接池
  1. 先要根据数据源的规范,创建连接池的实现类(数据源的实现),需要实现 java.sql.DataSource 接口。
  2. 使用一个 LinkedList 集合,用来存放指定数量的 Connection 对象。
  3. 使用一个静态块,提前初始化一些 Connection 对象供使用,如果不够,后面再根据具体情况去添加。
  4. 提供 getConnection() 方法,让调用者获取连接对象,缓冲池中必须要给一个连接对象出来。
  5. 当使用完,释放掉资源之后,就需要回收 Connection 对象,给下一个人用。

实现:

public class JdbcUtil {

    // 连接数据库的信息
    private static String URL;
    private static String USER;
    private static String PASSWORD;
    private static String DRIVER;

    // 1. 创建集合,当做是一个连接池,用来存放 Connection 对象
    private static LinkedList<Connection> connPool = new LinkedList<>();

    // 注册驱动
    static {

        // 读取 db.properties 配置文件中的数据,只需要读取一次即可
        // 此处,我们需要指定的是文件的名字,而不是整个【文件名+扩展名】,否则找不到
        ResourceBundle rb = ResourceBundle.getBundle("db");
        URL = rb.getString("jdbc.URL");
        USER = rb.getString("jdbc.USER");
        PASSWORD = rb.getString("jdbc.PASSWORD");
        DRIVER = rb.getString("jdbc.DRIVER");

        try {
            Class.forName(DRIVER);

            // 通过一个 for 循环,来连续提供多个 Connection 对象,供使用
            for (int i = 0; i < 10; i++) {

                Connection conn = DriverManager.getConnection(URL, USER, PASSWORD);

                // 添加到池子中(集合)
                connPool.add(conn);
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取数据库 Connection 连接对象
     * 
     * @return
     * @throws SQLException
     */
    public static Connection getConnection() {

        try {

            if (!connPool.isEmpty()) {

                // 如果池子中有 Connection 对象,则直接获取使用,池子中对应减少一个 Connection 对象
                Connection conn = connPool.removeFirst();

                return conn;
            }

            // 如果池子中没有 Connection 对象,最好是稍微等待空闲的 Connection,先不建议直接去创建。
            // 等待 200ms
            Thread.sleep(100);

            // 当睡醒之后,重新获取一次 Connection 对象
            return getConnection();

        } catch (Exception e) {

            // 如果有问题,直接抛一个运行时异常
            throw new RuntimeException();
            // e.printStackTrace();
        }
    }

    /**
     * 关闭资源
     * 
     * @param conn
     * @param stmt
     * @param rs
     */
    public static void closeResource(Connection conn) {

        if (conn != null) {
            try {
                // 原来 connection 用完之后,就直接关闭了,
                // 现在我们需要重复使用它,就不需要再关闭
                // conn.close();
                // 如果将来,你不希望使用 Connection 自带的 close() 方法直接将 Connnection 对象从堆内存中直接释放掉
                // 则可以考虑重写 close() 方法,将 Connection 对象,放回到一个集合(连接池)中

                // 直接将 conn 再次添加到 连接池中
                connPool.add(conn);

            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

Properties 配置文件

jdbc.URL=jdbc:mysql:///java01
jdbc.USER=root
jdbc.PASSWORD=root
jdbc.DRIVER=com.mysql.jdbc.Driver
// 使用线程类去获取 Connection 对象
class HelloConnection extends Thread {

    @Override
    public void run() {
        // 1. 获取 Connection
        Connection conn = JdbcUtil.getConnection();

        // 2. 使用方式:展示当前线程
        System.out.println(conn + "---" + Thread.currentThread());

        // 3. 释放资源(里面会自动归还 conn)
        JdbcUtil.closeResource(conn);
    }
}

public class Demo {

    public static void main(String[] args) {

        // 模仿多个人去使用 Connection
        for (int i = 0; i < 10; i++) {

            // 启动线程
            new HelloConnection().start();
        }
    }
}

运行结果:

com.mysql.jdbc.JDBC4Connection@2d330656---Thread[Thread-0,5,main]
com.mysql.jdbc.JDBC4Connection@118a576f---Thread[Thread-8,5,main]
com.mysql.jdbc.JDBC4Connection@10b7ce35---Thread[Thread-5,5,main]
com.mysql.jdbc.JDBC4Connection@17b35baa---Thread[Thread-9,5,main]
com.mysql.jdbc.JDBC4Connection@72f4d50a---Thread[Thread-2,5,main]
com.mysql.jdbc.JDBC4Connection@6900a9fc---Thread[Thread-6,5,main]
com.mysql.jdbc.JDBC4Connection@4a6382ee---Thread[Thread-1,5,main]
com.mysql.jdbc.JDBC4Connection@11d45617---Thread[Thread-7,5,main]
com.mysql.jdbc.JDBC4Connection@b7127ff---Thread[Thread-4,5,main]
com.mysql.jdbc.JDBC4Connection@2c30d2e4---Thread[Thread-3,5,main]
9.2 C3P0 连接池
  • 开源产品,Spring、Hibernate 等框架都有使用它。
    步骤:
    1.导入jar包到lib目录下,并build path
c3p0-0.9.5.2.jar
mchange-commons-java-0.2.12.jar

2.配置c3p0-config.xml文件

<c3p0-config>
  
  <!-- 使用默认的配置读取连接池对象 -->
  <default-config>
  
    <!--  连接参数 -->
    <property name="driverClass">com.mysql.jdbc.Driver</property>
    <property name="jdbcUrl">jdbc:mysql:///java01</property>
    <property name="user">root</property>
    <property name="password">root</property>
    
    <!-- 连接池参数 -->
    <property name="initialPoolSize">5</property>
    <property name="maxPoolSize">10</property>
    <property name="checkoutTimeout">3000</property>
  </default-config>
</c3p0-config>
public class C3P0Util {

    // 定义 ComboPooledDataSource 对象,来使用默认配置
    // 因为 c3p0-config 是默认的名字,如果不写也是可以的
    private static ComboPooledDataSource dataSource = new ComboPooledDataSource();

    // 如果 c3p0 管理了多个数据源,除开默认的,其他的需要指定名字才可以去调用到
    // private static ComboPooledDataSource dataSource = new ComboPooledDataSource("abc");

    /**
     * 获取数据源(Connection 连接池)
     * 
     * @return
     */
    public static DataSource getDataSource() {

        return dataSource;
    }

    /**
     * 获取 Connection 连接对象
     * 
     * @return
     */
    public static Connection getConnection() {
        try {
            return dataSource.getConnection();
        } catch (SQLException e) {
            // e.printStackTrace();
            throw new RuntimeException(e);
        }
    }
public static void closeResource(Connection conn) {
        if (conn != null) {
            try {
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

插入一条数据:

public class TestC3P0 {

    public static void main(String[] args) throws SQLException {

        Connection conn = C3P0Util.getConnection();
        // System.out.println(conn);

        PreparedStatement ptmt = conn.prepareStatement("insert into stu values (null, ?, ?, ?, ?)");

        ptmt.setString(1, "l2l134");
        ptmt.setString(2, "123123");
        ptmt.setInt(3, 1);
        ptmt.setString(4, "合肥");

        int count = ptmt.executeUpdate();
        System.out.println("成功插入 " + count + " 条数据。");

        C3P0Util.closeResource(conn);

    }

}
jdbc9.png
  • 如果需要配置多个不同的数据源,则可以使用 named-config 标签来设置
    例如在xml文件加入如下配置:
<c3p0-config>
<named-config name="abc"> 
     连接参数
    <property name="driverClass">com.mysql.jdbc.Driver</property>
    <property name="jdbcUrl">jdbc:mysql:///java01</property>
    <property name="user">root</property>
    <property name="password">root</property>
    
    连接池参数
    <property name="initialPoolSize">5</property>
    <property name="maxPoolSize">8</property>
    <property name="checkoutTimeout">1000</property>
  </named-config>
  
</c3p0-config>

只需要在C3P0Util中创建ComboPooledDataSource对象是指定名称即可。

private static ComboPooledDataSource dataSource = new ComboPooledDataSource("abc");
9.3 使用 DBCP 连接池

导入jar包


jdbc10.png
  • 配置文件
  • database.properties
# DBCP
url=jdbc:mysql:///java01
username=root
password=root
driverClassName=com.mysql.jdbc.Driver

initialSize=10
maxActive=50
maxWait=3000
public class DBCPUtil {

    // 定义数据源 dataSource
    private static DataSource dataSource;

    static {

        try {

            // 加载配置文件:根据类的类路径,就可以找到对应的配置文件
            // 通过 xx.class 就是找到 class 本身
            InputStream in = DBCPUtil.class.getClassLoader().getResourceAsStream("database.properties");

            Properties prop = new Properties();
            // 直接加载上面的输入流即可读取到里面的数据
            prop.load(in);

            // 创建数据源(连接池)
            dataSource = BasicDataSourceFactory.createDataSource(prop);

        } catch (Exception e) {

            throw new RuntimeException(e);
        }

    }

    public static DataSource getDataSource() {

        return dataSource;
    }

    // 获取 Connection
    public static Connection getConnection() {
        try {

            return dataSource.getConnection();

        } catch (SQLException e) {
            // e.printStackTrace();
            throw new RuntimeException(e);
        }
    }

    // 释放资源
    public static void closeResource(Connection conn, Statement stmt) {

        if (stmt != null) {
            try {
                stmt.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }

        if (conn != null) {
            try {
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }

}
public class DBCPDemo {

    public static void main(String[] args) throws SQLException {
        //Connection conn = DBCPUtil.getConnection();
        //Connection conn = DBCPUtil.getConnection();
        Connection conn = DBCPUtil.getDataSource().getConnection();
        System.out.println(conn);

                
        PreparedStatement ptmt = conn.prepareStatement("insert into stu values (null, ?, ?, ?, ?)");

        ptmt.setString(1, "6666");
        ptmt.setString(2, "9999");
        ptmt.setInt(3, 1);
        ptmt.setString(4, "河南");

        int count = ptmt.executeUpdate();
        System.out.println("成功插入 " + count + " 条数据。");

        DBCPUtil.closeResource(conn,ptmt);
    }
}

执行结果:

353842779, URL=jdbc:mysql:///java01, UserName=root@localhost, MySQL Connector Java
成功插入 1 条数据。
jdbc112.png

注意:这里开始执行的时候报了如下错误:

ClassNotFoundException:org.apache.commons.logging.LogFactory

是因为缺少了commons-logging.jar,从网上下载对应的jar,copy 到lib下面再add build path即可。

十、使用 DBUtils 工具类

10.1 JavaBean
  • 就是一个简单的 Java 类,用来封装数据的。
  • 一般的格式是:
  1. 添加字段
  2. 添加构造器
  3. 添加 get\set 方法
  4. 添加 toString() 方法
    案例:

public class Student {

    private int id;
    private String name;
    private String pass;
    private int sex;
    private String address;

    // 构造器
    public Student() {
        super();
    }

    public Student(int id, String name, String pass, int sex, String address) {
        super();
        this.id = id;
        this.name = name;
        this.pass = pass;
        this.sex = sex;
        this.address = address;
    }

    // get & set 方法
    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPass() {
        return pass;
    }

    public void setPass(String pass) {
        this.pass = pass;
    }

    public int getSex() {
        return sex;
    }

    public void setSex(int sex) {
        this.sex = sex;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    // toString 方法
    @Override
    public String toString() {
        return "Student [id=" + id + ", name=" + name + ", pass=" + pass + ", sex=" + sex + ", address=" + address
                + "]";
    }

}

10.2 使用 DBUtils实现增删改查

案例:

public class DBUtilsDemo {

    public static void main(String[] args) throws SQLException {
        
        //selectAllStu();
        //selectStuById();
        //updagteStu();
        inertStu();
    }
    
    public static void selectAllStu() throws SQLException {
        
        Connection conn = JdbcUtil.getConnection();

        // 1. 核心类
        QueryRunner qr = new QueryRunner();

        // 2. 准备 SQL
        String sql = "select * from stu";
        
        // 参数,用数组来存储
        Object[] params = {};

        // 3. 执行
        // 如果你要查的数据是多个的话,使用 BeanListHandler 来自动封装即可
        // 指定 Student.class 是想 DBUtils 查到数据之后,自动帮我们封装到 Student 类上
        List<Student> stuList= qr.query(conn, sql, new BeanListHandler<Student>(Student.class), params);
        
        for(Student stu : stuList) {
        
            System.out.println(stu);
        }
    }
    
    public static void selectStuById() throws SQLException {
        
        Connection conn = JdbcUtil.getConnection();

        // 1. 核心类
        QueryRunner qr = new QueryRunner();

        // 2. 准备 SQL
        String sql = "select * from stu where id = ?";
        
        // 参数,用数组来存储、
        Object[] params = {3};
        

        // 3. 执行
        // 如果你要查的数据是一个的话,使用 BeanHandler 来自动封装即可
        // 指定 Student.class 是想 DBUtils 查到数据之后,自动帮我们封装到 Student 类上
        Student stu = qr.query(conn, sql, new BeanHandler<Student>(Student.class), params);
        
        System.out.println(stu);
    }
    
    public static void deleteStu() throws SQLException {
        
        Connection conn = JdbcUtil.getConnection();

        // 1. 核心类
        QueryRunner qr = new QueryRunner();

        // 2. 准备 SQL
        String sql = "delete from stu where id = 5";

        // 3. 执行
        qr.update(conn, sql);
        
    }
    
    public static void updagteStu() throws SQLException {
        Connection conn = JdbcUtil.getConnection();

        // 1. 核心类
        QueryRunner qr = new QueryRunner();

        // 2. 准备 SQL
        String sql = "update stu set name = '1213141414' where id = 2";

        // 3. 执行
        int flag = qr.update(conn, sql);
        
        if (flag == 1) {
            System.out.println("执行成功");
        } else {
            System.out.println("更新失败,请再次检查sql语句");
        }
        
    }

    public static void inertStu() throws SQLException {
        Connection conn = JdbcUtil.getConnection();

        // 1. 核心类
        QueryRunner qr = new QueryRunner();

        // 2. 准备 SQL
        String sql = "insert into stu values (null, 'hhh', '123', 1, 'beijing')";

        // 3. 执行
        qr.update(conn, sql);
        
        int flag = qr.update(conn, sql);
        if (flag == 1) {
            System.out.println("执行成功");
        } else {
            System.out.println("更新失败,请再次检查sql语句");
        }
    }

}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,277评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,689评论 3 393
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,624评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,356评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,402评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,292评论 1 301
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,135评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,992评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,429评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,636评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,785评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,492评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,092评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,723评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,858评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,891评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,713评论 2 354