你好,欢迎来到电脑编程技巧与维护杂志社! 杂志社简介广告服务读者反馈编程社区  
合订本订阅
 
 
您的位置:技术专栏 / 数据库开发
3.12 利用设计模式实现数据访问的泛化
 

一、泛化数据访问的设计思路
数据访问是应用程序设计的一个重要功能模块,各种开发平台都提供了强大的数据访问组件。ADO.NET对SqlServer以及OleDb、ODBC等采用不同的类进行访问,这些类分布于不同的命名空间,包括System.Data.SqlClient、System.Data.OleDb、System.Data.Odbc等。这些类种类繁多,但是对开发者来说,如果不用到某种数据库的特性,其大体使用方法是一致的,都使用Connection 对象提供与数据源的连接,使用Command执行返回数据、修改数据、运行存储过程等操作命令,使用DataReader从数据源中提供高性能的数据流,使用DataAdapter 提供连接 DataSet 对象和数据源的桥梁。在编写应用系统的时候,希望能不因这么多类的不同而受到影响,能够尽量做到数据库无关,当后台数据库发生变更的时候,不需要更改客户端的代码,即实现对数据库访问的泛化。同时,为了性能和其他原因,也希望提供对数据库访问的缓存,特别是数据库连接的缓存。虽然微软给内置了数据库连接池,但是,自己控制缓存,无疑可以提供更大的灵活性和效率。 这就需要我们在实际开发过程中将这些数据库访问类再作一次封装。本文将详细阐述如何实现数据库的泛化访问,并建立自己的连接池,管理数据库连接。
本文的实现分为三个部分。首先,使用Façade模式(即外观模式)封装数据库访问的细节,用一个类TDBAccess来封装操作数据库的代码,对客户程序(所谓客户程序,指应用程序中,需要使用到这个数据访问模块的其他程序)开放这个类。客户程序不需要直接与操作数据库的实际对象打交道,无需考虑数据库类型,只要把需要执行的SQL语句作为参数传入TDBAccess,即可以执行数据访问操作。其次,在TDBAccess内部,使用类TDBToolFactory来提供真正执行数据访问所需要的数据访问对象,使用Simple Factory模式(即简单工厂模式)实现数据库访问的泛化。第三,使用Singleton模式(即单态模式)管理数据库连接池,以获得更大的灵活性和效率。这三个部分中,Facade模式对外提供接口,其实现过程需要使用到简单工厂模式产生的对象,其中,连接对象用 Singleton模式来进行管理。三个主要的类关系如图1所示。
 



                                 图1 泛化数据访问的主要类的关系
以上三个类在设计时处于同一个自定义的命名空间DataAccess。在设计中,为了便于用户表示层和业务逻辑层处理数据访问时发生的异常,本文定义了一个枚举类型EnumErrorCode,用以描述当前对数据库操作的状态。
二、使用Façade模式封装数据访问
Facade 模式,是一种结构型的设计模式,它定义了一个将子系统的一组接口集成在一起的高层接口,以提供一个一致的界面。通过这个界面,其他系统能够方便地调用子系统中的功能,而不用关心子系统的细节。
在利用ADO.NET进行数据访问时,业务逻辑层以及用户表示层需要不断的与数据库进行交互。完成一次与数据库的交互需要使用到Connection、Command和DataAdapter等类,涉及到初始化数据库、打开数据库连接、执行SQL命令、返回查询结果等操作。可以使用Facade模式对上述数据访问过程进行封装,对客户端仅提供一个TDBAccess类,这个类封装了数据访问中的上述一些重复的过程,只对外提供几个简单的方法,用以执行SQL命令并返回结果。该类的结构如图2所示。
 



                                         图2 应用Façade模式的数据访问类


