Librería de acceso a datos con Spring y JPA

11
32419

Librería de acceso a datos con Spring y JPA

0. Índice de contenidos.

1. Entorno

Este tutorial está escrito usando el siguiente entorno:

  • Hardware: Portátil Mac Book Pro 17″ (2,6 Ghz Intel Core
    i7, 8 GB
    DDR3)
  • Sistema Operativo: Mac OS X Snow Leopard 10.6.4
  • Spring 3.0.4
  • Maven 2.2.1
  • Eclipse 3.6 (Helios) con M2Eclipse y Spring IDE

2. Introducción

Este tutorial lo voy a aprovechar para crear una librería de acceso al esquema de datos que voy a utilizar en el resto de tutoriales que necesiten un acceso a base de datos.

Uno de los problemas más comunes que nos encontramos cuando participamos en proyectos ya comenzados es que todo el código de acceso a datos se repite en todos y cada uno de los proyectos.

En estos casos lo mejor que se puede hacer es crear una librería para cada uno de los esquemas de base de datos que intervengan en el proyecto. Como vamos a ver en este tutorial.

3. Creando el proyecto con Maven

En este tipo de proyectos en los que vamos a utilizar librerías
de
terceros como Spring, se hace especialmente útil la
utililización de
una herramienta de gestión de dependencias como Maven. Teniendo
Maven
ya instalado en nuestra máquina lo único que tenemos que
hacer es abrir
un terminal y situarnos en el directorio donde vayamos a crear el
proyecto:

4. Configurando las dependencias del proyecto

Con el fin de que la edición del proyecto sea más
cómoda podemos
importarlo a un IDE como Eclipse. Para hacer esto, contamos con el
plugin M2Eclipse que es la evolución del conocido EclipseIAM.

Para configurar las dependencias editamos el fichero pom.xml del
proyecto, añadiendo las siguientes:

Estamos definiendo que el proyecto lo vamos a implementar con Spring y la especificación JPA 1.0 con Hibernate. Además en desarrollo vamos a utilizar PostgreSQL, pero para los tests vamos a utilizar una base de datos en memoria como HSQLDB.

5. Configurando Spring

Para configurar Spring vamos a crear el fichero application-context-model-tutoriales.xml dentro de la carpeta src/main/resources de nuestro proyecto, con el siguiente contenido:

En este fichero estamos definiendo que vamos a utilizar anotaciones y especificamos que archivo se va a encargar de definir el acceso a la base de datos a través de JPA.

6. Configurando JPA

Como hemos definido anteriormente tenemos que crear un fichero llamado persistence.xml dentro de la carpeta src/main/resources/META-INF con el siguiente contenido:

7. Creando un DAO genérico

Con el fin de no repetir los mismos métodos en todas las clases del proyecto, vamos a crear un DAO genérico con los métodos comunes de acceso a datos, que vamos a definir en la siguiente interfaz:

En este caso vamos a implementar esta interfaz utilizando Spring y JPA con el siguiente código:

Para esta implementación utilizamos la facilidades de Spring para JPA extendiendo de JPADaoSupport y con la anotación @PersistenceContext.

8. Creando la entidad Persona

Lo principal cuando utilizamos JPA es la definición de las entidades, en este caso lo vamos a hacer con anotaciones de esta forma:

De esta implementación cabe destacar la forma como hemos definido el id de la entidad para que sea un autonumérico. Además hay que fijarse como hemos definido los métodos hashCode() y equals(), que nos evitarán problemas a la hora de trabajar con la persistencia.

9. Creando la clase de persistencia para la entidad Persona

Como ya hemos implementado el DAO genérico, para crear la clase que se va a encargar de la persistencia de la entidad Persona, va a ser tan sencillo como definir la siguiente interfaz:

Y la implementación de la clase es tan sencillo como lo siguiente:

Cabe destacar la utilización de la anotación @Repository que nos ahorra tener que definir un bean explícito en el fichero application-context-model-tutoriales.xml.

