Java学习20:JDBC和数据库连接池

JDBC和数据库连接池

JDBC概述

JDBC为访问不同的数据库提供了统一的接口,由数据库厂商实现这些接口,为使用者屏蔽了细节问题,Java程序员使用JDBC,可以连接任何提供了JDBC驱动程序的数据库系统,从而完成对数据库的各种操作。相关的类和接口在java.sql和javax.sql包中

image-20210825002618562

使用MYSQL的JDBC实现

使用MySQL的JDBC实现需要先引入对应的jar包,这里学习使用的是mysql-connector-java-5.1.37-bin.jar

操作数据基本步骤

  1. 注册驱动:加载Driver类
  2. 获取连接:得到Connection
  3. 执行增删改查:发送SQL给mysql执行
  4. 释放资源:关闭相关连接
import com.mysql.jdbc.Driver;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;

public class Connect01 {
    public static void main(String[] args) throws SQLException {
        // 注册驱动
        Driver driver = new Driver();

        String url = "jdbc:mysql://localhost:3306/lsg_db03";
        Properties properties = new Properties();
        properties.setProperty("user","root"); // 用户名
        properties.setProperty("password","root"); // 密码
        
        // 得到连接
        Connection connect = driver.connect(url,properties);

        // 执行sql语句
        String sql = "insert into students values(12, '姜维', 54, 69, 78)";
        Statement statement = connect.createStatement();
        int rows = statement.executeUpdate(sql); // 返回值是改变行数
        
        if (rows > 0){
            System.out.println("成功");
        } else {
            System.out.println("失败");
        }

        // 关闭连接
        statement.close();
        connect.close();
    }
}

注意:

  1. 注意Driver、Connection、Statement等相关类在哪个包下
  2. 这里的com.mysql.jdbc.Driver是实现了java.sql.Driver接口的类
  3. Connection是接口,运行类型是com.mysql.jdbc.JDBC4Connection
  4. Statement是接口,运行类型是class com.mysql.jdbc.StatementImpl

5种连接数据库方种

  1. 创建Driver对象注册驱动
public void connect01() throws SQLException {
    // 注册驱动
    Driver driver = new Driver();

    String url = "jdbc:mysql://localhost:3306/lsg_db03";
    Properties properties = new Properties();
    properties.setProperty("user", "root");
    properties.setProperty("password", "root");

    // 获取连接
    Connection connect = driver.connect(url, properties);

    // 释放资源
    connect.close();
}
  1. 通过反射注册驱动
public void connect02() throws Exception {
    // 加载Driver类,获取Driver类的Class对象
    Class<?> aClass = Class.forName("com.mysql.jdbc.Driver");
    // 通过Driver的Class对象创建Driver实例
    Driver driver = (Driver) aClass.newInstance();

    String url = "jdbc:mysql://localhost:3306/lsg_db03";
    Properties properties = new Properties();
    properties.setProperty("user", "root");
    properties.setProperty("password", "root");

    // 获取连接
    Connection connect = driver.connect(url, properties);

    // 释放资源
    connect.close();
}
  1. 通过DriverManager管理Driver,并获取连接
public void connect03() throws Exception{
    // 加载Driver类,获取Driver类的Class对象
    Class<?> aClass = Class.forName("com.mysql.jdbc.Driver");
    // 通过Driver类的Class对象获取Driver实例
    Driver driver = (Driver) aClass.newInstance();
    // Driver实例交给DriverManager
    DriverManager.registerDriver(driver);

    String url = "jdbc:mysql://localhost:3306/lsg_db03";
    String user = "root";
    String password = "root";
    
    // 通过DriverManager获取connection连接
    Connection connection = DriverManager.getConnection(url, user, password);

    // 释放资源
    connection.close();
}

DriverManager全名是java.sql.DriverManager,是类,用于管理driver

  1. 通过DriverManager管理Driver,并获取连接(优化版)
public void connect04() throws Exception{
    // 类加载时有静态代码块注册到DriverManager
    Class.forName("com.mysql.jdbc.Driver");

    String url = "jdbc:mysql://localhost:3306/lsg_db03";
    String user = "root";
    String password = "root";
    
    // 获取连接
    Connection connection = DriverManager.getConnection(url, user, password);

    // 释放资源
    connection.close();
}

