Java JPA Hibernate

Exemplo JPA com Hibernate.

Ingredientes

  • Docker
  • Docker Compose
  • Maven
  • Open JDK 11
  • Hibernate
  • PostgresSQL
  • H2 Database

  • vscode
  • vscode language support for Java(TM) by Red Hat
  • vscode extension pack for Java (Microsoft)
  • vscode debugger for Java (Microsoft)
  • vscode maven for Java (Microsoft)
  • vscode project management for Java (Microsoft)
  • vscode test runner for Java (Microsoft)
  • vscode Java code generators (Microsoft)
  • vscode XML language support by Red Hat

Preparo:

  1. No vscode: ctrl + shift + p
  2. Selecione Java: Create Java Project
  3. Selecione Maven
  4. Selecione maven-archetype-quickstart

Docker

Dockerfiles

maven:

FROM maven:3.8.3-openjdk-17-slim

RUN apt-get update && apt-get install -y netcat
ARG USER_HOME_DIR="/root"

ENV MAVEN_HOME /usr/share/maven
ENV MAVEN_CONFIG "$USER_HOME_DIR/.m2"

VOLUME "$USER_HOME_DIR/.m2"

WORKDIR /app
COPY src /app/src
COPY pom.xml /app

# Add entrypoint
COPY scripts/entrypoint.sh /app/scripts/entrypoint.sh
CMD ["chmod", "+x", "/app/scripts/entrypoint.sh"]
ENTRYPOINT ["/app/scripts/entrypoint.sh"]

maven test:

FROM maven:3.8.3-openjdk-17-slim

RUN apt-get update && apt-get install -y netcat
WORKDIR /app
COPY src /app/src
COPY pom.xml /app

Entrypoint

Produção:

#!/bin/bash
# --------------------------------------------------------------------------- #
# Wait until database starts.
# --------------------------------------------------------------------------- #
if [ "$DATABASE" = "postgres" ]
then
    echo "Waiting for postgres..."

    while ! nc -z $SQL_HOST $SQL_PORT; do
      sleep 0.1
    done

    echo "PostgreSQL started"
fi
# --------------------------------------------------------------------------- #
# Compile application.
# --------------------------------------------------------------------------- #
mvn -T 2C clean package \
  -Dmaven.test.skip -DskipTests -f  /app  && \
# --------------------------------------------------------------------------- #
# Run application main class.
# --------------------------------------------------------------------------- #
mvn -e exec:java \
  -Dexec.cleanupDaemonThreads=false \
  -Dexec.mainClass="com.dmenezesgabriel.jpa.view.ProfileView"

Docker Compose

# # hibernate/docker-compose.yml
version: "3.9"

services:
  # ------------------------------------------------------------------------- #
  # Production Database
  # ------------------------------------------------------------------------- #
  postgres:
    container_name: postgres
    image: postgres:14.0-alpine
    # volumes:
    # - ./postgres_data:/var/lib/postgresql/data/
    ports:
      - 5432:5432
    environment:
      POSTGRES_PASSWORD: postgres
    networks:
      - prod
  # ------------------------------------------------------------------------- #
  # Database Tool
  # ------------------------------------------------------------------------- #
  pgadmin:
    container_name: pgadmin
    image: dpage/pgadmin4
    ports:
      - 8087:80
      - 4443:443
    environment:
      PGADMIN_DEFAULT_EMAIL: dmenezes.gabriel@gmail.com
      PGADMIN_DEFAULT_PASSWORD: postgres
      PGADMIN_CONFIG_ENHANCED_COOKIE_PROTECTION: "True"
      PGADMIN_CONFIG_CONSOLE_LOG_LEVEL: 10
    depends_on:
      - postgres
    networks:
      - prod
  # ------------------------------------------------------------------------- #
  # Production Maven
  # ------------------------------------------------------------------------- #
  maven:
    build:
      context: .
      dockerfile: dockerfiles/Dockerfile-maven
    volumes:
      - ./.m2:/root/.m2
      - $PWD/src:/app/src
    networks:
      - prod
    depends_on:
      - postgres
    environment:
      # entrypoint.sh database health check variables
      SQL_HOST: postgres
      SQL_PORT: 5432
      DATABASE: postgres
  # ------------------------------------------------------------------------- #
  # Maven Tests
  # ------------------------------------------------------------------------- #
  maven-test:
    build:
      context: .
      dockerfile: dockerfiles/Dockerfile-maven-test
    command: /bin/bash -c 'mvn -e clean test'
    volumes:
      - ./.m2:/root/.m2
      - $PWD/src:/app/src
    networks:
      - test
