Build a Basic App with Spring Boot and JPA using PostgreSQL

Build a Basic App with Spring Boot and JPA using PostgreSQL

Build a Basic App with Spring Boot and JPA using PostgreSQL. Install PostgreSQL for JPA Persistence. Create a PostgreSQL Database for Your JPA Entities. Build a Spring Boot Resource Server. Add a Domain Class with Spring Data and JPA. Implement a CRUD Repository with Spring Data JPA.

Build a Basic App with Spring Boot and JPA using PostgreSQL. Install PostgreSQL for JPA Persistence. Create a PostgreSQL Database for Your JPA Entities. Build a Spring Boot Resource Server. Add a Domain Class with Spring Data and JPA. Implement a CRUD Repository with Spring Data JPA.

Every non-trivial application needs a way to save and update data: a resource server that is accessible via HTTP. Generally, this data must be secured. Java is a great language with decades of history in professional, enterprise development, and is a great choice for any application’s server stack. Within the Java ecosystem, Spring makes building secure resource servers for your data simple. When coupled with Okta, you get professionally maintained OAuth and JWT technologies easily integrated into Spring Boot using Spring Security.

In this post, you’re going to build a resource server using Spring Boot and Spring Data JPA. On top of that, you’re going to implement a group-based authentication and authorization layer using OAuth 2.0. If this sounds complicated - don’t worry! It’s not.

Before we dig in, let’s cover some background:

A resource server is a programmatic access point for your server’s functions and data (basically the same as an API server and/or possibly REST server).

JPA is the Java Persistence API, a specification for managing relational databases using Java. It describes an abstraction layer between Java classes and a relational database.

Spring Data JPA is a wrapper around JPA providers such as Hibernate. As you’ll see, it makes persisting your Java classes as simple as adding some annotations and creating a simple repository interface. No need to actually write persistence or retrieval methods! Another great benefit is that you can change the underlying database implementation transparently without having to change any code. For example, in this tutorial, you’ll be using Postgres, but later if you decided you’d rather use MySQL, all you’d have to do is change out some dependencies.

Install PostgreSQL for JPA Persistence

You’ll need to have PostgreSQL installed for this tutorial. If you don’t already have it installed, go to their downloads page and install it.

The next thing you’ll need to do is create a Postgres user and database for the project. For this, you can use the Postgres CLI, psql.

You should be able to run the following command: psql -V and get a response like:

psql (PostgreSQL) 11.12


Create a PostgreSQL Database for Your JPA Entities

Before you can use your database, you need to do a few things. You need to:

  1. Create a user for the app
  2. Set a password for that user
  3. Create a database for the app
  4. Grant privileges on the database for the user

This tutorial uses jpatutorial for the username and springbootjpa for the database name. Feel free to change these if you like, but you’ll have to remember to use your custom values throughout the tutorial.

Type psql from the terminal to enter the Postgres shell. Then enter the following command.

Create a user

create user jpatutorial;


The shell should respond with: CREATE ROLE

Don’t forget the semicolon! I would never, ever do this. I am definitely not speaking from experience. But if you don’t type in the semicolon psql doesn’t process the command and you can lose 20-30 minutes in frustrated confusion wondering what is happening until you do enter a semicolon, at which point it tries to process all of your commands.

Give the user a password

alter user jpatutorial with encrypted password '<your really secure password>';


The shell should respond with: ALTER ROLE.

Create the database

create database springbootjpa;


The shell should respond with: CREATE DATABASE.

Grant privileges

grant all privileges on database springbootjpa to jpatutorial;


The shell should respond with GRANT.

Finally, type \q to quit the shell, if you want.

If you want to read more about psql you can take a look at Postgres’ docs.

Build a Spring Boot Resource Server

Clone the starter project from the GitHub repository and check out the start branch:

git clone -b start https://github.com/oktadeveloper/okta-spring-boot-jpa-example.git


The starter project is a clean slate Spring Boot project with just a little bit of Postgres-specific configuration already in place. If you look in the build.gradle file you’ll see a PostgreSQL JPA connector dependency. You’ll also notice the file src/main/resources/hibernate.properties whose sole purpose is to get rid of an annoying warning/error that doesn’t really matter to us. The src/main/resources/application.yml file also has some properties pre-filled for you.

Go ahead and open the application.yml file and fill in the password you created for your database user. You should also update the username, database name, and port (if they are different).

spring:  
  jpa:  
    hibernate:  
      ddl-auto: create  
    database-platform: org.hibernate.dialect.PostgreSQLDialect  
  datasource:  
    url: "jdbc:postgresql://localhost:5432/springbootjpa"  
    username: jpatutorial  
    password: < your password >