类加载时有静态代码块注册到DriverManager

static {
    try {
        DriverManager.registerDriver(new Driver());
    } catch (SQLException var1) {
        throw new RuntimeException("Can't register driver!");
    }
}
  1. 通过DriverManager管理Driver,读取配置文件获取连接信息,并获取连接(常用版)
public void connect05() throws IOException, ClassNotFoundException, SQLException {
    Properties properties = new Properties();
    properties.load(new FileInputStream("src\\mysql.properties"));

    // 读取配置文件信息
    String user = properties.getProperty("user");
    String password = properties.getProperty("password");
    String url = properties.getProperty("url");
    String driver = properties.getProperty("driver");

	// 注册驱动
    Class.forName(driver); // 可以不写,建议写上

    // 获取连接
    Connection connection = DriverManager.getConnection(url, user, password);

    // 释放资源
    connection.close();
}

注意:

  1. mysql驱动5.1.6可以无需Class.forName(“com.mysql.jdbc.Driver”)
  2. 因为从jdk1.5以后使用了jdbc4,不需要显示调用class.forName()去注册驱动,而是自动调用驱动jar包下META-INF\services\java.sql.Driver文本中的类名称去注册
  3. 但还是建议写上Class.forName(“com.mysql.jdbc.Driver”)

查询

查询结果返回到ResultSet中,这里的ResultSet是java.sql.ResultSet接口,运行类型是com.mysql.jdbc.JDBC42ResultSet

结果集中,最初光标位于第一行之前,ResultSet对象执行next方法,光标就会向下移动一行,当光标移动到最后一行后,再执行next方法会放回false,所以可以使用while循环遍历结果集

public static void main(String[] args) throws Exception {
    // 读取配置信息
    Properties properties = new Properties();
    properties.load(new FileReader("src\\mysql.properties"));
    String user = properties.getProperty("user");
    String password = properties.getProperty("password");
    String url = properties.getProperty("url");
    String driver = properties.getProperty("driver");

    // 注册驱动
    Class.forName(driver); // 建议写上

    // 获取连接
    Connection connection = DriverManager.getConnection(url,user,password);

    // 执行sql语句
    Statement statement = connection.createStatement();
    String sql = "select `uid`,`name`,`birthday` from `user`";
    ResultSet resultSet = statement.executeQuery(sql);

    // 遍历查询结果
    while (resultSet.next()){
        int uid = resultSet.getInt("uid");
        String name = resultSet.getString("name");
        Date birthday = resultSet.getDate("birthday");
        System.out.println(uid + "\t" + name + "\t" + birthday);
    }

    // 释放资源
    resultSet.close();
    statement.close();
    connection.close();
}

注意:

  1. ResultSet还有previous()方法,作用是让光标向上移动一行,如果没有上一行,返回false
  2. 如果没有查询到数据,ResultSet为null

预处理

前面执行sql语句都是用Statement(java.sql.Statement是接口,运行类型是class com.mysql.jdbc.StatementImpl)调用executeQuery方法或executeUpdate方法,这样虽然也可以执行sql语句,但是这样执行的sql语句必须是提前写好的,这样既不方便,又存在sql注入风险

可以通过把Statement替换为PreparedStatement(java.sql.PreparedStatement是接口,运行类型是com.mysql.jdbc.JDBC42PreparedStatement)有效解决这个问题

image-20210825203601895

使用示例

PreparedStatement执行的SQL语句中的参数用占位符 ? 来表示,后面再调用setXxx()方法来设置这些参数

setXxx(参数位置,参数值):参数位置是指第几个参数(从1开始数)

