数据库设计入门


*注:本文大量引用了参考文献所列资料中的图片和知识,在此对这些文章和网站的作者表示由衷的感激,是你们的分享让我学到了很多知识。

实际开发中,少不了要和各种数据库打交道,一般说来,传统的关系型数据库使用方式主要有两种,一是普通用户通过命令行接口直接访问数据库,建立各种表格,维护这些表格(增、删、查、改),使用SQL语言基于数据生成各种报告;二是程序员基于数据库API抽象和封装特定于应用的数据库访问模式,实现对数据库的访问。两种方式都有各自的难点——前者难在数据建模上,良好的数据建模使得数据库中的表格易于维护,糟糕的设计则会产生不必要的冗余和使用上的蹩脚;后者难在访问模式的设计上,同样,好的设计使得接口易于扩展,不好的设计使得代码难以维护。因此,抓住两个中心点,良好的数据模型和数据访问模式,本文就从这两个方面展开,谈一谈自己在学习过程中的一些体会,若读者诸君能从本文中找到笔者思维上的盲点和文章中的错误,非常感激您能够不吝赐教;若读者诸君能够通过本文得到一点点的启发,则又幸矣。

一.   数据库设计

数据库设计是一门艺术,这里的数据库设计主要是指选用某关系型数据库的产品,然后在此基础上根据需求设计表格,这些相互关联的表格构成了我们需要的“数据库”,这里以一个非常简单的网上书店应用作为数据库设计的例子,介绍数据设计的一些入门知识。

1   数据建模

做任何事情之前都要先明确目标,数据建模也不例外。建模的第一步就是明确自己的目标。明确目标之后要考虑的就是想要捕捉哪些数据,也就是实体抽象。模型就是一种抽象,通过建模,我们把客观世界中我们感兴趣的对象身上的某些属性抽取出来,忽略掉其他的一些属性,建立它们的模型。

1.1 主键的选择

设计表格的同时,还要考虑表格之间可能的关联,因为表格之间如果没有相互关联的话,数据库的威力就要大打折扣了。要在数据库之间建立关系,主要是靠“键”,键有两种,分为主键和外键。主键用于唯一地标识表格中的一条记录,消除二义性;而外键则用于引用其他表的主键,这样表之间的关联就建立起来了。主键可以是一个属性,也可以是属性的组合(复合主键),关于主键的选取,并没有绝对的法则,Fabian Pascal在《SQL and Relational Basics》中提到,主键的设计决策要参考三个原则:

l  最小化原则:用最少的列作为主键;

l  稳定性原则:作为主键的列很少被修改;

l  简单/熟悉原则:作为主键的列既简单又被用户所熟悉。

除以上三个原则外,对键的设计还有一个经验之谈——尽量使用整数的数值类型作为主键,比如学号,数值类型的列比文本容易处理,且考虑到有时候姓名、地址这类文本信息有输入错误和发生重复的可能(比如一家人分别下单,派送地址都一样,那么地址虽然唯一,但在这里就不适合作为主键)这样数据就被简单归类为三个实体,如图1.2所示。

1.1.1 建立联系

有了主键和外键,我们就有了设计表之间关联关系的基础,现实世界中的实体之间的关系常常是错综复杂的,反映到数据模型中,我们就需要在表格之间建立联系,主要是三种:一对一、一对多和多对多。

1)一对一关系

如果对于第一张表中的每一条记录,在第二张表中最多存在一条记录与其相对应,则称两张表为一对一关系。现实世界中一对一关系很少,这种关系模式主要用于对信息访问加以限制,比如医院数据库中使用表Patients_TBL来维护病人信息,但可能使用Confidential_TBL来存放敏感信息(姓名,身份证号和住址等等),这两张表中每条记录都是针对同一个病人的,因此主键都是PatientId,且一一对应。

2)一对多关系