The ddl-auto property specifies hibernate’s behavior upon loading. The options are:

  • validate: validates the schema but makes no changes
  • update: updates the schema
  • create: creates the schema, destroying any previous data
  • create-drop: like create, but also drops the schema when the session closes (useful for testing)

You’re using create. Each time the program is run, a new database is created, starting with fresh tables and data.

database-platform is actually unnecessary. Spring Data/Hibernate can autodetect the platform. However, without this property, if you run the app without having started your Postgres server, what you’ll get is a rather unhelpful error about not having added this config property instead of being told to start your server. This happens because Hibernate cannot autodetect the database platform, so complains about that before complaining about there not actually being a running server.

Run the app with ./gradlew bootRun. You should see something like this:

2018-11-21 09:27:50.233  INFO 31888 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Located MBean 'dataSource': registering with JMX server as MBean [com.zaxxer.hikari:name=dataSource,type=HikariDataSource]
2018-11-21 09:27:50.302  INFO 31888 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2018-11-21 09:27:50.308  INFO 31888 --- [           main] c.o.s.SpringBootJpaApplication           : Started SpringBootJpaApplication in 21.361 seconds (JVM running for 21.848)
<=========----> 75% EXECUTING [4m 26s]
> :bootRun


It doesn’t do much just yet, though. There are no domain models, resource repositories, or controller classes.

Add a Domain Class with Spring Data and JPA

A domain or model is the programmatic representation of the data you will be storing. The magic of Spring Data and JPA is that Spring can take a Java class and turn it into a database table for you. It will even autogenerate the necessary load and save methods. The best part is that this is (more or less) database independent.

You’re using PostgreSQL in this tutorial, and you could pretty easily switch it to MySQL if you wanted, just by changing a dependency in the build.gradle file. And, of course, creating a MySQL database and updating the necessary properties in the application.yml file. This is super useful for testing, development, and long-term maintenance.

Keep reading to learn how to develop a simple server to store types of kayaks.

Create a Java file in the com.okta.springbootjpa package called Kayak.java. Your kayak model will have a name, an owner, a value, and a make/model.

package com.okta.springbootjpa;

import lombok.Data;

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

@Entity // This tells Hibernate to make a table out of this class
@Data // Lombok: adds getters and setters
public class Kayak {

    public Kayak(String name, String owner, Number value, String makeModel) {
        this.name = name;
        this.owner = owner;
        this.value = value;
        this.makeModel = makeModel;
    }

    @Id
    @GeneratedValue(strategy=GenerationType.AUTO)
    private Integer id;

    private final String name;

    private String owner;

    private Number value;

    private String makeModel;
}


This project uses Lombok to avoid having to code a bunch of ceremony getters and setters and whatnots. You can check out their docs, or more specifically for the @Data annotation you’re using.

The @Entity annotation is what tells Spring that this class is a model class and should be transformed into a database table.

Most properties can be mapped automatically. The id property, however, is decorated with a couple annotations because we need to tell JPA that this is the ID field and that it should be auto-generated.

Implement a CRUD Repository with Spring Data JPA

With the domain class defined, Spring knows enough to build the database table, but it doesn’t have any controller methods defined. There’s no output or input for the data. Spring makes adding a resource server trivial. In fact, it’s so trivial, you probably won’t believe it.

In the package com.okta.springbootjpa, create an interface called KayakRepository.java.

package com.okta.springbootjpa;

import org.springframework.data.repository.CrudRepository;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;

@RepositoryRestResource
public interface KayakRepository extends CrudRepository<Kayak, Integer> {
}


That’s it!

You can now create, read, update, and delete kayaks from the resource server. In just a sec you’re going to do exactly that, but before you do, make one more change.

Add the following init() method to the SpringBootJpaApplication class:

package com.okta.springbootjpa;

import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

import java.text.NumberFormat;
import java.text.ParseException;
import java.util.stream.Stream;

@SpringBootApplication
public class SpringBootJpaApplication {

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

  @Bean
  ApplicationRunner init(KayakRepository repository) {

    String[][] data = {
        {"sea", "Andrew", "300.12", "NDK"},
        {"creek", "Andrew", "100.75", "Piranha"},
        {"loaner", "Andrew", "75", "Necky"}
    };

    return args -> {
      Stream.of(data).forEach(array -> {
        try {
          Kayak kayak = new Kayak(
              array[0],
              array[1],
                  NumberFormat.getInstance().parse(array[2]),
              array[3]
          );
          repository.save(kayak);
        }
        catch (ParseException e) {
          e.printStackTrace();
        }
      });
      repository.findAll().forEach(System.out::println);
    };
  }

}


