Skip to content

18. Aplikacja internetowa MVC w architekturze trójwarstwowej – Przykład 4, Postgres

18.1. Baza danych Postgres

W tej wersji umieścimy listę osób w tabeli bazy danych Postgres o nazwie 8.x [http://www.postgres.org]. Poniższe zrzuty ekranu pochodzą z klienta EMS PostgreSQL Manager Lite [http://www.sqlmanager.net/fr/products/postgresql/manager], bezpłatnego klienta administracyjnego do Postgres.

Baza danych nosi nazwę [dbpersonnes]. Zawiera ona tabelę [PERSONNES]:

Image

Tabela [PERSONNES] będzie zawierała listę osób zarządzanych przez aplikację internetową. Została ona utworzona za pomocą następujących poleceń 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;

Nie będziemy się szczegółowo zajmować tą tabelą, ponieważ jest ona analogiczna do omówionej wcześniej tabeli typu Firebird o nazwie [PERSONNES]. Należy jednak zauważyć, że nazwy kolumn i tabel są umieszczone w cudzysłowach. Ponadto w nazwach tych rozróżniana jest wielkość liter. Możliwe, że ten sposób działania Postgresu 8.x można skonfigurować. Nie zgłębiałem tej kwestii.

Tabela [PERSONNES] mogłaby mieć następującą zawartość:

Image

Oprócz tabeli [PERSONNES] baza [dbpersonnes] zawiera obiekt zwany sekwencją o nazwie [SEQ_ID]. Generator ten generuje kolejne liczby całkowite, które wykorzystamy do nadania wartości kluczu głównego [ID] klasy [PERSONNES]. Przyjrzyjmy się przykładowi ilustrującemu jego działanie:

Można zauważyć, że wartość [Next value] z sekwencji [SEQ_ID] uległa zmianie (dwukrotne kliknięcie na nią + F5 w celu odświeżenia):

 

Zlecenie SQL

SELECT nextval('"SEQ_ID"')

pozwala zatem uzyskać następującą wartość sekwencji [SEQ_ID]. Wykorzystamy ją w pliku [personnes-postgres.xml], który gromadzi polecenia SQL wysłane na SGBD.

18.2. Projekt Eclipse warstw [dao] i [service]

Aby opracować warstwy [dao] i [service] naszej aplikacji z bazą danych Postgres 8.x, wykorzystamy następujący projekt Eclipse [spring-mvc-39]:

Image

Projekt ten jest prostym projektem Java, a nie projektem internetowym opartym na Tomcat.


Folder [src]


Ten folder zawiera kod źródłowy warstw [dao] i [service]:

Image

Wszystkie pliki, których nazwy zawierają ciąg znaków „[postgres]”, mogły zostać zmodyfikowane w stosunku do wersji Firebird lub pozostać bez zmian. Poniżej opisujemy pliki, które zostały zmodyfikowane.


Folder [database]


Ten folder zawiera skrypt służący do tworzenia bazy danych Postgres zawierającej dane osób:

Image

-- EMS PostgreSQL Manager Lite 3.1.0.1
-- ---------------------------------------
-- Host      : localhost
-- Baza danych: dbpersonnes



--
-- Definicja funkcji plpgsql_call_handler (OID = 17230): 
--
...
--
-- Struktura tabeli 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))
);
--
-- Definicja sekwencji SEQ_ID (OID = 17261): 
--
CREATE SEQUENCE "SEQ_ID"
    INCREMENT BY 1
    NO MAXVALUE
    NO MINVALUE
    CACHE 1;
--
-- Dane dla obiektów blob (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);
--
-- Definicja indeksu PERSONNES_pkey (OID = 17256): 
--
ALTER TABLE ONLY "PERSONNES"
    ADD CONSTRAINT "PERSONNES_pkey" PRIMARY KEY ("ID");
--
-- Dane dla sekwencji publicznej „SEQ_ID” (OID = 17261)
--
SELECT pg_catalog.setval('"SEQ_ID"', 37, true);
--
-- Uwagi
--
COMMENT ON SCHEMA public IS 'Standard public schema';

Folder [lib]


Ten folder zawiera archiwa niezbędne do działania aplikacji:

Warto zwrócić uwagę na obecność sterownika JDBC dla Postgres 8.x o numerze SGBD. Wszystkie te pliki archiwum stanowią część projektu Eclipse o numerze Classpath.

18.3. Warstwa [dao]

Warstwa [dao] wygląda następująco:

Image

Przedstawiamy jedynie zmiany w stosunku do wersji [Firebird].

Plik mapowania [personne-postgres.xml] ma następującą postać:


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