如果对于第一张表中的每一条记录,在第二张表中有0个,1个或多条记录与之相对应;同时,对于第二张表中的每一条记录,在第一张表中仅有一条记录与之相对应,则称两张表为一对多关系。比如披萨店的订单可能会订各种口味的披萨饼,那么Order_TBL和OrderDetail_TBL之间就是一对多的关系。一对多关系通常也被称为“父-子”关系,它是三种关系中最常见的。

3)多对多关系

如果对于第一张表中的每一条记录,在第二张表中都有多条记录与之相对应;同时,对于第二张表中的每一条记录,在第一张表中都有多条记录与之相对应,则称两张表为多对多关系。多对多关系在数据库中不能被直接建模,它常常被设计为两个一对多关系——通过增加一个连接表。比如一位顾客可能买了多个保险公司的保险,而一个保险公司同时也拥有多个顾客,因此Cutomers_TBL和Insurers_TBL之间就是多对多的关系,此时通过建立一张连接表CustInsurance_TBL将两张表变为两个一对多关系:一个顾客购买了0个,1个或多个保险;与此同时保险公司出售的保险也是这样一个关系。

1.1.2设计范式

         数据库建模有很多范式,一级比一级高,这里只介绍前三种:第一、第二和第三范式。范式的使用主要是为了在各个层面各个阶段尽量消除数据的冗余。满足第N范式的数据库一定先满足第N-1范式。但正如前文所说的,数据库设计是一门艺术,有时候不是必须要严格遵守这些范式,反而适当的数据冗余能够提高查找效率,这个是需要特别注意的,因此数据库设计常常不是一蹴而就的,需要逐步精炼和反复设计。

1)第一范式(1NF)

1NF:所有列值必须是原子的(不可分解)

       在“Fundamentals of Relational Database Design”中介绍了这么一个例子,如图1.1所示。该表的设计不满足第一范式,因为很明显Items列并不是原子的,不仅将购买数量和购买工具名称放在一起,且一列中存放了多个工具的信息,因此要分解该列,使得所有的列值都变成不可分割的,就像图1.2展示的那样。

       但这样的表格还是有问题,因为每条记录都只包含了Item1、Item2和Item3,万一顾客要买5件,6件工具怎么办?朴素的方法是将一条记录扩展成Item1到Item6,但是下次碰到买9件工具的客户呢?20件工具的客户呢?从这里可以看出,表设计的弊端在于非必要地限定购买量,且多个ItemX列和QuanX列实际上是重复的,所以还需要改造成图1.3的样子——所有的ItemX和QuanX列合并为Item列和Quantity列,每条记录仅描述一种购买的工具。所以实际上第一范式除了要求列值是原子的以外,还要求列没有重复组。

2)第二范式

2NF:每一个非键的列都完全依赖于整个主键

通俗地说,第二范式要求主键能够描述表中每一条记录中的每一列。通过一个主键能够决定全部的其他列,而不是部分列。图1.4中的表为OrderId和OrderItem#构成的联合主键。虽然满足1NF,但并不满足2NF——给定OrderId,可以知道CustomerId和OrderDate,并不需要OrderItem#;反之,给定OrderItem#,可以知道Quantity,ProductId和ProductDescription信息,不需要OrderId参与。解决方法就是——分解,将订单表分解为订单和订单明细即可,其中订单明细中的OrderId为外键,如图1.5所示。

3)第三范式

3NF:所有非键列都是互相独立的

       也就是说,不作为键的列之间不存在依赖关系,最常见的就是,一般情况下不要存储可以依赖其他列计算出值的列,比如表中不要有计算总和的列——应该用SQL语句来计算生成。但是,有时候为了提高查询效率,可以适当地违反3NF,存储一些计算值以提高速度。非计算依赖的例子也有,比如图1.5中的订单细节表中ProductId和ProductDescription就是相互依赖的列,每次修改前者时,都要把后者更新一次。解决的办法仍然是分表,将ProductId与ProductDescrption的对应关系分离出来形成查找表tblProduct,此时前者成为该表的主键和订单细节表的外键,如图1.6所示。

