程序园

数据库访问从原理到实践-java版

2019-09-29  本文已影响0人  赵阳_c149

在项目的开发过程中,很难不用到数据库访问。但是一直是只知其然,却不知其所以然。这几天有了点时间,就对数据库访问的原理进行了一番调查,写下来以便今后查阅。

对数据库的访问是一个非常常用的功能,市面上的实现可谓琳琅满目,不胜枚举。但是万变不离其宗,他们都是对DataSource这个java标准类库中的类进行了包装,在JDBC API的基础上根据项目的不同着眼点对其进行了扩展和增强。因此,本文从DataSource开始写起,描述其基本原理和特性,然后以UCP和Goovy Sql为例,介绍了成熟的实现的特性和一些用法。

DataSource概览

在连接数据源的时候,优先使用Datasource。DataSource为数据库连接提供了连接池和分布式的transaction管理【1】。实现了DataSource的类的实例可以代表数据源,例如一个特定的DBMS,甚至是一个文件。
DataSource是JDBC2.0的新特性【2】。与JDBC1.0 的DriverManager不同,DataSource不需要注册Driver,而是通过lookup以JNDI的方式注册DataSource,其接口由驱动程序供应商实现。

JNDI

在这里,有必要介绍一下JNDI。在java生态中,JNDI主要用于Jakarta EE(其前身为J2EE,J2EE是一个古老的称呼,至少在2005年以后,J2EE便更名为Java EE【3】,而在2018年3月正式更名为Jakarta EE【4】)应用的开发和部署。在Jakarta EE应用的开发和部署的过程中,主要有以下4种角色【5】:

Jakarta EE组件(例如WAR文件和EJB JAR文件)必须在他们的部署单元之外声明他们在资源上的依赖性。在JNDI被广泛使用之前,开发人员也必须涉足部署人员的领地,并且还要准备配置数据源等外部资源。JNDI提供了一套机制,通过将JNDI API映射为特定的命名服务和目录系统【6】,将外部资源(例如datasource)注册到系统中,为部署单元提供服务。

对于每个引用,部署人员都需要把新组件按特定的名称(比如说 ejb/ProcessOrders/1.1)绑定到全局树中,对于需要 EJB 组件的其他每个组件,还要为组件在部署描述符中解析 EJB 引用。依赖于 V1.0 以前的应用程序不需要进行任何修改,也不需要重新测试,这缩短了实现的时间、降低了成本并减少了复杂性。部署人员的工作就是创建 DataSource(或者是创建一个 Object 对象,让 foo 指向它,在我们的 Java 语言示例中就是这样)。从而,开发人员从他们不熟悉的部署工作中解脱了出来。
绑定资源:

Context ctx = new InitialContext();
ctx.bind("jdbc/billingDB", ds);

使用资源:

Context ctx = new InitialContext();
DataSource ds = (DataSource)ctx.lookup("jdbc/billingDB");

如果说我们只是在J2SE的环境下用DataSource连接数据库,其实无需考虑如何注册数据源。

UDP:在项目中使用DataSource:

UCP[7],连接池是数据连接对象的缓存。在运行时,应用会从连接池中请求一个连接。如果连接池内存在可以满足请求的连接,他就把连接返回给应用。如果没有的话,就会创建一个并返回给应用。

通常情况下,创建一个连接的代价是比较高的,通过对连接池进行合理的参数配置,可以有效的利用资源。JDBC UCP 提供了连接池的实现,以缓存JDBC连接,从而提高应用的性能并合理利用系统资源。一个UCP JDBC连接池可以用JDBC driver创建物理连接,并在池中对其进行维护。

UCP JDBC连接池的架构:


jdbc_arch.png

在这里有两个问题需要注意

  1. JDBC数据库连接池connection关闭后Statement和ResultSet未必关闭【8】
  2. 注意ResultSet读取的方式。

Releases this ResultSet object's database and JDBC resources immediately instead of >waiting for this to happen when it is automatically closed.
Note: A ResultSet object is automatically closed by the Statement object that generated it ?>when that Statement object is closed, re-executed, or is used to retrieve the next result from a >sequence of multiple results. A ResultSet object is also automatically closed when it is >garbage collected.

规范说明:
1.垃圾回收机制可以自动关闭它们;
2.Statement关闭会导致ResultSet关闭;
3.Connection关闭不一定会导致Statement关闭

当然,UCP此时的关闭Connection,并不是真正关闭,而是将其放回连接池的空闲队列中。

解决建议:

  1. 由于垃圾回收的线程级别是最低的,为了充分利用数据库资源,有必要显式关闭它们,尤其是使用Connection Pool的时候;
  2. 最优经验是按照ResultSet,Statement,Connection的顺序执行close;
  3. 为了避免由于java代码有问题导致内存泄露,需要在rs.close()和stmt.close()后面一定要加上rs = null和stmt = null;
public class OracleDBConnectionManager {

