【Java高级程序设计学习笔记】数据库编程

目录

1.1 数据库基础

1.1.1 关系数据库

1.1.2 结构化查询语言 

1.2 JDBC

1.2.1 工作原理

1.2.1.1 java.sql.Connection

 1.2.1.2 java.sql.Driver

1.2.1.3 java.sql.DriverManager

 1.2.2 ODBC和JDBC

1.2.3 应用实例

  1.2.3.1 建立连接

 1.2.3.2 通过Statement执行SQL语句

 1.2.3.3 通过PreparedStatement执行SQL语句

1.3 JDBC进阶

1.3.1 事务

1.3.2 存储过程

1.3.3 数据库连接池


1.1 数据库基础

现实生活产生的海量数据,纸质存储已经远远达不到要求。人们在计算机上建立了一套类似于现实生活的仓库,并把数据有组织的存储在计算机中,并对仓库配备了相应的管理系统,用于管理这些数据,这个仓库称为数据库。

1.1.1 关系数据库

随信息量增大,促进了数据库技术发展,先后出现了层次数据库系统、网状数据库系统以及关系数据库系统。现在又提出对象数据库,但关系数据库仍占数据库主要市场。

关系数据库就是通过关系模型建立的数据库。如需要存储和处理学生以及课程信息。在关系数据库中,会为学生和课程各自建立一张表,表中的每一条记录代表一个学生或课程的具体信息,如下表:

   课程表中有两条记录,分别是数学和哲学课程信息。在学生表中有张三和李四的信息。现在要表示学生选课信息,在关系数据库中要新建一张表用于描述学生表和课程表的关系,这张表称为关系表,关系表中每条记录都要关联着关系的双方,如在这个例子中,需要建立一张学生课程表用于表示学生的选课信息,表的结构如下图:

  表中每行记录有三个字段,分别是学生的学号、课程编号、这门课的成绩。通过这种关联学生和课程之间建立了关系。

1.1.2 结构化查询语言 

    为满足复杂的数据库操作,人们为关系数据库设计了一种数据处理语言,即结构化查询语言(SQL)。

   SQL语言分成以下四个部分:

  1. 数据定义语言(DDL):create,drop,alert等语句
  2. 数据操作语言(DML):insert,update,delete等语句
  3. 数据查询语言(DQL):select等语句
  4. 数据控制语句(DCL):grant,revoke,commit,rollback等语句

 

   以上列出了一些简单的SQL操作。

1.2 JDBC

  程序在运行过程中会产生和处理大量数据,JDBC是Java设计者为数据库编程提供的一组接口。这组接口对于开发者来说是访问数据库的工具,对数据库提供商来说是驱动程序的编写规范,从而保证Java可以访问它们数据库产品。使用JDBC后,开发者可以更加专注于业务的开发,而不必为特定的数据库编写程序,

1.2.1 工作原理

  JDBC的架构如下图:

  由上图可以看出,使用JDBC的用户很多,想访问的数据库各不相同。但JDBC使用这种可插拔的方式,让用户以一种方式访问数据库。

  什么是可插拔模式?先看一下下图所示的JDBC的生命周期。

   在JDBC的生命周期中,首先要加载需要用到的数据驱动,然后将加载的驱动注册到JDBC中,然后用户就可以用JDBC获取数据库的连接会话,获取会话后用户就可以使用该会话进行数据库操作,操作完成之后关闭释放连接,一个周期结束。

  在JDBC生命周期中,需要用到几个重要的类或接口,如Connection、Driver、DriverManager以及一些具体的驱动类。下面看一下这些类或接口之间的关系:

   从上图可以看出,JDBC使用了接口Driver和Connection,JDBC设计者并没有进行实现,而是留给了数据库提供商,正是这种面向接口的编程,使得JDBC的扩展更加灵活,使得我们可以对JDBC进行插拔操作。

  下面介绍JDBC多的核心类或接口:

1.2.1.1 java.sql.Connection

  该接口的实现类负责维护Java开发者与数据库之间的会话。特定的数据库需要实现该接口,以便开发者能正确操作数据库。开发者拥有该类的实例后,就可以访问数据库了,并可以执行特定的操作。下面列出了该接口中一些重要常用的方法。

1)Statement createStetement() throws SQLException;

  该方法返回一个用于执行静态SQL的Statement对象,开发者通过该方法获取实例后,可以通过该实例执行静态SQL语句并获取返回结果。

2)PreparedStatement prepareStatement(String sql) throws SQLException;

  返回一个SQL命令执行器PreparedStatement,它与S塔特门头不同的是,该类在初始化时要传入一个SQL,SQL需要的条件值,可通过参数的方式设置,该类会预编译SQL命令,执行效率高,但只能执行特定的SQL语句。

3)void commit() throws SQLException;

  提交数据库操作进行的数据库操作,默认情况下,connection会自动提交,即执行每条SQL语句后都会自动提交,如果取消了自动提交则必须用此方法进行提交,否则对数据库操作无效。

4)void rollback() throws SQLException;

取消当前事务的所有数据库操作,将已经修改的数据还原成初始状态。

 1.2.1.2 java.sql.Driver

  数据库供应商提供的驱动类必须实现此接口才能接入到JDBC中,该接口实现代码如下:

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

public interface Driver {
    Connection connect(String url, java.util.Properties info) throws SQLException;

    boolean acceptsURL(String url) throws SQLException;

    DriverPropertyInfo[] getPropertyInfo(String url, java.util.Properties info) throws SQLException;

    int getMajorVersion();

    int getMinorVersion();

    boolean jdbcCompliant();
}

  这就是Java驱动程序的规格,所有的数据库提供商提供的驱动都必须实现这个接口,这个接口中也包含六个方法,下面逐一介绍。

1)Connection connect(String url, java.util.Properties info) throws SQLException;

  该方法负责创建与指定数据库的会话,通过会话进行数据库操作。该方法拥有两个参数,其中参数url表示数据库的链接地址例如:

  jdbc:mysql://localhost:3306/demo

  参数info表示创建会话所需要的参数,参数用Properties类来进行表述,常见的参数为用户名,密码等。如果连接失败会抛出异常。

2)boolean acceptsURL(String url) throws SQLException;

  该方法负责检查url是否符合特定的数据库的连接协议。

3)DriverPropertyInfo[] getPropertyInfo(String url, java.util.Properties info) throws SQLException;

  可以通过该方法获取驱动程序提供的信息。该方法返回信息由驱动开发者根据自身的情况决定。该方法需要传入两个参数:1.url即数据库的连接地址2.info 连接数据库所必需的验证等辅助信息

4) int getMajorVersion();

  返回驱动程序的主版本号

5) int getMinorVersion();

  返回驱动的副版本号

 7)boolean jdbcCompliant();

  检测驱动程序是否符合JDBC标准,以及支持的SQL是不是标准的SQL或其扩展集。

1.2.1.3 java.sql.DriverManager

   如果说数据库提供商开发的驱动是插头,那么DriverManger类就是插座。用户需要将数据库驱动注册到DriverManger中,这样才能访问和操作数据库。DriverManger是JDBC的核心和管理者,DriverManger含有两个内部类,分别是DrivrtInfo和DrivrtService。DrivrtInfo的源代码如下:

public class DriverInfo {
    Driver driver;
    Class driverClass;
    String driverClassName;
    public String toString(){
        return ("driver[className="+driverClassName+","+"]");
    }
}

  DriverInfo类用于表示驱动类信息,包含三个属性。其中driver的类型是接口Driver,因此它可以被赋予所有具体实现类的实例;driverClass表示驱动器的具体类型,如com. mysql. jdbc. Driver;而driverClassName则表示具体驱动类的类名。

  DriverService类通过Service的配置文件加载java.sql.Driver的实现类(即具体的数据库驱动类)以便这些类能被实例化。DriverService类的源代码如下:

public class DriverService implements java.security.PrivilegedAction {
    Iterator ps=null;
    public DriverService(){};
    public Object run(){
        ps= Service.providers(java.sql.Driver.class);
        try {
            while (ps.hasNext()){
                ps.next();
            }
        }catch (Throwable t){}
        return null;
        
    }
    
}

  在DriverService类中,首先定义了了一个迭代器实例,在run方法中通过Service类的providers获取java.sql.Driver实现类,这些实现类被配置在service的文件中,可以在META-INFO的seivices文件夹下找到这些配置文件,获取驱动类的迭代器后,逐一遍历携带其中的驱动。

  在DriverManager中除了两个比较重要的内部类外,还有很多功能方法,下面列出常用的方法:

1)public static Connection getConnection(String url,String user,String password) throws SQLException

   DriverManger选择合适的数据库驱动程序,通过驱动程序与数据库建立连接,然后把该连接返回给用户,流程图如下:

  图中用户首先调用DriverManger的getConnection(String url,String user,String password)方法获取与数据库的会话连接,在该方法内部DriverManger首先遍历已经注册的数据库驱动,然后通过数据库驱动的额accptsURL(url)方法找到能够解析url的合适驱动,接着通过该驱动连接数据库,建立会话,最终可以确保它不依赖具体的驱动。下面列出了DriverManger的getConnection方法的源码:

public static Connection getConnection(String url,String user,String password) throws SQLException{
        java.util.Properties info=new java.util.Properties();
        ClassLoader callerCL= DriverManager.getCallerClassLoader();
        if (user!=null){
            info.put("user",user);
        }
        if (password!=null){
            info.put("password",password)
        }
        return (getConnection(url,info,callerCL));
    }
    private static Connection getConnection(String url,java.util.Properties info,ClassLoader callerCL) throws SQLException {
        java.util.Vector drivers=null;
        synchronized (DriverManager.class){
            if (callerCL==null){
                callerCL=Thread.currentThread().getContextClassLoader();
            }
        }
        if (!initialized){
            initialize();
        }
        synchronized (DriverManager.class) {
            drivers=readDrivers;
        }
        SQLException reason=null;
        for (int i=0;i<drivers.size();i++) {
            DriverInfo di=(DriverInfo) drivers.elementAt(i);
            if (getCallerClass(callerCL,di.driverClassName)!=di.driverClass)
                continue;
            try {
                Connection result=di.driver.connect(url,info);
                if (result!=null)
                    return (result);
            }catch (SQLException ex){
                if (reason==null){
                    reason=ex;
                }
            }
        }

     上述代码中包含两个静态方法,一个是公有的静态方法getConnection(String url,String user,String password),另一个是私有的静态方法getConnection(String url,java.util.Properties info,ClassLoader callCL)。在公有的getConnection静态方法中首先获取了当前驱动程序的类加载器,然后将连接数据库所需要的参数封装成Proprtties类的实例,最后将类加载器、参数信息,以及连接字符串作为参数传递给私有的getConnection静态方法。静态的getConnection方法内,首先获取当前类的类加载器,然后遍历注册的所有的数据库驱动。在遍历算法内部,手下判断驱动类是不是当前类加载器加载的,如果不是则跳过,如果是就尝试进行连接,如果连接通过就会返回一个Connection接口的实现类,终止遍历,建立会话结束。这个方法的实现中,使用的驱动类型为Driver接口,也就是说,它是针对Driver接口进行编程,这样做的好处是,任何实现Driver接口的驱动类都可以对其赋值,从而实现插拔。

2)public static synchronized void registerDriver(java.sql.Driver driver) throws SQLException

  数据库提供商编写的驱动程序,必须通过注册才能在驱动管理器中进行使用,源码如下:

public static synchronized void registerDriver(java.sql.Driver driver) throws SQLException {
        if (!initialized){
            initialize();
        }
        DriverInfo di=new DriverInfo();
        di.driver=driver;
        di.driverClass=driver.getClass();
        di.driverClassName=di.driverClass.getName();
        writrDrivers.addElement(di);
        System.out.println("registerDriver: "+di);
        readDrovers=(java.util.Vector)writeDrivers.clone();
    }

}

    方法内部首先判断DriverManager有没有进行初始化,如果没有则进行初始化。然后创建一个驱动信息类并赋值。然后在驱动集中添加驱动,最后更新驱动。该方法一般用于驱动程序进行调用注册。

3)public static Driver getDriver(String url) throws SQLException

  通过该方法可以获取能解析url的合适驱动,源码如下:

 public static Driver getDriver(String url) throws SQLException {
        java.util.Vector drivers = null;
        System.out.println("DriverManager.getDriver(\"" + url + "\")");
        if (!initialized) {
            initialize();
        }
        synchronized(DriverManager.class) {
            drivers=readDrivers;
        }
        ClassLoader callerCL=DriverManager.getCallerClassLoader();
        for (int i=0;i<drivers.size();i++){
            DriverInfo di=(DriverInfo) drivers.elementAt(i);
            if (getCallerClass(callerCL,di.driverClassName)!=di.driverClass){
                System.out.println(" skipping: "+di);
                continue;
            }
            try {
                System.out.println(" trying "+di);
                if (di.driver.acceptsURL(url)) {
                    System.out.println("getDriver returning "+di);
                    return (di.driver);
                }
            }catch (SQLException ex){
                
            }
        }
        System.out.println("getDriver: no suitable driver");
        throw new SQLException("No suitable driver","08001");
    }

  该方法首先定义了一个驱动信息集合,然后判断DriverManager是否初始化,如果没有则进行初始化,接着读取驱动信息集合。接着遍历驱动信息集合。在遍历代码块中,首先判断有没有权限访问驱动类,如果没有则跳过,如果有则调用驱动类的acceptsURL方法解析数据库连接串,如果能解析则返回该驱动,遍历结束后,如果仍没有找到合适的驱动,则抛出没有合适的驱动类异常。

  介绍了JDBC的核心类和接口后,下面来看驱动的具体实现。以MySQL的驱动类为例子,下面为com.mysql.jdbc,Driver驱动类的源码:

 public class MySqlDriver extends NonRegisteringDriver implements java.sql.Driver
    {
        static {
            try{
                java.sql.DriverManager.registerDriver(new Driver());
            }catch (SQLException e){
                throw new RuntimeException("Can not register driver!")
            }
            public Driver() throws SQLException{
            }
        }
    }

  该类继承了NonRegisteringDriver类并实现了java.sql.Driver接口,具体的数据库连接与处理细节都放到了NonRegisteringDriver中实现,在com.mysql.jdbc.Driver类中,又一段静态代码域,可以看到在这段代码域中,调用了DriverManager的registerDriver方法,对驱动进行注册,代码域会在类加载的时候执行,这就是为什么在使用驱动之前都需要调用CLass类的forName方法加载驱动的原因。

  NonRegisteringDriver也继承了接口java.sql.Driver,在其内部可以看出数据库连接与处理的详细逻辑:

   在上图中可以看出,NonRegisteringDriver可以分为三大块,分别是:属性快中大部分为静态常量,父类方法块中主要是继承并覆盖父类方法:工具与解析方法块主要是对连接字符串以及参数的解析。下面逐一介绍这些代码块:

(1)属性块的主要代码:

   这些常量定义了MySQL的数据库驱动所能理解和接受的协议。通过解析,发现连接字符串不是使用这些协议,MySQL驱动将禁止数据库连接。

 

 

 (2) 父类方法块主要的方法代码如下

 

 

 

 

 1.2.2 ODBC和JDBC

   ODBC是微软开放服务结构中有关数据库的一个组成部分,它建立了一组规范,提供了一组对数据库访问的标准API,这些API利用SQL来完成其大部分任务。ODBC本身也提供了对SQL的支持,用户可以直接把SQL语句送给ODBC,但ODBC不适合在java中国使用,因为它使用的是c语言接口。从Java调用本地c代码存在许多缺点。因此JDBC创建了JDBC-ODBC的桥接,以ODBC为基础进行数据库连接。JDBC API对于基本的SQL抽象和概念是一种自然的Java接口,它建立在ODBC上,保留了ODBC的基本设计特征。两种接口都基于X/Open SQL CLI,最大的区别在于JDBC以java的风格与优点为基础并进行优化。

1.2.3 应用实例

  1.2.3.1 建立连接

import java.sql.Connection;
import java.sql.DriverManager;

public class MySqlDAO {
    public static Connection getConnection() throws Exception {
        String driverName = "com.mysql.jdbc.Driver";
        String url = "jdbc:mysql://localhost:3306/demo";
        String userName="root";
        String password="lele20011019";
        Class.forName(driverName);
        Connection con= DriverManager.getConnection(url,userName,password);
        return con;
    }
}

这段代码主要功能是建立与数据库之间的会话。下面逐一分析一下这段代码首先是连接参数定义:

        String driverName = "com.mysql.jdbc.Driver";
        String url = "jdbc:mysql://localhost:3306/demo";
        String userName="root";
        String password="lele20011019";

  这段代码块首先定义了几个属性,分别是驱动名称、数据库的连接字符串、数据库用户名以及密码。定义这些属性要注意以下几点:

  1. 驱动名必须为全名,且保证路径正确。
  2. 连接字符串分为三个部分1)连接协议jdbc:mysql:// 2)数据库地址:localhost:3306 3)数据库名称:demo
  3. 确保用户名和密码正确,否则会拒绝连接

  接着是加载数据库的驱动:

Class.forName(driverName);

  在前面介绍过,加载数据库驱动时,程序会自动调用DriverManager中的registerDriver(Driver driver)方法,将自身注册到管理器中。

接下来是创建并获取连接:

 Connection con=DriverManager.getConnection(url,userName,password);

  次数调用了驱动管理器的getConnection的三参数方法,驱动管理器对该方法有多个重载。该方法会根据传入的参数选择合适的连接会话返回给用户。至此以及获取了数据库的连接会话,可以在此会话的基础上进行数据库的操作了。注意:执行此代码前,请确保将数据库驱动的JAR包加入到项目中。

 1.2.3.2 通过Statement执行SQL语句

  获取连接会话后,如何通过statement对数据进行增删改查?在对数据库进行操作前,需要获取Statement对象,用于执行数据的命令。因此需要在MysqlDAO中添加如下方法。

 public static Statement getStatement() throws Exception{
        Statement stmt=getConnection().createStatement();
        return stmt;
    }

首先需要创建一张表,作为数据操作的基础,创建表的代码如下:

public class DBTest {
    public static void main(String[] args) throws Exception {
        Statement stmt = MySqlDAO.getStatement();
        String sql = "create table student(no int primary key,name char(20))";
        stmt.execute(sql);
        stmt.close();

    }
}

  这段代码首先通过MySqlDAO获取了数据库命令执行对象Statement的实例,然后定义了一条数据库语句。该语句的作用是,创建一张student表,表中有两个字段:一个是整型的no,另一个时字符串类型的name,接着调用Statement对象的execute(String sql)方法,执行这条数据库语句,执行后,数据库中即可发现多了一张student表。

  接下来,需要在数据库中插入一条记录,插入操作的执行代码如下:

public class DBTest1 {
    public static void main(String[] args) throws Exception {
        Statement stmt = MySqlDAO.getStatement();
        String sql = "insert into student values(1,'jim')";
        stmt.execute(sql);
        stmt.close();
    }
}

  插入操作的代码与创建表的代码很相似,不一样的时执行的SQL语句不同,修改和删除也与插入操作相似。下面介绍如何从数据库中读取数据。

public class DBTest2 {
    public static void main(String[] args) throws Exception{
        Statement stmt=MySqlDAO.getStatement();
        String sql="select * from student where no=1";
        ResultSet rs=stmt.executeQuery(sql);
        while (rs.next()){
            System.out.println("学号: "+rs.getInt("no"));
            System.out.println("姓名: "+rs.getString(2));
        }
        stmt.close();
    }
}

  这段代码的功能是读取表student中no等于1的数据并打印出来。首先仍然是先获取数据库命令执行对象Statement的实例,接着定义一条查询SQL语句,执行这条语句回返回一个结果集,结果集中包含student表中no等于1的学生信息,调用结果集ResultSet的next方法,逐行读取学生信息,并将学生信息打印在命令行上。rs提供了很多方法用于获取属性字段,可以通过索引也可以通过字段名称。注意一点,字段的属性必须正确,不能通过getInt的方法获取字符串字段的值。可以发现,对于字段少的表,这种方法可以接受,但是字段多的时候就难以接受了。

 1.2.3.3 通过PreparedStatement执行SQL语句

  首先获取PreparedStatement实例,因此向MySqlDAO中添加如下方法:

 public static PreparedStatement preparedStatement(String sql) throws Exception {
        return getConnection().prepareStatement(sql);
    }

  该方法获取一个PreparedAStatement对象,并为其指定SQL语句,获取后进行顺序库操作

public class DBTest3 {
    public static void main(String[] args) throws Exception {
        String sql = "select * from student where no=?";
        PreparedStatement ps = MySqlDAO.preparedStatement(sql);
        ps.setInt(1, 1);
        ResultSet rs = ps.executeQuery();
        while (rs.next()) {
            System.out.println("学号:" + rs.getInt("no"));
            System.out.println("姓名:" + rs.getString(2));
        }
    }
}

  这段代码先指定一条SQL语句,但这条语句不同的是它采用?代替了具体的值。PreparedStatement对象允许在执行SQL语句时才进行参数指定。PreparedStatement对象有多个设置参数值的方法,在设置参数时,需要知道参数的值类型,比如int类型可以使用setInt(int paramIndex,int value)进行参数值设定,前一个参数表示参数的索引,即它代表着第几个问号,后面为参数的属性值。PreparedStatement对象会预先编译SQL语句,因此它的执行效率高于Statement,但他只能执行预先设定的SQL。

1.3 JDBC进阶

1.3.1 事务

  事务指的是一组操作的集合,这组操作作为一个逻辑单元执行,因此要么全部执行要么不执行。如果执行过程中出现错误,必须退回原来的初始状态。

    JDBC主要通过Connection类的rollback()、commit()以及setAutoCommit(boolean autoCommit)等方法进行事物回滚、提交操作。实例如下:

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

public class DBTest4 {
    public static void main(String[] args) {
        Connection con = null;
        try {
            con = MySqlDAO.getConnection();
            con.setAutoCommit(false);
            Statement stmt = con.createStatement();
            ;
            String sql1 = "select max(no) from student";
            ResultSet rs = stmt.executeQuery(sql1);
            int no = 1;
            while (rs.next()) {
                no = rs.getInt(1) + 1;
            }
            String sql2 = "insert into student values(" + no + ",'wahaha')";
            stmt.execute(sql2);
            con.commit();
        } catch (Exception e) {
            try {
                con.rollback();
            } catch (SQLException el) {
                el.printStackTrace();
            }
        }
    }
}

  获取连接后,首先将连接会话Coonection的自动提交取消,再通过连接会话创建了数据库命令执行对象Statement;接着执行力一条查询语句,当前最大的学生的学号no,获取这个no并增加1得到新的学号,并将新的学号作为此值插入数据库中,最后提交事物。如果出现错误,异常捕获后会进行回滚。