下面分析其实现代码中的关键函数。
1.函数ExcuteReader。该函数用来执行查询操作,它有两个参数:输入参数 szSQLcmd是客户代码需要执行的SQL命令;是输出参数dr,它是一个对IDataReader对象的引用,是通过IDbCommand执行查询之后产生的结果。该函数主要用于需要处理单条记录的时候。函数的返回值是自定义的枚举值EnumErrorCode。 需要注意的是,函数内部调用其他类的函数时,都是通过接口来引用的。这种面向接口的编程方式可以使对象在实例化时拥有更大的灵活性,是利用设计模式编程的主要方法之一。          
         public static EnumErrorCode excuteReader(string szSQLcmd, ref System.Data.IDataReader dr)
     {
            EnumErrorCode e=EnumErrorCode.Success;
 /*             
              *  此处调用了工具工厂TDBToolFactory的函数,IDbCommand是一个接口,它所定义对象cmd在
*  实例化时,由类TDBToolFactory根据数据库类型自动生成相应的类的实例。函数参见3节中的
*  说明。                 
              */
            IDbCommand cmd = TDBToolFactory.getCommandObj();
/*            
              *  此处调用了数据库连接池中获取连接对象的函数,以获得一个可用的数据库连接。IDbConnection
*  是一个接口,它所定义对象conn在实例化时,由数据库连接池TConnectionPool根据数据库类型
*  自动生成相应的实例。函数参见第4节中的说明。                                                             
             */
            IDbConnection conn = TConnectionPool.getInstance().getConnObj();
     try
      {
       cmd.Connection=conn;
       if(conn.State!=ConnectionState.Open)
       {
                    //重新获取一个可用连接
                    conn = TConnectionPool.getInstance().getConnObj();
       }
       cmd.CommandText = szSQLcmd;
       dr = cmd.ExecuteReader();
                 if (dr != null)
                 {
                    e = EnumErrorCode.Success;
                 }
      }
      catch(Exception ex)
      {
                dr = null;         
                e = EnumErrorCode.DBFailed;                             
      }
      finally
      {
       //释放连接资源
       TConnectionPool.getInstance().restituteConn(conn);
      }
            return e;
      }
2.函数ExcuteNoneQuery。该函数用来执行对数据库的更新作,szSQLcmd是需要执行的SQL命令,主要是执行对数据库的添加、删除以及更新操作。该函数的Command对象和Connection对象的获取都跟上述函数一样,此处不加以说明。  
     public static numErrorCode executeNoneQuery(string szSQLcmd)
     {
             EnumErrorCode e = EnumErrorCode.Success;   
      IDbConnection conn= TConnectionPool.getInstance().getConnObj();
      try
      {
                if (conn.State != ConnectionState.Open)
                {
                    conn = TConnectionPool.getInstance().getConnObj();
                }
       IDbCommand cmdObj= TDBToolFactory.getCommandObj(); 
       cmdObj.Connection = conn;
       cmdObj.CommandText = szSQLcmd;
       cmdObj.ExecuteNonQuery();
                  e = EnumErrorCode.Success;
      }
      catch(Exception ex)
      {
                e = EnumErrorCode.DBFailed;               
      }
      finally
      {
       TConnectionPool.getInstance().restituteConn(conn);    
      } 
      return e;
     }
3.函数ExcuteQuery。该函数同ExcuteReader的功能类似,都是用来执行查询操作。不同的是ExcuteReader函数返回一个IDataReader对象的引用,而这个函数返回一个DataTable 对象。在使用时,ExcuteReader主要使用在需要操作单条记录的地方,而这个ExcuteQuery主要进行批量数据的处理。
     public static EnumErrorCode executeQuery(string szSQLcmd, ref DataTable dt)
     {
             EnumErrorCode e = EnumErrorCode.Success;
             DataSet ds=new DataSet();        
      IDbConnection conn = TConnectionPool.getInstance().getConnObj();
      try
      {
                if (conn.State != ConnectionState.Open)
                {
                    conn = TConnectionPool.getInstance().getConnObj();
                }
     IDbCommand cmdObj=TDBToolFactory.getCommandObj();
     cmdObj.CommandText = szSQLcmd;
     cmdObj.Connection =conn;
     IDbDataAdapter adtObj=TDBToolFactory.getAdapterObj();
     adtObj.SelectCommand=cmdObj;
     adtObj.Fill(ds);
                dt = ds.Tables[0];
                e= EnumErrorCode.Success;
      }
      catch(Exception ex)
      {
                e = EnumErrorCode.DBFailed;              
      }   
      finally
      {
     TConnectionPool.getInstance().restituteConn(conn);    
      }
             return e;  
      }         