Con esto la entidad Persona ya tiene disponibles los métodos del DAO genérico, en caso de necesitar otros métodos específicos, habría que implementarlos en la clase PersonaDAOJPAImpl y definirlos en la interfaz PersonaDAO.

10. Vamos a probarlo

A fin de probar esta implementación vamos a definir un archivo application-context.xml y pesistence.xml específicos para las pruebas, donde vamos a utilizar una base de datos en memoria.

Para ello vamos a crear el fichero application-context-model-tutoriales-test.xml dentro de la carpeta src/test/resources con el siguiente contenido:

La única diferencia con el anterior es la definición del fichero persistence-test.xml, que se almacena en la carpeta src/test/resources/META-INF con el siguiente contenido:

Una vez configurado el entorno de pruebas sólo tenemos que implementar la siguiente clase con JUnit, donde vamos a probar la inserción, recuperación, modificación y eliminación de un registro de la entidad Persona.

La anotación @RunWith y @ContextConfiguration nos permiten levantar el contexto de Spring para que se produzca la inyección de la dependencia PersonaDAO.

11. Conclusiones

Hemos visto como crear una librería para acceder a un esquema de base de datos que podremos importar en cualquier proyecto que necesite trabajar con este esquema. De hecho yo voy a utilizarla en posteriores desarrollos de futuros tutoriales.

Saludos.

