failed to convert from String to type Long when field is LOB

in the spring MVC application, I have Question entity class

in the spring MVC application, I have Question entity class

@Entity
public class Question {
@Lob
@Column(name="QUESTION_TITLE")
private String question;

...
}

I use Thymeleaf. for this field my view is bellow

<input type="text" class="form-control" id="question"
th:field="*{question}" th:value="${question}" placeholder="">

my controller save method is

@PostMapping("/save")
public String saveQuestion(Question question, BindingResult bindingResult){
questionService.save(question);

    return "redirect:/admin/questions/all/";
}

but when I submit, I have got error

Failed to bind request element: org.springframework.beans.TypeMismatchException: Failed to convert value of type 'java.lang.String' to required type 'com.sendit.security.model.Question'; nested exception is org.springframework.core.convert.ConversionFailedException: Failed to convert from type [java.lang.String] to type [java.lang.Long] for value 'what'; nested exception is java.lang.NumberFormatException: For input string: "what"

when I add @Convert(converter = QuestionConverter.class) attribute to the question field and implemented QuestionConverter method like bellow.

@Converter
public static class QuestionConverter implements AttributeConverter<String, Integer> {

@Override
public Integer convertToDatabaseColumn(String attribute) {
    return attribute.length();
}

@Override
public String convertToEntityAttribute(Integer dbData) {
    return "";
}

}

I got the error again.

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

How to use Spring, Hibernate, and EhCache Caching Features

How to use Spring, Hibernate, and EhCache Caching Features

In this Spring Boot project tutorial to help you understand how to use Spring, Hibernate, and EhCache caching features. we are going to demonstrate the Spring cache + EhCache feature on an example Spring Boot project. Caching will be defined as data queried from a relational database (example configurations prepared for H2 and PostgreSQL database engines).

In this post, we are going to demonstrate the Spring cache + EhCache feature on an example Spring Boot project. Caching will be defined as data queried from a relational database (example configurations prepared for H2 and PostgreSQL database engines).

Application

Let's consider the database layer and application layer.

Database Layer

The below diagram shows relationships between data tables. Our main object type is Company, which we will want to cache. Company is related to many other tables; some of the relationships are OneToMany, so querying the whole structure might be a time-consuming operation.

Application Layer

The test application is developed in Spring Boot + Hibernate + Flyway with an exposed REST API. To demonstrate data company operations, the following endpoints were created:

@RestController
@RequestMapping("/company")
public class CompanyController {

    @Autowired
    private CompanyService companyService;