This method will be run when the application starts. It loads some sample data into the resource server just to give you something to look at in the next section.

Test Your Spring Boot Resource Server

HTTPie is a great command line utility that makes it easy to run requests against the resource server. If you don’t have HTTPie installed, install it using brew install httpie. Or head over to their website and make it happen. Or just follow along.

Make sure your Spring Boot app is running. If it isn’t, start it using ./gradlew bootRun.

Run a GET request against your resource server: http :8080/kayaks, which is shorthand for http GET http://localhost:8080/kayaks.

You’ll see this:

HTTP/1.1 200
Content-Type: application/hal+json;charset=UTF-8
Date: Wed, 21 Nov 2018 20:39:11 GMT
Transfer-Encoding: chunked

{
    "_embedded": {
        "kayaks": [
            {
                "_links": {
                    "kayak": {
                        "href": "http://localhost:8080/kayaks/1"
                    },
                    "self": {
                        "href": "http://localhost:8080/kayaks/1"
                    }
                },
                "makeModel": "NDK",
                "name": "sea",
                "owner": "Andrew",
                "value": 300.12
            },
            {
                "_links": {
                    "kayak": {
                        "href": "http://localhost:8080/kayaks/2"
                    },
                    "self": {
                        "href": "http://localhost:8080/kayaks/2"
                    }
                },
                "makeModel": "Piranha",
                "name": "creek",
                "owner": "Andrew",
                "value": 100.75
            },
            {
                "_links": {
                    "kayak": {
                        "href": "http://localhost:8080/kayaks/3"
                    },
                    "self": {
                        "href": "http://localhost:8080/kayaks/3"
                    }
                },
                "makeModel": "Necky",
                "name": "loaner",
                "owner": "Andrew",
                "value": 75
            }
        ]
    },
    "_links": {
        "profile": {
            "href": "http://localhost:8080/profile/kayaks"
        },
        "self": {
            "href": "http://localhost:8080/kayaks"
        }
    }
}


This output gives you a pretty solid idea of the format of data that the Spring Boot resource returns. You can also add a new kayak using a POST.

Command:

http POST :8080/kayaks name="sea2" owner="Andrew" value="500" makeModel="P&H"


Reply:

HTTP/1.1 201
Content-Type: application/json;charset=UTF-8
Date: Wed, 21 Nov 2018 20:42:14 GMT
Location: http://localhost:8080/kayaks/4
Transfer-Encoding: chunked

{
    "_links": {
        "kayak": {
            "href": "http://localhost:8080/kayaks/4"
        },
        "self": {
            "href": "http://localhost:8080/kayaks/4"
        }
    },
    "makeModel": "P&H",
    "name": "sea2",
    "owner": "Andrew",
    "value": 500
}


If you list the kayaks again (http :8080/kayaks) you’ll see the new kayak among the listed items.

HTTP/1.1 200
Content-Type: application/hal+json;charset=UTF-8
Date: Wed, 21 Nov 2018 20:44:22 GMT
Transfer-Encoding: chunked

{
    "_embedded": {
        "kayaks": [
            ...
            {
                "_links": {
                    "kayak": {
                        "href": "http://localhost:8080/kayaks/4"
                    },
                    "self": {
                        "href": "http://localhost:8080/kayaks/4"
                    }
                },
                "makeModel": "P&H",
                "name": "sea2",
                "owner": "Andrew",
                "value": 500
            }
        ]
    },
    ...
}


You can also delete the kayak. Run this command: http DELETE :8080/kayaks/4 This deletes the kayak with ID = 4, or the kayak we just created. GET the list of kayaks a third time and you’ll see that it’s gone.

With very minimal code, using Spring Boot you can create a fully functioning resource server. This data is being persisted to your Postgres database.

You can verify this by using the Postgres command shell. At the terminal, type psql to enter the shell, then type the following commands.

Connect to the database:

\connect springbootjpa

psql (9.6.2, server 9.6.6)
You are now connected to database "springbootjpa" as user "cantgetnosleep".


Show the table contents:

SELECT * FROM kayak;

 id | make_model |  name  | owner  |                                                                                   value
----+------------+--------+--------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------
  1 | NDK        | sea    | Andrew | \xaced0005737200106a6176612e6c616e67...8704072c1eb851eb852
  2 | Piranha    | creek  | Andrew | \xaced0005737200106a6176612e6c616e672e...078704059300000000000
  3 | Necky      | loaner | Andrew | \xaced00057372000e6a6176612e6c616e67...7870000000000000004b
  5 | P&H        | sea2   | Andrew | \xaced0005737200116a6176612e6...08b0200007870000001f4
(4 rows)


