`

使用设计模式构建通用数据库访问类

 
阅读更多

使用设计模式构建通用数据库访问类

发布时间:2003.03.20 10:31 来源:赛迪网作者:孙亚民

  在应用程序的设计中,数据库的访问是非常重要的,我们通常需要将对数据库的访问集中起来,以保证良好的封装性和可维护性。在.Net中,数据库的访问,对于微软自家的SqlServer和其他数据库(支持OleDb),采用不同的访问方法,这些类分别分布于System.Data.SqlClient和 System.Data.OleDb名称空间中。微软后来又推出了专门用于访问Oracle数据库的类库。我们希望在编写应用系统的时候,不因这么多类的不同而受到影响,能够尽量做到数据库无关,当后台数据库发生变更的时候,不需要更改客户端的代码。

  有的时候,为了性能和其他原因,我们也希望提供对数据库访问的缓存,特别是数据库连接的缓存。虽然微软给我们内置了数据库缓存,但是,自己控制缓存,无疑可以提供更大的灵活性和效率。

  这就需要我们在实际开发过程中将这些数据库访问类再作一次封装。这里,介绍一种在实际应用中得到了非常好的效果的实作策略。Factory和Silgleton设计模式是使用的主要方法。

  我们先来看看Factory的含义:定义一个用于创建对象的接口,让子类决定实例化哪一个类。Factory Method使一个类的实例化延迟到其子类。我们这里可能会处理对多种数据库的操作,因此,需要首先定义一个操纵数据库的接口,然后,根据数据库的不同,由类工厂决定实例化哪个类。

  下面,我们首先来定义这个访问接口。为了方便说明问题,我们为这个类定义了比较少的方法,其他的方法是很容易参照添加的。同时注意,我这里使用了abstract class来定义这个访问接口,而不是interface,理由在后面可以看到。

<ccid_nobr></ccid_nobr>

<ccid_code></ccid_code>public abstract class DBOperator
	{		
	public abstract IDbConnection Connection{get;}  //得到数据库连接
	public abstract void Open();                    //打开数据库连接
	public abstract void Close();                   //关闭数据库连接
	public abstract void BeginTrans();              //开始一个事务
	public abstract void CommitTrans();             //提交一个事务
	public abstract void RollbackTrans();           //回滚一个事务
	public abstract void exeSql(string strSql,string[] strParams,object[] objValues);      
        //执行Sql语句,没有返回值
        public abstract  DataSet exeSqlForDataSet(string QueryString);//执行Sql,返回DataSet
	}

  然后,我们分别为Sql Server和OleDb数据库编写两个数据访问的具体实现类:

  Sql Server的数据库访问类:

<ccid_nobr></ccid_nobr>

<ccid_code></ccid_code>internal class SqlDBOperator: DBOperator
	{ 		
	private SqlConnection conn;         //数据库连接
	private SqlTransaction trans;       //事务处理类
	private bool inTransaction=false;   //指示当前是否正处于事务中
	public override IDbConnection Connection
	{		
	  get{return this.conn;}
	}
	public SqlDBOperator(string strConnection)
	{	
	  this.conn= new SqlConnection(strConnection);
	}
	public override void Open()		
	{		
	  if(conn.State.ToString().ToUpper()!="OPEN")
                this.conn.Open();
          }		
	public override void Close()
	{		
	  if (conn.State.ToString().ToUpper()=="OPEN")
                this.conn.Close();
	}	
	public override void BeginTrans()	
	{		
	  trans=conn.BeginTransaction() ;		
	    inTransaction=true;	
	}	
	public override void CommitTrans()
	{
	  trans.Commit();
	    inTransaction=false;	
	}
	public override void RollbackTrans()	
         {
	  trans.Rollback();
	    inTransaction=false;	
	}	
	public override void exeSql(string strSql,string[] strParams,object[] strValues)
	{		
	  SqlCommand cmd=new SqlCommand();		
	  cmd.Connection=this.conn ;		
	  if(inTransaction)			
	    cmd.Transaction=trans;	
	  if((strParams!=null)&&(strParams.Length!=strValues.Length) )	
	    throw new ParamValueNotMatchException("查询参数和值不对应!");
                  cmd.CommandText=strSql;		
	  if(strParams!=null)		
	{
	  for(int i=0;i<strParams.Length;i++)
                cmd.Parameters.Add(strParams[i],strValues[i]);	
	}
	   cmd.ExecuteNonQuery();
	}
          public override  DataSet exeSqlForDataSet(string QueryString)		
	{	
	  SqlCommand cmd=new SqlCommand();	
                 cmd.Connection=this.conn ;		
	  if(inTransaction)
                cmd.Transaction=trans;			
	  DataSet ds = new DataSet();		
	  SqlDataAdapter ad = new SqlDataAdapter();
	  cmd.CommandText=QueryString;		
	  ad.SelectCommand =cmd;		
	  ad.Fill(ds);		
	  return ds;	
	  }	
    }

  OleDb数据库操作的类同Sql Server数据库操作的类非常相似,只是把相应的Sql类替换成OleDb类。需要注意的是,因为OleDb和Sql Server的参数传递方式不一致,所以,这里需要做一点小小的转换,将"@参数名"类型的参数转换成"?",这个细节希望读者能够注意到。代码如下:

<ccid_nobr></ccid_nobr>

<ccid_code></ccid_code>internal class OleDBOperator : DBOperator
	{	
	private OleDbConnection conn;	
	private OleDbTransaction trans;	
	private bool inTransaction=false;
	public OleDBOperator(string strConnection)	
	{		
	   this.conn= new OleDbConnection(strConnection);		
	}	
	public override IDbConnection Connection		
	{			
	   get{return this.conn;}		
	}		
	public override void Open()	
	{
	if(conn.State.ToString().ToUpper()!="OPEN")
             this.conn.Open();		
	}
	public override void Close()
	{
       if (conn.State.ToString().ToUpper()=="OPEN")
             this.conn.Close();		
	}
	public override void BeginTrans()
	{		
	  trans=conn.BeginTransaction() ;		
	   inTransaction=true;		
	}		
	public override void CommitTrans()	
	{		
	  trans.Commit();	
	  inTransaction=false;
	}	
	public override void RollbackTrans()	
	{		
	  trans.Rollback();		
	  inTransaction=false;	
	}	
	public override void exeSql(string strSql,string[] strParams,object[] strValues)	
	{		
	OleDbCommand cmd=new OleDbCommand();	
             cmd.Connection=this.conn ;		
	if(inTransaction)	
	  cmd.Transaction=trans;	
         if((strParams!=null)&&(strParams.Length!=strValues.Length) )
	    throw new ParamValueNotMatchException("查询参数和值不对应!");
	    cmd.CommandText=this.ChangeQueryString(strSql);
             if(strParams!=null)		
	{
	for(int i=0;i<strParams.Length;i++)
             cmd.Parameters.Add(strParams[i],strValues[i]);
	}
	  cmd.ExecuteNonQuery();	
	}
	public override  DataSet exeSqlForDataSet(string QueryString)		
	{
	  OleDbCommand cmd=new OleDbCommand();
                cmd.Connection=this.conn ;		
	  if(inTransaction)
            cmd.Transaction=trans;
	  DataSet ds = new DataSet();
	  OleDbDataAdapter ad = new OleDbDataAdapter();
                cmd.CommandText=QueryString;	
	  ad.SelectCommand =cmd;		
	  ad.Fill(ds);	
	  return ds;	
	  }
	}

  现在我们已经完成了所要的功能,下面,我们需要创建一个Factory类,来实现自动数据库切换的管理。这个类很简单,主要的功能就是根据数据库连接字符串,判断使用什么数据库,然后,返回适当的数据库操纵类。在这里,判断的方法很简单,只是根据两种数据库连接字符串的不同来判断。在实际中,随着数据库类的增加,判断的方法可能会有所变化,读者应当根据自己的实际情况来做相应的调整。

<ccid_nobr></ccid_nobr>

<ccid_code></ccid_code>public class DBOperatorFactory
    {	
    public static DBOperator GetDBOperator(string strConnection)
    {	
  if(strConnection.IndexOf("provider=")<0) //SqlServer	
    {		
  return new SqlDBOperator(strConnection);	
    }		
  else     //other database	
    {		
  return new OleDBOperator(strConnection);	
    }	
    }
    }

  好了,现在,一切都完成了,客户端在代码调用的时候,可能就是采用如下形式:

<ccid_nobr></ccid_nobr>

<ccid_code></ccid_code>DBOperator db=DBOperatorFactory.GetDBOperator(strConnection)
db.Open();
db.需要的操作
db.Close();

或者:
DBOperator db=DBOperatorFactory.GetDBOperator(strConnection)
db.Open();db.BeginTrans();
try
{
db.需要的操作	
db.CommitTrans();
}
catch
{	
db.RollbackTrans();
}
db.Close();

  当数据库发生变化的时候,DBOperatorFactory会根据数据库连接字符串自动调用相应的类,客户端不会感觉到变化,也不用去关心。这样,实现了良好的封装性。当然,前提是,你在编写程序的时候,没有用到特定数据库的特性,例如,Sql Server的专用函数。

  实际上,Factory模式也可以不使用Factory类来实现,而是让接口抽象类自己来管理,这可以称作自管理的Factory,是Factory模式的一种变形。这么做的好处,是可以免去一个Factory类,使代码更加简练。这么做,我们需要对DBOperator类做一些改变,增加一个Instance方法。这也是对DBOperator采用抽象类而不是接口的原因(接口的方法不能有实现),代码如下:

<ccid_nobr></ccid_nobr>

<ccid_code></ccid_code>public static DBOperator Instance(string strConnection)	
	{		
	if(strConnection.IndexOf("provider=")<0) //SqlServer	
	{			
	return new SqlDBOperator(strConnection);	
	}	
	else     //other database	
	{			
	return new OleDBOperator(strConnection);	
	}
	}

  然后,客户端代码就可能是类似于下面的形式:

<ccid_nobr></ccid_nobr>

<ccid_code></ccid_code>DBOperator db= DBOperator.Instance(strConnection)
db.Open();
db.需要的操作
db.Close();

  下面来看看连接池的做法,方法就是Singleton。

  先看Singleton模式的经典含义:保证一个类仅有一个实例,并提供一个访问它的全局访问点。推而广之,当我们需要精确控制类在系统中的实例的个数的时候,就可以使用Singleton模式。现在,我们需要构建一个缓冲池,保存数据库类的实例,正好是Singleton模式发挥作用的时候。

  我们仍然让DBOperator类自己来管理这个缓冲池,为了实现这个目的,我们需要对DBOperator类做一些变化:

  首先,增加两个变量:

<ccid_nobr></ccid_nobr>

<ccid_code></ccid_code> static DBOperator[] ConnectionPool=new
     DBOperator[int.Parse(ConfigurationSettings.AppSettings["PoolCount"])];	
 static int CurrentPosition=-1;

  然后,对Instance方法做一些改变:

<ccid_nobr></ccid_nobr>

<ccid_code></ccid_code>public static DBOperator Instance(string strConnection)
    {
    if(ApplicationConfiguration.PooledConnectionCount<1)  //没有缓冲		
    {		
    return CreateNewDBOperator(strConnection);	
    }		
    else			
    {			
    CurrentPosition++;	
    if(CurrentPosition==ApplicationConfiguration.PooledConnectionCount)
         CurrentPosition=0;	
    if(ConnectionPool[CurrentPosition]==null)	
    {		
    ConnectionPool[CurrentPosition]=CreateNewDBOperator(strConnection);
    }				
    return ConnectionPool[CurrentPosition];	
    }	
    }	
    private static DBOperator CreateNewDBOperator(string strConnection)	
    {		
    if(strConnection.IndexOf("provider=")<0) //SqlServer		
    {			
    return new SqlDBOperator(strConnection);	
    }	
    else     //other database		
    {		
    return new OleDBOperator(strConnection);	
    }		
    }

  这里使用的算法比较简单,只是为了能够比较清楚地说明问题,读者应当能够在实际使用过程中,实现更好的算法。

  以上,介绍了一种通用数据库操作类的实现设计方法,希望能够对大家有所启发。笔者设计Websharp中间件的时候,在数据库处理层,采用了上面的方法,取得了很好的效果。需要完整源代码的,可以同作者联系。

  作者:孙亚民,毕业于南京大学,中国科技大学计算机技术在读研究生,熟悉J2EE和.Net平台,苏州某软件公司技术总监,基于.Net平台的Websharp中间件的设计者。可以通过 sunny_y_m@163.com 联系。

分享到:
评论

相关推荐

    使用设计模式构建通用数据库访问类.

    数据库访问类 C# 设计模式构建通用数据库访问类

    .NET构建通用数据库访问类

    NET构建通用数据库访问类,使用设计模式构建通用数据库访问类

    二十三种设计模式【PDF版】

    设计模式之 Template(模板方法) 实际上向你介绍了为什么要使用 Java 抽象类,该模式原理简单,使用很普遍. 设计模式之 Strategy(策略) 不同算法各自封装,用户端可随意挑选需要的算法. 设计模式之 Chain of ...

    Java数据库编程宝典3

    2.1 数据库设计应考虑的事项 2.1.1 项目规范 2.1.2 设计表 2.1.3 生成发票 2.2 引用完整性 2.2.1 通用完整性规则 2.2.2 特定于数据库的完整性规则 2.3 小结 第3章 SQL基础 3.1 SQL语言 3.2 SQL数据类型...

    Java数据库编程宝典2

    2.1 数据库设计应考虑的事项 2.1.1 项目规范 2.1.2 设计表 2.1.3 生成发票 2.2 引用完整性 2.2.1 通用完整性规则 2.2.2 特定于数据库的完整性规则 2.3 小结 第3章 SQL基础 3.1 SQL语言 3.2 SQL数据类型...

    Java数据库编程宝典1

    2.1 数据库设计应考虑的事项 2.1.1 项目规范 2.1.2 设计表 2.1.3 生成发票 2.2 引用完整性 2.2.1 通用完整性规则 2.2.2 特定于数据库的完整性规则 2.3 小结 第3章 SQL基础 3.1 SQL语言 3.2 SQL数据类型...

    Java数据库编程宝典4

    2.1 数据库设计应考虑的事项 2.1.1 项目规范 2.1.2 设计表 2.1.3 生成发票 2.2 引用完整性 2.2.1 通用完整性规则 2.2.2 特定于数据库的完整性规则 2.3 小结 第3章 SQL基础 3.1 SQL语言 3.2 SQL数据类型...

    asp.net知识库

    DbHelperV2 - Teddy的通用数据库访问组件设计和思考 也论该不该在项目中使用存储过程代替SQL语句 如何使数据库中的表更有弹性,更易于扩展 存储过程——天使还是魔鬼 如何获取MSSQLServer,Oracel,Access中的数据字典...

    ASP.NET 控件的使用

    第一部分 构建ASP.NET页面 第1章 ASP.NET Framework概览 2 1.1 ASP.NET和.NET Framework 5 1.1.1 框架类库 5 1.1.2 公共语言运行库 9 1.2 ASP.NET控件 10 1.2.1 ASP.NET控件概览 11 1.2.2 HTML控件 12 1.2.3 理解...

    互齐智能Excel企业标准版客户端

    将操作界面完全嵌入到Excel当中,并利用C/S(多层)模式和B/S模式,高强度数据加密传输,实现远程多数据库安全访问,不用编程,结合SQLServer数据库,通过在Excel中设计模板、表间公式、工作流等,即可轻松构建适合...

    基于J2EE框架的个人博客系统项目毕业设计论文(源码和论文)

    3. Web服务器:Tomcat 5.5及以上版本,配合MVC设计模式及 Hibernate开发架构; 4. 客户端运行环境:能运行IE 5以上或Netscape 5以上浏览器的操作系统,配合使用Ajax技术; 5. 客户端运行工具:目前的系统采用...

    《Delphi 深度编程及其项目应用开发》PDF书及代码

    13.1.1 系统流程 13.1.2 系统功能 13.2 构建客户端应用程序框架 第14章 动态连接应用服务器的实现 第15章 通用权限管理模块的设计 15.1 系统登录的设计 15.2 权限设计表中数据的维护 第16章 通用查询和报表组件的...

    Delphi深度编程技术

    第11章 物资管理信息系统后台数据库设计 11.1 关系型数据库概述 11.1.1 关系型数据库 11.1.2 物资管理信息系统数据库的建立 11.2 物资管理信息系统数据结构的设计 11.2.1 权限管理数据结构的设计 11.2.2 ...

    JAVA_API1.6文档(中文)

    java.sql 提供使用 JavaTM 编程语言访问并处理存储在数据源(通常是一个关系数据库)中的数据的 API。 java.text 提供以与自然语言无关的方式来处理文本、日期、数字和消息的类和接口。 java.text.spi java.text ...

    Visual C#网络编程技术与实践源代码

    通过.NET Framework类库,可以访问多种操作系统服务和其他有用的精心设计的类,这些类可显著加快开发周期。 本书大量地使用了.NET Framework提供的类库中的函数来协同开发,通过使用这些已经被封装的类来协助开发...

    Visual C++ 2005入门经典--源代码及课后练习答案

    7.3.3 访问类的数据成员 308 7.3.4 类的成员函数 310 7.3.5 成员函数定义的位置 312 7.3.6 内联函数 312 7.4 类构造函数 313 7.4.1 构造函数的概念 313 7.4.2 默认的构造函数 315 7.4.3 在类定义中...

    领域驱动设计第二分卷

    第12章 把设计模式和模型联系起来 237 12.1 策略 238 12.2 组合 241 12.3 为什么不用flyweight? 245 第13章 向更深层理解重构 247 13.1 发起重构 247 13.2 探索团队 248 13.3 前期工作 249 13.4 针对开发...

    领域驱动设计第一分卷

    第12章 把设计模式和模型联系起来 237 12.1 策略 238 12.2 组合 241 12.3 为什么不用flyweight? 245 第13章 向更深层理解重构 247 13.1 发起重构 247 13.2 探索团队 248 13.3 前期工作 249 13.4 针对开发...

    PHP3程序设计

    8.8 构建多语言网站 124 8.9 常见问题 127 8.10 总结 127 第9章 模式匹配 129 9.1 正则表达式定义 129 9.1.1 方括号表达式 130 9.1.2 转义字符 130 9.2 POSIX风格的函数 131 9.2.1 ereg 和eregi 131 9.2.2 ereg_...

Global site tag (gtag.js) - Google Analytics