// 模拟用户登录
public static void main(String[] args) throws Exception{
    // 输入账号、密码
    Scanner scanner = new Scanner(System.in);
    System.out.print("请输入账号:");
    String username = scanner.nextLine();
    System.out.print("请输入密码:");
    String pwd = scanner.nextLine();
    
    // 读取配置文件信息
    Properties properties = new Properties();
    properties.load(new FileInputStream("src\\mysql.properties"));
    String user = properties.getProperty("user");
    String password = properties.getProperty("password");
    String url = properties.getProperty("url");
    String driver = properties.getProperty("driver");

    // 注册驱动
    Class.forName(driver); // 建议写

    // 获取连接
    Connection connection = DriverManager.getConnection(url, user, password);

    String sql = "select * from `user` where `username` = ? and `password` = ?";
    // 获取PreparedStatement
    PreparedStatement preparedStatement = connection.prepareStatement(sql);
    // 给参数赋值
    preparedStatement.setString(1,username);
    preparedStatement.setString(2,pwd);

    // 执行sql语句
    ResultSet resultSet = preparedStatement.executeQuery();

    if (resultSet.next()){
        System.out.println("登录成功");
    } else {
        System.out.println("登录失败");
    }

    // 释放资源
    resultSet.close();
    preparedStatement.close();
    connection.close();
}

注意:上面的代码,如果使用的Statement可以用sql注入登录成功

预处理好处

  1. 不在使用拼接字符串的方式写sql语句,减少语法cuow
  2. 有效的解决了sql注入问题
  3. 大大减少了编译次数,效率较高

JDBC工具类

JDBC每次操作数据库都要读取配置文件,获取连接、释放资源等重复操作,每次都写一遍代码冗余度太高,可以把这些重复用到的代码封装到一个工具类中

public class JDBCUtils {
    private static String user;
    private static String password;
    private static String url;
    private static String driver;

    static {
        // 静态代码块中读取配置文件
        Properties properties = new Properties();
        try {
            properties.load(new FileReader("src\\mysql.properties"));
        } catch (IOException e) {
            // 把编译异常转成运行异常抛出
            // 这时调用者可以选择捕获异常,也可以选择默认处理该异常,比较方便
            throw new RuntimeException(e);
        }
        user = properties.getProperty("user");
        password = properties.getProperty("password");
        url = properties.getProperty("url");
        driver = properties.getProperty("driver");
    }