A couple things to note. First, notice that value is being stored as a binary object because it was defined as a Number type instead of a primitive (double, float, or int). Second, remember that this data is being erased and the entire table is being recreated on every boot of the app because of the ddl-auto: create line in the application.yml file.

Set Up Authentication

Okta is a software-as-service identity, authentication, and authorization provider. While I have definitely worked on projects where outsourcing everything to SaaS providers created more problems than it promised to solve, authentication and authorization is a place where this model makes total sense. Online security is hard. Vulnerabilities are found and servers must be updated quickly. Standards change and code needs modification. All of these changes have the potential to create new vulnerabilities. Letting Okta handle security means that you can worry about the things that make your application unique.

To show you how easy it is to set up, you’re going integrate Okta OAuth and add token-based authentication to the resource server. If you haven’t already, head over to developer.okta.com and sign up for a free account. Once you have an account, open the developer dashboard and create an OpenID Connect (OIDC) application by clicking on the Application top-menu item, and then on the Add Application button.

Select Single-Page App.

The default application settings are great, except that you need to add a Login Redirect URI: a. You’re going to use this in a moment to retrieve a test token.

Also, note your Client ID, as you’ll need that in a moment.

Configure Your Spring Boot Resource Server for Token Authentication

Okta has made adding token authentication to Spring Boot super easy. They have a project called Okta Spring Boot Starter (check out the GitHub project) that simplifies the whole process to a few simple steps.

Add a couple dependencies to your build.gradle file.

compile('org.springframework.security.oauth.boot:spring-security-oauth2-autoconfigure:2.1.0.RELEASE')  
compile('com.okta.spring:okta-spring-boot-starter:0.6.1')


Add the following to the bottom of the build.gradle file (this resolves a logback logging dependency conflict).

configurations.all {  
  exclude group: 'org.springframework.boot', module: 'spring-boot-starter-logging'  
  exclude group: 'org.springframework.boot', module: 'logback-classic'  
}


Next, you need to add some configuration to your application.yml file, replacing {yourClientId} with the Client ID from your Okta OIDC application and {yourOktaDomain} with your Okta URL. Something like https://dev-123456.oktapreview.com.

okta:  
  oauth2:  
    issuer: https://{yourOktaDomain}/oauth2/default  
    client-id: {yourClientId}  
    scopes: openid profile email


Finally, you need to add the @EnableResourceServer annotation to your SpringBootVueApplication class.

import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;

@EnableResourceServer  // <- add me
@SpringBootApplication  
public class SpringBootJpaApplication {  

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


Test the Protected Spring Boot Server

Stop your Spring Boot server and restart it using: ./gradlew bootRun

From the command line, run a simple GET request.

http :8080/kayaks


You’ll get a 401/unauthorized.

HTTP/1.1 401
Cache-Control: no-store
Content-Type: application/json;charset=UTF-8

{
    "error": "unauthorized",
    "error_description": "Full authentication is required to access this resource"
}


Generate an Access Token

To access the server now, you need a valid access token. You can use OpenID Connect Debugger to help you do this. In another window, open oidcdebugger.com.

Authorize URI: https://{yourOktaUrl}/oauth2/default/v1/authorize, with {yourOktaUrl} replaced with your actual Okta preview URL.

Redirect URI: do not change. This is the value you added to your OIDC application above.

Client ID: from the OIDC application you just created.

Scope: openid profile email.

State: any value you want to pass through the OAuth redirect process. I set it to {}.

Nonce: can be left alone. Nonce means “number used once” and is a simple security measure used to prevent the same request being used multiple times.

Response Type: token.

Response mode: form_post.

Click Send Request. If you are not logged into developer.okta.com, then you’ll be required to log in. If you are (as is likely) already logged in, then the token will be generated for your signed-in identity.

Use the Access Token

You use the token by including in an Authorization request header of type Bearer.

Authorization: Bearer eyJraWQiOiJldjFpay1DS3UzYjJXS3QzSVl1MlJZc3VJSzBBYUl3NkU4SDJfNVJr...


To make the request with HTTPie:

http :8080/kayaks 'Authorization: Bearer eyJraWQiOiJldjFpay1DS3UzYjJXS3QzSVl1...'


Add Group-based Authorization

Up until now, the authorization scheme has been pretty binary. Does the request carry a valid token or not. Now you’re going to add Group-based authentication. Note that despite being used interchangeably sometimes on websites of ill repute, roles and groups are not the same thing, and are different ways of implementing authorization.

A role is a collection of collection of permissions that a user can inherit. A group is a collection of users to which a set of standard permissions are assigned. However, in the scope of tokens and how you’re using Spring Security with JPA, the implementation is exactly the same; they’re both passed from the OAuth OIDC application as a string “authority” to Spring, so for the moment they’re essentially interchangeable. The difference would be in what is protected and how they are defined.

To use group-based authorization with Okta, you need to add a “groups” claim to your access token. Create an Admin group (Users > Groups > Add Group) and add your user to it. You can use the account you signed up with, or create a new user (Users > Add Person). Navigate to API > Authorization Servers, click the Authorization Servers tab and edit the default one. Click the Claims tab and Add Claim. Name it “groups”, and include it in the access token. Set the value type to “Groups” and set the filter to be a Regex of .*.

Create a new access token using OIDC Debugger. Take a look at your decoded token by going to jsonwebtoken.io and entering in your generated access token.

The payload will look a bit like this:

{
 "ver": 1,
 "jti": "AT.Hk8lHezJNw4wxey1czypDiNXJUxIlKmdT16MrnLGp9E",
 "iss": "https://dev-533919.oktapreview.com/oauth2/default",
 "aud": "api://default",
 "iat": 1542862245,
 "exp": 1542866683,
 "cid": "0oahpnkb44pcaOIBG0h7",
 "uid": "00ue9mlzk7eW24e8Y0h7",
 "scp": [
  "email",
  "profile",
  "openid"
 ],
 "sub": "[email protected]",
 "groups": [
  "Everyone",
  "Admin"
 ]
}


The groups claim carries the groups to which the user is assigned. The user you’re using to sign into the developer.okta.com website will also be a member of both the “Everyone” group and the “Admin” group.

To get Spring Boot and the resource server to play nicely with group-based authorization, you need to make a few changes to the code.

First, add a new Java class in the com.okta.springbootjpa package called SecurityConfiguration.

package com.okta.springbootjpa;

import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
}