1.2 完整性规则

         完整性规则有两类:一般完整性规则和数据库特有的完整性规则。一般完整性规则又分为两种——实体完整性和引用完整性。前者要求数据库的主键不能为空值(NULL);后者要求数据库的外键不能包含任何不匹配的外键值,即:

l  除非被引用的记录存在,否则无法向一个包含外键的表中添加记录;

l  若被引用的记录被修改或删除,外键所在的表对应的记录不能变成“孤儿”。

所以,数据库就有了三种选项来保证引用完整性。Disallow使得这样的改变完全不允许;Cascade要求一个表中的变化被同步到其他表中;而 Nullify使得删除操作把外键置空。

数据库特有的完整性规则又称为业务规则,是与具体的应用相关的,它在一定程度上防止坏数据进入数据库,比如管理订单的数据库,派送日期应该在下单日期之后,用户购买的数量不能是负值等等,它是业务逻辑意义上的“合法性约束”。

1.3 一个简单的数据库设计

下面,我们来为网上书店建立一个很简单的关系型数据库,设计数据库的第一步就是要明确自己的目标,因此设计之初我们只有一个朴素的想法,如图1.7所示。

考虑得简单一些的话,我们肯定需要知道书店都有哪些书,书名叫什么,有多少本,售价几何,作者是谁等等,很自然地,就应该有一张表格来存放书籍的这类信息(一张表格只负责一种职责,只描述一种信息);用户要来买书,我们要把书送到人家手上,就要有派送地址,需要记录用户的姓名,年龄,地址等信息,那么维护客户信息的表也是必不可少的;除此之外,还需要一个“订单”表,用于维护用户每次的购买记录。表的初步设计就是考虑捕捉我们感兴趣的一些信息,然后进行实体抽象和简单的归类。从图1.8可以看出,订单表的设计有些问题,没有下单日期,因此要添加,且该表不满足第二范式,主键的依赖性不是完全的,需要做分表,图1.9为进一步修改后的设计。订单表还是有问题 ,quantity应该放到订单细节表中,因为book_id 可以决定某本书买了多少本,且quantity可以有一个为1的默认值(通常情况下顾客只会买一本),此外,一个book_id不足以做订单细节的主键,因为多个顾客可能会买同一本书,在这种情况下,一个book_id就会对应多条记录,因此这个表应该是book_id和order_id做联合主键,如图1.10所示。

进一步理论联系实际后发现,订单细节表和书籍表设计有问题,price冗余是第一个原因,第二个原因是,订单细节里的price应该理解为原价乘以折扣后的实际售价,所以订单细节里的price依赖于discount,应当去掉。最后得到图1.11所示的数据库。

这里要说明的是,这种简单到近乎幼稚的数据库在现实生产环境中是不存在的,真实的数据库设计往往需要对需求和业务的深入理解和长时间的精炼以及反复设计,因此数据库设计是一门艺术,要在实际工作中才能逐步掌握。

1.4 SQL建表

下面该将图表中的数据模型转换成真实的表格了,通过一些工具可以自动生成对应的SQL语句,我们的数据库对应的SQL语句如代码清单1-1所示。

代码清单1-1

CREATE  TABLE IF NOT EXISTS `xxx`.`Customers_TBL` (
  `customer_id` INT NOT NULL ,
  `first_name` VARCHAR(32) NOT NULL ,
  `last_name` VARCHAR(32) NOT NULL ,
  `age` INT NULL ,
  `address` VARCHAR(45) NOT NULL ,
  `phone_num` INT NOT NULL ,
  PRIMARY KEY (`customer_id`) )
ENGINE = MyISAM DEFAULT CHARSET = utf8

CREATE  TABLE IF NOT EXISTS `xxx`.`Orders_TBL` (
  `order_id` INT NOT NULL ,
  `customer_id` INT NOT NULL ,
  `date` DATETIME NOT NULL ,
  `delivery_address` VARCHAR(45) NULL ,
  PRIMARY KEY (`order_id`) ,
  KEY `customer_id` (`customer_id`),
  CONSTRAINT `customer_id_fk`
    FOREIGN KEY (`customer_id` )
    REFERENCES `xxx`.`Customers_TBL`(`customer_id`)
    ON DELETE CASCADE
    ON UPDATE CASCADE )
