Projeto Northwind (Instalação e Configuração do Ambiente) - Parte 01 [Java]

11. março 2012 12:45 by BBranquinho in Hibernate, Java, Maven, Tutoriais  //  Tags: , , , , , , ,   //   Comentários (10)

Aew Pessoal.

Hoje vou começar a postar sobre o projeto que estou trabalhando para meus alunos de Desenvolvimento de Sistemas. Este projeto consiste em criar uma aplicação do início ao fim usando diversas tecnologias, conceitos de Engenharia de Software e boas práticas de Arquitetura de Software. Este projeto será separados em vários subsistemas, sendo composto por: Servidor (disponibiliza serviços), Mobile (android), Desktop (Swing - Window Builder) e Web (estou analisando a tecnologia que vamos usar). Para este trabalho será usado o banco de dados Northwind, sendo que será possível usar SQL Server ou o MySQL.

O código principal do projeto está disponível em: http://wpattern.codeplex.com/SourceControl/BrowseLatest (sample projects => java => wpattern-northwind-sample => wpattern-northwind-all).

Este projeto é muito grande e utiliza várias tecnologias. Com o decorrer dos posts irei adicionando os ferramentas que vamos trabalhar. Neste primeiro post irei mostrar como configurar o ambiente e explicar os conceitos básicos do que está sendo feito.

As tecnologias que vamos utilizar são:

  • Northwind: Durante os trabalhos irei disponibilizar este banco de dados para o SQL Server e MySQL.
  • Maven: Será usado para o controle de dependências e a automatização de alguns processos.
  • Eclipse Indigo: Podem usar outras versões do Eclipse. Contudo, indico o uso do Indigo para possamos trabalhar sobre as mesmas versões, facilitando a solução de possíveis problemas. Os plugins devem ser instalados utilizando a opção "Install New Software..." ou "Eclipse Market Place...".
    • Plugin M2E (Maven 2 Eclipse): Plugin do Maven para o eclipse.
    • Hibernate: Facilita a configuração do acesso ao banco de dados usando Hibernate.
    • Subclipse: Usado para submeter códigos ao repositório SVN.
    • Window Builder: Ferramenta usada para criar aplicativos Desktop.
  • Tortoise: Usado para gerenciar códigos do repositório SVN.
  • JDK 1.6.
  • Spring: Vou utilizar as funcionalidades de Injeção de Dependência e RMI do Spring.
  • Log4J: Usado para o log de informações.
  • Dozer: Realiza o parser de Beans para Entidades e Entidades para Beans.
  • Commons Lang3: Facilita o processo de log de beans.
  • JUnit: Testes unitários.
  • Codeplex: Site que vamos usar para criar projetos e submeter códigos (Repositório SVN).

É preciso configurar as variáveis de ambiente MAVEN_HOME e JAVA_HOME para o Maven e Java, respectivamente. Apenas com essa configuração será possível executar algumas operações em nossos projetos.

O vídeo abaixo mostra a configuração do nosso ambiente.

No próximo post vou mostrar a criação os projetos com o Maven.

Até mais pessoal, abraços.

Criando um Serviço Genérico de Banco de Dados e Mapeamento Automático entre Entidades e Bean (Java)

24. fevereiro 2012 22:19 by BBranquinho in Java, Reflection  //  Tags: , , ,   //   Comentários (0)


Olá pessoal.

Apesar de já ter muito tempo que não público artigos, passei este período trabalhando em uma aplicação em Java e outra em .NET para postar no blog e ajudar meus alunos. Hoje irei escrever um pouco de uma parte do sistema em Java que estou trabalhando. Futuramente irei postar mais informações no blog, incluindo vídeos, diagramas e os códigos.

O código que irei mostrar é uma melhoria do tutorial de Introdução ao Hibernate. O objetivo é tornar os serviços mais simples de serem implementados e realizar o parser entre Entidades e Beans e vice-versa. Para realizar o parser é utilizada a biblioteca Dozer. Essa biblioteca é muito poderosa, permitindo a configuração de como é realizado o parser. Além disso, ela trata de beans complexos e é fácil de ser utilizada. Para adicionar essa biblioteca utilizando o maven temos o dependency:

<dependency>
	<groupId>net.sf.dozer</groupId>
	<artifactId>dozer</artifactId>
	<version>5.3.2</version>
</dependency>

Porém, com a adição dessa dependência ocorreram conflitos com a slf4j-log4j12. Sendo assim, para o meu cenário foi necessário remover as dependências da SLF4J. Sendo assim, temos

<dependency>
	<groupId>net.sf.dozer</groupId>
	<artifactId>dozer</artifactId>
	<version>5.3.2</version>
	<exclusions>
		<exclusion>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-api</artifactId>
		</exclusion>
		<exclusion>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-jdk14</artifactId>
		</exclusion>
	</exclusions>
</dependency>

Utilizando a Dozer o parser de objetos mais simples é realizado especificando o objeto que possui os valores e o tipo de destino que será feita a conversão. Uma vez que foi realizado o parser é retornado o objeto do tipo de destino. O código a seguir mostra um exemplo do método de parser de beans para entidades.

private static final Mapper dozerMapper = new DozerBeanMapper();

public static <S extends BaseBean, D extends BaseEntity> D parserBeanToEntity(S sourceObject, Class<D> destinationType) {
	return dozerMapper.map(sourceObject, destinationType);
}

Para realizar o parser em geral de entidades, beans e keys, temos:

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

import org.dozer.DozerBeanMapper;
import org.dozer.Mapper;

import br.com.wpattern.northwind.utils.beans.BaseBean;

public class ParserDatabase {

	private static final Mapper dozerMapper = new DozerBeanMapper();

	public static <S extends BaseBean, D extends BaseEntity> D parserBeanToEntity(S sourceObject, Class<D> destinationType) {
		return dozerMapper.map(sourceObject, destinationType);
	}

	public static <S extends BaseBean, D extends BaseEntity> List<D> parserListBeansToEntities(List<S> sourceObjects, Class<D> destinationType) {
		List<D> destinationObjects = new ArrayList<D>();

		for (Object obj : sourceObjects) {
			destinationObjects.add(dozerMapper.map(obj, destinationType));
		}

		return destinationObjects;
	}

	public static <S extends BaseEntity, D extends BaseBean> D parserEntityToBean(S sourceObject, Class<D> destinationType) {
		return dozerMapper.map(sourceObject, destinationType);
	}

	public static <S extends BaseEntity, D extends BaseBean> List<D> parserListEntitiesToBeans(List<S> sourceObjects, Class<D> destinationType) {
		List<D> destinationObjects = new ArrayList<D>();

		for (Object obj : sourceObjects) {
			destinationObjects.add(dozerMapper.map(obj, destinationType));
		}

		return destinationObjects;
	}

	public static <IDB extends Serializable, IDE extends Serializable> IDE parserKeyBeanToKeyEntity(IDB sourceObject, Class<IDE> destinationType) {
		return dozerMapper.map(sourceObject, destinationType);
	}

	public static <IDE extends Serializable, IDB extends Serializable> IDB parserKeyEntityToKeyBean(IDE sourceObject, Class<IDB> destinationType) {
		return dozerMapper.map(sourceObject, destinationType);
	}

}

Com a classe ParserDatabase conseguimos realizar o parser em geral dos objetos usados em um módulo de acesso ao banco de dados e serviços.

Além do ParserDatabase, implementei uma classe abstrata para tornar o desenvolvimento de serviços básicos mais simples. Nem sempre um serviço utilizara esses métodos de "CRUD", porém, essa classe deixa o trabalho de criação destes serviços muito simples. Inicialmente, temos a classe GenericService que possui alguns métodos otimizados quando o identificador de uma entidade é um tipo de dado básico (em geral é um Long). Está classe é mostrada a seguir.

import java.io.Serializable;
import java.util.List;

import br.com.wpattern.northwind.utils.beans.BaseBean;
import br.com.wpattern.northwind.utils.database.interfaces.IServiceBase;

