Skip to content

18. 三层架构中的 MVC Web 应用程序——示例 4,Postgres

18.1. Postgres 数据库

在此版本中,我们将人员列表存储在 Postgres 8.x 数据库表中 [http://www.postgres.org]。下文中的屏幕截图均来自 EMS PostgreSQL Manager Lite 客户端 [http://www.sqlmanager.net/fr/products/postgresql/manager],这是一个用于 Postgres 数据库管理系统 (DBMS) 的免费管理客户端。

该数据库命名为 [dbpersonnes]。其中包含一个名为 [PERSONNES] 的表:

Image

[PERSONNES] 表将存储由 Web 应用程序管理的人员列表。该表是通过以下 SQL 语句创建的:

CREATE TABLE "public"."PERSONNES" (
  "ID" INTEGER NOT NULL, 
  "VERSION" INTEGER NOT NULL, 
  "NOM" VARCHAR(30) NOT NULL, 
  "PRENOM" VARCHAR(30) NOT NULL, 
  "DATENAISSANCE" DATE NOT NULL, 
  "MARIE" BOOLEAN NOT NULL, 
  "NBENFANTS" INTEGER NOT NULL, 
  CONSTRAINT "PERSONNES_pkey" PRIMARY KEY("ID"), 
  CONSTRAINT "PERSONNES_chk_NBENFANTS" CHECK ("NBENFANTS" >= 0), 
  CONSTRAINT "PERSONNES_chk_NOM" CHECK (("NOM")::text <> ''::text), 
  CONSTRAINT "PERSONNES_chk_PRENOM" CHECK (("PRENOM")::text <> ''::text)
) WITH OIDS;

我们不会过多讨论这个表,它与前面讨论过的 Firebird [PERSONNES] 表类似。但请注意,列名和表名都用引号括起来。此外,这些名称区分大小写。Postgres 8.x 中的这种行为可能是可配置的。我尚未对此进行进一步研究。

[PERSONNES] 表可能包含以下内容:

Image

除了 [PERSONNES] 表外,[dbpersonnes] 数据库中还有一个名为 [SEQ_ID] 的序列对象。该生成器会生成连续的整数,我们将用这些整数来填充 [PERSONNES] 类的 [ID] 主键。让我们通过一个示例来说明其工作原理:

我们可以看到 [SEQ_ID] 序列的 [下一个值] 已发生变化(双击该序列并按 F5 刷新):

 

SQL 语句

SELECT nextval('"SEQ_ID"')

因此,该 SQL 语句允许我们检索 [SEQ_ID] 序列中的下一个值。我们将在 [people-postgres.xml] 文件中使用它,该文件包含在数据库管理系统(DBMS)上执行的 SQL 语句。

18.2. 用于 [dao] 和 [service] 层的 Eclipse 项目

为了使用 Postgres 8.x 数据库开发应用程序的 [dao] 和 [service] 层,我们将使用以下 Eclipse 项目 [spring-mvc-39]:

Image

该项目是一个简单的 Java 项目,而非 Tomcat Web 项目。


[src] 文件夹


该文件夹包含 [dao] 和 [service] 层的源代码:

Image

所有文件名中包含 [postgres] 的文件,与 Firebird 版本相比,可能已被修改,也可能未被修改。下面我们将描述已修改的文件。


[database] 文件夹


该文件夹包含用于为用户创建 Postgres 数据库的脚本:

Image

-- EMS PostgreSQL Manager Lite 3.1.0.1
-- ---------------------------------------
-- Host : localhost
-- Database : dbpersonnes



--
-- Definition for function plpgsql_call_handler (OID = 17230) : 
--
...
--
-- Structure for table PERSONNES (OID = 17254) : 
--
CREATE TABLE "PERSONNES" (
    "ID" integer NOT NULL,
    "VERSION" integer NOT NULL,
    "NOM" varchar(30) NOT NULL,
    "PRENOM" varchar(30) NOT NULL,
    "DATENAISSANCE" date NOT NULL,
    "MARIE" boolean NOT NULL,
    "NBENFANTS" integer NOT NULL,
    CONSTRAINT "PERSONNES_chk_NBENFANTS" CHECK (("NBENFANTS" >= 0)),
    CONSTRAINT "PERSONNES_chk_NOM" CHECK ((("NOM")::text <> ''::text)),
    CONSTRAINT "PERSONNES_chk_PRENOM" CHECK ((("PRENOM")::text <> ''::text))
);
--
-- Definition for sequence SEQ_ID (OID = 17261) : 
--
CREATE SEQUENCE "SEQ_ID"
    INCREMENT BY 1
    NO MAXVALUE
    NO MINVALUE
    CACHE 1;
--
-- Data for blobs (OID = 17254) (LIMIT 0,3)
--
INSERT INTO "PERSONNES" ("ID", "VERSION", "NOM", "PRENOM", "DATENAISSANCE", "MARIE", "NBENFANTS") VALUES (1, 1, 'Major', 'Joachim', '1984-11-13', true, 2);
INSERT INTO "PERSONNES" ("ID", "VERSION", "NOM", "PRENOM", "DATENAISSANCE", "MARIE", "NBENFANTS") VALUES (2, 1, 'Humbort', 'Mélanie', '1985-01-12', false, 1);
INSERT INTO "PERSONNES" ("ID", "VERSION", "NOM", "PRENOM", "DATENAISSANCE", "MARIE", "NBENFANTS") VALUES (3, 1, 'Lemarchand', 'Charles', '1986-01-01', false, 0);
--
-- Definition for index PERSONNES_pkey (OID = 17256) : 
--
ALTER TABLE ONLY "PERSONNES"
    ADD CONSTRAINT "PERSONNES_pkey" PRIMARY KEY ("ID");
--
-- Data for sequence public."SEQ_ID" (OID = 17261)
--
SELECT pg_catalog.setval('"SEQ_ID"', 37, true);
--
-- Comments
--
COMMENT ON SCHEMA public IS 'Standard public schema';

[lib] 文件夹


此文件夹包含应用程序所需的资源包:

请注意其中包含 Postgres 8.x 数据库管理系统 (DBMS) 的 JDBC 驱动程序。所有这些文件均属于 Eclipse 项目的类路径

18.3. [dao] 层

[dao] 层如下所示:

Image

我们仅展示与 [Firebird] 版本相比的变更。

映射文件 [personne-postgres.xml] 如下:


<?xml version="1.0" encoding="UTF-8" ?>
 
<!DOCTYPE sqlMap
    PUBLIC "-//iBATIS.com//DTD SQL Map 2.0//EN"
    "http://www.ibatis.com/dtd/sql-map-2.dtd">
 
<!-- warning - Postgresql 8 requires exact spelling of column names
     et des tables ainsi que des guillemets autour de ces noms -->
 
    <sqlMap>
        <!-- alias class [Person] -->
        <typeAlias alias="Personne.classe" 
            type="istia.st.mvc.personnes.entites.Personne"/>
        <!-- mapping table [PERSONNES] - object [Person] -->
        <resultMap id="Personne.map" 
            class="istia.st.mvc.personnes.entites.Personne">
            <result property="id" column="ID" />
            <result property="version" column="VERSION" />
            <result property="nom" column="NOM"/>
            <result property="prenom" column="PRENOM"/>
            <result property="dateNaissance" column="DATENAISSANCE"/>
            <result property="marie" column="MARIE"/>
            <result property="nbEnfants" column="NBENFANTS"/>
        </resultMap>
        <!-- list of all persons -->
        <select id="Personne.getAll" resultMap="Personne.map" > select "ID", 
            "VERSION", "NOM", "PRENOM", "DATENAISSANCE", "MARIE", "NBENFANTS" FROM 
            "PERSONNES"</select>
        <!-- get a specific person -->
        <select id="Personne.getOne" resultMap="Personne.map" >select "ID", 
            "VERSION", "NOM", "PRENOM", "DATENAISSANCE", "MARIE", "NBENFANTS" FROM 
            "PERSONNES" WHERE "ID"=#value#</select>
        <!-- add a person -->
        <insert id="Personne.insertOne" parameterClass="Personne.classe"> 
            <selectKey keyProperty="id">
                SELECT nextval('"SEQ_ID"') as value
            </selectKey> 
            insert into "PERSONNES"("ID", "VERSION", 
            "NOM", "PRENOM", "DATENAISSANCE", "MARIE", "NBENFANTS") VALUES(#id#, 
            #version#, #nom#, #prenom#, #dateNaissance#, #marie#, #nbEnfants#) 
            </insert>
        <!-- update a person -->
        <update id="Personne.updateOne" parameterClass="Personne.classe"> update 
            "PERSONNES" set "VERSION"=#version#+1, "NOM"=#nom#, "PRENOM"=#prenom#, 
            "DATENAISSANCE"=#dateNaissance#, "MARIE"=#marie#, "NBENFANTS"=#nbEnfants# 
            WHERE "ID"=#id# and "VERSION"=#version#</update>
        <!-- delete a person -->
        <delete id="Personne.deleteOne" parameterClass="int"> delete FROM 
            "PERSONNES" WHERE "ID"=#value# </delete>
    </sqlMap>

这与 [people-firebird.xml] 的内容相同,仅有以下细微差异:

  • 列名和表名需用引号括起,且区分大小写
  • 第 34–41 行中的 SQL 语句 "Person.insertOne" 已发生变更。Postgres 生成主键的方式与 Firebird 不同:
    • 第 36 行:SQL 语句 [SELECT nextval('"SEQ_ID"')] 提供主键。必须使用 [as value] 语法。[value] 代表获取的键值。该值将被赋给 [Person] 对象中由 [keyProperty] 属性(第 35 行)指定的字段,此处即 [id] 字段。
    • <insert> 标签内的 SQL 语句按其出现的顺序执行。因此,SELECT 语句会在 INSERT 语句之前执行。当插入操作发生时,[Person] 对象的 [id] 字段已由 SELECT SQL 语句更新完毕。
    • 第 38–40 行:插入 [Person] 对象

[dao] 层的实现类 [DaoImplCommon] 即 [Firebird] 版本中研究的那个类。

[dao]层的配置已适配至[Postgres]数据库管理系统。因此,配置文件[spring-config-test-dao-postgres.xml]如下所示:


<?xml version="1.0" encoding="ISO_8859-1"?>
<!DOCTYPE beans SYSTEM "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
    <!-- data source DBCP -->
    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
        destroy-method="close">
        <property name="driverClassName">
            <value>org.postgresql.Driver</value>
        </property>
        <property name="url">
            <value>jdbc:postgresql:dbpersonnes</value>
        </property>
        <property name="username">
            <value>postgres</value>
        </property>
        <property name="password">
            <value>postgres</value>
        </property>
    </bean>
    <!-- SqlMapCllient -->
    <bean id="sqlMapClient" 
        class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">
        <property name="dataSource">
            <ref local="dataSource"/>
        </property>
        <property name="configLocation">
            <value>classpath:sql-map-config-postgres.xml</value>
        </property>
    </bean>
    <!-- the [dao] layer access classes -->
    <bean id="dao" class="istia.st.mvc.personnes.dao.DaoImplCommon">
        <property name="sqlMapClient">
            <ref local="sqlMapClient"/>
        </property>
    </bean>
</beans>
  • 第 5–19 行:[dataSource] Bean 现在指向 [Postgres] 数据库 [dbpersonnes],其管理员为 [postgres],密码为 [postgres]。读者应根据自身环境修改此配置。
  • 第 31 行:[DaoImplCommon] 类是 [dao] 层的实现类

完成这些修改后,我们可以进行测试。

18.4. 针对 [dao] 和 [service] 层的测试

针对 [dao] 和 [service] 层的测试与 [Firebird] 版本的测试相同。让我们启动 Postgres 数据库管理系统,然后运行 Eclipse 测试。所得结果如下:

我们可以看到,使用 [DaoImplCommon] 实现时测试已成功通过。与 [Firebird] 数据库管理系统的情况不同,我们无需派生该类。

18.5. [Web] 应用程序测试

为了使用 [Postgres] 数据库管理系统测试 Web 应用程序,我们以与构建基于 Firebird 数据库的 [mvc-personnes-03B] 项目类似的方式,构建了一个 Eclipse 项目 [mvc-personnes-04B](参见 17.7)。 不过,我们无需重新生成 [personnes-dao.jar] 和 [personnes-service.jar] 归档文件。事实上,与 [mvc-personnes-03B] 项目相比,我们并未修改任何类。 [personnes-dao.jar] 归档文件中仅包含 [DaoImplFirebird] 类,而该类现已不再需要。

Image

我们将 Web 项目 [mvc-personnes-04B] 部署到 Tomcat 中:

现在可以进行测试了 。 [PERSONNES] 表中的内容如下:

Image

Tomcat 正在运行。我们使用浏览器访问 URL [http://localhost:8080/mvc-personnes-04B]:

Image

我们通过 [Add] 链接添加一位新用户:

我们在数据库中验证新增记录:

Image

欢迎读者进行进一步测试 [更新、删除]。