Accés a dades amb Java Enterprise Edition

Quan programeu en Java, la forma més simple per accedir a una base és mitjançant JDBC. Per desgràcia, amb JDBC es necessita una gran quantitat de treball manual per convertir els resultats d’una consulta a la base de dades en classes Java.

En el següent fragment de codi es mostra com transformar un resultat de consulta JDBC en un objecte User:

  1. ResultSet rs = stmt.executeQuery(qry);) {
  2. while (rs.next()) {
  3. int userId = rs.getInt("user_id");
  4. String username = rs.getString("username");
  5. String name = rs.getString("name");
  6. String email = rs.getString("email");
  7. int rank = rs.getInt("rank");
  8. boolean active = rs.getBoolean("active");
  9. Timestamp timestamp = rs.getTimestamp("created_on");
  10. User user = new User(userId, username, name, email, rank, timestamp, active);
  11. users.add(user);
  12. }

Com podeu veure, hi ha molt codi repetitiu per obtenir els resultats dels diferents camps de la base de dades i transformar-los en variables de Java. Penseu que si aquesta classe tingués 30 atributs la quantitat de codi necessària augmentaria considerablement. Aquesta situació seria encara pitjor si a més a més de tenir 30 atributs estigués relacionada amb una altra classe. Per exemple, a l’aplicació “SocIoc” que construireu, un alumne (User) pot crear preguntes. Si la classe que representa les preguntes té 10 atributs, cada cop que fem una consulta on es retornin alumnes com a resultat hauríeu de repetir el codi anterior 40 vegades.

Un altre desavantatge de JDBC és la seva portabilitat. La sintaxi de la consulta canviarà d’una base de dades a una altra. Per exemple, amb la base de dades Oracle la instrucció ROWNUM s’utilitza per limitar la quantitat de resultats retornats, mentre que SqlServer és TOP. El fet d’haver d’utilitzar SQL natiu específic per a cada BD fa que el nostre codi estigui acoblat amb el tipus de BD utilitzat. Això és un problema, ja que si en el futur es vol canviar de base de dades tindrem problemes. Hi ha diverses solucions a aquest tipus de problema, però totes passen per mantenir el codi SQL necessari per a cada tipus de BD; això costarà molt de mantenir i és molt propici a errors.

JPA (Java Persistence API) es va crear com una solució als problemes esmentats anteriorment. JPA permet treballar amb les classes de Java, ja que proporciona una capa transparent que s’encarrega de gestionar els detalls específics per a cada BD i permet focalitzar els esforços de desenvolupament en el codi Java. JPA representa una sèrie d’interfícies Java, així com una sèrie d’estàndards i especificacions que defineixen com han de ser les implementacions. JPA per si sol no farà res, necessitarà d’una implementació per poder ser utilitzada. Hi ha moltes implementacions de JPA disponibles, tant gratuïtes com de pagament; per exemple, Hibernate, OpenJPA i EclipseLink.

La principal característica de JPA és la capacitat d’establir i gestionar les relacions entre les taules de la BD i les classes del codi Java. En Java, l’aplicació es modela través d’objectes, però les bases de dades relacionals només poden emmagatzemar valors escalars, com cadenes de caràcters o enters, i organitzar-los en taules. Sense JPA, tal com hem vist a l’exemple de JDBC, és el programador qui ha de convertir els valors representats en forma d’objectes en valors simples o agrupats per poder-los emmagatzemar a la BD, així com implementar el procés invers per poder extreure les dades. JPA defineix com s’ha de resoldre aquest problema i defineix com s’ha de fer el mapatge d’objectes relacionals (ORM Object-Relational Mapping).

Posarem les bases de l’aprenentatge configurant l’aplicació Java “SocIoc”. Explicarem:

  • Creació de la base de dades “SocIoc” a partir d’un model de MySQL Workbench
  • Notacions JPA
  • Tipus de dades a la BD i correspondència amb Java
  • Persistence units
  • Entorn Glassfish configurant el datasource i connection pool
  • Validació
  • Tests unitaris
  • Utilitzar la base de dades en memòria H2 per escriure tests unitaris

"SocIoc". Dialogant amb clients amb JPA

El primer que haureu de fer serà importar la base de dades “SocIoc”, tal com es mostra en la figura.

Podeu trobar la base de dades “SocIoc” al fitxer de MySQL Workbench que teniu disponible als annexos de la unitat.

Figura Importar model de MySQL Workbench

Un cop importat, s’haurà d’haver importat el model entitat relació de la figura. El model ER de la base de dades “SocIoc” es compon de les següents taules:

  • rank: serveix per guardar la puntuació que aconsegueix un alumne en base a les votacions que reben les respostes que dóna.
  • questions: conté les preguntes dels alumnes.
  • answers: guarda les respostes.
  • votes: els alumnes que llegeixen una resposta la podran votar de forma positiva o negativa.
  • users: guarda informació dels alumnes.

Figura Model ER “SocIoc”

A continuació haureu de connectar-vos al vostre servidor MySQL per tal de crear les taules que hem definit al model ER “SocIoc”. El primer pas és crear la base de dades al servidor MySQL. Hi ha diferents formes de fer-ho, però en aquest exemple utilitzarem el client de la línia de comandes. En el cas del servidor que s’està fent servir, aquestes són les dades de connexió:

  • usuari: root
  • contrasenya: root
  • IP del servidor: 192.168.99.100
  • port: 32769
  1. mysql - -user=root - -password=root - -host=192.168.99.100 - -port=32769

Utilitzant Mysql Workbench, creeu una connexió amb el vostre servidor MySQL. Per fer-ho, seleccioneu Manage connections, tal com podeu veure en la figura.

Figura Manage connections

A continuació afegiu les dades de connexió. En la figura podeu veure les dades de connexió al servidor local de MySQL.

Figura Crear connexió amb DB

Un cop està configurada la connexió ja es pot passar a exportar el model al servidor. Seleccioneu l’opció Forward Engineering del menú Database (vegeu la figura).

Figura Exportar taules al servidor

Seleccioneu la connexió que heu creat (vegeu la figura); en fer clic a Continue us demanarà la contrasenya per accedir a la BD.

Figura Seleccioneu la connexió amb la BD

Quant a les opcions, podeu deixar les que hi ha per defecte (vegeu la figura).

Figura Opcions d’exportació

Com que només heu creat taules (vegeu la figura), només fa falta seleccionar la primera de les opcions, que exportarà les taules.

Figura Opcions d’exportació

Els dos últims passos són una revisió dels scripts de creació dels objectes seleccionats (en el nostre cas, les taules). Si el procés de creació és correcte haureu de veure un missatge de confirmació com el de la figura.

Figura Confirmació de la correcta exportació de les taules

Per comprovar que les taules s’han creat adequadament podeu utilitzar l’editor SQL de Workbench. Seleccioneu del menú Database l’opció Query database; després d’escollir la connexió amb la vostra BD accedireu a l’editor (vegeu la figura) i podreu executar una consulta perquè es mostrin totes les taules de la BD.

Figura Editor SQL

Annotacions JPA

Recordeu que JPA defineix com s’ha de fer el mapatge d’objectes relacionals (ORM Object-Relational Mapping) amb la base de dades. D’aquesta forma, quan desenvolupeu una aplicació, com a programadors us podeu centrar en el codi Java i deixar que JPA s’encarregui dels detalls específics de la base de dades. Començareu creant una classe que representi un usuari de la nostra aplicació i que es pugui utilitzar per emmagatzemar dades.

Per fer això creareu una classe i l’anotareu amb l’anotació @Entity. Això transformarà una classe Java simple (POJO, Plain Old Java Object) en una EJB (Enterprise Java Bean). L’ús d’EJB permet gaudir dels serveis prestats pels servidors Java Enterprise Edition (Java EE). Hi ha diferents serveis que proporcionen el seu ús, com clustering, transaccionalitat a través de JTA, seguretat i connexions amb base de dades. A més de l’anotació @Entity hi ha altres anotacions que permeten definir quins atributs de la classe seran persistents i a quina columna de la taula seran emmagatzemats.

El fitxer de partida per fer annotacions JPA el trobareu als annexos de la unitat.

Un cop descarregat i descomprimit el fitxer de partida, ja el podeu importar; el nom del projecte és “UDF4-02”. En la figura es mostra on és l’opció per obrir un projecte existent.

Figura Editor SQL

Netbeans, en detectar el fitxer pom.xml, reconeixerà que és un projecte Maven i començarà a descarregar les dependències.

En el fitxer pom.xml de Maven, les úniques dependències que us fan falta són la del driver de la BD H2, de JUnit, javax.persistence i javax.validation. javax.persistence conté les llibreries amb totes les classes que necessitareu per treballar amb JPA. Encara que amb les llibreries de javax.persistence no necessitaríeu res més, javax.validation us aporta una sèrie d’anotacions que seran molt útils a l’hora de validar les dades que emmagatzemareu a la base de dades.

  1. <dependencies>
  2. <dependency>
  3. <groupId>com.h2database</groupId>
  4. <artifactId>h2</artifactId>
  5. <version>1.4.190</version>
  6. </dependency>
  7. <dependency>
  8. <groupId>junit</groupId>
  9. <artifactId>junit</artifactId>
  10. <version>4.12</version>
  11. </dependency>
  12. <dependency>
  13. <groupId>javax.persistence</groupId>
  14. <artifactId>persistence-api</artifactId>
  15. <version>1.0.2</version>
  16. </dependency>
  17. <dependency>
  18. <groupId>javax.validation</groupId>
  19. <artifactId>validation-api</artifactId>
  20. <version>1.1.0.Final</version>
  21. </dependency>
  22. </dependencies>

Com a programadors d’aplicacions orientades a objectes, el que us interessa és treballar amb objectes que representin les dades, més que haver de treballar directament amb SQL. JPA permet això, utilitza ORM (Object Relational Mapping) per emmagatzemar i recuperar dades de la base de dades a través de l’ús de classes de tipus entitat (entity classes). Mitjançant l’anotació @Entity es transforma una classe Java simple (POJO, Plain Old Java Object) en una EJB (Enterprise Java Bean). L’ús d’EJB permet gaudir dels serveis prestats pels servidors Java Enterprise Edition (Java EE). Hi ha diferents serveis que proporciona el seu ús, com clustering, transaccionalitat a través JTA, seguretat i connexions amb base de dades. A més de l’anotació @Entity hi ha altres anotacions que permeten definir quins atributs de la classe seran persistents i a quina columna de la taula seran emmagatzemats. En el següent codi podeu veure les diferents anotacions que farem servir.

  1. @Table(name = "users")
  2. public class User implements Serializable{
  3. private static final long serialVersionUID = 1L;
  4. @Id
  5. @NotNull
  6. @Column(name = "user_id")
  7. private Long userId;
  8.  
  9. @NotNull
  10. @Size(max = 30)
  11. @Column(name = "username")
  12. private String username;
  13.  
  14. @NotNull
  15. @Size(max = 30)
  16. @Column(name = "name")
  17. private String name;
  18.  
  19. @NotNull
  20. @Size(max = 30)
  21. @Column(name = "email")
  22. private String email;
  23.  
  24. @NotNull
  25. @Size(max = 30, min = 5)
  26. @Column(name = "password")
  27. private String password;
  28.  
  29. @NotNull
  30. @Column(name = "rank")
  31. private Integer rank;
  32.  
  33. @NotNull
  34. @Column(name = "active")
  35. private Boolean active;
  36.  
  37. @NotNull
  38. @Column(name = "created_on")
  39. private Timestamp createdOn;
  40.  
  41. public User(){}
  42.  
  43. public Long getUserId() {
  44. return userId;
  45. }
  46.  
  47. public void setUserId(Long userId) {
  48. this.userId = userId;
  49. }
  50.  
  51. public String getUsername() {
  52. return username;
  53. }
  54.  
  55. public void setUsername(String username) {
  56. this.username = username;
  57. }
  58.  
  59. public String getName() {
  60. return name;
  61. }
  62.  
  63. public void setName(String name) {
  64. this.name = name;
  65. }
  66.  
  67. public String getEmail() {
  68. return email;
  69. }
  70.  
  71. public void setEmail(String email) {
  72. this.email = email;
  73. }
  74.  
  75. public String getPassword() {
  76. return password;
  77. }
  78.  
  79. public void setPassword(String password) {
  80. this.password = password;
  81. }
  82.  
  83. public Integer getRank() {
  84. return rank;
  85. }
  86.  
  87. public void setRank(Integer rank) {
  88. this.rank = rank;
  89. }
  90.  
  91. public Boolean getActive() {
  92. return active;
  93. }
  94.  
  95. public void setActive(Boolean active) {
  96. this.active = active;
  97. }
  98.  
  99. public Timestamp getCreatedOn() {
  100. return createdOn;
  101. }
  102.  
  103. public void setCreatedOn(Timestamp createdOn) {
  104. this.createdOn = createdOn;
  105. }
  106. }

L’anotació @Entity indica que serà una classe de tipus entitat i l’anotació @Table permet indicar quin és el nom de la taula que estarà lligada a aquesta classe. Un altre aspecte que és important destacar és que és sempre una bona idea fer que una classe de tipus entitat sigui serialitzable, per tal que la classe pugui ser passada per valor i no per referència. Això s’aconsegueix fent que la classe implementi la interfície java.io.Serializable i afegint l’atribut estàtic serialVersionUID. Sense entrar en molts detalls, serialVersionUID s’utilitza per comprovar que els objectes serialitzats i desserialitzats són compatibles.

Una classe del tipus entitat necessita tenir un constructor sense arguments i atributs per a cada una de les columnes de la taula. Les anotacions JPA que hem utilitzat les podeu veure en la taula.

Taula Annotacions JPA
Anotació Descripció
@Entity Transforma un POJO en una classe de tipus entitat per tal de poder utilitzar-la amb els serveis JPA.
@Table (opcional) Especifica el nom de la taula de la base de dades associada amb l’entitat.
@Id Designa un o més dels atributs de l’entitat com a la clau principal de la taula.
@Column Associa un atribut que es vol emmagatzemar amb el nom d’una columna de la taula.

Hi ha altres anotacions que hem utilitzat i que no apareixen en la llista. Intenteu contestar a les següents preguntes abans de continuar:

  • Quines són les anotacions que hem utilitzat i que no pertanyen a l’API de JPA?
  • A quina API pertanyen?
  • Per a què creieu que serveixen?

Les anotacions que no són part de JPA i que hem utilitzat (taula) són part de l’API javax.validation i serveixen per validar les dades que guardarem a la base de dades abans d’intentar guardar-les.

Taula Annotacions de validació
Anotació Descripció
@Size Permet especificar la longitud màxima i/o mínima d’una variable de tipus string.
@NotNull Estableix que l’atribut not pot tenir un valor null.

Com ja hem vist, JPA és una especificació que indica com es transformen classes en entitats i com aquestes es poden fer servir per llegir i escriure en una base de dades. És una especificació però no una implementació, és a dir, defineix com han de ser les operacions però no les implementa. Això vol dir que amb l’exemple de la classe User que hem vist no podem fer cap operació amb la base de dades. Per poder fer-ho hi ha dues opcions: utilitzar Persistence Units (unitats de persistència) o emprar Enterprise Java Beans, que requerirà de l’ús d’un servidor d’aplicacions com pot ser Glassfish.

Unitats de persistència

Les unitats de persistència són les peces que enllacen l’entitat que hem definit amb la base de dades. Poden usar un pool de connexions amb la BD definides al servidor d’aplicacions o, com en l’exemple següent, una connexió JDBC. El que es aconseguir és crear una unitat de persistència que pugueu utilitzar en els tests unitaris. Obriu el codi que crearà el projecte UDF4-02. Al fitxer src/test/resources/META-INF/persistence.xml trobareu la configuració de la unitat de persistència.

Als annexos de la unitat trobareu l’arxiu amb el codi per crear el projecte UDF4-02.

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
  5. http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
  6. <persistence-unit name="InMemoryH2PersistenceUnit" transaction-type="RESOURCE_LOCAL">
  7. <class>org.ioc.daw.user.User</class>
  8. <properties>
  9. <property name="javax.persistence.jdbc.driver" value="org.h2.Driver"/>
  10. <property name="javax.persistence.jdbc.user" value="username"/>
  11. <property name="javax.persistence.jdbc.password" value="password"/>
  12. <property name="javax.persistence.jdbc.url" value="jdbc:h2:mem:socioc_db"/>
  13. </properties>
  14. </persistence-unit>
  15. </persistence>

El fitxer XML que defineix la unitat de persistència és on resideix informació que necessita JPA per saber com connectar-se a la BD. Aquest fitxer pot contenir la configuració de com connectar-se a múltiples BD. Cadascuna d’aquestes configuracions indicarà el tipus de transacció que s’utilitzarà a l’hora de fer les operacions amb la BD que serà JTA o RESOURCE_LOCAL.

A persistence-unit name= s’indica el nom de la unitat de persistència, que serà utilitzat a l’aplicació per obtenir una referència a la configuració que defineix com ens connectem a la BD. El tipus de transacció indica si s’utilitzarà Java Transaction API (JTA) entity managers (gestors d’entitats) per ser utilitzats en un servidor d’aplicacions o bé entity managers locals que no necessiten de cap servidor d’aplicacions, i que és el tipus que us interessa per als vostres tests unitaris.

<class> permet definir quines classes (en el vostre cas només n’hi ha una, de moment) seran mapejades amb la BD. Després es defineixen els elements que permeten la connexió amb la BD. En aquest cas, nom d’usuari i contrasenya per accedir a la BD i l’string de connexió que defineix com accedir a la base de dades.

Un cop heu establert com es farà la connexió amb la base de dades, ja podeu escriure un test unitari.

  1. public class UserTest {
  2. private EntityManager entityManager;
  3. private EntityTransaction entityTransaction;
  4.  
  5. @Before
  6. public void setUp() {
  7. entityManager = Persistence.createEntityManagerFactory("InMemoryH2PersistenceUnit").createEntityManager();
  8. entityTransaction = entityManager.getTransaction();
  9. }
  10.  
  11. @After
  12. public void cleanUp() {
  13. entityManager.close();
  14. }
  15.  
  16. @Test
  17. public void findAllUsers(){
  18.  
  19. User user = new User();
  20. user.setActive(true);
  21. user.setCreatedOn(new Timestamp(new Date().getTime()));
  22. user.setEmail("test@test.com");
  23. user.setName("Jane");
  24. user.setPassword("password");
  25. user.setRank(100);
  26. user.setUsername("jdoe");
  27. user.setUserId(23L);
  28.  
  29. entityTransaction.begin();
  30. entityManager.persist(user);
  31. entityTransaction.commit();
  32.  
  33. Query query = entityManager.createNativeQuery("select * from users", User.class);
  34. List<User> userList = query.getResultList();
  35. Assert.assertEquals(1, userList.size());
  36. Assert.assertEquals("jdoe", userList.get(0).getUsername());
  37. }
  38.  
  39. }

Amb Persistence.createEntityManagerFactory definiu quina serà la unitat de persistència que necessitareu per crear l‘EntityManager. Després creeu un objecte per fer transaccions amb la BD; és part del mètode setUp, perquè aquest objecte l’utilitzareu en tots els tests. Al test findAllUsers primer creeu un objecte User (que, recordeu, serà una entitat), inicieu la transacció amb la BD, salveu l’objecte amb entityManager.persist(user); i finalment dieu a l’objecte que gestiona les transaccions que apliqui tots els canvis a la BD. Fixeu-vos que no serà fins a entityTransaction.commit() que els canvis s’escriuran a la base de dades. Finalment, el test comprova que a la taula “Users” de la BD només hi ha una entrada i que correspon a l’usuari que acabeu de crear. Executeu el test i obtindreu el següent error:

javax.persistence.PersistenceException: No resource files named META-INF/services/javax.persistence.spi.PersistenceProvider were found. Please make sure that the persistence provider jar file is in your classpath.

  at javax.persistence.Persistence.findAllProviders(Persistence.java:167)

Quin és el motiu pel qual teniu aquest error?

Com ja heu vist, JPA només és una especificació, però no una implementació. L’error vol dir exactament això: quan s’intenta executar el codi no es troba cap llibreria (PersistenceProvider) que implementi JPA, per la qual cosa resulta impossible fer cap operació amb la base de dades. Ho solucionareu utilitzant la implementació EclipseLink. No entrarem en detalls sobre EclipseLink, ja que més endavant utilitzareu Hibernate com a implementació.

Per incloure EclipseLink al nostre projecte, el primer serà modificar el fitxer de dependències pom.xml.

  1. <dependency>
  2. <groupId>org.eclipse.persistence</groupId>
  3. <artifactId>eclipselink</artifactId>
  4. <version>2.5.0</version>
  5. </dependency>

A continuació haureu de modificar la unitat de persitència per indicar quina implementació utilitzareu:

  1. <persistence-unit name="InMemoryH2PersistenceUnit" transaction-type="RESOURCE_LOCAL">
  2. <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
  3. <class>org.ioc.daw.user.User</class>
  4. <properties>
  5. <property name="javax.persistence.jdbc.driver" value="org.h2.Driver"/>
  6. <property name="javax.persistence.jdbc.user" value="username"/>
  7. <property name="javax.persistence.jdbc.password" value="password"/>
  8. <property name="javax.persistence.jdbc.url" value="jdbc:h2:mem:socioc_db;INIT=runscript from 'classpath:init.sql'"/>
  9. <property name="javax.persistence.schema-generation.database.action" value="drop-and-create"/>
  10. <property name="javax.persistence.schema-generation.create-source" value="metadata"/>
  11. <property name="javax.persistence.schema-generation.drop-source" value="metadata"/>
  12. </properties>
  13. </persistence-unit>

A la línia 2 s’ha afegit quina serà la implementació del PersistenceProvider. A la línia 8, on s’indica l’string de connexió, afegim també que s’executi un script on creareu la taula “Users”, que utilitzareu per emmagatzemar els usuaris del nostre sistema. El fitxer src/test/resources/init.sql té el següent contingut:

  1. CREATE TABLE users(user_id INT PRIMARY KEY AUTO_INCREMENT NOT NULL,
  2. username VARCHAR(30) NOT NULL,
  3. name VARCHAR(20) NOT NULL,
  4. email VARCHAR(50) NOT NULL,
  5. password VARCHAR(50) NOT NULL,
  6. rank INT DEFAULT 0,
  7. active BOOLEAN DEFAULT TRUE,
  8. created_on TIMESTAMP AS CURRENT_TIME)

Torneu a executar el test findAllUsers, que aquest cop hauria de passar. Un problema d’aquest codi és que la lògica per emmagatzemar i recuperar usuaris està al test. És important que aquest codi estigui a la seva pròpia classe per tal que es pugui utilitzar en tota l’aplicació. Creareu una classe, UserService, que s’encarregarà d’aquesta funció i que tindrà la lògica necessària per guardar un usuari a la base de dades, modificar-lo i buscar usuaris pel seu nom. Començareu creant una interfície que definirà les operacions relacionades amb la BD. Programar utilitzant interfícies té avantatges, com heu pogut veure amb JPA. Es defineix una interfície i després es pot canviar la implementació; així, si canviéssiu de BD i per exemple escollíssiu una BD NoSQL on no es pugui utilitzar JPA només hauríeu de preocupar-vos de canviar la implementació. Definiu la interfície org.ioc.daw.user.UserDAO que us permeti guardar usuaris a la BD, esborrar-los, modificar-los i trobar un usuari pel seu nom.

  1. public interface UserService {
  2. public void create(User user);
  3. public void edit(User user);
  4. public void remove(User user);
  5. public User findUserByUsername(String username);
  6. }

A continuació creeu la seva implementació:

  1. public class UserServiceImpl implements UserService {
  2. private EntityManager entityManager;
  3.  
  4. public UserServiceImpl(EntityManager entityManager) {
  5. this.entityManager = entityManager;
  6. }
  7.  
  8. @Override
  9. public void create(User user) {
  10. entityManager.persist(user);
  11. }
  12.  
  13. @Override
  14. public void edit(User user) {
  15. entityManager.merge(user);
  16. }
  17.  
  18. @Override
  19. public void remove(User user) {
  20. entityManager.remove(user);
  21. }
  22.  
  23. @Override
  24. public User findUserByUsername(String username) {
  25. return (User) entityManager.createQuery("select object(o) from User o " +
  26. "where o.username = :username")
  27. .setParameter("username", username)
  28. .getSingleResult();
  29. }
  30. }

Els mètodes create, remove i edit defineixen les operacions JPA per guardar, actualitzar i esborrar entitats de la BD. La part més interessant és la del mètode findUserByUsername. Utilitzeu createQuery, i en aquest cas feu servir una consulta parametritzada on indiqueu la classe de l’objecte que retornarà la consulta, el paràmetre que passareu (username) i que només retornarà un resultat, ja que només podeu tenir un usuari amb un nom determinat. El test el podeu implementar de la següent manera (en aquest cas emmagatzemeu dos usuaris):

  1. public class UserServiceTest {
  2. private EntityManager entityManager;
  3. private EntityTransaction entityTransaction;
  4. private UserService userService;
  5.  
  6. @Before
  7. public void setUp() {
  8. entityManager = Persistence.createEntityManagerFactory("InMemoryH2PersistenceUnit").createEntityManager();
  9. userService = new UserServiceImpl(entityManager);
  10. entityTransaction = entityManager.getTransaction();
  11. }
  12.  
  13. @After
  14. public void cleanUp() {
  15. entityManager.close();
  16. }
  17.  
  18. @Test
  19. public void findAllUsers(){
  20. String username = "jdoe";
  21. User user = new User();
  22. user.setActive(true);
  23. user.setCreatedOn(new Timestamp(new Date().getTime()));
  24. user.setEmail("test@test.com");
  25. user.setName("Jane");
  26. user.setPassword("password");
  27. user.setRank(100);
  28. user.setUsername(username);
  29. User user1 = new User();
  30. user1.setActive(true);
  31. user1.setCreatedOn(new Timestamp(new Date().getTime()));
  32. user1.setEmail("test1@test.com");
  33. user1.setName("Joe");
  34. user1.setPassword("password");
  35. user1.setRank(100);
  36. user1.setUsername("joeTest");
  37.  
  38. entityTransaction.begin();
  39. userService.create(user);
  40. userService.create(user1);
  41. entityTransaction.commit();
  42.  
  43. User userFromDB = userService.findUserByUsername(username);
  44. Assert.assertNotNull(userFromDB);
  45. Assert.assertEquals("jdoe", userFromDB.getUsername());
  46. Assert.assertEquals("test@test.com", userFromDB.getEmail());
  47. Assert.assertNotNull(userFromDB.getUserId());
  48. }
  49. }

Fixeu-vos que en aquest test no heu donat valor a userId, però tal com testeja Assert.assertNotNull(userFromDB.getUserId()); sí que té un valor. Quina anotació JPA s’ha d’afegir a l’atribut userId de la classe User que generarà automàticament un valor? La resposta és GeneratedValue.

  1. @Id
  2. @NotNull
  3. @GeneratedValue
  4. @Column(name = "user_id")
  5. private Long userId;

Servidor d'aplicacions Glassfish

Quan vulgueu desplegar la vostra aplicació, utilitzar EclipseJPA com a implementació de JPA no serà suficient. Necessiteu un servidor d’aplicacions que permeti desplegar aplicacions Java EE. Glassfish és el servidor d’aplicacions de referència per a aplicacions Java EE i inclou les tecnologies EJB, JPA, JSF (Java Server Faces) i JMS (Java Messaging System). Netbeans inclou per defecte un servidor Glassfish. A la pestanya Serveis, tal com podeu veure en la figura, ja hi ha un servidor Glassfish configurat.

En algunes versions del servidor Glassfish hi ha un bug no solucionat en afegir un pool de connexions. Utilitzeu la versió 4.0 o superior, que podeu descarregar de: glassfish.java.net/ download-archive.html.

Figura Glassfish

Arranqueu el servidor; després d’uns segons estarà funcionant, i si feu clic a Resources veureu que hi ha dues carpetes, JDBC Resources i Connection Pools (vegeu la figura).

Figura ‘Resources and connection pools’

'Pool' de connexions

Un pool de connexions és un sèrie de connexions amb la BD que es guarden en cache i que poden ser reutilitzades per diferents consultes que es facin a la BD. El motiu d’utilitzar-les és que milloren notablement el rendiment de l’aplicació i la fan molt més ràpida. La gestió d’obrir i tancar connexions amb al BD cada cop que es necessiti guardar o recuperar dades és molt costosa, especialment en aplicacions dinàmiques on el contingut es genera dinàmicament a partir de les dades de la BD. La forma de funcionar és que quan es crea una connexió es posa al pool, de manera que quan es torni a necessitar la connexió no s’ha de tornar a establir. El que volem fer és establir un pool de connexions amb la base de dades MySQL. Per fer-ho, un cop el servidor Glassfish ha arrencat, accediu a la consola d’administració (vegeu la figura).

Figura ‘Resources and connection pools’

La consola d’administració és una aplicació web, així que s’obrirà una finestra al vostre navegador. Navegueu a Resources/JDBC/JDBC Connection Pools i veureu els pools de connexions creats per defecte (vegeu la figura).

Figura ‘Resources and connection pools’

Feu clic a New i podreu crear el pool de connexions. Es fa en dues parts. En la primera part es dóna nom al pool de connexions i se selecciona el tipus de recurs i el driver que s’utilitzarà per connectar-se amb la base de dades (vegeu la figura).

Figura Configuració del ‘pool’ de connexions I

A continuació es configuren els paràmetres del pool de connexions (vegeu la figura). Els que hi ha per defecte ja aniran bé, així que no els canviareu. Cal destacar els següents:

  • Initial and Minimum Pool Size: nombre mínim de connexions amb la BD que mantindrem al pool.
  • Maximum Pool Size: nombre màxim de connexions simultànies que mantindrem al pool.
  • Pool Resize Quantity: nombre de connexions que es trauran del pool quan no hi hagi activitat durant més del temps establert pel paràmetre Idle Timeout.
  • Idle Timeout: temps màxim que una connexió pot estar inactiva.

Figura Configuració del ‘pool’ de connexions II

Els únics valors que heu de canviar són els relatius al nom de la base de dades, l’usuari, la contrasenya, la IP i el port que utilitza el servidor MySQL, que trobareu al final de la pàgina web (vegeu la figura).

Figura Configuració del ‘pool’ de connexions III

Un cop configurat, assegureu-vos que la connexió amb la BD funciona utilitzant el botó Ping. Si obteniu l’error mostrat en la figura és perquè el servidor Glassfish no pot accedir al driver de connexió amb MySQL.

Figura Connector MySQL no trobat

En aquest cas feu el següent:

  1. Descarregueu-vos el connector de dev.mysql.com/downloads/connector/ j/5.1.html.
  2. Descomprimiu-lo i copieu l’arxiu mysql-connector-java-5.1.40-bin.jar a /instalacio-del-servidor-glassfish/glassfish-4.1/glassfish/domains/domain1 /lib/ . Glassfish estarà instal·lat en el directori on hagueu instal·lat Netbeans. Per veure on està instal·lat exactament mireu les propietats del servidor Glassfish (Installation Location) (vegeu la figura i la figura).

Figura Directori d’instal·lació de Glassfish

Figura Directori d’instal·lació de Glassfish

Recursos JDBC

Un recurs JDBC proporciona a una aplicació la forma de connectar-se a una base de dades. Típicament, l’administrador del servidor Glassfish crearà un recurs JDBC que establirà quin dels pools de connexions utilitzarà l’aplicació. Cada recurs JDBC creat al servidor d’aplicacions tindrà un únic identificador JNDI (Java Naming and Directory Interface). JNDI és un servei ofert pels servidors d’aplicacions Java EE que permet als clients (en aquest cas, l’aplicació que estem desenvolupant) descobrir serveis i objectes utilitzant un nom. L’objecte que la vostra aplicació descobrirà serà l’objecte que representa el pool de connexions. A l’hora d’escollir el nom per al recurs JDBC es pot triar qualsevol, però per convenció s’utilitza jdbc/nom_del_recurs_pm. Per crear un recurs JDBC aneu a la consola d’administració de Glassfish i navegueu a Resources/JDBC/JDBC Resources. Amb el botó New, creeu un nou recurs i afegiu-hi les dades, tal com podeu veure en la figura.

Figura Creació d’un recurs JDBC

Si refresqueu la informació del servidor Glassfish a Netbeans hauríeu de veure tant el recurs JDBC creat com el pool de connexions (vegeu la figura).

Figura Recurs JDBC i ‘pool’ de connexions

Connectar l’aplicació "SocIoc" amb el recurs JDBC

Un cop ja teniu el pool de connexions i el recurs JDBC creat, ja podeu fer que la vostra aplicació els utilitzi. Per fer-ho haureu de definir una unitat de persistència que especifiqui el recurs JDBC que utilitzareu. A partir del codi , importeu el projecte UDF4-02 i editeu el fitxer persistence.xml i amb el següent contingut:

Trobareu el codi per connectar l’aplicació “SocIoc” amb el recurd JDBC als annexos de la unitat.

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
  5. http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
  6. <persistence-unit name="InMemoryH2PersistenceUnit" transaction-type="RESOURCE_LOCAL">
  7. <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
  8. <class>org.ioc.daw.user.User</class>
  9. <properties>
  10. <property name="javax.persistence.jdbc.driver" value="org.h2.Driver"/>
  11. <property name="javax.persistence.jdbc.user" value="username"/>
  12. <property name="javax.persistence.jdbc.password" value="password"/>
  13. <property name="javax.persistence.jdbc.url" value="jdbc:h2:mem:socioc_db;INIT=runscript from 'classpath:init.sql'"/>
  14. <property name="javax.persistence.schema-generation.database.action" value="drop-and-create"/>
  15. <property name="javax.persistence.schema-generation.create-source" value="metadata"/>
  16. <property name="javax.persistence.schema-generation.drop-source" value="metadata"/>
  17. </properties>
  18. </persistence-unit>
  19. <persistence-unit name="MysqlResourceSocIocPersistence" transaction-type="JTA">
  20. <jta-data-source>jdbc/socioc__pm</jta-data-source>
  21. <class>org.ioc.daw.user.User</class>
  22. <properties>
  23. <property name="eclipselink.logging.level" value="FINEST"/>
  24. </properties>
  25. </persistence-unit>
  26. </persistence>

Hi ha diversos canvis. El primer és que la definició de la nova unitat de persistència MysqlResourceSocIocPersistence és molt més simple. Penseu que ara tots els detalls de la connexió estan gestionats per Glassfish, així que vosaltres només us heu de preocupar d’indicar quin serà el recurs JDBC que utilitzareu. L’única propietat que s’ha afegit a la línia 23 és eclipselink.logging.level, que indica el nivell de logging de l’aplicació i que serà útil per solucionar problemes. Al test unitari anterior, la gestió de les transaccions era part del test.

  1. entityTransaction.begin();
  2. userService.create(user);
  3. userService.create(user1);
  4. entityTransaction.commit();

Això no hauria de formar part del test, ja que el que us interessa és que sigui la classe que fa les consultes a la BD la que s’encarregui de gestionar les transaccions. A més a més, en utilitzar un servidor d’aplicacions les vostres classes seran ara entitats de ple dret, per la qual cosa es pot aprofitar per simplificar el codi utilitzant anotacions. Per fer-ho haureu d’afegir la següent dependència al fitxer pom.xml.

  1. <dependency>
  2. <groupId>javax.ejb</groupId>
  3. <artifactId>javax.ejb-api</artifactId>
  4. <version>3.2</version>
  5. </dependency>

Vegeu com s’ha de modificar la classe UserServiceImpl:

  1. @Stateless
  2. @TransactionManagement(TransactionManagementType.BEAN)
  3. public class UserServiceImpl implements UserService {
  4. @PersistenceContext(unitName = "MysqlResourceSocIocPersistence")
  5. private EntityManager entityManager;
  6.  
  7. @Resource
  8. private EJBContext context;
  9.  
  10. @Override
  11. public void create(User user) {
  12. UserTransaction utx = context.getUserTransaction();
  13. try {
  14. utx.begin();
  15. entityManager.persist(user);
  16. utx.commit();
  17. } catch (Exception e) {
  18. e.printStackTrace();
  19. try {
  20. utx.rollback();
  21. } catch (Exception e1) {
  22. e1.printStackTrace();
  23. }
  24. }
  25. }
  26.  
  27. @Override
  28. public void edit(User user) {
  29. UserTransaction utx = context.getUserTransaction();
  30. try {
  31. utx.begin();
  32. entityManager.merge(user);
  33. utx.commit();
  34. } catch (Exception e) {
  35. try {
  36. utx.rollback();
  37. } catch (Exception e1) {
  38. e1.printStackTrace();
  39. }
  40. e.printStackTrace();
  41. }
  42. }
  43.  
  44. @Override
  45. public void remove(User user) {
  46. UserTransaction utx = context.getUserTransaction();
  47. try {
  48. utx.begin();
  49. entityManager.remove(user);
  50. utx.commit();
  51. } catch (Exception e) {
  52. try {
  53. utx.rollback();
  54. } catch (Exception e1) {
  55. e1.printStackTrace();
  56. }
  57. e.printStackTrace();
  58. }
  59. }
  60.  
  61. @Override
  62. public User findUserByUsername(String username) {
  63. return (User) entityManager.createQuery("select object(o) from User o " +
  64. "where o.username = :username")
  65. .setParameter("username", username)
  66. .getSingleResult();
  67. }

L’anotació Stateless transforma un POJO en una EJB de sessió que adquireix la capacitat d’executar operacions en un servidor d’aplicacions (Glassfish, en el vostre cas). TransactionManagement és necessària per permetre fer operacions amb la BD a través del servidor d’aplicacions. En aquest cas, farà que hi hagi disponible al context de EJB (EJBContext) la classe UserTransaction, que utilitzareu per indicar quan comencen i acaben les transaccions amb la BD. L’anotació PersistenceContext permet declarar quina serà la unitat de persistència que utilitzareu per indicar com l’EJB UserServiceImpl s’ha de connectar amb la BD. Finalment, a cadascun dels mètodes que s’encarreguen de modificar dades a la BD utilitzeu UserTransaction per establir quan comencen i acaben les transaccions. Hi ha un mètode, utx.rollback(), que desfarà les dades modificades a la BD en cas que hi hagi un error.

A continuació cal modificar el vostre test unitari. Hi ha un problema: volem que el test utilitzi el recurs JDBC que heu creat al servidor Glassfish. Això vol dir que el vostre test ha de poder accedir a les EJB a Glassfish. Per aconseguir-ho utilitzareu un servidor Glassfish-embedded, que s’iniciarà durant els tests i permetrà accedir als recursos JDBC i EJB del servidor real Glassfish. El primer pas és afegir unes dependències al fitxer pom.xml. Fixeu-vos que heu de substituir DIRECTORI_INSTALACIO_NETBEANS pel directori on tingueu instal·lat el servidor Glassfish (vegeu la figura i la figura). També heu de copiar el fitxer mysql-connector-java-5.1.40-bin.jar al directori /NetBeans/glassfish-4.1/glassfish/lib/embedded.

  1. <properties>
  2. <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  3. <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
  4. <glassfish.embedded-static-shell.jar>/DIRECTORI_INSTALACIO_NETBEANS/glassfish-4.1//glassfish/lib/embedded/glassfish-embedded-static-shell.jar</glassfish.embedded-static-shell.jar>
  5. <glassfish.mysql.jar>/DIRECTORI_INSTALACIO_NETBEANS/glassfish-4.1//glassfish/lib/embedded/mysql-connector-java-5.1.40-bin.jar</glassfish.mysql.jar>
  6. </properties>
  7.  
  8. <dependencies>
  9. <dependency>
  10. <groupId>com.h2database</groupId>
  11. <artifactId>h2</artifactId>
  12. <version>1.4.190</version>
  13. </dependency>
  14. <dependency>
  15. <groupId>junit</groupId>
  16. <artifactId>junit</artifactId>
  17. <version>4.12</version>
  18. </dependency>
  19. <dependency>
  20. <groupId>javax.validation</groupId>
  21. <artifactId>validation-api</artifactId>
  22. <version>1.1.0.Final</version>
  23. </dependency>
  24. <dependency>
  25. <groupId>org.eclipse.persistence</groupId>
  26. <artifactId>eclipselink</artifactId>
  27. <version>2.5.0</version>
  28. </dependency>
  29. <dependency>
  30. <groupId>org.glassfish.main.extras</groupId>
  31. <artifactId>glassfish-embedded-all</artifactId>
  32. <version>4.1.1</version>
  33. <scope>system</scope>
  34. <systemPath>${glassfish.embedded-static-shell.jar}</systemPath>
  35. </dependency>
  36. <dependency>
  37. <groupId>javax.ejb</groupId>
  38. <artifactId>javax.ejb-api</artifactId>
  39. <version>3.2</version>
  40. </dependency>
  41. <dependency>
  42. <groupId>mysql</groupId>
  43. <artifactId>mysql-connector-java</artifactId>
  44. <version>5.1.40</version>
  45. <scope>system</scope>
  46. <systemPath>${glassfish.mysql.jar}</systemPath>
  47. </dependency>
  48. </dependencies>

El directori glassfish-4.1 correspon a la versió 4.1. Heu de fer servir el que correspon a la vostra versió.

A continuació creeu un test que utilitzarà el recurs JDBC definit a UserServiceImpl i que, per tant, escriurà dades a la BD MySQL que heu creat.

  1. @Before
  2. public void setUp() throws NamingException {
  3. Context context = EJBContainer.createEJBContainer().getContext();
  4. userService = (UserService) context.lookup("java:global/classes/UserServiceImpl");
  5. }
  6.  
  7. @Test
  8. public void findUserByUsername() {
  9. String username = "jdoe";
  10. User user = new User();
  11. user.setActive(true);
  12. user.setCreatedOn(new Timestamp(new Date().getTime()));
  13. user.setEmail("test@test.com");
  14. user.setName("Jane");
  15. user.setPassword("password");
  16. user.setRank(100);
  17. user.setUsername(username);
  18. User user1 = new User();
  19. user1.setActive(true);
  20. user1.setCreatedOn(new Timestamp(new Date().getTime()));
  21. user1.setEmail("test1@test.com");
  22. user1.setName("Joe");
  23. user1.setPassword("password");
  24. user1.setRank(100);
  25. user1.setUsername("joeTest");
  26.  
  27. userService.create(user);
  28. userService.create(user1);
  29.  
  30. User userFromDB = userService.findUserByUsername(username);
  31. Assert.assertNotNull(userFromDB);
  32. Assert.assertEquals("jdoe", userFromDB.getUsername());
  33. Assert.assertEquals("test@test.com", userFromDB.getEmail());
  34. Assert.assertNotNull(userFromDB.getUserId());
  35. }

Primer inicialitzeu el servidor Glassfish-embedded i recupereu l’EJB del contenidor d’EJB del servidor Glassfish real. Un cop teniu l’EJB UserServiceImpl ja podeu fer les operacions amb la base de dades. Executeu el test, i hauríeu d’obtenir un error similar al de la figura.

Figura Error SQL

Per què creieu que teniu aquest error?

  • Perquè no es pot connectar amb la BD?
  • Perquè la taula “Users” no està disponible?
  • Perquè la taula “Sequence” no existeix?

L’error és perquè la taula “Sequence” no existeix, no l’heu definit i no hi feu referència enlloc; llavors, quan s’està intentant crear? Per què quan vau fer el test amb la BD en memòria no va fallar? La resposta està a la classe User.

  1. @GeneratedValue
  2. @Column(name = "user_id")
  3. private Long userId;

L’anotació GeneratedValue té com a estratègia per defecte GenerationType.AUTO, que intentarà crear la taula “Sequence” per guardar el valor autogenerat per al camp “user_id” de la taula. Com que la taula no existeix, l’aplicació acaba amb un error, ja que no hi pot escriure. A la vostra taula vau definir que el cap que formaria la clau principal s’autoincrementaria, llavors l’estratègia que heu d’utilitzar és GenerationType.IDENTITY. Modifiqueu la classe User.

  1. @GeneratedValue(strategy = GenerationType.IDENTITY)
  2. @Column(name = "user_id")
  3. private Long userId;

Executeu el test un altre cop, aquesta vegada tindreu el següent error:

Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Unknown column 'user_id' in 'field list'

L’error diu ara que no existeix un camp anomenat “user_id” a la taula “Users”. Netbeans deixa crear una connexió amb la BD que us permetrà examinar l’estructura de la taula. Seleccioneu la pestanya “Services”, feu clic amb el botó dret a Database i registreu una connexió amb un servidor MySQL, tal com podeu veure en la figura.

Figura Registrar un servidor MySQL a Netbeans

A continuació, afegiu les dades del vostre servidor MySQL (vegeu la figura).

Figura Registrar el servidor MySQL a Netbeans

Un cop definida la connexió ja podeu examinar el contingut de la taula “Users” (vegeu la figura) a través de la pestanya “Services” de Netbeans.

Figura Contingut de la taula “Users”

El problema és que la columna de la taula “Users” s’anomena “id” i no “user_id”. Canvieu, doncs, la classe User.

  1. @Id
  2. @GeneratedValue(strategy = GenerationType.IDENTITY)
  3. @Column(name = "id")
  4. private Long userId;

Executeu de nou el test, i ara acabarà sense cap error. Com que heu utilitzat la BD MySQL, podeu veure que els dos usuaris creats al tests estan a la taula “Users”. Amb l’opció Execute command (vegeu la figura) podeu executar consultes i veure que la taula conté els registres esperats (vegeu la figura).

Figura Executar una consulta SQL

Figura Registres creats durant el test

Refactoritzant el codi per simplificar el codi i millorar el test

Escriure test unitaris que modifiquin el contingut de la base de dades MySQL és un problema. Penseu que aquesta serà la BD que utilitzareu per a la vostra aplicació; tenir un test que escriu dades a aquesta BD és, doncs, un error. Us ha servit per veure com utilitzar el servidor Glassfish per emprar un recurs JDBC i escriure a la BD, però ho heu de canviar.

Un altre problema està a la classe UserServiceImpl. El primer problema és que esteu determinant quina unitat de persistència utilitzarà aquesta EJB, i això impossibilita escollir quina unitat de persistència emprar quan s’executen els tests. Heu de refactoritzar el codi per tal de poder escollir la unitat de persistència en funció que executeu tests unitaris o desplegueu l’aplicació al servidor Glassfish.

  1. @PersistenceContext(unitName = “MysqlResourceSocIocPersistence")

Un altra cosa que podeu millorar es la gestió de les transaccions. Aquest és el codi que heu utilitzat:

  1. @Override
  2. public void create(User user) {
  3. UserTransaction utx = context.getUserTransaction();
  4. try {
  5. utx.begin();
  6. entityManager.persist(user);
  7. utx.commit();
  8. } catch (Exception e) {
  9. e.printStackTrace();
  10. try {
  11. utx.rollback();
  12. } catch (Exception e1) {
  13. e1.printStackTrace();
  14. }
  15. }
  16. }

El que passa és que quan s’utilitza l’anotació @Stateless, automàticament EJB proporciona la següent anotació a cadascun dels mètodes @TransactionAttribute(TransactionAttributeType.REQUIRED). Això fa que s’encarregui automàticament de gestionar les transaccions; per tant, podeu simplificar la classe UserServiceImpl notablement.

  1. @Stateless
  2. public class UserServiceImpl implements UserService {
  3. @PersistenceContext
  4. private EntityManager entityManager;
  5.  
  6. @Override
  7. public void create(User user) {
  8. entityManager.persist(user);
  9. }
  10.  
  11. @Override
  12. public void edit(User user) {
  13. entityManager.merge(user);
  14. }
  15.  
  16. @Override
  17. public void remove(User user) {
  18. entityManager.remove(user);
  19. }
  20.  
  21. @Override
  22. public User findUserByUsername(String username) {
  23. return (User) entityManager.createQuery("select object(o) from User o " +
  24. "where o.username = :username")
  25. .setParameter("username", username)
  26. .getSingleResult();
  27. }
  28. }

De la forma que heu fet el test anterior era necessari que el servidor Glassfish estigués corrent. Això és un problema, ja que necessiteu executar els tests de manera independent del servidor d’aplicacions, i el més important: heu de ser capaços de poder especificar la unitat de persistència que voleu utilitzar en cada moment. Per poder aconseguir això fareu servir Arquillian (red.ht/2ls9x1Q), que és un framework de testeig que facilita escriure tests del components EJB. Utilitzant Arquillian podreu llançar una versió d’un servidor Glassfish en memòria que us permetrà accedir a les EJB tal com si estiguessin corrent al servidor real. Per aconseguir això, el primer que heu de fer és afegir unes dependències. Hi ha uns quants canvis, per la qual cosa aquí teniu tot el fitxer pom.xml.

  1. <dependencyManagement>
  2. <dependencies>
  3. <dependency>
  4. <groupId>org.jboss.arquillian</groupId>
  5. <artifactId>arquillian-bom</artifactId>
  6. <version>1.1.11.Final</version>
  7. <scope>import</scope>
  8. <type>pom</type>
  9. </dependency>
  10. </dependencies>
  11. </dependencyManagement>
  12.  
  13. <dependencies>
  14. <dependency>
  15. <groupId>com.h2database</groupId>
  16. <artifactId>h2</artifactId>
  17. <version>1.4.190</version>
  18. </dependency>
  19. <dependency>
  20. <groupId>junit</groupId>
  21. <artifactId>junit</artifactId>
  22. <version>4.12</version>
  23. </dependency>
  24. <dependency>
  25. <groupId>javax.validation</groupId>
  26. <artifactId>validation-api</artifactId>
  27. <version>1.1.0.Final</version>
  28. </dependency>
  29. <dependency>
  30. <groupId>org.eclipse.persistence</groupId>
  31. <artifactId>eclipselink</artifactId>
  32. <version>2.6.4</version>
  33. </dependency>
  34. <dependency>
  35. <groupId>javax.ejb</groupId>
  36. <artifactId>javax.ejb-api</artifactId>
  37. <version>3.2</version>
  38. </dependency>
  39. <dependency>
  40. <groupId>mysql</groupId>
  41. <artifactId>mysql-connector-java</artifactId>
  42. <version>5.1.40</version>
  43. </dependency>
  44.  
  45. <dependency>
  46. <groupId>javax.el</groupId>
  47. <artifactId>javax.el-api</artifactId>
  48. <version>3.0.1-b04</version>
  49. </dependency>
  50.  
  51. <dependency>
  52. <groupId>org.jboss.arquillian.container</groupId>
  53. <artifactId>arquillian-glassfish-embedded-3.1</artifactId>
  54. <version>1.0.0.Final</version>
  55. <scope>test</scope>
  56. </dependency>
  57. <dependency>
  58. <groupId>org.glassfish.main.extras</groupId>
  59. <artifactId>glassfish-embedded-all</artifactId>
  60. <version>4.1.1</version>
  61. <scope>provided</scope>
  62. </dependency>
  63. <dependency>
  64. <groupId>javax.inject</groupId>
  65. <artifactId>javax.inject</artifactId>
  66. <version>1</version>
  67. </dependency>
  68.  
  69. <dependency>
  70. <groupId>org.jboss.arquillian.junit</groupId>
  71. <artifactId>arquillian-junit-container</artifactId>
  72. <scope>test</scope>
  73. </dependency>
  74. </dependencies>

El primer que fareu serà separar el fitxer on definiu les unitats de persistència en dos fitxers. Un contindrà la unitat de persistència per escriure a la BD MySQL i l’altre la unitat de persistència per a testeig (vegeu la figura).

Figura Registres creats durant el test

Aquest és la unitat de persistència per escriure a MySQL:

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
  5. http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
  6. <persistence-unit name="SocIocPersistenceUnit" transaction-type="JTA">
  7. <jta-data-source>jdbc/socioc__pm</jta-data-source>
  8. <class>org.ioc.daw.user.User</class>
  9. <properties>
  10. <property name="eclipselink.logging.level" value="FINEST"/>
  11. </properties>
  12. </persistence-unit>
  13. </persistence>

I aquest és la unitat de persistència que utilitzareu per al testeig:

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
  5. http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
  6. <persistence-unit name="SocIocPersistenceUnit-TEST" transaction-type="JTA">
  7. <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
  8. <class>org.ioc.daw.user.User</class>
  9. <properties>
  10. <property name="javax.persistence.jdbc.driver" value="org.h2.Driver"/>
  11. <property name="javax.persistence.jdbc.user" value="username"/>
  12. <property name="javax.persistence.jdbc.password" value="password"/>
  13. <property name="javax.persistence.jdbc.url" value="jdbc:h2:mem:socioc_db;INIT=runscript from 'classpath:init.sql'"/>
  14. <property name="javax.persistence.schema-generation.database.action" value="drop-and-create"/>
  15. <property name="javax.persistence.schema-generation.create-source" value="metadata"/>
  16. <property name="javax.persistence.schema-generation.drop-source" value="metadata"/>
  17. </properties>
  18. </persistence-unit>
  19. </persistence>

Ara ja podeu reescriure el test unitari. Si teniu el servidor Glassfish en execució pareu-lo, o tindreu conflictes de port.

  1. @RunWith(Arquillian.class)
  2. public class UserServiceTest {
  3. @Inject
  4. private UserService userService;
  5.  
  6. @Deployment(testable = true)
  7. public static JavaArchive createTestableDeployment() {
  8. final JavaArchive jar = ShrinkWrap.create(JavaArchive.class, "example.jar")
  9. .addClasses(UserService.class, UserServiceImpl.class)
  10. .addAsManifestResource("META-INF/persistence-test.xml", "persistence.xml")
  11. .addAsManifestResource(EmptyAsset.INSTANCE, ArchivePaths.create("beans.xml"));
  12. return jar;
  13. }
  14.  
  15. @Test
  16. public void findUserByUsername() {
  17. String username = "jdoe";
  18. User user = new User();
  19. user.setActive(true);
  20. user.setCreatedOn(new Timestamp(new Date().getTime()));
  21. user.setEmail("test@test.com");
  22. user.setName("Jane");
  23. user.setPassword("password");
  24. user.setRank(100);
  25. user.setUsername(username);
  26. User user1 = new User();
  27. user1.setActive(true);
  28. user1.setCreatedOn(new Timestamp(new Date().getTime()));
  29. user1.setEmail("test1@test.com");
  30. user1.setName("Joe");
  31. user1.setPassword("password");
  32. user1.setRank(100);
  33. user1.setUsername("joeTest");
  34.  
  35. userService.create(user);
  36. userService.create(user1);
  37.  
  38. User userFromDB = userService.findUserByUsername(username);
  39. Assert.assertNotNull(userFromDB);
  40. Assert.assertEquals("jdoe", userFromDB.getUsername());
  41. Assert.assertEquals("test@test.com", userFromDB.getEmail());
  42. Assert.assertNotNull(userFromDB.getUserId());
  43. }
  44. }

@RunWith(Arquillian.class) indica que executareu el test utilitzant Arquillian, i a continuació injecteu l’EJB UserService. Això és possible perquè Arquillian crea un servidor Glassfish en memòria amb un contenidor amb totes les EJB de la vostra aplicació, la qual cosa us permet afegir-les a una determinada classe quan sigui necessari. A createTestableDeployment és on es configura el servidor d’aplicacions utilitzant Arquillian. La part més important és on indiqueu quines classes són les EJB que voleu afegir al contenidor EJB del servidor i quin és el fitxer amb la definició de la unitat de persistència que voleu fer servir.

Podeu trobar tot el codi resultant de la refactorització als annexos de la unitat.

Què s'ha après?

Heu après que utilitzant JDBC directament al codi té una sèrie d’inconvenients, ja que fa que us hagueu d’encarregar de programar operacions de baix nivell amb la base de dades, com establir i tancar les connexions, encarregar-se del mapatge de les dades de la BD amb els objectes del codi, etc.

Per solucionar aquests problemes heu vist JPA, que és una especificació que determina i fa estàndards les operacions amb la BD. És una especificació però no hi ha una implementació, així que per fer els exemples hem utilitzat EclipseLink, una de les implementacions disponibles. També heu vist que, encara que pugueu utilitzar implementacions de JPA per accedir directament a les bases de dades, utilitzar un servidor EJB com Glassfish proporciona diversos avantatges.

Pel que fa a JPA, permet establir un pool de connexions per fer més eficients les operacions i reutilitzar les connexions establertes amb la BD, que són operacions molt costoses. Finalment, heu vist diferents formes de testejar aplicacions que treballen amb bases de dades i heu creat una estructura, separant les unitats de persistència i utilitzant el framework Arquillian, que pot servir de base per testejar qualsevol aplicació que treballi amb JPA.

Anar a la pàgina anterior:
Annexos
Anar a la pàgina següent:
Activitats