    @RequestMapping(method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
    @ResponseStatus(value = HttpStatus.OK)
    public @ResponseBody
    List<Company> getAll() {
        return companyService.getAll();
    }

    @RequestMapping(value = "/{id}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
    @ResponseStatus(value = HttpStatus.OK)
    public @ResponseBody
    Company get(@PathVariable Long id) {
        return companyService.get(id);
    }

    @RequestMapping(value = "/filter", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
    @ResponseStatus(value = HttpStatus.OK)
    public @ResponseBody
    Company get(@RequestParam String name) {
        return companyService.get(name);
    }

    @RequestMapping(method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
    @ResponseStatus(value = HttpStatus.OK)
    public ResponseEntity<?> create(@RequestBody Company company) {
        companyService.create(company);
        HttpHeaders headers = new HttpHeaders();
        ControllerLinkBuilder linkBuilder = linkTo(methodOn(CompanyController.class).get(company.getId()));
        headers.setLocation(linkBuilder.toUri());
        return new ResponseEntity<>(headers, HttpStatus.CREATED);
    }

    @RequestMapping(method = RequestMethod.PUT, produces = MediaType.APPLICATION_JSON_VALUE)
    @ResponseStatus(value = HttpStatus.OK)
    public void update(@RequestBody Company company) {
        companyService.update(company);
    }

    @RequestMapping(value = "/{id}", method = RequestMethod.DELETE, produces = MediaType.APPLICATION_JSON_VALUE)
    @ResponseStatus(value = HttpStatus.OK)
    public void delete(@PathVariable Long id) {
        companyService.delete(id);
    }
}
Cache Configuration

Let's see how to enable caching and work with cache regions.

Enable Caching

To enable the annotation-driven cache management capability in your Spring application, we need to add @EnableCaching annotation to configuration class. This annotation registers CacheInterceptor or AnnotationCacheAspect, which will detect cache annotations like @Cacheable, @CachePut, and @CacheEvict.

Spring comes with the cache manager interface org.springframework.cache.CacheManager, so we need to provide concrete implementation for cache storage. There’re multiple implementations, such as:

  • Generic
  • JCache
  • EhCache 2.x
  • Hazelcast
  • Infinispan
  • Couchbase
  • Redis
  • Caffeine

In this post, we are going to use the EhCache provider. The below example shows cache enabling with EhCache-related beans in a separate configuration class. Overriding these two beans is not needed if you want to stay with the default definition, but we wanted to make cache transactions aware to synchronize put/evict operations with ongoing Spring-managed transactions.

@Configuration
@EnableCaching(mode = AdviceMode.ASPECTJ)
public class CacheConfiguration {

    @Bean
    public EhCacheManagerFactoryBean ehCacheManagerFactory() {
        EhCacheManagerFactoryBean cacheManagerFactoryBean = new EhCacheManagerFactoryBean();
        cacheManagerFactoryBean.setConfigLocation(new ClassPathResource("ehcache.xml"));
        cacheManagerFactoryBean.setShared(true);

        return cacheManagerFactoryBean;
    }

    @Bean
    public EhCacheCacheManager ehCacheCacheManager() {
        EhCacheCacheManager cacheManager = new EhCacheCacheManager();
        cacheManager.setCacheManager(ehCacheManagerFactory().getObject());
        cacheManager.setTransactionAware(true);

        return cacheManager;
    }
}

Cache Regions

Cached data could be stored in separate regions and we can define individual configurations for cache items with an XML file. Let’s define two regions:

  1. Storing companies by ID.
  2. Storing companies by name.

For both regions, the maximum elements kept in memory is 10,000 and the maximum time for the company before it's invalidated is 60 minutes (for more detailed configuration, go to EhCache Official Reference).

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE ehcache>
<ehcache>
    <diskStore path="java.io.tmpdir"/>

    <cache name="company.byId"
           maxElementsInMemory="10000" eternal="false" timeToIdleSeconds="600"
           timeToLiveSeconds="3600" overflowToDisk="true"/>

    <cache name="company.byName"
           maxElementsInMemory="10000" eternal="false" timeToIdleSeconds="600"
           timeToLiveSeconds="3600" overflowToDisk="true"/>
</ehcache>
Cache Operations

Let's look at populating with @Cacheable, invaldating with @CacheEvict, and updating with @CachePut.

Populate: @Cacheable

The @Cacheable annotation indicates that the result of invoking a method (or all methods in a class) can be cached. Each time an advised method is invoked, the caching behavior will be applied, checking whether the method was already invoked for the given arguments.

In the example below, we want to cache Company objects in the company.byId cache region. The key in our region is the Company ID field. To handle something other than cache example test data (name starting with test), we can add a condition based on the result object (object returned as a result of a method).

@Cacheable(value = "company.byId", key = "#id", unless = "#result != null and #result.name.toUpperCase().startsWith('TEST')")
public Company find(Long id) {
    CriteriaBuilder builder = entityManager.getCriteriaBuilder();
    CriteriaQuery<Company> query = builder.createQuery(Company.class);

    Root<Company> root = query.from(Company.class);
    root.fetch(Company_.cars, JoinType.LEFT);
    Fetch<Company, Department> departmentFetch = root.fetch(Company_.departments, JoinType.LEFT);
    Fetch<Department, Employee> employeeFetch = departmentFetch.fetch(Department_.employees, JoinType.LEFT);
    employeeFetch.fetch(Employee_.address, JoinType.LEFT);
    departmentFetch.fetch(Department_.offices, JoinType.LEFT);

    query.select(root).distinct(true);
    Predicate idPredicate = builder.equal(root.get(Company_.id), id);
    query.where(builder.and(idPredicate));

    return DataAccessUtils.singleResult(entityManager.createQuery(query).getResultList());
}

To better see what’s happening, we can turn on debug-level logging for Hibernate:

logging.level.org.hibernate.SQL=debug

...and invoke REST company endpoint:

curl http://localhost:9000/company/1

Server logs will show the below SQL query only once:

2018-01-19 10:00:34.143 DEBUG 20256 --- [nio-8080-exec-4] org.hibernate.SQL                        : 
    select
        distinct company0_.id as id1_2_0_,
        cars1_.id as id1_1_1_,
        department2_.id as id1_3_2_,
        employees3_.id as id1_4_3_,
        address4_.id as id1_0_4_,
        offices5_.id as id1_5_5_,
        company0_.name as name2_2_0_,
        cars1_.company_id as company_3_1_1_,
        cars1_.registration_number as registra2_1_1_,
        cars1_.company_id as company_3_1_0__,
        cars1_.id as id1_1_0__,
        department2_.company_id as company_3_3_2_,
        department2_.name as name2_3_2_,
        department2_.company_id as company_3_3_1__,
        department2_.id as id1_3_1__,
        employees3_.address_id as address_4_4_3_,
        employees3_.department_id as departme5_4_3_,
        employees3_.name as name2_4_3_,
        employees3_.surname as surname3_4_3_,
        employees3_.department_id as departme5_4_2__,
        employees3_.id as id1_4_2__,
        address4_.house_number as house_nu2_0_4_,
        address4_.street as street3_0_4_,
        address4_.zip_code as zip_code4_0_4_,
        offices5_.address_id as address_3_5_5_,
        offices5_.department_id as departme4_5_5_,
        offices5_.name as name2_5_5_,
        offices5_.department_id as departme4_5_3__,
        offices5_.id as id1_5_3__ 
    from
        company company0_ 
    left outer join
        car cars1_ 
            on company0_.id=cars1_.company_id 
    left outer join
        department department2_ 
            on company0_.id=department2_.company_id 
    left outer join
        employee employees3_ 
            on department2_.id=employees3_.department_id 
    left outer join
        address address4_ 
            on employees3_.address_id=address4_.id 
    left outer join
        office offices5_ 
            on department2_.id=offices5_.department_id 
    where
        company0_.id=1

The next time the repository method is invoked, data will be gathered from the cache.

The example below shows an implementation for caching data based on the name.

@Cacheable(value = "company.byName", key = "#name", unless = "#name != null and #name.toUpperCase().startsWith('TEST')")
public Company find(String name) {
    CriteriaBuilder builder = entityManager.getCriteriaBuilder();
    CriteriaQuery<Company> query = builder.createQuery(Company.class);

    Root<Company> root = query.from(Company.class);
    root.fetch(Company_.cars, JoinType.LEFT);
    Fetch<Company, Department> departmentFetch = root.fetch(Company_.departments, JoinType.LEFT);
    Fetch<Department, Employee> employeeFetch = departmentFetch.fetch(Department_.employees, JoinType.LEFT);
    employeeFetch.fetch(Employee_.address, JoinType.LEFT);
    departmentFetch.fetch(Department_.offices, JoinType.LEFT);

    query.select(root).distinct(true);
    Predicate idPredicate = builder.equal(root.get(Company_.name), name);
    query.where(builder.and(idPredicate));

    return DataAccessUtils.singleResult(entityManager.createQuery(query).getResultList());
}

Invalidate: @CacheEvict

The @CacheEvict annotation indicates that a method (or all methods on a class) triggers a cache evict operation, removing only specific or removing all items from the cache region.

Since the code below removes data from the database, the object needs to be removed from both caches:

@Caching(evict = {@CacheEvict(value = "company.byId", key = "#company.id"), @CacheEvict(value = "company.byName", key = "#company.name")})
public void delete(Company company) {
    entityManager.remove(company);
}

Update: @CachePut

With the @CachePut annotation, it’s possible to update the cache. In the below example, the cache for storing companies by ID is updated. It’s worth noting that since the company name is mutable, we cannot update the cache because we don’t know the old name value for that company. To proceed with that, we remove all entries in the cache for companies by name.

@Caching(evict = {@CacheEvict(value = "company.byName", allEntries = true)},
        put = {@CachePut(value = "company.byId", key = "#result.id", unless = "#result != null and #result.name.toUpperCase().startsWith('TEST')")})
public Company update(Company company) {
    return entityManager.merge(company);
}
Cache Statistics

To preview live cache, statistics it is possible to expose EhCache MBeans through JMX like below:

@Configuration
@Dev
public class CacheMonitoring {

    @Autowired
    private EhCacheCacheManager ehCacheCacheManager;

    @Bean
    public MBeanServer mBeanServer() {
        MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer();

        return mBeanServer;
    }

    @Bean
    public ManagementService managementService() {
        ManagementService managementService = new ManagementService(ehCacheCacheManager.getCacheManager(), mBeanServer(), true, true, true, true);
        managementService.init();

        return managementService;
    }
}

The following MBeans will be exposed:

  • CacheManager
  • Cache
  • CacheConfiguration
  • CacheStatistics

Summary

In this post, we covered basic cache operations like getting, inserting, removing, and updating. Defining these operations and more complex requirements (like conditional caching or cache synchronization) is straightforward with annotations.

The source code for above listings can be found in the GitHub project company-structure-hibernate-cache.

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