ENGINE = MyISAM DEFAULT CHARSET = utf8

CREATE  TABLE IF NOT EXISTS `xxx`.`Orders_Detail_TBL` (
  `order_id` INT NOT NULL ,
  `book_id` INT NOT NULL ,
  `discount` DECIMAL(2,2) NULL ,
  `quantity` INT NULL DEFAULT 1 ,
  PRIMARY KEY (`order_id`, `book_id`) ,
  INDEX `fk_Orders_Detail_TBL_Orders_TBL1_idx` (`order_id` ASC) ,
  CONSTRAINT `order_id`
    FOREIGN KEY (`order_id` )
    REFERENCES `xxx`.`Orders_TBL` (`order_id` )
    ON DELETE CASCADE
    ON UPDATE CASCADE)
ENGINE = MyISAM DEFAULT CHARSET = utf8

CREATE  TABLE IF NOT EXISTS `xxx`.`Books_TBL` (
  `book_id` INT NOT NULL ,
  `isbn` VARCHAR(64) NOT NULL ,
  `title` VARCHAR(45) NOT NULL ,
  `author_first_name` VARCHAR(45) NULL ,
  `author_last_name` VARCHAR(45) NULL ,
  `num` INT NOT NULL ,
  `price` DECIMAL(6,2) NOT NULL ,
  INDEX `fk_Books_TBL_Orders_Detail_TBL1_idx` (`book_id` ASC) ,
  PRIMARY KEY (`book_id`) ,
  CONSTRAINT `book_id_fk`
    FOREIGN KEY (`book_id` )
    REFERENCES `xxx`.`Orders_Detail_TBL` (`book_id` )
    ON DELETE CASCADE
    ON UPDATE CASCADE)
ENGINE = MyISAM DEFAULT CHARSET = utf8

2   访问数据库

好吧,终于走到主题了,接下来的内容终于从数据库建模走到了编程,我们来看看怎样用经典的“数据访问对象”(Data Access Object)来访问数据库。DAO只是数据访问模式的一种,根据具体的应用场景会有各种不同的访问模式,关键是要根据情况选择合适的技术方案。

2.1 DAO模式介绍

         “数据访问对象”模式由一系列相关的类共同构成。一个典型的应用通常有三层:展现层、业务逻辑层和数据访问层。数据访问层的数据源是不同的——它们可能是不同厂商的数据库产品,可能是WebService,也可能是XML文件,不仅不同的数据库产品之间存在差别,而且不同数据源的访问方式也不同。DAO模式抽象并对业务逻辑层隔离了这种数据源的相异性,它“知道”如何跟各种不同数据源打交道,且提供的接口使得应用程序不需要了解与之交换信息的数据源究竟是什么样的,所以,总结一下,我们得出这样的结论:DAO模式通过“分离职责”,将数据源接口和数据访问机制分开,使特定API应用于一般接口成为可能(颇有适配器模式的味道)。原则上数据访问对象并不需要知道太多所存储的数据的细节,该细节由它的好搭档——被称为DTO的“数据传输对象”来负责。

2.2 DTO模式介绍

DTO模式的提出是有一定问题背景的,在典型的分布式应用程序中,为了响应单个客户端的请求,应用程序要通过远程调用来访问远端的数据源(可能是数据库,也可能是其他数据源)。如果需要通过多次调用才能满足一个请求,这些调用的响应时间通常会让人难以接受。所有的框架都把远程调用的复杂性对调用者隐藏了,但耗费的时间是无法缩短的,因为这通常包含“定位远程对象——建立连接——发出请求——返回数据”的过程,中间还包括将数据序列化为字节流,加密传输等等。跨网络边界的访问要考虑滞后时间和吞吐量这两个因素,“滞后时间”定义为数据的首字节到达目的地之前经过的时间;“吞吐量”表示单位时间内通过网络发送的字节数。在基于IP路由的网络中,滞后时间往往影响更大,这是网络传输的复杂性所决定的。