# -------------------------------------------------------------------------- #
# Docker networks
# -------------------------------------------------------------------------- #
networks:
  prod:
  test:

Código fonte

Entidades

Usuário:

// hibernate/src/main/java/com/dmenezesgabriel/jpa/domain/User.java
package com.dmenezesgabriel.jpa.domain.entity;

/**
 * User entity
 *
 * @author Gabriel Menezes
 * @version 1.0
 */
import java.util.Calendar;
import javax.persistence.Column;
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.SequenceGenerator;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import java.io.Serializable;
import java.text.SimpleDateFormat;

import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;
import java.util.Objects;

@Entity
@Table(name = "tbl_usuario")
// Each class has it's own table and subclass tables are mapped to superclass
@Inheritance(strategy = InheritanceType.JOINED)
public class User implements Serializable {
    protected static final long serialVersionUID = 1L;

    @Id
    @SequenceGenerator(name = "tbl_usuario_cd_usuario_seq",
            sequenceName = "tbl_usuario_cd_usuario_seq", allocationSize = 1)
    @GeneratedValue(strategy = GenerationType.SEQUENCE,
            generator = "tbl_usuario_cd_usuario_seq")
    @Column(name = "cd_usuario", updatable = false)
    // The naming tablename_columname_seq is the PostgreSQL default sequence
    // naming
    // for SERIAL
    // The allocationSize=1 is important if you need Hibernate to co-operate
    // with
    // other clients
    // Note that this sequence will have "gaps" in it if transactions roll back.
    // Never assume that for any id n there is an id n-1 or n+1.
    private int id;

    @Column(name = "nm_usuario", nullable = false, length = 60)
    private String name;

    @Column(name = "ds_email", nullable = false, unique = true, length = 60)
    private String email;

    @Column(name = "ds_senha", nullable = false, length = 60)
    private String password;

    @CreationTimestamp
    @Temporal(TemporalType.TIMESTAMP)
    @Column(name = "dt_criacao", columnDefinition = "TIMESTAMP WITH TIME ZONE",
            updatable = false)
    private Calendar createdAt;

    @UpdateTimestamp
    @Temporal(TemporalType.TIMESTAMP)
    @Column(name = "dt_atualizacao",
            columnDefinition = "TIMESTAMP WITH TIME ZONE")
    private Calendar updatedAt;

    // Constructor - getters and setters


    public User() {}

    public User(int id, String name, String email, String password,
            Calendar createdAt, Calendar updatedAt) {
        this.id = id;
        this.name = name;
        this.email = email;
        this.password = password;
        this.createdAt = createdAt;
        this.updatedAt = updatedAt;
    }

    public int getId() {
        return this.id;
    }

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

    public String getName() {
        return this.name;
    }

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

