Data Persistence with Hibernate and Spring

Data Persistence with Hibernate and Spring

Data Persistence with Hibernate and Spring

With the advent of Hibernate (and many similar tools) the Java EE team decided to propose a new pattern to guide ORM frameworks using a single language. The JPA (Java Persistence API) was created and it is entirely defined as Java annotations (besides XML) which increase code readability and maintainability.

Java developers typically encounter the need to store data on a regular basis. If you’ve been developing for more than 15 years, you probably remember the days of JDBC in Java. Using JDBC can be tedious if you don’t like writing SQL. Not only that, but there’s nothing in JDBC that helps you create your database. Hibernate came along and changed everything by allowing you to map POJOs (plain ol’ Java objects) to database tables. Not only that, but it had a very Java-esque API that made it easy to create CRUD POJOs. Shortly after, Spring came along and added abstractions for Hibernate that took API simplification even further. Fast forward to today, and most Java applications use both Spring and Hibernate.

For some time now, developers have operated under one of two separate but distinct models to represent business entities. The relational model, which is prevalent in databases, and the object-oriented model. These two models are similar in that both work using similar structures to represent business logic, and they are distinct in that they were designed for different purposes: one to store data, other to describe behavior.

Use Hibernate Old Fashioned Way, without Spring

With the advent of Hibernate (and many similar tools) the Java EE team decided to propose a new pattern to guide ORM frameworks using a single language. The JPA (Java Persistence API) was created and it is entirely defined as Java annotations (besides XML) which increase code readability and maintainability. Below is an example of an ol’ school XML-based mapping and more current annotation based mapping for the same entity.

Xml-Based mapping

<hibernate-mapping>
  <class name="net.dovale.okta.springhibernate.spring.entities.Teacher" table="teacher">
    <id name="id" type="java.lang.Long">
      <column name="id" />
      <generator class="identity" />
    </id>
    <property name="name" type="string">
      <column name="name" length="255" not-null="true" />
    </property>
    <property name="pictureURL" type="string">
      <column name="pictureURL" length="255" not-null="true"  />
    </property>
    <property name="email" type="string">
      <column name="email" length="255" not-null="true" unique="true" />
    </property>
  </class>
</hibernate-mapping>


Annotation-based mapping

@Entity
@Table(uniqueConstraints = @UniqueConstraint( name = "un_teacher_email", columnNames = {"email" }))
public class Teacher {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @NotNull
    private String name;
    @NotNull
    private String pictureURL;
    @NotNull
    private String email;

    // (...) getter and setters (...)
}


In this post, you are going to work in two different technology stacks. You’ll create a very simple project using Hibernate (JPA annotations), then move to a Spring Boot project doing the same thing. After that, you’ll be introduced to Project Lombok which will reduce your project’s lines-of-code counter even further. Then, you are going to expose the CRUD operations as REST endpoints secured with Okta, and OAuth 2.0.

Create a Project Using Hibernate

In this project, you are going to use core Hibernate functionality with JPA annotations. You are not addressing XML configuration as it is not commonly used nowadays.

The project is already implemented here on the raw branch. The database model is represented in the following model:

+--------+*        1 +-------+
| Course +---------> |Teacher|
+--------+           +-------+


For simplicity’s sake, the database chose is an H2 Database, an in memory, small, 100% Java database, excellent for testing and development purposes. The project has two significant dependencies:

<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-core</artifactId>
    <version>5.3.6.Final</version>
</dependency>
<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <version>1.4.197</version>
</dependency>


Each database table is mapped to an entity. Course table is represented by net.dovale.entities.Course entity and Teacher is represented by net.dovale.entities.Teacher. Let’s take a look into the Teacher entity:

package net.dovale.entities;

import javax.persistence.*;

@Entity
public class Teacher {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    private String name;
    private String pictureURL;
    private String email;

    public Teacher() { }

    public Teacher(String name, String pictureURL, String email) {
        this.name = name;
        this.pictureURL = pictureURL;
        this.email = email;
    }
    // (...) getter and setters (...)
}


You just need to add @Entity annotation for Hibernate to understand the database must have a table with the same class name. Also, the entity has an @Id which means the attribute with it is an entity identifier. In this case, we defined how our ID’s will be automatically generated (the decision is up to Hibernate dialect).

Now, you are going to review a more complex relationship type on Course entity:

package net.dovale.entities;

import javax.persistence.*;

@Entity
public class Course {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    private String name;
    private int workload;
    private int rate;
    @ManyToOne
    @JoinColumn(foreignKey = @ForeignKey(name = "fk_course_teacher"))
    private Teacher teacher;

    public Course() { }

    public Course(String name, int workload, int rate, Teacher teacher) {
        this.name = name;
        this.workload = workload;
        this.rate = rate;
        this.teacher = teacher;
    }
    // (...) getter and setters (...)
}


As you can see, there is a @ManyToOneand a @JoinColumn annotation. Those annotations represent, as the name says, a many-to-one relationship (when a single entity has many relationships with other entity). The annotation @JoinColumn specifies the relationship must be made by a column in the One entity (Course, in this case) and @ForeignKey specifies the constraint name. I always recommend specifying the foreign key name to help to debugging.

We have two DAO’s (Data Access Objects) on this project: CourseDao and TeacherDao. They both extend AbstractCrudDao a simple abstract class that has some common CRUD operations:

package net.dovale.dao;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import javax.persistence.criteria.CriteriaQuery;
import java.util.List;

public abstract class AbstractCrudDao<T> {
    private final SessionFactory sessionFactory;
    private final Class<T> entityClass;
    private final String entityName;

    protected AbstractCrudDao(SessionFactory sessionFactory, Class<T> entityClass, String entityName) {
        this.sessionFactory = sessionFactory;
        this.entityClass = entityClass;
        this.entityName = entityName;
    }
    public T save(T entity) {
        sessionFactory.getCurrentSession().save(entity);
        return entity;
    }
    public void delete(T entity) {
        sessionFactory.getCurrentSession().delete(entity);
    }

    public T find(long id) {
        return sessionFactory.getCurrentSession().find(entityClass, id);
    }
    public List<T> list() {
        Session session = sessionFactory.getCurrentSession();
        CriteriaQuery<T> query = session.getCriteriaBuilder().createQuery(entityClass);
        query.select(query.from(entityClass));
        return session.createQuery(query).getResultList();
    }
}


The abstract puts all CRUD logic into the same class. In the next sample, a new and better solution will be presented with Spring.

Hibernate uses a Session abstraction to communicate with the database and convert objects to relations and vice-versa. The framework also introduces its own query language called HQL(Hibernate Query Language). In the code above, HQL is represented on the list method. The cool thing about HQL is you are querying objects and not tables and relationships. The Hibernate query engine will convert to SQL internally.

ACID (Atomic Consistent Isolated Durable) is a relational database key feature. It guarantees consistency between the data inserted through transactions. In other words: if you are running a transaction, all your operations are atomic and not influenced by other operations that may be running in the same database, at the same time. To fully accomplish this, Hibernate also has a Transaction abstraction. The code on net.dovale.Application shows how it works:

package net.dovale;

import net.dovale.dao.*;
import net.dovale.entities.*;
import org.hibernate.*;
import org.hibernate.boot.MetadataSources;
import org.hibernate.boot.registry.StandardServiceRegistry;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;

public class Application {

    public static void main(String[] args) {
        StandardServiceRegistry registry = new StandardServiceRegistryBuilder().configure().build();
        try (SessionFactory sessionFactory = new MetadataSources(registry).buildMetadata().buildSessionFactory()) {
            CourseDao courseDao = new CourseDao(sessionFactory);
            TeacherDao teacherDao = new TeacherDao(sessionFactory);
            try (Session session = sessionFactory.getCurrentSession()) {
                Transaction tx = session.beginTransaction();
                // create teachers
                Teacher pj = teacherDao.save(new Teacher("Profesor Jirafales","https://upload.wikimedia.org/wikipedia/commons/thumb/d/d1/Ruben2017.jpg/245px-Ruben2017.jpg","[email protected]"));
                Teacher px = teacherDao.save(new Teacher("Professor X","https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcS9uI1Cb-nQ2uJOph4_t96KRvLSMjczAKnHLJYi1nqWXagvqWc4","[email protected]_.com"));
                courseDao.save(new Course("Mathematics", 20, 10, pj));
                courseDao.save(new Course("Spanish", 20, 10, pj));
                courseDao.save(new Course("Dealing with unknown", 10, 100, px));
                courseDao.save(new Course("Handling your mental power", 50, 100, px));
                courseDao.save(new Course("Introduction to psychology", 90, 100, px));
                tx.commit();
            }
            try (Session session = sessionFactory.getCurrentSession()) {
                session.beginTransaction();
                System.out.println("Courses");
                courseDao.list().forEach(course -> System.out.println(course.getName()));
                System.out.println("Teachers");
                teacherDao.list().forEach(teacher -> System.out.println(teacher.getName()));
            }
        }
    }
}