This configuration class is required to enable the @PreAuthorize annotation that you’re going to use to protect the resource server based on group membership.

Next, add the @PreAuthorize annotation to the KayakRepository, like so:

...
import org.springframework.security.access.prepost.PreAuthorize;
...

@RepositoryRestResource  
@PreAuthorize("hasAuthority('Admin')")  
public interface KayakRepository extends CrudRepository<Kayak, Long> {  
}


Finally, in the SpringBootJpaApplication, delete the ApplicationRunner init(KayakRepository repository) method (or just comment out the @Bean annotation). If you skip this step, the build will fail with the following error:

 AuthenticationCredentialsNotFoundException: An Authentication object was not found in the SecurityContext


The @PreAuthorize annotation actually blocks the init() method from creating the bootstrapped data programmatically because no user is logged in. Thus with the method runs, it throws an error.

Notice that you’re using hasAuthority() in the @PreAuthorize annotation and not hasRole(). The difference is that hasRole() expects groups or roles to be in ALL CAPS and to have a ROLE_ prefix. This can be configured, of course, but hasAuthority() comes without this baggage and simply checks whatever claim you’ve defined as the okta.oauth2.roles-claim in your application.yml.

Test the Admin User in Your Spring Boot App

Restart your Spring Boot app (start with ./gradlew bootRun).

Try an unauthenticated GET request: http :8080/kayaks.

HTTP/1.1 401
Cache-Control: no-store
Content-Type: application/json;charset=UTF-8

{
    "error": "unauthorized",
    "error_description": "Full authentication is required to access this resource"
}


Try it using your token.

Command:

http :8080/kayaks 'Authorization: Bearer eyJraWQiOiJldjFpay1DS3UzYjJXS3QzSVl1MlJZc3VJSzBBYUl3NkU4SDJf...'


Reply:

HTTP/1.1 200
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Content-Type: application/hal+json;charset=UTF-8

{
    "_embedded": {
        "kayaks": []
    },
    "_links": {
        "profile": {
            "href": "http://localhost:8080/profile/kayaks"
        },
        "self": {
            "href": "http://localhost:8080/kayaks"
        }
    }
}


It worked! We don’t have any kayaks because we had to remove the init() method above, so the _embedded.kayaks array is empty.

TIP: going forward, if you don’t want to copy and paste the whole enormous token string, you can store it to a shell variable and reuse it like so:

TOKEN=eyJraWQiOiJldjFpay1DS3UzYjJXS3QzSVl1MlJZc3VJSzBBYUl3NkU4SDJf...
http :8080/kayaks 'Authorization: Bearer $TOKEN'


Create a Non-Admin User

To demonstrate group-based authorization, you need to create a new user on Okta that isn’t an admin. Go to the developer.okta.com dashboard.

From the top-menu, select Users and People.

Click the Add Person button.

Give the user a First Name, Last Name, and Username (which will also be the Primary Email). The values do not matter, and you won’t need to be able to check the email. You simply need to know the email address/username and password so you can log in to Okta in a minute.