    private static final String CONNECTION_CLASS_NAME = "oracle.jdbc.pool.OracleDataSource";
    private static final String JDBC_URL = "jdbc:oracle:thin:@//host:1521/xe";
    private static final int POOL_SIZE_INITIAL = 1;
    private static final int POOL_SIZE_MIN = 1;
    private static final int POOL_SIZE_MAX = 3;
    private static final int POOL_CONNECTION_REUSE_COUNT_MAX = 1000;
    private static final int POOL_CONNECTION_REUSE_TIME_MAX = 150;
    private static final int POOL_CONNECTION_WAIT_TIMEOUT_MAX = 300;
    private static final boolean USE_CONNECTION_POOL = true;

    public static DataSource getDataSource() {
        Connection testConn = null;
        Statement stmt = null;
        ResultSet rs = null;
        try {
            PoolDataSource pds = PoolDataSourceFactory.getPoolDataSource();
            pds.setConnectionFactoryClassName(CONNECTION_CLASS_NAME);

            pds.setURL(JDBC_URL);
            pds.setUser("sys as SYSDBA");
            pds.setPassword("oracle");


            pds.setInitialPoolSize(POOL_SIZE_INITIAL);
            pds.setMinPoolSize(POOL_SIZE_MIN);
            pds.setMaxPoolSize(POOL_SIZE_MAX);
            pds.setMaxConnectionReuseCount(POOL_CONNECTION_REUSE_COUNT_MAX);
            pds.setMaxConnectionReuseTime(POOL_CONNECTION_REUSE_TIME_MAX);
            pds.setValidateConnectionOnBorrow(USE_CONNECTION_POOL);
            pds.setConnectionWaitTimeout(POOL_CONNECTION_WAIT_TIMEOUT_MAX);

            Properties connProps = new Properties();
            //auto-commit should always be false
            connProps.setProperty("autoCommit", "true");
            pds.setConnectionProperties(connProps);

            testConn = pds.getConnection();

            stmt = testConn.createStatement();
            rs = stmt.executeQuery("select * from STUDENT");
            while(rs.next()){
                System.out.println("===========");
                System.out.println(rs.getString("SNO"));
                System.out.println(rs.getString("SNAME"));
                System.out.println(rs.getString("SSEX"));
                System.out.println(rs.getDate("SBIRTHDAY"));
                System.out.println(rs.getString("CLASS"));
            }
            return pds;
        } catch (SQLException e) {
            e.printStackTrace();
        }finally{
            try{
                rs.close();
            }catch (Exception e){
                // do sth
            }finally {
                rs = null;
            }

            try{
               stmt.close();
            }catch (Exception e){
                // do sth
            }finally {
                stmt = null;
            }

            try{
                testConn.close();
            }catch (Exception e){
                // do sth
            }finally {
                testConn = null;
            }
        }
        return null;
    }
}

当然这种做法显得很笨拙,你可以对其进行封装或者直接采用已有的方案【9】。

成熟的类库对JDBC API的封装

实现类库非常的多,这里仅仅介绍Groovy Sql。Groovy Sql 在JDBC API 的基础上,以外观(Facade)模式对资源管理和resultset操作进行了很大程度上的简化。隐藏了获得数据库连接,构造和配置statement,同数据库连接的交互,关闭资源和记录日志。此外,还提供了其他的一些特性,例如操作ResultSet提供了更加丰富的类似Collection的 API,支持闭包,批处理,transaction等等。

public class SampleDao {

    private static OracleDBConnectionManager oracleDBConnectionManager;

    public static void main(String...strings){
        select("select * from STUDENT");
    }

    public static void select(String query) {

        Sql querySql = null;

        try {
            oracleDBConnectionManager = new OracleDBConnectionManager();
            querySql = new Sql(oracleDBConnectionManager.getDataSource());
            GroovyRowResult result = querySql.firstRow(query);

            System.out.println("Get the first row of the results");
            System.out.println((String)result.get("SNO"));

            List<GroovyRowResult> groovyRowResults = querySql.rows(query);


            for (GroovyRowResult groovyRowResult1: groovyRowResults) {
                List<String> inner = new ArrayList<String>();
                Set<String> keys = groovyRowResult1.keySet();


                for (String k: keys) {
                    if(groovyRowResult1.get(k) instanceof  String) {
                        System.out.println("Value for String " + k + " is " + (String) result.get(k));
                    }
                }

            }
        } catch (SQLException e) {
        } finally {
            if(querySql!=null){
                querySql.close();
            }
        }
    }
}

详细的操作请参照【10】【11】。

【1】sqldatasources
【2】两种创建DBConnection1以调用JDBC API的方式
【3】j2ee
【4】Jakarta_EE
【5】JNDI
【6】百度百科 JNDI
【7】UDP
【8】JDBC数据库连接池connection关闭后Statement和ResultSet未关闭的问题
【9】在Java中关闭数据库连接
【10】groovy Sql
【11】groovy-databases
【12】Resultset的用法
【13】Statement

上一篇 下一篇

猜你喜欢

热点阅读