<!-- uwaga – PostgreSQL 8 wymaga dokładnej pisowni nazw kolumn
     et des tables ainsi que des guillemets autour de ces noms -->

    <sqlMap>
        <!-- alias klasy [Personne] -->
        <typeAlias alias="Personne.classe" 
            type="istia.st.mvc.personnes.entites.Personne"/>
        <!-- tabela mapowania [PERSONNES] – obiekt [Personne] -->
        <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>
        <!-- lista wszystkich osób -->
        <select id="Personne.getAll" resultMap="Personne.map" > select "ID", 
            "VERSION", "NOM", "PRENOM", "DATENAISSANCE", "MARIE", "NBENFANTS" FROM 
            "PERSONNES"</select>
        <!-- pobranie konkretnej osoby -->
        <select id="Personne.getOne" resultMap="Personne.map" >select "ID", 
            "VERSION", "NOM", "PRENOM", "DATENAISSANCE", "MARIE", "NBENFANTS" FROM 
            "PERSONNES" WHERE "ID"=#wartość#</select>
        <!-- dodaj osobę -->
        <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#, 
             #wersja#, #nazwisko#, #imię#, #dateNaissance#, #mąż#, #nbEnfants#) 
            </insert>
        <!-- zaktualizuj dane osoby -->
        <update id="Personne.updateOne" parameterClass="Personne.classe"> update 
            "PERSONNES" set "VERSION"=#wersja#+1, „NOM”=#nazwisko#, „PRENOM”=#imię#, 
            "DATENAISSANCE"=#dateNaissance#, „MARIE”=#żona#, „NBENFANTS”=#nbEnfants# 
            WHERE "ID"=#id# oraz „VERSION”=#wersja#</aktualizacja>
        <!-- usunięcie osoby -->
        <delete id="Personne.deleteOne" parameterClass="int"> delete FROM 
            "PERSONNES" WHERE "ID"=#wartość# </delete>
    </sqlMap>

Zawartość jest identyczna jak w pliku [personnes-firebird.xml], z wyjątkiem następujących szczegółów:

  • nazwy kolumn i tabel są ujęte w cudzysłowy, a w tych nazwach rozróżniana jest wielkość liter
  • kolejność SQL „ Personne.insertOne ” uległa zmianie w wierszach 34–41. Sposób generowania klucza głównego w Postgresie różni się od tego stosowanego w Firebirdzie:
    • wiersz 36: kolejność SQL [SELECT nextval('"SEQ_ID"')] określa klucz główny. Składnia [as value] jest obowiązkowa. [value] oznacza uzyskany klucz. Wartość ta zostanie przypisana do pola obiektu [Personne] wskazanego przez atrybut [keyProperty] (wiersz 35), w tym przypadku do pola [id].
    • Polecenia SQL zawarte w tagu <insert> są wykonywane w kolejności, w jakiej występują. Zatem polecenie SELECT jest wykonywane przed poleceniem INSERT. W momencie operacji wstawiania pole [id] obiektu [Personne] zostanie zatem zaktualizowane przez polecenie SQL SELECT.
    • wiersze 38–40: wstawienie obiektu [Personne]

Klasa implementacyjna [DaoImplCommon] warstwy [dao] jest tą samą, którą analizowano w wersji [Firebird].

Konfiguracja warstwy [dao] została dostosowana do SGBD i [Postgres]. W związku z tym plik konfiguracyjny [spring-config-test-dao-postgres.xml] ma następującą treść:


<?xml version="1.0" encoding="ISO_8859-1"?>
<!DOCTYPE beans SYSTEM "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
    <!-- źródło danych 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>
    <!-- klasa dostępu do warstwy [dao] -->
    <bean id="dao" class="istia.st.mvc.personnes.dao.DaoImplCommon">
        <property name="sqlMapClient">
            <ref local="sqlMapClient"/>
        </property>
    </bean>
</beans>
  • wiersze 5–19: bean [dataSource] odnosi się teraz do bazy [Postgres] [dbpersonnes], której administratorem jest [postgres] z hasłem [postgres]. Czytelnik powinien dostosować tę konfigurację do własnego środowiska.
  • wiersz 31: klasa [DaoImplCommon] jest klasą implementacyjną warstwy [dao]

Po wprowadzeniu tych zmian można przejść do testów.

18.4. Testy warstw [dao] i [service]

Testy warstw [dao] i [service] są takie same jak dla wersji [Firebird]. Uruchommy SGBD Postgres, a następnie testy w Eclipse. Uzyskane wyniki są następujące:

Widać, że testy zakończyły się powodzeniem w przypadku implementacji [DaoImplCommon]. Nie będziemy musieli tworzyć klasy pochodnej, jak to było konieczne w przypadku SGBD i [Firebird].

18.5. Testy aplikacji [web]

Aby przetestować aplikację internetową z wykorzystaniem SGBD i [Postgres], tworzymy projekt Eclipse o nazwie [mvc-personnes-04B] w sposób analogiczny do tego, który zastosowano przy tworzeniu projektu [mvc-personnes-03B] z bazą danych Firebird (patrz paragraf 17.7). Nie musimy jednak ponownie tworzyć archiwów [personnes-dao.jar] i [personnes-service.jar]. W rzeczywistości nie wprowadziliśmy żadnych zmian w klasach w stosunku do projektu [mvc-personnes-03B]. Po prostu archiwum [personnes-dao.jar] zawiera klasę [DaoImplFirebird], która stała się zbędna.

Image

Wdrażamy projekt internetowy [mvc-personnes-04B] w środowisku Tomcat:

Jesteśmy gotowi do przeprowadzenia testów . Zawartość tabeli [PERSONNES] wygląda wówczas następująco:

Image

Tomcat został uruchomiony. W przeglądarce wpisujemy adres URL [http://localhost:8080/mvc-personnes-04B]:

Image

Dodajemy nową osobę za pomocą linku [Ajout]:

Sprawdzamy, czy wpis został dodany do bazy danych:

Image

Użytkownik jest proszony o przeprowadzenie dalszych testów [modification, suppression].