    // 连接数据库,并返回连接
    public static Connection getConnection(){
        try {
            return DriverManager.getConnection(url,user,password);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    // 关闭相关资源
    public static void close(ResultSet resultSet, Statement statement, Connection connection){
        try {
            if (resultSet != null){
                resultSet.close();
            }
            if (statement != null){
                statement.close();
            }
            if (connection != null){
                connection.close();
            }
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
}

事务

  1. JDBC程序中当一个Connection对象创建时,默认情况下是自动提交事务:每次执行一个SQL语句时,如果执行成功,就会向数据库自动提交,而不能回滚。
  2. JDBC程序中为了让duogeSQL语句作为一个整体执行,需要使用事务。
  3. 调用Connection的setAutoCommit(false)可以取消自动提交事务。
  4. 在所有的SQL语句都成功执行后,调用commit() 方法提交事务。
  5. 当其中某个操作失败或出现异常时,调用rollback() 方法回滚事务
public static void main(String[] args) {
    // 获取连接
    Connection connection = JDBCUtils.getConnection();
    PreparedStatement preparedStatement = null;
    Savepoint point = null;
    try {
        // 取消自动提交事务——开启事务
        connection.setAutoCommit(false);
        
        // 执行sql语句
        String sql = "update `users` set `balance` = `balance` - 100 where `id` = 1";
        preparedStatement = connection.prepareStatement(sql);
        preparedStatement.executeUpdate();
        
        // 设置保存点
        point = connection.setSavepoint();

        // 这里会抛出异常
        int a = 5 / 0;

        // 如果前面的异常,后面执行不到
        // 执行又一条sql语句
        sql = "update `users` set `balance` = `balance` + 100 where `id` = 2";
        preparedStatement = connection.prepareStatement(sql);
        preparedStatement.executeUpdate();

        // 所有语句正常执行,提交事务
        connection.commit();
    } catch (Exception throwables) { // 捕获到异常
        try {
            // 回滚事务
            // connection.rollback(point);
            connection.rollback(); // 回滚到开启事务位置
            // 回滚后提交事务
            connection.commit();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        throwables.printStackTrace();
    } finally {
        // 释放资源
        JDBCUtils.close(null,preparedStatement,connection);
    }
}

批处理

当需要成批插入或者更新记录时,如果sql语句一条一条地执行,效率特别低

可以采用Java的批量更新机制,这一机制允许多条语句一次性提交给数据库批量处理,通常情况下比单独提交更有效率

使用方法

  1. JDBC连接数据库的url后加参数:?rewriteBatchedStatements=true
  2. addBatch():把一条要执行的SQL语句存入PreparedStatement中的一个ArrayList集合中
  3. executeBatch():执行批量处理语句
  4. clearBatch():清空PreparedStatement中的存放SQL语句的ArrayList集合
public void batch() throws Exception{
    // 获取连接
    Connection connection = JDBCUtils.getConnection();
    String sql = "insert into admin02 values (?,?)";
    
    // 获取PreparedStatement
    PreparedStatement preparedStatement = connection.prepareStatement(sql);
    for (int i = 1; i <= 500; i++) {
        // 设置参数
        preparedStatement.setInt(1,i);
        preparedStatement.setString(2,"tom" + i);
        
        // 把要执行的SQL语句存入PreparedStatement中的一个ArrayList集合中
        preparedStatement.addBatch();
        if (i%100 == 0){
            // 执行批量处理语句
            preparedStatement.executeBatch();
            // 不必单独执行清空,执行批量处理语句后自动清空
            // preparedStatement.clearBatch();
        }
    }
    
    // 释放资源
    JDBCUtils.close(null,preparedStatement,connection);
}

批处理往往和PreparedStatement一起搭配使用,可以即减少编译次数,又减少运行次数,效率大大提高

数据库连接池

引入连接池概念

传统连接数据库问题

  1. 传统JDBC数据库连接使用DriverManager来获取,每次向数据库建立连接的时候都要将Connection加载到内存中,再验证IP地址、用户名和密码。需要数据库连接的时候,就向数据库要求一个,频繁的进行数据库连接操作将占用很多系统资源,容易造成服务器崩溃。
  2. 每一次数据库连接,使用完后都得断开,如果程序出现异常而未能关闭,将导致数据库内存泄漏,最终将导致重启数据库。
  3. 传统获取连接的方式,不能控制连接数量,如果连接过多,也可能导致内存泄漏,MySQL崩溃

连接池介绍

  1. 预先在缓冲池中放入一定数量的连接,当需要建立数据库时,只需从“缓冲池”中取出一个,使用完毕之后再放回去
  2. 数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是重新建立一个
  3. 当应用程序向连接池请求的连接数超过最大连接数量时,这些请求将被加入到等待队列中

image-20210826020705916

常用连接池

JDBC的数据库连接池使用java.sql.DataSource来表示,DataSource只是一个接口,该接口通常由第三方提供实现

  1. C3P0:速度相对较慢,稳定性不错
  2. DBCP:速度相对C3P0较快,但不稳定
  3. Proxool:有监控连接池状态的功能,稳定性较C3P0差一些
  4. BoneCP:速度快
  5. Druid(德鲁伊):是阿里提供的数据库连接池,集C3P0、DBCP、Proxool优点于一身的数据库连接池
C3P0

这里用的是c3p0-0.9.1.2.jar

用法
  1. 根据src下的c3p0-config.xml配置文件创建ComboPooledDataSource连接池对象
  2. 用ComboPooledDataSource连接池对象获取连接
// 连接测速代码
public void useXml() throws Exception{
    // 根据xml文件创建连接池对象
    ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource("mysql");
    long start = System.currentTimeMillis();
    for (int i = 0; i < 500000; i++) {
        // 从连接池取出连接
        Connection connection = comboPooledDataSource.getConnection();
        // 关闭连接:其实连接没有关闭,只是放回到连接池
        connection.close();
    }
    long end = System.currentTimeMillis();
    System.out.println(end - start);
}

注意:

  1. ComboPooledDataSource实现了DataSource接口
  2. 这里取出的connection的运行类型是com.mchange.v2.c3p0.impl.NewProxyConnection
  3. connection.close() 并不会真的把连接关闭,只是放回了连接池
配置文件

src下的c3p0-config.xml配置文件

<c3p0-config>
    <!-- 数据源名称,代表连接池 -->
    <named-config name="mysql">
        <!-- 配置数据库用户名 -->
        <property name="user">root</property>
        <!-- 配置数据库密码 -->
        <property name="password">root</property>
        <!-- 配置数据库链接地址 -->
        <property name="jdbcUrl">jdbc:mysql://localhost:3306/lsg_db02</property>
        <!-- 配置数据库驱动 -->
        <property name="driverClass">com.mysql.jdbc.Driver</property>
        <!-- 每次增长的连接数 -->
        <property name="acquireIncrement">5</property>
        <!-- 初始化连接数 -->
        <property name="initialPoolSize">10</property>
        <!-- 最小连接数 -->
        <property name="minPoolSize">5</property>
        <!--连接池中保留的最大连接数。Default: 15 -->
        <property name="maxPoolSize">30</property>
        <!--JDBC的标准参数,用以控制数据源内加载的PreparedStatements数量。但由于预缓存的statements 属于单个connection而不是整个连接池。所以设置这个参数需要考虑到多方面的因素。如果maxStatements与maxStatementsPerConnection均为0,则缓存被关闭。Default:0 -->
        <property name="maxStatements">2</property>
        <!--maxStatementsPerConnection定义了连接池内单个连接所拥有的最大缓存statements数。Default: 0 -->
        <property name="maxStatementsPerConnection">2</property>
    </named-config>
</c3p0-config>
配置文件为properties情况(少用)

java代码

public void useProperties() throws Exception{
    Properties properties = new Properties();
    properties.load(new FileReader("src\\mysql.properties"));

    String user = properties.getProperty("user");
    String url = properties.getProperty("url");
    String password = properties.getProperty("password");
    String driver = properties.getProperty("driver");

    ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();
    comboPooledDataSource.setDriverClass(driver);
    comboPooledDataSource.setUser(user);
    comboPooledDataSource.setPassword(password);
    comboPooledDataSource.setJdbcUrl(url);

    // 设置初始化连接数
    comboPooledDataSource.setInitialPoolSize(10);
    // 设置最大连接数
    comboPooledDataSource.setMaxPoolSize(500);

    long start = System.currentTimeMillis();
    for (int i = 0; i < 5000; i++) {
        Connection connection = comboPooledDataSource.getConnection();
        connection.close();
    }
    long end = System.currentTimeMillis();
    System.out.println(end - start);
}

properties配置文件

user=root
password=root
url=jdbc:mysql://localhost:3306/lsg_db02?rewriteBatchedStatements=true
driver=com.mysql.jdbc.Driver
Druid(德鲁伊)

这里用的是druid-1.1.22.jar

用法
  1. 加载配置文件
  2. 根据配置文件用DruidDataSourceFactory创建DataSource连接池对象
  3. 在连接池对象中取出连接
// 测试连接速度代码
public static void main(String[] args) throws Exception {
    // 加载配置文件
    Properties properties = new Properties();
    properties.load(new FileReader("src\\druid.properties"));

    // 根据配置文件用DruidDataSourceFactory创建DataSource连接池对象
    DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);

    long start = System.currentTimeMillis();
    for (int i = 0; i < 500000; i++) {
        // 从连接池中取出连接
        Connection connection = dataSource.getConnection();
        // 关闭连接:其实连接没有关闭,只是放回到连接池
        connection.close();
    }
    long end = System.currentTimeMillis();
    System.out.println(end - start);
}

注意:

  1. DataSource是接口,运行类型是com.alibaba.druid.pool.DruidDataSource
  2. connection的运行类型是com.alibaba.druid.pool.DruidPooledConnection
配置文件
# 驱动名称
driverClassName=com.mysql.jdbc.Driver
# 连接地址
url=jdbc:mysql://localhost:3306/lsg_db02?rewriteBatchedStatements=true
# 账号
username=root
# 密码
password=root
# 初始连接数
initialSize=10
# 最大连接数
maxActive=20
# 最小连接数
minIdle=5
# 最长等待时间
maxWait=1000
德鲁伊工具类

和JDBC工具类一样,虽然获取连接的方式不同了,但是根据减少冗余代码的思想仍可以编写工具类

public class JDBCUtilsByDruid {
    private static DataSource dataSource;
    
    static { // 静态代码块读取配置文件,并初始化连接池
        Properties properties = new Properties();
        try {
            properties.load(new FileReader("src\\druid.properties"));
            dataSource = DruidDataSourceFactory.createDataSource(properties);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    // 获取连接方法
    public static Connection getConnection(){
        try {
            return dataSource.getConnection();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    // 关闭连接
    public static void close(Connection connection, PreparedStatement preparedStatement, ResultSet resultSet){
        try {
            if (connection != null){
                connection.close();
            }
            if (preparedStatement != null){
                preparedStatement.close();
            }
            if (resultSet != null){
                resultSet.close();
            }
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
}

DBUtils

这里用的是commons-dbutils-1.3.jar

原生的操作数据代码比较麻烦,并要解决一些问题,比如:查询语句查询到结果是放在ResultSet对象中,但是如果connection关闭,这个ResultSet对象就不能再使用,我们想要较长时间保存,就要先把这里的数据提前另存到别的对象中

现在有一个工具类类库,帮我们做了数据迁移的功能,当然这个类库的功能不止这一个

commons-dbutils是Apache组织提供的一个开源JDBC工具类库,它是对JDBC的封装,使用dbutils能极大简化jdbc编码的工作量

处理查询操作
用法
  1. 创建org.apache.commons.dbutils.QueryRunner对象
  2. QueryRunner对象调用query方法执行sql语句(是线程安全的)
public static void main(String[] args) throws SQLException {
    // 获取连接
    Connection connection = JDBCUtilsByDruid.getConnection();
    
    // 创建QueryRunner对象
    QueryRunner queryRunner = new QueryRunner();
    String sql = "select `uid`,`name`,`birthday` from `user` where `uid` != ?";
    
    // 执行sql语句
    List<User> users = queryRunner.query(connection, sql, new BeanListHandler<>(User.class),1);

    for (User user : users) { // 遍历查询结果
        System.out.println(user.getUid() + "\t" + user.getName() + "\t" + user.getBirthday());
    }

    // 释放资源
    JDBCUtilsByDruid.close(connection,null,null);
}
关于query方法

共四个参数

  1. 第一个参数:connection连接
  2. 第二个参数:sql语句
  3. 第三个参数:ResultSetHandler接口,根据传入不同的子类作为参数决定返回类型
  4. 第四个参数:可变参数,用于给sql语句中的占位符赋值,如果没有占位符,则不写这个参数

第三个参数子类可选

  1. ArrayHandler:把结果集中的第一行数据转成对象数组
  2. ArrayListHandler:把结果集中的每一行数据都转成一个数组,再存放到List中
  3. BeanHandler:将结果集中的第一行数据封装到一个对应的JavaBean实例中
  4. BeanListHandler:将结果集中的每一行数据都封装到一个对应的JavaBean实例中,存放到List里
  5. ColumnListHandler:将结果集中某一列存放到List中
  6. KeyedHandler(name):将结果集中的每行数据都封装到Map里,再把这些map存放到一个map里,其key为指定的key
  7. MapHandler:将结果集中的第一行数据封装到一个Map里,key是列名,value就是对应的值
  8. MapListHandler:将结果集中的每一行数据都封装到一个Map里,然后再存放到List
  9. ScalarHandler:将结果集中的第一行、第一列的数据返回,返回类型为Object
源码分析

query方法底层会创建PreparedStatement执行sql语句,并获取ResultSet结果集,不过在query方法中就会把他们关闭

public <T> T query(Connection conn, String sql, ResultSetHandler<T> rsh, Object... params) throws SQLException {
    PreparedStatement stmt = null;
    ResultSet rs = null;
    Object result = null;

    try {
        // 根据connection获取PreparedStatement,里面是conn.prepareStatement(sql)
        stmt = this.prepareStatement(conn, sql);
        // 把可变参数赋给指定的占位符
        this.fillStatement(stmt, params);
        // 执行sql语句,并把ResultSet结果集返回,wrap方法中直接返回结果集,没有任何操作
        rs = this.wrap(stmt.executeQuery());
        // 把ResultSet结果集转成JavaBean的List集合(如果res是BeanListHandler)
        result = rsh.handle(rs);
    } catch (SQLException var33) {
        this.rethrow(var33, sql, params);
    } finally {
        try {
            // 关闭ResultSet
            this.close(rs);
        } finally {
            // 关闭PreparedStatement
            this.close((Statement)stmt);
        }
    }

    // 返回结果
    return result;
}
处理DML操作
用法
  1. 创建org.apache.commons.dbutils.QueryRunner对象
  2. QueryRunner对象调用update方法执行sql语句
public static void main(String[] args) throws SQLException {
    // 获取连接
    Connection connection = JDBCUtilsByDruid.getConnection();
    
    // 创建QueryRunner对象
    QueryRunner queryRunner = new QueryRunner();

    String sql = "insert into `users` values(?, ? ,?)";
    
    // 执行sql语句,返回值是受影响的行数
    int rows = queryRunner.update(connection, sql, 44, "宫本武藏", 600);

    if (rows > 0){
        System.out.println("执行成功");
    }
    
    // 释放资源
    JDBCUtilsByDruid.close(connection,null,null);
}
update源码

update方法源码类似query方法,不重复分析bn

public int update(Connection conn, String sql, Object... params) throws SQLException {
    PreparedStatement stmt = null;
    int rows = 0;

    try {
        stmt = this.prepareStatement(conn, sql);
        this.fillStatement(stmt, params);
        rows = stmt.executeUpdate();
    } catch (SQLException var10) {
        this.rethrow(var10, sql, params);
    } finally {
        this.close((Statement)stmt);
    }

    return rows;
}

BasiDao

DAO:data access object 数据访问对象

把操作数据库的共同操作,放入BasicDao里,可以简化代码,提高维护性和可读性

编写示例

BasicDao
public class BasicDao<T> {
    private QueryRunner queryRunner = new QueryRunner();

    /**
     * dml方法
     * @param sql
     * @param parameters
     * @return
     */
    public int update(String sql, Object... parameters){
        Connection connection = null;
        try {
            connection = JDBCUtilsByDruid.getConnection();

            int update = queryRunner.update(connection, sql, parameters);
            return update;
        } catch (SQLException e) {
            throw new RuntimeException(e);
        } finally {
            JDBCUtilsByDruid.close(connection,null,null);
        }
    }

    /**
     * 查询多条数据
     * @param sql
     * @param clazz
     * @param parameters
     * @return
     */
    public List<T> queryMulti(String sql, Class<T> clazz, Object... parameters){
        Connection connection = null;
        try {
            connection = JDBCUtilsByDruid.getConnection();
            return queryRunner.query(connection, sql, new BeanListHandler<>(clazz), parameters);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        } finally {
            JDBCUtilsByDruid.close(connection,null,null);
        }
    }

    /**
     * 查询一条数据
     * @param sql
     * @param clazz
     * @param parameters
     * @return
     */
    public T querySingle(String sql, Class<T> clazz, Object... parameters){
        Connection connection = JDBCUtilsByDruid.getConnection();
        try {
            return queryRunner.query(connection,sql,new BeanHandler<>(clazz),parameters);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        } finally {
            JDBCUtilsByDruid.close(connection,null,null);
        }

    }

    /**
     * 查询单行单列数据
     * @param sql
     * @param parameters
     * @return
     */
    public Object queryScalar(String sql, Object... parameters){
        Connection connection = JDBCUtilsByDruid.getConnection();
        try {
            return queryRunner.query(connection,sql,new ScalarHandler(),parameters);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        } finally {
            JDBCUtilsByDruid.close(connection,null,null);
        }
    }
}
UserDao

在BasicDao基础上编写具体的操作

public class UserDao extends BasicDao<User>{
    public int insertUser(User user){
        if (user == null) return -1;
        String sql = "insert into `users` values(?,?,?)";
        return update(sql,user.getId(),user.getName(),user.getBalance());
    }

    public List<User> selectAll(){
         return queryMulti("select * from `users`", User.class);
    }
}
测试代码
public class DaoTest {
    @Test
    public void test1(){
        // 插入一条数据
        UserDao userDao = new UserDao();
        int rows = userDao.insertUser(new User(4, "李云龙", 800));
        if (rows > 0){
            System.out.println("插入成功");
        }
    }

    @Test
    public void test2(){
        // 查询表中所有数据
        UserDao userDao = new UserDao();
        List<User> users = userDao.selectAll();
        if (users != null){
            for (User user : users) {
                System.out.println(user);
            }
        }
    }
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值