    public String getEmail() {
        return this.email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String getPassword() {
        return this.password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public Calendar getCreatedAt() {
        return this.createdAt;
    }

    public void setCreatedAt(Calendar createdAt) {
        this.createdAt = createdAt;
    }

    public Calendar getUpdatedAt() {
        return this.updatedAt;
    }

    public void setUpdatedAt(Calendar updatedAt) {
        this.updatedAt = updatedAt;
    }

    public User id(int id) {
        setId(id);
        return this;
    }

    public User name(String name) {
        setName(name);
        return this;
    }

    public User email(String email) {
        setEmail(email);
        return this;
    }

    public User password(String password) {
        setPassword(password);
        return this;
    }

    public User createdAt(Calendar createdAt) {
        setCreatedAt(createdAt);
        return this;
    }

    public User updatedAt(Calendar updatedAt) {
        setUpdatedAt(updatedAt);
        return this;
    }

    @Override
    public boolean equals(Object o) {
        if (o == this)
            return true;
        if (!(o instanceof User)) {
            return false;
        }
        User user = (User) o;
        return id == user.id && Objects.equals(name, user.name)
                && Objects.equals(email, user.email)
                && Objects.equals(password, user.password)
                && Objects.equals(createdAt, user.createdAt)
                && Objects.equals(updatedAt, user.updatedAt);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id, name, email, password, createdAt, updatedAt);
    }

    @Override
    public String toString() {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");

        return "{" + " id='" + getId() + "'" + ", name='" + getName() + "'"
                + ", email='" + getEmail() + "'" + ", password='"
                + getPassword() + "'" + ", createdAt='"
                + sdf.format(createdAt.getTime()) + "'" + ", updatedAt='"
                + sdf.format(updatedAt.getTime()) + "'" + "}";
    }

}

Gênero Enum:

// hibernate/src/main/java/com/dmenezesgabriel/jpa/domain/Gender.java
package com.dmenezesgabriel.jpa.domain.entity;

public enum Gender {

    MALE("masculino"), FEMALE("feminino");

    private String description;

    Gender(String description) {
        this.description = description;
    }

    public String getDescription() {
        return description;
    }
}

Testes

Entidades:

// hibernate/src/test/java/com/dmenezesgabriel/jpa/User.java
package com.dmenezesgabriel.jpa.domain.entity;

import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

public class TestUser {
    @Test
    @DisplayName("Should instance User object correctly.")
    public void shouldInstanceObjectCorrectly() throws Exception {
        User user = new User();

        user.setName("Gabriel");
        user.setEmail("gabriel@example.com");
        user.setPassword("123");

        assertEquals(user.getName(), "Gabriel");
        assertEquals(user.getEmail(), "gabriel@example.com");
        assertEquals(user.getPassword(), "123");

    }
}

persistence.xml

Produção:

<!-- src/main/resources/META-INF/persistence.xml -->
<persistence xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence
             http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd" version="2.1">

  <!-- transaction-type -->
  <!-- - RESOURCE_LOCAL -->
  <!-- - JTA -->
  <persistence-unit name="com.dmenezesgabriel.jpa" transaction-type="RESOURCE_LOCAL">
    <!-- Framework used for JPA especification -->
    <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
    <class>com.dmenezesgabriel.jpa.domain.entity.User</class>
    <class>com.dmenezesgabriel.jpa.domain.entity.Phone</class>
    <class>com.dmenezesgabriel.jpa.domain.entity.Address</class>
    <class>com.dmenezesgabriel.jpa.domain.entity.Customer</class>
    <class>com.dmenezesgabriel.jpa.domain.entity.EstablishmentResponsible</class>
    <class>com.dmenezesgabriel.jpa.domain.entity.Document</class>
    <class>com.dmenezesgabriel.jpa.domain.entity.Establishment</class>
    <class>com.dmenezesgabriel.jpa.domain.entity.EstablishmentSegment</class>
    <class>com.dmenezesgabriel.jpa.domain.entity.EstablishmentEvaluation</class>
    <class>com.dmenezesgabriel.jpa.domain.entity.Product</class>
    <class>com.dmenezesgabriel.jpa.domain.entity.ProductCategory</class>
    <class>com.dmenezesgabriel.jpa.domain.entity.ProductType</class>
    <class>com.dmenezesgabriel.jpa.domain.entity.GuestCheckPad</class>

