Jul

04

Generación del metamodelo de base de datos con Maven y uso en Criteria JPA 2.0

Posted by : hop2croft | On : 04/07/2011

En la anterior entrada Ejemplo básico JPA con EntityManager , Spring y Maven veíamos como podemos conectarnos con nuestra base de datos mediante JPA a través de EntityManager.

En este post vamos a ver como podemos crear un metamodelo de nuestra base de datos en nuestro proyecto local (Ojo, no estoy hablando de clases Java anotadas como @Entity, sino clases java que podemos usar para construir nuestras consultas).Para ello vamos a usar la API de Criteria que nos da JPA 2.0.

En JPA podemos usar JPQL para construir consultas SQL, pero podemos usar también la API de Criteria. Esta última nos ofrece una serie de ventajas como conocer si nuestra consulta es válida en tiempo de compilación, asignar constraints dinámicas, incluir paginación, filtros, … . En este post vamos a comparar dos formas de hacer la misma consulta, mediante JPQL y mediante Criteria.

La configuración de nuestro proyecto (applicationContext.xml y persistence.xml) va a ser la misma que la de Ejemplo básico JPA con EntityManager , Spring y Maven. Lo único que tenemos que modificar es la implementación de nuestro Dao y el pom de nuestro proyecto.
Comencemos por el fichero pom.xml. Para generar el metamodelo de nuestra base de datos a partir de las entidades java que tenemos en nuestro proyecto tenemos que añadir un par de plugins a nuestro proyecto.

  • maven-compiler-plugin. Nos permite compilar nuestro proyecto. Para generar nuestro metamodelo debemos añadir como argumento del compilador la clase org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor. Esta opción permitirá generar clases a partir de nuestra entidades java cuya particularidad será que expondrá sus atributos/columnas como elementos estáticos accesibles desde nuestras queries. Las clases generadas tomarán el nombre de la entidad seguido de un guión bajo.
  • maven-processor-plugin. Se encargará de copiar a src/main/java la clases de metamodelo generadas por el anterior plugin.

Nuestro fichero pom.xml será el siguiente:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.hopcroft.examples.spring</groupId>
	<artifactId>jpaModelGenerator</artifactId>
	<version>0.0.1-SNAPSHOT</version>

	<pluginRepositories>
		<pluginRepository>
			<id>maven-annotation</id>
			<url>http://maven-annotation-plugin.googlecode.com/svn/trunk/mavenrepo</url>
		</pluginRepository>
	</pluginRepositories>

	<build>
		<plugins>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<configuration>
					<source>1.6</source>
					<target>1.6</target>
					<compilerArguments>
						<processor>org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor</processor>
					</compilerArguments>
				</configuration>
				<version>2.3.2</version>
			</plugin>
			<plugin>
				<groupId>org.bsc.maven</groupId>
				<artifactId>maven-processor-plugin</artifactId>
				<version>2.0.2</version>
				<executions>
					<execution>
						<id>process</id>
						<goals>
							<goal>process</goal>
						</goals>
						<phase>generate-sources</phase>
						<configuration>
							<outputDirectory>src/main/java</outputDirectory>
						</configuration>
					</execution>
				</executions>
			</plugin>
		</plugins>
	</build>

	<dependencies>
		<dependency>
			<groupId>javax.security</groupId>
			<artifactId>jacc</artifactId>
			<version>1.0</version>
			<scope>compile</scope>
		</dependency>
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>5.1.10</version>
			<type>jar</type>
			<scope>compile</scope>
		</dependency>
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>4.8</version>
			<type>jar</type>
			<scope>compile</scope>
		</dependency>
		<dependency>
			<groupId>commons-dbcp</groupId>
			<artifactId>commons-dbcp</artifactId>
			<version>1.2.2</version>
			<type>jar</type>
			<scope>compile</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-core</artifactId>
			<version>3.0.5.RELEASE</version>
			<type>jar</type>
			<scope>compile</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-orm</artifactId>
			<version>3.0.5.RELEASE</version>
			<type>jar</type>
			<scope>compile</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-beans</artifactId>
			<version>3.0.5.RELEASE</version>
			<type>jar</type>
			<scope>compile</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-aspects</artifactId>
			<version>3.0.5.RELEASE</version>
			<type>jar</type>
			<scope>compile</scope>
		</dependency>
		<dependency>
			<groupId>javassist</groupId>
			<artifactId>javassist</artifactId>
			<version>3.12.1.GA</version>
			<type>jar</type>
			<scope>compile</scope>
		</dependency>
		<dependency>
			<groupId>org.hibernate</groupId>
			<artifactId>hibernate-entitymanager</artifactId>
			<version>3.5.6-Final</version>
			<type>jar</type>
			<scope>compile</scope>
		</dependency>
		<dependency>
			<groupId>org.ow2.easybeans</groupId>
			<artifactId>easybeans-uberjar-hibernate</artifactId>
			<version>1.2.1</version>
			<scope>compile</scope>
		</dependency>
		<dependency>
			<groupId>org.hibernate</groupId>
			<artifactId>hibernate-jpamodelgen</artifactId>
			<version>1.0.0.Final</version>
			<type>jar</type>
			<scope>compile</scope>
		</dependency>
	</dependencies>
