MSN: luxuabc@hotmail.com

发布新日志

  • ANT十五大最佳实践(转)

    2009-03-02 16:52:24

    ANT十五大最佳实践

    作者:Eric M. Burke, coauthor of Java Extreme Programming Cookbook

    原文:http://www.onjava.com/pub/a/onjava/2003/12/17/ant_bestpractices.html

    译者:徐彤MSN:xt121@hotmail.com

     

    在 Ant出现之前,构建和部署Java应用需要使用包括特定平台的脚本、Make文件、各种版本的IDE甚至手工操作的“大杂烩”。现在,几乎所有的开源 Java项目都在使用Ant,大多数公司的内部项目也在使用Ant。Ant在这些项目中的广泛使用自然导致了读者对一整套Ant最佳实践的迫切需求。

    本文总结了我喜爱的Ant技巧或最佳实践,多数是从我亲身经历的项目错误或我听说的其他人经历的 “恐怖”故事中得到灵感的。比如,有人告诉我有个项目把XDoclet 生成的代码放入带有锁定文件功能的版本控制工具中。当开发者修改源代码时,他必须记住手工检出(Check out)并锁定所有将要重新生成的文件。然后,手工运行代码生成器,只到这时他才能够让Ant编译代码,这一方法还存在如下一些问题:

    • 生成的代码无法存储在版本控制系统中。
    • Ant(本案例中是Xdoclet)应该自动确定下一次构建涉及的源文件,而不应由程序员手工确定。
    • Ant的构建文件应该定义好正确的任务依赖关系,这样程序员就不必为了完成构建而不得不按照特定顺序调用任务。

    当 我开始一个新项目时,我首先编写Ant构建文件。Ant文件明确地定义构建的过程,并被团队中的每个程序员使用。本文所列的技巧基于这样的假定:Ant构 建文件是一个必须仔细编写的重要文件,它应在版本控制系统中得到维护,并被定期进行重构。下面是我的十五大Ant最佳实践。

    1. 采用一致的编码规范

    Ant用户有的喜欢有的痛恨其构建文件的XML语法。与其跳进这一令人迷惑的争论中,不如让我们先看一些能保持XML构建文件简洁的方法。

    首 先也是最重要的,花费时间格式化你的XML让它看上去很清晰。不论XML是否美观,Ant都可以工作。但是丑陋的XML很难令人读懂。倘若你在任务之间留 出空行,有规则的缩进,每行文字不超过90列左右,那么XML令人惊讶地易读。再加上使用能够高亮XML语法的优秀编辑器或IDE工具,你就不会有阅读的 麻烦。

    同样,精选含意明确、容易读懂的词汇来命名任务和属性。比如,dir.reports就比rpts特定的编码规范并不重要,只要拿出一套规范并坚持使用就行。

    2. 将build.xml放在项目根目录中

    Ant构建文件build.xml可以放在任何位置,但是放在项目顶级目录中可以保持项目简洁。这是最常用的规范,开发者能够在顶级目录中找到预期的build.xml。把构建文件放在根目录中,也能够使人容易了解项目目录树中不同目录之间的逻辑关系。以下是一个典型的项目目录层次:

    [root dir]
      | build.xml 
      +--src 
      +--lib (包含第三方 JAR包) 
      +--build (由 build任务生成) 
      +--dist (由 build任务生成)

    build.xml在顶级目录时,假设你处于项目某个子目录中,只要输入:ant -find compile 命令,不需要改变工作目录就能够以命令行方式编译代码。参数-find告诉Ant寻找存在于上级目录中的build.xml并执行。

    3. 使用单一的构建文件

    有人喜欢将一个大项目分解成几个小的构建文件,每个构建文件分担整个构建过程的一小部分工作。这确实是看法不同的问题,但是应该认识到,将构建文件分割会 增加对整体构建过程的理解难度。要注意在单一构建文件能够清楚表现构建层次的情况下不要过工程化(over-engineer)。

    即使你把项目划分为多个构建文件,也应使程序员能够在项目根目录下找到核心build.xml。尽管该文件只是将实际构建工作委派给下级构建文件,也应保证该文件可用。

    4. 提供良好的帮助说明

    应尽量使构建文件自文档化。增加任务描述是最简单的方法。当你输入ant -projecthelp时,你就可以看到带有描述的任务清单。比如,你可以这样定义任务:

    <target name="compile"  
       description="Compiles code, output goes to the build dir.">

    最简单的规则是把所有你想让程序员通过命令行就可以调用的任务都加上描述。对于一般用来执行中间处理过程的内部任务,比如生成代码或建立输出目录等,就无法使用描述属性。

    这时,可以通过在构建文件中加入XML注释来处理。或者专门定义一个help任务,当程序员输入ant help时来显示详细的使用说明。

    <target name="help" description="Display detailed usage information">
      <echo>Detailed help...</echo></target>

    5. 提供清除任务

    每个构建文件都应包含一个清除任务,用来删除所有生成的文件和目录,使系统回到构建文件执行前的初始状态。执行清空任务后还存在的文件都应处在版本控制系统的管理之下。比如:

    <target name="clean"
        description="Destroys all generated files and dirs.">
      <delete dir="${dir.build}"/>
      <delete dir="${dir.dist}"/>
    </target>

    除非是在产生整个系统版本的特殊任务中,否则不要自动调用clean任务。当程序员仅仅执行编译任务或其他任务时,他们不需要构建文件事先执行既令人讨厌又没有必要的清空任务。要相信程序员能够确定何时需要清空所有文件。

    6. 使用ANT管理任务从属关系

    假 设你的应用由Swing GUI组件、Web界面、EJB层和公共应用代码组成。在大型系统中,你需要清晰地定义每个Java包属于系统的哪一层。否则任何一点修改都要被迫重新编 译成百上千个文件。糟糕的任务从属关系管理会导致过度复杂而脆弱的系统。改变GUI面板的设计不应造成Servlet和EJB的重编译。

    当系统变得庞大后,稍不注意就可能将依赖于客户端的代码引入到服务端。这是因为典型的IDE项目文件编译任何文件都使用单一的classpath。而Ant能让你更有效地控制构建活动。

    设计你的Ant构建文件编译大型项目的步骤:首先,编译公共应用代码,将编译结果打成JAR包文件。然后,编译上一层的项目代码,编译时依靠第一步产生的JAR文件。不断重复这一过程,直到最高层的代码编译完成。

    分步构建强化了任务从属关系管理。如果你工作在底层Java框架上,偶然引用到高层的GUI模板组件,这时代码不需要编译。这是由于构建文件在编译底层框架时在源路径中没有包含高层GUI面板组件的代码。

    7. 定义并重用文件路径

    如果文件路径在一个地方一次性集中定义,并在整个构建文件中得到重用,那么构建文件更易于理解。以下是这样做的一个例子:

    <project name="sample" default="compile" basedir=".">
      <path id="classpath.common">
        <pathelement location="${jdom.jar.withpath}"/>
        ...etc  </path>
      <path id="classpath.client">
        <pathelement location="${guistuff.jar.withpath}"/>
        <pathelement location="${another.jar.withpath}"/>
        <!-- reuse the common classpath -->
        <path refid="classpath.common"/>
      </path>
      <target name="compile.common" depends="prepare">
        <javac destdir="${dir.build}" srcdir="${dir.src}">
              <classpath refid="classpath.common"/>
              <include name="com/oreilly/common/**"/>
        </javac>
      </target>
    </project>

    当项目不断增长构建日益复杂时,这一技术越发体现出其价值。你可能需要为编译不同层次的应用定义各自的文件路径,比如运行单元测试的、运行应用程序的、运 行Xdoclet的、生成JavaDocs的等等不同路径。这种组件化路径定义的方法比为每个任务单独定义路径要优越得多。否则,很容易丢失任务从属关系 的轨迹。

    8. 定义恰当的任务从属关系

    假设dist任务从属于jar任务,那么哪个任务从属于compile任务哪个任务从属于prepare任务呢?Ant构建文件最终定义了任务的从属关系图,它必须被仔细地定义和维护。

    应该定期检查任务的从属关系以保证构建工作得到正确执行。大的构建文件随着时间推移趋向于增加更多的任务,所以到最后可能由于不必要的从属关系导致构建工作非常困难。比如,你可能发现在程序员只需编译一些没有使用EJB的GUI代码时又重新生成了EJB代码。

    以“优化”的名义忽略任务的从属关系是另一种常见的错误。这种错误迫使程序员为了得到恰当的结果必须记住并按照特定的顺序调用一串任务。更好的做法是:提 供描述清晰的公共任务,这些任务包含正确的任务从属关系;另外提供一套“专家”任务让你能够手工执行个别的构建步骤,这些任务不提供完整的构建过程,但是 让那些专家用户在快速而恼人的编码期间能够跳过某些步骤。

    9.使用属性

    任何需要配置或可能发生变化的信息都应作为Ant属性定义下来。对于在构建文件中多次出现的值也同样处理。属性既可以在构建文件头部定义,也可以为了更好的灵活性而在单独的属性文件中定义。以下是在构建文件中定义属性的样式:

    <project name="sample" default="compile" basedir=".">
      <property name="dir.build" value="build"/>
      <property name="dir.src" value="src"/>
      <property name="jdom.home" value="../java-tools/jdom-b8"/>
      <property name="jdom.jar" value="jdom.jar"/>
      <property name="jdom.jar.withpath"
                        value="${jdom.home}/build/${jdom.jar}"/>
        etc...
    </project>

    或者你可以使用属性文件:

    <project name="sample" default="compile" basedir=".">
      <property file="sample.properties"/>
       etc...
    </project>

    在属性文件 sample.properties中:

    dir.build=build
    dir.src=src
    jdom.home=../java-tools/jdom-b8
    jdom.jar=jdom.jarjdom.jar.withpath=${jdom.home}/build/${jdom.jar}

    用一个独立的文件定义属性是有好处的,它可以清晰地定义构建中的可配置部分。另外,在开发者工作在不同操作系统的情况下,你可以在不同的平台上提供该文件的不同版本。

    10. 保持构建过程独立

    为了最大限度的扩展性,不要应用外部路径和库文件。最重要的是不要依赖于程序员的CLASSPATH设置。取而代之的是,在构建文件中使用相对路径并定义自己的路径。如果你引用了绝对路径如C:\java\tools,其他开发者未必使用与你相同的目录结构,所以就无法使用你的构建文件。

    如果你部署开放源码项目,应该提供包含编译代码所需的所有JAR文件的发行版本。当然,这是在遵守许可协议的基础上。对于内部项目,相关的JAR文件都应在版本控制系统的管理中,并捡出(check out)到大家都知道的位置。

    当你必须引用外部路径时,应将路径定义为属性。使程序员能够用适合他们自己的机器环境的参数重载这些属性。你也可以使用以下语法引用环境变量:

    <property environment="env"/>
    <property name="dir.jboss" value="${env.JBOSS_HOME}"/>

    11. 使用版本控制系统

    构建文件是一个重要的制品,应该像代码一样进行版本控制。当你标记你的代码时,也应用同样的标签标记构建文件。这样当你需要回溯到旧版本并进行构建时,能够使用相应版本的构建文件。

    除构建文件之外,你还应在版本控制中维护第三方JAR文件。同样,这使你能够重新构建旧版本的软件。这也能够更容易保证所有开发者拥有一致的JAR文件,因为他们都是同构建文件一起从版本控制系统中捡出的。

    通常应避免在版本控制系统中存放构建成果。倘若你的源代码很好地得到了版本控制,那么通过构建过程你能够重新生成任何版本的产品。

    12. 把Ant作为“最小公分母”

    假设你的开发团队使用IDE工具,当程序员通过点击图标就能够构建整个应用时为什么还要为Ant而烦恼呢?

    IDE的问题是一个关于团队一致性和重现性的问题。几乎所有的IDE设计初衷都是为了提高程序员的个人生产率,而不是开发团队的持续构建。典型的IDE要 求每个程序员定义自己的项目文件。程序员可能拥有不同的目录结构,可能使用不同版本的库文件,还可能工作在不同的平台上。这将导致出现这种情况:在Bob 那里运行良好的代码,到Sally那里就无法运行。

    不管你的开发团队使用何种IDE,一定要建立所有程序员都能够使用的Ant构建文件。要建立一个程序员在将新代码提交版本控制系统前必须执行Ant构建文 件的规则。这将确保代码是经过同一个Ant构建文件构建的。当出现问题时,要使用项目标准的Ant构建文件,而不是通过某个IDE来执行一个干净的构建。

    程序员可以自由选择任何他们习惯使用的IDE工具或编辑器。但是Ant应作为公共基线以保证代码永远是可构建的。

    13. 使用zipfileset属性

    人们经常使用Ant产生WAR、JAR、ZIP和 EAR文件。这些文件通常都要求有一个特定的内部目录结构,但其往往与你的源代码和编译环境的目录结构不匹配。

    一个最常用的方法是写一个Ant任务,按照期望的目录结构把一大堆文件拷贝到临时目录中,然后生成压缩文件。这不是最有效的方法。使用zipfileset属性是更好的解决方案。它让你从任何位置选择文件,然后把它们按照不同目录结构放进压缩文件中。以下是一个例子:

    <ear earfile="${dir.dist.server}/payroll.ear"
        appxml="${dir.resources}/application.xml">
      <fileset dir="${dir.build}" includes="commonServer.jar"/>
      <fileset dir="${dir.build}">
        <include name="payroll-ejb.jar"/>
      </fileset>
      <zipfileset dir="${dir.build}" prefix="lib">
        <include name="hr.jar"/>
        <include name="billing.jar"/>
      </zipfileset>
      <fileset dir=".">
        <include name="lib/jdom.jar"/>
        <include name="lib/log4j.jar"/>
        <include name="lib/ojdbc14.jar"/>
      </fileset>
      <zipfileset dir="${dir.generated.src}" prefix="META-INF">
        <include name="jboss-app.xml"/>
      </zipfileset>
    </ear>

    在这个例子中,所有JAR文件都放在EAR文件包的lib目录中。hr.jar和billing.jar是从构建目录拷贝过来的。因此我们使用zipfileset属性把它们移动到EAR文件包内部的lib目录。prefix属性指定了其在EAR文件中的目标路径。

    14. 测试Clean任务

    假设你的构建文件中有clean和compile的任务,执行以下的测试。第一步,执行ant clean;第二步,执行ant compile;第三步,再执行ant compile。第三步应该不作任何事情。如果文件再次被编译,说明你的构建文件有问题。

    构建文件应该只在与输出文件相关联的输入文件发生变化时执行任务。一个构建文件在不必执行诸如编译、拷贝或其他工作任务的时候执行这些任务是低效的。当项目规模增长时,即使是小的低效工作也会成为大的问题。

    15. 避免特定平台的Ant封装

    不管什么原因,有人喜欢用简单的、名称叫做compile之类的批文件或脚本装载他们的产品。当你去看脚本的内容你会发现以下内容:

    ant compile

    其实开发人员都很熟悉Ant,并且完全能够自己键入ant compile。请不要仅仅为了调用Ant而使用特定平台的脚本。这只会使其他人在首次使用你的脚本时增加学习和理解的烦扰。除此之外,你不可能提供适用于每个操作系统的脚本,这是真正烦扰其他用户的地方。

    总结

    太多的公司依靠手工方法和特别程序来编译代码和生成软件发布版本。那些不使用Ant或类似工具定义构建过程的开发团队,花费了太多的时间来捕捉代码编译过程中出现的问题:在某些开发者那里编译成功的代码,到另一些开发者那里却失败了。

    生成并维护构建脚本不是一项富有魅力的工作,但却是一项必需的工作。一个好的Ant构建文件将使你能够集中到更喜欢的工作——写代码中去!

  • 【转】数据库设计三大范式应用实例剖析

    2008-03-14 15:57:04

    引言

      数据库的设计范式是数据库设计所需要满足的规范,满足这些规范的数据库是简洁的、结构明晰的,同时,不会发生插入(insert)、删除(delete)和更新(update)操作异常。反之则是乱七八糟,不仅给数据库的编程人员制造麻烦,而且面目可憎,可能存储了大量不需要的冗余信息。

      设计范式是不是很难懂呢?非也,大学教材上给我们一堆数学公式我们当然看不懂,也记不住。所以我们很多人就根本不按照范式来设计数据库。

      实质上,设计范式用很形象、很简洁的话语就能说清楚,道明白。本文将对范式进行通俗地说明,并以笔者曾经设计的一个简单论坛的数据库为例来讲解怎样将这些范式应用于实际工程。

      范式说明

      第一范式(1NF):数据库表中的字段都是单一属性的,不可再分。这个单一属性由基本类型构成,包括整型、实数、字符型、逻辑型、日期型等。

      例如,如下的数据库表是符合第一范式的:

    字段1 字段2 字段3 字段4
           

      而这样的数据库表是不符合第一范式的:

    字段1 字段2 字段3 字段4
        字段3.1 字段3.2  


      很显然,在当前的任何关系数据库管理系统(DBMS)中,傻瓜也不可能做出不符合第一范式的数据库,因为这些DBMS不允许你把数据库表的一列再分成二列或多列。因此,你想在现有的DBMS中设计出不符合第一范式的数据库都是不可能的。

      第二范式(2NF):数据库表中不存在非关键字段对任一候选关键字段的部分函数依赖(部分函数依赖指的是存在组合关键字中的某些字段决定非关键字段的情况),也即所有非关键字段都完全依赖于任意一组候选关键字。

     
     


      假定选课关系表为SelectCourse(学号, 姓名, 年龄, 课程名称, 成绩, 学分),关键字为组合关键字(学号, 课程名称),因为存在如下决定关系:

      (学号, 课程名称) → (姓名, 年龄, 成绩, 学分)

      这个数据库表不满足第二范式,因为存在如下决定关系:

      (课程名称) → (学分)

      (学号) → (姓名, 年龄)

      即存在组合关键字中的字段决定非关键字的情况。

      由于不符合2NF,这个选课关系表会存在如下问题:

      (1) 数据冗余:

      同一门课程由n个学生选修,"学分"就重复n-1次;同一个学生选修了m门课程,姓名和年龄就重复了m-1次。

      (2) 更新异常:

      若调整了某门课程的学分,数据表中所有行的"学分"值都要更新,否则会出现同一门课程学分不同的情况。

      (3) 插入异常:

      假设要开设一门新的课程,暂时还没有人选修。这样,由于还没有"学号"关键字,课程名称和学分也无法记录入数据库。

      (4) 删除异常:

      假设一批学生已经完成课程的选修,这些选修记录就应该从数据库表中删除。但是,与此同时,课程名称和学分信息也被删除了。很显然,这也会导致插入异常。

      把选课关系表SelectCourse改为如下三个表:

      学生:Student(学号, 姓名, 年龄);

      课程:Course(课程名称, 学分);

      选课关系:SelectCourse(学号, 课程名称, 成绩)。

      这样的数据库表是符合第二范式的,消除了数据冗余、更新异常、插入异常和删除异常。

      另外,所有单关键字的数据库表都符合第二范式,因为不可能存在组合关键字。

      第三范式(3NF):在第二范式的基础上,数据表中如果不存在非关键字段对任一候选关键字段的传递函数依赖则符合第三范式。所谓传递函数依赖,指的是如果存在"A → B → C"的决定关系,则C传递函数依赖于A。因此,满足第三范式的数据库表应该不存在如下依赖关系:

      关键字段 → 非关键字段x → 非关键字段y

      假定学生关系表为Student(学号, 姓名, 年龄, 所在学院, 学院地点, 学院电话),关键字为单一关键字"学号",因为存在如下决定关系:

      (学号) → (姓名, 年龄, 所在学院, 学院地点, 学院电话)

      这个数据库是符合2NF的,但是不符合3NF,因为存在如下决定关系:

      (学号) → (所在学院) → (学院地点, 学院电话)

      即存在非关键字段"学院地点"、"学院电话"对关键字段"学号"的传递函数依赖。

      它也会存在数据冗余、更新异常、插入异常和删除异常的情况,读者可自行分析得知。

      把学生关系表分为如下两个表:

      学生:(学号, 姓名, 年龄, 所在学院);

      学院:(学院, 地点, 电话)。

      这样的数据库表是符合第三范式的,消除了数据冗余、更新异常、插入异常和删除异常。

      鲍依斯-科得范式(BCNF):在第三范式的基础上,数据库表中如果不存在任何字段对任一候选关键字段的传递函数依赖则符合第三范式。

     假设仓库管理关系表为StorehouseManage(仓库ID, 存储物品ID, 管理员ID, 数量),且有一个管理员只在一个仓库工作;一个仓库可以存储多种物品。这个数据库表中存在如下决定关系:

      (仓库ID, 存储物品ID) →(管理员ID, 数量)

      (管理员ID, 存储物品ID) → (仓库ID, 数量)

      所以,(仓库ID, 存储物品ID)和(管理员ID, 存储物品ID)都是StorehouseManage的候选关键字,表中的唯一非关键字段为数量,它是符合第三范式的。但是,由于存在如下决定关系:

      (仓库ID) → (管理员ID)

      (管理员ID) → (仓库ID)

      即存在关键字段决定关键字段的情况,所以其不符合BCNF范式。它会出现如下异常情况:

      (1) 删除异常:

      当仓库被清空后,所有"存储物品ID"和"数量"信息被删除的同时,"仓库ID"和"管理员ID"信息也被删除了。

      (2) 插入异常:

      当仓库没有存储任何物品时,无法给仓库分配管理员。

      (3) 更新异常:

      如果仓库换了管理员,则表中所有行的管理员ID都要修改。

      把仓库管理关系表分解为二个关系表:

      仓库管理:StorehouseManage(仓库ID, 管理员ID);

      仓库:Storehouse(仓库ID, 存储物品ID, 数量)。

      这样的数据库表是符合BCNF范式的,消除了删除异常、插入异常和更新异常。

     

    范式应用

      我们来逐步搞定一个论坛的数据库,有如下信息:

      (1) 用户:用户名,email,主页,电话,联系地址

      (2) 帖子:发帖标题,发帖内容,回复标题,回复内容

      第一次我们将数据库设计为仅仅存在表:
      

    用户名 email 主页 电话 联系地址 发帖标题 发帖内容 回复标题 回复内容

      这个数据库表符合第一范式,但是没有任何一组候选关键字能决定数据库表的整行,唯一的关键字段用户名也不能完全决定整个元组。我们需要增加"发帖ID"、"回复ID"字段,即将表修改为:

    用户名 email 主页 电话 联系地址 发帖ID 发帖标题 发帖内容 回复ID 回复标题 回复内容

      这样数据表中的关键字(用户名,发帖ID,回复ID)能决定整行:

      (用户名,发帖ID,回复ID) → (email,主页,电话,联系地址,发帖标题,发帖内容,回复标题,回复内容)

      但是,这样的设计不符合第二范式,因为存在如下决定关系:

      (用户名) → (email,主页,电话,联系地址)

      (发帖ID) → (发帖标题,发帖内容)

      (回复ID) → (回复标题,回复内容)

      即非关键字段部分函数依赖于候选关键字段,很明显,这个设计会导致大量的数据冗余和操作异常。
     
     


      我们将数据库表分解为(带下划线的为关键字):

      (1) 用户信息:用户名,email,主页,电话,联系地址

      (2) 帖子信息:发帖ID,标题,内容

      (3) 回复信息:回复ID,标题,内容

      (4) 发贴:用户名,发帖ID

      (5) 回复:发帖ID,回复ID

      这样的设计是满足第1、2、3范式和BCNF范式要求的,但是这样的设计是不是最好的呢?

      不一定。

      观察可知,第4项"发帖"中的"用户名"和"发帖ID"之间是1:N的关系,因此我们可以把"发帖"合并到第2项的"帖子信息"中;第5项"回复"中的"发帖ID"和"回复ID"之间也是1:N的关系,因此我们可以把"回复"合并到第3项的"回复信息"中。这样可以一定量地减少数据冗余,新的设计为:

      (1) 用户信息:用户名,email,主页,电话,联系地址

      (2) 帖子信息:用户名,发帖ID,标题,内容

      (3) 回复信息:发帖ID,回复ID,标题,内容

      数据库表1显然满足所有范式的要求;

      数据库表2中存在非关键字段"标题"、"内容"对关键字段"发帖ID"的部分函数依赖,即不满足第二范式的要求,但是这一设计并不会导致数据冗余和操作异常;

      数据库表3中也存在非关键字段"标题"、"内容"对关键字段"回复ID"的部分函数依赖,也不满足第二范式的要求,但是与数据库表2相似,这一设计也不会导致数据冗余和操作异常。

      由此可以看出,并不一定要强行满足范式的要求,对于1:N关系,当1的一边合并到N的那边后,N的那边就不再满足第二范式了,但是这种设计反而比较好!

      对于M:N的关系,不能将M一边或N一边合并到另一边去,这样会导致不符合范式要求,同时导致操作异常和数据冗余。
    对于1:1的关系,我们可以将左边的1或者右边的1合并到另一边去,设计导致不符合范式要求,但是并不会导致操作异常和数据冗余。

      结论

      满足范式要求的数据库设计是结构清晰的,同时可避免数据冗余和操作异常。这并意味着不符合范式要求的设计一定是错误的,在数据库表中存在1:1或1:N关系这种较特殊的情况下,合并导致的不符合范式要求反而是合理的。

      在我们设计数据库的时候,一定要时刻考虑范式的要求。

    转自CSDN新闻频道

Open Toolbar