Spring boot Data JPA+ Angular JS + MySQL Database CRUD

Github Link: 

We are using  Spring boot version 2.0.0.RELEASE . We will be creating simple TodoManager application using which we can manage our daily todo tasks. We will be using angular for front end. It will provide user interface from which you can add, update or delete tasks in todo list.We will use controller, JpaRepository classes to achieve these functionalities.We will connect to MySQL database using SessionFactory class of hibernate.

Tools used for below project –

  1. Spring Boot 2.0.0.RELEASE
  2. Spring 5.0.4.RELEASE
  3. Tomcat Embed 8
  4. Maven 3.3
  5. Java 8
  6. Spring Tool Suite IDE (STS)
  7. Spring Data JPA 2.0.5.RELEASE
  8. MySql 5.1.21

Step 1: Project Structure

Step 2: Create a project named TodoListManagerJPA in STS (Refer Create new project in STS)

Step 3: Change pom.xml as below –

<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.anup.springboot</groupId>
<artifactId>TodoListManagerJPA</artifactId>
<packaging>war</packaging>
<version>0.0.1-SNAPSHOT</version>
<name>Springboot JPA Maven Webapp</name>
<url>http://maven.apache.org</url>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>

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

<dependencies>

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

<!-- JSTL for JSP -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
</dependency>

<!-- For JSP compilation -->
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
<scope>provided</scope>
</dependency>

<!-- mySQL DB -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.21</version>
</dependency>

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

</dependencies>

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

<!-- <plugin>
<artifactId>maven-war-plugin</artifactId>
<version>2.6</version>
<configuration>
<failOnMissingWebXml>false</failOnMissingWebXml>
</configuration>
</plugin> -->
</plugins>

<finalName>TodoListManagerJPA</finalName>
</build>
</project>

The spring-boot-starter-parent provides you all maven defaults required for any spring project. Since we are developing a web application, we also need to add spring-boot-starter-web dependency. Additionally we need to include spring-boot-starter-data-jpa to run this application with MySQL database.You need to also put mysql-connector-java for MySql JDBC driver.If you are using any other database, you need to use different database connector.
Let’s do hibernate configuration first.

Step 4: Create a  “application.properties”  file in package /src/main/resources

spring.mvc.view.prefix: /WEB-INF/views/
spring.mvc.view.suffix: .jsp
server.port=8081

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

## Hibernate Properties

# The SQL dialect makes Hibernate generate better SQL for the chosen database
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5Dialect

# Hibernate ddl auto (create, create-drop, validate, update)
spring.jpa.hibernate.ddl-auto = update

In the above properties file, the last two properties are for 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.

It does two things –

  • When you define a domain model, a table will automatically be created in the database and the fields of the domain model will be mapped to the corresponding columns in the table.
  • Any change to the domain model will also trigger an update to the table. For example, If you change the name or type of a field, or add another field to the model, then all these changes will be reflected in the mapped table as well.

Using update for spring.jpa.hibernate.ddl-auto property is fine for development. But, For production, You should keep the value of this property to “validate”, and use a database migration tool like Flyway for managing changes in the database schema.

Step 5: Create a “Task.java” model class in com.myjavablog.springboot.pojo package

package com.myjavablog.springboot.pojo;

import java.io.Serializable;
import java.util.Date;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EntityListeners;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;

import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;

/**
* @author anup
*
*/

@Entity
@Table(name="TASKS")
@EntityListeners(AuditingEntityListener.class)
@JsonIgnoreProperties(value = {"createdAt", "updatedAt"},
allowGetters = true)
public class Task implements Serializable {

/**
*
*/
private static final long serialVersionUID = 1L;

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

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

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

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

@Column(nullable = false, updatable = false , name= "CREATED_AT")
@Temporal(TemporalType.TIMESTAMP)
@CreatedDate
private Date createdAt;

@Column(nullable = false, name= "UPDATED_AT")
@Temporal(TemporalType.TIMESTAMP)
@LastModifiedDate
private Date updatedAt;

public Long getId() {
return id;
}

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

public String getTaskName() {
return taskName;
}

public void setTaskName(String taskName) {
this.taskName = taskName;
}

public String getTaskDesc() {
return taskDesc;
}

public void setTaskDesc(String taskDesc) {
this.taskDesc = taskDesc;
}

public String getStatus() {
return status;
}

public void setStatus(String status) {
this.status = status;
}

public Date getCreatedAt() {
return createdAt;
}

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

public Date getUpdatedAt() {
return updatedAt;
}

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

@Entity – This marks class as Entity

@Table – This maps class to database table

@Id – Marks the class variable as primary key column in table

@Column – Marks it as a column in table , we can also configure name attribute defining its name in database table

@Temporal – annotation is used with java.util.Dateand java.util.Calendar classes. It converts the date and time values from Java Object to compatible database type and vice versa.

@JsonIgnoreProperties – annotation is a Jackson annotation. Spring Boot uses Jackson for Serializing and Deserializing Java objects to and from JSON.This annotation is used because we don’t want the clients of the rest api to supply the createdAt and updatedAt values. If they supply these values then we’ll simply ignore them. However, we’ll include these values in the JSON response.

@EntityListeners(AuditingEntityListener.class)

In our Task model we have annotated createdAt and updatedAt fields with @CreatedDate and @LastModifiedDate annotations respectively.

Now, what we want is that these fields should automatically get populated whenever we create or update an entity.

To achieve this, we need to do two things –

  1. Add Spring Data JPA’s AuditingEntityListener to the domain model. We have already done this in our Task model with the annotation @EntityListeners(AuditingEntityListener.class).
  2. Enable JPA Auditing in the main application. Open TodoManagerApplication.java and add @EnableJpaAuditing annotation.

@GeneratedValue – Marking a field with the @GeneratedValue annotation specifies that a value will be automatically generated for that field. This is primarily intended for primary key fields .There are below types of strategies –

GenerationType.AUTO It is the default generation type and lets the persistence provider choose the generation strategy.

@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "id", updatable = false, nullable = false)
private Long id;

GenerationType.IDENTITY – The GenerationType.IDENTITY is the easiest to use but not the best one from a performance point of view. It relies on an auto-incremented database column and lets the database generate a new value with each insert operation. From a database point of view, this is very efficient because the auto-increment columns are highly optimized, and it doesn’t require any additional statements.

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

GenerationType.SEQUENCE – The GenerationType.SEQUENCE is my preferred way to generate primary key values and uses a database sequence to generate unique values.

If you don’t provide sequence name hibernate will provide its default sequence

@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
@Column(name = "id", updatable = false, nullable = false)
private Long id;

You can change that by referencing the name of a @SequenceGenerator in the generator attribute of the @GeneratedValue annotation. The @SequenceGenerator annotation lets you define the name of the generator, the name, and schema of the database sequence and the allocation size of the sequence.

@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "book_generator")
@SequenceGenerator(name="book_generator", sequenceName = "book_seq", allocationSize=50)
@Column(name = "id", updatable = false, nullable = false)
private Long id;

GenerationType.TABLE – The GenerationType.TABLE gets only rarely used nowadays. It simulates a sequence by storing and updating its current value in a database table which requires the use of pessimistic locks which put all transactions into a sequential order. This slows down your application, and you should, therefore, prefer the GenerationType.SEQUENCE, if your database supports sequences, which most popular databases do

If you don’t provide sequence name hibernate will provide its default sequence

@Id
@GeneratedValue(strategy = GenerationType.TABLE)
@Column(name = "id", updatable = false, nullable = false)
private Long id;

Summary:

  1. AUTO: Hibernate selects the generation strategy based on the used dialect,
  2. IDENTITY: Hibernate relies on an auto-incremented database column to generate the primary key,
  3. SEQUENCE: Hibernate requests the primary key value from a database sequence,
  4. TABLE: Hibernate uses a database table to simulate a sequence.

Step 6: Create database tables

create schema test;

create table TASKS (id bigint NOT NULL AUTO_INCREMENT, TASK_NAME varchar(255), TASK_DESC varchar(255), STATUS varchar(255) , CREATED_AT timestamp, UPDATED_AT timestamp, primary key (id));

INSERT INTO TASKS VALUES(1, 'Bath', 'Have a bath' , 'PENDING', SYSDATE, SYSDATE);

INSERT INTO TASKS VALUES(2, 'Snacks', 'Have snacks' , 'PENDING' , CURTIME() , CURTIME());

INSERT INTO TASKS VALUES(3, 'Office', 'Office' , 'PENDING' , CURTIME() , CURTIME());

INSERT INTO TASKS VALUES(4, 'Lunch', 'Lunch' , 'PENDING' , CURTIME() , CURTIME());

Controller class

Step 7: Create class “TodoController.java” in package com.myjavablog.springboot.controller

/**
*
*/
package com.myjavablog.springboot.controller;

import java.util.List;

import javax.validation.Valid;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
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.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import com.myjavablog.springboot.exception.ResourceNotFoundException;
import com.myjavablog.springboot.pojo.Task;
import com.myjavablog.springboot.repository.TodoRepository;

/**
* @author anup
*
*/

@RestController
public class TodoController {

@Autowired
TodoRepository todoRepository;

@PostMapping(value = "/todos/addTodo")
public Task addTodo(@Valid @RequestBody Task task) {
return todoRepository.save(task);
}

@PutMapping(value = "/todos/updateTodo")
public Task updateTodo(@Valid @RequestBody Task task) {

Task todo = todoRepository.findById(task.getId())
.orElseThrow(() -> new ResourceNotFoundException("Todo", "id", task.getId()));

todo.setStatus(task.getStatus());
todo.setTaskDesc(task.getTaskDesc());
todo.setTaskName(task.getTaskName());

Task updatedTask = todoRepository.save(todo);
return updatedTask;
}
@GetMapping(value = "/todos/getAllTodos")
public List<Task> getAllTodos() {
return todoRepository.findAll();
}

@DeleteMapping(value="/todos/deleteTodo/{id}")
public ResponseEntity<?> deleteCust(@PathVariable Long id) {
todoRepository.deleteById(id);

return ResponseEntity.ok().build();
}

}

@RestController annotation is a combination of Spring’s @Controller and @ResponseBody annotations.

Repository 

Step 8: Create interface “TodoRepository.java” in package “com.myjavablog.springboot.repository” and extend it from JpaRepository.

We have to create a repository to access Tasks from the database.

Well, Spring Data JPA comes to rescue us here. It comes with a JpaRepository interface which defines methods for all the CRUD operations on the entity, and a default implementation of JpaRepository called SimpleJpaRepository. JPA has eliminated lot of boilerplate code which we need to write for CRUD operations otherwise.

package com.myjavablog.springboot.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import com.myjavablog.springboot.pojo.Task;

/**
* @author anupb
*
*/
@Repository
public interface TodoRepository extends JpaRepository<Task, Long>{

}

we have annotated the interface with @Repository annotation. This tells Spring to bootstrap the repository during component scan.

Cool! That is all you have to do in the repository layer. You will now be able to use JpaRepository’s methods like save()findOne()findAll()count()delete() etc.

Custom Exception

Step 9: Create “ResourceNotFoundException.java” in “com.myjavablog.springboot.exception” package –

We have defined the Rest APIs for creating, retrieving, updating, and deleting a Task .

The APIs will throw a ResourceNotFoundException whenever a Task with a given id is not found in the database.

package com.myjavablog.springboot.exception;

import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.http.HttpStatus;
/**
* @author anupb
*
*/
@ResponseStatus(value = HttpStatus.NOT_FOUND)
public class ResourceNotFoundException extends RuntimeException {
private String resourceName;
private String fieldName;
private Object fieldValue;

public ResourceNotFoundException(String resourceName, String fieldName, Object fieldValue) {
super(String.format("%s not found with %s : '%s'", resourceName, fieldName, fieldValue));
this.resourceName = resourceName;
this.fieldName = fieldName;
this.fieldValue = fieldValue;
}

public String getResourceName() {
return resourceName;
}

public String getFieldName() {
return fieldName;
}

public Object getFieldValue() {
return fieldValue;
}
}

AngularJS UI Layer

Step 10: Create “index.jsp” under \src\main\webapp\WEB-INF\views\

<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
pageEncoding="ISO-8859-1"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%@ taglib uri="http://www.springframework.org/tags" prefix="spring"%>
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form"%>

<html>
<head>
<link href="/css/app.css" rel="stylesheet"/>

<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.4/angular.js"></script>
<script type="text/javascript" src="/js/app.js"></script>

<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Todo Manager</title>
</head>
<body ng-app="todoManager" ng-controller="todoController">
</br>
<h4 class="todo">Todo Manager</h4>

<form ng-submit="submitTodo()">
<table>

<tr>
<th colspan="2">Add/Edit todo</th>
</tr>
<tr>
<td>Task Name</td>
<td><input type="text" ng-model="todoForm.taskName" /></td>
</tr>
<tr>
<td>Task Desc</td>
<td><input type="text" ng-model="todoForm.taskDesc" /></td>
</tr>
<tr>
<td>Status</td>
<td><select id="status" ng-model="todoForm.status" ng-options="x for x in status" /></select></td>
</tr>
<tr>
<td colspan="2"><input type="submit" value="Submit"
class="blue-button" /></td>
</tr>
</table>
</form>

<table>
<tr>

<th width="120">Task Name</th>
<th width="120">Task Desc</th>
<th width="60">Status</th>
<th width="60">Operations</th>

</tr>

<tr ng-repeat="todo in todos" ng-init="statusCls=getClass(todo)">

<td>{{ todo.taskName }}</td>
<td>{{ todo.taskDesc }}</td>
<td class="statusCls"> {{ todo.status }} </td>

<td><a ng-click="editTodo(todo)" class="blue-button">Edit</a>
| <a ng-click="deleteTodo(todo)" class="red-button">Delete</a></td>
</tr>

</table>

</body>
</html>

Step 11: Create javascript file “app.js” under \src\main\resources\static\js\

var app = angular.module("todoManager" , []);

app.controller("todoController" , function($scope, $http){

$scope.todos = [];
$scope.status = ["PENDING", "COMPLETED"];
$scope.todoForm = {
id : -1,
taskName : "",
taskDesc : ""
};

$scope.getClass = function(todo){

if(todo.status == 'PENDING')
return 'red';
else if(todo.status == 'COMPLETED'){
return 'green';
}
}

_refreshTodoData();

function _refreshTodoData(){

$http({
method : 'GET',
url : 'http://localhost:8082/todos/getAllTodos'
}).then(function successCallback(response) {
$scope.todos = response.data;

//alert($scope.todos);
}, function errorCallback(response) {
console.log(response.statusText);
});
}

$scope.submitTodo = function() {

var method = "";
var url = "";
if ($scope.todoForm.id == -1) {
//Id is absent in form data, it is create new todo operation
method = "POST";
url = '/todos/addTodo';
} else {
//Id is present in form data, it is edit todo operation
method = "PUT";
url = '/todos/updateTodo';
}

$http({
method : method,
url : url,
data : angular.toJson($scope.todoForm),
headers : {
'Content-Type' : 'application/json'
}
}).then( _success, _error );
};

$scope.deleteTodo = function(todo) {
$http({
method : 'DELETE',
url : '/todos/deleteTodo/' + todo.id
}).then(_success, _error);
};

$scope.editTodo = function(todo) {

$scope.todoForm.taskName = todo.taskName;
$scope.todoForm.taskDesc = todo.taskDesc;
$scope.todoForm.status = todo.status;
$scope.todoForm.id = todo.id;
};

function _success(response) {
//alert("success");
_refreshTodoData();
_clearFormData();
}

function _error(response) {
//alert("error");
console.log(response.statusText);
}

//Clear the form
function _clearFormData() {
$scope.todoForm.id = -1;
$scope.todoForm.taskName = "";
$scope.todoForm.taskDesc = "";
$scope.todoForm.status = "";

};
});

Step 12: Create CSS file “app.css” under \src\main\resources\static\css\ for adding style

.blue-button {
background: #25A6E1;
filter: progid: DXImageTransform.Microsoft.gradient( startColorstr='#25A6E1',
endColorstr='#188BC0', GradientType=0);
padding: 3px 5px;
color: #fff;
font-family: 'Helvetica Neue', sans-serif;
font-size: 12px;
border-radius: 2px;
-moz-border-radius: 2px;
-webkit-border-radius: 4px;
border: 1px solid #1A87B9;
cursor: pointer;
}

.red-button {
background: #CD5C5C;
padding: 3px 5px;
color: #fff;
font-family: 'Helvetica Neue', sans-serif;
font-size: 12px;
border-radius: 2px;
-moz-border-radius: 2px;
-webkit-border-radius: 4px;
border: 1px solid #CD5C5C;
cursor: pointer;
}

table {
font-family: "Helvetica Neue", Helvetica, sans-serif;
width: 50%;
}

caption {
text-align: left;
color: silver;
font-weight: bold;
text-transform: uppercase;
padding: 5px;
}

th {
background: SteelBlue;
color: white;
}

tbody tr:nth-child(even) {
background: WhiteSmoke;
}

tbody tr td:nth-child(2) {
text-align: center;
}

tbody tr td:nth-child(3), tbody tr td:nth-child(4) {
text-align: center;
font-family: monospace;
}

tfoot {
background: SeaGreen;
color: white;
text-align: right;
}

tfoot tr th:last-child {
font-family: monospace;
}

td, th {
border: 1px solid gray;
width: 25%;
text-align: left;
padding: 5px 10px;
}

#status{
width: 53%;
padding: 1px 0;
}

.todo {
font-size: 24px;
color: #CD5C5C;
text-transform: uppercase;
font-family: arial;
width: 50%;
background: #ccc;
text-align: center;
border-radius: 4px;
margin: 2% 0%;
padding: 3px 0;
box-shadow: 0px 1px 7px 1px grey;
}

.pending{
color: red;
}

.completed{
color: green;
}

  • $http – It’s used to make AJAX calls to server in order to submitTodo(), deleteTodo() , editTodo() task.
  • When you click on submit button on form, it actually calls POST or PUT depending on operation. If you click on edit and submit data then it will be PUT operation as it will update existing resource. If you directly submit data, then it will be POST operation to create new resource.
  • Every time you submit data, it calls _refreshTodoData() to refresh Todo table below.
  • When you call $http, you need to pass method type and URL, it will call it according, You can either put absolute URL or relative URL with respect to context root of web application.

Spring boot main file

Step 13: Create “TodoManagerApplication.java” a main spring file under com.anup.springboot

package com.myjavablog.springboot;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;

@SpringBootApplication
@EnableJpaAuditing
@EnableJpaRepositories(basePackages="com.myjavablog.springboot.repository")
public class TodoManagerApplication {

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

This is the mail file which bootstraps spring application.

We have just added @SpringBootApplication and it does all the work.
Let’s understand more about this annotation.
@SpringBootApplication is an annotation that adds all of the following:

@Configuration – It makes the class as a source of bean definitions for the application context.
@EnableAutoConfiguration – It enables Spring boot to add beans presents in classpath setting and various property setting.
Normally you would add @EnableWebMvc for a Spring MVC application, but Spring Boot adds it automatically when it sees spring-webmvc on the classpath.
This flags the application as a web application and activates key behaviors such as setting up a DispatcherServlet.
@ComponentScan – It tells Spring to look for other components, configurations, and services in the default package, allowing it to find the controllers.
If specific packages are not defined, scanning will occur from the package of the class that declares this annotation.

Run the application

Step 14: Right Click on Project -> Debug As -> Maven Build

Step 15: Provide goals as below – 

mvn clean install spring-boot:run (Cmd prompt)

Or

Step 16: Now once the application is up and running, you can access it from below link –

Adding new task –

Editing the task –

Deleting the task –

Now we will try to update Task which is not present in database. It should throw a custom exception and we will get response as shown below –

Bitnami