三、使用Simple Factory模式泛化数据访问
Simple Factory(简单工厂模式)是对工厂方法的简化,它是一种参数化的工厂方法。该模式对客户隐藏对象实例化的过程,使客户不需要了解使用的对象属于哪个具体的子类。
简单工厂模式使得应用程序对数据库的泛化访问变得容易实现。设计了TDBToolFactory类,该类对外提供两个函数:getCommandObj和getAdapterObj,这两个函数对外生成Command对象和DataAdapter对象。在实现时,只需要在应用程序的配置文件中加入一个标示数据库类型的字段,TDBToolFactory在实例化数据库操作对象时,首先从配置文件中读取该字段,同时读取相应的数据库连接字符串,实例化相应的类。类结构如图3所示。


图3 应用Simple Factory模式TDBToolFactory类


该类主要的两个函数说明如下:
1.函数getAdapterObj。该函数根据配置文件中数据库类型的标示,返回相应的数据适配器IDbDataAdapter对象。
  public static IDbDataAdapter getAdapterObj()
  {
            try
            {
    //读取配置文件中的数据库类型字段
                  string szType=System.Configuration.ConfigurationSettings.AppSettings["DBType"];
    //根据数据库类型,返回相应的数据适配器IDbDataAdapter对象
       if(szType=="SQLServer")
       {
        return new System.Data.SqlClient.SqlDataAdapter();
       }
       else if(szType=="Access")
       {
        return new System.Data.OleDb.OleDbDataAdapter();
       }
       else if(szType=="FoxPro")
       {
        return new System.Data.OleDb.OleDbDataAdapter();
       }
       else if(szType=="ODBC")
       {
        return new System.Data.Odbc.OdbcDataAdapter();
       }
       else
       {
        return null;
       }
            }
            catch (Exception ex)
            {
                return null;
            }   
  }  
2.函数getCommandObj。该函数根据配置文件中数据库类型的标示,返回相应的IDbCommand 对象。
    public static IDbCommand  getCommandObj()
  {           
            try
            {
                //读取配置文件中的数据库类型字段
                string szType =System.Configuration.ConfigurationSettings.AppSettings["DBType"];
//根据数据库类型,返回相应的数据适配器IDbCommand 对象              
                if(szType=="SQLServer")
          {
           return new System.Data.SqlClient.SqlCommand();
          }
          else if(szType=="Access")
          {
           return new System.Data.OleDb.OleDbCommand();
          }
          else if(szType=="FoxPro")
          {
           return new System.Data.OleDb.OleDbCommand();
          }
          else if(szType=="ODBC")
          {
         return new System.Data.Odbc.OdbcCommand();
          }        
                else
                {
                    return null;
                }
            }
            catch(Exception ex)
            {
                return null;
            } 
  }
程序的配置文件如下,各个配置的功能参见配置文件中的注释,本处不再一一列举。
<?xml version="1.0"?>
<configuration xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0">
 <!-- 自定义参数模块  -->
 <appSettings>
  <!--
  DBType标示数据库类型
  如果使用SQLServer2000, 则value值为"SQLServer";
  如果使用FoxPro,则Value的值为"FoxPro";
  如果使用Access,则值为"Access"; 
  如果使用ODBC,则值为"ODBC";
      -->
  <add key="DBType" value="SQLServer"></add>
  <!--
  使用SQLServer时使用的连接字符串
      -->
  <add key="SQLConnstring" value="server=.;dataBase=OA;uid=sa;pwd=;"></add>
  <!--
  使用Access数据库时使用的连接字符串  
      -->
  <add key="AccessConnstring" value="DataBase/DataBase.mdb"></add>
  <!--
  使用FoxPro时使用的连接字符串
       -->
  <add key="FoxProConnstring" value="DataBase"></add>
  <!--
  使用ODBC时使用的连接字符串
         -->
  <add key="ODBC" value="MES"></add>
  <!--
  自定义数据库连接池的大小
      -->
  <add key="connPoolSize" value="100"></add>
  </appSettings>  
