Preprosto preskušanje entitete JPA 2 z Netbeans 7 in Maven
Java EE 6 je stara dobro leto dni, NetBeans 7 Beta je zunaj že kar nekaj časa, čez dva meseca bo zunaj končna različica, Maven pa pridobiva na priljubljenosti in je dobro integriran v današnje IDE, tudi v Netbeansu. Zato sta tokrat naša cilja naslednja:
- ustvariti nov projekt Maven s pomočjo Netbeansov, ki bo preskušal na novo ustvarjeno entiteto (JPA2) prek ogrodja JUnit
- prikazati integracijo med Netbeansi in Mavenom
Uporabili bomo podatkovno bazo PostgreSQL, aplikacijskega strežnika pa za ta namen ne bomo potrebovali. Vse skupaj bomo zaganjali na Windows XP.
Mogoče se kdo sprašuje, kakšen je smisel uporabe JUnit-a nad poljubno entiteto, saj so v njej samo metode tipa setter/getter. Vendar, ker za tovrstno testiranje ne potrebujemo transakcij JTA (torej ne potrebujemo aplikacijskega strežnika) in ker uporabljamo nad podatkovno bazo strategijo “drop and create”, lahko nad obravnavano entiteto brez velikega vložka dela preskušamo vsaj poizvedbe JPQL.
1. Priprava okolja za Maven
Uporabili bomo Apache Maven 3.0.1. Maven je bundlan v Netbeanse, vendar bomo v našem primeru uporabili ločeno inštalacijo. Maven lahko snamete tule: http://maven.apache.org/download.html in ga odpakirate v poljuben direktorij, npr. f:/apache-maven-3.0.1. Pri sami inštalaciji je pomebno to, da nastavimo njegov bin v spremenljivko PATH. Tedaj lahko preverimo, ali je vse v redu:
Po definiciji je Maven rešitev za buildanje projektov (in še veliko več …). Pri svojem delu uporablja datoteko POM – to je datoteka XML, v kateri je opredeljen naš projekt. Projekt običajno potrebuje več t. i. artifaktov – zunanjih modulov, od katerih je odvisen. To so t. i. dependencies, ki so prav tako opredeljene v datoteki POM. Ker je tudi sam projekt nek artifakt, rečemo, da je Maven orodje za upravljanje artifaktov.
Artifakti se sprva nahajajo v oddaljenih repozitorijih in se med buildanjem projekta običajno prenesejo v lokalni repozitorij. Zato Maven potrebuje tudi povezavo do spleta. Privzeta nastavitev za lokalni repozitorij je /Documents and Settings/User/.m2/repository. Ta je v tem trenutku še prazen.
Ker Mavena ne bomo uporabljali command line, moramo v Netbeansih nastaviti določene parametre. Znotraj Tools => Options => Miscellaneous, zavihek Maven, nastavimo pot do nameščenega Mavena in pot do lokalnega repozitorija:
2. Priprava podatkovne baze
Uporabili bomo PostgreSql, kjer bomo ustvarili novo podatkovno bazo po imenu MyAppTest. Test smo dali v ime zato, ker bomo ob zaganjanju aplikacije v tej bazi sproti brisali in ustvarjali tabele.
3. Ustvarjanje projekta
Gremo v Netbeanse in izberemo New Project, kjer potem izberemo Maven => Java Application.
Nato izpolnimo naslednje parametre:
Tako ustvarimo nov projekt glede na archetype: maven-archetype-quickstart:1.1. Prav tako se v lokalni repozitorij naloži nekaj osnovnih potrebnih modulov (npr. maven plugin api). Sedaj smo dobili osnovno strukturo projekta:
Vidimo, da je projekt razdeljen na več delov: del, v katerem so izvorne datoteke, del v katerem so testi, del za (Test) Dependencies in del za datoteko POM, ki v tem trenutku vsebuje naslednje:
<groupId>blog.milanpevec</groupId> <artifactId>MyApp</artifactId> <version>1.0-SNAPSHOT</version> <packaging>jar</packaging> <name>MyApp</name> <url>http://maven.apache.org</url> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.8.2</version> <scope>test</scope> </dependency> </dependencies>
Vidimo, da je edini artifakt junit, namreč ta modul bomo potrebovali za preskušanje. Dodatno pri artifaktu popravimo različico na vrednost 4.8.2, kajti Netbeansi kot privzeto uporabljajo različico 3.
Zaradi uporabe JPA2 dodamo v datoteko še naslednje vrstice:
<build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>2.3.2</version> <inherited>true</inherited> <configuration> <source>1.6</source> <target>1.6</target> </configuration> </plugin> </plugins> </build>
S tem povemo, naj Maven v življenjskem ciklu prevajanja (compile) uporabi plugin za prevajanje Java SE različice 1.6. V nasprotnem bi se nam kasneje pri delu z JPA2 pojavljala opozorila.
4. Izdelava entitete JPA2
Izberemo File => New => Persistance => Entity Class. Obrazec izpolnimo z naslednjimi podatki:
Nastavili smo, da se entiteta po imenu Auser ustvari znotraj paketa blog.milanpevec.entities. Ostalo smo pustili privzeto. Pomembno je to, da spodaj obkljukamo “Create Persistance Unit”, da lahko v naslednjem koraku hkrati opredelimo še PU:
Za t. i. persistance providerja izberemo EclipseLink, izberemo novo povezavo do baze (ali jo ustvarimo, če je še nimamo) in izberemo strategijo “Drop and Create”, saj hočemo, da se ob preskušanju vedno pobriše shema in se na novo ustvarijo bazni objekti.
Zaradi integracije Mavena z Netbeansi so sedaj v datoteki POM samodejno opredeljeni novi artifakti, od katerih je odvisen naš projekt:
<dependency> <groupId>org.eclipse.persistence</groupId> <artifactId>eclipselink</artifactId> <version>2.2.0-M4</version> </dependency> <dependency> <groupId>org.eclipse.persistence</groupId> <artifactId>javax.persistence</artifactId> <version>2.0.0</version> </dependency>
Gre za dva artifakta, ki sta uvrščena v isto skupino (org.eclipse.persistance). Artifakt Eclipselink je definiran zaradi PU, javax.persistance pa zaradi entitete JPA2. Ker bo PU uporabljal JDBC, moramo tudi zanj dodati artifakt:
<dependency> <groupId>postgresql</groupId> <artifactId>postgresql</artifactId> <version>9.0-801.jdbc4</version> </dependency>
Ker omenjenih artifaktov še nimamo v lokalnem repozitoriju, nam Netbeansi prikažejo opozorila:
Zgornje lahko rešimo na več načinov. Lahko bi sprožili build in bi nam takrat naložilo vse potrebne artifakte. Lahko pa iz kontekstnega menija našega projekta izberemo “Show and resolve problems”, “Download Dependencies”:
Vendar je pred tem treba povedati, kje v oddaljenem repozitoriju se nahaja artifakt eclipselink. Netbeansi nam v datoteko POM privzeto nastavijo naslednje:
<repositories> <repository> <url>ftp://ftp.ing.umu.se/mirror/eclipse/rt/eclipselink/maven.repo</url> <id>eclipselink</id> <layout>default</layout> <name>Repository for library Library[eclipselink]</name> </repository> </repositories>
Toda zaradi FTP-ja lahko pride do težave. Zato zgornjo vrstico nadomestimo z naslednjim:
Sedaj Netbeansi sami naložijo manjkajoče artifakte v lokalni repozitorij.
Da bi bilo lažje slediti, kaj se dogaja med postopkom preskušanja, dodamo v PU novo lastnost (znotraj datoteke persistance.xml). Želimo, da se nam izpisujejo vse poizvedbe nad bazo. Zato dodamo lastnost eclipselink.logging.level z vrednostjo FINE:
<?xml version="1.0" encoding="UTF-8"?> <persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"> <persistence-unit name="blog.milanpevec_MyApp_PU" transaction-type="RESOURCE_LOCAL"> <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider> <class>blog.milanpevec.entities.Auser</class> <properties> <property name="javax.persistence.jdbc.url" value="jdbc:postgresql://localhost:5432/MyAppTest"/> <property name="javax.persistence.jdbc.password" value="postgres"/> <property name="javax.persistence.jdbc.driver" value="org.postgresql.Driver"/> <property name="javax.persistence.jdbc.user" value="postgres"/> <property name="eclipselink.ddl-generation" value="drop-and-create-tables"/> <property name="eclipselink.logging.level" value="FINE"/> </properties> </persistence-unit> </persistence>
Iz zgornjega je razvidno, da bomo uporabili transakcije RESOURSE_LOCAL, saj bomo preskušanje zaganjali mimo aplikacijskega strežnika.
Pozor! EclipseLink potrebuje eksplicitno določena imena entitet, s katerimi upravlja, zato moramo dodati zgoraj znotraj <class> še ime entitete. V nasprotnem primeru dobimo “JPA exception: Object: … is not a known entity type.”
5. Izdelava testa
Najprej v našo entiteto dodamo dve lastnosti: firstname in lastname ter določimo, da primarni ključ tabele določa sekvenca auser_id_auser_seq. Prav tako opredelimo t. i. named query, ki poišče vse entitete Auser v podatkovni bazi. To poizvedbo bomo kasneje tudi preskusili.
@Entity @NamedQuery(name=Auser.findAll,query="SELECT a FROM Auser a") public class Auser implements Serializable { public final static String findAll = "entities.Message.findAll"; private static final long serialVersionUID = 1L; @Id @GeneratedValue(generator = "ContSeq") @SequenceGenerator(name = "ContSeq", sequenceName = "auser_id_auser_seq", allocationSize = 1) private Long id; private String firstname; private String lastname; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getFirstname() { return firstname; } public void setFirstname(String firstname) { this.firstname = firstname; } public String getLastname() { return lastname; } public void setLastname(String lastname) { this.lastname = lastname; } }
Sedaj pa si poglejmo, kako bomo sestavili test za našo entiteto. Napisali bomo testno metodo (anotacija @Test), ki se zažene samodejno zaradi omenjene anotacije. Znotraj nje bomo preskusili shranjevanje entitete v podatkovno bazo in edino opredeljeno poizvedbo. Uporabili bomo fixture @Before, ki označuje metodo, ki se zažene pred vsakim testom. V njej bomo implementirali pridobivanje trenutne transakcije. Dodatno bomo uporabili fixture @BeforeClass in @AfterClass, s katerim bomo ustvarili in zaprli naš EntityManager:
public class AuserTest { private static EntityManagerFactory emf; private static EntityManager em; private static EntityTransaction tx; @BeforeClass public static void initEntityManager() throws Exception { emf = Persistence.createEntityManagerFactory("blog.milanpevec_MyApp_PU"); em = emf.createEntityManager(); } @AfterClass public static void closeEntityManager() throws SQLException { em.close(); emf.close(); } @Before public void initTransaction() { tx = em.getTransaction(); } @Test public void createAuser() throws Exception { // Creates an instance of auser Auser auser = new Auser(); auser.setFirstname("Janez"); auser.setLastname("Novak"); // Persists the auser to the database tx.begin(); em.persist(auser); tx.commit(); assertNotNull("ID should not be null", auser.getId()); // Retrieves all users from the database List<Auser> ausers = em.createNamedQuery(Auser.findAllAusers).getResultList(); assertEquals(1, ausers.size()); } }
Uspešno izvedeno preskušanje ima naslednji izhod:
Zgoraj smo tako pokazali, da je mogoče zelo hitro in preprosto pripraviti okolje za preskušanje entitet. Če bi kdo želel celotno kodo projekta, mi lahko piše. Do naslednjič …
March 16, 2011 at 4:06 pm
Zdravo Milan,
Zelo lepo, da se nekdo ukvarja z bloganjem o Enterprise Javi v Slovenscini. Upam se na mnoge poste.
Nekaj namigov (pametovanje 😉 ):
– Entitete, ki so samo shrambe podatkov imajo vonj po anemicnem modelu in prav vabijo k proceduralnem programiranju, kjer nekateri objekti prevzamejo vlogo struktur drugi pa so zgolj zbirke procedur.
– Pri testih, ki delajo s podatkovno bazo je dobra praksa, da se jih poganja v transakcijah, ki se jih po vsakem testu “rollbacka” (v pomanjkanju boljsega izraza). Tako vsi testi pustijo aplikacijo za sabo v zacetnem stanju in so medsebojno neodvisni. Zacetno stanje se lahko inicializira z orodji kot je npr dbunit.
LP,
Dalibor
March 19, 2011 at 8:13 pm
Thnx,
žal nimam več časa, že nekaj časa imam pripravljen primer testiranja aplikacije EE 6 z Glassfish Embeddable in s Arquillian.
Glede komentarjev: super, ti kar piši..
Anemične entitete mi pridejo prav pri SOA, ker jih detacham in prenesem do viewja, ta pa ne rabi videti dodatne logike v entitetah. Pri DDD pa uporabljam neanemične (npr. Query komponenta v SEAMu). Zelo mi je všeč knjiga od Adama Biena, ki se s temle ukvarja..
Glede testiranja pa uporabljam posebno bazo za teste, ki se vedno avtomatsko po-dropa pred testiranjem.