JDAL - Aplicación de Ejemplo - Capa de Integración


Capa de acceso a datos


La base de datos utilizará tres tablas:

Script de creación de la base de datos para MySql se encuentra en /db/create.sql :

CREATE TABLE authors (
	id INTEGER PRIMARY KEY AUTO_INCREMENT,
	name VARCHAR(100),
	surname VARCHAR(100)
) ENGINE=InnoDB;
 
 
CREATE TABLE categories (
	id INTEGER PRIMARY KEY AUTO_INCREMENT,
	name VARCHAR(100)
) ENGINE=InnoDB;
 
 
CREATE TABLE books (
	id INTEGER PRIMARY KEY AUTO_INCREMENT,
	name VARCHAR(250),
	ISBN VARCHAR(32),
	publishedDate DATE,
	authorid INTEGER,
	categoryid INTEGER
) ENGINE=InnoDB;
 
ALTER TABLE books ADD CONSTRAINT category_fk FOREIGN KEY (categoryid) REFERENCES categories (id);
ALTER TABLE books ADD CONSTRAINT author_fk FOREIGN KEY (authorid) REFERENCES authors (id);
 

El script se encuentra en el directorio db/create.sql del código del ejemplo


Clases de los modelos


Vamos a utilizar hibernate y JPA para mapear el estado de los modelos del dominio a la base de datos. Al ser un ejemplo sencillo, los modelos no tendrán comportamiento y tendrán una correspondencia uno a uno con el esquema de la base de datos. Evidentemente esto no es un requerimiento en absoluto.

los modelos del dominio se encuentran en el paquete org.jdal.samples.library.model

Book.java

@Entity
@Table(name="books")
public class Book extends info.joseluismartin.model.Entity {
 
	private static final long serialVersionUID = 1L;
	@ManyToOne
	@JoinColumn(name="authorid")
	private Author author;
	@ManyToOne
	@JoinColumn(name="categoryid")
	private Category category;
	private String isbn;
	private Date publishedDate;
 
	public String toString() {
		return name;
	}
 
	// Getters and Setters
}
 

Category.java

@Entity
@Table(name="categories")
public class Category extends info.joseluismartin.model.Entity {
	// nothing to add a Entity class.
}
 

Author.java

 
@Entity
@Table(name="authors")
@Filter(name="patternFilter", condition="name like :pattern or surname like :pattern")
@FilterDef(name="patternFilter", parameters=@ParamDef(name="pattern", type="string"))
public class Author  extends info.joseluismartin.model.Entity {
 
	private String surname;
 
	public String getSurname() {
		return surname;
	}
 
	public void setSurname(String surname) {
		this.surname = surname;
	}
 
	public String toString() {
		return name + " " + surname;
	}
 
	// Overwrite equals and hashcode using name and surname 
	...
}
 

El filtro authorByNameFilter lo utilizaremos en combos con auto-completado del autor en el alta de un nuevo libro.

Configuración de hibernate: hibernate.cfg.xml

<!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
    "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
 
<hibernate-configuration>
	<session-factory>
		<property name="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider</property>
		<property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>
		<property name="hibernate.transaction.factory_class">org.hibernate.transaction.JDBCTransactionFactory</property>
		<property name="hibernate.show_sql">true</property>
		<property name="hibernate.cache.region.factory_class">net.sf.ehcache.hibernate.EhCacheRegionFactory</property>
		<property name="hibernate.cache.use_second_level_cache">true</property>
		<property name="hibernate.cache.use_query_cache">true</property>
 
		<mapping class="org.jdal.samples.library.model.Book"/>
		<mapping class="org.jdal.samples.library.model.Author"/>
		<mapping class="org.jdal.samples.library.model.Category"/>
	</session-factory>
</hibernate-configuration>

Hasta ahora no hay nada nuevo. Hemos creado la base de datos y los modelos de la aplicación como hubiéramos hecho en cualquier aplicación similar. En la siguiente sección veremos como utilizar JDAL para crear la aplicación de gestión.


Configurando JDAL


Crearemos cuatro ficheros de configuración de contexto de Spring Framework:


<!-- DAOs -->
<bean id="dao" abstract="true">
	<property name="sessionFactory" ref="sessionFactory" />
</bean>
 
<bean id="basicDao" class="info.joseluismartin.dao.hibernate.HibernateDao" parent="dao" />
 
<bean id="bookDao" class="info.joseluismartin.dao.hibernate.HibernateDao"
	parent="dao">
	<constructor-arg value="org.jdal.samples.library.model.Book" />
	<!-- Add the bookCriteriaBuilder to map whit 'filterName' as key -->
	<property name="criteriaBuilderMap">
		<map key-type="java.lang.String" value-type="info.joseluismartin.dao.hibernate.CriteriaBuilder">
			<entry key="bookFilter" value-ref="bookCriteriaBuilder" />
		</map>
	</property>
</bean>
 
<!-- Book Criteria Builder -->
<bean id="bookCriteriaBuilder" class="org.jdal.samples.library.dao.filter.BookCriteriaBuilder" />
 
<bean id="authorDao" class="info.joseluismartin.dao.hibernate.HibernateDao"
	parent="dao">
	<constructor-arg value="org.jdal.samples.library.model.Author" />
</bean>
 
<bean id="categoryDao" class="info.joseluismartin.dao.hibernate.HibernateDao" parent="dao">
	<constructor-arg value="org.jdal.samples.library.model.Category" />
</bean>
 
 

Objetos de Acceso a Datos


La interfaz info.joseluismartin.dao.PageableDatasource define el método Page getPage(Page p) que nos permiten obtener datos mediante páginas de resultados.

/**
 * Simple interface to get data by pages
 * 
 * @author Jose Luis Martin - (jlm@joseluismartin.info)
 */
public interface PageableDataSource {
	/**
	 * Fill and return a page of data
	 * @return the page
	 */
	public Page getPage(Page page);
	/**
	 * return a Set with keys of page
	 * @param page
	 * @return
	 */
	public List<Serializable> getKeys(Page page);
}
 

la interfaz info.joseluismartin.dao.Dao que extiende PageableDataSource define los métodos CRUD genéricos sobre los modelos persistentes. Si por ejemplo queremos tener acceso a la interfaz para el modelo Book, la declararemos como:

	Dao<Book> bookDao;

Posteriormente podremos inyectar una implementación concreta, en este caso info.joseluismartin.dao.hibernate.HibernateDao donde se utiliza el constructor de la clase para especificar la clase del modelo sobre la que se realizan las operaciones de persistencia.

 
<bean id="bookDao" class="info.joseluismartin.dao.hibernate.HibernateDao"
	parent="dao">
	<!-- Set model class via constructor injection -->
	<constructor-arg value="org.jdal.samples.library.model.Book" />
	<!-- CriteriaBuilderMap is a Map<String, CriteriaBuilder> that maps 
	Page.getFilter().getFilterName() to Criteria Builders -->
	<property name="criteriaBuilderMap">
		<map key-type="java.lang.String" value-type="info.joseluismartin.dao.hibernate.CriteriaBuilder">
			<entry key="bookFilter" value-ref="bookCriteriaBuilder" />
		</map>
	</property>
</bean>
 
<bean id="bookCriteriaBuilder" class="org.jdal.samples.library.dao.filter.BookCriteriaBuilder" />
 
 

Al solicitar páginas de datos, puede utilizarse un objeto filtro mediante la propiedad "filter" del objeto Page. La interpretación del filtro depende de cada implementación del DAO. En el caso de HibernateDao tenemos dos posibilidades:

En ninguno de los cinco casos anteriores debe preocuparse del orden o la paginación del resultado de la consulta. HibernateDao establecerá los valores correctos a partir de la información suministrada en el objeto Page.

En la configuración de bookDao se ha optado por la implementación del constructor de consultas, que generalmente es la opción más sencilla. .

El filtro sobre los libros debe filtrar mediante los siguientes campos:

Sobre esta especificación creamos un bean que extienda info.joseluismartin.dao.filter.BeanFilter que contenga las propiedades de filtrado:

public class BookFilter extends BeanFilter {
 
	private String name;
	private String authorName;
	private String authorSurname;
	private Date before;
	private Date after;
	private String isbn;
	private Category category;
 
	public BookFilter() {
		this("bookFilter");
	}
 
	public BookFilter(String filterName) {
		super(filterName);
	}
 
	// Getter And Setters...
}
 

El siguiente paso es el constructor de Criterias en base al filtro. Recordar que no debemos preocuparnos sobre la paginación o el orden de los resultados, HibernateDao ya lo hace por nosotros. Extendemos AbstractCriteriaBuilder e implementamos el el método build():

public class BookCriteriaBuilder extends AbstractCriteriaBuilder {
 
	public Criteria build(Criteria criteria, Object filter) {
		BookFilter f = (BookFilter) filter;
 
		like(criteria, "name", f.getName());
		eq(criteria, "category", f.getCategory());
		le(criteria, "publishedDate", f.getBefore());
		ge(criteria, "publishedDate", f.getAfter());
 
		// Author, add alias (join) only if needed.
		if (StringUtils.hasText(f.getAuthorName()) || StringUtils.hasText(f.getAuthorSurname())) {
			criteria.createAlias("author", "author");
			like(criteria, "author.name", f.getAuthorName());
			like(criteria, "author.surname", f.getAuthorSurname());
		}
 
		return criteria;
	}
 

Como podemos ver, la implementación del constructor de Criterias es bastante sencilla y se lee directamente de la especificación de la funcionalidad del filtro.

La capa de acceso a datos está finalizada. Resumiendo hemos codificado las siguientes clases:

El siguiente paso es crear la capa de Servicios