Password: change the drop down to Set by admin.

Assign the user a password.

Click Save.

You’ve just created a user that is NOT a member of the Admin group but is a member of the default group Everyone.

Test Group-based Authorization in Your Spring Boot App

Log out of your Okta developer dashboard.

Return to the OIDC Debugger and generate a new token.

This time, log in as your new non-admin user. You’ll be asked to choose a security question, after which you’ll be redirected to the https://oidcdebugger.com/debug page where your token can be copied.

If you like, you can go to jsonwebtoken.io and decode your new token. In the payload, the sub claim will show the email/username of the user, and the groups claim will show only the Everyone group.

{
 ...
 "sub": "[email protected]",
 "groups": [
  "Everyone"
 ]
}


If you use the new token to make a request on the /kayaks endpoint, you’ll get a 403/Access Denied.

http :8080/kayaks 'Authorization: Bearer eyJraWQiOiJldjFpay1DS3UzYjJX...'

HTTP/1.1 403
...

{
    "error": "access_denied",
    "error_description": "Access is denied"
}


To demonstrate the real power of the @PreAuthorize annotation, create a method-level security constraint. Change the KayakRepository class to the following:

@RepositoryRestResource  
public interface KayakRepository extends CrudRepository<Kayak, Long> {  

    @PreAuthorize("hasAuthority('Admin')")  
    <S extends Kayak> S save(S entity);  

}


This restricts only the save() method to members of the Admin group. The rest of the repository will be restricted simply requiring authentication, but no specific group membership.

Restart your Spring Boot server. Run the same request again.

http :8080/kayaks 'Authorization: Bearer eyJraWQiOiJldjFpay1DS3UzYjJX...'

HTTP/1.1 200
...

{
    "_embedded": {
        "kayaks": []
    },
    "_links": {
        "profile": {
            "href": "http://localhost:8080/profile/kayaks"
        },
        "self": {
            "href": "http://localhost:8080/kayaks"
        }
    }
}


The kayaks repository is empty, so _.embedded.kayaks is an empty array.

Try and create a new kayak.

http POST :8080/kayaks name="sea2" owner="Andrew" value="500" makeModel="P&H" "Authorization: Bearer eyJraWQiOiJldjFpay1DS3UzYjJX..."


You’ll get another 403. “Saving” is going to equal an HTML POST here.

However, if you use a token generated from your original, admin account, it’ll work.

NOTE: It’s possible your token will be expired and you’ll have to log out of developer.okta.com again and re-generate the token on the OIDC Debugger.

POST a new kayak with the token generated from your admin account.

This time you’ll get a 201.

HTTP/1.1 201
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Content-Type: application/json;charset=UTF-8
...

{
    "_links": {
        "kayak": {
            "href": "http://localhost:8080/kayaks/1"
        },
        "self": {
            "href": "http://localhost:8080/kayaks/1"
        }
    },
    "makeModel": "P&H",
    "name": "sea2",
    "owner": "Andrew",
    "value": 500
}


Success!

Take a look at Spring Data’s CrudRepository interface to get an idea of the methods that can be overridden and assigned method-level security. The @PreAuthorize annotation can be used with a lot more than just groups, as well. The entire power of Spring’s expression language (SpEL) can be leveraged.

public interface CrudRepository<T, ID> extends Repository<T, ID> {
  <S extends T> S save(S entity);
  <S extends T> Iterable<S> saveAll(Iterable<S> entities);
  Optional<T> findById(ID id);
  boolean existsById(ID id);
  Iterable<T> findAll();
  Iterable<T> findAllById(Iterable<ID> ids);
  long count();
  void deleteById(ID id);
  void delete(T entity);
  void deleteAll(Iterable<? extends T> entities);
  void deleteAll();
}


And that’s it! Pretty cool right? In this tutorial, you set up a PostgreSQL database, created a Spring Boot resource server that used Spring Data and JPA to persist a data model, and then turned this data model into a REST API with shockingly little code. Further, you used Okta to add OIDC authentication and OAuth 2.0 authorization to your server application. And finally, you implemented a simple group-based authorization scheme.

If you’d like to check out this complete project, you can find the repo on GitHub at @oktadeveloper/okta-spring-boot-jpa-example.

Watch out for our next post in this series that will cover using a NoSQL database (MongoDB) with Spring WebFlux.

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

The Complete Python & PostgreSQL Developer Course

SQL & Database Design A-Z™: Learn MS SQL Server + PostgreSQL

The Complete SQL Bootcamp

The Complete Oracle SQL Certification Course

SQL for Newbs: Data Analysis for Beginners

Java Spring - Limiting Query Result with Spring Data JPA