    <properties>
      <!-- Database Driver -->
      <property name="javax.persistence.jdbc.driver" value="org.postgresql.Driver" />
      <!-- Database connection uri -->
      <!-- "jdbc:sgdb_name://host_address:port_number/database_name" -->
      <!-- - When using docker, host addres is the container name -->
      <property name="javax.persistence.jdbc.url" value="jdbc:postgresql://postgres:5432/postgres" />
      <!-- Database User -->
      <property name="javax.persistence.jdbc.user" value="postgres" />
      <!-- Database Password -->
      <property name="javax.persistence.jdbc.password" value="postgres" />
      <!-- Database Dialect -->
      <property name="hibernate.dialect" value="org.hibernate.dialect.PostgreSQLDialect" />
      <!-- create / create-drop / update -->
      <!-- - create: creates database from scratch using definitions -->
      <!-- - update: try to update new attributes -->
      <!-- - validate: verify if the entities are compatible with db tables -->
      <property name="hibernate.hbm2ddl.auto" value="update" />
      <!-- Show SQL in console -->
      <!-- Should be disabled in production -->
      <property name="hibernate.show_sql" value="false" />
      <!-- Show SQL formatted -->
      <property name="hibernate.format_sql" value="true" />
    </properties>

  </persistence-unit>

</persistence>

Testes:

  • H2 Database: banco em memória.
<!-- src/test/resources/META-INF/persistence.xml -->
<persistence xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence
             http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd" version="2.1">

  <!-- transaction-type -->
  <!-- - RESOURCE_LOCAL -->
  <!-- - JTA -->
  <persistence-unit name="com.dmenezesgabriel.jpa" transaction-type="RESOURCE_LOCAL">
    <!-- Framework used for JPA especification -->
    <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
    <class>com.dmenezesgabriel.jpa.domain.entity.User</class>
    <class>com.dmenezesgabriel.jpa.domain.entity.Phone</class>
    <class>com.dmenezesgabriel.jpa.domain.entity.Address</class>
    <class>com.dmenezesgabriel.jpa.domain.entity.Customer</class>
    <class>com.dmenezesgabriel.jpa.domain.entity.EstablishmentResponsible</class>
    <class>com.dmenezesgabriel.jpa.domain.entity.Document</class>
    <class>com.dmenezesgabriel.jpa.domain.entity.Establishment</class>
    <class>com.dmenezesgabriel.jpa.domain.entity.EstablishmentSegment</class>
    <class>com.dmenezesgabriel.jpa.domain.entity.EstablishmentEvaluation</class>
    <class>com.dmenezesgabriel.jpa.domain.entity.Product</class>
    <class>com.dmenezesgabriel.jpa.domain.entity.ProductCategory</class>
    <class>com.dmenezesgabriel.jpa.domain.entity.ProductType</class>
    <class>com.dmenezesgabriel.jpa.domain.entity.GuestCheckPad</class>


    <properties>
      <!-- Database Driver -->
      <property name="javax.persistence.jdbc.driver" value="org.h2.Driver" />
      <!-- Database connection uri -->
      <!-- "jdbc:sgdb_name://host_address:port_number/database_name" -->
      <!-- - When using docker, host addres is the container name -->
      <property name="javax.persistence.jdbc.url" value="jdbc:h2:mem:test" />
      <!-- Database User -->
      <property name="javax.persistence.jdbc.user" value="" />
      <!-- Database Password -->
      <property name="javax.persistence.jdbc.password" value="" />
      <!-- Database Dialect -->
      <property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect" />
      <!-- create / create-drop / update -->
      <!-- - create: creates database from scratch using definitions -->
      <!-- - update: try to update new attributes -->
      <!-- - validate: verify if the entities are compatible with db tables -->
      <property name="hibernate.hbm2ddl.auto" value="create" />
      <!-- Show SQL in console -->
      <!-- Should be disabled in production -->
      <property name="hibernate.show_sql" value="false" />
      <!-- Show SQL formatted -->
      <property name="hibernate.format_sql" value="true" />
    </properties>