1.3.2 存储过程

    存储过程相当于在数据库中服务器中的函数,它接受输入并返回输出。该函数会在数据库服务器上进行编译,供用户多次使用,因此可以大大提高效率。MySQL中创建一个存储过程的语句如下:

  这个存储过程的作用时向Student表中插入一条新的记录,输入参数时新记录学生的姓名,输出参数是新学生的学号。存储过程的内部首先获取当前最大的学生号值,然后将其加一作为新学生的学号。并把生成的姓名和学号插入到数据库中。首先要定义一个接受符,因为在存储过程中;b被用作一句语句的结束,因此不能作为整个SQL接受标志,我们定义的结束符为”$$“。接着创建存储过程,存储过程名是必须的,输入参数和输出参数可选。接下来以begin开始,再定义一个整型变量maxid作为新学生的学生号,接着从学生表中找出当前最大的学生号并加1赋值给maxid。将新的学生号以及姓名插入学生表中,然后再次查询最大学生号,赋值给输出参数。以end表示存储过程体结束。最后以$$表示存储过程声明结束。使用JDBC的调用方法如下:

import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.Types;

public class DBTest5 {
    public static void main(String[] args) throws Exception {
        Connection con = MySqlDAO.getConnection();
        String sql = "call insertNewStudent('coco',?);";
        CallableStatement stmt = con.prepareCall(sql);
        stmt.registerOutParameter(1, Types.INTEGER);
        stmt.execute();
        System.out.println("新生学号: " + stmt.getInt(1));
        stmt.close();
        con.close();
    }
}

 这段代码调用了上面创建的insertNewStudent。首先获取连接,然后定义一条访问存储过程的语句,注意输出参数以”?“来代替。接着创建一个可以执行存储过程的命令对象,并设置其对象的存储过程,然后设置存储过程的输出类型,接着执行存储过程命令对象并将存储过程的输入参数打印到控制台上,最后关闭对象和连接。

  将部分逻辑放到数据库服务器上编辑大大提高效率。也给服务器带来了性能上的压力。

1.3.3 数据库连接池

  数据库创建连接是一项非常耗资源的操作,因此为了节省创建和释放联系的性能消耗,人们提出了池的概念。池的概念被广泛应用在服务器端软件的开发上。使用池结构可以明显提高应用程序的速度,改善效率和降低系统资源的开销。什么是池?可以简单想象运行时环境。当大量用户并发访问应用服务器时如何提供服务呢?可以为每一个用户提供一个新的服务对象进行服务。这种方法看起来简单但实际上会出现问题,显而易见的是不断创建和销毁新服务对象必将造成系统资源的巨大开销,导致系统性能下降。池可以想象成一个容器,保存着各种需要的对象。对这些对象进行复用。从结构看,它应该具有容器对象的具体的元素对象。从使用方法上看,可以直接取得池中的元素来用,也可以把要做的任务交给他处理,所以从目的上看,池应该有两种类型,一种是用于处理客户提交的任务的,通常用Thread Pool(线程池)描述,另一种是客户从池中获取有关的对象进行使用,通常用Resource Pool(资源池)来描述。数据库连接池就是一种资源池。下列代码展现了一个简单的数据库连接池例子

1.MyCon类,该类表示一个有状态的数据连接。

/**
 * 含有状态的数据库连接
 */
public class MyCon {
    public static final int FREE = 100;    //当前连接空闲
    public static final int BUSY = 101;    //当前连接繁忙
    public static final int CLOSED = 102;  //当前连接关闭
    private Connection con;                //持有的数据库连接
    private int state=FREE;                //数据库连接当前状态,初始为空闲
    public MyCon(Connection con){
        this.con=con;
    }
    public Connection getCon(){
        return con;
    }
    public int getState(){
        return state;
    }
    public void setState(int state){
        this.state=state;
    }
}