Java Spring - Limiting Query Result with Spring Data JPA

Java Spring - Limiting Query Result with Spring Data JPA

Sometimes we may need to limit the result returned by query. Adding LIMIT clause is the solution if we use SQL query. Unfortunately, it’s not supported by Spring Data JPA’s @Query annotation. It will throw error if you add LIMIT to the query. If you are using Spring or Spring Boot, here are the simple solutions to limit the query result.

Using Pageable

Maybe you’re already familiar with Pageable. It’s usually used for pagination. But, even if you only need to limit the query without using pagination, it can be useful too. In the repository, where you define the method, just add Pageable as the last parameter.

ItemRepository.java

  package com.woolha.example.querylimit.repository;

  import com.woolha.example.querylimit.model.Item;
  import org.springframework.data.domain.Page;
  import org.springframework.data.domain.Pageable;
  import org.springframework.data.repository.PagingAndSortingRepository;

  public interface ItemRepository extends PagingAndSortingRepository<Item, UUID> {
      Page<Item> findAllByTypeAndIsActive(String type, Boolean isActive, Pageable pageable);
  }

That means we have to pass an instance of Pageable as the third argument. Below is the example of limiting query result to 20 (the second argument). The first argument is the offset, while the third argment allows us to define the ORDER BY clause. To get the result as List<Item>, use .getContent().

ItemServiceImpl.java

  Pageable pageable = PageRequest.of(0, 20, Sort.by(Sort.Direction.DESC, "updatedAt"));

  return this.itemRepository.findAllByTypeAndIsActive(user, true, pageable).getContent();

Using EntityManager

Another way is using EntityManager. Use the createQuery method to define the query, set all parameters and define the limit with setMaxResults.

  package com.woolha.example.querylimit.repository;

  import com.woolha.example.querylimit.model.Item;
  import org.springframework.stereotype.Repository;

  import javax.persistence.EntityManager;
  import javax.persistence.PersistenceContext;
  import java.util.List;

  @Repository
  public class ItemRepositoryImpl {

      @PersistenceContext
      private EntityManager entityManager;

      public List<Item> findAllByTypeAndIsActive(String type,
                                                 Boolean isActive,
                                                 int limit) {
          return entityManager.createQuery("SELECT i FROM Item i"
                          + " WHERE (i.type IS :type)"
                          + " AND (i.isActive = :isActive)",
                  Item.class)
                  .setParameter("type", type)
                  .setParameter("isActive", isActive)
                  .setMaxResults(limit).getResultList();
      }
  }


Thanks for reading ❤

Spring Boot With Spring Data JPA

Spring Boot With Spring Data JPA

Welcome to the Spring Boot with Spring Data JPA tutorial! In this tutorial, we are going to see how Spring Data JPA provides complete abstraction over the DAO layer. We don’t need to write the implementation for the DAO layer anymore; Spring Data auto-generates the implementation DAO implementations.

Welcome to the Spring Boot with Spring Data JPA tutorial! In this tutorial, we are going to see how Spring Data JPA provides complete abstraction over the DAO layer. We don’t need to write the implementation for the DAO layer anymore; Spring Data auto-generates the implementation DAO implementations.

We already had an introduction to Spring Boot, and for this tutorial, we will use Spring Boot along with Spring Data. You will also see how Spring Boot auto-configuration helps to get data source configurations done, hassle free.

In our tutorial Spring Boot Rest Service, we created a DogService, which included a simple CRUD service based on the Mock Data Provider. We will use the same DogService and replace the Mock Data Provider with the actual MySQL database along with Spring Data and JPA.

Datasource Configuration

We now have dependencies configured. It is not time to tell which data source to connect to. Here is my application.yml, with Spring Boot data source entries.

spring:
  datasource:
    url: jdbc:mysql://localhost:33099/dogs
    password: <ENTER _ PASSWORD _ HERE >
    username: root
    driver-class-name: "com.mysql.jdbc.Driver"
  jpa:
    database-platform: org.hibernate.dialect.MySQL5InnoDBDialect
    hibernate:
      ddl-auto: update

Here, we have the specified JDBC URL, username, password, and driver class name (MySQL).

Apart from this, there are JPA specific configurations. First is the database-platform_,_ which is tells us the underlying Hibernate features to consider under the MySQL query dialect. This is so that all the database operations will be handled in MySQL specific syntax. The second JPA configuration is ddl-auto, which tells Hibernate to create the respective database and table structure, if not already present.

When this option is turned on, Hibernate will create the database structure based on the Entity Beans and the data source.

Entity Bean

The first code level thing we will do is write an Entity Bean. Here is what the Oracle Documentation says about Entity Beans.

Using JPA, you can designate any POJO class as a JPA entity – a Java object whose nontransient fields should be persisted to a relational database using the services of an entity manager obtained from a JPA persistence provider (either within a Java EE EJB container or outside of an EJB container in a Java SE application).
In simpler words, JPA Entity is any Java POJO, which can represent the underlying table structure. As our service is based on theDog table, we will create a Dog Entity object.

package com.amitph.spring.dogs.repo;

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

@Entity
public class Dog {
    @Id
    @GeneratedValue
    private long id;
    private String name;
    private int age;

    public long getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

The above POJO is annotated with @Entity, which is to denote this is an entity object for the table name Dog.

Then, there are three fields that represent the datable table columns. Field id is our Primary Key and, hence, marked as @Id.

The field id is also marked with @GeneratedValue, which denotes that this is an Auto-Increment column and Hibernate will take care of putting in the next value. Hibernate will first query the underlying table to know the max value of the column and increment it with next insert. This also means that we don’t need to specify any value for the Id column and can leave it blank.

Repository Interface

The Repository represents the DAO layer, which typically does all the database operations. Thanks to Spring Data, who provides the implementations for these methods. Let’s have a look at our DogsRepoisitory*,* which extends the CrudRepository:

package com.amitph.spring.dogs.repo;

import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface DogsRepository extends CrudRepository<Dog, Long> {}

There are no method declarations here in the DogsRepository. That is because Spring Data’s CrudInterface has already declared basic CRUD methods.

Here, we are done with the JPA and Spring data things — in other words, the DAO layer. Let’s now write a simple Service Layer and a Controller.

Controller and Service Layer

As we have our data access layer done, we will write our controller and service layer. Notice that the DogsRepository is annotated with @Repository, which also adds it to the Spring Context. We can now Autowire the repository in Service.

Dogs Service

This class has simple CRUD methods. It also converts the Entity bean to a DTO (data transfer object). DTO is also a simple Java POJO, which is used to transfer data between systems. Here, we are returning DTOs from our REST endpoints.

package com.amitph.spring.dogs.service;

import com.amitph.spring.dogs.model.DogDto;
import com.amitph.spring.dogs.repo.Dog;
import com.amitph.spring.dogs.repo.DogsRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

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

@Component
public class DogsService {
    @Autowired DogsRepository repository;

    public void add(DogDto dto) {
        repository.save(toEntity(dto));
    }

    public void delete(long id) {
        repository.deleteById(id);
    }

    public List<Dog> getDogs() {
        return (List<Dog>) repository.findAll();
    }

    public Dog getDogById(long id) {
        Optional<Dog> optionalDog = repository.findById(id);
        return optionalDog.orElseThrow(() -> new DogNotFoundException("Couldn't find a Dog with id: " + id));
    }

    private Dog toEntity(DogDto dto) {
        Dog entity = new Dog();
        entity.setName(dto.getName());
        entity.setAge(dto.getAge());
        return entity;
    }
}

Dogs Controller

The Dogs Controller is a standard REST controller with simple CRUD endpoints. The job of the controller is to handle the HTTP requests and invoke the Service class methods.

package com.amitph.spring.dogs.web;

import com.amitph.spring.dogs.model.DogDto;
import com.amitph.spring.dogs.repo.Dog;
import com.amitph.spring.dogs.service.DogsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
@RequestMapping("/dogs")
public class DogsController {
    @Autowired DogsService service;

    @GetMapping
    public List<Dog> getDogs() {
        return service.getDogs();
    }

    @PostMapping
    public void postDogs(@RequestBody DogDto dto) {
        service.add(dto);
    }

    @GetMapping("/{id}")
    public Dog getById(@PathVariable(required = true) long id) {
        return service.getDogById(id);
    }

    @DeleteMapping("/{id}")
    public void delete(@PathVariable(required = true) long id) {
        service.delete(id);
    }
}

Now, the Dogs Service is ready to run. Start the application and execute the HTTP endpoints — that’s it.

Conclusion

This is the end of our Spring Boot with Spring data and JPA tutorial. We saw how to use Spring Data’s abstraction for the Data Access Layer. We saw how to represent a database table in the form of Entity Bean and how to Use Spring Data’s autogenerated repository implementations. Additionally, we also saw how to use Spring Boot to do automatic datasource configurations.

In the Spring Boot Rest Service post, we have already seen creating a RESTful web-service with Spring Boot. In the current article, we did not care about Exception Handling. Visit Spring Rest Service Exception Handling to learn about handling exceptions. We also skipped the unit testing part here, which will be covered in upcoming articles.

For the full source code and examples used here, please visit https://github.com/amitrp/dog-service-jpa.

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