</project>

Tras ejecutar estos plugins, a partir de nuestra clase Car se generará un clase Car_ con el siguiente aspecto:

package com.hopcroft.examples.domain;

import javax.persistence.metamodel.SingularAttribute;
import javax.persistence.metamodel.StaticMetamodel;

@StaticMetamodel(Car.class)
public abstract class Car_ {

	public static volatile SingularAttribute<Car, Long> id;
	public static volatile SingularAttribute<Car, String> model;
	public static volatile SingularAttribute<Car, Long> price;
	public static volatile SingularAttribute<Car, String> company;

}

Ahora ya tenemos accesible la clase Car_ para generar queries usando la API de Criteria para JPA 2.0. Vamos a ver en nuestro Dao dos métodos que hacen lo mismo de dos formas distintas. Pero primero peguemos el contenido de nuestro dao.

package com.hopcroft.examples.dao;

import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Path;
import javax.persistence.criteria.Root;

import org.springframework.dao.DataAccessException;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

import com.hopcroft.examples.domain.Car;
import com.hopcroft.examples.domain.Car_;
@Repository
public class CarDaoImpl implements CarDao {

	protected EntityManager entityManager;

	public EntityManager getEntityManager() {
		return entityManager;
	}
	@PersistenceContext
	public void setEntityManager(EntityManager entityManager) {
		this.entityManager = entityManager;
	}

	@SuppressWarnings("unchecked")
	@Transactional
	public List<Car> getCars() throws DataAccessException {
		Query query = getEntityManager().createQuery("select c from Car c");
		List<Car> resultList = query.getResultList();
		return resultList;
	}
	@Transactional
	public Car getCar(Long carId) throws DataAccessException {
		return getEntityManager().find(Car.class, carId);
	}

	@Transactional
	public List<Car> getCarByCompany(String company) {
		CriteriaBuilder criteriaBuilder = getEntityManager().getCriteriaBuilder();
		CriteriaQuery<Car> criteriaQuery = criteriaBuilder.createQuery(Car.class);
		Root<Car> root = criteriaQuery.from(Car.class);
		Path<String> company_ = root.get(Car_.company);
		criteriaQuery.where(criteriaBuilder.equal(company_, company));
		return getEntityManager().createQuery(criteriaQuery).getResultList();

	}
	@SuppressWarnings("unchecked")
	@Transactional
	public List<Car> getCarByCompanyNamedQuery(String company) {
		Query query = getEntityManager().createNamedQuery("Car.getCarsByCompany");
		query.setParameter("company",company);
		return query.getResultList();
	}

}

Podemos ver dos métodos nuevos getCarByCompany y getCarByCompanyNamedQuery. El segundo método es la manera más usada de hacer consultas sobre base de datos. Tenemos una NamedQuery en nuestra entidad Car a la que llamamos estableciendo el paramétro company como vemos en ese específico método. La NamedQuery será la siguiente.


@NamedQuery(name="Car.getCarsByCompany",query="select car from Car car where company = :company")

Podemos ver que es bastante sencillo usar una NamedQuery. Por otro lado tenemos el problema de que podemos escribir la consulta SQL y no darnos cuenta hasta que la ejecutemos. Para solucionar esto tenemos el método getCarByCompany que usará Criteria de JPA.
En la primera línea del método vemos como obtenemos un criteriaBuilder a través del cual generaremos la consulta. Este criteriaBuilder creará una query que devolverá objetos de la clase Car (línea 2 del método). En la línea 3 vemos que hacemos el from de la query sobre la tabla/entidad Car. En la línea 4 se va a usar el atributo company de la clase Car_ que posteriormente se utilizará dentro de la parte where de la consulta en la línea 5. Finalmente ejecutamos la consulta.
Las ventajas de criteriaBuilder es que como hemos dicho nos permite conocer en tiempo de compilación si la consulta es válida. Igualmente le podemos añadir filtros, paginación, … . La desventaja de este enfoque es que añadimos bastante verbosidad a los métodos.

Si queremos compilar nuestro metamodelo necesitamos ejecutar dos veces el ciclo de vida de Maven. La primera vez para generar las clases del metamodelo. En la segunda ejecucción las clases del Dao que usan las clases del metamodelo ya son conscientes de las mismas.

Para este post me he basado en un ejemplo que viene en el libro Spring Persistence with Hibernate de Paul Tepper FischerPaul Tepper Fischer y Brian D. Murphy.