This class creates some data for our example database, and all the data is inserted using the same transaction: if an error occurs, all data are erased before any user can query them. To correctly implement this on raw hibernate, we call session.beginTransaction() and session.commitTransaction(). It is also important to call sessionFactory.getCurrentSession() and not sessionFactory.openSession() to use the same session all over the operation.

Last but not least, we have the configuration file (src/main/resources/hibernate.cfg.xml):

<hibernate-configuration>
    <session-factory>
        <property name="connection.driver_class">org.h2.Driver</property>
        <property name="connection.url">jdbc:h2:./data/db</property>
        <property name="connection.username">sa</property>
        <property name="connection.password"/>
        <property name="dialect">org.hibernate.dialect.H2Dialect</property>
        <property name="hbm2ddl.auto">create</property>
        <property name="hibernate.connection.pool_size">1</property>
        <property name="hibernate.current_session_context_class">thread</property>
        <property name="hibernate.show_sql">true</property>
        <mapping class="net.dovale.entities.Course"/>
        <mapping class="net.dovale.entities.Teacher"/>
    </session-factory>
</hibernate-configuration>


Note that we need to declare all entities with mapping node. For debugging purposes, it is important to set hibernate.show_sql = true as it is possible to identify possible mapping problems just by reading the generated SQL.

Now, just run the command below to run your project:

./mvnw compile exec:java -Dexec.mainClass="net.dovale.Application"


Phew! That’s a lot of code. Now we are going to remove a lot of then by introducing Spring Data.

Reduce Hibernate Boilerplate with Spring Boot

As you probably know, Spring Boot has a lot of magic under the hood. I have to say, using it together with Spring Data is awesome.

You need to create a new project using Spring Initializr with JPA and H2 dependencies. After the project is created, copy all entities package to the new project, without any changes. Then, add the @EnableTransactionManagement annotation to net.dovale.okta.springhibernate.spring.Application class as follows:

@SpringBootApplication
@EnableTransactionManagement
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}


Then, remove the original DAO’s. With Spring, CourseDao and TeacherDao will be changed to interfaces and extend the CrudRepository interface. Spring automatically identifies you are creating a Repository (or DAO) class when you extend a Repository interface. CrudRepository automatically delivers CRUD methods like save, delete, update, and list for your entity without any effort and with transaction support.

package net.dovale.okta.springhibernate.spring.dao;

import net.dovale.okta.springhibernate.spring.entities.Course;
import org.springframework.data.repository.CrudRepository;

public interface CourseDao extends CrudRepository<Course, Long> {}


If you want to skip to a a pre built example you can grab the code from GitHub. Please, clone it and go to from_raw_project branch:

git clone https://github.com/oktadeveloper/okta-spring-boot-hibernate-spring-project
cd okta-spring-boot-hibernate-spring-project
git checkout from_raw_project


Now, to change how we fill in the database. Create a service class DataFillerService that is responsible for filling our H2 database with data:

package net.dovale.okta.springhibernate.spring.services;

import net.dovale.okta.springhibernate.spring.dao.*;
import net.dovale.okta.springhibernate.spring.entities.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.PostConstruct;

@Service
public class DataFillerService {
    private final CourseDao courseDao;
    private final TeacherDao teacherDao;

    @Autowired
    public DataFillerService(CourseDao courseDao, TeacherDao teacherDao) {
            this.courseDao = courseDao;
            this.teacherDao = teacherDao;
    }
    @PostConstruct
    @Transactional
    public void fillData() {
        Teacher pj = new Teacher("Profesor Jirafales",
                                 "https://upload.wikimedia.org/wikipedia/commons/thumb/d/d1/Ruben2017.jpg/245px-Ruben2017.jpg",
                                 "[email protected]");
        Teacher px = new Teacher("Professor X",
                                 "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcS9uI1Cb-nQ2uJOph4_t96KRvLSMjczAKnHLJYi1nqWXagvqWc4",
                                 "[email protected]_.com");
        teacherDao.save(pj);
        teacherDao.save(px);
        courseDao.save(new Course("Mathematics", 20, (short) 10, pj));
        courseDao.save(new Course("Spanish", 20, (short) 10, pj));
        courseDao.save(new Course("Dealing with unknown", 10, (short) 100, pj));
        courseDao.save(new Course("Handling your mental power", 50, (short) 100, pj));
        courseDao.save(new Course("Introduction to psychology", 90, (short) 100, pj));
    }
}


Do you see how clean your code is when compared to the raw project? This happens because you have only one concern: maintain your business code sanely. While on your raw project you had to handle Sessions and Ttransactions, here we just need to add @Transactional annotation to keep the entire method execution inside a database transaction. Besides, the @PostConstruct tells Spring this method must be invoked after the context is fully loaded.

Add the following lines in src\main\resources\application.properties file to show up all SQL executed and to create the database if it does not exists.

spring.jpa.show-sql=true
spring.jpa.generate-ddl=true


Also, keep in mind Spring Boot automatically discovered H2 dependencies and configured it as the database you are using without any manual configuration.

To execute this code (which simply adds the entities to an ephemeral database), just run on a console:

./mvnw spring-boot:run


Remove Even More Code with Project Lombok

Have you read about Project Lombok? It works in compile level to reduce Java famous verbosity and add features that are not available in your current JDK version (e.g. val and var). In our case, we will remove all boilerplate in entities classes.

Check branch lombok to see the final result. Now we just need to add the dependency:

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
</dependency>


And change our entities to:

Course

package net.dovale.okta.springhibernate.spring.entities;

import lombok.*;
import javax.persistence.*;

@Entity
@Data
@NoArgsConstructor
@RequiredArgsConstructor
public class Course {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    @NonNull private String name;
    @NonNull private int workload;
    @NonNull private int rate;
    @ManyToOne
    @JoinColumn(foreignKey = @ForeignKey(name = "fk_course_teacher"))
    @NonNull private Teacher teacher;
}


and Teacher

package net.dovale.okta.springhibernate.spring.entities;

import lombok.*;
import javax.persistence.*;

@Entity
@Data
@NoArgsConstructor
@RequiredArgsConstructor
public class Teacher {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    @NonNull private String name;
    @NonNull private String pictureURL;
    @NonNull private String email;
}


Every getter and setter will be automatically generated by Lombok thanks to @Data annotation. @NoArgsConstructor and @RequiredArgsConstructor tells the tool the entity needs two constructors: one empty and another with all arguments that has @NonNull annotation (or are final). There is another annotation @AllArgsConstructor that creates an constructor with all arguments, but we cannot use it as id attribute can be null (only persisted entities should have a non-null value).

Note: Lombok has a lot of compile-level stuff and works out-of-the-box with Maven and Spring. Some IDE’s needs a specific plugin to work without compilation problems. Check out Project Lombok’s IDE setup guide.
As in the previous step, you just need to run the command ./mvnw spring-boot:run to see everything working if you do not believe in me.

Expose CRUD Operations as a REST Service with Spring Data REST

Now we will explore a microservice area. You are going to change the project to externalize the data into a REST API. Also, we will explore a little bit more about Spring Repositories.

About the magic thing, do you believe you just need to add a single dependency to publish your DAO’s as a REST API? Test it! Add the following dependency:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-rest</artifactId>
</dependency>


Run ./mvnw spring-boot run and HTTPie the following address:

http http://localhost:8080/teachers

HTTP/1.1 200
Content-Type: application/hal+json;charset=UTF-8
Date: Fri, 01 Feb 2019 16:32:43 GMT
Transfer-Encoding: chunked

{
    "_embedded": {
        "teachers": [
            {
                "_links": {
                    "self": {
                        "href": "http://localhost:8080/teachers/1"
                    },
                    "teacher": {
                        "href": "http://localhost:8080/teachers/1"
                    }
                },
                "email": "[email protected]",
                "name": "Profesor Jirafales",
                "pictureURL": "https://upload.wikimedia.org/wikipedia/commons/thumb/d/d1/Ruben2017.jpg/245px-Ruben2017.jpg"
            },
            {
                "_links": {
                    "self": {
                        "href": "http://localhost:8080/teachers/2"
                    },
                    "teacher": {
                        "href": "http://localhost:8080/teachers/2"
                    }
                },
                "email": "[email protected]_.com",
                "name": "Professor X",
                "pictureURL": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcS9uI1Cb-nQ2uJOph4_t96KRvLSMjczAKnHLJYi1nqWXagvqWc4"
            }
        ]
    },
    "_links": {
        "profile": {
            "href": "http://localhost:8080/profile/teachers"
        },
        "self": {
            "href": "http://localhost:8080/teachers"
        }
    }
}


Test with the other entities like students, teachers and courses and you’ll see all data we manually insert into the database. It is also possible to PUT, POST or DELETE to create, update or delete a registry, respectively.

There is another cool thing about Spring Data Repositories: you can customize them by creating methods into a predefined format. Example for StudentDao:

public interface StudentDao extends JpaRepository<Student, Long> {
    List<Student> findByNameContaining(String name);
    List<Student> findByAgeBetween(short smallerAge, short biggerAge);
}


All updates are available on the rest_repository branch of our Git Repository.

Secure Your Spring Boot Application with OAuth 2.0

Security is an important part of any application, and adding OAuth 2.0/OIDC support to any Spring Boot web application only takes few minutes!

Our project is a OAuth 2.0 resource server and, as such, does not handle login steps directly. It only validates that the request has valid authorization and roles.

First, you need to add the following Maven dependencies:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
    <groupId>com.okta.spring</groupId>
    <artifactId>okta-spring-boot-starter</artifactId>
    <version>1.0.0</version>
</dependency>


And update your Application class:

package net.dovale.okta.springhibernate.spring;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@SpringBootApplication
@EnableTransactionManagement
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
    @Configuration
        static class OktaOauth2WebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
         @Override
     protected void configure(HttpSecurity http) throws Exception {
            http
                 .authorizeRequests().anyRequest().authenticated()
                 .and()
                 .oauth2ResourceServer().jwt();
        }
    }
}


Now, you need to configure your application.properties file:

okta.oauth2.issuer=https://${yourOktaDomain}/oauth2/default
okta.oauth2.clientId=${clientId}


This will enable your REST endpoints to only accept authorized users. To obtain the client ID, you must register for a free Okta account.

In the Okta dashboard, create an application of type Service. This indicates a resource server that does not have a login page or any way to obtain new tokens.

Click Next, type the name of your service, then click Done. You will be presented with a screen similar to the one below. Paste your Client ID and enter it on into the application.properties file (just change ${clientId} variable).

Now, start the application again. It’ll be locked and you will be unable to make any requests as all of them are now protected. You just need to acquire a token to connect. An easy way to achieve a token is to generate one using OpenID Connect .

Create a new Web application in Okta:

Set the Login redirect URIs field to https://oidcdebugger.com/debug and Grant Type Allowed to Hybrid. Click Done and copy the client ID for the next step.

On the OIDC Debugger website, fill the form in like the picture below (do not forget to fill in the client ID for your recently created Okta web application):

Submit the form to start the authentication process. You’ll receive an Okta login form if you are not logged in or you’ll see the screen below with your custom token.

The token will be valid for one hour so you can do a lot of testing with your API. It’s simple to use the token, just copy it and modify the curl command to use it as follows:

export TOKEN=${YOUR_TOKEN}
http http://localhost:8080 "Authorization: Bearer $TOKEN"


For this last step, you can check out the master branch and see all the changes I made.

Learn More

Complete Java Masterclass

Complete Step By Step Java For Testers

Java Web Service Complete Guide - SOAP + REST + Buide App

Selenium WebDriver with Java - Basics to Advanced& Interview

Java Persistence: Hibernate and JPA Fundamentals

Java Swing (GUI) Programming: From Beginner to Expert

Java Basics: Learn to Code the Right Way

Multitenant configuration: StaleObjectStateException on Transaction (hibernate + spring-data-jpa)

i'm trying to setup a configuration for manage a multi-tenant enviroment with spring-boot, spring-data-jpa, hibernate and mysql (same schema, every tenant table has a tenant_code column). For dependencies the parent maven project is&nbsp;<strong>spring-boot-starter-parent</strong>&nbsp;(2.1.2.RELEASE).

i'm trying to setup a configuration for manage a multi-tenant enviroment with spring-boot, spring-data-jpa, hibernate and mysql (same schema, every tenant table has a tenant_code column). For dependencies the parent maven project is spring-boot-starter-parent (2.1.2.RELEASE).

On save entity hibernate throws this exception: org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) :

The highlights steps are:

  1. Intercept "tenant code" with custom spring HandlerInterceptorAdapter (i read oauth2 token and extract custom attribute "tenant code");
  2. Save "tenant code" in a @RequestScope bean; 

  3. On Save
  4. Define an custom hibernate EmptyInterceptor to intercept onSave action and previously set the "tenant code" (taken on a @RequestScope bean) 

  5. On Read
  6. In abstract Entity (extended by all my entities) i define @Filter and set it via AOP

If in interceptor i override onSave method i get this exception:

org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) : [com.test.module.api.domain.entity.User#22]
    at org.hibernate.persister.entity.AbstractEntityPersister.check(AbstractEntityPersister.java:2522) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
    at org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:3355) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
    at org.hibernate.persister.entity.AbstractEntityPersister.updateOrInsert(AbstractEntityPersister.java:3229) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
    at org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:3630) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
    at org.hibernate.action.internal.EntityUpdateAction.execute(EntityUpdateAction.java:146) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
    at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:604) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
    at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:478) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
    at org.hibernate.event.internal.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:356) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
    at org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:39) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
    at org.hibernate.internal.SessionImpl.doFlush(SessionImpl.java:1454) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
    at org.hibernate.internal.SessionImpl.managedFlush(SessionImpl.java:511) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
    at org.hibernate.internal.SessionImpl.flushBeforeTransactionCompletion(SessionImpl.java:3283) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
    at org.hibernate.internal.SessionImpl.beforeTransactionCompletion(SessionImpl.java:2479) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
    at org.hibernate.engine.jdbc.internal.JdbcCoordinatorImpl.beforeTransactionCompletion(JdbcCoordinatorImpl.java:473) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
    at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.beforeCompletionCallback(JdbcResourceLocalTransactionCoordinatorImpl.java:178) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
    at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.access$300(JdbcResourceLocalTransactionCoordinatorImpl.java:39) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
    at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl$TransactionDriverControlImpl.commit(JdbcResourceLocalTransactionCoordinatorImpl.java:271) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
    at org.hibernate.engine.transaction.internal.TransactionImpl.commit(TransactionImpl.java:98) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
    at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:532) ~[spring-orm-5.1.4.RELEASE.jar:5.1.4.RELEASE]
    at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:746) ~[spring-tx-5.1.4.RELEASE.jar:5.1.4.RELEASE]
    at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:714) ~[spring-tx-5.1.4.RELEASE.jar:5.1.4.RELEASE]
    at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:533) ~[spring-tx-5.1.4.RELEASE.jar:5.1.4.RELEASE]
    at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:304) ~[spring-tx-5.1.4.RELEASE.jar:5.1.4.RELEASE]
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:98) ~[spring-tx-5.1.4.RELEASE.jar:5.1.4.RELEASE]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.1.4.RELEASE.jar:5.1.4.RELEASE]
    at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:688) ~[spring-aop-5.1.4.RELEASE.jar:5.1.4.RELEASE]
    at com.test.module.api.service.UserServiceImpl$$EnhancerBySpringCGLIB$$baf4f87e.updateUser(<generated>) ~[classes/:na]
    at com.test.module.api.web.UserController.updateUser(UserController.java:38) ~[classes/:na]


Here the code: 

Spring Interceptor from Request Header:

    @Component
    public class TokenInterceptor extends HandlerInterceptorAdapter {
        public final String TENANT_CODE = "tenant_code";
    @Autowired
    private CurrentUser currentUser;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
        String tenantCode = ... extract tenant code from request...;
        currentUser.setTenantCode(tenantCode);        
        return true;
    }
}


Class to setup inteceptor in hibernate:

    @Component
public class TenantHibernateInterceptorCustomizer implements HibernatePropertiesCustomizer {

    @Autowired
    private TenantJpaInterceptor jpaInterceptor;

    @Override
    public void customize(Map&lt;String, Object&gt; hibernateProperties) {
        hibernateProperties.put("hibernate.session_factory.interceptor", jpaInterceptor);
    }

}


Hibernate custom interceptor:

    @Component
public class TenantJpaInterceptor extends EmptyInterceptor {
//Comment to test if problem is about spring context
//@Autowired
//private CurrentUser currentUser;

    @Override
    public boolean onSave(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types) {
        if (entity instanceof TenantAuditable) {
            //((TenantAuditable&lt;?&gt;) entity).setTenandCode(currentUser.getTenantCode());
            //if i comment this line everythings works (without tenant code)
            ((TenantAuditable&lt;?&gt;) entity).setTenandCode("TEST");            
        }
        return false;
    }
}


Service layer:

    @Transactional
public UpsertResponse<UserDto> updateUser(UserDto userDto) {
UpsertResponse<UserDto> validationResult = CommonValidators.validateUserDto(userDto);
if(!validationResult.isValidated()) {
return validationResult;

    SpUser user = userRepo.findById(userDto.getUserId()).orElse(new SpUser());
    userMapper.mapUserDtoToEntity(userDto, user);
    try {
        SpUser savedUser = userRepo.save(user);
        validationResult.setEntity(userMapper.mapUserDtoFromEntity(savedUser));
        validationResult.setValidated(true);
    }catch(Exception e) {
        e.printStackTrace();
        validationResult.setValidated(false);
        validationResult.setGlobalMessage(e.getLocalizedMessage());

    }
    return validationResult;
}

I expected a insert/update sql query with filled tenant_code field.


Here complete log

2019-01-22 01:10:57.521  INFO 14304 --- [nio-8090-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring DispatcherServlet 'dispatcherServlet'
2019-01-22 01:10:57.523 INFO 14304 --- [nio-8090-exec-1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet'
2019-01-22 01:10:57.550 INFO 14304 --- [nio-8090-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 27 ms
Hibernate:
select
spuser0_.user_id as user_id1_0_0_,
spuser0_.created_by as created_2_0_0_,
spuser0_.created_date as created_3_0_0_,
spuser0_.last_modified_by as last_mod4_0_0_,
spuser0_.last_modified_date as last_mod5_0_0_,
spuser0_.tenant_code as tenant_c6_0_0_,
spuser0_.buyer_id as buyer_id7_0_0_,
spuser0_.email as email8_0_0_,
spuser0_.family_name as family_n9_0_0_,
spuser0_.given_name as given_n10_0_0_,
spuser0_.middle_name as middle_11_0_0_,
spuser0_.user_name as user_na12_0_0_
from
sp_user spuser0_
where
spuser0_.user_id=?
2019-01-22 01:11:10.689 TRACE 14304 --- [nio-8090-exec-2] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [BIGINT] - [2]
Hibernate:
select
spuser0_.user_id as user_id1_0_0_,
spuser0_.created_by as created_2_0_0_,
spuser0_.created_date as created_3_0_0_,
spuser0_.last_modified_by as last_mod4_0_0_,
spuser0_.last_modified_date as last_mod5_0_0_,
spuser0_.tenant_code as tenant_c6_0_0_,
spuser0_.buyer_id as buyer_id7_0_0_,
spuser0_.email as email8_0_0_,
spuser0_.family_name as family_n9_0_0_,
spuser0_.given_name as given_n10_0_0_,
spuser0_.middle_name as middle_11_0_0_,
spuser0_.user_name as user_na12_0_0_
from
sp_user spuser0_
where
spuser0_.user_id=?
2019-01-22 01:11:14.414 TRACE 14304 --- [nio-8090-exec-2] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [BIGINT] - [2]
Hibernate:
insert
into
sp_user
(created_by, created_date, last_modified_by, last_modified_date, tenant_code, buyer_id, email, family_name, given_name, middle_name, user_name)
values
(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
2019-01-22 01:11:23.137 TRACE 14304 --- [nio-8090-exec-2] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [VARCHAR] - [3bf38f12-5d17-47e4-9504-ad0705289d10]
2019-01-22 01:11:23.138 TRACE 14304 --- [nio-8090-exec-2] o.h.type.descriptor.sql.BasicBinder : binding parameter [2] as [TIMESTAMP] - [Tue Jan 22 01:11:14 CET 2019]
2019-01-22 01:11:23.140 TRACE 14304 --- [nio-8090-exec-2] o.h.type.descriptor.sql.BasicBinder : binding parameter [3] as [VARCHAR] - [3bf38f12-5d17-47e4-9504-ad0705289d10]
2019-01-22 01:11:23.140 TRACE 14304 --- [nio-8090-exec-2] o.h.type.descriptor.sql.BasicBinder : binding parameter [4] as [TIMESTAMP] - [Tue Jan 22 01:11:14 CET 2019]
2019-01-22 01:11:23.140 TRACE 14304 --- [nio-8090-exec-2] o.h.type.descriptor.sql.BasicBinder : binding parameter [5] as [VARCHAR] - [null]
2019-01-22 01:11:23.140 TRACE 14304 --- [nio-8090-exec-2] o.h.type.descriptor.sql.BasicBinder : binding parameter [6] as [VARCHAR] - [1]
2019-01-22 01:11:23.140 TRACE 14304 --- [nio-8090-exec-2] o.h.type.descriptor.sql.BasicBinder : binding parameter [7] as [VARCHAR] - [[email protected]]
2019-01-22 01:11:23.140 TRACE 14304 --- [nio-8090-exec-2] o.h.type.descriptor.sql.BasicBinder : binding parameter [8] as [VARCHAR] - [Muscas]
2019-01-22 01:11:23.140 TRACE 14304 --- [nio-8090-exec-2] o.h.type.descriptor.sql.BasicBinder : binding parameter [9] as [VARCHAR] - [Gabrielex]
2019-01-22 01:11:23.141 TRACE 14304 --- [nio-8090-exec-2] o.h.type.descriptor.sql.BasicBinder : binding parameter [10] as [VARCHAR] - [Giuseppe Pippo]
2019-01-22 01:11:23.141 TRACE 14304 --- [nio-8090-exec-2] o.h.type.descriptor.sql.BasicBinder : binding parameter [11] as [VARCHAR] - [gabriele.muscas]
Hibernate:
update
sp_user
set
created_by=?,
created_date=?,
last_modified_by=?,
last_modified_date=?,
tenant_code=?,
buyer_id=?,
email=?,
family_name=?,
given_name=?,
middle_name=?,
user_name=?
where
user_id=?
2019-01-22 01:11:24.912 TRACE 14304 --- [nio-8090-exec-2] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [VARCHAR] - [3bf38f12-5d17-47e4-9504-ad0705289d10]
2019-01-22 01:11:24.912 TRACE 14304 --- [nio-8090-exec-2] o.h.type.descriptor.sql.BasicBinder : binding parameter [2] as [TIMESTAMP] - [Tue Jan 22 01:11:14 CET 2019]
2019-01-22 01:11:24.913 TRACE 14304 --- [nio-8090-exec-2] o.h.type.descriptor.sql.BasicBinder : binding parameter [3] as [VARCHAR] - [3bf38f12-5d17-47e4-9504-ad0705289d10]
2019-01-22 01:11:24.913 TRACE 14304 --- [nio-8090-exec-2] o.h.type.descriptor.sql.BasicBinder : binding parameter [4] as [TIMESTAMP] - [Tue Jan 22 01:11:24 CET 2019]
2019-01-22 01:11:24.913 TRACE 14304 --- [nio-8090-exec-2] o.h.type.descriptor.sql.BasicBinder : binding parameter [5] as [VARCHAR] - [PIPPO]
2019-01-22 01:11:24.913 TRACE 14304 --- [nio-8090-exec-2] o.h.type.descriptor.sql.BasicBinder : binding parameter [6] as [VARCHAR] - [1]
2019-01-22 01:11:24.914 TRACE 14304 --- [nio-8090-exec-2] o.h.type.descriptor.sql.BasicBinder : binding parameter [7] as [VARCHAR] - [[email protected]]
2019-01-22 01:11:24.914 TRACE 14304 --- [nio-8090-exec-2] o.h.type.descriptor.sql.BasicBinder : binding parameter [8] as [VARCHAR] - [Muscas]
2019-01-22 01:11:24.914 TRACE 14304 --- [nio-8090-exec-2] o.h.type.descriptor.sql.BasicBinder : binding parameter [9] as [VARCHAR] - [Gabrielex]
2019-01-22 01:11:24.915 TRACE 14304 --- [nio-8090-exec-2] o.h.type.descriptor.sql.BasicBinder : binding parameter [10] as [VARCHAR] - [Giuseppe Pippo]
2019-01-22 01:11:24.915 TRACE 14304 --- [nio-8090-exec-2] o.h.type.descriptor.sql.BasicBinder : binding parameter [11] as [VARCHAR] - [gabriele.muscas]
2019-01-22 01:11:24.915 TRACE 14304 --- [nio-8090-exec-2] o.h.type.descriptor.sql.BasicBinder : binding parameter [12] as [BIGINT] - [23]
2019-01-22 01:11:24.918 ERROR 14304 --- [nio-8090-exec-2] o.h.i.ExceptionMapperStandardImpl : HHH000346: Error during managed flush [Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) : [com.supplhi.buyer.api.domain.entity.SpUser#23]]
Hibernate:
select
spuser0_.user_id as user_id1_0_0_,
spuser0_.created_by as created_2_0_0_,
spuser0_.created_date as created_3_0_0_,
spuser0_.last_modified_by as last_mod4_0_0_,
spuser0_.last_modified_date as last_mod5_0_0_,
spuser0_.tenant_code as tenant_c6_0_0_,
spuser0_.buyer_id as buyer_id7_0_0_,
spuser0_.email as email8_0_0_,
spuser0_.family_name as family_n9_0_0_,
spuser0_.given_name as given_n10_0_0_,
spuser0_.middle_name as middle_11_0_0_,
spuser0_.user_name as user_na12_0_0_
from
sp_user spuser0_
where
spuser0_.user_id=?
2019-01-22 01:11:24.928 TRACE 14304 --- [nio-8090-exec-2] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [BIGINT] - [23]
2019-01-22 01:11:24.940 ERROR 14304 --- [nio-8090-exec-2] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.orm.ObjectOptimisticLockingFailureException: Object of class [com.supplhi.buyer.api.domain.entity.SpUser] with identifier [23]: optimistic locking failed; nested exception is org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) : [com.supplhi.buyer.api.domain.entity.SpUser#23]] with root cause

org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) : [com.supplhi.buyer.api.domain.entity.SpUser#23]
at org.hibernate.persister.entity.AbstractEntityPersister.check(AbstractEntityPersister.java:2522) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:3355) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.persister.entity.AbstractEntityPersister.updateOrInsert(AbstractEntityPersister.java:3229) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:3630) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.action.internal.EntityUpdateAction.execute(EntityUpdateAction.java:146) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:604) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:478) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.event.internal.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:356) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:39) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.internal.SessionImpl.doFlush(SessionImpl.java:1454) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.internal.SessionImpl.managedFlush(SessionImpl.java:511) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.internal.SessionImpl.flushBeforeTransactionCompletion(SessionImpl.java:3283) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.internal.SessionImpl.beforeTransactionCompletion(SessionImpl.java:2479) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.engine.jdbc.internal.JdbcCoordinatorImpl.beforeTransactionCompletion(JdbcCoordinatorImpl.java:473) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.beforeCompletionCallback(JdbcResourceLocalTransactionCoordinatorImpl.java:178) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.access$300(JdbcResourceLocalTransactionCoordinatorImpl.java:39) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl$TransactionDriverControlImpl.commit(JdbcResourceLocalTransactionCoordinatorImpl.java:271) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.engine.transaction.internal.TransactionImpl.commit(TransactionImpl.java:98) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:532) ~[spring-orm-5.1.4.RELEASE.jar:5.1.4.RELEASE]
at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:746) ~[spring-tx-5.1.4.RELEASE.jar:5.1.4.RELEASE]
at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:714) ~[spring-tx-5.1.4.RELEASE.jar:5.1.4.RELEASE]
at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:533) ~[spring-tx-5.1.4.RELEASE.jar:5.1.4.RELEASE]
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:304) ~[spring-tx-5.1.4.RELEASE.jar:5.1.4.RELEASE]
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:98) ~[spring-tx-5.1.4.RELEASE.jar:5.1.4.RELEASE]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.1.4.RELEASE.jar:5.1.4.RELEASE]
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:688) ~[spring-aop-5.1.4.RELEASE.jar:5.1.4.RELEASE]
at com.supplhi.buyer.api.service.UserServiceImpl$$EnhancerBySpringCGLIB$$baf4f87e.updateUser(<generated>) ~[classes/:na]
at com.supplhi.buyer.api.web.UserController.updateUser(UserController.java:38) ~[classes/:na]

A little help, s'll vous plaît. Thanks

How to use Spring Boot with Hibernate

How to use Spring Boot with Hibernate

A quick, practical intro to integrating Spring Boot and Hibernate/JPA.

1. Overview

In this article, we’ll have a look at how to use Spring Boot with Hibernate.

We’ll build a simple Spring Boot application and see how easy it is to integrate it with Hibernate.

2. Bootstrapping the Application

We’ll use Spring Initializr to bootstrap our Spring Boot application. For this example, we’ll use only the needed configurations and dependencies to integrate Hibernate, adding Web, JPA, and H2 dependencies. We’ll explain these dependencies in the next section.

Let’s generate the project and open it up in our IDE. We can check the generated project structure and identify the configuration files we’ll need.

This is how the project structure will look like:

3. Maven Dependencies

If we open up pom.xml, we’ll see that we have spring-boot-starter-web and spring-boot-starter-test as maven dependencies. As their names suggest, these are starting dependencies in Spring Boot.

Let’s have a quick look at dependency that pulls in JPA:

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

This dependency includes JPA API, JPA Implementation, JDBC and other needed libraries. Since the default JPA implementation is Hibernate, this dependency is actually enough to bring it in as well.

Finally, we’ll use H2 as a very lightweight database for this example:

<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <scope>runtime</scope>
</dependency>

We can use the H2 console to check that the DB is up and running, also for a user-friendly GUI for our data entry. Let’s go ahead and enable it in application.properites:

spring.h2.console.enabled=true

This is all we needed to configure to include Hibernate and H2 for our example. We can check the configuration was successful on the logs when we start up the Spring Boot application:

HHH000412: Hibernate Core {#Version}

HHH000206: hibernate.properties not found

HCANN000001: Hibernate Commons Annotations {#Version}

HHH000400: Using dialect: org.hibernate.dialect.H2Dialect

We can now access the H2 console on localhost: http://localhost:8080/h2-console/.

4. Creating the Entity

To check that our H2 is working properly, we’ll first create a JPA entity in a new models folder:

@Entity
public class Book {
 
    @Id
    @GeneratedValue
    private Long id;
    private String name;
 
    // standard constructors
 
    // standard getters and setters
}

We have now a basic entity, which H2 can create a table from. Restarting the application and checking H2 console, a new table called Book will be created.

To add some initial data to our application, we need to create a new SQL file, with some insert statements and put it in our resources folder. *We can use import.sql (Hibernate support) or data.sql (Spring JDBC support) files to load data. *

Here are our example data:

insert into book values(1, 'The Tartar Steppe');
insert into book values(2, 'Poem Strip');
insert into book values(3, 'Restless Nights: Selected Stories of Dino Buzzati');

Again, we can restart the Spring Boot application and check the H2 console – the data is now in the Book table.

5. Creating the Repository and Service

We’ll continue creating the basic components in order to test our application. First, let’s add the JPA Repository in a new repositories folder:

@Repository
public interface BookRepository extends JpaRepository<Book, Long> {
}

We can use the JpaRepository interface from Spring framework, which provides a default implementation for the basic CRUD operations.

Next, let’s add the BookService in a new services folder:

@Service
public class BookService {
 
    @Autowired
    private BookRepository bookRepository;
 
    public List<Book> list() {
        return bookRepository.findAll();
    }
}

To test our application, we need to check that the data created can be fetched from the list() method of the service.

We’ll write the following SpringBootTest:

@RunWith(SpringRunner.class)
@SpringBootTest
public class BookServiceTest {
 
    @Autowired
    private BookService bookService;
 
    @Test
    public void whenApplicationStarts_thenHibernateCreatesInitialRecords() {
        List<Book> books = bookService.list();
 
        Assert.assertEquals(books.size(), 3);
    }
}

By running this test, we can check that Hibernate creates the Book data which are then fetched successfully by our service. That was it, Hibernate is running with Spring Boot.

6. Uppercase Table Name

Sometimes we may need to have the table names in our database written in uppercase letters. As we already know, by default Hibernate will generate the names of the tables in lowercase letters.

We could also try to explicitly set the table name, like this:

@Entity(name="BOOK")
public class Book {
    // members, standard getters and setters
}

However, that wouldn’t work. What works is setting this property in application.properties:

spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl

As a result, we can check in our database that the tables are created successfully with uppercase letters.

7. Conclusion

In this article, we looked at how easy it is to integrate Hibernate with Spring Boot. We used the H2 database, as a very lightweight in-memory solution.

We gave a full example of an application, which uses all these technologies and eventually, we also gave a small hint on how to set the table names in uppercase in our database.

As always, all of the code snippets mentioned in this article can be found on our GitHub repository.

Understand to Spring Boot: MySQL and JPA, Hibernate

Understand to Spring Boot: MySQL and JPA, Hibernate

Spring Boot uses Hibernate as the default JPA implementation. The property spring.jpa.hibernate.ddl-auto is used for database initialization. I've used the value “update” for this property.

Before we start, this post is mostly aimed at Java developers, specifically Java developers who use the Spring umbrella of frameworks for development. If you are not a Java developer, you can still read on, chances are you haven't found the love of your life - in terms of languages - yet.

Also, I apologise in advance for the markup mishaps that will pop up here and there in various code sections. You could check out the original blog post here Original Post if you have trouble understanding/copy pasting the code.

First off, what really is Spring?

If you are a java developer, you've probably stumbled upon the term 'Spring' at some point in your journey, and possibly fell face first at either its learning curve, or its complexity. One thing most developers fail to understand - I also struggled with this at first - is that Spring represents a collection of frameworks that are tailored to meet specific development needs. As an example, if you're a java web developer, Spring provides the Web Servlet Framework for web development where Spring MVC (Included in this framework) is built on top of the Servlet API. Therefore, you need not learn all the frameworks that Spring provides, but rather the frameworks that fit your specific use case. Yeah, that's a shortcut, and yes, you're welcome.

If you've never heard about Spring before, Spring is an Inversion of Control and Dependency injection framework. This are fairly big terms but this comprehensive post will help you understand the meanings of these two concepts: IoC and Dependency Injection

Now onto Spring Boot

If you have used Spring MVC before, you've definitely have had to wrestle with Spring MVC's pre-configurations like Setting up the Dispatcher Servlet etc. etc. before you were able to get the framework up and running. This is where Spring Boot comes in. Spring Boot is an auto-configuration tool for setting up your Spring-powered applications. You can now put away those boxing gloves cause you might not need to wrestle with Spring Boot.

To help you understand Spring Boot further, and shine a light on why you should be using it if you already aren't, we'll build a simple Netflix API that allows client devices to register themselves, suggest movies and query movies.

Let us begin

Step 1: Setting up Spring Boot on your application.

Spring offers a project initializer, Spring Initialzr that allows you to select your project specifications and download an already configured Spring Boot project as a zip file or a maven build file. You could skip to step 2 if you have done this.

If you're a more of a hands on type of person who enjoys understanding what's happening under the hood, you can continue with this step.

Folder Structure.

Create a new Java project with you favourite IDE and configure your folder structure to mimic the following design:

└── src
    └── main
        └── controllers
        └── models
        └── repositories
        └── resources
            └── templates
                └── error.html
            └── application.properties
        └── Application.java

contollers - This folder will contain the controllers we define for this project

repositories - This folder will contain the repositories we'll define for our models that will be used to fetch data from the database.

resources - this folder will contain our project resources. The templatesfolder contains our template files that will be rendered by Spring. You can include other folders like static which will be used to server static content like javascript and css files.

Maven dependencies

Spring Boot allows us to include in our pom.xml file all the Spring dependencies that we'll use in our project. Copy paste the following dependencies, together with the Spring Boot Maven Plugin to your pom.xml.

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
&lt;groupId&gt;org.springframework&lt;/groupId&gt;
&lt;artifactId&gt;gs-spring-boot&lt;/artifactId&gt;
&lt;version&gt;0.1.0&lt;/version&gt;

&lt;parent&gt;
    &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
    &lt;artifactId&gt;spring-boot-starter-parent&lt;/artifactId&gt;
    &lt;version&gt;2.1.6.RELEASE&lt;/version&gt;
&lt;/parent&gt;

&lt;dependencies&gt;
    &lt;!--Spring dependencies--&gt;
&lt;dependency&gt;
    &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
    &lt;artifactId&gt;spring-boot-starter-web&lt;/artifactId&gt;
&lt;/dependency&gt;
&lt;dependency&gt;
    &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
    &lt;artifactId&gt;spring-boot-starter-thymeleaf&lt;/artifactId&gt;
&lt;/dependency&gt;
&lt;dependency&gt;
    &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
    &lt;artifactId&gt;spring-boot-devtools&lt;/artifactId&gt;
    &lt;optional&gt;true&lt;/optional&gt;
&lt;/dependency&gt;

<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.16</version>
</dependency>
<!--Spring JPA -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
</dependencies>

&lt;properties&gt;
    &lt;java.version&gt;1.8&lt;/java.version&gt;
&lt;/properties&gt;


&lt;build&gt;
    &lt;plugins&gt;
        &lt;plugin&gt;
            &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
            &lt;artifactId&gt;spring-boot-maven-plugin&lt;/artifactId&gt;
        &lt;/plugin&gt;
    &lt;/plugins&gt;
&lt;/build&gt;

</project>

Our dependencies overview:

1. spring-boot-starter-web - When building web applications using java, we often need other external dependencies that we include in our pom.xml like tomcat and Spring MVC. What spring-boot-starter-web does is add all these dependencies through one single dependency.

2. spring-boot-starter-thymeleaf - If you've never used thymeleaf before, thymeleaf is a templating engine for processing and creating HTML, XML, JavaScript, CSS, and text whose template files retain the .html extension and therefore a better alternative to JSPs (Java Server Pages). What this basically means is that you can run thymeleaf template files like normal web pages without a backend server for template processing as in the case of JSPs.

3. spring-boot-devtools - These tools grease your gears of development therefore making the overall development process more bearable. To learn more about what these tools offer, you can check out this link: spring-boot-devtools

4. mysql-connector-java - These is the MySQL JDBC implementation that we'll use to make connections to our MySQL database.

5. spring-boot-starter-data-jpa - Most if not all web applications need some form of persistence, which in java cases, is often JPA (Java Persistence API). If spring-boot-data-jpa is in the classpath, Spring boot will automatically configure our data-source through reading our database configuration from the application.properties file that we will configure next.

Note that we've set our java version to 1.8 since JDK 11 does not offer a lot of things out of the box and therefore you may run into errors like: springboot: org.hibernate.MappingException: Could not get constructor for org.hibernate.persister.entity.SingleTableEntityPersister

Application.properties file

Spring boot automatically reads configuration settings from this file and configures our spring boot environment accordingly. We'll configure our database here and also at the same time disable Spring boot's whitelabel error page which we'll replace with our own custom error page. You can copy paste all this into your own application.properties file if you do not intended to make any changes.

## Spring DATASOURCE (DataSourceAutoConfiguration & DataSourceProperties)
spring.datasource.url = jdbc:mysql://localhost:3306/netflix?useSSL=false
spring.datasource.username = netflix
spring.datasource.password = netflix

Hibernate Properties The SQL dialect makes Hibernate generate better SQL for the chosen database

spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5InnoDBDialect

Hibernate ddl auto (create, create-drop, validate, update)

spring.jpa.hibernate.ddl-auto = update

#Disabling the whitelabel error page
server.error.whitelabel.enabled=false

In the above application.properties file, We've configured our database, username and password to netflix . You can configure this if you want to. Spring JPA automatically uses the Hibernate implementation of JPA. 

We've set spring.jpa.hibernate.ddl-auto to update which will ensure that any changes we make to our models will be reflected in our Database, which also includes creating a new model. Please note that this option is only suitable for development environments rather than production environments. For more information, you can check this link: Database Initialization.

We've also set server.error.whitelabel.enabled to false to disable Spring boot's whitelabel error pages which we'll replace with our own custom error page.

Configuring our Application.java file

This file will contain the main method which we'll use to ignite our Spring Application with. Copy paste the following to your Application.java file:

package main;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@SpringBootApplication
@EnableJpaRepositories(basePackages="main.repositories")
@EnableTransactionManagement
@EnableJpaAuditing
@EntityScan(basePackages={"main.entities","main.models"})
public class Application {

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

}

@SpringBootApplication is a combination of the following more specific spring annotations -

1. @Configuration : Any class annotated with @Configuration annotation is bootstrapped by Spring and is also considered as a source of other bean definitions.

2. @EnableAutoConfiguration : This annotation tells Spring to automatically configure your application based on the dependencies that you have added in the pom.xml file. For example, If spring-data-jpa or spring-jdbc is in the classpath, then it automatically tries to configure a DataSource by reading the database properties from application.properties file.

3. @ComponentScan : It tells Spring to scan and bootstrap other components defined in the current package (main) and all the sub-packages.

@EnableJpaAuditing is used to support the automatic filling of fields that we'll annotate with @CreatedDate.

@EnableJpaRepositories tells Spring where to find our defined Repositories, since we'll not be using the @Repository annotation.


Step 2. Coding our Controllers.

We'll create only 3 Contollers namely: CustomErrorController that we'll use to format and serve our custom error page, MoviesController that will perform movie related functions and UsersContoller that will perform user related functions.

CustomErrorController

In this controller, we'll register a route error that will be mapped to our renderErrorPage method. Therefore all requests made through the errorroute will be recieved by our method.

Note that here we'll use the @Controller annotation since we'd like to return a view rather than plain text and therefore our method returning a string will return the name of the view. To return plain text rather than views, use the @RestController annotation.

We will also format our error messages to make them more user friendly when we display them on our error page.

We've also implemented the ErrorController interface and overridden the getErrorPath() method which will automatically be invoked when Spring encounters an error.

@Controller
public class CustomErrorController implements ErrorController {

@RequestMapping(value = "error",produces = "application/json;charset=UTF-8")
public String renderErrorPage(HttpServletRequest request, Model model) {
     String errorMsg = "";
    Object status = request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);
    int httpErrorCode = 404;
    if(status != null){
        httpErrorCode = Integer.valueOf(status.toString());
    }
    switch (httpErrorCode) {
        case 400: {
            errorMsg = "Http Error Code: 400. Bad Request";
            break;
        }
        case 401: {
            errorMsg = "Http Error Code: 401. Unauthorized";
            break;
        }
        case 404: {
            errorMsg = "Http Error Code: 404. Resource not found";
            break;
        }
        case 500: {
            errorMsg = "Http Error Code: 500. Internal Server Error";
            break;
        }
    }
    model.addAttribute("error",errorMsg);
    return "error";
}

@Override
public String getErrorPath() {
    return "/error";
}

}

Movies Controller

As we have stated earlier, this Controller will store functionalities related to our movies.

Since we are creating an api, we'll map api requests to url patterns that start with /api. Therefore, we add a @RequestMapping annotation on top of the class, rather than the method so that every request url we map on our methods will be appended to /api.

@RestController
@RequestMapping(value = "/api",produces = "application/json;charset=UTF-8") //All our api request URLs will start with /api and will return Json
public class MoviesController {

private MoviesRepository moviesRepository;
private CategoriesRepository categoriesRepository;
private UserRepository userRepository;

@Autowired
public MoviesController(MoviesRepository moviesRepository, CategoriesRepository categoriesRepository, UserRepository userRepository){
    this.moviesRepository = moviesRepository;
    this.categoriesRepository = categoriesRepository;
    this.userRepository = userRepository;
}

//Suggest A movie
@GetMapping(value = "/suggestMovie")
public String suggestMovie(@RequestParam(name = "category_id") Long categoryId,@RequestParam(name = "name")String name
,@RequestParam(name = "suggested_by")Long suggestedBy){
    //Movies added through this API route are automatically marked as suggested.
    String movieType = Movies.MovieType.SUGGESTED.getMovieType();
    Movies movies = new Movies();

    //Provided category id should be in our categories table.
    if(categoriesRepository.findById(categoryId).isPresent()){

        if(userRepository.findById(suggestedBy).isPresent()){
            movies.setCategoryId(categoryId);
            movies.setName(name);
            movies.setType(movieType);
            movies.setSuggestedBy(suggestedBy);
            return moviesRepository.save(movies).toString();
        } else {
            return "{'error':'The specified user id does not exist.'}";
        }

    } else {
        return "{'error':'The specified category id does not exist.'}";
    }



}

//delete a suggested movie
@GetMapping(value = "/deleteMovie")
public String deleteMovie(@RequestParam(name = "movie_id") Long movieId,@RequestParam(name = "user_id")Long userId) {
    if(userRepository.findById(userId).isPresent()){
        Optional&lt;Movies&gt; movies = moviesRepository.findById(movieId);
        if(movies.isPresent()){
            List&lt;Movies&gt; movie = moviesRepository.findBySuggestedByEqualsAndIdEquals(userId,movieId);
            if(movie.size()&gt;0){
                moviesRepository.delete(movie.get(0));
                return movie.toString();
            } else {
                return generateErrorResponse("The user specified cannot delete this movie");
            }


        } else {
            return  generateErrorResponse("Specified movie id does not exist");
        }

    } else {
        return generateErrorResponse("Specified user id does not exist");
    }
}

//update a suggested movie. Supports only updating of the movie name or category.
@GetMapping(value = "/updateMovie/{movie_id}")
public String updateMovie(@PathVariable(name = "movie_id") Long movieId,@RequestParam(name = "user_id")Long userId,
                          @RequestParam(name = "movie_name",required = false)String movieName, @RequestParam(name = "movie_category",required = false) Long movieCategory) {
    List&lt;Movies&gt; movie = moviesRepository.findBySuggestedByEqualsAndIdEquals(userId,movieId);
    if(!(movie.size()&gt;0)){
        return generateErrorResponse("The user specified cannot update this movie");
    }

    if(moviesRepository.findById(movieId).isPresent()){
        Movies movies = moviesRepository.findById(movieId).get();
        if(movieName != null &amp;&amp; !movieName.isEmpty()){
            movies.setName(movieName);
        }
        if(movieCategory != null &amp;&amp; categoriesRepository.findById(movieCategory).isPresent()){
            movies.setCategoryId(movieCategory);
        }

        return moviesRepository.save(movies).toString();
    } else {
        return generateErrorResponse("The specified movie id does not exist");
    }
}

//query available movies
@GetMapping(value = "/queryMovies/{categoryId}")
public String queryMovies(@PathVariable Long categoryId,@RequestParam(name = "type") String type){
    JsonObjectBuilder jsonResponse = Json.createObjectBuilder();
    JsonObjectBuilder temp = Json.createObjectBuilder();
    int count = 0;
    for(Movies movie:moviesRepository.findAllByCategoryIdEqualsAndTypeEquals(categoryId,type)){
        temp.add("id",movie.getId());
        temp.add("name",movie.getName());
        temp.add("type",movie.getType());
        temp.add("category_id",movie.getCategoryId());
        temp.add("created_at",movie.getCreatedAt().toString());
        jsonResponse.add(count + "",temp);
        temp = Json.createObjectBuilder();
        count++;
    }

    return jsonResponse.build().toString();
}

private String generateErrorResponse(String message){
    return "{\"error\":\"" + message + "\"";
}

//add categories
@GetMapping(value = "/addCategories")
public String addCategories(@RequestParam(name = "name") String name){
    Categories categories = new Categories();
    categories.setName(name);

    return categoriesRepository.save(categories).toString();
}

}

In this Class, you may have noticed annotations that you might have not seen before. Let's go through them quickly:

1. @Autowired - As the annotation itself suggests, this annotation automatically injects an implementation of the movies, users and categories repository interface which we assign the the field variables we have declared. As we mentioned earlier, you need a repository to be able to access database contents, which explains these three repositories. I'll explain this further when we reach the repositories section.

2. @GetMapping - This annotation is the same as @RequestMapping except that it only maps get requests to the specified url.

3. @RequestParam - This annotation automatically injects the specified query parameter name to this variable.

4. @PathVariable` - This annotation automatically injects the path value - enclosed in curly braces - to this variable.

Users Controller

This controller will contain functionalities related to users. In this case, we'll define only a single method that will be responsible for creating a user.

@RestController

@RequestMapping(value = "/api",produces = "application/json;charset=UTF-8") //All our api request URLs will start with /api and return Json

public class UsersController {

private UserRepository userRepository;

@Autowired
public UsersController(UserRepository userRepository){
this.userRepository = userRepository;
}

@GetMapping(path = "/addUser")
public String addUser(@RequestParam(name = "id")Long id, @RequestParam(name="name") String name) {
Users users = new Users();
users.setId(id);
users.setName(name);

users = userRepository.save(users);
return users.toString();

}
}

Our user IDs in this case will not be auto-generated but instead, we'll provide users with an option to define their own IDs.

Step 3. Defining our Repositories

Repositories will be used by our models to query data from the Database. spring-jpa comes with a JpaRepository interface that defines all CRUD operations that we can perform on an Entity. We'll use the CrudRepositoryimplementation of JpaRespository as it offers many CRUD operations out of the box through methods like findAll(), save() etc. At the same time, CrudRepository automatically generated for us dynamic queries based on method names as we'll see in the following example.

We'll define three repositories for our three entities: CategoriesRepository , MoviesRepository and UsersRepository, which will all be interfaces extending CrudRepository.

CategoriesRepository

public interface CategoriesRepository extends

CrudRepository<Categories,Long> {

}

MoviesRepository

public interface MoviesRepository extends CrudRepository {

List<Movies> findAllByCategoryIdEqualsAndTypeEquals(Long categoryId,String type);

List<Movies> findBySuggestedByEqualsAndIdEquals(Long suggestedBy,Long movieId);

In this repository, notice the abstract methods we have defined. Extending CrudRepository will automatically compel Spring to create an implementation of these methods automatically at run-time just from the definition of the method name. To add Custom methods, we can add them in the following ways:

  1. We can start our query method names with find...Byread...Byquery...Bycount...By, and get...By. Before By we can add expression such as Distinct . After By we need to add property names of our entity.
  2. To get data on the basis of more than one property we can concatenate property names using And and Or while creating method names.
  3. If we want to use completely custom name for our method, we can use @Query annotation to write query.

UsersRepository

@Repository

public interface UserRepository extends CrudRepository {

Final Step: Defining our models.

The models (Entities) that we define will be used to store our table structures as will be defined in the database. We will therefore have three models for our three tables: Categories , Movies and Users.

Categories Model

@Entity

@Table(name = "categories")

public class Categories {

@Id
@GeneratedValue
private Long id;

@NotBlank
private String name;

public Long getId() {
return id;
}

public String getName() {
return name;
}

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

@Override
public String toString() {
JsonObjectBuilder builder = Json.createObjectBuilder();

//serialize to Json only if the data was persisted.
if(!Objects.isNull(id)){
    builder.add("id",id);
}
if(!Objects.isNull(name)){
    builder.add("name",name);
}

return builder.build().toString();

}

}

`

An entity is a plain old Java object (POJO) class that is mapped to the database and configured for usage through JPA using annotations and/or XML.

Note that we've included a @Table annotation to explicitly define the name of our table. The @Id annotation automatically declares the created field as a primary key for our table in our database. At the same time, the @GeneratedValue annotation will automatically generate a value and store it in the database during saving of a record, pretty much like an auto-increment field. The @NotBlank annotation will automatically validate values that will be inserted into the name variable we defined and ensure that this field is not blank.

We've also defined our own toString method (overriding the superclass's toString method) that will convert our model to a Json string that we return as a response in our controllers.

Movies Model

@Entity

@Table(name = "movies")

@EntityListeners(AuditingEntityListener.class)

@JsonIgnoreProperties(value = {"createdAt"},allowGetters = true)

public class Movies implements Serializable {

@Id
@GeneratedValue
private Long id;

private Long categoryId;

@NotBlank
private String type;

@NotBlank
private String name;

private Long suggestedBy;

@Column(nullable = false, updatable = false)
@Temporal(TemporalType.TIMESTAMP)
@CreatedDate
private Date createdAt; //Stores the date at which a user was created.

@PrePersist
public void prePersist(){
createdAt = new Date();
}

public Long getId() {
return id;
}

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

public Long getCategoryId() {
return categoryId;
}

public void setCategoryId(Long categoryId) {
this.categoryId = categoryId;
}

public String getType() {
return type;
}

public void setType(String type) {
this.type = type;
}

public String getName() {
return name;
}

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

public Date getCreatedAt() {
return createdAt;
}

@Override
public String toString() {
JsonObjectBuilder builder = Json.createObjectBuilder();

//serialize to Json only if the data was persisted.
if(!Objects.isNull(id)){
    builder.add("id",id);
}
if(!Objects.isNull(name)){
    builder.add("name",name);
}

if(!Objects.isNull(categoryId)){
    builder.add("category_id",categoryId);
}

if(!Objects.isNull(createdAt)) {
    builder.add("created_at",createdAt.toString());
}
return builder.build().toString();

}

public Long getSuggestedBy() {
return suggestedBy;
}

public void setSuggestedBy(Long suggestedBy) {
this.suggestedBy = suggestedBy;
}

public enum MovieType{
SUGGESTED("suggested"),ORIGINAL("original");

private String movieType;

 MovieType(String movieType){
    this.movieType = movieType;
}

public String getMovieType() {
    return movieType;
}

}

In this model, note the annotations below:

1. @EntityListeners(AuditingEntityListener.class) - This will attach an entity listener to our model class that will automatically fill the fields we've annotated with @CreatedAt.

2. `@PrePersist - This annotation will ensure that the automatically generated value for the createdAt field is stored in this field whenever we'll need access. For more information on Database Auditing you can check this link: Database Auditing

Users Model

@Entity
@Table(name = "users")
@EntityListeners(AuditingEntityListener.class)
@JsonIgnoreProperties(value = {"createdAt"},
allowGetters = true)
public class Users implements Serializable {
private static final long serialVersionUID = 2L;

@Column(updatable = false)
@Id
private Long id;

@NotBlank(message = "The field 'name' is mandatory.")
private String name;

@Column(nullable = false, updatable = false)
@Temporal(TemporalType.TIMESTAMP)
@CreatedDate
private Date createdAt; //Stores the date at which a user was created.

@PrePersist
public void prePersist(){
    createdAt = new Date();
}

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

public long getId() {
    return id;
}

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

public String getName() {
    return name;
}



@Override
public String toString() {
    JsonObjectBuilder builder = Json.createObjectBuilder();

    //serialize to Json only if the data was persisted.
    if(!Objects.isNull(id)){
        builder.add("id",id);
    }
    if(!Objects.isNull(name)){
        builder.add("name",name);
    }

    if(!Objects.isNull(createdAt)) {
        builder.add("created_at",createdAt.toString());
    }
    return builder.build().toString();
}


public Date getCreatedAt() {
    return createdAt;
}

}

The Custom Error Page Template

In the templates folder we defined, create a html page and name it error.html and copy paste the following code into it:

<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>Error</title>
</head>
<body>
    <div>Web Application. Error : th:text="${error}"</div>
</body>
</html>

thymeleaf will automatically parse this html page and render our error message by replacing the th:text attribute.

Finally

Run your Application.java's main method and test out your netflix api on your browser by navigation to localhost:8080/. You should be able to see your json responses on your browser. Alternatively, you can check out my git repository for the source code and a client you can test your code with: github repo

Conclusion

You've successfully made a netflix api using Spring boot, mysql and JPA. Congrats!

Thanks For Visiting, Keep Visiting.