Skip to content

13. Aplikacja [SimuPaie] – wersja 9 – integracja Spring / NHibernate

W tym miejscu proponujemy przerobić trójwarstwową aplikację ASP.NET na podstawie wersji 7 [pam-v7-3tier-nhibernate-multivues-multipages]. Architektura warstwowa aplikacji wyglądała następująco:

W powyższym przykładzie warstwa [dao] została zaimplementowana przy użyciu frameworka NHibernate. Framework Spring został wykorzystany wyłącznie do integracji poszczególnych warstw. Framework Spring udostępnia klasy pomocnicze do pracy z frameworkiem Nhibernate. Wykorzystanie tych klas ułatwia pisanie kodu warstwy [dao]. Poprzednia architektura ewoluuje w następujący sposób:

Ze względu na zastosowaną strukturę warstwową wykorzystanie integracji Spring / NHibernate powoduje zmianę wyłącznie w warstwie [dao]. Warstwy [presentation] (web / ASP.NET) oraz [metier] nie będą wymagały modyfikacji. Jest to główna zaleta architektur warstwowych zintegrowanych przez Spring.

W dalszej części zbudujemy warstwę [dao] wraz z [Spring / NHibernate], omawiając kod działającego rozwiązania. Nie będziemy starać się przedstawić wszystkich możliwości konfiguracji ani zastosowania frameworka [Spring / Nhibernate]. Czytelnik będzie mógł dostosować proponowane rozwiązanie do własnych potrzeb, korzystając z dokumentacji Spring.NET i [http://www.springframework.net/documentation.html] (czerwiec 2010 r.).

Podejście zastosowane przy tworzeniu warstw [dao] i [metier] jest zgodne z wersją 3 opisaną w paragrafie 7. Podejście zastosowane w przypadku warstwy [présentation] jest zgodne z wersją 7 opisaną w paragrafie 11.

13.1. Warstwa [dao] zapewniająca dostęp do danych

13.1.1. Projekt Visual Studio C# warstwy [dao]

Projekt Visual Studio warstwy [dao] wygląda następująco:

  • w [1] znajduje się cały projekt
    • folder [pam] zawiera klasy projektu oraz konfigurację encji NHibernate
    • pliki [App.config] i [Dao.xml] konfigurują framework Spring / NHibernate. Będziemy musieli opisać zawartość tych dwóch plików.
  • W pliku [2] znajdują się różne klasy projektu
    • w folderze [entites] znajdują się encje NHibernate analizowane w projekcie [pam-dao-nhibernate]
    • w folderze [service] znajdujemy interfejs [IPamDao] oraz jego implementację przy użyciu frameworka Spring / NHibernate [PamDaoSpringNHibernate]. Będziemy musieli napisać tę nową implementację interfejsu [IPamDao]
    • Katalog [tests] zawiera te same testy, co projekt [pam-dao-nhibernate]. Testują one ten sam interfejs [IPamdao].
  • W [3] znajdują się odniesienia do projektu. Integracja Spring / NHibernate wymaga dwóch nowych plików: DLL, [Spring.Data] oraz [Spring.Data.NHibernate12]. Te pliki DLL są dostępne w frameworku Spring.Net. Zostały one dodane do folderu [lib] w ramach DLL i [4]:

W odniesieniach [3] projektu znajdują się następujące odniesienia DLL:

  • NHibernate: dla ORM NHibernate
  • MySql.Data: sterownik ADO.NET dla SGBD MySQL
  • Spring.Core: dla frameworka Spring, który zapewnia integrację warstw
  • log4net: biblioteka logów
  • nunit.framework: biblioteka testów jednostkowych
  • Spring.Data i Spring.Data.NHibernate12: zapewniają obsługę Spring / NHibernate.

Odwołania te zostały pobrane z folderu [lib] [4]. Należy upewnić się, że dla wszystkich tych odwołań właściwość „Kopia lokalna” ma wartość „True” [5]:

13.1.2. Konfiguracja projektu C#

Projekt jest skonfigurowany w następujący sposób:

  • w [1] nazwa zestawu projektu to [pam-dao-spring-nhibernate]. Nazwa ta pojawia się w różnych plikach konfiguracyjnych projektu.

13.1.3. Elementy warstwy [dao]

Elementy (obiekty) niezbędne dla warstwy [dao] zostały zebrane w folderze [entites] [1] projektu. Są to te same elementy, co w projekcie [pam-dao-nhibernate], z jedną różnicą w plikach konfiguracyjnych NHibernate. Weźmy na przykład plik [Employe.hbm.xml]:

  • w pliku [2] jest on skonfigurowany tak, aby został włączony do zestawu projektu

Jego zawartość jest następująca:


<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
namespace="Pam.Dao.Entites" assembly="pam-dao-spring-nhibernate">
    <class name="Employe" table="EMPLOYES">
        <id name="Id" column="ID">
            <generator class="native" />
        </id>
        <version name="Version" column="VERSION"/>
        <property name="SS" column="SS" length="15" not-null="true" unique="true"/>
        <property name="Nom" column="NOM" length="30" not-null="true"/>
        <property name="Prenom" column="PRENOM" length="20" not-null="true"/>
        <property name="Adresse" column="ADRESSE" length="50" not-null="true" />
        <property name="Ville" column="VILLE" length="30" not-null="true"/>
        <property name="CodePostal" column="CP" length="5" not-null="true"/>
        <many-to-one name="Indemnites" column="INDEMNITE_ID" cascade="all" lazy="false"/>
    </class>
</hibernate-mapping>
  • wiersz 2: atrybut assembly wskazuje, że plik [Employe.hbm.xml] będzie znajdował się w zestawie [pam-dao-spring-nhibernate]

13.1.4. Konfiguracja Spring / NHibernate

Wróćmy do projektu Visual C#:

  • w pliku [1] pliki [App.config] i [Dao.xml] konfigurują integrację Spring / NHibernate

13.1.4.1. Plik [App.config]

Plik [App.config] ma następującą treść:


<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <!-- sekcje konfiguracyjne -->
    <configSections>
        <sectionGroup name="spring">
            <section name="parsers" type="Spring.Context.Support.NamespaceParsersSectionHandler, Spring.Core" />
            <section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" />
            <section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core" />
        </sectionGroup>
        <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler,log4net" />
    </configSections>


    <!-- konfiguracja Spring -->
    <spring>
        <parsers>
            <parser type="Spring.Data.Config.DatabaseNamespaceParser, Spring.Data" />
        </parsers>
        <context>
            <resource uri="Dao.xml" />
        </context>
    </spring>

    <!-- Ta sekcja zawiera ustawienia konfiguracyjne log4net -->
    <!-- NOTE IMPORTANTE: logi nie są domyślnie aktywne. Należy je aktywować programowo
    avec l'instruction log4net.Config.XmlConfigurator.Configure();
    ! -->
    <log4net>
        <appender name="ConsoleAppender" type="log4net.Appender.ConsoleAppender">
            <layout type="log4net.Layout.PatternLayout">
                <conversionPattern value="%-5level %logger - %message%newline" />
            </layout>
        </appender>

        <!-- Ustaw domyślny poziom logowania na DEBUG -->
        <root>
            <level value="DEBUG" />
            <appender-ref ref="ConsoleAppender" />
        </root>

        <!-- Ustaw rejestrowanie dla Spring. Nazwy rejestratorów w Spring odpowiadają przestrzeni nazw -->
        <logger name="Spring">
            <level value="INFO" />
        </logger>

        <logger name="Spring.Data">
            <level value="DEBUG" />
        </logger>

        <logger name="NHibernate">
            <level value="DEBUG" />
        </logger>
    </log4net>

</configuration>

Powyższy plik [App.config] konfiguruje Spring (wiersze 5–9, 15–22), log4net (wiersz 10, wiersze 28–53), ale nie NHibernate. Obiekty Springa nie są skonfigurowane w pliku [App.config], lecz w pliku [Dao.xml] (wiersz 20). Konfiguracja Springa / NHibernate, polegająca na zadeklarowaniu konkretnych obiektów Springa, znajduje się zatem w tym pliku.

13.1.4.2. Plik [Dao.xml]

Plik [Dao.xml], zawierający obiekty zarządzane przez Spring, wygląda następująco:


<?xml version="1.0" encoding="utf-8" ?>
<objects xmlns="http://www.springframework.net"
         xmlns:db="http://www.springframework.net/database">

    <!-- Odwołuje się do głównego pliku konfiguracyjnego kontekstu aplikacji -->
    <description>
        Application Spring / NHibernate
    </description>

    <!-- Konfiguracja bazy danych i NHibernate -->
    <db:provider id="DbProvider"
                   provider="MySql.Data.MySqlClient"
                   connectionString="Server=localhost;Database=dbpam_nhibernate;Uid=root;Pwd=;"/>

    <object id="NHibernateSessionFactory" type="Spring.Data.NHibernate.LocalSessionFactoryObject, Spring.Data.NHibernate12">
        <property name="DbProvider" ref="DbProvider"/>
        <property name="MappingAssemblies">
            <list>
                <value>pam-dao-spring-nhibernate</value>
            </list>
        </property>
        <property name="HibernateProperties">
            <dictionary>
                <entry key="hibernate.dialect" value="NHibernate.Dialect.MySQLDialect"/>
                <entry key="hibernate.show_sql" value="false"/>
            </dictionary>
        </property>
        <property name="ExposeTransactionAwareSessionFactory" value="true" />
    </object>

    <!-- menedżer transakcji -->
    <object id="transactionManager"
        type="Spring.Data.NHibernate.HibernateTransactionManager, Spring.Data.NHibernate12">
        <property name="DbProvider" ref="DbProvider"/>
        <property name="SessionFactory" ref="NHibernateSessionFactory"/>
    </object>

    <!-- Szablon Hibernate -->
    <object id="HibernateTemplate" type="Spring.Data.NHibernate.Generic.HibernateTemplate">
        <property name="SessionFactory" ref="NHibernateSessionFactory" />
        <property name="TemplateFlushMode" value="Auto" />
        <property name="CacheQueries" value="true" />
    </object>

    <!-- Obiekty dostępu do danych -->
    <object id="pamdao" type="Pam.Dao.Service.PamDaoSpringNHibernate, pam-dao-spring-nhibernate" init-method="init" destroy-method="destroy">
        <property name="HibernateTemplate" ref="HibernateTemplate"/>
    </object>
</objects>
  • Wiersze 11–13 konfigurują połączenie z bazą danych [dbpam_nhibernate]. Znajduje się tam:
    • dostawca ADO.NET niezbędny do połączenia, w tym przypadku dostawca z pliku SGBD MySQL. Oznacza to, że w odwołaniach projektu muszą znajdować się DLL i [Mysql.Data].
    • łańcuch połączenia z bazą danych (serwer, nazwa bazy, właściciel połączenia, jego hasło)
  • wiersze 15–29 konfigurują obiekt SessionFactory z NHibernate, który służy do uzyskiwania sesji NHibernate. Przypominamy, że każda operacja na bazie danych odbywa się w ramach sesji NHibernate. W wierszu 15 widać, że SessionFactory jest zaimplementowany przez klasę Spring Spring.Data.NHibernate.LocalSessionFactoryObject znajdującą się w DLL Spring.Data.NHibernate12.
  • Wiersz 16: właściwość DbProvider określa parametry połączenia z bazą danych (dostawca ADO.NET i ciąg połączenia). W tym przypadku właściwość ta odwołuje się do obiektu DbProvider zdefiniowanego wcześniej w wierszach 11–13.
  • wiersze 17–20: określają listę zestawów zawierających pliki [*.hbm.xml] konfigurujące elementy zarządzane przez NHibernate. Wiersz 19 wskazuje, że pliki te będą znajdować się w zestawie projektu. Przypominamy, że nazwa ta znajduje się we właściwościach projektu C#. Przypominamy również, że wszystkie pliki [*.hbm.xml] zostały skonfigurowane tak, aby były włączone do zestawu projektowego.
  • wiersze 22–27: właściwości specyficzne dla NHibernate.
    • wiersz 24: używany dialekt SQL będzie pochodził z MySQL
    • wiersz 25: SQL wygenerowany przez NHibernate nie pojawi się w logach konsoli. Ustawienie tej właściwości na true pozwala poznać polecenia SQL wysyłane przez NHibernate. Może to pomóc na przykład w zrozumieniu, dlaczego aplikacja działa wolno podczas dostępu do bazy danych.
  • wiersz 28: ustawienie właściwości ExposeTransactionAwareSessionFactory na true spowoduje, że Spring będzie zarządzał adnotacjami dotyczącymi transakcji, które zostaną znalezione w kodzie C#. Powrócimy do tego tematu podczas pisania klasy implementującej warstwę [dao].
  • Wiersze 32–36 definiują menedżera transakcji. Również w tym przypadku menedżer ten jest klasą Springa z pakietu DLL Spring.Data.NHibernate12. Menedżer ten musi znać parametry połączenia z bazą danych (wiersz 34), a także SessionFactory z NHibernate (wiersz 35).
  • Wiersze 39–43 definiują właściwości klasy HibernateTemplate, która również jest klasą Springa. Klasa ta będzie wykorzystywana jako klasa pomocnicza w klasie implementującej warstwę [dao]. Ułatwia ona interakcje z obiektami NHibernate. Klasa ta posiada pewne właściwości, które należy zainicjować:
    • wiersz 40: SessionFactory z NHibernate
    • wiersz 41: właściwość TemplateFlushMode określa tryb synchronizacji kontekstu trwałości NHibernate z bazą danych. Tryb Auto powoduje, że synchronizacja nastąpi:
      • na końcu transakcji
      • przed operacją select
    • wiersz 42: zapytania HQL (Hibernate Query Language) będą buforowane. Może to przyczynić się do poprawy wydajności.
  • wiersze 46–48 definiują klasę implementacyjną warstwy [dao]
    • wiersz 46: warstwa [dao] zostanie zaimplementowana przez klasę [PamdaoSpringNHibernate] z klasy DLL [pam-dao-spring-nhibernate]. Po utworzeniu instancji klasy natychmiast zostanie wykonana metoda init tej klasy. Po zamknięciu kontenera Spring zostanie wykonana metoda destroy tej klasy.
    • wiersz 47: klasa [PamDaoSpringNHibernate] będzie posiadać właściwość HibernateTemplate, która zostanie zainicjowana wartością właściwości HibernateTemplate z wiersza 39.

13.1.5. Implementacja warstwy [dao]

13.1.5.1. Szkielet klasy implementacyjnej

Interfejs [IPamDao] jest taki sam jak w projekcie [pam-dao-nhibernate]:


using Pam.Dao.Entites;

namespace Pam.Dao.Service {
    public interface IPamDao {
        // lista wszystkich tożsamości pracowników 
        Employe[] GetAllIdentitesEmployes();
        // konkretny pracownik wraz z jego dodatkami 
        Employe GetEmploye(string ss);
        // lista wszystkich składek 
        Cotisations GetCotisations();
    }
}
  • wiersz 1: importujemy przestrzeń nazw elementów warstwy [dao].
  • wiersz 3: warstwa [dao] znajduje się w przestrzeni nazw [Pam.Dao.Service]. Elementy przestrzeni nazw [Pam.Dao.Entites] mogą być tworzone w wielu egzemplarzach. Elementy przestrzeni nazw [Pam.Dao.Service] są tworzone w jednym egzemplarzu (singleton). To właśnie uzasadniało wybór nazw przestrzeni nazw.
  • wiersz 4: interfejs nosi nazwę [IPamDao]. Definiuje on trzy metody:
    • wiersz 6: metoda [GetAllIdentitesEmployes] zwraca tablicę obiektów typu [Employe], która przedstawia listę opiekunek do dzieci w uproszczonej formie (nazwisko, imię, SS).
    • w wierszu 8 metoda [GetEmploye] zwraca obiekt typu [Employe]: pracownika o numerze ubezpieczenia społecznego przekazanym jako parametr metody wraz z dodatkami związanymi z jego wskaźnikiem.
    • w wierszu 10, [GetCotisations] zwraca obiekt [Cotisations], który zawiera stawki różnych składek na ubezpieczenie społeczne pobieranych od wynagrodzenia brutto.

Szkielet klasy implementującej ten interfejs przy użyciu biblioteki Spring / NHibernate mógłby wyglądać następująco:


using System;
using System.Collections;
using System.Collections.Generic;
using Pam.Dao.Entites;
using Spring.Data.NHibernate.Generic.Support;
using Spring.Transaction.Interceptor;

namespace Pam.Dao.Service {
    public class PamDaoSpringNHibernate : HibernateDaoSupport, IPamDao {
        // pola prywatne 
        private Cotisations cotisations;
        private Employe[] employes;

        // init 
        [Transaction(ReadOnly = true)]
        public void init() {
...
        }

        // usunięcie obiektu
        public void destroy() {
            if (HibernateTemplate.SessionFactory != null) {
                HibernateTemplate.SessionFactory.Close();
            }
        }

        // lista wszystkich tożsamości pracowników
        public Employe[] GetAllIdentitesEmployes() {
            return employes;
        }


        // konkretny pracownik wraz z jego dodatkami 
        [Transaction(ReadOnly = true)]
        public Employe GetEmploye(string ss) {
....
        }

        // lista składek 
        public Cotisations GetCotisations() {
            return cotisations;
        }
    }
}
  • wiersz 9: klasa [PamDaoSpringNHibernate] prawidłowo implementuje interfejs warstwy [dao] [IPamDao]. Wywodzi się ona również z klasy Spring [HibernateDaoSupport]. Klasa ta posiada właściwość [HibernateTemplate], która jest inicjowana przez skonfigurowane ustawienia Springa (wiersz 2 poniżej):

    <object id="pamdao" type="Pam.Dao.Service.PamDaoSpringNHibernate, pam-dao-spring-nhibernate" init-method="init" destroy-method="destroy">
        <property name="HibernateTemplate" ref="HibernateTemplate"/>
</object>
  • W wierszu 1 powyżej widać, że definicja obiektu [pamdao] wskazuje, iż metody init i destroy klasy [PamDaoSpringNHibernate] muszą zostać wykonane w określonych momentach. Obie te metody rzeczywiście znajdują się w tej klasie w wierszach 16 i 21.
  • wiersze 15, 34: adnotacje, które powodują, że metoda opatrzona adnotacją zostanie wykonana w ramach transakcji. Atrybut ReadOnly=true oznacza, że transakcja ma charakter tylko do odczytu. Metoda wykonywana w ramach transakcji może wygenerować wyjątek. W takim przypadku Spring automatycznie Rollback transakcji. Adnotacja ta eliminuje konieczność zarządzania transakcją wewnątrz metody.
  • wiersz 16: metoda init jest wykonywana przez Spring natychmiast po instancjonowaniu klasy. Zobaczymy, że jej celem jest zainicjowanie pól prywatnych z wierszy 11 i 12. Metoda ta będzie przebiegać w ramach transakcji (wiersz 15).
  • Metody interfejsu [IPamDao] są zaimplementowane w wierszach 28, 35 i 40.
  • wiersze 28–30: metoda [GetAllIdentitesEmployes] ogranicza się do zwracania atrybutu z wiersza 12 zainicjowanego przez metodę init.
  • wiersze 40–42: metoda [GetCotisations] ogranicza się do zwracania atrybutu z wiersza 11 zainicjowanego przez metodę init.

13.1.5.2. Przydatne metody klasy HibernateTemplate

Będziemy korzystać z następujących metod klasy HibernateTemplate:

IList<T> Find<T>(string requete_hql)
wykonuje zapytanie HQL i zwraca listę obiektów typu T
IList<T> Find<T>(string requete_hql, object[])
wykonuje zapytanie HQL z parametrami oznaczonymi jako ?. Wartości tych parametrów są dostarczane przez tablicę obiektów.
IList<T> LoadAll<T>()
zwraca wszystkie encje typu T
  

Istnieją inne przydatne metody, których nie będziemy mieli okazji wykorzystać, a które pozwalają na wyszukiwanie, zapisywanie, aktualizowanie i usuwanie elementów:

T Load<T>(object id)
dodaje do sesji NHibernate encję typu T o kluczu głównym id.
void SaveOrUpdate(object entité)
wstawia (INSERT) lub aktualizuje (UPDATE) obiekt entité w zależności od tego, czy posiada on klucz podstawowy (UPDATE), czy nie (INSERT). Brak klucza głównego można skonfigurować za pomocą atrybutu unsaved-values w pliku konfiguracyjnym encji. Po wykonaniu operacji SaveOrUpdate obiekt entité znajduje się w sesji NHibernate.
void Delete(object obiekt)
usuwa obiekt entité z sesji NHibernate.

13.1.5.3. Implementacja metody init

Metoda init klasy [PamDaoSpringNHibernate] jest domyślnie metodą wykonywaną po utworzeniu instancji klasy przez Spring. Jej celem jest zapisanie w lokalnej pamięci podręcznej uproszczonych danych identyfikacyjnych pracowników (nazwisko, imię, SS) oraz stawek składek. Jej kod mógłby wyglądać następująco.


[Transaction(ReadOnly = true)]
        public void init() {
            try {
                // pobieramy uproszczoną listę pracowników
                IList<object[]> lignes = HibernateTemplate.Find<object[]>("select e.SS,e.Nom,e.Prenom from Employe e");
                // umieszczamy ją w tabeli
                employes = new Employe[lignes.Count];
                int i = 0;
                foreach (object[] ligne in lignes) {
                    employes[i] = new Employe() { SS = ligne[0].ToString(), Nom = ligne[1].ToString(), Prenom = ligne[2].ToString() };
                    i++;
                }
                // umieszczamy stawki składek w obiekcie 
                cotisations = (HibernateTemplate.LoadAll<Cotisations>())[0];
            } catch (Exception ex) {
                // przetwarzamy wyjątek 
                throw new PamException(string.Format("Erreur d'accès à la BD : [{0}]", ex.ToString()), 43);
            }
        }
  • wiersz 5: wykonywane jest zapytanie HQL. Pobiera ono pola SS, Nazwisko, Imię ze wszystkich encji Employé. Zwraca listę obiektów. Gdybyśmy zapytali o pełne dane pracownika w postaci „select e from Employe e”, otrzymalibyśmy listę obiektów typu Employe.
  • wiersze 7–12: ta lista obiektów jest kopiowana do tablicy obiektów typu Employe.
  • wiersz 14: żądamy listy wszystkich encji typu Cotisations. Wiemy, że lista ta zawiera tylko jeden element. Pobieramy zatem pierwszy element listy, aby uzyskać stawki składek.
  • Wiersze 7 i 14 inicjują dwa pola prywatne klasy.

13.1.5.4. Implementacja metody GetEmploye

Metoda GetEmploye musi zwrócić jednostkę typu „Pracownik” o podanym numerze SS. Jej kod mógłby wyglądać następująco:


[Transaction(ReadOnly = true)]
        public Employe GetEmploye(string ss) {
            IList<Employe> employés = null;
            try {
                // zapytanie
                employés = HibernateTemplate.Find<Employe>("select e from Employe e where e.SS=?", new object[]{ss});
            } catch (Exception ex) {
                // przekształcamy wyjątek 
                throw new PamException(string.Format("Erreur d'accès à la BD lors de la demande de l'employé de n° ss [{0}] : [{1}]", ss, ex.ToString()), 41);
            }
            // czy udało się pozyskać pracownika? 
            if (employés.Count == 0) {
                // zgłaszamy ten fakt 
                throw new PamException(string.Format("L'employé de n° ss [{0}] n'existe pas", ss), 42);
            } else {
                return employés[0];
            }
        }
  • wiersz 6: pobiera listę pracowników o podanym numerze SS
  • wiersz 12: zazwyczaj, jeśli pracownik istnieje, powinna pojawić się lista zawierająca jeden element
  • wiersz 14: jeśli tak nie jest, zgłaszamy wyjątek
  • wiersz 16: jeśli tak jest, zwracamy pierwszego pracownika z listy

13.1.5.5. Conclusion

Jeśli porównamy kod warstwy [dao] w przypadku wykorzystania

  1. samego frameworka NHibernate
  2. frameworka Spring / NHibernate

można zauważyć, że drugie rozwiązanie pozwoliło na napisanie prostszego kodu.

13.2. Testy warstwy [dao]

13.2.1. Projekt w Visual Studio

Projekt Visual Studio został już przedstawiony. Przypomnijmy:

  • w [1], cały projekt
  • w [2] – poszczególne klasy projektu. Folder [tests] zawiera test konsolowy [Main.cs] oraz test jednostkowy [NUnit.cs].
  • w pliku [3] skompilowano program [Main.cs].
  • w pliku [4] plik [NUnit.cs] nie jest generowany.
  • Projekt jest aplikacją konsolową. Klasą wykonywaną jest ta określona w pliku [5], czyli klasa z pliku [Main.cs].

13.2.2. Program testowy konsolowy [Main.cs]

Program testowy [Main.cs] jest uruchamiany w następującej architekturze:

Jego zadaniem jest testowanie metod interfejsu [IPamDao]. Podstawowy przykład mógłby wyglądać następująco:


using System;
using Pam.Dao.Entites;
using Pam.Dao.Service;
using Spring.Context.Support;

namespace Pam.Dao.Tests {
    public class MainPamDaoTests {
        public static void Main() {
            try {
                // instancja warstwy [dao]
                IPamDao pamDao = (IPamDao)ContextRegistry.GetContext().GetObject("pamdao");
                // lista tożsamości pracowników 
                foreach (Employe Employe in pamDao.GetAllIdentitesEmployes()) {
                    Console.WriteLine(Employe.ToString());
                }
                // pracownik wraz z jego dodatkami 
                Console.WriteLine("------------------------------------");
                Console.WriteLine(pamDao.GetEmploye("254104940426058"));
                Console.WriteLine("------------------------------------");
                // lista składek 
                Cotisations cotisations = pamDao.GetCotisations();
                Console.WriteLine(cotisations.ToString());
            } catch (Exception ex) {
                // wyświetlenie wyjątku 
                Console.WriteLine(ex.ToString());
            }
            //przerwa 
            Console.ReadLine();
        }
    }
}
  • wiersz 11: zwracamy się do Springa o odwołanie do warstwy [dao].
  • wiersze 13–15: test metody [GetAllIdentitesEmployes] interfejsu [IPamDao]
  • wiersz 18: test metody [GetEmploye] z interfejsu [IPamDao]
  • wiersz 21: test metody [GetCotisations] z interfejsu [IPamDao]

Spring, NHibernate i log4net są konfigurowane przez plik [App.config] omówiony w paragrafie 13.1.4.1.

Uruchomienie programu z wykorzystaniem bazy danych opisanej w punkcie 6.2 daje następujący wynik na konsoli:

1
2
3
4
5
6
[254104940426058,Jouveinal,Marie,,,,]
[260124402111742,Laverti,Justine,,,,]
------------------------------------
[254104940426058,Jouveinal,Marie,5 rue des oiseaux,St Corentin,49203,[2, 2,1, 2,1, 3,1, 15]]
------------------------------------
[3,49,6,15,9,39,7,88]
  • wiersze 1–2: dwaj pracownicy typu [Employe], dla których podano jedynie informacje [SS, Nom, Prenom]
  • wiersz 4: pracownik typu [Employe] o numerze ubezpieczenia społecznego [254104940426058]
  • wiersz 5: stawki składek

13.2.3. Testy jednostkowe z NUnit

Przechodzimy teraz do testu jednostkowego NUnit. Projekt Visual Studio warstwy [dao] ulegnie następującym zmianom:

  • do [1], program testowy [NUnit.cs]
  • na [2,3], projekt wygeneruje plik DLL o nazwie [pam-dao-spring-nhibernate.dll]
  • w [4], odwołanie do DLL w frameworku NUnit: [nunit.framework.dll]
  • w [5], klasa [Main.cs] nie zostanie uwzględniona w DLL [pam-dao-spring-nhibernate]
  • w [6] klasa [NUnit.cs] zostanie uwzględniona w DLL [pam-dao-spring-nhibernate]

Klasa testowa NUnit ma następujący wygląd:


using System.Collections;
using NUnit.Framework;
using Pam.Dao.Service;
using Pam.Dao.Entites;
using Spring.Objects.Factory.Xml;
using Spring.Core.IO;
using Spring.Context.Support;

namespace Pam.Dao.Tests {

    [TestFixture]
    public class NunitPamDao : AssertionHelper {
        // warstwa [dao] do przetestowania 
        private IPamDao pamDao = null;

        // konstruktor 
        public NunitPamDao() {
            // instancjonowanie warstwy [dao]
            pamDao = (IPamDao)ContextRegistry.GetContext().GetObject("pamdao");
        }

        // inicjalizacja 
        [SetUp]
        public void Init() {

        }

        [Test]
        public void GetAllIdentitesEmployes() {
            // weryfikacja liczby pracowników 
            Expect(2, EqualTo(pamDao.GetAllIdentitesEmployes().Length));
        }

        [Test]
        public void GetCotisations() {
            // weryfikacja stawek składek 
            Cotisations cotisations = pamDao.GetCotisations();
            Expect(3.49, EqualTo(cotisations.CsgRds).Within(1E-06));
            Expect(6.15, EqualTo(cotisations.Csgd).Within(1E-06));
            Expect(9.39, EqualTo(cotisations.Secu).Within(1E-06));
            Expect(7.88, EqualTo(cotisations.Retraite).Within(1E-06));
        }

        [Test]
        public void GetEmployeIdemnites() {
            // weryfikacja osób fizycznych 
            Employe employe1 = pamDao.GetEmploye("254104940426058");
            Employe employe2 = pamDao.GetEmploye("260124402111742");
            Expect("Jouveinal", EqualTo(employe1.Nom));
            Expect(2.1, EqualTo(employe1.Indemnites.BaseHeure).Within(1E-06));
            Expect("Laverti", EqualTo(employe2.Nom));
            Expect(1.93, EqualTo(employe2.Indemnites.BaseHeure).Within(1E-06));
        }

        [Test]
        public void GetEmployeIdemnites2() {
            // weryfikacja nieistniejącej osoby 
            bool erreur = false;
            try {
                Employe employe1 = pamDao.GetEmploye("xx");
            } catch {
                erreur = true;
            }
            Expect(erreur, True);
        }
    }
}

Klasa ta została już omówiona w punkcie 7.3.4.

Podczas generowania projektu tworzone są pliki DLL i [pam-dao-spring-nhibernate.dll] w folderze [bin/Release].

Pliki DLL i [pam-dao-spring-nhibernate.dll] są ładowane za pomocą narzędzia [NUnit-Gui] w wersji 2.4.6, a następnie przeprowadzane są testy:

Image

Powyżej przedstawiono pomyślnie zakończone testy.

Zadanie praktyczne:


  • zrealizować na komputerze testy klasy [PamDaoSpringNHibernate].
  • należy użyć różnych plików konfiguracyjnych [Dao.xml], aby korzystać z innych serwerów SGBD (Firebird, MySQL, Postgres, SQL)

13.2.4. Generacj a z warstwy [dao]

Po napisaniu i przetestowaniu klasy [PamDaoNHibernate] wygenerujemy klasę DLL z warstwy [dao] w następujący sposób:

  • [1], programy testowe są wykluczone z kompilacji projektu
  • [2,3], konfiguracja projektu
  • [4], generowanie projektu
  • plik DLL jest generowany w folderze [bin/Release] [5]. Dodajemy go do plików DLL już znajdujących się w folderze [lib] [6]:

13.3. Warstwa biznesowa

Wróćmy do ogólnej architektury aplikacji [SimuPaie]:

Zakładamy teraz, że warstwa [dao] została już wdrożona i została enkapsulowana w warstwie DLL [pam-dao-spring-nhibernate.dll]. Teraz skupiamy się na warstwie [metier]. To właśnie ona implementuje reguły biznesowe, w tym przypadku reguły obliczania wynagrodzenia.

Projekt Visual Studio warstwy biznesowej mógłby wyglądać następująco:

  • w [1] cały projekt skonfigurowany przez pliki [App.config] i [Dao.xml]. Plik [App.config] jest identyczny jak w projekcie warstwy [dao] i [pam-dao-spring-nhibernate]. To samo dotyczy pliku [Dao.xml], z tą różnicą, że deklaruje on dodatkowy obiekt Spring o identyfikatorze pammetier. Deklaracja tego ostatniego jest identyczna z tą, która znajdowała się w pliku [App.config] projektu [pam-metier-dao-nhibernate].
  • W pliku [2] folder [pam] jest identyczny z tym, jaki znajdował się w warstwie [metier] projektu [pam-metier-dao-nhibernate]
  • w [3] odwołania wykorzystywane przez projekt. Należy zwrócić uwagę na DLL i [pam-dao-spring-nhibernate] z warstwy [dao], którą analizowaliśmy wcześniej.

Zadanie: skompilować powyższy projekt [pam-metier-dao-spring-nhibernate]. Zostanie on przetestowany oddzielnie:

  • w trybie konsoli przez program konsolowy [Main.cs]

  • za pomocą testu jednostkowego [NUnit.cs] uruchamianego przez framework NUnit

Nowy projekt [pam-metier-dao-spring-nhibernate] można skompilować poprzez proste skopiowanie projektu [pam-metier-dao-nhibernate], a następnie zmodyfikowanie elementów, które wymagają zmian.


Po zakończeniu testów wygenerujemy plik DLL z warstwy [metier], który nazwiemy [pam-metier-dao-spring-nhibernate]:

  • w plik [1], test NUnit zakończył się powodzeniem
  • w [2], DLL wygenerowany przez projekt

Dodamy plik DLL z warstwy [metier] do plików DLL już znajdujących się w folderze [lib] [3]:

13.4. Warstwa [web]

Wróćmy do ogólnej architektury aplikacji [SimuPaie]:

Zakładamy, że warstwy [dao] i [métier] zostały już przygotowane i zawarte w warstwach DLL oraz [pam-dao-spring-nhibernate, pam-metier-dao-spring-nhibernate]. Teraz opiszemy warstwę internetową.

Projekt Visual Web Developer warstwy [web] uzyskuje się najpierw poprzez proste skopiowanie folderu projektu internetowego [pam-v7-3tier-nhibernate-multivues-multipages]. Następnie projekt zmienia się nazwę na [pam-v9-3tier-spring-nhibernate-multivues-multipages]:

Nowy projekt internetowy [pam-v9-3tier-spring-nhibernate-multivues-multipages] różni się od projektu [pam-v7-3tier-nhibernate-multivues-multipages] w następujących aspektach:

  • w projekcie [1] konfigurację zapewniają pliki [Dao.xml] i [Web.config]. Plik [Dao.xml] nie istniał w projekcie [pam-v7], a plik [Web.config] musi uwzględniać konfigurację Spring / NHibernate, podczas gdy w projekcie [pam-v7] konfigurowano jedynie plik NHibernate.
  • W pliku [2] pliki DLL warstw [dao] i [metier] to te, które właśnie utworzyliśmy.

Plik [Dao.xml] jest tym, który wykorzystano do utworzenia warstwy [metier]. Plik [Web.config] to plik warstwy [pam-v7], do którego dodano konfigurację Spring / NHibernate, którą znaleziono w plikach [App.config] warstw [dao] i [metier]. Plik [Web.config] z pliku [pam-v9] ma następującą treść:


<configuration>

  <configSections>
    <sectionGroup name="system.web.extensions" type="System.Web.Configuration.SystemWebExtensionsSectionGroup, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35">
........
    </sectionGroup>
    <sectionGroup name="spring">
      <section name="parsers" type="Spring.Context.Support.NamespaceParsersSectionHandler, Spring.Core" />
      <section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" />
      <section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core" />
    </sectionGroup>
    <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler,log4net" />
  </configSections>

  <!-- konfiguracja Spring -->
  <spring>
    <parsers>
      <parser type="Spring.Data.Config.DatabaseNamespaceParser, Spring.Data" />
    </parsers>
    <context>
      <resource uri="~/Dao.xml" />
    </context>
  </spring>
............. le reste est identique au fichier [Web.config] de [pam-v7]

W wierszach 7–11 i 16–23 znajduje się konfiguracja Spring, którą mieliśmy w plikach [App.config] warstw [dao] i [metier] utworzonych wcześniej, z jedną różnicą: w plikach [App.config] wiersz 17 miał następującą treść:


      <resource uri="Dao.xml" />

Z następującą konfiguracją:

plik [Dao.xml] jest kopiowany do folderu [bin] w folderze projektu internetowego. Z następującą składnią


      <resource uri="Dao.xml" />

plik [Dao.xml] będzie wyszukiwany w bieżącym katalogu procesu uruchamiającego aplikację internetową. Okazuje się, że katalog ten nie jest katalogiem [bin] w katalogu projektu internetowego, który jest uruchamiany. Należy wpisać:


      <resource uri="~/Dao.xml" />

aby plik [Dao.xml] był wyszukiwany w folderze [bin] w katalogu uruchomionego projektu internetowego.


Zadanie: wdrożyć tę aplikację internetową na komputerze.


13.5. Conclusion

Przeszliśmy z architektury:

w architekturze:

Chodziło o zaimplementowanie warstwy [dao], korzystając z możliwości oferowanych przez integrację NHibernate z frameworkiem Spring.

Okazało się, że miało to wpływ na warstwę [dao].

  • wpłynęło na warstwę [dao]. Warstwa ta była prostsza do napisania, ale wymagała bardziej złożonej konfiguracji Springa.
  • miało niewielki wpływ na warstwy [metier] i [web]

Był to kolejny przykład korzyści płynących z architektur warstwowych.