Category Archives: Português

JPA Uma Tabela por Classe

EXISTE UMA NOVA VERSÃO DESSE POST.
CLICK AQUI: http://uaihebert.com/?p=1622&page=14

Olá pessoal, tudo bem?

Vamos falar hoje em como criar uma tabela por classe em uma herança onde cada tabela tem apenas as colunas da sua classe. No último post foi visto como persistir toda a herança em uma tabela apenas.

Irei utilizar o mesmo exemplo do ultimo post já colocando aqui como deve ser o código correto para utilizar essa abordagem.

Ao final do post você encontrará o link para download do projeto de hoje.

Click nos links a seguir para ver o último post sobre o assunto ou qualquer outro post sobre JPA: JPA Uma tabela por herança, Ordernando listas e utilizando Map como atributo mapeado, @ManyToMany Unidirecional e Bidirecional, JPA @OneToMany e @ManyToOne Unidirecional e Bidirecional, @OneToOne Unidirecional e Bidirecional, Mapeando Duas Tabelas em uma Classe, Mapeando Datas (Date) e Enum, Chave Primária Composta, SequenceGenerator, TableGenerator – Chave Primária Simples, Auto Create Schema Script com: Ant, Hibernate 3 e JPA 2, Tutorial Hibernate 3 com JPA 2.

Veja como ficará nosso código:

package com.model;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Inheritance;
import javax.persistence.InheritanceType;
import javax.persistence.Table;

@Entity
@Table(name="CAR")
@Inheritance(strategy=InheritanceType.JOINED)
public abstract class Car {
	@Id
	@GeneratedValue(strategy=GenerationType.AUTO)
	private int id;
	private String name;

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}
}
package com.model;

import javax.persistence.Entity;

@Entity
public class Beetle extends Car {
	private int gasCapacity;

	public void talk(){
		System.out.println("Herbie!");
	}

	public int getGasCapacity() {
		return gasCapacity;
	}

	public void setGasCapacity(int gasCapacity) {
		this.gasCapacity = gasCapacity;
	}
}
package com.model;

import javax.persistence.Entity;

@Entity
public class Ferrari extends Car {
	private int model;

	// The newest new models will fly... some day! =P
	public void fly() {

	}

	public int getModel() {
		return model;
	}

	public void setModel(int model) {
		this.model = model;
	}
}

Após executar a classe Main, veja como nossas tabelas ficaram:

Agora para cada tabela temos os dados pertinentes a sua respectiva classe.

Gostaria de deixar apenas uma alerta.

Repare que os IDs de todas as nossas classes têm o nome “id”. Caso sua classe tenha um id diferente de sua super classe, será necessário mapear de modo diferente.

Imagine que nossa classe Beetle teria seu id como beetleId, nosso mapeamento teria que apresentar a seguinte anotação a mais:

package com.model;

import javax.persistence.Entity;
import javax.persistence.PrimaryKeyJoinColumn;

@Entity
@PrimaryKeyJoinColumn(name="beetleId")
public class Beetle extends Car {

	private int gasCapacity;

	public void talk() {
		System.out.println("Herbie!");
	}

	public int getGasCapacity() {
		return gasCapacity;
	}

	public void setGasCapacity(int gasCapacity) {
		this.gasCapacity = gasCapacity;
	}
}


Repare que a chave primária agora tem o id com nome diferente da classe Car e Ferrari.

Lembre-se: se sua classe filha tem o nome do campo de id diferente da classe pai, você terá que usar a anotação @PrimaryKeyJoinColumn onde o campo “name” indica qual a coluna a ser mapeada. E caso você queira alterar qual a coluna de ligação na classe pai, basta você utilizar o parâmetro “referencedColumnName”.

Finalizando, vejamos vantagens e desvantagens:

  • Vantagem: Você poderá ter campos not null em sua classe ao contrário da estratégia de uma tabela para toda a herança. E tem vantagens em sobre o próximo tipo mapeamento que veremos: “Uma tabela para cada Sub-Classe com todos os campos da herança”. E segundo o livro “Enterprise JavaBens 3.0” essa estratégia será melhor do que a “Uma tabela para cada Sub-Classe com todos os campos da herança” caso SQL Union não seja suportado por seu banco de dados.
  • Desvantagem: Seu desempenho é pior do que a estratégia de Uma Tabela Para toda a Herança.

Clique aqui para fazer o download do post de hoje. Não esqueça de alterar a senha de conexão e o usuário.

Espero que esse post possa te ajudar.

Qualquer dúvida/colocação basta postar.

Até! \o_

JSF Exibindo Objeto e Mensagens após Redirect

Olá, tudo bem?

Vamos falar hoje sobre como enviar uma mensagem utilizando JSF após um redirect. O redirect acaba por eliminar os dados da requisição original impossibilitando a exibição de alguma informação.

Essa situação é muito comum quando se utiliza Ajax para navegar entre páginas, e uma alternativa para forçar a troca de páginas, é utilizar o redirect.

Outros posts de JSF/Web você poderá encontrar aqui: Validação de Login de Usuário com JSF e JAASJSF: Converter and Bean AutoCompleteJSF – Hello World, AutoCompleteTratando Exceções em uma Aplicação WebAutenticação de Usuários (Filter/Servlet)Criando um WebServer.

Ao final do post você poderá fazer o download do código do post de hoje.

Vou abordar dois modos para envio de mensagem e/ou objeto após um redirect. Utilizando o Flash (API do JSF 2.0) ou então criando uma classe para controlar o os ciclos (JSF 1.2 para cima).

Abaixo, as imagens mostrando a navegação e a classe do nosso projeto.

/*******************************************************************************
 * Copyright (c) 2010 Red Hat, Inc.
 * Distributed under license by Red Hat, Inc. All rights reserved.
 * This program is made available under the terms of the
 * Eclipse Public License v1.0 which accompanies this distribution,
 * and is available at http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     Red Hat, Inc. - initial API and implementation
 ******************************************************************************/
package demo;

import javax.faces.bean.ManagedBean;
import javax.faces.bean.RequestScoped;

/**
 * Created by JBoss Tools
 */
@ManagedBean(name="user")
@RequestScoped
public class User {
	private String name;

	public User() {
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String sayHello() {
		return "greeting";
	}
}
<?xml version="1.0" encoding="UTF-8"?>

<faces-config
    xmlns="http://java.sun.com/xml/ns/javaee"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_2_0.xsd"
	version="2.0">

	<navigation-rule>
		<from-view-id>/pages/inputname.xhtml</from-view-id>
		<navigation-case>
			<from-outcome>greeting</from-outcome>
			<to-view-id>/pages/greeting.xhtml</to-view-id>
		</navigation-case>
	</navigation-rule>

    <application>
        <resource-bundle>
            <base-name>resources</base-name>
            <var>msgs</var>
        </resource-bundle>
    </application>
</faces-config>

Agora, faremos uma pequena alteração para que a ação de ir para página final seja através de um redirect. O código abaixo demonstra como ficará nosso faces-config.xml e o resultado dessa alteração (note a linha 14).

<?xml version="1.0" encoding="UTF-8"?>

<faces-config
    xmlns="http://java.sun.com/xml/ns/javaee"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_2_0.xsd"
	version="2.0">

	<navigation-rule>
		<from-view-id>/pages/inputname.xhtml</from-view-id>
		<navigation-case>
			<from-outcome>greeting</from-outcome>
			<to-view-id>/pages/greeting.xhtml</to-view-id>
			<redirect/>
		</navigation-case>
	</navigation-rule>

    <application>
        <resource-bundle>
            <base-name>resources</base-name>
            <var>msgs</var>
        </resource-bundle>
    </application>
</faces-config>


Onde o Peter foi parar? É isso que acontece quando utilizamos um redirect e o objeto não está na sessão. Para consertar esse “problema” vamos utilizar o não tão famoso Flash Scope.

Veja como o código da classe User ficará (repare nas linhas 38,39):

/*******************************************************************************
 * Copyright (c) 2010 Red Hat, Inc.
 * Distributed under license by Red Hat, Inc. All rights reserved.
 * This program is made available under the terms of the
 * Eclipse Public License v1.0 which accompanies this distribution,
 * and is available at http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     Red Hat, Inc. - initial API and implementation
 ******************************************************************************/
package demo;

import javax.faces.bean.ManagedBean;
import javax.faces.bean.RequestScoped;

import com.sun.faces.context.flash.ELFlash;

/**
 * Created by JBoss Tools
 */
@ManagedBean(name="user")
@RequestScoped
public class User {
	private String name;

	public User() {
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String sayHello() {
		// Put the object inside the flash scope
		ELFlash.getFlash().put("user", this);
		return "greeting";
	}
}

E para acessar o objeto usuário (user), basta alterar a página:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:ui="http://java.sun.com/jsf/facelets"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core">

	<ui:composition template="/templates/common.xhtml">
			<ui:define name="pageTitle">Greeting to User</ui:define>
			<ui:define name="pageHeader">Greeting Page</ui:define>
			<ui:define name="body">
				#{msgs.greeting} #{flash.user.name}!
			</ui:define>
	</ui:composition>
</html>

E o Peter voltará a aparecer.

O escopo Flash nada mais faz do que guarda o objeto como se fosse sessão. Atenção para o fato de que após a sua utilização o objeto “user” será removido do escopo. Como manter o objeto no Flash Scope mesmo após um redirect? Fácil, basta você trocar o comando

      #{flash.user.name}

por

      #{flash.keep.user.name}

Desse modo o objeto usuário irá continuar dentro do Flash Scope.

Para JSF 1.2: Infelizmente para se manter um objeto em uma requisição após um redirect no JSF 1.2 (até o dia de hoje), será necessário fazer o controle do objeto na sessão. =/ MAS, caso você queira enviar apenas um alerta para a próxima tela existe outra solução.

Vou criar uma nova página para exibir uma mensagem em forma de alerta “<h:messages/>“. Veja como nosso código ficará:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
	xmlns:ui="http://java.sun.com/jsf/facelets"
	xmlns:h="http://java.sun.com/jsf/html"
	xmlns:f="http://java.sun.com/jsf/core"
	xmlns:ez="http://java.sun.com/jsf/composite/demo">

