Skip to content

18. MVC-Webanwendung in einer 3-Tier-Architektur – Beispiel 4, Postgres

18.1. Die Postgres-Datenbank

In dieser Version speichern wir die Liste der Personen in einer Postgres 8.x-Datenbanktabelle [http://www.postgres.org]. Die folgenden Screenshots stammen aus dem EMS PostgreSQL Manager Lite-Client [http://www.sqlmanager.net/fr/products/postgresql/manager], einem kostenlosen Verwaltungsclient für das Postgres-DBMS.

Die Datenbank trägt den Namen [dbpersonnes]. Sie enthält eine Tabelle namens [PERSONNES]:

Image

Die Tabelle [PERSONNES] enthält die Liste der Personen, die von der Webanwendung verwaltet werden. Sie wurde mit den folgenden SQL-Anweisungen erstellt:

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;

Wir werden nicht näher auf diese Tabelle eingehen, die der zuvor besprochenen Firebird-Tabelle [PERSONNES] entspricht. Beachten Sie jedoch, dass die Spalten- und Tabellennamen in Anführungszeichen stehen. Außerdem wird bei diesen Namen die Groß-/Kleinschreibung beachtet. Es ist möglich, dass dieses Verhalten in Postgres 8.x konfigurierbar ist. Ich habe dies nicht weiter untersucht.

Die Tabelle [PERSONNES] könnte folgenden Inhalt haben:

Image

Zusätzlich zur Tabelle [PERSONNES] enthält die Datenbank [dbpersonnes] ein Objekt namens [SEQ_ID], das als Sequenz bezeichnet wird. Dieser Generator erzeugt aufeinanderfolgende Ganzzahlen, die wir verwenden werden, um den Primärschlüssel [ID] der Klasse [PERSONNES] zu füllen. Betrachten wir ein Beispiel, um zu veranschaulichen, wie dies funktioniert:

Wir können sehen, dass sich der [Nächste Wert] der Sequenz [SEQ_ID] geändert hat (doppelklicken Sie darauf und drücken Sie F5, um die Seite zu aktualisieren):

 

Die SQL-Anweisung

SELECT nextval('"SEQ_ID"')

ermöglicht es uns daher, den nächsten Wert in der Sequenz [SEQ_ID] abzurufen. Wir werden sie in der Datei [people-postgres.xml] verwenden, die die auf dem DBMS ausgeführten SQL-Anweisungen enthält.

18.2. Das Eclipse-Projekt für die [dao]- und [service]-Schichten

Um die [dao]- und [service]-Schichten unserer Anwendung mit der Postgres 8.x-Datenbank zu entwickeln, verwenden wir das folgende Eclipse-Projekt [spring-mvc-39]:

Image

Das Projekt ist ein einfaches Java-Projekt, kein Tomcat-Webprojekt.


Ordner [src]


Dieser Ordner enthält den Quellcode für die [dao]- und [service]-Schichten:

Image

Alle Dateien, deren Namen [postgres] enthalten, können im Vergleich zur Firebird-Version geändert worden sein oder auch nicht. Im Folgenden beschreiben wir die geänderten Dateien.


Ordner [database]


Dieser Ordner enthält das Skript zum Erstellen der Postgres-Datenbank für Benutzer:

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';

Ordner [lib]


Dieser Ordner enthält die von der Anwendung benötigten Archive:

Beachten Sie den Vorhandensein des JDBC-Treibers für das Postgres 8.x-DBMS. Alle diese Dateien sind Teil des Klassenpfads des Eclipse-Projekts.

18.3. Die [dao]-Schicht

Die [dao]-Schicht sieht wie folgt aus:

Image

Wir stellen hier nur die Änderungen gegenüber der [Firebird]-Version dar.

Die Mapping-Datei [personne-postgres.xml] sieht wie folgt aus:


<?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>

Dies ist derselbe Inhalt wie in [people-firebird.xml], mit den folgenden geringfügigen Unterschieden:

  • Spalten- und Tabellennamen stehen in Anführungszeichen und unterscheiden zwischen Groß- und Kleinschreibung
  • Die SQL-Anweisung „Person.insertOne“ wurde in den Zeilen 34–41 geändert. Die Art und Weise, wie der Primärschlüssel mit Postgres generiert wird, unterscheidet sich von der bei Firebird verwendeten:
    • Zeile 36: Die SQL-Anweisung [SELECT nextval('"SEQ_ID"')] liefert den Primärschlüssel. Die Syntax [as value] ist obligatorisch. [value] steht für den erhaltenen Schlüssel. Dieser Wert wird dem Feld des [Person]-Objekts zugewiesen, das durch das Attribut [keyProperty] (Zeile 35) bezeichnet wird, hier das Feld [id].
    • Die SQL-Anweisungen innerhalb des <insert>-Tags werden in der Reihenfolge ihrer Erscheinung ausgeführt. Daher wird die SELECT-Anweisung vor der INSERT-Anweisung ausgeführt. Zum Zeitpunkt der Einfügung ist das Feld [id] des Objekts [Person] bereits durch die SELECT-SQL-Anweisung aktualisiert worden.
    • Zeilen 38–40: Einfügen des [Person]-Objekts

Die Implementierungsklasse [DaoImplCommon] der [dao]-Schicht ist diejenige, die in der [Firebird]-Version untersucht wird.

Die Konfiguration der [dao]-Schicht wurde an das [Postgres]-DBMS angepasst. Die Konfigurationsdatei [spring-config-test-dao-postgres.xml] sieht daher wie folgt aus:


<?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>
  • Zeilen 5–19: Die [dataSource]-Bean verweist nun auf die [Postgres]-Datenbank [dbpersonnes], deren Administrator [postgres] ist und deren Passwort [postgres] lautet. Der Leser sollte diese Konfiguration entsprechend seiner eigenen Umgebung anpassen.
  • Zeile 31: Die Klasse [DaoImplCommon] ist die Implementierungsklasse für die [dao]-Schicht

Nachdem diese Änderungen vorgenommen wurden, können wir mit dem Testen fortfahren.

18.4. Tests für die [dao]- und [service]-Schichten

Die Tests für die [dao]- und [service]-Schichten sind dieselben wie für die [Firebird]-Version. Starten wir das Postgres-DBMS und führen wir dann die Eclipse-Tests aus. Die erhaltenen Ergebnisse lauten wie folgt:

Wir sehen, dass die Tests mit der [DaoImplCommon]-Implementierung erfolgreich bestanden wurden. Wir müssen diese Klasse nicht ableiten, wie wir es beim [Firebird]-DBMS tun mussten.

18.5. [Web]-Anwendungstests

Um die Webanwendung mit dem [Postgres]-DBMS zu testen, erstellen wir ein Eclipse-Projekt [mvc-personnes-04B] auf ähnliche Weise wie beim Erstellen des Projekts [mvc-personnes-03B] mit der Firebird-Datenbank (siehe Abschnitt 17.7). Wir müssen jedoch die Archive [personnes-dao.jar] und [personnes-service.jar] nicht neu erstellen. Tatsächlich haben wir im Vergleich zum Projekt [mvc-personnes-03B] keine Klassen geändert. Das Archiv [personnes-dao.jar] enthält lediglich die Klasse [DaoImplFirebird], die nicht mehr benötigt wird.

Image

Wir stellen das Webprojekt [mvc-personnes-04B] in Tomcat bereit:

Wir sind bereit für Test en. Der Inhalt der Tabelle [PERSONNES] sieht nun wie folgt aus:

Image

Tomcat läuft. Über einen Browser rufen wir die URL [http://localhost:8080/mvc-personnes-04B] auf:

Image

Wir fügen über den Link [Hinzufügen] eine neue Person hinzu:

Wir überprüfen den Eintrag in der Datenbank:

Image

Der Leser ist eingeladen, weitere Tests durchzuführen [Aktualisieren, Löschen].