这些因素影响了代码的设计,首先,远程调用的粒度不能太细,细粒度意味着多次调用,这无疑会增加滞后时间;其次,长参数列表并不是很好的设计,参数过多容易导致因疏忽搞错参数次序导致出错,在这样的背景下,DTO模式产生了。

DTO被设计为具有一系列getter和setter方法的对象,它是待传输数据的代码容器,同时也是数据库记录的一种抽象。使用时,将其作为单一参数调用远程接口,在本地取得DTO对象后对其进行信息存取,这就减少了滞后时间的影响。

有两种常用的DTO实现方式,第一种针对不同的数据设计具体的类,然后为每个类的属性提供一对getter和setter方法;第二种是使用泛型容器,以“key-value”的形式进行存储。两种方法各有千秋,前者属于强类型,可以实现编译时检查,约束条件检查,但是编写过程枯燥乏味,且对数据很敏感,数据内容和格式的变更往往意味着类的变动;后者属于弱类型,最大的优势是泛型容器总是被不同的编程语言支持,因此使用方便,缺点是按序号或字段访问容易出现细微的错误。

2.3 DAO编写实践

介绍完理论背景,我们来看看如何为我们之前设计的在线书店数据库提供数据访问对象。这里编程语言使用C++,且使用MySQL作为数据源。为了避免重复写相同的代码,这里仅以Book类作为例子介绍DAO的编写,首先先来看看相关的类层次结构。

2.3.1 类层次结构一览

图1.12展示了类的层次结构,IBookDAO作为接口,定义了对Book类的对象进行存取需要的函数。MysqlDBBookDAO和XMLBookDAO继承了该接口,实现了具体的数据访问机制,只不过一个是使用关系型数据库,而另一个则解析XML文件。如果使用了抽象工厂模式,就可以根据实际需要生成这两种DAO,满足不同的数据存取需要。

2.3.2 数据传输对象的实现

Book类作为数据传输对象的职责只有一个,就是对其属性提供一系列的getter()/setter()方法,还可以在setter()方法上加入数据库特有约束检查等等,代码的实现比较简单,这里只提供一个简单的类描述如代码清单2-1所示。

代码清单2-1

/*** Data Transfer Object ***/
class Book {
public:

    Book();
    unsigned int getBookID() const;
    void setBookID(unsigned int bookId);

    string getISBN() const;
    void setISBN(string isbn);

    string getTitle() const;
    void setTitle(string title);

    string getAuthorFirstName() const;
    void setAuthorFirstName(string firstName);

    string getAuthorLastName() const;
    void setAuthorLastName(string lastName);

    unsigned int getNum() const;
    void setNum(unsigned int nm);

    double getPrice() const;
    void setPrice(double price);

private:
    unsigned int bookId;
    string isbn;
    string title;
    string authorFirstName;
    string authorLastName;
    unsigned int num;
    double price;
};


 

2.3.3 数据访问对象的实现

代码清单2-2展示了数据访问对象的接口,它主要提供对Book类的增删查改操作,这里只实现了几个基本的函数,实际应用中,应当根据需求提供不同的查询方法,比如根据各种具体条件进行查询的语句,因此这个类是随着需求不断变化的。

代码清单2-2

class IBookDAO {
public:
    // virtual destructor for class hierarchy
    virtual ~IBookDAO() {}
    virtual bool saveBook(Book& book) = 0;
    virtual bool deleteBook(unsigned int bkId) = 0;
    virtual bool updateBook(Book& book) = 0;
    virtual bool getBook(Book& book, unsigned int bkId) = 0;
    virtual list<Book> getAllBooks(unsigned int order) = 0;
};


 

对MysqlDBBookDAO,我们想设计成这样,首先,数据库相关配置不是直接写在代码中的,而是放入一个XML配置文件,每次DAO启动时,都会读取配置文件并尝试连接数据库;结束时自动释放该连接,这样就可以通过修改配置文件来修改对数据库的访问,而不是每次都去重新编译代码。代码清单2-3展示了这样的一个配置文件。