	<ui:composition template="/templates/common.xhtml">

		<ui:define name="pageTitle">Input User Name</ui:define>

		<ui:define name="pageHeader">JSF 2 Hello Application</ui:define>

		<ui:define name="body">
			<h:message showSummary="true" showDetail="false" style="color: red; font-weight: bold;" for="inputname" />
			<ez:input id="inputname" label="${msgs.prompt}" value="#{user.name}" action="#{user.sayHello}" submitlabel="Say Hello"/>
			<ez:input id="inputname2" label="${msgs.prompt}" value="#{user.name}" action="#{user.sayHelloWithAlert}" submitlabel="Say Hello With Alert"/>
		</ui:define>
	</ui:composition>
</html>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:ui="http://java.sun.com/jsf/facelets"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core">

	<ui:composition template="/templates/common.xhtml">
			<ui:define name="pageTitle">Greeting to User With alert</ui:define>
			<ui:define name="pageHeader">Greeting Page With alert</ui:define>
			<ui:define name="body">
				<h:messages />
			</ui:define>
	</ui:composition>
</html>
/*******************************************************************************
 * Copyright (c) 2010 Red Hat, Inc.
 * Distributed under license by Red Hat, Inc. All rights reserved.
 * This program is made available under the terms of the
 * Eclipse Public License v1.0 which accompanies this distribution,
 * and is available at http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     Red Hat, Inc. - initial API and implementation
 ******************************************************************************/
package demo;

import javax.faces.application.FacesMessage;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.RequestScoped;
import javax.faces.context.FacesContext;

import com.sun.faces.context.flash.ELFlash;

/**
 * Created by JBoss Tools
 */
@ManagedBean(name="user")
@RequestScoped
public class User {
	private String name;

	public User() {
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String sayHello() {
		// Put the object inside the flash scope
		ELFlash.getFlash().put("user", this);
		return "greeting";
	}

	public String sayHelloWithAlert() {
		String message = "Hello : " + name;
		FacesContext context = FacesContext.getCurrentInstance();

		context.addMessage(null, new FacesMessage(FacesMessage.SEVERITY_INFO, message, message));

		return "greetingWithAlert";
	}
}
<?xml version="1.0" encoding="UTF-8"?>

<faces-config
    xmlns="http://java.sun.com/xml/ns/javaee"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_2_0.xsd"
	version="2.0">

	<navigation-rule>
		<from-view-id>/pages/inputname.xhtml</from-view-id>
		<navigation-case>
			<from-outcome>greeting</from-outcome>
			<to-view-id>/pages/greeting.xhtml</to-view-id>
			<redirect/>
		</navigation-case>
		<navigation-case>
			<from-outcome>greetingWithAlert</from-outcome>
			<to-view-id>/pages/greetingWithAlert.xhtml</to-view-id>
		</navigation-case>
	</navigation-rule>

    <application>
        <resource-bundle>
            <base-name>resources</base-name>
            <var>msgs</var>
        </resource-bundle>
    </application>
</faces-config>


E caso utilizemos o redirect a mensagem nossa mensagem não será exibida. Veja o que acontece:

<?xml version="1.0" encoding="UTF-8"?>

<faces-config
    xmlns="http://java.sun.com/xml/ns/javaee"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_2_0.xsd"
	version="2.0">

	<navigation-rule>
		<from-view-id>/pages/inputname.xhtml</from-view-id>
		<navigation-case>
			<from-outcome>greeting</from-outcome>
			<to-view-id>/pages/greeting.xhtml</to-view-id>
			<redirect/>
		</navigation-case>
		<navigation-case>
			<from-outcome>greetingWithAlert</from-outcome>
			<to-view-id>/pages/greetingWithAlert.xhtml</to-view-id>
			<redirect/>
		</navigation-case>
	</navigation-rule>

    <application>
        <resource-bundle>
            <base-name>resources</base-name>
            <var>msgs</var>
        </resource-bundle>
    </application>
</faces-config>


A solução abaixo eu encontrei no site: “ocpsoft.com/java/persist-and-pass-facesmessages-over-page-redirects/”.

O que a classe faz é adicionar sua mensagem ao contexto de sessão, exibe e depois remove a mensagem da sessão. Uma solução bem útil quando se quer prolongar as mensagens.

package demo;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.faces.application.FacesMessage;
import javax.faces.context.FacesContext;
import javax.faces.event.PhaseEvent;
import javax.faces.event.PhaseId;
import javax.faces.event.PhaseListener;

/**
 * Enables messages to be rendered on different pages from which they were set.
 *
 * After each phase where messages may be added, this moves the messages
 * from the page-scoped FacesContext to the session-scoped session map.
 *
 * Before messages are rendered, this moves the messages from the
 * session-scoped session map back to the page-scoped FacesContext.
 *
 * Only global messages, not associated with a particular component, are
 * moved. Component messages cannot be rendered on pages other than the one on
 * which they were added.
 *
 * To enable multi-page messages support, add a <code>lifecycle</code> block to your
 * faces-config.xml file. That block should contain a single
 * <code>phase-listener</code> block containing the fully-qualified classname
 * of this file.
 *
 * @author Jesse Wilson jesse[AT]odel.on.ca
 * @secondaryAuthor Lincoln Baxter III lincoln[AT]ocpsoft.com
 */
public class MultiPageMessagesSupport implements PhaseListener
{

    private static final long serialVersionUID = 1250469273857785274L;
    private static final String sessionToken = "MULTI_PAGE_MESSAGES_SUPPORT";

    public PhaseId getPhaseId()
    {
        return PhaseId.ANY_PHASE;
    }

    /*
     * Check to see if we are "naturally" in the RENDER_RESPONSE phase. If we
     * have arrived here and the response is already complete, then the page is
     * not going to show up: don't display messages yet.
     */
    // TODO: Blog this (MultiPageMessagesSupport)
    public void beforePhase(final PhaseEvent event)
    {
        FacesContext facesContext = event.getFacesContext();
        this.saveMessages(facesContext);

        if (PhaseId.RENDER_RESPONSE.equals(event.getPhaseId()))
        {
            if (!facesContext.getResponseComplete())
            {
                this.restoreMessages(facesContext);
            }
        }
    }

    /*
     * Save messages into the session after every phase.
     */
    public void afterPhase(final PhaseEvent event)
    {
        if (!PhaseId.RENDER_RESPONSE.equals(event.getPhaseId()))
        {
            FacesContext facesContext = event.getFacesContext();
            this.saveMessages(facesContext);
        }
    }

    @SuppressWarnings("unchecked")
    private int saveMessages(final FacesContext facesContext)
    {
        List<FacesMessage> messages = new ArrayList<FacesMessage>();
        for (Iterator<FacesMessage> iter = facesContext.getMessages(null); iter.hasNext();)
        {
            messages.add(iter.next());
            iter.remove();
        }

        if (messages.size() == 0)
        {
            return 0;
        }

        Map<String, Object> sessionMap = facesContext.getExternalContext().getSessionMap();
        List<FacesMessage> existingMessages = (List<FacesMessage>) sessionMap.get(sessionToken);
        if (existingMessages != null)
        {
            existingMessages.addAll(messages);
        }
        else
        {
            sessionMap.put(sessionToken, messages);
        }
        return messages.size();
    }

    @SuppressWarnings("unchecked")
    private int restoreMessages(final FacesContext facesContext)
    {
        Map<String, Object> sessionMap = facesContext.getExternalContext().getSessionMap();
        List<FacesMessage> messages = (List<FacesMessage>) sessionMap.remove(sessionToken);

        if (messages == null)
        {
            return 0;
        }

        int restoredCount = messages.size();
        for (Object element : messages)
        {
            facesContext.addMessage(null, (FacesMessage) element);
        }
        return restoredCount;
    }
}

E por último precisamos alterar o “faces-config.xml”:

<?xml version="1.0" encoding="UTF-8"?>

<faces-config
    xmlns="http://java.sun.com/xml/ns/javaee"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_2_0.xsd"
	version="2.0">

	<navigation-rule>
		<from-view-id>/pages/inputname.xhtml</from-view-id>
		<navigation-case>
			<from-outcome>greeting</from-outcome>
			<to-view-id>/pages/greeting.xhtml</to-view-id>
			<redirect/>
		</navigation-case>
		<navigation-case>
			<from-outcome>greetingWithAlert</from-outcome>
			<to-view-id>/pages/greetingWithAlert.xhtml</to-view-id>
			<redirect/>
		</navigation-case>
	</navigation-rule>
	<lifecycle>
		<phase-listener>demo.MultiPageMessagesSupport</phase-listener>
	</lifecycle>	

    <application>
        <resource-bundle>
            <base-name>resources</base-name>
            <var>msgs</var>
        </resource-bundle>
    </application>
</faces-config>

E ao executar novamente nosso sistema, veja quem voltou… Peter! \o/

Para realizar o download do código fonte do post, clique aqui. Para executar o código você precisará ter o JBoss 6 configurado em seu Eclipse, para isso, terá que instalar o JBoss Tools. Nesse post, a instalação e configuração são abordadas: User Login Validation with JAAS and JSF.

Espero que o post de hoje possa te ajudar.

Qualquer dúvida ou colocação basta postar.

Esse post foi baseado:

  • Livro: “Core JavaServer Faces” Capítulo: “Redirection and Flash”
  • Código gerado pela ferramenta JBoss Tools
  • Código da classe de mensagem persistida “ocpsoft.com/”

Até a próxima! \o_

JPA Uma tabela por herança

EXISTE UMA NOVA VERSÃO DESSE POST.
CLICK AQUI: http://uaihebert.com/?p=1622&page=13

Olá, tudo bem?

Vamos falar hoje sobre como salvar nas tabelas que envolvem herança. Vamos começar pelo modo “Single Table per Class Hierarchy” (Uma tabela para toda herança).

Os outros assuntos já tratados sobre JPA: Ordernando listas e utilizando Map como atributo mapeado, @ManyToMany Unidirecional e Bidirecional, JPA @OneToMany e @ManyToOne Unidirecional e Bidirecional, @OneToOne Unidirecional e Bidirecional, Mapeando Duas Tabelas em uma Classe, Mapeando Datas (Date) e Enum, Chave Primária Composta, SequenceGenerator, TableGenerator – Chave Primária Simples, Auto Create Schema Script com: Ant, Hibernate 3 e JPA 2, Tutorial Hibernate 3 com JPA 2.

Ao final do post de hoje você irá encontrar o código fonte para download.

Vamos utilizar um sistema de carros onde teremos a classe principal e outras duas classes herdando da classe pai. Vejamos nosso código inicial:

package com.model;

public abstract class Car {
	private int id;
	private String name;

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}
}
package com.model;

public class Beetle extends Car {
	private int gasCapacity;

	public void talk(){
		System.out.println("Herbie!");
	}

	public int getGasCapacity() {
		return gasCapacity;
	}

	public void setGasCapacity(int gasCapacity) {
		this.gasCapacity = gasCapacity;
	}

}
package com.model;

public class Ferrari extends Car {
	private int model;

	// The newest new models will fly... some day! =P
	public void fly() {

	}

	public int getModel() {
		return model;
	}

	public void setModel(int model) {
		this.model = model;
	}
}

Temos um modelo bem simples onde queremos persistir todas as informações em uma tabela. No exemplo de hoje irei utilizar o Postgres como banco de dados para exibir no final como os dados ficarão gravados no banco de dados.

Coloquemos as anotações JPA para que a herança seja persistida:

package com.model;

import javax.persistence.DiscriminatorColumn;
import javax.persistence.DiscriminatorType;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Inheritance;
import javax.persistence.InheritanceType;
import javax.persistence.Table;

@Entity
@Table(name="CAR")
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name="FROM_CLASS", discriminatorType=DiscriminatorType.STRING)
public abstract class Car {
	@Id
	@GeneratedValue(strategy=GenerationType.AUTO)
	private int id;
	private String name;

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}
}
package com.model;

import javax.persistence.Entity;

@Entity
public class Beetle extends Car {
	private int gasCapacity;

	public void talk(){
		System.out.println("Herbie!");
	}

	public int getGasCapacity() {
		return gasCapacity;
	}

	public void setGasCapacity(int gasCapacity) {
		this.gasCapacity = gasCapacity;
	}
}
package com.model;

import javax.persistence.Entity;

@Entity
public class Ferrari extends Car {
	private int model;

	// The newest new models will fly... some day! =P
	public void fly() {

	}

	public int getModel() {
		return model;
	}

	public void setModel(int model) {
		this.model = model;
	}
}

Algumas explicações sobre o código acima:

  • A anotação “@Inheritance” indica ao JPA qual o tipo de herança iremos utilizar ao persistir as informações.
  • A anotação “@DiscriminatorColumn” é uma coluna onde ficará armazenado um valor descrevendo a qual classe pertence aquela linha.

Com a classe abaixo, vamos inserir dados no banco de dados e ver como ficou.

package com;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;

import com.model.Beetle;
import com.model.Ferrari;

public class Main {

	public static void main(String[] args) {
		EntityManagerFactory emf = Persistence.createEntityManagerFactory("Database");
		EntityManager em = emf.createEntityManager();

		em.getTransaction().begin();

		try {
			Ferrari ferrari = new Ferrari();
			ferrari.setModel(455);
			ferrari.setName("WWJD");

			Beetle beetle = new Beetle();
			beetle.setGasCapacity(46);
			beetle.setName("Heeerbie");

			em.persist(ferrari);
			em.persist(beetle);
		} catch (Exception ex) {
			ex.printStackTrace();
		} finally {
			em.getTransaction().commit();
			emf.close();
		}

		System.out.println("It is over");
	}

}


Repare que na tabela apareceu uma coluna chamada “from_class” que indica à qual classe pertence à respectiva linha.

E para finalizar, como faríamos para alterar o valor salvo na no coluna? E se quisemos mudar o nome de como o valor da classe será salvo? Ao invés de Ferrari for CAR_FERRARI, como faríamos?

Basta editar a classe e deixar conforme o código abaixo:

package com.model;

import javax.persistence.DiscriminatorValue;
import javax.persistence.Entity;

@Entity
@DiscriminatorValue(value="CAR_BEETLE")
public class Beetle extends Car {
	private int gasCapacity;

	public void talk(){
		System.out.println("Herbie!");
	}

	public int getGasCapacity() {
		return gasCapacity;
	}

	public void setGasCapacity(int gasCapacity) {
		this.gasCapacity = gasCapacity;
	}
}
package com.model;

import javax.persistence.DiscriminatorValue;
import javax.persistence.Entity;

@Entity
@DiscriminatorValue(value="CAR_FERRARI")
public class Ferrari extends Car {
	private int model;

	// The newest new models will fly... some day! =P
	public void fly() {

	}

	public int getModel() {
		return model;
	}

	public void setModel(int model) {
		this.model = model;
	}
}

Execute a classe Main novamente e veja o resultado:


Note que as linhas 1 e 2 estão com os valores antigos. As linhas 3 e 4 já constam os valores da nova anotação inserida “@DiscriminatorValue”.

Vamos falar agora dos contras e prós dessa abordagem:

  • Prós: O desempenho é maior e melhor, pois o JPA não irá necessitar fazer diversos Joins para ter as informações de todas as classes. Para quem está começando a trabalhar com esse tipo de persistência é mais fácil de adaptar e coletar informações no banco de dados.
  • Contras: As subclasses não poderão conter campos “não nullos”. Se a classe Beetle tivesse um atributo não nulo ao se salvar um objeto Ferrari o banco de dados levantaria uma Exception.

Clique aqui para fazer o download do código de hoje. Esteja atento ao fato que para o código rodar você precisará ter o Postgres instalado e com um “database” chamado “SingleTable”. E não esqueça também de configurar o usuário e senha. ^o^

Espero que o post de hoje possa te ajudar.

Qualquer dúvida/colocação basta postar.

Até mais! \o_

Cobrindo seus testes com Cobertura, JUnit, HSQLDB, JPA

Olá tudo bem?

Vamos falar hoje sobre uma ferramenta muito importante chamada Cobertura. Esse framework tem a mesma função de cobertura de testes que o framework Emma, já mostrado aqui.

A diferença é que a tela inicial do framework Cobertura nos mostra os dados de um modo mais gráfico.

Caso você queira ver os outros posts sobre esse assunto,basta clicar nos links a seguir: Cobertura de testes com JUnit Ant e Emma, JUnit com HSQLDB, JPA e Hibernate, Primeiros passos.

Vou utilizar exatamente o código do post JUnit com HSQLDB, JPA e Hibernate. Caso você queira montar o ambiente corretamente, basta fazer o passo a passo que se encontra lá (no fim do post você irá encontrar o código fonte para download).
Eu não sou um usuário habilidoso do ant, você poderá ver algum código e pensar: “ele poderia ter usado o comando xyk”. Fique a vontade para sugerir. ;)