public abstract class GenericService<T extends BaseBean, K extends BaseEntity, ID extends Serializable>
extends GenericServiceWithKey<T, K, ID, ID> implements IServiceBase<T, ID> {

	/////////////////////////////////////////////////////////////////////////////////
	// PUBLIC METHODS (IServiceBase)
	/////////////////////////////////////////////////////////////////////////////////

	@Override
	public T findById(ID id) {
		return parserEntity(this.getConcreteDao().findById(id));
	}

	@Override
	public List<T> findAll() {
		return parserEntity(this.getConcreteDao().findAll());
	}

	@Override
	public ID insert(T bean) {
		return getConcreteDao().save(parserBean(bean));
	}

	@Override
	public void update(T bean) {
		this.getConcreteDao().update(parserBean(bean));
	}

	@Override
	public void delete(T bean) {
		this.getConcreteDao().delete(parserBean(bean));
	}

}

Na GenericService especificamos o tipo da entidade e o bean correspondente. Além disso, definimos o tipo da chave usada pela entidade. É interessante observar que T estende BaseBean e K estende BaseEntity. Com isso evitamos possíveis erros quando as classes de serviço herdarem este abstract. Abaixo apresento a classe GenericServiceWithKey que é herdada pela GenericService. A classe GenericServiceWithKey trabalha da mesma forma que GenericService. Contudo, GenericServiceWithKey trata de chaves compostas de entidades. A seguir mostro a implementação da classe GenericServiceWithKey

import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;

import org.apache.log4j.Logger;

import br.com.wpattern.northwind.database.utils.interfaces.IGenericDao;
import br.com.wpattern.northwind.utils.beans.BaseBean;
import br.com.wpattern.northwind.utils.database.interfaces.IServiceBase;

public abstract class GenericServiceWithKey<T extends BaseBean, K extends BaseEntity, IDE extends Serializable, IDB extends Serializable> implements IServiceBase<T, IDB> {

	private final Logger logger = Logger.getLogger(this.getClass());

	private final Class<T> typeOfBean;

	private final Class<K> typeOfEntity;

	private final Class<IDE> typeOfKeyEntity;

	private final Class<IDB> typeOfKeyBean;

	/////////////////////////////////////////////////////////////////////////////////
	// CONSTRUCTORS
	/////////////////////////////////////////////////////////////////////////////////

	@SuppressWarnings("unchecked")
	public GenericServiceWithKey() {
		try {
			ParameterizedType parameterizedType = (ParameterizedType)getClass().getGenericSuperclass();
			Type[] genericTypes = parameterizedType.getActualTypeArguments();

			this.typeOfBean = (Class<T>)genericTypes[0];
			this.typeOfEntity = (Class<K>)genericTypes[1];
			this.typeOfKeyEntity = (Class<IDE>)genericTypes[2];
			this.typeOfKeyBean = (Class<IDB>)genericTypes[(genericTypes.length == 4) ? 3 : 2];
		} catch (RuntimeException e) {
			this.logger.error(e.getMessage(), e);
			// Re-throw the exception.
			throw e;
		}

		if (this.logger.isInfoEnabled()) {
			this.logger.info(String.format("Creating a service with the bean [%s], entity [%s], key bean [%s] and key entity [%s].",
					getTypeOfBean(), getTypeOfEntity(), getTypeOfKeyBean(), getTypeOfKeyEntity()));
		}
	}

	/////////////////////////////////////////////////////////////////////////////////
	// ABSTRACT METHODS
	/////////////////////////////////////////////////////////////////////////////////

	protected abstract IGenericDao<K, IDE> getConcreteDao();

	/////////////////////////////////////////////////////////////////////////////////
	// PUBLIC METHODS (IServiceBase)
	/////////////////////////////////////////////////////////////////////////////////

	@Override
	public T findById(IDB id) {
		return parserEntity(this.getConcreteDao().findById(parserKeyBean(id)));
	}

	@Override
	public List<T> findAll() {
		return parserEntity(this.getConcreteDao().findAll());
	}

	@Override
	public IDB insert(T bean) {
		return parserKeyEntity(getConcreteDao().save(parserBean(bean)));
	}

	@Override
	public void update(T bean) {
		this.getConcreteDao().update(parserBean(bean));
	}

	@Override
	public void delete(T bean) {
		this.getConcreteDao().delete(parserBean(bean));
	}

	/////////////////////////////////////////////////////////////////////////////////
	// PUBLIC METHODS (GET)
	/////////////////////////////////////////////////////////////////////////////////