代码清单2-3

<?xml version="1.0" encoding="ISO-8859-1"?>
<mysqldb>
    <host>localhost</host>
    <username>xxxxxx</username>
    <password>xxxxxx</password>
    <db>xxx</db>
    <port>0</port>
</mysqldb>


下面开始编写MysqlDBBookDAO,代码清单2-4展示了该类的声明。其中四个字符串属性分别用于存储对数据库进行增删查改的SQL语句,通过修改SQL语句就能实现不同的增删查改条件和方式。

代码清单2-4

class MysqlDBBookDAO : public IBookDAO {
public:
    MysqlDBBookDAO();
    MysqlDBBookDAO(const string tbl, const string& configFileName);
    virtual ~MysqlDBBookDAO();
    virtual bool saveBook(Book& book);
    virtual bool deleteBook(unsigned int bkId);
    virtual bool updateBook(Book& book);
    virtual bool updateBook(Book& book, unsigned int bkId);
    virtual bool getBook(Book& book, unsigned int bkId);
    virtual list<Book> getAllBooks(const unsigned int order);
    void setSqlQryStmt(string qryStmt);
    void setSqlQryAllStmt(string qryAllStmt);
    void setSqlAddStmt(string addStmt);
    void setSqlUptStmt(string uptStmt);
    void setSqlDelStmt(string delStmt);

private:
    MYSQL  mysqlDB;
    string sqlQryStmt;
    string sqlQryAllStmt;
    string sqlAddStmt;
    string sqlUptStmt;
    string sqlDelStmt;
const string BOOKS_TBL;
};


 

1)  构造函数和析构函数的编写

DAO的构造函数主要实现三个工作,首先读取配置文件中各项参数,如主机、用户名和密码等配置信息;其次,初始化并建立SQL连接;最后初始化基本的SQL语句;对应的析构函数则负责释放连接。如代码清单2-5所示。

代码清单2-5

using boost::property_tree::ptree;
using boost::property_tree::xml_parser::xml_parser_error;
using std::cerr;
using std::endl;
using std::stringstream;

MysqlDBBookDAO::
MysqlDBBookDAO(){}

MysqlDBBookDAO::
MysqlDBBookDAO(const string tbl, const string& configFileName):
BOOKS_TBL(tbl) {

    // load configuration from xml
    ptree pt;
    try {
        read_xml(configFileName,  pt);
    }
    catch (xml_parser_error& err) {
        cerr << "Error, can't open config file "
             << configFileName << endl;
        exit(EXIT_FAILURE);
    }
    const string host(pt.get<string>("mysqldb.host"));
    const string user(pt.get<string>("mysqldb.username"));
    const string pwd(pt.get<string>("mysqldb.password"));
    const string db(pt.get<string>("mysqldb.db"));
    const int port(pt.get("mysqldb.port", 0));

    mysql_init(&mysqlDB);

    if (!mysql_real_connect(
        &mysqlDB,
        host.c_str(),
        user.c_str(),
        pwd.c_str(),
        db.c_str(),
        port,
        NULL,
        0))
    {
        cerr << "exception: mysql connection failed" << endl;
        if (mysql_errno(&mysqlDB)) {
            cerr << mysql_error(&mysqlDB) << endl;
        }
        exit(EXIT_FAILURE);
    }

    stringstream sqlStrm;
    sqlStrm << "INSERT INTO `%s`(`book_id`, `isbn`, `title`, ";
    sqlStrm << "`author_first_name`, `author_last_name`, `num`, `price`) ";
    sqlStrm << "VALUES(%u, '%s', '%s', '%s', '%s', %u, %f) ";
    setSqlAddStmt(sqlStrm.str());

    sqlStrm.str("");
    sqlStrm << "DELETE FROM `%s` WHERE `book_id` = %u ";
    setSqlDelStmt(sqlStrm.str());

    sqlStrm.str("");
    sqlStrm << "UPDATE `%s` SET `isbn` = '%s', ";
    sqlStrm << "`title` = '%s', `author_first_name` = '%s', ";
    sqlStrm << "`author_last_name` = '%s', `num` = %u, ";
    sqlStrm << "`price` = %f WHERE `book_id` = %u ";
    setSqlUptStmt(sqlStrm.str());

    sqlStrm.str("");
    sqlStrm << "SELECT `book_id`, `isbn`, `title`, `author_first_name`, `author_last_name`, ";
    sqlStrm << "`num`, `price` FROM `%s` ";
    setSqlQryAllStmt(sqlStrm.str());

    sqlStrm << "WHERE `book_id` = %u ";
    setSqlQryStmt(sqlStrm.str());
}
MysqlDBBookDAO::
~MysqlDBBookDAO() {
    mysql_close(&mysqlDB);
}


