Duplicate Entry Exception: Spring Hibernate/JPA cascade save Many To One

It is a spring application (no spring boot). The database I am using is MySQL. The issue I am having is when saving the entity&nbsp;<code>Driver</code>&nbsp;which has a Many to one relationship on both&nbsp;<code>Carrier</code>&nbsp;and&nbsp;<code>Location</code>.

It is a spring application (no spring boot). The database I am using is MySQL. The issue I am having is when saving the entity Driver which has a Many to one relationship on both Carrier and Location.

What I want to do is, when I do the save on Driver. Driver along with Location and Carrier is persisted to the database. The issue I am having is when trying to save. I get duplicate key violation

Stack trace:

org.hibernate.engine.jdbc.spi.SqlExceptionHelper logExceptions
WARN: SQL Error: 1062, SQLState: 23000
Feb 18, 2019 1:25:42 PM org.hibernate.engine.jdbc.spi.SqlExceptionHelper logExceptions
ERROR: Duplicate entry '910327' for key 'UK_lheij6i9eldhfhyu9j1q5fjls'
Exception in thread "main" org.springframework.dao.DataIntegrityViolationException: could not execute statement; SQL [n/a]; constraint [UK_lheij6i9eldhfhyu9j1q5fjls]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement
    at org.springframework.orm.jpa.vendor.HibernateJpaDialect.convertHibernateAccessException(HibernateJpaDialect.java:296)
    at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:253)
    at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:527)
    at org.springframework.dao.support.ChainedPersistenceExceptionTranslator.translateExceptionIfPossible(ChainedPersistenceExceptionTranslator.java:61)
    at org.springframework.dao.support.DataAccessUtils.translateIfNecessary(DataAccessUtils.java:242)
    at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:153)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:135)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:93)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    at org.springframework.data.repository.core.support.SurroundingTransactionDetectorMethodInterceptor.invoke(SurroundingTransactionDetectorMethodInterceptor.java:61)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:212)
    at com.sun.proxy.$Proxy47.saveAll(Unknown Source)
    at greyhound.service.GreyhoundServiceImpl.process(GreyhoundServiceImpl.java:38)
    at greyhound.Main.main(Main.java:17)
Caused by: org.hibernate.exception.ConstraintViolationException: could not execute statement
    at org.hibernate.exception.internal.SQLExceptionTypeDelegate.convert(SQLExceptionTypeDelegate.java:59)
    at org.hibernate.exception.internal.StandardSQLExceptionConverter.convert(StandardSQLExceptionConverter.java:42)
    at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:113)
    at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:99)
    at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:178)
    at org.hibernate.dialect.identity.GetGeneratedKeysDelegate.executeAndExtract(GetGeneratedKeysDelegate.java:57)
    at org.hibernate.id.insert.AbstractReturningDelegate.performInsert(AbstractReturningDelegate.java:42)
    at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:3073)
    at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:3666)
    at org.hibernate.action.internal.EntityIdentityInsertAction.execute(EntityIdentityInsertAction.java:81)
    at org.hibernate.engine.spi.ActionQueue.execute(ActionQueue.java:645)
    at org.hibernate.engine.spi.ActionQueue.addResolvedEntityInsertAction(ActionQueue.java:282)
    at org.hibernate.engine.spi.ActionQueue.addInsertAction(ActionQueue.java:263)
    at org.hibernate.engine.spi.ActionQueue.addAction(ActionQueue.java:317)
    at org.hibernate.event.internal.AbstractSaveEventListener.addInsertAction(AbstractSaveEventListener.java:332)
    at org.hibernate.event.internal.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:289)
    at org.hibernate.event.internal.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:196)
    at org.hibernate.event.internal.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:127)
    at org.hibernate.event.internal.DefaultPersistEventListener.entityIsTransient(DefaultPersistEventListener.java:192)
    at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:135)
    at org.hibernate.internal.SessionImpl.firePersist(SessionImpl.java:828)
    at org.hibernate.internal.SessionImpl.persist(SessionImpl.java:795)
    at org.hibernate.engine.spi.CascadingActions$7.cascade(CascadingActions.java:298)
    at org.hibernate.engine.internal.Cascade.cascadeToOne(Cascade.java:490)
    at org.hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:415)
    at org.hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:216)
    at org.hibernate.engine.internal.Cascade.cascade(Cascade.java:149)
    at org.hibernate.event.internal.AbstractSaveEventListener.cascadeBeforeSave(AbstractSaveEventListener.java:428)
    at org.hibernate.event.internal.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:266)
    at org.hibernate.event.internal.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:196)
    at org.hibernate.event.internal.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:127)
    at org.hibernate.event.internal.DefaultPersistEventListener.entityIsTransient(DefaultPersistEventListener.java:192)
    at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:135)
    at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:62)
    at org.hibernate.internal.SessionImpl.firePersist(SessionImpl.java:804)
    at org.hibernate.internal.SessionImpl.persist(SessionImpl.java:789)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:308)
    at com.sun.proxy.$Proxy44.persist(Unknown Source)
    at org.springframework.data.jpa.repository.support.SimpleJpaRepository.save(SimpleJpaRepository.java:489)
    at org.springframework.data.jpa.repository.support.SimpleJpaRepository.saveAll(SimpleJpaRepository.java:521)
    at org.springframework.data.jpa.repository.support.SimpleJpaRepository.saveAll(SimpleJpaRepository.java:73)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.springframework.data.repository.core.support.RepositoryComposition$RepositoryFragments.invoke(RepositoryComposition.java:359)
    at org.springframework.data.repository.core.support.RepositoryComposition.invoke(RepositoryComposition.java:200)
    at org.springframework.data.repository.core.support.RepositoryFactorySupport$ImplementationMethodExecutionInterceptor.invoke(RepositoryFactorySupport.java:644)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.doInvoke(RepositoryFactorySupport.java:608)
    at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.lambda$invoke$3(RepositoryFactorySupport.java:595)
    at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.invoke(RepositoryFactorySupport.java:595)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    at org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.invoke(DefaultMethodInvokingMethodInterceptor.java:59)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:294)
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:98)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:139)
    ... 11 more
Caused by: java.sql.SQLIntegrityConstraintViolationException: Duplicate entry '910327' for key 'UK_lheij6i9eldhfhyu9j1q5fjls'
    at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:117)
    at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:97)
    at com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:122)
    at com.mysql.cj.jdbc.ClientPreparedStatement.executeInternal(ClientPreparedStatement.java:970)
    at com.mysql.cj.jdbc.ClientPreparedStatement.executeUpdateInternal(ClientPreparedStatement.java:1109)
    at com.mysql.cj.jdbc.ClientPreparedStatement.executeUpdateInternal(ClientPreparedStatement.java:1057)
    at com.mysql.cj.jdbc.ClientPreparedStatement.executeLargeUpdate(ClientPreparedStatement.java:1377)
    at com.mysql.cj.jdbc.ClientPreparedStatement.executeUpdate(ClientPreparedStatement.java:1042)
    at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:175)
    ... 69 more

Process finished with exit code 1

Entity/Model classes: (Have removed getters/setters)

@Entity
@Table(name = "Driver")
public class Driver {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Version
@Column(name = "version")
private int version;
@Column(name = "driver_id")
private Long driverId;
@Column(name = "first_name")
private String firstName;
@Column(name = "last_name")
private String lastName;
@Column(name = "middle_init")
private String middleInitial;

@ManyToOne(fetch = FetchType.EAGER)
@Cascade({CascadeType.ALL})
private Carrier carrier;

@ManyToOne(fetch = FetchType.EAGER)
@Cascade({CascadeType.ALL})
private Location location;

@Entity
@Table(name="Carrier")
public class Carrier {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Version
@Column(name = "version")
private int version;

@PrimaryKeyJoinColumn
@Column(name = "carrier_name")
private String carrierName;

@OneToMany
@JoinColumn(name = "carrier_id", referencedColumnName = "id")

@Entity
@Table(name="Locations")
public class Location {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private Long id;

@Version
private Long version;
@Column(name = "location_id")
private Long locationId;
@Column(name = "location_name")
private String locationName;

@OneToMany
@JoinColumn(name = "location_id", referencedColumnName = "location_id")
private List&lt;Driver&gt; drivers = new ArrayList&lt;Driver&gt;();

}

Code preparing the entities

private List<Driver> prepareEntityList(Result result) {
List<Driver> drivers = new ArrayList<Driver>();

    for(DriverAssignment driverAssignment : result.getDriverAssignments()) {
        Location location = new Location();
        location.setLocationName(driverAssignment.getHomeLocation3());
        location.setLocationId(driverAssignment.getHomeLocation());
        Carrier carrier = new Carrier();
        carrier.setCarrierName(driverAssignment.getCarrierId());
        Driver driver = new Driver();
        driver.setDriverId(driverAssignment.getDriverId());
        driver.setFirstName(driverAssignment.getFirstName());
        driver.setLastName(driverAssignment.getLastName());
        driver.setMiddleInitial(driverAssignment.getMiddleInitial());
        driver.setCarrier(carrier);
        driver.setLocation(location);
        drivers.add(driver);
    }

    return drivers;
}

Question: is it possible to achieve what I am trying to do? Expect hibernate to handle the relationships when I try to save and associate a location with a driver if it has already been saved instead of trying to save it again. If not, what is a suggested approach to save these entities?

Datasource configuration

@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setDataSource(dataSource());
em.setPackagesToScan(new String[] { "greyhound" });

    JpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
    em.setJpaVendorAdapter(vendorAdapter);
    em.setJpaProperties(additionalProperties());

    return em;
}

@Bean
public DataSource dataSource() {
    DriverManagerDataSource dataSource = new DriverManagerDataSource();
    dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
    dataSource.setUrl("jdbc:mysql://localhost:3306/greyhound1");
    dataSource.setUsername("root");
    dataSource.setPassword("");
    return dataSource;
}

@Bean
public PlatformTransactionManager transactionManager(EntityManagerFactory emf) {
    JpaTransactionManager transactionManager = new JpaTransactionManager();
    transactionManager.setEntityManagerFactory(emf);

    return transactionManager;
}

@Bean
public PersistenceExceptionTranslationPostProcessor exceptionTranslation() {
    return new PersistenceExceptionTranslationPostProcessor();
}

Properties additionalProperties() {
    Properties properties = new Properties();
    properties.setProperty("hibernate.hbm2ddl.auto", "create-drop");
    properties.setProperty("hibernate.dialect", "org.hibernate.dialect.MySQL5Dialect");

    return properties;
}

Update #2

Have a DriverRepository like this

@Repository
public interface DriverRepository extends JpaRepository<Driver, Long> {

}

To save:

repository.saveAll(drivers);

Github link https://github.com/mukulgoel1989/greyhound

I have added the github link in case someone is willing to give this a try.

Understand to Spring Boot: MySQL and JPA, Hibernate

Understand to Spring Boot: MySQL and JPA, Hibernate

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

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

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

First off, what really is Spring?

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

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

Now onto Spring Boot

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

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

Let us begin

Step 1: Setting up Spring Boot on your application.

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

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

Folder Structure.

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

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

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

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

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

Maven dependencies

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

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
&lt;groupId&gt;org.springframework&lt;/groupId&gt;
&lt;artifactId&gt;gs-spring-boot&lt;/artifactId&gt;
&lt;version&gt;0.1.0&lt;/version&gt;

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

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

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

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


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

</project>

Our dependencies overview:

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

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

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

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

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

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

Application.properties file

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

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

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

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

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

spring.jpa.hibernate.ddl-auto = update

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

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

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

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

Configuring our Application.java file

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

package main;

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

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

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

}

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

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

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

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

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

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


Step 2. Coding our Controllers.

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

CustomErrorController

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

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

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

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

@Controller
public class CustomErrorController implements ErrorController {

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

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

}

Movies Controller

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

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

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

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

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

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

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

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

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



}

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


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

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

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

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

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

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

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

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

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

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

}

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

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

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

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

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

Users Controller

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

@RestController

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

public class UsersController {

private UserRepository userRepository;

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

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

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

}
}

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

Step 3. Defining our Repositories

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

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

CategoriesRepository

public interface CategoriesRepository extends

CrudRepository<Categories,Long> {

}

MoviesRepository

public interface MoviesRepository extends CrudRepository {

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

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

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

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

UsersRepository

@Repository

public interface UserRepository extends CrudRepository {

Final Step: Defining our models.

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

Categories Model

@Entity

@Table(name = "categories")

public class Categories {

@Id
@GeneratedValue
private Long id;

@NotBlank
private String name;

public Long getId() {
return id;
}

public String getName() {
return name;
}

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

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

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

return builder.build().toString();

}

}

`

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

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

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

Movies Model

@Entity

@Table(name = "movies")

@EntityListeners(AuditingEntityListener.class)

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

public class Movies implements Serializable {

@Id
@GeneratedValue
private Long id;

private Long categoryId;

@NotBlank
private String type;

@NotBlank
private String name;

private Long suggestedBy;

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

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

public Long getId() {
return id;
}

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

public Long getCategoryId() {
return categoryId;
}

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

public String getType() {
return type;
}

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

public String getName() {
return name;
}

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

public Date getCreatedAt() {
return createdAt;
}

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

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

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

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

}

public Long getSuggestedBy() {
return suggestedBy;
}

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

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

private String movieType;

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

public String getMovieType() {
    return movieType;
}

}

In this model, note the annotations below:

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

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

Users Model

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

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

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

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

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

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

public long getId() {
    return id;
}

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

public String getName() {
    return name;
}



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

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

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


public Date getCreatedAt() {
    return createdAt;
}

}

The Custom Error Page Template

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

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

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

Finally

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

Conclusion

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

Thanks For Visiting, Keep Visiting.

Spring Boot + JPA + Hibernate + Oracle

Spring Boot + JPA + Hibernate + Oracle

In this tutorial, we will learn how to create a Spring Boot application that communicates with an Oracle data source through Hibernate.

In this tutorial, we will learn how to create a Spring Boot application that communicates with an Oracle data source through Hibernate.

Prerequisites
  • Eclipse IDE (neon release)
  • Maven 4
  • Java 1.8
Create a Maven Project

Open Eclipse, then create a new Maven project and name it SpringBootHibernate.

At the end of this tutorial, we’ll get the following project structure:

pom.xml

Configure Spring Boot inside your pom.xml by adding the following parent dependency:

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.5.10.RELEASE</version>
</parent>

Then add a spring-boot-starter dependency in order to run the application as a standalone JAR application:

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

Now in order to make use of Spring Data JPA and Hibernate, we need to just add spring-boot-starter-data-jpa as a dependency:

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

As soon as we include Spring Boot Starter JPA in our project, we get the following features from a wide variety of dependencies:

  • Eclipse IDE (neon release)
  • Maven 4
  • Java 1.8

This is the whole pom.xml for reference:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.programmer.gate</groupId>
  <artifactId>SpringBootHibernate</artifactId>
  <packaging>jar</packaging>
  <version>0.0.1-SNAPSHOT</version>
  <name>SpringBootHibernate</name>

  <properties>
       <maven.compiler.source>1.8</maven.compiler.source>
       <maven.compiler.target>1.8</maven.compiler.target>
    </properties>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.10.RELEASE</version>
    </parent>

  <dependencies>

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

  </dependencies>

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

</project>

Add the Oracle Driver to the Classpath

In this tutorial, we’re going to override the default in-memory database provided by Spring Boot and use our own Oracle database.

For this purpose, we add “oracle-ojdbc6-11.2.0.3.jar” under WEB-INF/lib and define it in our classpath.

application.properties

Configure the Oracle data source and Hibernate in application.properties:

# create and drop tables and sequences, loads import.sql
spring.jpa.hibernate.ddl-auto=create-drop

# Oracle settings
spring.datasource.url=jdbc:oracle:thin:@localhost:1522:orcl
spring.datasource.username=HIBERNATE_TEST
spring.datasource.password=HIBERNATE_TEST
spring.datasource.driver.class=oracle.jdbc.driver.OracleDriver

# logging
logging.pattern.console=%d{yyyy-MM-dd HH:mm:ss} %-5level %logger{36} - %msg%n
logging.level.org.hibernate.SQL=debug

Entities

Our entities represent a player and a team with a one-to-many relationship. Each team could have many players, whereas a player could only play with a single team at a time.

So we create our entities under the com.programmer.gate.model package:

Player.java:

package com.programmer.gate.model;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.SequenceGenerator;

@Entity
public class Player {

    @Id
    @GeneratedValue(strategy=GenerationType.SEQUENCE, generator = "player_Sequence")
    @SequenceGenerator(name = "player_Sequence", sequenceName = "PLAYER_SEQ")
    private Long id;

    @Column(name = "name")
    private String name;

    @Column(name = "num")
    private int num;

    @Column(name = "position")
    private String position;

    @ManyToOne(fetch = FetchType.LAZY)
        @JoinColumn(name = "team_id", nullable = false)
        private Team team;

    public Player() {
    }
        // getters/setters
}

Team.java:

package com.programmer.gate.model;

import java.util.List;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.SequenceGenerator;

@Entity
public class Team {

    @Id
    @GeneratedValue(strategy=GenerationType.SEQUENCE, generator = "team_Sequence")
    @SequenceGenerator(name = "team_Sequence", sequenceName = "TEAM_SEQ")
    private Long id;

    @Column(name = "name")
    private String name;

    @OneToMany(cascade = CascadeType.ALL,
            fetch = FetchType.EAGER,
            mappedBy = "team")
    private List<Player> players;

    public Team() {
    }

        // getters/setters
}

Since we set spring.jpa.hibernate.ddl-auto=create-drop inside application.properties, our application will automatically create Player and Team entities in our database, along with their sequences and constraints.

Our application would also look for import.sql in the classpath and execute it, if found.

In our example, we define import.sql under src/main/resources in order to fill our tables with static data:

insert into Team (id,name) values(1,'Barcelona');

insert into Player (id, team_id, name, num, position) values(1,1,'Lionel Messi', 10, 'Forward');
insert into Player (id, team_id, name, num, position) values(2,1,'Andreas Inniesta', 8, 'Midfielder');
insert into Player (id, team_id, name, num, position) values(3,1,'Pique', 3, 'Defender');

Repositories

We define our repositories’ interfaces under com.programmer.gate.repository. Each repository extends Spring CrudRepository, which provides a default implementation for the basic find, save,and delete methods — so we don’t care about defining implementation classes for them.

PlayerRepository:

package com.programmer.gate.repository;

import java.util.List;

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

import com.programmer.gate.model.Player;

@Repository
public interface PlayerRepository extends CrudRepository<Player, Long> {

    List<Player> findByTeamId(long teamId);
}

TeamRepository:

package com.programmer.gate.repository;

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

import com.programmer.gate.model.Team;

@Repository
public interface TeamRepository extends CrudRepository<Team, Long> {

    Team findByPlayers(long playerId);
}

Service

Now we define our service class, which holds the business logic of our application. Our service exposes two methods: getAllTeamPlayers() and addBarcelonaPlayer() ( just rename it to your favorite club if you don’t like Barcelona!). Our service layer communicates directly with the repository layer.

SoccerService.java:

package com.programmer.gate.service;

import java.util.List;

public interface SoccerService {
    public List<String> getAllTeamPlayers(long teamId);
    public void addBarcelonaPlayer(String name, String position, int number);
}

SoccerServiceImpl:

package com.programmer.gate.service;

import java.util.ArrayList;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.programmer.gate.model.Player;
import com.programmer.gate.model.Team;
import com.programmer.gate.repository.PlayerRepository;
import com.programmer.gate.repository.TeamRepository;

@Service
public class SoccerServiceImpl implements SoccerService {

    @Autowired
    private PlayerRepository playerRepository;
    @Autowired
    private TeamRepository teamRepository;

    public List<String> getAllTeamPlayers(long teamId) {
        List<String> result = new ArrayList<String>();
        List<Player> players = playerRepository.findByTeamId(teamId);
        for (Player player : players) {
            result.add(player.getName());
        }

        return result;
    }

    public void addBarcelonaPlayer(String name, String position, int number) {

        Team barcelona = teamRepository.findOne(1l);

        Player newPlayer = new Player();
        newPlayer.setName(name);
        newPlayer.setPosition(position);
        newPlayer.setNum(number);
        newPlayer.setTeam(barcelona);
        playerRepository.save(newPlayer);
    }
}

Application.java

The final step is to create the Spring Boot initializer. This is the entry point of our application. We define Application.javaunder com.programmer.gate:

package com.programmer.gate;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

import com.programmer.gate.service.SoccerService;

@SpringBootApplication
public class Application implements CommandLineRunner{

    @Autowired
    SoccerService soccerService;

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

    @Override
    public void run(String... arg0) throws Exception {

        soccerService.addBarcelonaPlayer("Xavi Hernandez", "Midfielder", 6);

        List<String> players = soccerService.getAllTeamPlayers(1);
        for(String player : players)
        {
            System.out.println("Introducing Barca player => " + player);
        }
    }
}

P.S.: It’s worth mentioning that the Spring Boot application automatically reads and creates entities, repositories, and services defined in the same package or in a sub-package relative to where you have your initializer class. So if we define Application.java under a different package, then we need to explicitly specify the package of the model, repository, and service.

Output:

When running the application as a standard Java app, we get the following output in the console:

2018-04-13 14:54:47 DEBUG org.hibernate.SQL - create sequence player_seq start with 1 increment by 1
2018-04-13 14:54:47 DEBUG org.hibernate.SQL - create sequence team_seq start with 1 increment by 1
2018-04-13 14:54:47 DEBUG org.hibernate.SQL - create table player (id number(19,0) not null, name varchar2(255 char), num number(10,0), position varchar2(255 char), team_id number(19,0) not null, primary key (id))
2018-04-13 14:54:47 DEBUG org.hibernate.SQL - create table team (id number(19,0) not null, name varchar2(255 char), primary key (id))
2018-04-13 14:54:47 DEBUG org.hibernate.SQL - alter table player add constraint FKdvd6ljes11r44igawmpm1mc5s foreign key (team_id) references team
2018-04-13 14:54:47 INFO  o.h.tool.hbm2ddl.SchemaExport - HHH000476: Executing import script '/import.sql'
2018-04-13 14:54:47 INFO  o.h.tool.hbm2ddl.SchemaExport - HHH000230: Schema export complete
2018-04-13 14:54:47 INFO  o.s.o.j.LocalContainerEntityManagerFactoryBean - Initialized JPA EntityManagerFactory for persistence unit 'default'
2018-04-13 14:54:48 INFO  o.s.j.e.a.AnnotationMBeanExporter - Registering beans for JMX exposure on startup
2018-04-13 14:54:48 DEBUG org.hibernate.SQL - select team0_.id as id1_1_0_, team0_.name as name2_1_0_, players1_.team_id as team_id5_0_1_, players1_.id as id1_0_1_, players1_.id as id1_0_2_, players1_.name as name2_0_2_, players1_.num as num3_0_2_, players1_.position as position4_0_2_, players1_.team_id as team_id5_0_2_ from team team0_, player players1_ where team0_.id=players1_.team_id(+) and team0_.id=?
2018-04-13 14:54:48 DEBUG org.hibernate.SQL - select player_seq.nextval from dual
2018-04-13 14:54:48 DEBUG org.hibernate.SQL - insert into player (name, num, position, team_id, id) values (?, ?, ?, ?, ?)
2018-04-13 14:54:48 INFO  o.h.h.i.QueryTranslatorFactoryInitiator - HHH000397: Using ASTQueryTranslatorFactory
2018-04-13 14:54:48 DEBUG org.hibernate.SQL - select player0_.id as id1_0_, player0_.name as name2_0_, player0_.num as num3_0_, player0_.position as position4_0_, player0_.team_id as team_id5_0_ from player player0_, team team1_ where player0_.team_id=team1_.id(+) and team1_.id=?

Introducing Barca player => Lionel Messi
Introducing Barca player => Andreas Inniesta
Introducing Barca player => Pique
Introducing Barca player => Xavi Hernandez

2018-04-13 14:54:49 INFO  com.programmer.gate.Application - Started Application in 4.213 seconds (JVM running for 4.555)

Source code

You can download the source code from this repository: spring-boot-jpa-hibernate.

Building a Simple CRUD App using Spring Boot, MySQL, JPA/Hibernate

Building a Simple CRUD App using Spring Boot, MySQL, JPA/Hibernate

In this article, we will walk through how to build a simple CRUD application using Spring Boot, MySQL, JPA/Hibernate and Okta OpenID Connect (OIDC) Single Sign-On (SSO).

In this article, we will walk through how to build a simple CRUD application using Spring Boot, MySQL, JPA/Hibernate and Okta OpenID Connect (OIDC) Single Sign-On (SSO).

The Java Persistence API (JPA) provides a specification for persisting, reading, and managing data from your Java object to relational tables in the database. The default implementation of JPA via Spring Boot is Hibernate. Hibernate saves you a lot of time writing code to persist data to a database, allowing you to focus on the business logic.

In this example, we will use MySQL for the database.

The application you will build will have two main parts. The first is the authentication piece. This will use Okta OIDC SSO to authenticate admin or ordinary users into the application. The second part will be the management of user events. These events are a record of any time a new token is issued for this application. The event will store the user’s name, internal id from Okta, and store additional information per token issuance including the time of the original login and the time the token was last used within the application to view a page. If you are wondering what the difference is between these three dates/times, here is a cheat-sheet:

  • Login Date/Time - This is the time at which the user actually had to enter their credentials. There can be more than one token issued against the same original login. This could be the time the user logged into this application, the Okta console, or another application tied to the same developer account (since this is an SSO example).
  • **Token Date/Time ** - When the user starts a session with the application described in this post, a new token is issued by Okta. This token is valid for a certain amount of time and is reused across page refreshes until it expires
  • Last View Date/Time - This is the last time the application was viewed. If you refresh the page, this time will update, while the Token Date/Time and Login Date/Time should remain the same.
Summary of CRUD actions

CRUD - Create When any user visits the application with a new session, they will be issued a new token. When this token is issued, a userEvent will be generated and persisted in the MySQL Database.

CRUD - Read Ordinary users will be able to see a list of all of the times they have received a new Okta token for this application. The application will also display the Login Date/Time and Last View Date/Time for that token (described earlier).

Admin users will be able to see the same info as the normal user, but for all users who have logged into the system.

CRUD - Update Each time any user visits the application, it will check if the token that they are using has already been saved as a userEvent. If so, it will update that entry with a new Last View Date/Time

CRUD - Delete Admin users will have the option to delete any of the user events in the system. Pro Tip: In a production system, you wouldn’t want to ever delete a log history, particularly a log regarding authentication or authorization actions. If this were production code and you had a requirement to delete log entries in order to hide them from ordinary users, you would likely want to implement this by setting a deleted flag on that record and only displaying records that are not flagged as deleted.

Prerequisites

MySQL - You must have installed a local instance of MySQL or have access to a remote instance of MySQL. For this exercise, I recommend that you have a fresh empty database prepared and have the username and password for that database handy. They will be required in the application.properties file later.

Setup your Okta OIDC Application, Authorization Server, groups and users

Before we can dive into the code, we will want to first get our Okta configuration in place. If you haven’t already, head on over to developer.okta.com to create yourself a free-forever developer account. Look for the email to complete the initialization of your Okta org.

Once you have your developer account, we will need to set up your web application, authorization server, group, and users!

Setup you Okta OIDC Application

Navigate to Applications in the admin console and click: Add Application. Choose Web and click Next. Populate the fields with these values:

Click Done. Scroll down and copy the Client ID and Client Secret. You’ll use those values shortly.

Setup your Okta Authorization Server

Next, you’ll set up an Authorization Server with custom claims and access policies. This drives whether or not Okta will issue a token when one is requested. Navigate to API > Authorization Servers. Click Add Authorization Server. Fill in the values as follows:

Click Done. Click the Claims tab. Click Add Claim. Fill in the fields with these values and click Create (leave those not mentioned as their defaults):

Note that there are double quotes (“) around “Beyond Authentication Application”.

Next, click on Add Claim again. Fill in the fields with these values and click Create (leave those not mentioned as their defaults):

Click the Access Policies tab. Click Add Policy. Fill in the fields with these values and click Create Policy

This binds the policy to your OIDC app.

Next click Add Rule. Enter: Beyond Authentication Application for the Rule Name field. Deselect all the grant types except for Authorization Code. Click Create Rule. This ensures that the request must use the Authorization Code flow in order for Okta to create tokens. This is the most secure flow of all the available OAuth flows. It ensures that all sensitive information (like tokens) are delivered via a response to a POST request. Check out the references at the end of this post for more on OAuth flows.

Click the Settings tab and copy the Issuer URL. You’ll make use of this value shortly.

Create Okta Admin Group for your application

In order to complete this application, we need to set up an “Admin” group for our application. To do this, within your Okta developer console, click on Users > Groups and then click on Add Group. Enter the following values:

Create Okta Users for your application

Finally, we need to create two users. The first will be an ordinary user and the second will be an admin user. From the developers console, click on Users > People and then click on Add Person. Fill out the form with the information for the ordinary (non-admin) user using the table below. Repeat this for the Admin user, also using the table below.

Take note of the username and password for each of the two users you created as you will use them to login later on.

Let’s code it

The structure of our project will look like this:

NOTE: For this post, I will be using Eclipse, as it is my preferred IDE. However, you can use any IDE or editor you please.

Dependencies

The Spring Initializr makes it super easy to create the scaffolding for your project. What’s even better is that it has a RESTful API, so you can get everything setup right from the command line. Run this command:

curl -s https://start.spring.io/starter.zip \
    -d dependencies=data-jpa,web,okta,thymeleaf,mysql \
    -d  packageName=com.okta.examples.jpa \
    -d groupId=com.okta.examples.jpa \
    -d name=BeyondAuthentication \
    > BeyondAuthentication.zip

Create a folder named: BeyondAuthentication, switch into it and unzip the resulting file.You’ll see the whole project has been setup for you. Let’s take a look at the pom.xml file, which has all the dependencies for the project.

The file should be set up as follows


<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        4.0.0
        
                org.springframework.boot
                spring-boot-starter-parent
                2.1.6.RELEASE
                 
        
        com.okta.examples.jpa
        demo
        0.0.1-SNAPSHOT
        BeyondAuthentication
        Demo project for Spring Boot

        
                1.8
        

        
                
                        org.springframework.boot
                        spring-boot-starter-data-jpa
                
                
                        org.springframework.boot
                        spring-boot-starter-thymeleaf
                
                
                        org.springframework.boot
                        spring-boot-starter-web
                
                
                        com.okta.spring
                        okta-spring-boot-starter
                        1.2.1
                
           
                
                        mysql
                        mysql-connector-java
                        runtime
                
                
                        org.springframework.boot
                        spring-boot-starter-test
                        test
                
        

        
                
                        
                                org.springframework.boot
                                spring-boot-maven-plugin
                        
                
        

     

The dependencies above tell the system that you want to use Thymeleaf for your web templates, MySQL for your database, and Hibernate (the default JPA implementation) to persist and retrieve data from MySQL.

Templates

Let’s also knock out the HTML template now, so you will have an idea what the application is trying to do/show later. Create the home.html file in the src/main/resources/templates folder.



    
        
    

    
        
            
                
                    
                    <div th:if="${user.claims.containsKey('appName')}"
                         class="alert alert-success text-center" role="alert"
                    >
                        ## 

                    
                    
                    <div th:if="${user.claims.containsKey('name')}"
                         class="alert alert-primary text-center" role="alert"
                    >
                        ### Welcome [[${user.claims.name}]]!

                    
                    
                        
                            
                                ## No User Events yet!

                                
                                    
                                        
                                        
                                            Name
                                            Login Date/Time
                                            Token Date/Time
                                            Last View Date/Time
                                            Delete
                                        
                                        
                                        
                                        
                                            
                                            
                                            
                                            
                                            
                                        
                                        
                                    
                                
                            
                        
                    
                
            
        
    

This template is very basic. It shows a list of user events and related fields. If the logged in user is an admin, it will show a delete button by each event. This template doesn’t control which events are shown, that will be determined in the controller later.

Project Configuration

We will configure the project via the application.properties file located in src/main/resources. In this file, we will configure the database connection, the Okta account information, and a few other fields.

okta.oauth2.issuer={authServerUrl}
okta.oauth2.clientId={clientId}
okta.oauth2.clientSecret={clientSecret}
okta.oauth2.scope=openid

## MySQL
spring.datasource.url=jdbc:mysql://localhost:3306/{dbName}

spring.datasource.username={dbUser}
spring.datasource.password={dbPassword}

# drop in to create the table, good for testing, comment this in production. This will create the table for you on each load of application, so you may want to comment this out after the first load
spring.jpa.hibernate.ddl-auto=create

The first section of the properties file is the Okta configuration. Earlier, you copied a few items including your Okta Web Application’s client id, client secret, and authorization server issuer URL. Paste those values into the application.properties file.

The next section is the MySql configuration. Replace the values inside the {} with the appropriate database name, user, and password. Note, you will have to replace the whole data source URL if you are not running MySql locally.

The following line in the properties file is very important: spring.jpa.hibernate.ddl-auto=create

This property tells the system to create (or re-create) the table from scratch to match the definition in the POJO Entity class (described below). The first time you run your application, or any time you change the structure of the entity class, you will want to have this line uncommented. However, after the table is constructed as you like, you will want to comment this line out or else it will drop and recreate every time you run it and you may wonder why your data isn’t persisting!

POJO Entity

Under the com.okta.examples.jpa package, create the UserEvent class. This class is a POJO that represents the fields in the UserEvent object that will persist to the database.

@Entity
public class UserEvent {

    private static SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm'Z'");

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

    private String name;
    
    @Lob 
    private String token;

    private String userId;

    @Temporal(TemporalType.TIMESTAMP)
    private Date loginAt;

    @Temporal(TemporalType.TIMESTAMP)
    private Date tokenIssuedAt;

    @Temporal(TemporalType.TIMESTAMP)
    private Date lastViewedAt;
    
    public UserEvent() {}

    public UserEvent(String userId, String name,String token,Date loginAt,Date issueAt) {
        this.userId=userId;
        this.name = name;
        this.token = token;
        this.loginAt = loginAt;
        this.lastViewedAt=loginAt;
        this.tokenIssuedAt=issueAt;
    }

    @Override
    public String toString() {
        return "UserEvent{" +
            "id=" + id +
            ", name='" + name + '\'' +
            ", token='" + token + '\'' +
            ", loginAt='" + loginAt + '\'' +
            '}';
    }

    // put boilerplate getters and setters here

    @Transient
    public String getTokenIssuedAtString() {
        return formatter.format(tokenIssuedAt);
    }
    
    @Transient
    public String getLastViewedAtString() {
        return formatter.format(lastViewedAt);
    }
    
    @Transient
    public String getLoginAtString() {
        return formatter.format(loginAt);
    }
}

The @Entity notation tells the system that this object will represent a row in a table. It tells Hibernate that this data should be stored in a table called user_event (from lowercasing the class name and adding “_” between words that were capitalized). If you want the table named something different, you can use the @table(name="tbl_something") annotation. The @Entity class also tells Hibernate that all of the non-static fields and/or getter methods should be persisted to the database. It will ignore any fields/methods that have the @Transient annotation and it will ignore methods that are a getter for a corresponding field. In this case, the entity will store the following fields in the database: id, name, token, userId, loginAt, tokenIssuedAt, and lastViewedAt. In most cases, Hibernate will understand the type of field that the values should be stored as in the database, but there are some times when you need to explicitly tell it what field type to use. In this example, I have used the annotation Lob to tell the system that the token field should be set to longtext. I have also used the @Temporal(TemporalType.TIMESTAMP) annotation to specify that three of the fields should be saved as datetime values. Lastly, I have used the following annotations to indicate that the id field is the primary key that should be autogenerated by the database:

@Id
@GeneratedValue(strategy = GenerationType.AUTO)

Repository

Create the UserEventRepository class. This class is a POJO that represents the fields in the UserEventRepository object that will expose the ability to read and write from the user_event table.

public interface UserEventRepository extends JpaRepository {

    List findByName(String name);
    List findByToken(String token);
    List findByUserId(String userId);
}

This class must extend JpaRepository with the type UserEvent specified so that it knows the type of objects that will be passed in and out. The JpaRepository interface already specifies basic read/write methods, like findById, findAll, and save. However, if you want other find methods specific to the fields, you would include them here, as I did for findByName, findByToken, and findByUserId. The magic is that you do not need to create the implementation class for the UserEventRepository as Spring will automatically create the implementation class at runtime.

Main Application Class

Take a look at the BeyondAuthenticationApplication that was automatically created by the Spring Initializr project. This class is the main application class that runs the application.

@SpringBootApplication
public class BeyondAuthenticationApplication{

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

Create a Controller Class to Route Requests

Last, but far from least, is the controller class. Create the HomeController class. This class does all of the work to read and write from the database. It also prepares the objects needed for the template to render including the list of events to show (all or just the user’s) and whether the logged in user is an admin or not.

@RestController
public class HomeController {

    private final UserEventRepository userEventRepository;

    public HomeController(UserEventRepository userEventRepository) {
        this.userEventRepository = userEventRepository;
    }
    
    @GetMapping("/")
    public ModelAndView home(@AuthenticationPrincipal OidcUser user) {
        String token = user.getIdToken().getTokenValue();
        
        //check if first time with this token, if so record new auth event
        List userEventsForToken = userEventRepository.findByToken(token);
        UserEvent event;
        if (userEventsForToken.size() == 0) {
            //add new event
            event = new UserEvent(
                user.getSubject(), user.getClaims().get("name").toString(),
                token, Date.from(user.getAuthenticatedAt()), Date.from(user.getIssuedAt())
            );
        } else {
            //edit existing event
            event = userEventsForToken.get(0); //there will only ever be one because we update it if it exists already
            event.setLastViewedAt(Date.from(Instant.now()));
        }
        userEventRepository.save(event);
        
        List eventsToShow;
        boolean isAdmin = user.getUserInfo().getClaimAsStringList("groups").contains("Admin");
        if (isAdmin) {
            eventsToShow = userEventRepository.findAll();
        } else {
            eventsToShow = userEventRepository.findByUserId(user.getSubject());
        }
        
        ModelAndView mav = new ModelAndView();
        mav.addObject("user", user.getUserInfo());
        mav.addObject("idToken", user.getIdToken().getTokenValue());
        mav.addObject("userEvents",eventsToShow);
        mav.addObject("isAdmin",isAdmin);
        mav.setViewName("home");
        return mav;
    }
    
    @GetMapping("/delete/{id}")
    public RedirectView deleteUser(@AuthenticationPrincipal OidcUser user,@PathVariable("id") long id, Model model) {
        UserEvent userEvent = userEventRepository.findById(id)
          .orElseThrow(() -> new IllegalArgumentException("Invalid event Id:" + id));
        userEventRepository.delete(userEvent);
        return new RedirectView("/");
    }
}

The constructor of this class automatically passes in the UserEventRepository implementation that Spring auto generated for you. This is the modern way to handle dependency injection. We don’t need the @Autowired annotation anymore.

The home method is annotated with the @GetMapping("/") annotation which tells the system to serve this model/view anytime someone accesses the application with no other path information specified. This method builds up the model and view for the main page of the site that uses the home.xml template. It does the following:

  • Uses the token to look up old user events to see if one already exists for this token. If so, it updates the lastViewedAt time. If not, it creates a new user event.
  • Determines if the logged in user is admin or not
  • Gets list of user events. If user is admin, it gets all of the events. If the user is not an admin, it only gets events for that user.
  • It returns the model and view with the appropriate variables set to use for rendering the template

The deleteUser method is annotated with the @GetMapping("/delete/{id}") annotation which tells it to execute this method when a user calls a URL or clicks on a link with the href that passes in the id of the event to delete. The home.html template included links with this URL configured for an admin to delete user events. This is the code that will get triggered when the user clicks on the delete button. The code simply finds the user event, deletes it, and then redirects back to the main listing page, where you will see the updated list without the deleted item.

Let’s Run it!

Let’s see it in action! In Eclipse, just right click on the BeyondAuthenticationApplication file, click run as > Java application, and it will kick it off. You can now test your application! Type [http://localhost:8080](http://localhost:8080) into your favorite web browser. You should be presented with an Okta login screen:

Note: If you don’t see the Okta login screen, it is likely because you recently logged into the dev console and it is recognizing that login as valid for this application (after all, it is SSO). If that is the case, just use a different browser or an incognito browser tab.

From the login screen, login as your admin user. It will then redirect you back to your application, which should look something like this:

Next, let’s login with a non-admin user. Do the same steps as above in a different browser or within a new incognito tab. It should present you with the login screen again. From there, log in with your non-admin user and you will see something like this:

Notice, the delete button is missing. When you refresh your screen for the admin user, you will now see events for both your admin and your non-admin. However, the view for your non-admin user will only ever show their own events.

Play around with the delete button from the admin’s view and try refreshing the page and notice that the last view date/time of the entry will update. If you log in again later on, you will see multiple entries for the user once a new token is issued.