11 Comentarios

  1. Hola Ruben, antes que nada quería agradecerte por el excelente tutorial, es muy claro y por sobre todo muy útil. Tengo un problema al querer realizar las pruebas, me sale un error en la carga del contexto de aplicación, revisé todo y se supone que esta todo bien. Podrías subir el código o decirme como puedo enviarte el mío para que me des una mano. Gracias. Saludos

  2. Hola Ruben igualmente muchas gracias por el tutorial, pero tengo el mismo error que Booger. Como podemos solucionar esto? Muchas Gracias!

  3. Ya corregi el error que tenia: la falla estaba en el Persistence.xml y el persistence-test.xml, no habia definido el nombre de mi base de datos en: tenia tutoriales. Espero que esto te sirva Booger

  4. Hola gracias por todo Ruben, pero me sale un error en la carga del contexto de aplicación. He leido intentado hacer otros tutoriales de esta sitio y siempre o los dejan a medias o con errores. Un consejo si van a dar tutoriales es porque lo van a leer personas que estan aprendiendo. Se hacen preguntas y nunca se responden. Que estes bien y gracias por el tuto.

  5. Hola gabohacker,

    ¿Podrías darme más información sobre cuál es el error que está dando? Por ejemplo, el log de la aplicación con la traza de la pila estaría genial.

    Saludos.

  6. 4/04/2011 04:59:38 PM org.springframework.test.context.TestContextManager retrieveTestExecutionListeners
    INFO: @TestExecutionListeners is not present for class [class com.urbanalab.modelo.pruebas.PersonaDAOJPAImplTest]: using defaults.
    4/04/2011 04:59:38 PM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
    INFO: Loading XML bean definitions from class path resource [com/urbanalab/modelo/pruebas/applicationContext.xml]
    4/04/2011 04:59:38 PM org.springframework.context.support.AbstractApplicationContext prepareRefresh
    INFO: Refreshing org.springframework.context.support.GenericApplicationContext@4720d62b: startup date [Mon Apr 04 16:59:38 COT 2011]; root of context hierarchy
    4/04/2011 04:59:38 PM org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingletons
    INFO: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@687bc899: defining beans [org.springframework.context.annotation.internalConfigurationAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalRequiredAnnotationProcessor,org.springframework.context.annotation.internalCommonAnnotationProcessor,org.springframework.context.annotation.internalPersistenceAnnotationProcessor,personaDAO,org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor#0,entityManagerFactory,transactionManager,org.springframework.aop.config.internalAutoProxyCreator,org.springframework.transaction.annotation.AnnotationTransactionAttributeSource#0,org.springframework.transaction.interceptor.TransactionInterceptor#0,org.springframework.transaction.config.internalTransactionAdvisor]; root of factory hierarchy
    4/04/2011 04:59:38 PM org.springframework.beans.factory.support.DefaultSingletonBeanRegistry destroySingletons
    INFO: Destroying singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@687bc899: defining beans [org.springframework.context.annotation.internalConfigurationAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalRequiredAnnotationProcessor,org.springframework.context.annotation.internalCommonAnnotationProcessor,org.springframework.context.annotation.internalPersistenceAnnotationProcessor,personaDAO,org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor#0,entityManagerFactory,transactionManager,org.springframework.aop.config.internalAutoProxyCreator,org.springframework.transaction.annotation.AnnotationTransactionAttributeSource#0,org.springframework.transaction.interceptor.TransactionInterceptor#0,org.springframework.transaction.config.internalTransactionAdvisor]; root of factory hierarchy
    4/04/2011 04:59:38 PM org.springframework.test.context.TestContextManager prepareTestInstance
    GRAVE: Caught exception while allowing TestExecutionListener [org.springframework.test.context.support.DependencyInjectionTestExecutionListener@11e9c82e] to prepare test instance [com.urbanalab.modelo.pruebas.PersonaDAOJPAImplTest@b554d32]
    java.lang.IllegalStateException: Failed to load ApplicationContext
    at org.springframework.test.context.TestContext.getApplicationContext(TestContext.java:308)
    at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.injectDependencies(DependencyInjectionTestExecutionListener.java:109)
    at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.prepareTestInstance(DependencyInjectionTestExecutionListener.java:75)
    at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:321)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.createTest(SpringJUnit4ClassRunner.java:220)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner$1.runReflectiveCall(SpringJUnit4ClassRunner.java:301)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.methodBlock(SpringJUnit4ClassRunner.java:303)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:240)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:49)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:193)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:52)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:191)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:42)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:184)
    at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
    at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:236)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:180)
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:49)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)
    Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name \\\’personaDAO\\\’ defined in file [C:\\\\Users\\\\Comba_Desarrollo1\\\\EclipseProjects\\\\Database\\\\target\\\\classes\\\\com\\\\urbanalab\\\\modelo\\\\PersonaDAOJPAImpl.class]: Invocation of init method failed; nested exception is java.lang.IllegalArgumentException: entityManagerFactory or jpaTemplate is required
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1420)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:519)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:456)
    at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:291)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:288)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:190)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:580)
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:895)
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:425)
    at org.springframework.test.context.support.AbstractGenericContextLoader.loadContext(AbstractGenericContextLoader.java:84)
    at org.springframework.test.context.support.AbstractGenericContextLoader.loadContext(AbstractGenericContextLoader.java:1)
    at org.springframework.test.context.TestContext.loadApplicationContext(TestContext.java:280)
    at org.springframework.test.context.TestContext.getApplicationContext(TestContext.java:304)
    … 24 more
    Caused by: java.lang.IllegalArgumentException: entityManagerFactory or jpaTemplate is required
    at org.springframework.orm.jpa.support.JpaDaoSupport.checkDaoConfig(JpaDaoSupport.java:120)
    at org.springframework.dao.support.DaoSupport.afterPropertiesSet(DaoSupport.java:44)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1477)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1417)
    … 37 more

  7. Hola Rubén, felicidades, son muy buenas estas clases, espero que sigan ilustrandonos… Bueno, la implementacion de todo este codigo esta perfectamente, obviamente tambien a mi me vinieron errores, pero uno de los trabajos del programador es resolver los problemas que se presentan… Yo he resuelto modificando una dependencia en el pom.xml.
    Se debe sostituir la dependencia persistence-api-1.0.2 por hibernate-jpa-2.0-api, dejo el fragmento del pom con la modifica… Saludos y gracias 😉

    CODIGO DE ELIMINAR O COMENTAR

    javax.persistence
    persistence-api
    1.0.2

    DEPENDENCIA QUE SOSTITUYE LA ANTERIOR

    org.hibernate.javax.persistence
    hibernate-jpa-2.0-api
    1.0.1.Final

  8. Hola Rubén, felicidades, son muy buenas estas clases, espero que sigan ilustrandonos… Bueno, la implementacion de todo este codigo esta perfectamente, obviamente tambien a mi me vinieron errores, pero uno de los trabajos del programador es resolver los problemas que se presentan… Yo he resuelto modificando una dependencia en el pom.xml.
    Se debe sostituir la dependencia persistence-api-1.0.2 por hibernate-jpa-2.0-api, dejo el fragmento del pom con la modifica… Saludos y gracias 😉

    CODIGO DE ELIMINAR O COMENTAR
    groupId: javax.persistence
    artifactId: persistence-api
    version: 1.0.2

    DEPENDENCIA QUE SOSTITUYE LA ANTERIOR
    groupId: org.hibernate.javax.persistence
    artifactId: hibernate-jpa-2.0-api
    version: 1.0.1.Final

  9. Hola,

    Primero agredecer el tuto, está muy bastante clarito todo. Pero tengo el mismo problema que otros tantos aquí al lanzar el test y es que el contexto no se levanta correctamente y no se llega a inicializar el persistenceContext.

    ¿Alguna luz que arrojar sobre esto? Está todo ok según parece. Pongo el stack trace:

    java.lang.IllegalStateException: Failed to load ApplicationContext
    at org.springframework.test.context.TestContext.getApplicationContext(TestContext.java:308)
    at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.injectDependencies(DependencyInjectionTestExecutionListener.java:109)
    at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.prepareTestInstance(DependencyInjectionTestExecutionListener.java:75)
    at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:333)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.createTest(SpringJUnit4ClassRunner.java:220)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner$1.runReflectiveCall(SpringJUnit4ClassRunner.java:301)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.methodBlock(SpringJUnit4ClassRunner.java:303)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:240)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:49)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:193)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:52)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:191)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:42)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:184)
    at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
    at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:236)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:180)
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)
    Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name \\\’TaskDAO\\\’ defined in file [D:\\\\DEVEL\\\\git\\\\common-gateway\\\\persistence\\\\target\\\\classes\\\\eu\\\\ohim\\\\cfportal\\\\dao\\\\imp\\\\jpa\\\\TaskDAOJPAImpl.class]: Invocation of init method failed; nested exception is java.lang.IllegalArgumentException: entityManagerFactory or jpaTemplate is required
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1420)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:519)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:456)
    at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:291)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:288)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:190)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:580)
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:895)
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:425)
    at org.springframework.test.context.support.AbstractGenericContextLoader.loadContext(AbstractGenericContextLoader.java:84)
    at org.springframework.test.context.support.AbstractGenericContextLoader.loadContext(AbstractGenericContextLoader.java:1)
    at org.springframework.test.context.TestContext.loadApplicationContext(TestContext.java:280)
    at org.springframework.test.context.TestContext.getApplicationContext(TestContext.java:304)
    … 24 more
    Caused by: java.lang.IllegalArgumentException: entityManagerFactory or jpaTemplate is required
    at org.springframework.orm.jpa.support.JpaDaoSupport.checkDaoConfig(JpaDaoSupport.java:120)
    at org.springframework.dao.support.DaoSupport.afterPropertiesSet(DaoSupport.java:44)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1477)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1417)
    … 37 more

  10. Buenas,

    Hice una prueba con un código pero me ha errores las lineas:
    import javax.persistence.EntityManager;
    import javax.persistence.EntityManagerFactory;
    import javax.persistence.Persistence;

    import org.junit.After;
    import org.junit.AfterClass;
    import org.junit.Assert;
    import org.junit.Before;
    import org.junit.BeforeClass;
    import org.junit.Test;

    Alguien me puede ayudar, parece que no localiza estas importaciones de clases.

    Un saludo;

    David

Dejar respuesta

Please enter your comment!
Please enter your name here