1)  具体的数据访问函数

代码清单2-6展示了具体的数据访问函数的实现。

代码清单2-6

bool MysqlDBBookDAO::
saveBook(Book& book) {
    bool isOK = true;

    char * cStr = new char[1024];

    sprintf(cStr, sqlAddStmt.c_str(),
            BOOKS_TBL.c_str(),
            book.getBookID(),
            book.getISBN().c_str(),
            book.getTitle().c_str(),
            book.getAuthorFirstName().c_str(),
            book.getAuthorLastName().c_str(),
            book.getNum(),
            book.getPrice());

    int res = mysql_query(&mysqlDB, cStr);

    if (res) {
        cerr << mysql_error(&mysqlDB) << endl;
        isOK = false;
    }

    delete cStr;
    return isOK;
}

bool MysqlDBBookDAO::
deleteBook(unsigned int bkId) {
    bool isOK = false;

    char * cStr = new char[1024];

    sprintf(cStr, sqlDelStmt.c_str(),
            BOOKS_TBL.c_str(), bkId);

    int res = mysql_query(&mysqlDB, cStr);

    if (res) {
        cerr << mysql_error(&mysqlDB) << endl;
        isOK = false;
    }

    delete cStr;
    return isOK;
}

bool MysqlDBBookDAO::
updateBook(Book& book) {
    return updateBook(book, book.getBookID());
}

bool MysqlDBBookDAO::
updateBook(Book& book, unsigned int bkId) {
    bool isOK = false;

    char * cStr = new char[1024];

    sprintf(cStr, sqlUptStmt.c_str(),
            BOOKS_TBL.c_str(),
            book.getISBN().c_str(),
            book.getTitle().c_str(),
            book.getAuthorFirstName().c_str(),
            book.getAuthorLastName().c_str(),
            book.getNum(), book.getPrice(),
            bkId);

    int res = mysql_query(&mysqlDB, cStr);

    if (res) {
        cerr << mysql_error(&mysqlDB) << endl;
        isOK = false;
    }

    delete cStr;
    return isOK;
}

bool MysqlDBBookDAO::
getBook(Book& book, unsigned int bkId) {
    bool isOK = true;
    char * cStr = new char[1024];
    MYSQL_RES *resPtr = NULL;
    MYSQL_ROW sqlRow;

    sprintf(cStr, sqlQryStmt.c_str(),
            BOOKS_TBL.c_str(), bkId);

    int res = mysql_query(&mysqlDB, cStr);

    if (res) {
        cerr << mysql_error(&mysqlDB) << endl;
        isOK = false;
    } else {
        resPtr = mysql_use_result(&mysqlDB);
        if (resPtr) {
            while ((sqlRow = mysql_fetch_row(resPtr))) {
                book.setBookID(atoi(sqlRow[0]));
                book.setISBN(sqlRow[1]);
                book.setTitle(sqlRow[2]);
                book.setAuthorFirstName(sqlRow[3]);
                book.setAuthorLastName(sqlRow[4]);
                book.setNum(atoi(sqlRow[5]));
                book.setPrice(atof(sqlRow[6]));
            }
            if (mysql_errno(&mysqlDB)) {
                cerr << "Retrieve error: " << mysql_error(&mysqlDB) << endl;
            }
            mysql_free_result(resPtr);
        }
    }

    delete cStr;
    return isOK;
}

