数据库连接池

        数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个。释放空闲时间超过最大空闲时间的数据库连接来避免因为没有释放数据库连接而引起的数据库连接遗漏。

        因为数据库连接的建立和关闭是非常消耗资源的,频繁地打开、关闭连接造成系统性能低下。因此使用数据库连接池明显提高对数据库操作的性能。

编写数据库连接池

  1. 编写连接池需要实现java.sql.DataSource接口
  2. 创建批量Connection用LinkedList保存【既然是个池,当然用集合保存、LinkedList底层是链表,增删性能较好】
  3. 实现getConnection(),让getConnection()每次调用,都是在LinkedList中取一个Connection返回给用户
  4. 调用Connection.close()方法,Connection返回给LinkedList
    private static String driver;
    private static String url;
    private static String user;
    private static String password;

    private static LinkedList<Connection> list = new LinkedList<>();

    static {
/*        InputStream inputStream = Demo1.class.getClassLoader().getResourceAsStream("db.properties");
        Properties properties = new Properties();
        properties.load(inputStream);
        String url = properties.getProperty("url");
        String username = properties.getProperty("username");
        String driver = properties.getProperty("driver");
        String password = properties.getProperty("password");*/

        ResourceBundle resourceBundle = ResourceBundle.getBundle("db");
        driver = resourceBundle.getString("driver");
        url = resourceBundle.getString("url");
        user = resourceBundle.getString("user");
        password = resourceBundle.getString("password");
        try {
            Class.forName(driver);
            for(int i=0;i<10;i++){
                Connection connection = DriverManager.getConnection(url, user, password);
                list.add(connection);
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }


    @Override
    public Connection getConnection() throws SQLException {
        System.out.println(list.size());
        System.out.println(list);
        return list.size()>0?list.removeFirst():null;
    }

但是出现了问题,调用Connection.close()方法,是把数据库的物理连接关掉,而不是返回给LinkedList的

解决思路

  1. 写一个Connection子类,覆盖close()方法
  2. 写一个Connection包装类,增强close()方法
  3. 用动态代理,返回一个代理对象出去,拦截close()方法的调用,对close()增强

Connection是通过数据库驱动加载的,保存了数据的信息。写一个子类Connection,new出对象,子类的Connection无法直接继承父类的数据信息,也就是说子类的Connection是无法连接数据库的,更别谈覆盖close()方法了。

    @Override
    public Connection getConnection() throws SQLException {
        if(list.size()>0){
            final Connection connection=list.removeFirst();
            //连接池大小
            System.out.println(list.size());
            //返回一个动态代理对象
            return (Connection) Proxy.newProxyInstance(UserInfo.class.getClassLoader(), connection.getClass().getInterfaces(), new InvocationHandler() {
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    if(!method.getName().equals("close")){
                        method.invoke(connection,args);
                    }else{
                        list.add(connection);
                        System.out.println(list.size());
                    }
                    return null;
                }
            });
        }
        return null;
    }

DBCP

使用DBCP数据源的步骤

  1. 导入两个jar包【Commons-dbcp.jar和Commons-pool.jar】
  2. 读取配置文件【dbcpconfig.properties】
  3. 获取BasicDataSourceFactory对象
  4. 创建DataSource对象

属性资源文件(dbcpconfig.properties)

#连接设置
driverClassName=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/testdb?characterEncoding=utf8&useSSL=false&serverTimezone=GMT&allowPublicKeyRetrieval=true
username=sa
password=admin

#<!-- 初始化连接 -->
initialSize=10

#最大连接数量
maxActive=50

#<!-- 最大空闲连接 -->
maxIdle=20

#<!-- 最小空闲连接 -->
minIdle=5

#<!-- 超时等待时间以毫秒为单位 6000毫秒/1000等于60秒 -->
maxWait=60000


#JDBC驱动建立连接时附带的连接属性属性的格式必须为这样:[属性名=property;] 
#注意:"user" 与 "password" 两个属性会被明确地传递,因此这里不需要包含他们。
connectionProperties=useUnicode=true;characterEncoding=utf8

#指定由连接池所创建的连接的自动提交(auto-commit)状态。
defaultAutoCommit=true

#driver default 指定由连接池所创建的连接的只读(read-only)状态。
#如果没有设置该值,则“setReadOnly”方法将不被调用。(某些驱动并不支持只读模式,如:Informix)
defaultReadOnly=

#driver default 指定由连接池所创建的连接的事务级别(TransactionIsolation)。
#可用值为下列之一:(详情可见javadoc。)NONE,READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE
defaultTransactionIsolation=REPEATABLE_READ

数据源工具类DBCPUtil.java

public class DBCPUtil {
    //连接池
    private static DataSource dataSource = null;

    static {
        try {
            //读取配置文件
            InputStream inputStream=DBCPUtils.class.getClassLoader().getResourceAsStream("DBCPConfig.properties");
            Properties properties=new Properties();
            properties.load(inputStream);
            BasicDataSourceFactory basicDataSourceFactory=new BasicDataSourceFactory();
            //得到连接池对象,同时获取配置文件中的信息给连接池对象使用
            dataSource = basicDataSourceFactory.createDataSource(properties);
        } catch (IOException e) {
            e.printStackTrace();
        }catch (Exception e){
            e.printStackTrace();
        }
    }

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

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

C3P0

使用步骤:

  1. 添加C3p0-0.9.1.2.jar
  2. 编写配置文件c3p0-config.xml,防止classpath中,或classes目录中(放在工程的src文件下即可)

配置文件c3p0-config.xml

<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>
    <default-config>
        <property name="driverClass">com.mysql.cj.jdbc.Driver</property>
        <property name="jdbcUrl">jdbc:mysql://localhost:3306/testdb?characterEncoding=utf8&amp;useSSL=false&amp;serverTimezone=GMT&amp;allowPublicKeyRetrieval=true</property>
        <property name="user">sa</property>
        <property name="password">admin</property>

        <property name="initialPoolSize">10</property>
        <property name="maxIdleTime">30</property>
        <property name="maxPoolSize">100</property>
        <property name="minPoolSize">10</property>
        <property name="maxStatements">200</property>

        <user-overrides user="test-user">
            <property name="maxPoolSize">10</property>
            <property name="minPoolSize">1</property>
            <property name="maxStatements">0</property>
        </user-overrides>
    </default-config>
</c3p0-config>

工具类C3P0Util.java

public class C3P0Util {
    //连接池对象
    private static ComboPooledDataSource dataSource = new ComboPooledDataSource();

    //返回Connection对象
    public static Connection getConnection() throws SQLException {
        return dataSource.getConnection();
    }

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

Tomcat数据源

Tomcat服务器也提供了连接池,内部其实就是DBCP。

步骤:

  1. 导入数据库连接的jar包到Tomcat的lib目录下。
  2. 配置数据源XML文件:如果把配置信息写在tomcat下的conf目录的context.xml中,那么所有应用都能使用此数据源;如果是在当前应用的META-INF中创建context.xml,编写数据源,那么只有当前应用可以使用。

在tomcat下的conf目录context.xml添加以下代码

<?xml version="1.0" encoding="UTF-8"?>
<Context>
    <Resource name="jdbc/dataSource"
              auth="Container"
              type="javax.sql.DataSource"
              driverClassName="com.mysql.cj.jdbc.Driver"
              url="jdbc:mysql://localhost:3306/testdb?characterEncoding=utf8&amp;useSSL=false&amp;serverTimezone=GMT&amp;allowPublicKeyRetrieval=true"
              username="sa"
              password="admin"
              maxActive="8"
              maxIdle="4"/>
</Context>

在项目的web.xml中添加以下代码

    <resource-ref>
        <description>DBConnection</description>
        <res-ref-name>jdbc/dataSource</res-ref-name>
        <res-type>javax.sql.DataSource</res-type>
        <res-auth>Container</res-auth>
    </resource-ref>

在java类中

        try {
            //初始化JNDI容器
            Context initCtx = new InitialContext();
            //获取到JNDI容器
            Context envCtx = (Context) initCtx.lookup("java:comp/env");
            //扫描以jdbc/dataSource名字绑定在JNDI容器下的连接池
            DataSource ds = (DataSource) envCtx.lookup("jdbc/dataSource");
            Connection connection = ds.getConnection();
            System.out.println(connection);
        } catch (NamingException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        }

使用dbutils框架

dbutils对JDBC进行简单的封装,极大简化jdbc编码的工作量。

DbUtil类

提供了关闭连接,装载JDBC驱动,回滚提交事务等方法的工具类【比较少用,一般使用连接池连接数据库】

QueryRunner类

该类简化了SQL查询,配合ResUltSetHandler使用,可以完成大部分的数据库操作,重载了许多的查询,更新,批处理方法,大大减少了代码量。

ResultSetHandler接口

该接口规范了对ResultSet的操作,要对结果集进行什么操作,传入ResultSetHandler接口的实现类即可。

  • ArrayHandler:把结果集中的第一行数据转成对象数组。
  • ArrayListHandler:把结果集中的每一行数据都转成一个数组,再存放到List中。
  • BeanHandler:将结果集中的第一行数据封装到一个对应的JavaBean实例中。
  • BeanListHandler:将结果集中的每一行数据都封装到一个对应的JavaBean实例中,存放到List里。
  • ColumnListHandler:将结果集中某一列的数据存放到List中。
  • KeyedHandler(name):将结果集中的每一行数据都封装到一个Map里,再把这些map再存到一个map里,其key为指定的key。
  • MapHandler:将结果集中的第一行数据封装到一个Map里,key是列名,value就是对应的值。
  • MapListHandler:将结果集中的每一行数据都封装到一个Map里,然后再存放到List。
  • ScalarHandler 将ResultSet的一个列到一个对象中。

使用DbUtils框架对数据库CRUD

public static void update() throws Exception{
        Connection connection = C3P0Util.getConnection();
        QueryRunner queryRunner=new QueryRunner();
        String sql="update userinfo set userage=userage+2 where userage<25";
        queryRunner.update(connection,sql);
        C3P0Util.release(connection,null,null);
    }

    public  static void query() throws Exception{
        Connection connection=C3P0Util.getConnection();
        QueryRunner queryRunner=new QueryRunner();
        String sql="select * from userinfo";
        List<UserInfo> list = (List<UserInfo>) queryRunner.query(connection,sql,new BeanListHandler(UserInfo.class));
        for(UserInfo userInfo :list){
            System.out.println(userInfo);
        }
        C3P0Util.release(connection,null,null);
    }

    public static void insertBatch() throws Exception{
        Connection connection = C3P0Util.getConnection();
        QueryRunner queryRunner=new QueryRunner();
        String sql="insert userinfo(userid,username,usersex,userage,useraddress) values(?,?,?,?,?)";
        Object[][] objects=new Object[5][];
        for(int i=0;i<5;i++){
            objects[i]=new Object[]{20+i,"卡哇伊","male","30","猛龙"};
        }
        queryRunner.insertBatch(connection,sql,new BeanListHandler(UserInfo.class),objects);
        C3P0Util.release(connection,null,null);
    }

    public static void delete() throws Exception{
        Connection connection=C3P0Util.getConnection();
        QueryRunner queryRunner=new QueryRunner();
        String sql="delete from userinfo where userid>?";
        Object[] objects=new Object[]{15};
        queryRunner.update(connection,sql,objects);
        C3P0Util.release(connection,null,null);
    }

分页

分页技术是非常常见的,在搜索引擎下搜索页面,不可能把全部数据都显示在一个页面里面,所以用到分页技术。

Oracle实现分页

    /*
      Oracle分页语法:
        @lineSize---每页显示数据行数
        @currentPage----当前所在页
    
    */
    SELECT *FROM (
        SELECT 列名,列名,ROWNUM rn
        FROM 表名
        WHERE ROWNUM<=(currentPage*lineSize)) temp
    
    WHERE temp.rn>(currentPage-1)*lineSize;

MySql实现分页

    /*
      Mysql分页语法:
      @start---偏移量,不设置就是从0开始【也就是(currentPage-1)*lineSize】
      @length---长度,取多少行数据
    
    */
    SELECT *
    FROM 表名
    LIMIT [START], length;
    
    /*
      例子:
        我现在规定每页显示5行数据,我要查询第2页的数据
    
      分析:
        1:第2页的数据其实就是从第6条数据开始,取5条
    
      实现:
        1:start为5【偏移量从0开始】
        2:length为5

*/

总结:

  • MySql从(currentPage-1)*lineSize开始取数据,取lineSize条数据。
  • Oracle先获取currentPage*lineSize条数据,从(currentPage-1)*lineSize开始取数据。

使用JDBC连接数据库实现分页

重要的4个变量

  • currentPage 当前页【由用户决定的】
  • totalRecord 总数据数【查询表可知】
  • pageSize  每页显示数据的数量【开发人员决定】
  • pageCount  页数【totalRecod和pageSize决定】
    public static int getTotalRecords(){
        try {
            Connection connection = C3P0Util.getConnection();
            QueryRunner queryRunner=new QueryRunner();
            String sql="select count(*) from userinfo";
            List list = queryRunner.execute(connection, sql, new ScalarHandler());
            int i=Integer.parseInt(list.get(0).toString());
            return i;
        } catch (SQLException e) {
            e.printStackTrace();
            return  0;
        }
    }

    public static int getPageCount(int totalRecords,int pageSize){
        return totalRecords/pageSize==0?totalRecords/pageSize:totalRecords/pageSize+1;
    }

    public  static List<UserInfo> getPageData(int currentPage,int pageSize) throws  Exception{
        Connection connection=C3P0Util.getConnection();
        QueryRunner queryRunner=new QueryRunner();
        String sql="select * from userinfo limit ?,?";
        Object[] object=new Object[]{(currentPage-1)*pageSize,pageSize};
        List<UserInfo> persons =(List<UserInfo>)queryRunner.query(connection, sql, new BeanListHandler(UserInfo.class), object);
        return persons;
    }
        try {
            int totalRecords=Pagination.getTotalRecords();
            int pageSize=10;
            int currentPage=2;
            int pageCount=Pagination.getPageCount(totalRecords,pageSize);
            List<UserInfo> list=Pagination.getPageData(currentPage,pageSize);
            System.out.println("total records:"+totalRecords);
            System.out.println("page count:"+pageCount);
            System.out.println("page size:"+pageSize);
            System.out.println("current page:"+currentPage);
            for(UserInfo userInfo:list){
                System.out.println(userInfo);
            }

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

 

 posted on 2019-06-02 22:07  会飞的金鱼  阅读(119)  评论(0)    收藏  举报