</configuration>
四、 用Singleton模式管理数据库连接池
先看Singleton模式的经典含义:保证一个类仅有一个实例,并提供一个访问它的全局访问点。Singleton模式是一种细粒度的模式。它的结构非常简单,包括防止其他对象创建实例的私有构造函数、保存唯一实例的私有静态成员变量,以及全局访问接口等。现在,需要构建一个缓冲池,用以保存数据库对象,并供整个应用程序来调用,正好适用Singleton模式。
Singleton模式的数据库连接池类TConnectionPool的结构如图4所示。

 
图4 Singleton模式的数据库连接池
该类的构造函数是私有的;同时,该类拥有一个私有静态成员变量TConnectionPool,用以保存类的唯一实例。该类的一些主要属性和函数的说明如下。
1.m_ConnContainer。这是一个ArrayList类型的成员变量,用以存放数据库连接对象。
2.m_poolObj。这是一个静态的TConnectionPool类型的成员变量,是全局连接池实例。
3.构造函数TConnectionPool。这是一个私有的构造函数。私有的目的是为了防止其它类通过new的方法来产生该类的对象,从而保证整个应用中只有该类一个实例存在。
private TConnectionPool()
{
  //读取根据配置文件中自定义的连接池大小
int iSize = Convert.ToInt32(System.Configuration.ConfigurationSettings.AppSettings["connPoolSize"]);
     m_ConnContainer=new ArrayList(iSize);
     }
4.函数getInstance。该函数是数据库连接池的全局访问点,由它向外提供对数据库连接池的访问入口。
        public static TConnectionPool getInstance()
        {
            if (m_poolObj == null)
            {
                m_poolObj = new TConnectionPool();
            }
            return m_poolObj;
        }
 5.函数getNewConnObj。该函数根据配置文件中数据库的类型以及连接字符串,生成数据库连接对象。这个函数是一个私有函数,用于在连接池内部建立对数据库的可用连接。
       private System.Data.IDbConnection getNewConnObj()
        {
            string szConnstring;
            IDbConnection conn;
     //读取根据配置文件中数据库类型
            string szType = System.Configuration.ConfigurationSettings.AppSettings["DBType"];
            if (szType == "SQLServer")
            {
                szConnstring = System.Configuration.ConfigurationSettings.AppSettings["SQLConnstring"];
                conn = new SqlConnection(szConnstring);
            }
            else if (szType == "FoxPro")
            {
                string szPath = AppDomain.CurrentDomain.BaseDirectory.ToString();
                szPath = szPath + "DataBase";
                szConnstring = "Provider=Microsoft.Jet.OLEDB.4.0;Extended Properties=dBASE IV;User ID=Admin;Password=;Data Source=" + szPath;
                conn = new System.Data.OleDb.OleDbConnection(szConnstring);
            }
            else if (szType == "Access")
            {  
                string szPath = AppDomain.CurrentDomain.BaseDirectory.ToString();
                szPath = szPath + System.Configuration.ConfigurationSettings.AppSettings["AccessConnstring"];
                szConnstring = "Provider=Microsoft.Jet.OLEDB.4.0;Password=;Data Source=" + szPath;
                conn = new OleDbConnection(szConnstring);
            }
            else if (szType == "ODBC")
            {
            szConnstring = "DSN="+System.Configuration.ConfigurationSettings.AppSettings["ODBC"]+";";               
                conn = new System.Data.Odbc.OdbcConnection(szConnstring);
            }
            else
            {
                return null;
            }
            try
            {
                conn.Open();
            }           
            catch (Exception ex)
            {
                return null;                
            }
            return conn;
        }
 6.函数getConnObj。这是一个public类型的函数,对外提供可用的数据库连接对象。
        public IDbConnection getConnObj()
        {
            IDbConnection conn = null;
            //如果池中有可用连接,则获取当前该连接
            if (m_ConnContainer.Count > 0)
            {
                conn = (IDbConnection)m_ConnContainer[0];
                m_ConnContainer.RemoveAt(0);
                if (conn.State == ConnectionState.Closed)
                {
                    getConnObj();
                }
            }
            else
            {
                conn = getNewConnObj();
            }
            return conn;
        }
