Java

Documentando uma API Spring Boot com o Swagger

Documentar uma aplicação é um ponto essencial de qualquer projeto, muitas vezes negligenciado. Quando se trabalha em equipe, uma má documentação pode dificultar (e muito) o trabalho dos demais desenvolvedores.

Por já ter muita dificuldade com isso, procuro sempre documentar as aplicações que estou trabalhando. No ano passado, mostrei como documentar uma aplicação ASP.NET Core Web API com o Swagger. Por gostar muito dele, nesta semana quando precisei documentar API Spring Boot, não tive dúvidas que ele é era a escolha correta.

A partir da experiência que tive neste processo, aqui vou lhe mostrar como documentar uma API Spring Boot com o Swagger.

Relembrando características do Swagger

Caso não tenha visto o meu artigo de ASP.NET Core Web API com o Swagger, vamos relembrar alguns detalhes do Swagger:

  • O Swagger é uma aplicação open source que auxilia os desenvolvedores a definir, criar, documentar e consumir APIs REST;
  • É composto de um arquivo de configuração, que pode ser definido em YAML ou JSON;
  • Fornece ferramentas para: auxiliar na definição do arquivo de configuração (Swagger Editor), interagir com API através das definições do arquivo de configuração (Swagger UI) e gerar templates de código a partir do arquivo de configuração (Swagger Codegen).

Como é possível notar, o ponto mais importante é o arquivo de configuração do Swagger. É nele que a API é documentada.

Criar este arquivo na mão pode ser um trabalho hercúleo, felizmente existem algumas bibliotecas do Java que facilitam este processo. No caso de uma aplicação Spring, a melhor opção é a biblioteca SpringFox.

Adicionando a biblioteca SpringFox em uma aplicação API Spring Boot

Para este artigo, estou utilizando a aplicação mostrada no meu último artigo, onde ensinei a criar uma aplicação REST API no Spring Boot. Você também pode ver a aplicação que criei no meu Github.

Com a aplicação criada, temos que adicionar a dependência do SpringFox nela:

<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger2</artifactId>
    <version>2.9.2</version>
</dependency>

Para interagir com a configuração, também é necessário adicionar a dependência que fornece o Swagger UI:

<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger-ui</artifactId>
    <version>2.9.2</version>
</dependency>

Agora para que o arquivo de especificação da API seja criado, é necessário habilitar o Swagger na aplicação. Para isso, adicione nela uma classe chamada SwaggerConfig, com o conteúdo abaixo:

@Configuration
@EnableSwagger2
public class SwaggerConfig {
    @Bean
    public Docket api() {
        return new Docket(DocumentationType.SWAGGER_2)
          .select()
          .apis(RequestHandlerSelectors.any())
          .paths(PathSelectors.any())
          .build();
    }
}

No Spring Boot o Swagger é ativado através da anotação @EnableSwagger2. O Docket que estamos definindo no nosso bean nos permite configurar aspectos dos endpoints expostos por ele.

Nos métodos apis() e paths() definimos que todas as apis e caminhos estarão disponíveis. Com isso através de reflection a biblioteca já consegue obter os endpoints definidos na aplicação.

Ao executá-la, o Swagger UI estará disponível em /swagger-ui.html:

Note que ele está pegando informações de todos os controllers definidos na aplicação até o de erro padrão do Spring Boot. Caso queria evitar isso, é possível configurar o Swagger.

Customizando o Swagger na aplicação Spring Boot

Todas as configurações do Swagger devem ser definidas na classe SwaggerConfig. No momento ela contém apenas as configurações padrão.

Indicando código e mensagem de retorno do Swagger

Nas configurações padrão, o Swagger irá indicar que os endpoints retornam os códigos 200, 201, 204, 401, 403 e 404. Caso a sua aplicação não retorne todos esses códigos, você pode especificar quais códigos ela retorna com o método globalResponseMessage.

O globalResponseMessage recebe por parâmetro o método HTTP e uma lista de ResponseMessage que indica quais códigos e mensagens o método retorna. Para que a aplicação fique modular, vamos definir os ResponseMessage em um método:

private List<ResponseMessage> responseMessageForGET()
{
    return new ArrayList<ResponseMessage>() {{
        add(new ResponseMessageBuilder()
            .code(500)
            .message("500 message")
            .responseModel(new ModelRef("Error"))
            .build());
        add(new ResponseMessageBuilder()
            .code(403)
            .message("Forbidden!")
            .build());
    }};
}

E ele será passado como parâmetro do globalResponseMessage:

public Docket api() {
    return new Docket(DocumentationType.SWAGGER_2)
        .select()
        .apis(RequestHandlerSelectors.any())
        .paths(PathSelectors.any())
        .build()
        .useDefaultResponseMessages(false)
        .globalResponseMessage(RequestMethod.GET, responseMessageForGET());
}

Agora, ficará indicado que todos os endpoints GET retornam o código 200 (que é sempre padrão) e os códigos 500 e 403:

Note na imagem acima, que em “Response content type” é indicado o valor */*. Para alterar este ponto, basta indicar no seu endpoint o tipo do conteúdo que ele produz com o atributo produces:

@RequestMapping(value = "/pessoa", method = RequestMethod.GET, produces="application/json")
public List<Pessoa> Get() {
    return _pessoaRepository.findAll();
}

Com o atributo consumes é possível especificar o tipo de conteúdo que ele consome:

@RequestMapping(value = "/pessoa", method =  RequestMethod.POST, produces="application/json", consumes="application/json")
public Pessoa Post(@Valid @RequestBody Pessoa pessoa)
{
    return _pessoaRepository.save(pessoa);
}

Também é possível especificar os códigos e as mensagens de retorno diretamente no controller com as anotações @ApiResponses e @ApiResponse:

@ApiResponses(value = {
    @ApiResponse(code = 200, message = "Retorna a lista de pessoa"),
    @ApiResponse(code = 403, message = "Você não tem permissão para acessar este recurso"),
    @ApiResponse(code = 500, message = "Foi gerada uma exceção"),
})
@RequestMapping(value = "/pessoa", method = RequestMethod.GET, produces="application/json")
public List<Pessoa> Get() {
    return _pessoaRepository.findAll();
}

Estas configurações serão válidas para os endpoints onde estiverem definidos. Onde não estiver, será utilizado o padrão.

Também é possível utilizar a anotação @ApiOperation para descrever o endpoint:

@ApiOperation(value = "Retorna uma lista de pessoas")
@ApiResponses(value = {
    @ApiResponse(code = 200, message = "Retorna a lista de pessoa"),
    @ApiResponse(code = 403, message = "Você não tem permissão para acessar este recurso"),
    @ApiResponse(code = 500, message = "Foi gerada uma exceção"),
})
@RequestMapping(value = "/pessoa", method = RequestMethod.GET, produces="application/json")
public List<Pessoa> Get() {
    return _pessoaRepository.findAll();
}

E caso queira descrever o model, pode ser utilizado a anotação @ApiModelProperty:

@Entity
public class Pessoa
{
    @ApiModelProperty(value = "Código da pessoa")
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;

    @ApiModelProperty(value = "Nome da pessoa")
    @Column(nullable = false)
    private String nome;

    public long getId() {
        return id;
    }

    public String getNome() {
        return nome;
    }

    public void setNome(String nome) {
        this.nome = nome;
    }

    public void setId(long id) {
        this.id = id;
    }
}

Filtrando os endpoints da API Spring Boot no Swagger

No momento o Swagger está listando todos os endpoints definidos na aplicação, inclusive os padrões do Spring Boot, como o de erro. Mas na configuração dele, no método apis podemos utilizar a classe RequestHandlerSelectors para filtrar quais serão considerados com base no pacote ou anotação. Por exemplo, para que seja listada apenas os endpoints definidos pela nossa aplicação, utilizamos o método basePackage() desta classe:

@Bean
public Docket api() {
    return new Docket(DocumentationType.SWAGGER_2)
        .select()
       .apis(RequestHandlerSelectors.basePackage("br.com.treinaweb.springbootapi.controller"))
        .paths(PathSelectors.any())
        .build()
        .useDefaultResponseMessages(false)
        .globalResponseMessage(RequestMethod.GET, responseMessageForGET());
}

Com a classe PathSelectors, também é possível filtrar os caminhos aceitos para os endpoints, como:

@Bean
public Docket api() {
    return new Docket(DocumentationType.SWAGGER_2)
        .select()
       .apis(RequestHandlerSelectors.basePackage("br.com.treinaweb.springbootapi.controller"))
        .paths(PathSelectors.ant("/api/*"))
        .build()
        .useDefaultResponseMessages(false)
        .globalResponseMessage(RequestMethod.GET, responseMessageForGET());
}

Acima estamos utilizando o método ant() para adicionar o filtro, mas também seria possível definir uma expressão regular com o método regex().

Especificando a autenticação da aplicação no Swagger

Uma das principais vantagens da Swagger UI é a possibilidade de testar os endpoints diretamente pela interface. Quando a aplicação define alguma autenticação, é necessário configurar isso, para que o SpringFox também especifique isso, e mesmo endpoints protegidos sejam testáveis.

Esta especificação é realizada com os métodos securitySchemes e securityContexts. No primeiro é definido o tipo de autenticação (no momento os suportados são: ApiKey, BasicAuth e OAuth). Já no segundo são especificadas particularidades desta autenticação, como os escopos e endpoints que necessitam de autenticação.

No caso deste exemplo, será definida uma autenticação ApiKey:

@Bean
public Docket api() {
    return new Docket(DocumentationType.SWAGGER_2)
        .select()
        .apis(RequestHandlerSelectors.basePackage("br.com.treinaweb.springbootapi.controller"))
        .paths(PathSelectors.any())
        .build()
        .useDefaultResponseMessages(false)
        .globalResponseMessage(RequestMethod.GET, responseMessageForGET())
        .securitySchemes(Arrays.asList(new ApiKey("Token Access", HttpHeaders.AUTHORIZATION, In.HEADER.name())))
        .securityContexts(Arrays.asList(securityContext()));
}

Já o securityContext() conterá o código abaixo:

private SecurityContext securityContext() {
    return SecurityContext.builder()
        .securityReferences(defaultAuth())
        .forPaths(PathSelectors.ant("/pessoa/**"))
        .build();
}

List<SecurityReference> defaultAuth() {
    AuthorizationScope authorizationScope
        = new AuthorizationScope("ADMIN", "accessEverything");
    AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
    authorizationScopes[0] = authorizationScope;
    return Arrays.asList(
        new SecurityReference("Token Access", authorizationScopes));
}

Onde é definido que os endpoint de /pessoa necessitam de autenticação.

Agora ao executar a aplicação e acessar o Swagger, haverá o botão “Authorize”:

Ao clicar nele, será exibido a tela onde a forma de acesso deve ser informada:

Na aplicação deste exemplo, a autenticação é TOKEN, então ao informá-lo e clicar em Authorize, será possível realizar solicitações nos endpoints que necessitam de autenticação.

Descrevendo a API no Swagger

Por fim, é possível adicionar algumas informações da api no método apiInfo:

@Bean
public Docket api() {
    return new Docket(DocumentationType.SWAGGER_2)
        .select()
        .apis(RequestHandlerSelectors.basePackage("br.com.treinaweb.springbootapi.controller"))
        .paths(PathSelectors.any())
        .build()
        .useDefaultResponseMessages(false)
        .globalResponseMessage(RequestMethod.GET, responseMessageForGET())
        .securitySchemes(Arrays.asList(new ApiKey("Token Access", HttpHeaders.AUTHORIZATION, In.HEADER.name())))
        .securityContexts(Arrays.asList(securityContext()))
        .apiInfo(apiInfo());
}

private ApiInfo apiInfo() {
    return new ApiInfoBuilder()
            .title("Simple Spring Boot REST API")
            .description("\"Um exemplo de aplicação Spring Boot REST API\"")
            .version("1.0.0")
            .license("Apache License Version 2.0")
            .licenseUrl("https://www.apache.org/licenses/LICENSE-2.0\"")
            .contact(new Contact("Wladimilson", "https://treinaweb.com.br", "contato@treinaweb.com.br"))
            .build();
}

O resultado será:

Você pode ver a aplicação deste artigo no meu Github.

Devo usar o Swagger em uma aplicação API Spring Boot?

Neste artigo vemos que está claro que o Swagger é uma ótima forma de documentar APIs REST e com a biblioteca SpringFox, caso esteja desenvolvendo uma API Spring Boot, a geração da especificação do Swagger é facilitada. Assim, sempre que possível documente as suas aplicações utilizando esta ferramenta. Tenho certeza que isso irá facilitar o trabalho de todos que consomem a sua API, incluindo você.

Criando uma API REST com o Spring Boot

Até pouco tempo o Java possuia um ciclo atualizações grande, entre o lançamento da versão 5 (07/2004) até a versão 9 (07/2017) foram treze anos. A partir desta nona versão o ciclo foi alterado e agora há lançamento de novas versões duas vezes ao ano, nos meses de março e setembro, por isso que entre a versão 9 e a 12 (a mais atual), o intervalo é de 1 ano e meio.

Este grande intervalo entre as antigas versões era necessário devido ao seu complexo processo de atualização. Visando estabilidade e segurança, cada versão passava por vários testes e interações até serem liberados para os usuários. É preciso dizer que infelizmente nem sempre a versão lançada fornecia estabilidade e segurança. Qualquer usuário que já tenha instalado o Java no seu computador sabe que mensalmente havia alguma atualização de segurança.

Tudo isso dificultava a adição de novos recursos na linguagem, o que permitiu o destaque de alguns frameworks. Dentre vários, o que mais ganhou os holofotes foi o Spring. Criado para aplicações Java EE, hoje o Spring é um projeto que contém várias bibliotecas, de segurança (Spring Security) à big data (Spring XD).

Dentre todos os projetos abaixo da asa do Spring, o que mais se destaca é o Spring Boot.

Spring Boot

Todo desenvolvedor Java sabe que as vezes configurar uma aplicação pode ser um trabalho hercúleo. Às vezes são horas de configurações e apenas alguns minutos de codificação.

O Spring Boot veio para resolver esta situação. Utilizando como base o Core do Spring, o Spring Boot trabalha seguindo convenções e configurações padrão para abstrair o máximo possível das configurações necessárias de uma aplicação Spring.

Basta você definir qual tipo de aplicação deseja criar, escolher o starter apropriado, que o Spring se encarregará as configurações básicas necessárias da aplicação que escolheu.

O starter escolhido contém todas as dependências que sua aplicação necessita para funcionar. Então não é necessário nem se preocupar com as dependências do projeto.

Mesmo funcionando através de convenção, caso queira, é possível customizar todas as configurações da aplicação, de forma simples ou complexa, dependendo do que pretende fazer.

O maior benefício deste framework é que permite o desenvolvedor se preocupar com o ponto mais importante da aplicação, as regras de negócio.

Para demonstrar esta facilidade, neste artigo veremos como definir uma API REST utilizando este framework.

Criando a aplicação

Existem algumas formas de criar uma aplicação Spring Boot. Ela pode ser criada no Eclipse, utilizando a IDE Spring Tool Suíte, que também é fornecida como plug-in para o Eclipse, Visual Studio Conde e Atom. Ou mesmo no site Spring Initializr.

No momento estou utilizando uma máquina Mac OS X com o Visual Studio Code instalando, assim vou criar a aplicação inicial utilizando a extensão deste editor fornecida pela Microsoft:

Ao criar, a aplicação terá a estrutura abaixo:

Neste projeto os pontos mais importantes são o arquivo pom.xml, que contém o starter web e das dependências que definimos:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.6.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>br.com.treinaweb</groupId>
    <artifactId>springbootapi</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>demo</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

E o arquivo DemoApplication.java que contém o nosso método main:

package br.com.treinaweb.springbootapiexemplo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

}

Caso não esteja habituado com o Spring Boot, pode estranhar a criação de um método main em uma aplicação web. Isso ocorre porque este método será responsável por carregar todas dependências embutidas na aplicação, isso inclui o servidor web (o Tomcat).

Desta forma, para esta aplicação web não é necessário definir um arquivo war e adicioná-lo no servidor.

Caso execute a aplicação agora, notará que o servidor embutido será iniciado e a aplicação já poderá ser utilizada. Só que no momento ela não possui nada.

Criando a entidade e repositório

Uma das conversões que o Spring Boot adota é que ele reconhece como componentes da aplicação, todas as classes definidas no mesmo pacote da classe que contém o método main ou em um package “abaixo” do package.

Ou seja, como a nossa classe está definida no pacote br.com.treinaweb.springbootapiexemplo, qualquer classe definida nele ou em um “subpackage” dele, será reconhecida pelo Spring Boot. Desta forma, vamos definir a nossa entidade no pacote br.com.treinaweb.springbootapiexemplo.entity:

package br.com.treinaweb.springbootapi.entity;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Entity
public class Pessoa
{
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;

    @Column(nullable = false)
    private String nome;

    public long getId() {
        return id;
    }

    public String getNome() {
        return nome;
    }

    public void setNome(String nome) {
        this.nome = nome;
    }

    public void setId(long id) {
        this.id = id;
    }
}

Já o repositório ficará no pacote br.com.treinaweb.springbootapi.repository:

package br.com.treinaweb.springbootapi.repository;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import br.com.treinaweb.springbootapi.entity.Pessoa;

@Repository
public interface PessoaRepository extends JpaRepository<Pessoa, Long> { }

Note que para o repositório foi necessário apenas estender da interface JpaRepository do Spring Data. Esta interface possui métodos para as operações padrão de um CRUD.

Aproveitando que definimos o repositório, vamos configurar o banco de dados. Esta configuração deve ser definida no arquivo application.properties:

## Database Properties
spring.datasource.url = jdbc:mysql://localhost:3306/treinaweb?useSSL=false
spring.datasource.username = root
spring.datasource.password = root
## Hibernate Properties
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5InnoDBDialect
spring.jpa.hibernate.ddl-auto = update

Alguns comportamentos do Spring Boot podem ser alterados com configurações neste arquivo. Vamos deixar apenas as configurações do banco.

Controller

Por fim, vamos definir o controller da nossa aplicação. Assim como antes, ele será definido em um sub-package do package padrão da aplicação:

package br.com.treinaweb.springbootapi.controller;

import java.util.List;
import java.util.Optional;

import javax.validation.Valid;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import br.com.treinaweb.springbootapi.entity.Pessoa;
import br.com.treinaweb.springbootapi.repository.PessoaRepository;

@RestController
public class PessoaController {
    @Autowired
    private PessoaRepository _pessoaRepository;

    @RequestMapping(value = "/pessoa", method = RequestMethod.GET)
    public List<Pessoa> Get() {
        return _pessoaRepository.findAll();
    }

    @RequestMapping(value = "/pessoa/{id}", method = RequestMethod.GET)
    public ResponseEntity<Pessoa> GetById(@PathVariable(value = "id") long id)
    {
        Optional<Pessoa> pessoa = _pessoaRepository.findById(id);
        if(pessoa.isPresent())
            return new ResponseEntity<Pessoa>(pessoa.get(), HttpStatus.OK);
        else
            return new ResponseEntity<>(HttpStatus.NOT_FOUND);
    }

    @RequestMapping(value = "/pessoa", method =  RequestMethod.POST)
    public Pessoa Post(@Valid @RequestBody Pessoa pessoa)
    {
        return _pessoaRepository.save(pessoa);
    }

    @RequestMapping(value = "/pessoa/{id}", method =  RequestMethod.PUT)
    public ResponseEntity<Pessoa> Put(@PathVariable(value = "id") long id, @Valid @RequestBody Pessoa newPessoa)
    {
        Optional<Pessoa> oldPessoa = _pessoaRepository.findById(id);
        if(oldPessoa.isPresent()){
            Pessoa pessoa = oldPessoa.get();
            pessoa.setNome(newPessoa.getNome());
            _pessoaRepository.save(pessoa);
            return new ResponseEntity<Pessoa>(pessoa, HttpStatus.OK);
        }
        else
            return new ResponseEntity<>(HttpStatus.NOT_FOUND);
    }

    @RequestMapping(value = "/pessoa/{id}", method = RequestMethod.DELETE)
    public ResponseEntity<Object> Delete(@PathVariable(value = "id") long id)
    {
        Optional<Pessoa> pessoa = _pessoaRepository.findById(id);
        if(pessoa.isPresent()){
            _pessoaRepository.delete(pessoa.get());
            return new ResponseEntity<>(HttpStatus.OK);
        }
        else
            return new ResponseEntity<>(HttpStatus.NOT_FOUND);
    }
}

Na classe acima, é importante destacar alguns detalhes:

  • A anotação @RestController permite definir um controller com características REST;
  • A anotação @Autowired delega ao Spring Boot a inicialização do objeto;
  • A anotação @RequestMapping permite definir uma rota. Caso não seja informado o método HTTP da rota, ela será definida para todos os métodos.
  • A anotação @PathVariable indica que o valor da variável virá de uma informação da rota;
  • A anotação @RequestBody indica que o valor do objeto virá do corpo da requisição;
  • E a anotação @Valid indica que os dados recebidos devem ser validados.

Pronto, a nossa aplicação pode ser utilizada.

Para testar os endpoints, vamos utilizar o Postman:

Post

Get

Get id

Put

Conclusão

O Spring Boot facilita muito a criação de aplicações web em Java, caso seja um desenvolvedor desta linguagem, é quase uma obrigação conhecê-lo.

Você pode baixar a aplicação deste artigo no meu Github.

3 linguagens de programação que continuam em alta

Você provavelmente já se pegou pensando em qual linguagem você deve focar seus estudos. Seja se você está iniciando ou não, é sempre bom ver o que o mercado está pedindo. Claro que não devemos avaliar apenas o mercado, mas também suas preferências. A ideia é fazer uma junção dessas duas coisas: escolher uma linguagem em alta, mas que dentre elas, você escolha a que mais se identificar.

No começo deste ano fizemos um post sobre as linguagens promissoras para se estudar neste ano. Mas como na área de TI tudo muda muito rápido, será que agora, perto do final do ano, essas linguagens ainda estão no topo? Vamos focar hoje nas 3 principais do momento.

1) JavaScript

Assim como no começo do ano, o JavaScript aparece em primeiro lugar. Como é uma linguagem dinâmica pode ser utilizada para várias coisas. Temos um post que aborda justamente isso: O que se pode fazer com JavaScript hoje em dia?

O site Stack Overflow sempre realiza uma pesquisa para obter estatísticas da comunidade de desenvolvedores. E nas tecnologias mais populares, está ela, em primeiro lugar, com uma porcentagem de 69,8%.

O GitHub (site de compartilhamento de código) também diz que o JavaScript é o mais utilizado.

Como podemos ver acima, em segundo aparece a linguagem Python, que veremos a seguir.

2) Python

Desde o começo do ano ela era uma das linguagens mais promissoras, e ela ainda está no pódio. O Python é uma linguagem poderosa e que cada vez mais ganha popularidade em meio aos desenvolvedores.

Podendo ser utilizada por uma variedade de tarefas, desde desenvolvimento web, aprendizado de máquina, análise de dados e muitas outras.

Ainda na pesquisa do Stack Overflow, quase 40% dos usuários utilizam Python em seus projetos. E ainda tem mais: segundo o site TIOBE, podemos ver como o Python tem crescido.

3) Java

Muitas pessoas quando pensam em estudar uma linguagem, já pensam logo no Java. Se você gostar, porque não? Apesar de não ter aparecido como uma das linguagens mais promissoras para este ano, o Java voltou a ser um dos centros a partir de julho/2018.

Como é uma linguagem de programação bem versátil, você pode utilizá-la para diversas finalidades como desktop, web e até mobile. Para tudo isso, o Java atenderá suas necessidades.

Concluindo

Agora você pode estar se perguntando… “Ok, então eu tenho que escolher uma dessas três linguagens?” e a resposta é: não exatamente…

Por que não aprender as três? Essas são as linguagens mais bem classificadas pelos sites como pudemos ver acima.

Quanto maior o seu conhecimento, tanto na linguagem em si quanto na quantidade, pode abrir um leque de oportunidades. Você pode estudar uma por vez, para poder levar sua carreira a um próximo nível.

Até a próxima! 😊

Para que servem os métodos ToString(), Equals() e GetHashCode()?

Muitas pessoas costumam se confundir com três métodos que qualquer classe possui no .NET e no Java: os métodos ToString(), Equals() e GetHashCode(). Os dois primeiros são mais ou menos claros até certo ponto, mas o último vive causando confusão e pânico entre desenvolvedores Java e C#, ainda mais na hora em que necessitamos sobrescrevê-los… Que tal entendermos de uma vez por todas para que estes três métodos servem?

Importante: os conceitos que veremos aqui servem tanto para a plataforma Java quanto para a plataforma .NET! 😀

Por que toda classe tem estes métodos?

No Java e no .NET, toda classe tem um ancestral por “padrão”: a classe Object. Dessa maneira, qualquer objeto de qualquer classe que criarmos em Java ou .NET, pelo Princípio da Substituição de Liskov, também será uma instância de Object. E a classe Object já expõe por padrão pelo menos os três métodos que estão em discussão.

O método ToString()

Talvez este seja o método que é mais claro com relação ao seu propósito. Seu objetivo é trazer uma representação textual de uma instância de um objeto.
Essa representação textual de um objeto vem a ser muito útil principalmente em situações de debugging e de logging. Isso ocorre porque os métodos de saída para o streamming padrão (os famosos System.out.print[ln]() ou Console.Write[Line]()), assim como os principais métodos de praticamente todas as APIs de log (métodos como o debug() e info()) sempre chamam por padrão o método ToString() de instâncias de objetos que tenham sido passadas para eles.

O exemplo abaixo está escrito em C#, mas o princípio é exatamente o mesmo para o Java também, rs.

Imagine o código abaixo:

namespace TreinaWeb.Exemplo
{
    public class Pessoa
    {
        public string Nome { get; set; }
    }
}
// ...
Pessoa p = new Pessoa { Nome = "TreinaWeb" };
Console.WriteLine(p);
// ou
Console.WriteLine(p.ToString());

Perceba que não foi realizada nenhuma sobrecarga do método ToString(), o que faz com que a chamada deste método para a classe Pessoa encaminhe a chamada para a classe ancestral, ou seja: na verdade será chamado o método Object.ToString().

Não se esqueça: ==todo e qualquer objeto/classe sempre vai ter o método ToString() por causa da herança da classe Object==. Se a própria classe não implementa este método, a chamada será encaminhada para o ToString() “padrão”, ou seja, o método ToString() da classe Object.

No caso do C#, a saída do código abaixo seria a representada abaixo, independente da chamada explícita ao método ToString() ou não:

TreinaWeb.Exemplo.Pessoa

A saída tanto para a chamada explícita ao método ToString() como para a chamada ocultando-se a chamada é a mesma. Isso ocorre porque os método Console.Write[Line]() chama o método ToString() da instância repassada como parâmetro por padrão. 😉

O que o .NET emite como saída nesses casos é um elemento conhecido como Full Qualified Name, ou simplesmente FQN. Apesar de o nome assustar, ele é muito simples: trata-se somente do “nome completo” e único da classe, que é composto por namespace + nome da classe.

Se tivéssemos o código equivalente em Java, teríamos uma saída similar à abaixo:

TreinaWeb.Exemplo.Pessoa@1033203

A saída é muito similar ao FQN do .NET, com o acréscimo deste número precedido por @. Costumam dizer por aí que este número logo após o @ é a posição de memória do objeto. Daqui a pouco vamos ver que não é “beeem” assim, hehe.

O grande ponto é que essa saída fornecida pelo método ToString() não tem nenhuma utilidade prática para nós, tanto no streamming padrão de saída, quanto em um arquivo de log. Daí vem a necessidade da sobrescrita deste método.

A idéia é que o método ToString() forneça uma representação simplificada e direta do estado do objeto em questão. Uma maneira de atingir este objetivo é fazer com que a saída forneça o valor atual dos atributos do objeto.

Poderíamos sobrescrever o método ToString() da nossa classe Pessoa da seguinte maneira:

namespace TreinaWeb.Exemplo
{
    public class Pessoa
    {
        public string Nome { get; set; }

        public override string ToString()
        {
            return string.Format("Pessoa [Nome = {0}]", this.Nome);
        }
    }
}

Nossa saída agora, tanto em Java quanto em .NET, seria a mesma abaixo:

Pessoa [Nome = TreinaWeb]

Agora temos uma representação “palpável” e que faça sentido para nós. Essa é com certeza uma representação muito melhor do que as representações padrão da classe Object.

O método Equals()

Antes de falarmos do método Equals(), precisamos relembrar um pouco sobre manipulação de memória, principalmente sobre stack e heap.

Se você quer relembrar como funciona a manipulação de memória, tanto em Java quanto em .NET, você pode ver este nosso artigo, onde tratamos sobre manipulação de memória, stack, heap, value-types e reference-types.

Quando criamos um objeto da classe Pessoa, este objeto será armazenado na memória heap:

Pessoa minhaPessoa = new Pessoa();

Alocação de memória: reference-type

Não se esqueça de que o compilador não acessa os objetos na heap de maneira direta por questões de performance. Sendo assim, o acesso a esse objeto armazenado na heap é feito através de uma referência dentro da stack para o objeto minhaPessoa, apontando onde na memória heap que este objeto está de fato guardado! Sim: estamos falando de ponteiros.

Acesso à memória: reference-types

É importante entendermos estes conceitos para entendermos melhor como o método Equals() funciona.

Quando comparamos objetos, é considerada uma boa prática utilizarmos o método Equals() para fazer a comparação de igualdade. E, quando a classe a qual os objetos em questão pertencem não sobrescreve o método Equals(), o método Object.Equals() será chamado.

Vamos verificar o código abaixo, considerando ainda a classe Pessoa:

namespace TreinaWeb.Exemplo
{
    public class Pessoa
    {
        public string Nome { get; set; }
    }
}
// ...
Pessoa p = new Pessoa { Nome = "TreinaWeb" };
Pessoa p2 = new Pessoa { Nome = "TreinaWeb" }; 

Console.WriteLine(p.Equals(p2));

Faria muito sentido se esse código fornecesse como saída true, afinal, os objetos aparentemente são iguais, certo? Mas esse código irá produzir false, apesar de o nome nos dois objetos serem iguais… Por que isso acontece?

Aí entra em cena a relação entre o método Object.Equals() e a maneira como a memória é manipulada no Java e no .NET.

A cada vez que utilizamos a keyword new, nós estamos instruindo o compilador a reservar um espaço na memória heap e criar um ponteiro na stack para que seja possível acessar esta área da heap. Sendo assim, no código acima, são criadas duas áreas de memória na heap distintas. Estas áreas são gerenciadas, respectivamente, pelos ponteiros p e p2.

O que ocorre quando utilizamos o Equals() baseado na implementação de Object.Equals() é que este por padrão verifica se os ponteiros ==apontam para a mesma área de memória na heap!== Como temos dois objetos instanciados de maneira distintas (chamamos o new para cada um dos ponteiros), nós temos também duas posições de memória distintas para cada um dos objetos p e p2. Por isso, por padrão, temos como resposta false para o código acima.

Agora, vamos imaginar o código abaixo:

namespace TreinaWeb.Exemplo
{
    public class Pessoa
    {
        public string Nome { get; set; }
    }
}
// ...
Pessoa p = new Pessoa { Nome = "TreinaWeb" };
Pessoa p2 = p; 

Console.WriteLine(p.Equals(p2));

Agora sim! Perceba que para p2 nós não instruímos o compilador a criar uma nova área de memória na heap, e sim o instruímos a fazer com que p2 aponte para a mesma posição de memória na heap para a qual p aponta. Desta maneira, neste último código, obteremos true como resposta quando utilizamos a implementação do método Equals() baseada em Object.Equals().

Agora, não faz muito sentido para nós esta implementação do método Equals() baseado em Object.Equals(). Para nós, faria muito mais sentido que o método Equals() na primeira situação deste tópico retornasse true, afinal, ambos os objetos Pessoa possuem o mesmo nome. Por isso, é importantíssimo para nós sobrescrevermos de maneira adequada o método Equals().

Antes de partirmos para a sobrecarga, é importante entendermos algumas premissas para o método Equals():

  • Se ambos os objetos que estão sendo comparados apontam para a mesma posição de memória, é mandatório que Equals() retorne true, baseado na implementação de Object.Equals();
  • x.Equals(x) sempre tem que retornar true;
  • x.Equals(y) sempre tem que retornar o mesmo que y.Equals(x). Este princípio é conhecido como princípio de simetria;
  • Se x.Equals(y) e y.Equals(z), z.Equals(x) tem que retornar true também. Isso ocorre por decorrência do princípio da simetria;
  • x.Equals(null) sempre será falso. O método Equals() por definição não pode retornar exceções do tipo NullReferenceException ou NullPointerException. Isso faz sentido: se um ponteiro é nulo, na verdade ele não aponta para nenhuma área da heap e, portanto, é impossível fazer uma comparação coerente entre os objetos.

Tendo todos estes princípios em vista, temos 3 pontos importantes a serem observados quando sobrescrevermos o método Equals() para que este atenda a todos estes requisitos:

  • Precisamos ver se um dos participantes da chamada do método Equals() é nulo;
  • Precisamos ver se os dois objetos apontam para a mesma área de memória;
  • Precisamos comparar o estado interno dos participantes da chamada do Equals().

Poderíamos sobrescrever o método Equals() da nossa classe Pessoa() da seguinte maneira, afim de que a nossa sobrecarga atenda aos requisitos essenciais:

namespace TreinaWeb.Exemplo
{
    public class Pessoa
    {
        public string Nome { get; set; }

        public override bool Equals(Object obj)
        {
            // Verificando se o segundo participante está nulo
            if (obj == null)
            {
                return false;
            }
            Pessoa p2 = obj as Pessoa;
            // Verificando se o cast foi realizado com sucesso. 
            // Caso não foi, obj nem é um objeto do tipo Pessoa 
            // e automaticamente o método tem que retornar false
            // NOTA: o operador de cast "as" retorna
            // null caso o cast não seja possível
            if (p2 == null)
            {
                return false;
            }
            // Vamos agora verificar se ambos apontam para a mesma posição 
            // de memória utilizando Object.Equals()
            if (base.Equals(obj))
            {
                return true;
            }
            // Agora comparamos o estado interno dos objetos!
            return this.Nome == p2.Nome;
        }
    }
}

Agora nós temos o método Equals() devidamente sobrescrito e respeitando todas as condições necessárias. Considerando esta sobrecarga, se chamarmos o código abaixo…

Pessoa p = new Pessoa { Nome = "TreinaWeb" };
Pessoa p2 = new Pessoa { Nome = "TreinaWeb" }; 
Pessoa p3 = p2;

Console.WriteLine(p.Equals(p2));
Console.WriteLine(p2.Equals(p3));
Console.WriteLine(p.Equals(p3));

… obteremos true em todas as saídas, o que faz muito sentido!

No caso específico do C#, nós ainda poderíamos utilizar p == p2, o que causaria também a chamada de Object.Equals(). Por isso, no C#, temos a possibilidade de fazermos a sobrescrita também de operadores. Nesta situação, precisaríamos sobrescrever o operador == para a classe Pessoa para que tenhamos tudo “nos trilhos” e não tenhamos duas implementações distintas de igualdade. Da mesma maneira, acaba sendo prudente sobrescrever também o operador !=.
Nosso código poderia ficar como está abaixo:

namespace TreinaWeb.Exemplo
{
    public class Pessoa
    {
        public string Nome { get; set; }

        public static bool operator ==(Pessoa a, Pessoa b)
        {
            // Fazemos o cast para Object
            // para evitar StackOverFlowException:
            // o código cairia em loop infinito
            // chamando de maneira encadeada
            // o método Equals() sobrescrito
            // e o operador == sobrescrito! 😉
            if ((((object)a) == null) || (((object)b) == null))
            {
                return false;
            }
            return object.Equals(a, b) || a.Nome == b.Nome;
        }

        public static bool operator !=(Pessoa a, Pessoa b)
        {
            return !(a == b);
        }

        public override bool Equals(Object obj)
        {
            // Verificando se o segundo participante está nulo
            if (obj == null)
            {
                return false;
            }
            Pessoa p2 = obj as Pessoa;
            // Verificando se o cast foi realizado com sucesso. 
            // Caso não foi, obj nem é um objeto do tipo Pessoa 
            // e automaticamente o método tem que retornar false
            if (p2 == null)
            {
                return false;
            }
            // Vamos agora verificar se ambos apontam 
            // para a mesma posição de memória utilizando Object.Equals()
            if (base.Equals(obj))
            {
                return true;
            }
            // Agora comparamos o estado interno dos objetos!
            return this.Nome == p2.Nome;
        }
    }
}
O método GetHashCode() ou hashCode()

Por fim, temos o famigerado método GetHashCode() ou hashCode(). Este é um dos métodos que causam mais confusão nos desenvolvedores.

O hash code é um número inteiro que é gerado de maneira única para cada objeto que esteja alocado em memória. É como se ele fosse um ID único para cada objeto que esteja sob domínio da CLR ou da JVM.

E onde este código único é utilizado? Aí é a grande sacada! Ele é utilizado principalmente dentro de coleções com a finalidade de melhoria da performance. A JVM e a CLR utilizam o hash code internamente para localizar objetos em coleções de maneira mais rápida. E daí também vem sua relação direta com o método Equals().

Uma das maneiras que os ambientes de execução do .NET e do Java utilizam para ver se um determinado objeto existe dentro de uma coleção é comparando os hash codes dos objetos pertencentes à coleção com o hash code do objeto a ser localizado. E, se um objeto é localizado dentro de uma coleção, é porque ele é igual ao elemento dentro de alguma posição da coleção em questão. Percebe a relação entre GetHashCode() e Equals()?

Por isso, vale a máxima abaixo para o método GetHashCode():

Se x.Equals(y) e x e y são objetos da mesma classe, x.GetHashCode() == y.GetHashCode() tem que obrigatoriamente retornar true.

A sobrescrita do Equals() impacta diretamente na implementação do GetHashCode() e vice-versa por causa dessa relação entre os dois. E é por isso que as IDEs emitem warnings quando você, por exemplo, sobrescreve o método Equals() e não sobrescreve o método GetHashCode() e vice-versa.

Agora, um ponto interessante: nem a CLR e nem a JVM garantem a situação inversa! Isso quer dizer que dois objetos, deste que de tipos (ou classes) diferentes, podem por coincidência retornar o mesmo hash code!

Se x é do tipo A e y é do tipo B (ou seja: x e y são objetos de classes diferentes), x.Equals(y) irá retornar por definição false; porém, pode ser que x.GetHashCode() == y.GetHashCode() retorne true.

Essa situação, apesar de ser incomum, pode acontecer. Ela é chamada de colisão. A colisão, quando ocorre, geralmente acontece por causa de sobrescrita equivocada do método GetHashCode().

O método padrão Object.GetHashCode() tem uma implementação um pouco complexa. Se estivermos falando de .NET, a chamada a Object.GetHashCode() irá ser convertida para uma chamada de baixo nível para ObjectNative::GetHashCode. Se estivermos falando de Java, a chamada de Object.hashCode() irá converter a posição de memória onde o objeto está alocado para uma representação numérica, adotando esta representação como sendo o hash code. Inclusive, lembra-se da implementação padrão do método toString() no Java? Pois então… Aquele número estranho que vem depois do @ é o hash code do objeto! =)

Existe um outro ponto muito importante com relação ao método GetHashCode():

Se GetHashCode() tem relação direta com Equals(), um mesmo objeto sempre deverá retornar o mesmo hash code, da mesma maneira que seu método Equals() sempre retornará o mesmo resultado quando haver uma comparação entre dois objetos.

Aí entra um problema grave: a implementação padrão vinda de Object.GetHashCode() não retorna o mesmo hash code para o mesmo objeto. Na verdade, a cada chamada ao método padrão Object.GetHashCode(), um novo hash code será invocado. Isso faz cair por terra o ganho de performance que a utilização de GetHashCode() poderia trazer… Daí vem a necessidade de sobrescrevermos corretamente este método em nossas classes.

Uma técnica geralmente utilizada para conseguirmos sobrescrever corretamente o método GetHashCode() é somar os hash codes de todos os atributos da classe e multiplicar por um número primo. Isso reduz bastante as chances de haver algum tipo de colisão.

Sendo assim, poderíamos sobrescrever o método GetHashCode() da classe Pessoa da seguinte maneira:

namespace TreinaWeb.Exemplo
{
    public class Pessoa
    {
        public string Nome { get; set; }

        public override int GetHashCode()
        {
            // 17 é um número primo! 😉
            return this.Nome.GetHashCode() * 17;
        }
    }
}

O método GetHashCode() auxilia na performance dentro de coleções porque é muito mais simples para os compiladores comparar dois números inteiros do que chamar o método Equals() para cada elemento que faça parte da coleção, sendo que a implementação do Equals() pode ser um pouco complexa e lenta.

Sendo assim, quando o compilador precisa localizar um objeto dentro de uma coleção, ele faz uma iteração em cada elemento que faça parte e faz a comparação com o objeto a ser localizado da seguinte maneira:

  • A primeira comparação é feita através do hash code dos objetos envolvidos. Se eles forem diferentes, o compilador para o trabalho por aqui, já que se dois objetos são iguais (ou seja, o método Equals() com os dois objetos deveria retornar true), o hash code de ambos também deveria ser igual;
  • Caso os hash codes sejam iguais, o compilador levanta a hipótese de estar havendo uma colisão. Então, ele chama o método Equals() para confirmar se os objetos são iguais ou não. Se eles forem iguais, o compilador considera que encontrou o objeto dentro da coleção. Caso não, o compilador avança para o próximo item da coleção e reinicia o processo de comparação.

Consegue perceber como o hash code pode acelerar o processo de manipulação de coleções? Ele ajuda o compilador a evitar a chamada ao método Equals(), que pode ser lento, de maneira desnecessária! o/

O método GetHashCode() é importantíssmo para coleções. Desde coleções mais básicas, como ArrayList; até coleções mais complexas, como Dictionary<TKey, TValue> ou Map<K, U>, se utilizam deste método. O Dictionary<TKey, TValue>, inclusive, utiliza este método para localizar se existem chaves duplicadas ou não.

Os métodos ToString(), Equals() e GetHashCode() são importantíssimos!

Esperamos que você tenha percebido melhor a importância desses métodos para os desenvolvedores. Muitos costumam não dar a devida importância para a sobrescrita correta destes métodos, o que pode ocasionar problemas bem críticos no código em determinadas situações (principalmente quando falamos de serialização de objetos e ambientes de alta concorrência). Você, inclusive, pode acompanhar um problema decorrente da sobrescrita e utilização incorretas destes métodos em um ambiente real neste post do StackOverflow.

Tem alguma dúvida? Quer discutir sobre algum determinado ponto? Quer expor uma situação pela qual você já passou no seu dia-a-dia como desenvolvedor que envolvia a utilização destes métodos? Compartilha com a gente nos comentários! Vamos discutir sobre este assunto! o/

Até o próximo post! =)

Entendendo o método merge do JPA

O JPA trouxe muitos benefícios para os desenvolvedores Java, mas existe certa dificuldade em entender alguns detalhes desta especificação.

Para facilitar esta compreensão, vamos aqui analisar um método que gera muita controversa, que é o merge.

Estados de um objeto JPA

Antes de vermos o método merge em detalhes, é importante entendermos os estados das entidades JPA, pois estão diretamente relacionados com a função deste método.

No JPA um objeto pode assumir quatro estados: New, Managed, Removed e Detached. A imagem abaixo ilustra o ciclo de vida de um objeto que passa por esses quatro estados:

Quando um objeto é criado, ele se encontra no estado New. Neste estado, o objeto não tem nenhuma relação com EntityManager ou uma representação
no banco de dados. Qualquer alteração no objeto, neste estado, não é notada pelo JPA.

Quando o objeto é persistido (geralmente em um banco de dados), ele passa para o estado Managed. Neste, qualquer alteração no objeto será sincronizada com o dado persistido. Essa sincronização não é em tempo real. Uma alteração no objeto não irá executar automaticamente um comando SQL, isso só irá acontecer em “flush time”.

O “flush time” ocorre por padrão (de acordo com a implementação do Hibernate, uma vez que a especificação do JPA não define isso) em três momentos:

  • Antes da execução de uma query.
  • Na chamada do método commit() de EntityTransaction.
  • Na chamada do método flush() de EntityManager.

É muito comum o objeto se manter por pouco tempo no estado Managed, pois, geralmente, após persistí-lo, o EntityManager já é fechado e então ele passa para o estado Detached.

O estado Detached significa que o objeto não está vinculado ao EntityManager. Qualquer alteração não irá impactar no dado persistido. Isso só irá ocorrer se o estado voltar a ser Managed.

Por fim, temos o estado Removed. O objeto assume este estado quando um objeto Managed é marcado para a remoção com o método remove.

Funcionamento do merge

Agora que conhecemos os estados de um objeto JPA, é possível afirmar que ele só pode ser alterado, quando este está no estado Managed. Mas como
definir um objeto neste estado? Bom, isso pode ser feito através de dois métodos: persist e merge.

O funcionamento do persist é bem simples: ao ser chamado, o objeto é salvo no banco e o seu estado muda para Managed.

Já o funcionamento do merge irá depender de alguns fatores. O seu comportamento principal é lidar com objetos no estado Detached. Quando um objeto neste estado é passado para o método merge, é retornado um novo objeto no estado Managed.

Se o objeto estiver no estado New, mas tendo os seus dados exatamente iguais aos persistidos na base, o merge retornará um novo objeto no estado Managed. Mas, se o objeto no estado New contiver ao menos um dado diferente dos persistidos, será gerado um novo registro e, um novo objeto no estado Managed será retornado.

Se o objeto contiver outros objetos relacionados (através de uma propriedade de relacionamento) e o JPA não conseguir localizar seus dados no banco, também será gerado um novo registro do objeto e também dos que estiverem relacionados. Além disso, um novo objeto no estado Managed também será retornado.

Se o objeto estiver no estado Removed o método merge irá gerar um erro.

Repare que sempre cito que o merge retorna o objeto no estado Managed. Assim, o uso correto desse método é:

Entidade e = new Entidade();

Entidade e2 = em.merge(e);

e2.setAtributo(novoValor);

Veja que penas o objeto e2 se encontra no estado Managed. Qualquer alteração em e não será sincronizada com o banco. Se o e fosse alterado, como abaixo:

Entidade e = new Entidade();

em.merge(e);

e.setAtributo(novoValor);

Ao executar o “flush”, a alteração não seria refletida no banco de dados.

Caso não tenha certeza se o método merge irá gerar um novo registro ou não, opte pelo uso do find:

Entidade e = em.find(Entidade.class, 1);

Este método sempre irá retornar um objeto no estado Managed (ou null se o registro não for encontrado). Aí não corremos o risco de gerar um novo registro apenas para se obter um objeto neste estado.

JUNTE-SE A MAIS DE 150.000 PROGRAMADORES