	public final Class<T> getTypeOfBean() {
		return this.typeOfBean;
	}

	public final Class<K> getTypeOfEntity() {
		return this.typeOfEntity;
	}

	public final Class<IDE> getTypeOfKeyEntity() {
		return this.typeOfKeyEntity;
	}

	public final Class<IDB> getTypeOfKeyBean() {
		return this.typeOfKeyBean;
	}

	/////////////////////////////////////////////////////////////////////////////////
	// PUBLIC METHODS
	/////////////////////////////////////////////////////////////////////////////////

	public final T parserEntity(K entity) {
		return ParserDatabase.parserEntityToBean(entity, getTypeOfBean());
	}

	public final K parserBean(T bean) {
		return ParserDatabase.parserBeanToEntity(bean, getTypeOfEntity());
	}

	public final IDB parserKeyEntity(IDE idEntity) {
		return ParserDatabase.parserKeyEntityToKeyBean(idEntity, getTypeOfKeyBean());
	}

	public final IDE parserKeyBean(IDB idBean) {
		return ParserDatabase.parserKeyBeanToKeyEntity(idBean, getTypeOfKeyEntity());
	}

	public final List<T> parserEntity(List<K> entities) {
		return ParserDatabase.parserListEntitiesToBeans(entities, getTypeOfBean());
	}

	public final List<K> parserBean(List<T> beans) {
		return ParserDatabase.parserListBeansToEntities(beans, getTypeOfEntity());
	}

}

Entretanto, existe um problema na implementação da GenericServiceWithKey. Na chamada por reflexão da super classe não necessariamente teremos um classe GenericService ou GenericServiceWithKey. Sendo assim, é preciso que recursivamente seja(m) recuperada(s) a(s) super(s) classe(s), e assim sejam extraídos os tipos genéricos. Em um próximo post irei falar sobre a solução deste problema. 

A partir dessas classes podemos criar os serviços. A seguir apresento um exemplo de serviço desenvolvida seguindo estes conceitos.

import javax.inject.Inject;
import javax.inject.Named;

import br.com.wpattern.northwind.database.entities.CustomerCustomerDemoEntity;
import br.com.wpattern.northwind.database.entities.keys.CustomerCustomerDemoKeyEntity;
import br.com.wpattern.northwind.database.interfaces.ICustomerCustomerDemoDao;
import br.com.wpattern.northwind.database.utils.GenericServiceWithKey;
import br.com.wpattern.northwind.database.utils.interfaces.IGenericDao;
import br.com.wpattern.northwind.utils.database.beans.CustomerCustomerDemoBean;
import br.com.wpattern.northwind.utils.database.interfaces.ICustomerCustomerDemoService;
import br.com.wpattern.northwind.utils.database.keys.CustomerCustomerDemoKeyBean;

@Named
public class CustomerCustomerDemoService extends GenericServiceWithKey<CustomerCustomerDemoBean, CustomerCustomerDemoEntity, CustomerCustomerDemoKeyBean, CustomerCustomerDemoKeyEntity>
implements ICustomerCustomerDemoService {

	@Inject
	private ICustomerCustomerDemoDao customerCustomerDemoDao;

	@Override
	protected IGenericDao<CustomerCustomerDemoEntity, CustomerCustomerDemoKeyEntity> getConcreteDao() {
		return this.customerCustomerDemoDao;
	}

}

Um melhoria que estou planejando de fazer é de recuperar no próprio abstract a instância do DAO. O código completo do projeto que estou trabalhando pode ser encontrada em wpattern.codeplex.com (sample projects => java => wpattern-northwind-sample => wpattern-northwind-all).

Nos próximos posts irei explicar em detalhes este sistema.

Até mais a todos.

WPattern

O objetivo deste blog é de disponibilizar os resultados dos meus estudos. Não será tratada apenas uma tecnologia ou área, mas qualquer tópico que seja interessante e possa contribuir com a comunidade de desenvolvedores, comunidade científica e meus alunos. Entretanto, meus trabalhos sempre estão voltado principalmente para as tecnologias .NET, Java e C/C++.

Posts Recentes

Google Translator

JetBrains

JetBrains: Ferramentas de desenvolvimento com .NET.

ReSharper: Excelente ferramenta para .NET.