7.函数restituteConn。这也是一个public类型的函数,数据操作类TDBAccess在使用完访问数据的操作后,需要归还这个可用的连接到数据库连接池中,供其他数据访问操作使用该连接。
        public void restituteConn(IDbConnection connObj)
        {          
            m_ConnContainer.Add(connObj);
        }
8.函数freeAllConn。这也是一个public类型的函数,应用程序在运行结束后,需要调用这个函数来断开所有与数据库的连接。
        public void freeAllConn()
        {
            for (int i = 0; i < m_ConnContainer.Count - 1; i++)
            {
                if (((IDbConnection)m_ConnContainer[i]).State != ConnectionState.Closed)
                {
                    ((IDbConnection)m_ConnContainer[i]).Close();
                    ((IDbConnection)m_ConnContainer[i]).Dispose();
                }
            }
        }
五、调用示例
我们只需要在应用程序中加入包含上述三个类的文件,并引入命名空间DataAccess,就可以使用这几个类了。客户程序在使用这个功能模块时,只需要了解TDBAccess这一个类的结构。下面举例说明,该例子来源于笔者编写的OA程序的一个内部BBS模块,该函数属于业务逻辑层的,功能是获取当前所有正常开放的内部BBS论坛,并将该信息存于一个DataTable中,供用户表示层来使用,调用的代码如下:
        /*
         * 函数名:getAllInuseBBS
         * 功   能:获取所有的正常开放的论坛信息
         * 参   数:[Out ]  dt 用以返回信息的DataTable                                                                     
         */
        public static EnumErrorCode getAllInuseBBS(ref DataTable dt)
        {
            string szSQLcmd = null;
            if (dt == null)
            {
                return EnumErrorCode.InputContainerIsNull;
            }
            try
            {
               szSQLcmd = "select bName,Subject,WelcomeWord,Manager from BBSInfo where status='开放'";
    //客户端程序只需要使用TDBAccess提供的方法即可,不需要关心访问数据库的细节             
               return TDBAccess.executeQuery(szSQLcmd, ref dt);
            }
            catch (Exception ex)
            {
                return EnumErrorCode.DBFailed;
            }
        }      
该DataTable即可交给用户表示层使用,部分实例代码如下:
            DataTable dt = new DataTable();
            BusinessRules.TBBSRules.getAllInuseBBS(ref dt);          
            gvAllBBS.DataSource = dt;
            gvAllBBS.DataBind();
其中,gvAllBBS是一个用以显示所有论坛信息的GridView。      
   
六、结语
以上介绍了在ADO.NET下利用设计模式实现数据访问泛化的一种方法。笔者在实际项目中,在数据库处理层时,多次采用了上面的方法,取得了很好的效果。从这个例子也可以看出,设计模式的应用,极大地提高了面向对象软件设计的抽象层次(更多针对接口的抽象),为软件设计更高层次更大规模的复用提供了可能,大大减小了设计的复杂度,同时也提高了软件设计的质量,更有利于软件的维护。

 

  推荐精品文章

·2024年12月目录 
·2024年11月目录 
·2024年10月目录 
·2024年9月目录 
·2024年8月目录 
·2024年7月目录 
·2024年6月目录 
·2024年5月目录 
·2024年4月目录 
·2024年3月目录 
·2024年2月目录 
·2024年1月目录
·2023年12月目录
·2023年11月目录

  联系方式
TEL:010-82561037
Fax: 010-82561614
QQ: 100164630
Mail:gaojian@comprg.com.cn

  友情链接
 
Copyright 2001-2010, www.comprg.com.cn, All Rights Reserved
京ICP备14022230号-1,电话/传真:010-82561037 82561614 ,Mail:gaojian@comprg.com.cn
地址:北京市海淀区远大路20号宝蓝大厦E座704,邮编:100089