list<Book> MysqlDBBookDAO::
getAllBooks(const unsigned int order) {
    MYSQL_RES *resPtr = NULL;
    MYSQL_ROW sqlRow;
    char * cStr = new char[1024];

    string qryAllStmt = sqlQryAllStmt;
    switch(order) {
        case BOOKID:
            qryAllStmt += "ORDER BY `book_id` ASC";
            break;
        case PRICE:
            qryAllStmt += "ORDER BY `price` ASC";
            break;
        case DEFAULT:
        default:
            break;
    }

    sprintf(cStr, qryAllStmt.c_str(),
            BOOKS_TBL.c_str());

    int res = mysql_query(&mysqlDB, cStr);
    list<Book> bookList;
    if (res) {
        cerr << mysql_error(&mysqlDB) << endl;
    } else {
        // fetch a row one at a time
        resPtr = mysql_use_result(&mysqlDB);
        if (resPtr) {
            while ((sqlRow = mysql_fetch_row(resPtr))) {
                Book book;
                book.setBookID(atoi(sqlRow[0]));
                book.setISBN(sqlRow[1]);
                book.setTitle(sqlRow[2]);
                book.setAuthorFirstName(sqlRow[3]);
                book.setAuthorLastName(sqlRow[4]);
                book.setNum(atoi(sqlRow[5]));
                book.setPrice(atof(sqlRow[6]));
                bookList.push_back(book);
            }
            if (mysql_errno(&mysqlDB)) {
                cerr << "Retrieve error: " << mysql_error(&mysqlDB) << endl;
            }
            mysql_free_result(resPtr);
        }
    }

    delete cStr;
    return bookList;
}

void MysqlDBBookDAO::
setSqlQryStmt(string qryStmt) {
    if (!qryStmt.empty()) {
        sqlQryStmt = qryStmt;
    }
}

void MysqlDBBookDAO::
setSqlQryAllStmt(string qryAllStmt) {
    if (!qryAllStmt.empty())  {
        sqlQryAllStmt = qryAllStmt;
    }
}

void MysqlDBBookDAO::
setSqlAddStmt(string addStmt) {
    if (!addStmt.empty()) {
        sqlAddStmt = addStmt;
    }
}

void MysqlDBBookDAO::
setSqlUptStmt(string uptStmt) {
    if (!uptStmt.empty()) {
        sqlUptStmt = uptStmt;
    }
}

void MysqlDBBookDAO::
setSqlDelStmt(string delStmt) {
    if (!delStmt.empty()) {
        sqlDelStmt = delStmt;
    }
}


参考文献和网站

1.       Fundamentals Of Rational Database Design

1995年Paul Litwin为Access数据库写的一篇文献,虽然时间很久了但仍不失为一篇很好的入门教程,强烈推荐。

2.       系统的数据建模教程

http://www.databaseanswers.org/tutorials.htm

国外一位DBA大牛的网站,有很多针对初学者的数据建模教程和现实世界的数据模型例子。

DAO设计模式资料

http://www.ibm.com/developerworks/cn/data/library/techarticles/dm-0502chenjunwei/

IBM DevelopWorks一篇介绍各种数据库访问技术应用场景的文章,介绍各种更复杂的工业级解决方案。

3.       DAO最佳实践

http://best-practice-software-engineering.ifs.tuwien.ac.at/patterns/dao.html

该网站介绍了大量的设计模式最佳实践,还有与之配套的使用Java语言编写的例子可供参考,是学习设计模式的好网站。

4.       用Java语言编写的复杂DAO

http://www.javaworld.com/javaworld/jw-03-2002/jw-0301-dao.html?page=1

5.       百度百科对DTO的解释

http://baike.baidu.com/view/160599.htm



 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值