Vamos fazer o download da biblioteca do framework CoberturaDownload1.9.4.1Faça o download e coloque todos os arquivos jars (/cobertura.jar, /lib/* que tem os arquivos asm, jakarta, log4j) dentro na pasta lib do nosso projeto.

Na raiz do seu projeto crie um arquivo chamado “build.xml”com o seguinte código (tem que estar exatamente na raiz do projeto para funcionar):

<project name="Cobertura Coverage" basedir=".">

    <!--  Project Source  Code -->
    <property name="src.dir" value="src" />
    <property name="build.dir" value="bin" />
    <property name="teste.dir" value="src/test" />
    <property name="lib.dir" value="lib" />
    <property name="report.dir" value="cobertura" />

    <!-- Project classpath -->
    <path id="project.classpath">
        <pathelement location="${bin.dir}" />
        <fileset dir="${lib.dir}">
            <include name="*.jar" />
        </fileset>
    </path>

    <!-- Tested Class -->
    <property name="DogFacadeTest" value="test.com.facade.DogFacadeTest" />

</project>

No código acima, estamos apenas declarando os caminhos para o código fonte e as bibliotecas.

Agora vamos criar uma tarefa que irá apagar arquivos criados pelo framework Cobertura e pela compilação do nosso projeto.

<!-- Clears the paths -->
<target name="01-CleannUp" description="Remove all generated files.">
    <delete dir="${build.dir}" />
    <delete file="cobertura.ser" />
    <delete dir="${report.dir}" />

    <mkdir dir="${build.dir}" />
    <mkdir dir="${report.dir}" />
</target>

E para compilar seu código Java, adicione o código abaixo e depois execute para ter a certeza de que está compilando:

<!-- Compiles the Java code -->
<target name="02-Compile" depends="01-CleannUp" description="invoke compiler">
    <javac debug="true" debuglevel="vars,lines,source" srcdir="${src.dir}" destdir="${build.dir}">
        <classpath refid="project.classpath" />
    </javac>
    <copy file="${src.dir}/META-INF/persistence.xml" todir="${build.dir}/META-INF" />
</target>

Vamos configurar o framework Cobertura para que ele possa instrumentar as classes de testes e já preparar o ambiente:

<!-- Cobertura configs -->
<property name="cobertura.instrumented-classes.dir" value="${report.dir}/instrumented-classes" />
<property name="cobertura.data.file" value="cobertura.ser" />
<path id="cobertura.classpath">
    <fileset dir="${lib.dir}" includes="/*.jar" />
</path>

<!-- Points to the cobertura jar -->
<taskdef classpath="${lib.dir}/cobertura.jar" resource="tasks.properties" classpathref="cobertura.classpath" />

<!-- Instruments the classes -->
<target name="03-Instrument" depends="02-Compile">
    <delete quiet="false" failonerror="false">
        <fileset dir="${cobertura.instrumented-classes.dir}" />
    </delete>
    <delete file="${cobertura.data.file}" />
    <cobertura-instrument todir="${cobertura.instrumented-classes.dir}">
        <fileset dir="${build.dir}">
            <include name="**/*.class" />
            <exclude name="**/*Test.class" />
        </fileset>
    </cobertura-instrument>
    <copy todir="${cobertura.instrumented-classes.dir}">
        <fileset dir="${src.dir}" casesensitive="yes">
            <patternset id="resources.ps" />
        </fileset>
    </copy>
</target>

Adicione o código abaixo e vamos executar o JUnit pelo “ant.xml”:

<!-- Set up the instrumented classes path -->
<path id="cover-test.classpath">
    <fileset dir="${lib.dir}" includes="**/*.jar" />
    <pathelement location="${cobertura.instrumented-classes.dir}" />
    <pathelement location="${build.dir}" />
</path>

<!-- Run the JUnit test -->
<target name="04-RunTest" depends="03-Instrument" >
    <junit printsummary="yes" haltonerror="no" haltonfailure="no"  fork="yes">
        <batchtest>
            <fileset dir="${build.dir}" includes="**/*Test.class" />
        </batchtest>
        <classpath refid="cover-test.classpath" />
    </junit>
    <delete file="transaction.log" />
</target>

E por último, vamos executar gerar nosso relatório. Adicione o código abaixo ao “build.xml” e execute a task.

<!-- Creates the Cobertura report -->
<target name="00-CreateReport" depends="04-RunTest">
    <cobertura-report srcdir="${cobertura.data.file}" destdir="${report.dir}">
        <fileset dir="${src.dir}">
            <include name="**/*.java" />
        </fileset>
    </cobertura-report>
    <delete dir="${report.dir}/instrumented-classes" />
    <delete file="cobertura.ser"  />
</target>

Atualize seu projeto no Eclipse (F5 no projeto) e você verá que os arquivos do relatório foram criados com sucesso. Abra o arquivo “index.html” que estará dentro da pasta cobertura.




O framework Cobertura inclusive mostra quantas vezes você tem que testar um método. Como desafio, escreva o método equals da classe Dog,gere o relatório só para você conferir que não está sendo coberto. E crie seus testes e olhe depois a cobertura do seu método como ficou.

Você poderá fazer o download do código do post de hoje.

Qualquer dúvida/comentário basta postar.

Até a próxima! \o_

Lei de Demeter

Olá pessoal, tudo bem?

Hoje vamos falar sobre a Lei de Demeter. É um pattern para orientação objeto que nos ajuda e muito a diminuir o acoplamento, aumentar a manutenibilidade e adaptabilidade de nossos sistemas.

Onde essas palavras difíceis nos ajuda? Ao dar manutenção,sua aplicação não irá sofrer tanto impacto, suas classes irão conhecer apenas a quem deve e com isso as alterações serão mais rápidas e com menos impacto no sistema.

Tudo de bom não é mesmo? Calma aí que depois nós vamos chegar à desvantagem dessa abordagem! (:

Para ver outro post sobre OO basta clicar no link: “Diga, não pergunte!“.

Olhe o código abaixo, em que ele pode ser melhorado?

package com;

public class Main {

    public static void main(String[] args) {
        IAddress address = new Address();
        address.setName("01");
        address.setZipCode("000001");

        IHouse house = new House();
        house.setAddress(address);

        IPerson person = new Person();
        person.setHouse(house);

        // Print the person zip code
        System.out.println(person.getHouse().getAddress().getZipCode());
    }
}

O código acima executa tranquilamente, estamos programando para Interface, nosso código está bem identado e formatado. O que mais poderia melhorar em nosso código?

A Lei de Demeter prega que uma classe não deve conhecer mais do que a quem está sendo invocado. HEIN?! Vamos por partes; repare nossa classe que nossa classe Main quer imprimir o CEP da pessoa, mas para fazer isso a classe Main toma conhecimento de outras duas classes. Caso você não tenha percebido, aí está seu acoplamento!

Repare a classe Main está passando por dentro da classe Person, House e finalmente Address.
Qual a parte ruim dessa história? Imagine que por uma determinação da empresa, a classe Address deixa de existir e a classe House será a responsável por conter o ZipCode.

Em nosso exemplo, será muito fácil fazer essa alteração.Imagine um sistema enorme onde o comando para buscar o ZipCode fosse utilizado quase 100 vezes. Você teria que alterar em 100 lugares diferentes por causa dessa alteração.

A lei de Demeter veio para nos ajudar nesse ponto, com uma pequena alteração em nossa classe Person e House, nós poderemos evitar esse enorme impacto na hora da remoção da classe Address. Veja como nosso código ficará:

package com;

public class Main {

    public static void main(String[] args) {
        IAddress address = new Address();
        address.setName("01");
        address.setZipCode("000001");

        IHouse house = new House();
        house.setAddress(address);

        IPerson person = new Person();
        person.setHouse(house);

        // Print the person zip code
        System.out.println(person.getZipCode());
    }
}
package com;

public interface IPerson {

    void setHouse(IHouse house);

    IHouse getHouse();

    String getZipCode();

}
package com;

public class Person implements IPerson {
    
    private IHouse house;

    @Override
    public void setHouse(IHouse house) {
        this.house = house;
    }

    @Override
    public IHouse getHouse() {
        return house;
    }

    @Override
    public String getZipCode() {
        return house.getZipCode();
    }
}
package com;

public interface IHouse {

    void setAddress(IAddress address);

    IAddress getAddress();

    String getZipCode();

}
package com;

public class House implements IHouse {

    private IAddress address;

    @Override
    public void setAddress(IAddress address) {
        this.address = address;
    }

    @Override
    public IAddress getAddress() {
        return address;
    }

    @Override
    public String getZipCode() {
        return address.getZipCode();
    }
}
package com;

public interface IAddress {

    void setName(String string);

    void setZipCode(String string);

    String getZipCode();

    public abstract String getName();

}
package com;

public class Address implements IAddress {

    private String name;
    private String zipCode;

    @Override
    public void setName(String name) {
        this.name = name;
    }

    @Override
    public void setZipCode(String zipCode) {
        this.zipCode = zipCode;
    }

    @Override
    public String getName() {
        return name;
    }

    @Override
    public String getZipCode() {
        return zipCode;
    }
}

Olhando pelo novo código, imagine qual seria o impacto em nosso código para remover a classe Address. Apenas a classe Home seria alterada, o restante de nosso sistema permaneceria intacto.

Essa é a grande vantagem da Lei de Demeter. Na hora da manutenção seu projeto inteiro terá o menor impacto possível. As adaptações solicitadas pelos responsáveis serão mais simples, menos custosas. No exemplo de hoje, apenas uma classe seria afetada. As demais classes do seu sistema continuariam não sendo afetada se seu sistema continuará com baixo acoplamento.

A desvantagem dessa abordagem é o impacto sobre o desempenho. Você poderia ter uma queda de desempenho caso utilize essa abordagem em um loop como “While, For, …”. Nesse caso, você terá que ver em quais partes do sistema não haveria impacto ao aplicar a lei de Demeter.

Creio que mesma com essa desvantagem sobre o desempenho em alguns lugares localizados, essa abordagem vale a pena ser aplicada em nossos sistemas; a lei de Demeter pode ser utilizada em quase todo o sistema, sendo deixada de lado apenas em locais críticos.

Espero que este post possa te ajudar.

Qualquer dúvida/colocação basta postar.

Até a próxima! \o_

TDD Cobertura de testes com JUnit Ant e Emma

Olá pessoal, bom dia.

Quando escrevemos testes temos sempre a preocupação de saber se estamos testando toda nossa classe. Imagine só, testar 4 métodos e esquecer de simular um caso de teste que caia em uma Exception do Try/Catch?

Para solucionar esse problema existem ferramentas para cobrir seu teste, que irão informar por onde seu teste passou ou deixou de passar.

Vou falar sobre duas ferramentas para cobertura do seu teste Emma (no post de hoje) e Cobertura (no próximo post sobre TDD).

Vou usar exatamente o código deste post (TDD com HSQLDB, JPA e Hibernate). Se você quiser montar um ambiente, siga o que for descrito no post anterior e tudo irá funcionar 100%. Outro post sobre o assunto é: TDD – Primeiros passos.

Hoje iremos utilizar o framework Emma com Ant. Não sou um bom conhecedor ainda do Ant, então os códigos que “garimpei” pela internet talvez tenha alguma redundância ou coisa assim. Um dia irei melhorá-lo.

Vamos começar com os downloads:

O JUnit é utilizado apenas pelo o framework do Emma possa executar os testes.

Copie as bibliotecas do Emma e do JUnit para a pasta lib; não será necessário adicioná-los ao “Build Path” do projeto.

Crie um arquivo chamado “build.xml” na raiz do seu projeto (
o arquivo tem que ficar exatamente na raiz do projeto, ou então o script irá falhar)
.

Altera altere o arquivo para ficar com o seguinte código:

<project name="Emma Reports" basedir=".">

    <!--  Project Source  Code -->
    <property name="src.dir" value="src" />
    <property name="bin.dir" value="bin" />
    <property name="teste.dir" value="src/test" />
    <property name="lib.dir" value="lib" />

    <!-- Emma source code -->
    <property name="emma.bin.dir" value="emma/bin" />
    <property name="emma.metadate.dir" value="emma/metadate" />
    <property name="emma.report.dir" value="emma/report" />

    <!-- Tested Class -->
    <property name="DogFacadeTest" value="test.com.facade.DogFacadeTest"/>

    <!-- Project classpath -->
    <path id="project.classpath">
        <pathelement location="${bin.dir}" />
        <fileset dir="${lib.dir}">
            <include name="*.jar" />
        </fileset>
    </path>

    <!-- Emma task definitions that you will find inside the jar -->
    <taskdef resource="emma_ant.properties">
        <classpath refid="project.classpath" />
    </taskdef>

    <!-- JUnit task definition -->
    <taskdef name="junit" classname="org.apache.tools.ant.taskdefs.optional.junit.JUnitTask" />

</project>

No arquivo build.xml estamos definindo as variáveis do script, os caminhos dos arquivos e o JUnit a ser executado.

Agora vá ao menu Window > Show View > Other. Digite Ante aperte Ok:


Arraste o arquivo para o dentro do da view do Ant. Duplo click no arquivo para executá-lo. A seguinte mensagem irá aparecer:

Caso a mensagem “Could not load definitions from resource emma_ant.properties. It could not be found.” É por que o caminho da sua lib está errado. Verifique novamente se o seu arquivo“build.xml” está na raiz do projeto. Não prossiga sem resolver esse problema de path.

Vamos alterar nosso “build.xml” e fazer com que ele a partir de agora compile nosso código java. Ele tem dois passos (ações chamadas de “target”).

    <!-- Clean UP Your Code -->
    <target name="01-CleanUp">
        <delete dir="${bin.dir}" />
        <mkdir dir="${bin.dir}" />
    </target>

    <!-- Compile Your Code -->
    <target name="02-CompileSourceCode" depends="01-CleanUp">
        <javac debug="on" srcdir="${src.dir}" destdir="${bin.dir}">
            <classpath refid="project.classpath" />
        </javac>
        <copy file="${src.dir}/META-INF/persistence.xml" todir="${bin.dir}/META-INF" />
    </target>

Perceba que temos as ações “01-CleanUp” e “02-CompileSourceCode”onde a ação de compilar “depends” da ação de limpar o código, com isso, toda vez que você quiser compilar o projeto o Ant irá executar a ação de limpar automaticamente.

Vamos gerar agora a task com uma palavra que você irá ouvir muito, Instrumentação. Falando vagamente, o Emma ele é bytecode instrumentado,ou seja, ele não necessita de adaptações ou alterações na JVM para mensurar o seu código. Ele analisa o seu bytecode e pronto.

Vamos alterar o build.xml e adicionar a task:

    <!-- Generate the Emma  -->
    <target name="03-Instrumentation" depends="02-CompileSourceCode">
        <emma>
            <instr instrpath="${bin.dir}" destdir="${emma.bin.dir}" metadatafile="${emma.metadate.dir}/metadate.emma" merge="false" mode="fullcopy" />
        </emma>
    </target>

Vamos criar agora, a chave que irá compilar e executar o teste JUnit:

    <!-- Runs JUnit Tests -->
    <target name="04-RunTests" depends="03-Instrumentation">
        <junit haltonfailure="false" haltonerror="false" fork="true">
            <classpath>
                <pathelement location="${emma.bin.dir}/classes" />
                <pathelement location="${emma.bin.dir}/lib" />
                <path refid="project.classpath" />
            </classpath>
            <formatter type="plain" usefile="false" />
            <test name="${DogFacadeTest}" />
            <jvmarg value="-Demma.coverage.out.file=${emma.metadado.dir}/cobertura.emma" />
            <jvmarg value="-Demma.coverage.out.merge=false" />
        </junit>
    </target>

Execute o teste e veja que o nosso teste não retornou erro:

Vamos criar agora o nosso gerador de relatório:

    <!-- Creates the report -->
    <target name="00-GenerateReport" depends="04-RunTests">
        <delete dir="${emma.report.dir}" />
        <emma enabled="true">
            <report sourcepath="${src.dir}" sort="+block,+name,+method,+class" metrics="method:70,block:80,line:80,class:100">
                <fileset dir="${emma.metadate.dir}">
                    <include name="*.emma" />
                </fileset>
                <html outfile="${emma.report.dir}/report.html" depth="method" columns="name,class,method,block,line" />
            </report>
        </emma>
    </target>

Execute a tarefa de Gerar relatório e abra o arquivo que irá aparecer em “
/emma/report/report.html
”.


E por ultimo vamos apagar os arquivos que foram gerados para que o relatório fosse extraído.

Altere a task para gerar relatório e vamos criar uma task que irá ser chamada automaticamente pelo Ant após gerar um relatório:

    <!-- Creates the report -->
    <target name="00-GenerateReport" depends="04-RunTests">
        <delete dir="${emma.report.dir}" />
        <emma enabled="true">
            <report sourcepath="${src.dir}" sort="+block,+name,+method,+class" metrics="method:70,block:80,line:80,class:100">
                <fileset dir="${emma.metadate.dir}">
                    <include name="*.emma" />
                </fileset>
                <html outfile="${emma.report.dir}/report.html" depth="method" columns="name,class,method,block,line" />
            </report>
        </emma>

        <antcall target="05-DeleteOldReportData" />
    </target>

    <!-- Delete Old Report Data -->
    <target name="05-DeleteOldReportData">
        <delete dir="${emma.bin.dir}" />
        <delete dir="${emma.metadate.dir}" />
    </target>

Click aqui para fazer o download do tutorial de hoje.
Espero que esse post possa te ajudar.

Qualquer dúvida ou comentário, basta colocar.

Inté+! \o_

OBS.: Fonte de pesquisa:

JUnit com HSQLDB, JPA e Hibernate

Olá, bom dia.

Vamos falar hoje em como integrar seus testes unitários utilizando um banco de dados? Uma das melhores soluções do mercado atualmente é utilizarum banco de dados em memória.

O banco de dados HSQLDB faz todo esse trabalho de, criar aestrutura de tabelas, relacionamento entre as chaves e permitir que o JPAconsiga trabalhar sem problemas.

Nesse tutorial vamos ver como criar teste unitário (TDD) comJPA e HSQLDB.

Para ver o outro post sobre TDD você pode clicar aqui: TDD – Primeiros passos.

Você irá precisar fazer o download do JAR do HSQLDB aqui (hsqldb.org/Versão2.25 - Última versão).

Vou utilizar o Hibernate como provider do JPA. Nessetutorial (Tutorial Hibernate 3 com JPA 2) você irá encontrar os links necessários para download.

Crie um projeto Java em File > New Project > JavaProject.

Crie uma pasta lib e dentro dela coloque todas asbibliotecas necessárias dentro dessa pasta. Os arquivos são as bibliotecas do HSQLB e do Hibernate.

Vou resumir daqui para frente, mas no tutorial (Tutorial Hibernate 3 com JPA 2) mostracomo colocar seu projeto para executar com sucesso o Hibernate.

Clique com o botão direito do mouse sobre o Projeto >Properties. Vá a Java Build Path > aba Libraries > Add Jars.Selecione as bibliotecas que lá estão e aperte Ok.

Crie uma pasta chamada “src/META-INF” e dentro dela coloqueo seu persistence.xml:

<?xml version="1.0" encoding="UTF-8"?>

<persistence version="2.0"
    xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">

    <persistence-unit name="HsqldbWithTDD" transaction-type="RESOURCE_LOCAL">
        <provider>org.hibernate.ejb.HibernatePersistence</provider>

        <properties>
            <property name="javax.persistence.jdbc.driver" value="org.hsqldb.jdbcDriver" />
            <property name="javax.persistence.jdbc.url" value="jdbc:hsqldb:mem:." />
            <property name="javax.persistence.jdbc.user" value="sa" />
            <property name="javax.persistence.jdbc.password" value="" />
            <property name="hibernate.show_sql" value="true" />
            <property name="hibernate.dialect" value="org.hibernate.dialect.HSQLDialect"/>
            <property name="hibernate.connection.shutdown" value="true"/>
            <property name="hibernate.hbm2ddl.auto" value="create-drop"/>
        </properties>
    </persistence-unit>
</persistence>

Vamos criar a classe Dog que será persistida.

package com.model;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.NamedQuery;
import javax.persistence.Table;

@Entity
@Table(name = "dog")
@NamedQuery(name="listALL", query="select d from Dog d")
public class Dog {

    public static final String LIST_ALL = "listALL";

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private int id;
    private String name;
    private double weight;

    // Getters and Setters
}

Vamos criar agora a classe DAO que irá ter um CRUD básico da classe Dog (Na classe Dog você irá a anotação @NamedQuery, ainda não vimos aqui no blog mas basicamente é um SQL que irá realizar a consulta):

package com.dao;

import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;

import com.model.Dog;

public class DogDAO {

    private EntityManagerFactory emf;
    private EntityManager em;

    public void startConnection(){
        emf = Persistence.createEntityManagerFactory("HsqldbWithTDD");
        em = emf.createEntityManager();
        em.getTransaction().begin();
    }

    public void closeConnection(){
        em.getTransaction().commit();
        emf.close();
    }

    public void save(Dog dog){
        em.persist(dog);
    }

    public void edit(Dog dog){
        em.merge(dog);
    }

    public Dog find(int dogId){
        return em.find(Dog.class, dogId);
    }

    public void remove(Dog dog){
        em.remove(dog);
    }

    public List listALL(){
        return em.createNamedQuery(Dog.LIST_ALL, Dog.class).getResultList();
    }
}

Vamos testar se nossa informação está sendo persistida? Vamos criar uma classe Main e executar um teste, o resultado da classe Main deve ser como a figura abaixo:

package com;

import com.dao.DogDAO;
import com.model.Dog;

public class Main {

    public static void main(String[] args) {
        DogDAO dogDAO = new DogDAO();

        dogDAO.startConnection();

        try {
            Dog dog = new Dog();
            dog.setName("Beethoven");
            dog.setWeight(45);

            dogDAO.save(dog);

            // It was the first saved dog, so its id is 1
            Dog persistedDog = dogDAO.find(1);

            System.out.println("Name: " + persistedDog.getName());
            System.out.println("Weight: " + persistedDog.getWeight());
        } catch (Exception e) {
            System.out.println("Ops, something happen: " + e.getMessage());
            e.printStackTrace();
        }finally{
            dogDAO.closeConnection();
        }
    }
}


Agora, vamos utilizar a metodologia de TDD para criar um método/classe? Vamos simular que, precisamos de um método que calcule a média de peso de todos os cachorros cadastrados no sistema.

Vou criar a classe DogFacade que irá fazer esse trabalho, einclusive será ela quem irá trabalhar com o DAO.

Vamos configurar a biblioteca do JUnit em nosso sistema, namesma tela em que adicionamos as bibliotecas do Hibernate e do HSQLDB. Cliqueem Add Library.

Selecione JUnit 4 e pronto.

Seguindo boas práticas do TDD, vamos criar nossa classe deteste sem ter a classe DogFacade criada.

package test.com.facade;

import static junit.framework.Assert.assertEquals;

import org.junit.Test;

import com.model.Dog;

public class DogFacadeTest {

    @Test
    public void isWeightOfAllDogsCorret(){
        DogFacade dogFacade = new DogFacade();
        dogFacade.startConnection();

        createData(dogFacade);
        assertEquals(30, dogFacade.getAverageDogsWeight());

        dogFacade.closeConnection();
    }

    private void createData(){
        Dog dogA = new Dog();
        dogA.setName("Big Dog");
        dogA.setWeight(45);

        Dog dogB = new Dog();
        dogB.setName("Medium Dog");
        dogB.setWeight(30);

        Dog dogC = new Dog();
        dogC.setName("Small Dog");
        dogC.setWeight(15);
    }
}

Ao criar essa classe vários erros serão exibidos, masseguindo o princípio do TDD, “Vermelho, Verde, Refatorar”!  Para mais detalhes veja aqui (TDD – Primeiros passos).

Como nosso exemplo é simples, estou deixando o Facadegerenciando a conexão. Em uma aplicação web uma boa saída seria utilizar ainjeção de recursos, para continuar utilizando essa abordagem você pode adotaro que for melhor para você (algumas sugestões minhas, mas você poderá achar outraspela internet):

  • Passar o EntityManager por construtores. Sua classe de teste criaria o EntityManager através do EntityManagerFactory e o enviaria através de um construtor sobrecarregado. Ficaria assim: new DogFacade(entityManager); e dentro do Facade você iria fazer new DogDao(entityManager).
  • Criar o DAO na classe de teste com a conexão ativa e passar o DAO através de construtores sobrecarregados nos Facades.

Será necessário adotar alguma estratégia ou então vai choverNullPointer em seus testes. A parte ruim dessa estratégia é que sua classeFacade vai ficar conhecendo o EntityManager, ou ter uma dependência em umconstrutor de DAO. Mas esses construtores serão utilizados apenas em testes,não irão/devem interferir na aplicação em tempo real.

Vamos ver como ficou nossa classe DogFacade:

package com.facade;

import java.util.List;

import com.dao.DogDAO;
import com.model.Dog;

public class DogFacade {

    private DogDAO dogDAO;

    public DogFacade() {
        dogDAO = new DogDAO();
    }

    public void startConnection() {
        dogDAO.startConnection();
    }

    public double getAverageDogsWeight() {
        double totalWeight = 0;

        List dogs = dogDAO.listALL();

        for(Dog dog : dogs){
            totalWeight += dog.getWeight();
        }

        return totalWeight / dogs.size();
    }

    public void save(Dog dog){
        dogDAO.save(dog);
    }

    public void closeConnection() {
        dogDAO.closeConnection();
    }
}

Vamos executar nosso teste novamente?


Considerações Finais:

  • Eu prefiro essa abordagem a realizar Mocks. Um objeto “mocado” não irá se comportar como você não espera, esse objeto irá apenas se comportar do modo que ele foi programado. Em nosso caso o DAO não teve nenhum método “mocado”. Utilizamos o Facade de modo natural e não alteramos nenhum dos seus métodos.
  • Vejo o mock sendo realmente útil em casos onde são necessárias conexões com outras aplicações ou em aplicações de código legado onde o grau de complexidade é alta e utilizar essa abordagem seria muito custosa.
  • Com esse tipo de abordagem poderemos inclusive criar testes para as classes de view: JSF, Struts, Seam, etc. Próximo post que vou escrever sobre esse assunto! ;)

Espero que esse post possa te ajudar.

Qualquer dúvida ou colocação, basta postar.

Até +! \o_

Validação de Login de Usuário com JSF e JAAS

Olá pessoal, tudo bem?

Em outro post eu mostrei como fazer uma validação via Servlet e Filter. Vamos fazer de um modo diferente hoje, vamos validar usuário utilizando JSF e JAAS.

A validação de usuário via JAAS faz com que o próprio servidor valide o usuário e sua senha juntamente a qual perfil de acesso ele tem. Sem a necessidade de a todo o momento estar (programaticamente) defendendo nossa aplicação. Deixemos o JBoss fazer isso por nós.

A seguir todos os links sobre web que já postei aqui no Blog: JSF: Converter e Bean Auto Complete, JSF – Hello World, Auto Complete, Autenticação de Usuários (Filter/Servlet), Tratando Exceções em uma Aplicação Web, Criando um WebServer.

Nós iremos utilizar JSF, Postgres, Eclipse e o JBoss 6:

O JBoss Tools é apenas para adiantar a parte JSF e poder utilizar o JBoss 6 por dentro do Eclipse. Você poderá criar todo o código passado aqui, e depois colocar apenas o war dentro da pasta do JBoss e executá-lo por fora do Eclipse. E mais acima tem um link para o post de como fazer de como fazer uma aplicação JSF.

Não irei mostrar como instalar o Postgres, pois irá fugir do tema do post, mas você poderá utilizar qualquer banco como MySQL, SqlServer, Oracle e assim vai.

Para instalar o JBoss Server descompacte o arquivo e pronto.

Descompacte o Eclipse e instale o JBoss Tools. É uma ferramenta muito boa quando se trabalha com JBoss e JSF.
Para instalar o JBoss Tools vá ao menu Help >Install New Software digite o endereço http://download.jboss.org/jbosstools/updates/stable/helios/na barra “Work with” e aperte a tecla “enter”.

Para ficar mais fácil, selecionei essas duas opções:

Depois avance e finalize a instalação.
E para instalar o servidor é fácil, botão direito na aba “Servers” > New > Server
Selecione JBoss 6 > Next

Selecione o path onde está seu JBoss descompactado.

E agora clique em Finish.

Vamos criar um novo projeto JSF (File > New > Project). Estou utilizando já um modelo pronto fornecido pelo JBoss Tools. Caso você queria criar um projeto JSF do zero, basta olhar mais acima onde eu postei outros links sobre a parte web.

Configure sua aplicação, eu a estou chamando de LoginJSF, selecione JSF 2.0 e depois clique em Finish.

O Eclipse irá perguntar se você quer mudar o modo de visualização web, eu marquei que não. Você pode marcar o sim depois e ver como que fica.

Ele irá criar uma aplicação onde já tem uma navegação básica. Note que ele já adicionou seu projeto dentro do servidor, caso não o tenha adicionado clique com o botão direito em cima do projeto >Run As > Run on Server. E selecione o JBoss.


Rode o servidor e acesse o seguinte link: http://localhost:8080/LoginJSF/

Pare o JBoss pois vamos criar três pastas:

  • Uma pública
  • Uma com acesso apenas para admin
  • Uma com acesso parar admin e user

Abaixo, como ficará nossa estrutura de arquivos e os arquivos listados.

<?xml version="1.0"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
                      "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:composite="http://java.sun.com/jsf/composite">

    <h:head>
        I am public! [=
    </h:head>
    <h:body>
        Everyone can see me. \o/
    </h:body>
</html>
<?xml version="1.0"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
                      "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:composite="http://java.sun.com/jsf/composite">

    <h:head>
        Yes master admin! Welcome!
    </h:head>
    <h:body>
        Let us run this business!
    </h:body>
</html>
<?xml version="1.0"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
                      "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:composite="http://java.sun.com/jsf/composite">

    <h:head>
        Welcome mister user!
    </h:head>
    <h:body>
        <br/>
        Please, do not create any bugs, ok?! >=)
    </h:body>
</html>

Agora vamos criar as configurações que interessam. Primeiramente, vamos alterar o “web.xml” para que ele proteja as nossas páginas:

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
    <display-name>LoginJSF</display-name>
    <servlet>
        <servlet-name>Faces Servlet</servlet-name>
        <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>Faces Servlet</servlet-name>
        <url-pattern>*.xhtml</url-pattern>
        <url-pattern>*.jsf</url-pattern>
        <url-pattern>/faces/*</url-pattern>
    </servlet-mapping>
    <welcome-file-list>
        <welcome-file>index.html</welcome-file>
    </welcome-file-list>

    <!-- Protected Areas -->
    <security-constraint>
        <web-resource-collection>
            <web-resource-name>Only admins</web-resource-name>
            <url-pattern>/pages/protected/admin/*</url-pattern>
        </web-resource-collection>
        <auth-constraint>
            <role-name>ADMIN</role-name>
        </auth-constraint>
    </security-constraint>
    <security-constraint>
        <web-resource-collection>
            <web-resource-name>Users and admins</web-resource-name>
            <url-pattern>/pages/protected/user/*</url-pattern>
        </web-resource-collection>
        <auth-constraint>
            <role-name>ADMIN</role-name>
            <role-name>USER</role-name>
        </auth-constraint>
    </security-constraint>

    <!-- Validation By Form -->
    <login-config>
        <auth-method>FORM</auth-method>
        <form-login-config>
            <form-login-page>/pages/public/login.xhtml</form-login-page>
            <form-error-page>/pages/public/loginError.xhtml</form-error-page>
        </form-login-config>
    </login-config>

    <!-- Allowed Roles -->
    <security-role>
        <role-name>ADMIN</role-name>
    </security-role>
    <security-role>
        <role-name>USER</role-name>
    </security-role>

    <!-- Filter to get the user name and work with it -->
    <filter>
        <filter-name>LoginFilter</filter-name>
        <filter-class>com.filters.LoginFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>LoginFilter</filter-name>
        <url-pattern>/pages/protected/*</url-pattern>
    </filter-mapping>
</web-app>

Nós iremos precisar de uma página para login juntamente com uma página para erro. Observe que essas páginas estão descritas no “web.xml”.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:f="http://java.sun.com/jsf/core" xmlns:h="http://java.sun.com/jsf/html"
    xmlns:ui="http://java.sun.com/jsf/facelets">
<h:head>
    JSF 2 Login
</h:head>
<h:body>
    <p>Login to access secure pages:</p>
    <form method="post" action="j_security_check">
        <h:panelGrid columns="2">
            <h:outputLabel for="j_username" value="Username" />
            <input type="text" id="j_username" name="j_username" />
            <h:outputLabel for="j_password" value="Password" />
            <input type="password" id="j_password" name="j_password" />
            <input type="submit" name="submit" value="Login" />
        </h:panelGrid>
        <br />
    </form>
</h:body>
</html>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:f="http://java.sun.com/jsf/core" xmlns:h="http://java.sun.com/jsf/html"
    xmlns:ui="http://java.sun.com/jsf/facelets">
<h:head>
    JSF 2 Login
</h:head>
<h:body>
    <p>Ops! Check your user and password</p>
</h:body>
</html>

Vamos criar um Filtro java que irá capturar o nome do login que foi digitado onde você poderá utilizar o login para buscar no banco de dados o usuário e colocá-lo na sessão.

package com.filters;

import java.io.IOException;

import javax.servlet.*;

import org.jboss.security.SecurityAssociation;

public class LoginFilter implements Filter {

    @Override
    public void destroy() {
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException,
            ServletException {

        String userName = SecurityAssociation.getPrincipal().getName();

        System.out.println("Yeeey! Get me here and find me in the database: " + userName);

        filterChain.doFilter(servletRequest, servletResponse);
    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }
}

Vamos criar agora nossas tabelas no banco de dados?

Estou utilizando um exemplo mais simples possível, duas tabelas apenas e cada uma com o mínimo de campos necessários.
Crie um database chamado LoginJSF e em seguida execute o script abaixo:

CREATE TABLE "users"
(
  "name" character varying(50),
  pass character varying(50)
)
WITH (
  OIDS=FALSE
);
ALTER TABLE "users" OWNER TO postgres;

CREATE TABLE user_roles
(
  user_name character varying(50),
  role_name character varying
)
WITH (
  OIDS=FALSE
);
ALTER TABLE user_roles OWNER TO postgres;

Vamos inserir dois usuários, um com perfil de USER e outro com perfil de ADMIN:

INSERT INTO users("name", pass) VALUES ('user', 'user');
INSERT INTO users("name", pass) VALUES ('admin', 'admin');
INSERT INTO user_roles(user_name, role_name) VALUES ('user', 'USER');
INSERT INTO user_roles(user_name, role_name) VALUES ('admin', 'ADMIN');

Estamos quase lá, seja um pouquinho mais paciente! [=

Agora precisamos configurar o arquivo “login-config.xml” do próprio JBoss. Ele fica no diretório /JBOSS_QUE_VC_DESCOMPACTOU/server/default/conf/ . Adicione as seguintes linhas nele junto com as outras tags declaradas como “application-policy”:

<!-- Others application-policy -->

<!-- ..................... -->

<!-- Security Profile Of our Software-->
<application-policy name="LoginJSFRealm">
    <authentication>
        <login-module code="org.jboss.security.auth.spi.DatabaseServerLoginModule" flag="required">
            <module-option name="dsJndiName">java:/LoginJSFRealmDS</module-option>
            <module-option name="principalsQuery">select pass from users where name=?</module-option>
            <module-option name="rolesQuery">select role_name, 'Roles' from user_roles where user_name = ?</module-option>
        </login-module>
    </authentication>
</application-policy>

Acabamos de informar o JBoss que iremos utilizar o Realm:LoginJSFRealm e precisamos informar ao JBoss agora que nossa aplicação irá utilizar esse Realm. Vamos criar um arquivo chamado “jboss-web.xml” dentro da pasta WEB-INF com o seguinte código:

<?xml version="1.0" encoding="UTF-8"?>
<jboss-web>
    <!-- Links with JBoss the Realm to use -->
    <security-domain>java:/jaas/LoginJSFRealm</security-domain>
</jboss-web>

No arquivo “login-config.xhml” informamos ao JBoss para utilizar o seguinte datasource LoginJSFRealmDS. Através desse datasource ele irá se conectar ao banco de dados. Crie um arquivo chamado “LoginJSFRealm-ds.xml”dentro da pasta /JBOSS_QUE_VC_DESCOMPACTOU/server/ default/deploy/:

<?xml version="1.0" encoding="UTF-8"?>

<datasources>
    <local-tx-datasource>
        <jndi-name>LoginJSFRealmDS</jndi-name>
        <connection-url>jdbc:postgresql://localhost:5432/LoginJSF</connection-url>
        <driver-class>org.postgresql.Driver</driver-class>
        <user-name>postgres</user-name>
        <password>YOUR_PASSWORD</password>
        <!-- sql to call when connection is created. Can be anything, select 1 is valid for PostgreSQL <new-connection-sql>select 1</new-connection-sql> -->

        <!-- sql to call on an existing pooled connection when it is obtained from pool. Can be anything, select 1 is valid for PostgreSQL <check-valid-connection-sql>select
            1</check-valid-connection-sql> -->

        <!-- corresponding type-mapping in the standardjbosscmp-jdbc.xml (optional) -->
        <metadata>
            <type-mapping>PostgreSQL 7.2</type-mapping>
        </metadata>
    </local-tx-datasource>
</datasources>

E por último, copie o Driver JDBC 4 do Postgres que você fez o download para o diretório /JBOSS_QUE_VC_DESCOMPACTOU/server/ default/lib/.

Vamos agora rodar nossa aplicação e fazer o teste final? \o/
Acesse ao diretório publico primeiro: http://localhost:8080/LoginJSF/pages/public/public.html


Vamos ao diretório que apenas Admin pode acessar: http://localhost:8080/LoginJSF/pages/protected/admin/admin.html

Digite a senha e o usuário correto e pronto. Você irá logar.


E se um usuário tentasse acessar o link do admin? Faça o teste e veja o que acontece:

Tente o mesmo com o USER acessando a URL: http://localhost:8080/LoginJSF/pages/protected/user/user.html
Bem, por último e não menos importante, note no console quem apareceu por lá:

Através do filtro você irá conseguir fazer a consulta no seu banco de dados utilizando o usuário digitado e colocá-lo na sessão.

Importante 1: Você só irá passar pelo filtro após o login com sucesso feito pelo usuário. O trabalho de validar se o usuário digitou a senha correta fica por conta do JBoss! ;)

Importante 2: Caso você digite a senha correta e ele te envie para a tela de erro confira novamente os dados do JBoss como datasource,se o nome do datasource declarado no login-config é igual ao nome declarado dentro do arquivo de final “–ds.xml”, se as configurações descritas no datasource estão ok como senha, database, url de conexão.

Importante 3: Só após terminar o post percebi que eu dei o nome às páginas como html e não xhtml (user.html, admin.html, public.html). Você pode alterar e acessar novamente vendo o JSF entrar em ação (não se esqueça de acertar trocando o link e colocando xhtml no final!). Foi mals! =/

Espero que o post possa ter lhe ajudado.

Qualquer dúvida, pergunta ou sugestão basta falar.

Até a próxima pessoal! \o_

JPA: Ordernando listas e utilizando Map como atributo mapeado

Olá pessoal, tudo bem?

Como ordenar uma lista em um relacionamento sem utilizar uma query? Ou então como mapear seu relacionamento dentro de um Map? Vamos ver como são simples essas funções.

Vou utilizar códigos antigos dos outros posts. Para montar um ambiente para executar o código de hoje, você poderá olhar os posts antigos: @ManyToMany Unidirecional e Bidirecional, JPA @OneToMany e @ManyToOne Unidirecional e Bidirecional, @OneToOne Unidirecional e Bidirecional, Mapeando Duas Tabelas em uma Classe, Mapeando Datas (Date) e Enum, Chave Primária Composta, SequenceGenerator, TableGenerator – Chave Primária Simples, Auto Create Schema Script com: Ant, Hibernate 3 e JPA 2, Tutorial Hibernate 3 com JPA 2.

Vou postar aqui o código da classe Customer, Dog, e Main:

package com;

//Using the * to make the import list smaller
import javax.persistence.*;

@Entity
@Table(name="DOG")
@SequenceGenerator(name="DOG_SEQUENCE", sequenceName="DOG_SEQUENCE", allocationSize=1, initialValue=0)
public class Dog {

    public Dog(){

    }

    public Dog(String name){
        this.name = name;
    }

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator="DOG_SEQUENCE")
    private int id;

    @Column
    private String name;

    // Getters and Setters
}
package com;

import java.util.List;

//Using the * to make the import list smaller
import javax.persistence.*;

@Entity
@Table(name = "CUSTOMER")
@SequenceGenerator(name = "CUSTOMER_SEQUENCE", sequenceName = "CUSTOMER_SEQUENCE", allocationSize = 1, initialValue = 0)
public class Customer {

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "CUSTOMER_SEQUENCE")
    private int id;

    @Column
    private String name;

    @OneToMany(cascade=CascadeType.ALL, fetch=FetchType.EAGER)
    @JoinTable(name="CUSTOMER_HAS_DOGS", joinColumns={@JoinColumn(name="CUSTOMER_ID", referencedColumnName="id")}, inverseJoinColumns={@JoinColumn(name="DOG_ID", referencedColumnName="id")})
    private List dogs;

    // Getters and Setters
}
package com;

import java.util.ArrayList;
import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;

public class Main {

    public static void main(String[] args) {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("Hello");
        EntityManager em = emf.createEntityManager();

        try {
            em.getTransaction().begin();

            List dogs = new ArrayList();
            dogs.add(new Dog("Terminator"));
            dogs.add(new Dog("Fluffy"));
            dogs.add(new Dog("Bacon"));

            Customer customer = new Customer();
            customer.setName("Arnold");
            customer.setDogs(dogs);

            em.persist(customer);

            em.getTransaction().commit();
        }
        catch (Exception e) {
            em.getTransaction().rollback();
            e.printStackTrace();
        }
        finally{
            emf.close();
        }

        System.out.println("It is over");
    }
}

Ao executar o código acima você irá criar dados necessário para poder começar o estudo de hoje.

Após uma pequena alteração na classe Main, vamos exibir os itens da lista Dog de um Customer. Veja a baixo o novo código da classe Main e o resultado impressao:

package com;

//Using the * to make the import list smaller
import javax.persistence.*;

public class Main {

    public static void main(String[] args) {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("Hello");
        EntityManager em = emf.createEntityManager();

        try {
            em.getTransaction().begin();

            Customer customer = em.find(Customer.class, 1);

            for(Dog dog : customer.getDogs()){
                System.out.println(dog.getName());
            }

            em.getTransaction().commit();
        }
        catch (Exception e) {
            em.getTransaction().rollback();
            e.printStackTrace();
        }
        finally{
            emf.close();
        }

        System.out.println("It is over");
    }
}
Unordered List

Note que os objetos da lista não estão sendo impressos em ordem alfabética do nome do cachorro. Mas, caso queiramos ordenar os resultados como faríamos?

Iremos utilizar a anotação @OrderBy que nos permite especificar o nome do campo da tabela ao qual queremos que nossa lista seja ordenada.

Basta adicionar a anotação em nossa classe Customer, e nossa lista será ordenada. Veja como ficará:

package com;

import java.util.List;

//Using the * to make the import list smaller
import javax.persistence.*;

@Entity
@Table(name = "CUSTOMER")
@SequenceGenerator(name = "CUSTOMER_SEQUENCE", sequenceName = "CUSTOMER_SEQUENCE", allocationSize = 1, initialValue = 0)
public class Customer {

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "CUSTOMER_SEQUENCE")
    private int id;

    @Column
    private String name;

    @OneToMany(cascade=CascadeType.ALL, fetch=FetchType.EAGER)
    @OrderBy("name")
    @JoinTable(name="CUSTOMER_HAS_DOGS", joinColumns={@JoinColumn(name="CUSTOMER_ID", referencedColumnName="id")}, inverseJoinColumns={@JoinColumn(name="DOG_ID", referencedColumnName="id")})
    private List dogs;

    // Getters and Setters
}

Execute nossa classe Main e a ordenação irá aparecer:

Bem simples. Lógico que é apenas uma solução para caso você queira ordenar sua coleção sem ter que ter uma nativeQuery/namedQuery/Criteria para isso.

E se precisássemos colocar nossa lista dentro de um map, como fazer?

Vamos criar um atributo com o java.util.Map e a anotação @MapKey:

package com;

import java.util.List;
import java.util.Map;

//Using the * to make the import list smaller
import javax.persistence.*;

@Entity
@Table(name = "CUSTOMER")
@SequenceGenerator(name = "CUSTOMER_SEQUENCE", sequenceName = "CUSTOMER_SEQUENCE", allocationSize = 1, initialValue = 0)
public class Customer {

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "CUSTOMER_SEQUENCE")
    private int id;

    @Column
    private String name;

    @OneToOne(cascade = CascadeType.ALL, optional = true, fetch = FetchType.EAGER, orphanRemoval = true)
    @JoinColumn(name = "USER_ID", nullable = true)
    private User user;

    @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
    @OrderBy("name")
    @JoinColumn(name = "CUSTOMER_ID")
    private List dogs;

    @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
    @JoinColumn(name = "CUSTOMER_ID")
    @MapKey(name = "id")
    private Map dogsInMap;

    // Getters and Setters
}

O campo “id” é o campo que será utilizado para chave do mapa.

E para finalizar executemos a classe Main para exibir a iteração sobre nosso map:

package com;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;

public class Main {

    public static void main(String[] args) {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("Hello");
        EntityManager em = emf.createEntityManager();

        try {
            em.getTransaction().begin();

            Customer customer = em.find(Customer.class, 1);

            System.out.println("Ordered List");
            for(Dog dog : customer.getDogs()){
                System.out.println(dog.getName());
            }

            System.out.println("\nThe map: ");
            for(int id : customer.getDogsInMap().keySet()){
                Dog dog = customer.getDogsInMap().get(id);
                System.out.println(dog.getName());
            }

            em.getTransaction().commit();
        }
        catch (Exception e) {
            em.getTransaction().rollback();
            e.printStackTrace();
        }
        finally{
            emf.close();
        }

        System.out.println("It is over");
    }
}

Fiz a iteração dentro do Map buscado pela chave (key) para mostrar que o atributo da chave foi corretamente populado.

Espero que esse post possa te ajudar.

Até a próxima.
\o_

JPA @ManyToMany Unidirecional e Bidirecional

EXISTEM DUAS NOVAS VERSÕES DESSE POST.
MANY TO MANY SIMPLES: http://uaihebert.com/?p=1622&page=21
MANY TO MANY COM CAMPO EXTRA: http://uaihebert.com/?p=1622&page=22

Pessoal, bom dia.

Vamos falar hoje sobre o relacionamento @ManyToMany Unidirecional e Bidirecional.

Caso você tenha dúvidas em como criar o ambiente necessário para executar o código, basta olhar os posts mais antigos sobre JPA: JPA @OneToMany e @ManyToOne Unidirecional e Bidirecional, @OneToOne Unidirecional e Bidirecional, Mapeando Duas Tabelas em uma Classe, Mapeando Datas (Date) e Enum, Chave Primária Composta, SequenceGenerator, TableGenerator – Chave Primária Simples, Auto Create Schema Script com: Ant, Hibernate 3 e JPA 2, Tutorial Hibernate 3 com JPA 2.

Vamos imaginar um simples caso de uso, um notebook (classe Notebook) pode ter vários donos (classe Person), e uma pessoa pode ter vários notebooks.

Estarei utilizando código de posts anteriores, então só explicarei o necessário para o código desse post.

Vamos ao código da classe Person com o relacionamento Unidirecional:

package com;

import java.util.List;

//Using the * to make the import list smaller
import javax.persistence.*;

@Entity
@Table(name = "person")
@SecondaryTable(name = "health_care", pkJoinColumns = { @PrimaryKeyJoinColumn(name = "id") })
public class Person {

    @Id
    private int id;

    @Column
    private String name;

    @Column(table = "health_care", name = "company_name")
    private String companyName;

    @ManyToMany
    @JoinTable(name="person_has_notebooks", joinColumns={@JoinColumn(name="person_id")}, inverseJoinColumns={@JoinColumn(name="notebook_id")})
    private List notebooks;

    // Getters and setters
}

A anotação @JoinTable já foi citada em outro post, caso você não a especifique, o JPA criará uma tabela padrão; o nome dessa tabela seria “PERSON_NOTEBOOK“.

E a classe Notebook:

package com;

//Using the * to make the import list smaller
import javax.persistence.*;

@Entity
@Table(name="notebook")
public class Notebook {

    @Id
    @GeneratedValue(strategy=GenerationType.AUTO)
    private int id;
    private String serialNumber;
    private int ramMemoryTotal;
    private int hdSpaceTotal;

    // Getters and setters
}

Para criar o relacionamento execute o código abaixo:

package com;

//Using the * to make the import list smaller
import javax.persistence.*;
import java.util.*;

public class Main {

    public static void main(String[] args) {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("Hello");
        EntityManager em = emf.createEntityManager();

        try {
            em.getTransaction().begin();

            Notebook noteA = new Notebook();
            noteA.setSerialNumber("A0123");
            Notebook noteB = new Notebook();
            noteB.setSerialNumber("B0123");
            Notebook noteC = new Notebook();
            noteC.setSerialNumber("C0123");

            List notebooks = new ArrayList();
            notebooks.add(noteA);
            notebooks.add(noteB);
            notebooks.add(noteC);

            Person person = new Person();
            person.setName("Zorro");
            person.setNotebooks(notebooks);

            em.persist(person);

            em.getTransaction().commit();
        }
        catch (Exception e) {
            em.getTransaction().rollback();
            e.printStackTrace();
        }
        finally{
            emf.close();
        }

        System.out.println("It is over");
    }
}

Execute o código veja o seu resultado:

Imagem do Console.

Como podemos perceber o console nos mostra que o relacionamento foi criado corretamente.

Verifique seu banco de dados e verás que a tabela de relacionamento foi criada corretamente.

Vamos fazer com que nosso relacionamento seja bidirecional?

Vamos alterar a classe Notebook:

package com;

import java.util.List;

//Using the * to make the import list smaller
import javax.persistence.*;

@Entity
@Table(name="notebook")
public class Notebook {

    @Id
    @GeneratedValue(strategy=GenerationType.AUTO)
    private int id;
    private String serialNumber;
    private int ramMemoryTotal;
    private int hdSpaceTotal;

    @ManyToMany(mappedBy="notebooks")
    private List persons;

    // Getters and setters
}

A diferença entre um relacionamento unidirecional e bidirecional você poderá ver nesse post: @OneToOne Unidirecional e Bidirecional.

Em um relacionamento @ManyToMany qualquer um dos lados do relacionamento pode ser o lado dominante. Lembre-se que o lado dominado (descrito com mappedBy), se persistido apenas ele, ele não irá disparar o Cascade do lado dominante. Em outras palavras. Ao se fazer Notebook.setPersons() apenas antes salvar o objeto, o relacionamento com as pessoas não será atualizado/criado, pois o lado dominado não tem esse “poder”.

Espero que o post de hoje possa te ajudar.

Até a próxima.