美文网首页程序园
数据库访问从原理到实践-java版

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

作者: 赵阳_c149 | 来源:发表于2019-09-29 17:08 被阅读0次

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

对数据库的访问是一个非常常用的功能,市面上的实现可谓琳琅满目,不胜枚举。但是万变不离其宗,他们都是对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组件,Jakarta EE组件可以是 Web 应用程序、企业级 JavaBean(EJB)组件,或者是应用程序客户机(例如基于 Swing 的 GUI 客户机应用程序)。组件提供者包括:HTML 设计师、文档编程人员以及其他开发人员角色。大多数 Jakarta EE开发人员在组件提供者这一角色上耗费了相当多的时间。
  • 应用程序组装者
    这个角色将多个 Jakarta EE模块捆绑成一个彼此结合的、可以部署的整体:企业归档(EAR)文件。应用程序组装者要选择组件,分清它们之间的交互方式,配置它们的安全性和事务属性,并把应用程序打包到 EAR 文件中。许多 IDE,例如 WebSphere® Studio、IDEA、JBuilder、WebLogic Workshop 和其他 IDE,都可以帮助应用程序组装者以交互方式配置 EAR 文件。
  • 部署人员(Deployer)
    这个角色负责部署,这意味着将 EAR 安装到 Jakarta EE容器(应用服务器)中,然后配置资源(例如数据库连接池),把应用程序需要的资源绑定到应用服务器中的特定资源上,并启动应用程序。
  • 系统管理员(System Administrator)
    这个角色负责保证容器需要的资源可用于容器。

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读取的方式。
  • 对于问题1,可以看到Java Spec的说明:

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】。

  • 对于问题2
    如果一定要传递ResultSet,应该使用RowSet,RowSet可以不依赖于Connection和Statement。Java传递的是引用,所以如果传递ResultSet,你会不知道Statement和Connection何时关闭,不知道ResultSet何时有效。

成熟的类库对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

相关文章

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

    在项目的开发过程中,很难不用到数据库访问。但是一直是只知其然,却不知其所以然。这几天有了点时间,就对数据库访问的原...

  • JDBC

    在java中,数据库存取技术可分为: 1.JDBC直接访问数据库(java访问数据库的基石,以下都是基于此的封装)...

  • JDBC

    JDBC (Java Data Base Connectivity 数据库连接)是java 访问数据库的标准规范。...

  • JDBC的简单了解

    JDBC Java DataBase Connectivity java数据库连接 是一种数据库访问规则规范。 ...

  • Kubernetes部署java web demo(tomcat

    参考《Kubernetes 权威指南(第二版)》第一章中的例子,部署一个 Java 应用,访问 MySql 数据库...

  • 2018-03-07 JDBC

    什么是JDBC? java语言访问数据库的技术 (Java DataBase Connectivity)...

  • Java SQL 注入学习笔记

    介绍 JDBC: 全称 Java Database Connectivity 是 Java 访问数据库的 API,...

  • SpringBoot(四、MyBatis,隔离级别,传播行为)

    SpringBoot集成MyBatis 常见访问数据库方式 1、原始java访问数据库 开发流程麻烦注册驱动/加载...

  • JDBC概述

    JDBC(Java DataBase Connectivity),Java数据库连接,是一个用于数据库访问的应用程...

  • Java自学-JDBC Hello JDBC

    JDBC基础 JDBC (Java DataBase Connection) 是通过JAVA访问数据库 步骤 1 ...

网友评论

    本文标题:数据库访问从原理到实践-java版

    本文链接:https://www.haomeiwen.com/subject/kpzductx.html