2.ConPool类,该类为数据库连接池实例

import java.sql.Connection;
import java.util.ArrayList;
import java.util.List;

/**
 * 数据库连接池
 * 该池为单例池
 * 用户可从此池中获取含有状态的数据库连接
 */
public class ConPool {
    private List<MyCon> freeCons = new ArrayList<>();  //空闲的连接列表
    private List<MyCon> busyCons = new ArrayList<>();  //繁忙的连接列表
    private int max = 10;                              //最大连接数
    private int min = 2;                               //最小连接数
    private int current = 0;                           //当前连接数
    private static ConPool instance;                   //单例实例
    /**
     * 私有的构造方法,在构造池实例时,检查当前连接是否小于最小连接如果小于则创建新的连接
     * 直到大于等于最小链接
     */
    private ConPool(){
        while (this.min>this.current){
            this.freeCons.add(this.createCon());
        }
    }
    /**
     * 获取池实例
     */
    public static ConPool getInstance(){
        if (instance==null)
            instance=new ConPool();
        return instance;
    }
    /**
     * 获取空闲数据库连接
     * 先从空闲列表中找出一个连接
     * 如果空闲列表中没有连接则试图创建一个连接
     */
    public MyCon getCon(){
        MyCon myCon=this.getFreeCon();
        if (myCon != null){
            return myCon;
        }else {
            return this.getNewCon();
        }
    }
    /**
     * 获取一个空闲连接
     */
    private MyCon getFreeCon(){
        if (freeCons.size()>0){
            MyCon con = freeCons.remove(0);
            con.setState(MyCon.BUSY);
            this.busyCons.add(con);
            return con;
        }else {
            return null;
        }
    }
    /**
     * 试图获取一个新连接
     * 如果当前连接数小于最大,则创建新连接,否则返回null
     */
    private MyCon getNewCon(){
        if (this.current<this.max){
            MyCon myCon=this.createCon();
            myCon.setState(MyCon.BUSY);
            this.busyCons.add(myCon);
            return myCon;
        }else {
            return null;
        }
    }
    /**
     * 试图创建新的连接,并更新当前连接总数
     */
    private MyCon createCon(){
        try{
            Connection con=MySqlDAO.getConnection();
            MyCon myCon=new MyCon(con);
            this.current++;
            return myCon;
        }catch (Exception e){
            return null;
        }
    }
    /**
     * 将连接设为空闲
     * @param con
     */
    public void setFree(MyCon con){
        this.busyCons.remove(con);
        con.setState(MyCon.FREE);
        this.freeCons.add(con);
    }
    /**
     * 输入当前池的连接状态
     */
    public String toString(){
        return "当前连接数: "+this.current+"\n"
                +"空闲连接数: "+this.freeCons.size()+"\n"
                + "繁忙连接数: "+this.busyCons.size();
    }
}

3.MySqlDAO类,用户数据库连接的获取与操作

import java.sql.Connection;
import java.sql.DriverManager;

public class MySqlDAO {
    public static Connection getConnection() throws Exception{
        String driverName = "com.mysql.cj.jdbc.Driver";
        String url = "jdbc:mysql://localhost:3306/demo";
        String userName = "root";
        String password = "lele20011019";
        Class.forName(driverName);
        Connection con = DriverManager.getConnection(url, userName, password);
        return con;
    }
}

4. DBTest类,测试类输出池中连接情况

public class DBTest {
    public static void main(String[] args) throws Exception{
        System.out.println(ConPool.getInstance().toString());
        MyCon con=null;
        for (int i=0;i<5;i++){
            con= ConPool.getInstance().getCon();
        }
        System.out.println(ConPool.getInstance().toString());
        ConPool.getInstance().setFree(con);
        System.out.println(ConPool.getInstance().toString());
    }
}

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

天的命名词

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值