  </persistence-unit>

</persistence>

pom.xml

<!-- hibernate/pom.xml -->
<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>
  <!-- ==================================================================== -->
  <!-- Organization -->
  <!-- ==================================================================== -->
  <!-- organization/application url on the contrary br.com.domain -->
  <groupId>com.dmenezesgabriel</groupId>
  <!-- ==================================================================== -->
  <!--  Project -->
  <!-- ==================================================================== -->
  <!-- Artifacty ID must be unique within the group -->
  <artifactId>jpa</artifactId>
  <version>1.0-SNAPSHOT</version>
  <name>jpa</name>
  <!-- ==================================================================== -->
  <!-- Project's Website -->
  <!-- ==================================================================== -->
  <url>dmenezesgabriel.github.io</url>
  <!-- ==================================================================== -->
  <!-- Projects properties -->
  <!-- ==================================================================== -->
  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
  </properties>
  <!-- ==================================================================== -->
  <!-- Project's dependencies -->
  <!-- ==================================================================== -->
  <dependencies>
    <dependency>
      <groupId>org.junit.jupiter</groupId>
      <artifactId>junit-jupiter-engine</artifactId>
      <version>5.8.2</version>
      <scope>test</scope>
    </dependency>
    <!-- end::junit[] -->
    <!-- tag::hibernate[] -->
    <dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate-core</artifactId>
      <version>5.4.24.Final</version>
    </dependency>
    <!-- end::hibernate[] -->
    <!-- tag::oracle[] -->
    <!-- https://mvnrepository.com/artifact/com.oracle.database.jdbc/ojdbc8 -->
    <dependency>
      <groupId>com.oracle.database.jdbc</groupId>
      <artifactId>ojdbc8</artifactId>
      <version>21.1.0.0</version>
    </dependency>
    <!-- end::oracle[] -->
    <!-- tag::postgres[] -->
    <dependency>
      <groupId>org.postgresql</groupId>
      <artifactId>postgresql</artifactId>
      <version>42.3.3</version>
    </dependency>
    <!-- end::postgres[] -->
    <!-- tag::H2[] -->
    <dependency>
      <groupId>com.h2database</groupId>
      <artifactId>h2</artifactId>
      <version>2.1.210</version>
    </dependency>
    <!-- end::H2[] -->
    <!-- tag::log4j[] -->
    <dependency>
      <groupId>org.apache.logging.log4j</groupId>
      <artifactId>log4j-api</artifactId>
      <version>2.17.1</version>
    </dependency>
    <dependency>
      <groupId>org.apache.logging.log4j</groupId>
      <artifactId>log4j-core</artifactId>
      <version>2.17.1</version>
    </dependency>
    <!-- end::log4j[] -->
  </dependencies>
  <!-- ==================================================================== -->
  <!-- Project's build -->
  <!-- ==================================================================== -->
  <build>
    <pluginManagement>
      <plugins>
        <!-- clean lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#clean_Lifecycle -->
        <plugin>
          <artifactId>maven-clean-plugin</artifactId>
          <version>3.1.0</version>
        </plugin>
        <!-- default lifecycle, jar packaging: see https://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_jar_packaging -->
        <plugin>
          <artifactId>maven-resources-plugin</artifactId>
          <version>3.0.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-compiler-plugin</artifactId>
          <version>3.8.0</version>
        </plugin>
        <plugin>
          <artifactId>maven-surefire-plugin</artifactId>
          <version>2.22.1</version>
        </plugin>
        <plugin>
          <artifactId>maven-jar-plugin</artifactId>
          <version>3.0.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-install-plugin</artifactId>
          <version>2.5.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-deploy-plugin</artifactId>
          <version>2.8.2</version>
        </plugin>
        <!-- site lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#site_Lifecycle -->
        <plugin>
          <artifactId>maven-site-plugin</artifactId>
          <version>3.7.1</version>
        </plugin>
        <plugin>
          <artifactId>maven-project-info-reports-plugin</artifactId>
          <version>3.0.0</version>
        </plugin>
      </plugins>
    </pluginManagement>
  </build>
</project>

Compilar e executar

Instruções:

Description

General Sales Management System

Requirements

  • Openjdk 17
  • Hibernate
  • Docker
  • Docker Compose

Usage

Run:

# Optional
docker-compose up -d pgadmin
# Run
docker-compose run maven --rm

Run tests:

# Run
docker-compose run --rm maven-test

Recompile and Run:

# Run
docker-compose run maven --rm

Bring down with volumes:

# Destroy database storage
docker-compose down -v

Referências

Updated: