Java-连接池(预编译对象、连接池)

1. 预编译对象PreparedStatement

1.1 SQL注入问题

  • 输入的内容作为SQL语句语法的一部分,改变了原有SQL真正的意义
  • 需要用预编译对象解决这个问题

1.2 PreparedStatement介绍

  • PreparedStatement是一个接口,其超级接口为Statement和Wrapper,子接口为CallableStatement;

1.3 PreparedStatement的执行原理

  • 数据库需要执行编译后的SQL语句;

  • Statement和PreparedStatement的区别:

    • 对于相同的SQL语句,Statement对象每次执行都会先将该语句发送给数据库编译,数据库再执行。每次都要编译,耗时较大。执行次数越多,效率越低;
    • 而PreparedStatement对象先将相同的SQL语句发给数据库预编译,PreparedStatement对象会引用预编译后的结果(相当于数据库只预编译一次,后面只需根据传入的参数执行语句,减少编译次数,提高效率

1.4 PreparedStatement的好处

  • 减少SQL编译次数,提高效率(前提:同一条语句多次重复执行);
  • 安全性更高,没有SQL注入的隐患;
  • 提高程序的可读性。

1.5 PreparedStatement实现增删改查

1.5.1 API介绍

1.5.1.1 获取PreparedStatement对象
PreparedStatement preparestatement(String sql)
    将SQL语句发送给数据库预编译,PreparedStatement对象会引用预编译后的结果。使用Connection对象调用该方法获取。
1.5.1.2 PreparedStatement常用方法
- void setInt(int parameterIndex, int x);
    将指定参数设置为给定Java int 值
- void setLong(int parameterIndex, long x);
    将指定参数设置为给定Java long 值
- void setFloat(int parameterIndex, float x);
    将指定参数设置为给定Java float 值
- void setDouble(int parameterIndex, double x);
    将指定参数设置为给定Java double
- void setObject(int parameterIndex, object x);
    使用给定对象设置指定参数的值
- void setString(int parameterIndex, String x);
    将指定参数设置为给定Java String 值
ResultSet executeQuery();
    在此PreparedStatement对象中执行SQL查询,并返回该查询生成的ResultSet对象
int executeUpdate();
    在此 PreparedStatement 对象中执行 SQL 语句,该语句必须是一个 SQL 数据操作语言(DML)语句,比如 INSERT、UPDATE 或 DELETE 语句;或者是无返回内容的 SQL 语句,比如 DDL语句。

1.5.2 示例代码

  • PreparedStatement使用步骤
    • 编写SQL语句,未知内容使用?占位;
    • 获得PreparedStatement对象;
    • 设置实际参数;
    • 执行参数化SQL语句;
    • 关闭资源。
/*
下面是用了JDBC里面定义的工具类
*/
public class Demo01 {
    /*插入数据*/
    public void add() throws SQLException {
        //获取连接
        Connection connection = JDBCUtils.getConnection();
        //准备sql语句,得到preparedstatement
        String sql = "insert into student(name,age) values(?,?)";
        PreparedStatement pst = connection.prepareStatement(sql);

        //设置参数
        pst.setString(1,"大黄");
        pst.setInt(2,18);
        //执行sql
        pst.executeUpdate();
        //关闭资源
        JDBCUtils.close(null,pst,connection);
    }

    /*删除数据 */
    public void delete() throws SQLException {
        //获取连接
        Connection connection = JDBCUtils.getConnection();
        //准备sql语句,得到preparedstatement
        String sql = "delete from student where id=?";
        PreparedStatement pst = connection.prepareStatement(sql);

        //设置参数
        pst.setInt(1,1);
        //执行sql
        pst.executeUpdate();
        //关闭资源
        JDBCUtils.close(null,pst,connection);
    }

    /*修改数据*/
    public void update() throws SQLException {
        //获取连接
        Connection connection = JDBCUtils.getConnection();
        //准备sql语句,得到preparedstatement
        String sql = "update student set name = ? where id = ?";
        PreparedStatement pst = connection.prepareStatement(sql);

        //设置参数
        pst.setString(1,"大狗");
        pst.setInt(2,2);
        //执行sql
        pst.executeUpdate();
        //关闭资源
        JDBCUtils.close(null,pst,connection);
    }

     /*修改数据*/
    public void query() throws SQLException {
        //获取连接
        Connection connection = JDBCUtils.getConnection();
        //准备sql语句,得到preparedstatement
        String sql = "select * from student";
        PreparedStatement pst = connection.prepareStatement(sql);
        //执行sql
        ResultSet rs = pst.executeQuery();
        while(rs.next()){
            System.out.println("编号:"+ rs.getInt("id")+" 姓名:"+ rs.getString("name")+" 年龄:"+ rs.getInt("age"));
        }

    /*关闭资源*/
    JDBCUtils.close(null,pst,connection);
    }
}

2. 自定义连接池和动态代理

  • 连接池是创建和管理数据库连接的缓冲池技术。连接池中保存了一些数据库可重复使用的连接。

2.1 连接池解决数据库重复创建连接的问题

  • 不使用连接池的情况:
    • 每次访问数据库执行SQL语句都需要创建数据库连接,,而创建连接需要消耗较多资源,创建时间较长,导致连接对象使用率低

2.1.1 连接池解决问题的原理

  1. 程序一开始创建一定数量的连接,放在容器(连接池)中;
  2. 使用时直接取出已经创建好的连接对象;
  3. 关闭是不真正关闭连接,而是将连接对象再次放回连接池

2.2 自定义连接池

  • 连接池的公共接口:javax.sql.DataSource

  • 自定义步骤如下

    1. 定义一个javax.sql.DataSource接口的实现类
    2. 重写接口的抽象方法
    3. 定义连接池相关参数
      • 连接池必要因素:初始化连接个数、最大的连接个数、当前已创建的连接个数
    4. 创建容器保存连接
      • 创建集合用以存储Connection
    5. 提供创建新连接的方法
    6. 提供连接池的构造方法
    7. 提供获取连接方法(看示例代码)
    8. 提供关闭连接方法(看示例代码)
/*
自定义连接池
 */
import studynotes.Notes00_预编译对象.JdbcUtils;

import javax.sql.DataSource;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.LinkedList;
import java.util.logging.Logger;

//1. 定义一个javax.sql.DataSource接口的实现类
public class MyPool implements DataSource {

    //3. 定义连接池相关参数
    //   - 连接池必要因素:初始化连接个数、最大的连接个数、当前已创建的连接个数
    private int initCount = 3;
    private int maxCount = 10;
    private int curCount = 0;//创建时默认为零

    //4. 创建容器保存连接
    //   - 创建集合用以存储Connection==>连接池
    private LinkedList<Connection> list = new LinkedList<>();

    //5.提供创建新连接的方法
    public Connection createConnection() {
        //可使用工具类创建连接
        Connection connection = JdbcUtils.getConnection();
        curCount++;
        return connection;
    }

    //6.提供连接池的构造方法
    public MyPool() {
        //一创建连接池就按照初始化连接个数创建连接
        for (int i = 0; i < initCount; i++) {
            Connection connection = createConnection();
            //添加到连接池中
            list.add(connection);
        }
    }

    //2. 重写接口的抽象方法
    //7. 提供获取连接方法
    @Override
    public Connection getConnection() throws SQLException {
        /*分为三种情况*/

        //情况1:连接池中还有连接,直接获取
        if (list.size() > 0) {
            return list.removeFirst();  //删除集合元素的同时返回值为被删除对象,实现从连接池取出连接的操作
        }

        //情况2:连接池中没有连接,但是连接的总个数没有超过最大值
        //注意:情况2不能和情况1的位置调换,否则会出现初始化的3个还没利用就开始创建新的连接
        if (curCount < maxCount) {
            return createConnection();
        }

        //情况三:连接总个数达到最大值,且连接池没有连接,则抛出异常
        throw new RuntimeException("当前访问人数过多,请稍后重新连接!");
    }

    //8.提供关闭连接方法,把链接还回给连接池
    public void close(Connection connection){
        list.add(connection);
    }



    /*其余的没有用到,直接空实现*/
    @Override
    public Connection getConnection(String username, String password) throws SQLException {
        return null;
    }

    @Override
    public PrintWriter getLogWriter() throws SQLException {
        return null;
    }

    @Override
    public void setLogWriter(PrintWriter out) throws SQLException {

    }

    @Override
    public void setLoginTimeout(int seconds) throws SQLException {

    }

    @Override
    public int getLoginTimeout() throws SQLException {
        return 0;
    }

    @Override
    public Logger getParentLogger() throws SQLFeatureNotSupportedException {
        return null;
    }

    @Override
    public <T> T unwrap(Class<T> iface) throws SQLException {
        return null;
    }

    @Override
    public boolean isWrapperFor(Class<?> iface) throws SQLException {
        return false;
    }
}

注意:根据编程习惯,一般是习惯用资源直接调用方法。此时可使用动态代理增强方法。

2.3 代理模式(重要)

2.3.1 代理模式的作用

  • 代理对象可以在调用者和目标对象之间其到中介的作用。代理对象可对目标对象的某个功能进行增强。
  • 优点1:动态代理可以在不知道类名的基础下直接增强某个功能,而继承需要知道父类名才能重载增强;
  • 优点2:实现客户与被代理类间的解耦
  • 缺点:动态代理需要接口,如果一个类不实现任何接口,则动态代理无法增强

2.3.2 代理模式涉及到4个要素

  1. 调用者:使用数据库的用户
  2. 代理对象
  3. 目标对象:实现接口中的方法
  4. 抽象对象:代理对象和目标对象共有的接口

2.3.3 动态代理

  • 程序运行过程中,动态创建出代理对象
2.3.3.1 动态代理相应的API
  1. Proxy类

    - static Object newProxyInstance(ClassLoader loader,Class[] interfaces,InvocationHandler h);
       作用:生成实现指定接口的代理对象
       需要用Proxy类对象调用

    其中:

    loader参数:目标对象的类加载器(格式:当前模块下的任意类的名字.class.getClassLoader()
    interfaces:代理对象所实现的接口数组(接口名.class的数组)

    h: 具体的代理操作(处理器),InvocationHandler是一个接口,需要传入一个实现了此接口的实现类
    返回值:实现指定接口的代理对象。

    如何理解处理器的作用

    动态代理对象实现接口的时候时采用空实现(重写方法中没有方法体),而是通过处理器将接口中的方法分类实现(增强被代理的方法,原汁原味地实现不被代理的方法)

  2. InvocationHandler接口

    - Object invoke(Object proxy, Method method, Object[] args);
       作用:在这个方法中实现对真实方法的增强

    其中:

    proxy:当前代理对象。
    method: 代理对象当前调用的方法。
    args:代理对象当前调用方法时传递的参数。

    返回值:是真实对象方法的返回值。

    图解:

    ![invoke方法参数的说明](E:\Java study\每日笔记\Java笔记汇总\notesPic\invoke方法参数的说明.jpg)

2.3.3.2 动态代理的使用步骤及原则
  • 动态代理的使用步骤

    1. 直接创建真实对象;
    2. 通过Proxy类创建代理对象;
    3. 调用代理方法;
    4. 在InvocationHandler的invoke分别进行处理被代理方法和非代理方法。
  • 原则(固定套路)

    • 处理器内部一定要维护一个被代理对象(保证处理器内部的方法均由被代理对象执行【因为实际上执行方法的是被代理对象而不是代理对象!!】)
    • 处理器内部一定需要在invoke方法内获取当前调用的方法名
    • 如果不需要被代理的方法,则使用被代理对象直接执行该方法(这些方法不被代理不需要增强功能,按原功能执行即可,且必须将方法的返回值返回去)
    • 如果是需要被代理的方法,则根据自己的业务逻辑(业务需求)改写方法即可
2.3.3.3 动态代理的示例代码

需求:实现黄牛与火车站的代理关系,黄牛赚差价

public class YellowCow {
    public static void main(String[] args) {
        //获取动态代理对象,因为返回的是一个代理对象(该对象是一个接口的实现类对象),可以直接强转
        Fuction yellowCow = (Fuction) Proxy.newProxyInstance(YellowCow.class.getClassLoader(),
                new Class[]{Fuction.class}, new MyHandler());

        //代理对象只能使用接口定义的方法
        yellowCow.sellTicket(1000);
    }
}
class MyHandler implements InvocationHandler {
    /*处理器的内部一定要维护一个被代理对象。*/
    Trainstation trainstation = new Trainstation();
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //获取当前调用的方法名
        String methodName = method.getName();
        //判断是否为代理的功能
        if ("sellTicket".equalsIgnoreCase(methodName)) {
            //根据业务逻辑编写代理的功能
            Double totalPrice = (Double) args[0];//获取传入参数的第一个参数,因为已知类型为double,所以直接强转
            double payPrice = totalPrice*0.5;
            System.out.println("代理商收入了:"+ totalPrice+" 给了电脑厂商:"+ payPrice);
            //实际执行方法的仍是被代理对象,即火车站!!!
            /*invoke方法的第一个参数为方法的执行者,第二个参数是执行方法时需要传入的参数*/
            return method.invoke(trainstation,payPrice);
        } else {
            //接口中不需要被代理的功能,直接执行即可
            return method.invoke(trainstation,args);
        }
    }
}
2.4 动态代理解决close问题(增强close功能)
  • 那么之后就可以直接用资源(连接)调用close方法,而不需要MyPool2.close();这样不清晰的调用
/*
使用动态代理解决close问题(增强close功能)
 */
import studynotes.Notes00_预编译对象.JdbcUtils;

import javax.sql.DataSource;
import java.io.PrintWriter;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.LinkedList;
import java.util.logging.Logger;

//1. 定义一个javax.sql.DataSource接口的实现类
public class MyPool2 implements DataSource {

    //3. 定义连接池相关参数
    private int initCount = 3;
    private int maxCount = 10;
    private int curCount = 0;

    //4. 创建容器保存连接
    private LinkedList<Connection> list = new LinkedList<>();

    //5.提供创建新连接的方法
    public Connection createConnection() {
        //维护一个被代理对象(方法实际执行者)
        Connection connection = JdbcUtils.getConnection();
        //产生一个代理对象,该代理对象仍是被代理对象类型的
        Connection proxyConnection = (Connection) Proxy.newProxyInstance(MyPool2.class.getClassLoader(),
                new Class[]{Connection.class}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                //获得方法名
                String methodName = method.getName();

                //如果是需要增强的方法
                if ("close".equalsIgnoreCase(methodName)) {
                    //需要被代理的方法,调用close方法是将当前连接放回到连接池中。理解:外面调用close方法时用资源直接.close的
                    list.add((Connection)proxy);
                    return null;
                } else {
                    //如果不是代理的方法,直接执行即可
                    return method.invoke(connection,args);
                }
            }
        });
        /*上述的可以用lambda改写*/

        curCount++;
        //将代理对象返回去
        return proxyConnection;
    }

    //6.提供连接池的构造方法
    public MyPool2() {
        for (int i = 0; i < initCount; i++) {
            Connection connection = createConnection();
            list.add(connection);
        }
    }

    //2. 重写接口的抽象方法
    //7. 提供获取连接方法
    @Override
    public Connection getConnection() throws SQLException {
        /*分为三种情况*/

        //情况1:连接池中还有连接,直接获取
        if (list.size() > 0) {
            return list.removeFirst();
        }

        //情况2:连接池中没有连接,但是连接的总个数没有超过最大值
        if (curCount < maxCount) {
            return createConnection();
        }

        //情况三:连接总个数达到最大值,且连接池没有连接,则抛出异常
        throw new RuntimeException("当前访问人数过多,请稍后重新连接!");
    }

    //8.提供关闭连接方法,把链接还回给连接池
    public void close(Connection connection){
        list.add(connection);
    }

    /*其余的没有用到,直接空实现*/
    /*此处省略没用到的抽象方法*/
}

3. C3P0连接池

3.1 概念及常用配置参数

  • 开源的连接池。jar包c3p0-0.9.5.2.jar

  • 常用的配置参数:

参数 说明
initialPoolSize 初始连接数
maxPoolSize 最大连接数
checkoutTimeout 最大等待时机
maxIdleTime 最大空闲回收时间

3.2 C3P0连接池的使用步骤

  1. 导入jar包c3p0-0.9.5.2.jarmchange-commons-java-0.2.12.jar
  2. 编写c3p0-config.xml 配置文件,配置对应参数
  3. 将配置文件放在src目录下(注意:不能改文件位置,不能更改文件名
  4. 创建连接池对象ComboPooledDataSource ,使用默认配置或命名配置
  5. 从连接池中获取连接对象
  6. 使用连接对象操作数据库
  7. 关闭资源

3.3 C3P0连接池常用方法

- public ComboPooledDataSource()
    无参构造使用默认配置(使用xml中default‐config标签中对应的参数)
- public ComboPooledDataSource(String configName)
    有参构造使用命名配置(configName:xml中配置的名称,使用xml中named‐config标签中对应的参数)
- public Connection getConnection() throws SQLException
    从连接池中取出一个连接

3.4 C3P0连接池的注意事项及好处

  • 注意事项:C3P0配置文件名称必须为c3p0-config.xml
  • 好处:
    • C3P0命名配置可以有多个,可根据连接池名字连接不同的数据库、不同的连接池、和不同厂商的数据库

3.5 示例代码

/*
使用前提:根据使用环境对配置文件的内容进行调整完毕
*/

import com.mchange.v2.c3p0.ComboPooledDataSource;

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

public class Notes03 {
    /*
    配置文件信息处理完毕后,可以直接创建C3P0连接池
     */
    public static void main(String[] args) throws SQLException {
        /*
        无参时使用默认的连接池。由于C3P0可以配置多个连接池,可以在参数中传入其他连接池的名字,达到转换连接池的效果
         */
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        Connection connection = dataSource.getConnection();
        System.out.println("已连接:"+ connection);
        connection.close();

        //有参
        ComboPooledDataSource dataSource1 = new ComboPooledDataSource("anotherc3p0");
        Connection connection1 = dataSource.getConnection();
        System.out.println("已连接:"+ connection1);
        connection1.close();

    }
}

4. DRUID连接池

  • 阿里巴巴开发的数据库连接池

4.1 常用API

  • 创建连接池

    - public static DataSource createDataSource(Properties properties)
    创建一个连接池,连接池的参数使用properties中的数据

    DRUID连接池的配置文件建议放在src目录下方便加载。使用前对配置文件的信息进行修改

4.2 DRUID使用

  • 配置文件Properties的加载:利用类文件路径获取输入流,然后再load

    InputStream is = 当前类名.class.getResourceAsStream("类文件路径");

    其中类文件路径建议为/druid.properties/代表src目录

  • 获取连接池:使用工具类DruidDataSourceFactory类的*createDataSource*(properties);方法

其余使用同普通连接池

import com.alibaba.druid.pool.DruidDataSourceFactory;

import javax.sql.DataSource;
import java.io.InputStream;
import java.sql.Connection;
import java.util.Properties;

public class Notes04 {
    public static void main(String[] args) throws Exception {
        Properties properties  = new Properties();
        InputStream is = Notes04.class.getResourceAsStream("/druid.properties");
        properties.load(is);

        //得到一个连接池
        DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);

        /*以下操作同普通连接池*/
        Connection connection = dataSource.getConnection();
        System.out.println("已连接:"+ connection);
        connection.close();
    }
}

4.3 Jdbc工具类

  1. 声明静态数据源成员变量
  2. 创建连接池对象
  3. 定义公有的得到数据源的方法
  4. 定义得到连接对象的方法
  5. 定义关闭资源的方法

示例代码:

import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.io.InputStream;
import java.sql.*;
import java.util.Properties;

public class JDBCUtils {

    private static DataSource dataSource ;

  /*  配置文件的加载只需加载一次,使用静态代码块*/
    static{
        try {
            Properties properties =new Properties();
            InputStream is =  JDBCUtils.class.getResourceAsStream("/druid.properties");
            properties.load(is);

            //创建druid的连接池
            dataSource = DruidDataSourceFactory.createDataSource(properties);
        } catch (Exception e) {
          e.printStackTrace();
        }
    }

    //获取连接池
    public static DataSource getDataSource(){
        return dataSource;
    }

    //获取连接
    public static Connection getConnection() throws SQLException {
        return dataSource.getConnection();
    }

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

猜你喜欢

转载自blog.csdn.net/KeepStruggling/article/details/82143743