Spring Boot + AngularJS + Spring Data + JPA CRUD App Example

In this post we will be developing a full-blown CRUD application using Spring Boot, AngularJS, Spring-Data, JPA/Hibernate and MySQL, learning the concepts in details along the way. This application can as well serve as a base/starting point for your own application. In addition, we will also use the notion of profiles to deploy the application into two different databases [H2 & MySQL] to emulate the local and production environment, to be more realistic.

springbootcrudapp_img5


Following technologies stack being used:

  • Spring Boot 1.4.3.RELEASE
  • Spring 4.3.5.RELEASE [transitively]
  • Spring data JPA 1.10.6.RELEASE [transitively]
  • Hibernate 5.0.11.Final [transitively]
  • MySQL 5.1.40 [transitively]
  • H2 1.4.187
  • Hikari CP 2.4.7 [transitively]
  • AngularJS 1.5.8
  • Maven 3.1
  • JDK 1.8
  • Eclipse MARS.1

Let’s Begin.

1. Project Structure

springbootcrudapp_img0_1springbootcrudapp_img0_2

2. Dependency Management [pom.xml]

<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">
	<modelVersion>4.0.0</modelVersion>

	<groupId>com.websystique.springboot</groupId>
	<artifactId>SpringBootCRUDApplicationExample</artifactId>
	<version>1.0.0</version>
	<packaging>jar</packaging>

	<name>SpringBootCRUDApplicationExample</name>

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

	<properties>
		<java.version>1.8</java.version>
		<h2.version>1.4.187</h2.version>
	</properties>

	<dependencies>
		<!-- Add typical dependencies for a web application -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<!-- Add freemarker template support -->		
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-freemarker</artifactId>
		</dependency>
		<!-- Add JPA support -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-jpa</artifactId>
		</dependency>
		<!-- Add Hikari Connection Pooling support -->
		<dependency>
			<groupId>com.zaxxer</groupId>
			<artifactId>HikariCP</artifactId>
		</dependency>
		<!-- Add H2 database support [for running with local profile] -->
		<dependency>
			<groupId>com.h2database</groupId>
			<artifactId>h2</artifactId>
			<version>${h2.version}</version>
		</dependency>
		<!-- Add MySQL database support [for running with PRODUCTION profile] -->		
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
		</dependency>
		<dependency>
    		<groupId>org.apache.commons</groupId>
    		<artifactId>commons-lang3</artifactId>
    		<version>3.5</version>
		</dependency>
	</dependencies>
	<build>
		<plugins>
			<plugin><!-- Include if you want to make an executable jar[FAT JAR which 
				includes all dependencies along with sprinboot loader] that you can run on 
					commandline using java -jar NAME -->
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>
</project>

spring-boot-starter-parent : In most of the cases[unless imported], your maven project POM will simply inherit from the spring-boot-starter-parent project. The spring-boot-starter-parent provides useful Maven defaults, provides dependency-management section so that you can omit version tags for dependencies you would need for your own project. Once inherited from spring-boot-starter-parent, you would declare dependencies to one or more “Starters” jars.

spring-boot-starter-web : Provides typical WEB MVC + Embedded container support.

spring-boot-starter-freemarker : Provides freemarker template support. We will be using freemarker in this example.

spring-boot-starter-data-jpa : Provides spring-data setup using JPA abstraction. Since we are talking about fast-development using spring-boot, spring-data would certainly save time compare to traditional DAO/Creteria/Query manual setup.

HikariCP : Provides Hikari connection pooling support. We could have as well used Tomcat datapooling. Common DBCP is usually not recommended for performance reasons.

h2: Provides H2 database support. Please note that it is used here just to demonstrate the real-life scenarios where your local setup uses one database while the one on production might be altogether a different database.Additionally, we are deliberately using a different version of h2, just to demonstrate that you CAN change the dependencies if needed.

mysql-connector-java: Provides MySQL database support. Again, just because we are simulating a local[H2]-Production[MySQL] scenario.

2. Spring Boot Application [Main class]

You read it right. Good old main is what all we need to start our newly created spring boot app. Spring Boot provides SpringApplication class to bootstrap a Spring application that will be started from a main() method using static SpringApplication.run method.

package com.websystique.springboot;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Import;

import com.websystique.springboot.configuration.JpaConfiguration;


@Import(JpaConfiguration.class)
@SpringBootApplication(scanBasePackages={"com.websystique.springboot"})
public class SpringBootCRUDApp {

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

This class is annotated with @SpringBootApplication which is actually the combination of [shortcut] @EnableAutoConfiguration, @Configuration & @ComponentScan. You can choose either of them.

Spring Boot @EnableAutoConfiguration attempts to automatically configure your Spring application based on the jar dependencies that you have added. Since we have added spring-boot-starter-web, Spring boot will setup the Spring configuration for a web-application.

3. JPA configuation

In this configuration class, we are doing a lot: Creating datasource [using Hikari connection pooling], creating EntityManagerFactory, setting up transaction manager, referring to Spring-data repositories etc.

  • Spring Data @EnableJpaRepositories: @EnableJpaRepositories Annotation enables JPA repositories. It will scan the specified packages for Spring Data repositories. by default, it will look into current package for Spring-data repositories.
  • Spring Boot DataSourceProperties : DataSourceProperties is the helper class for configuration of a data source. Interesting point is that we can map the properties right from .yml files, thanks to hierarchical data. Matching-name properties from .yml will be mapped directly to properties of DataSourceProperties object.
  • Spring Boot DataSourceBuilder : DataSourceBuilder is a builder that can help creating a datasource using the mapped properties.
  • Additionally,if a datasource property is missing in DataSourceProperties [maxPoolSize e.g.], we can still take the advantage of good old @Value annotation to map it form property file to actual object property.
package com.websystique.springboot.configuration;

import java.util.Properties;

import javax.naming.NamingException;
import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;

import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.env.Environment;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.JpaVendorAdapter;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import com.zaxxer.hikari.HikariDataSource;

@Configuration
@EnableJpaRepositories(basePackages = "com.websystique.springboot.repositories",
		entityManagerFactoryRef = "entityManagerFactory",
		transactionManagerRef = "transactionManager")
@EnableTransactionManagement
public class JpaConfiguration {

	@Autowired
	private Environment environment;

	@Value("${datasource.sampleapp.maxPoolSize:10}")
	private int maxPoolSize;

	/*
	 * Populate SpringBoot DataSourceProperties object directly from application.yml 
	 * based on prefix.Thanks to .yml, Hierachical data is mapped out of the box with matching-name
	 * properties of DataSourceProperties object].
	 */
	@Bean
	@Primary
	@ConfigurationProperties(prefix = "datasource.sampleapp")
	public DataSourceProperties dataSourceProperties(){
		return new DataSourceProperties();
	}

	/*
	 * Configure HikariCP pooled DataSource.
	 */
	@Bean
	public DataSource dataSource() {
		DataSourceProperties dataSourceProperties = dataSourceProperties();
			HikariDataSource dataSource = (HikariDataSource) DataSourceBuilder
					.create(dataSourceProperties.getClassLoader())
					.driverClassName(dataSourceProperties.getDriverClassName())
					.url(dataSourceProperties.getUrl())
					.username(dataSourceProperties.getUsername())
					.password(dataSourceProperties.getPassword())
					.type(HikariDataSource.class)
					.build();
			dataSource.setMaximumPoolSize(maxPoolSize);
			return dataSource;
	}

	/*
	 * Entity Manager Factory setup.
	 */
	@Bean
	public LocalContainerEntityManagerFactoryBean entityManagerFactory() throws NamingException {
		LocalContainerEntityManagerFactoryBean factoryBean = new LocalContainerEntityManagerFactoryBean();
		factoryBean.setDataSource(dataSource());
		factoryBean.setPackagesToScan(new String[] { "com.websystique.springboot.model" });
		factoryBean.setJpaVendorAdapter(jpaVendorAdapter());
		factoryBean.setJpaProperties(jpaProperties());
		return factoryBean;
	}

	/*
	 * Provider specific adapter.
	 */
	@Bean
	public JpaVendorAdapter jpaVendorAdapter() {
		HibernateJpaVendorAdapter hibernateJpaVendorAdapter = new HibernateJpaVendorAdapter();
		return hibernateJpaVendorAdapter;
	}

	/*
	 * Here you can specify any provider specific properties.
	 */
	private Properties jpaProperties() {
		Properties properties = new Properties();
		properties.put("hibernate.dialect", environment.getRequiredProperty("datasource.sampleapp.hibernate.dialect"));
		properties.put("hibernate.hbm2ddl.auto", environment.getRequiredProperty("datasource.sampleapp.hibernate.hbm2ddl.method"));
		properties.put("hibernate.show_sql", environment.getRequiredProperty("datasource.sampleapp.hibernate.show_sql"));
		properties.put("hibernate.format_sql", environment.getRequiredProperty("datasource.sampleapp.hibernate.format_sql"));
		if(StringUtils.isNotEmpty(environment.getRequiredProperty("datasource.sampleapp.defaultSchema"))){
			properties.put("hibernate.default_schema", environment.getRequiredProperty("datasource.sampleapp.defaultSchema"));
		}
		return properties;
	}

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

}

4. Property file [application.yml]

Although traditional .properties would just do fine, Spring Boot’s SpringApplication class also supports YAML out of the box provided SnakeYAML library is on class-path which usually would be due to starters. YAML is a superset of JSON, and as such is a very convenient format for specifying hierarchical configuration data. YAML file is also known as streams, containing several documents, each separated by three dashes (—). A line beginning with “—” may be used to explicitly denote the beginning of a new YAML document.YAML specification is a good read to know more about them.

src/main/resources/application.yml

server:
  port: 8080
  contextPath: /SpringBootCRUDApp
---
spring:
  profiles: local,default
datasource:
  sampleapp:
    url: jdbc:h2:~/test
    username: SA
    password:
    driverClassName: org.h2.Driver
    defaultSchema:
    maxPoolSize: 10
    hibernate:
      hbm2ddl.method: create-drop
      show_sql: true
      format_sql: true
      dialect: org.hibernate.dialect.H2Dialect
---
spring:
  profiles: prod
datasource:
  sampleapp:
    url: jdbc:mysql://localhost:3306/websystique
    username: myuser
    password: mypassword
    driverClassName: com.mysql.jdbc.Driver
    defaultSchema:
    maxPoolSize: 20
    hibernate:
      hbm2ddl.method: update
      show_sql: true
      format_sql: true
      dialect: org.hibernate.dialect.MySQLDialect
  • Since our app will be running on an Embedded container, we would need a way to configure the port and context-path for our app. By-default, Spring-Boot will use no context-path, and the default port would be 8080, means your application would be available at localhost:8080. But you can overwrite these properties by declaring them in application.yml [or application.yaml/application.properties] file. In our case, the first document [top level part, above '---' line] is the one configuring port and context path.
  • Since we will be using profiles, we have created two separate documents each with it’s own profile.
  • By default if no profile is specified, ‘default’ profile is used, this is standard spring behavior. You can additionally create different profiles based on your environments and use them on run.
  • In our case, we are pointing both default and local to same profile, hence letting user to run the app directly, without specifying any profile, in that case the default profile will be used. But you are free to specify a profile. While running our example [via IDE or command-line], we can provide the profile information using -Dspring.profiles.active=local or -Dspring.profiles.active=prod in VM arguments[for IDE] or on command-line java -jar JARPATH --spring.profiles.active=local.
  • Notice the datasource part in yml file: here we are specifying all stuff related to database. Similarly if you have other aspects/concerns [security e.g.], you could create separate levels for that. We will be using H2 database while running under profile ‘local’ and MySQL while running with profile ‘prod’.
In case you face trouble with YAML, Online YAML editor comes handy to validate your YAML.

5. Model

User.java

package com.websystique.springboot.model;

import org.hibernate.validator.constraints.NotEmpty;

import javax.persistence.*;
import java.io.Serializable;

@Entity
@Table(name="APP_USER")
public class User implements Serializable{

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

	@NotEmpty
	@Column(name="NAME", nullable=false)
	private String name;

	@Column(name="AGE", nullable=false)
	private Integer age;

	@Column(name="SALARY", nullable=false)
	private double salary;

--- getter/setter omitted to save space
}

6. Spring-Data repositories

This one is rather simple.

package com.websystique.springboot.repositories;

import com.websystique.springboot.model.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface UserRepository extends JpaRepository<User, Long> {

    User findByName(String name);

}

That’s it for spring-data part. Interface JpaRepository packs a punch. It provides all the CRUD operations by-default using id as the key. In case you need to lookup on a property other than id, you could just create a ‘camelCase’ signature with that property, spring-data will itself generate the implementation and execute the appropriate SQL to get the data out from database. spring-data @Query annotation goes a step further by allowing you to write the JPQL or even native SQL yourself instead of relying on spring-data to do that. One could as well extend from CrudRepository instead of JpaRepository but JpaRepository provides some goodies like paging and sorting which most of the time is needed in a FE application.

7. Service

Our controller will be using this service for all user-related operations. Service in turn uses our spring-data repository to access and update the user.

package com.websystique.springboot.service;

import com.websystique.springboot.model.User;
import java.util.List;

public interface UserService {
	
	User findById(Long id);

	User findByName(String name);

	void saveUser(User user);

	void updateUser(User user);

	void deleteUserById(Long id);

	void deleteAllUsers();

	List<User> findAllUsers();

	boolean isUserExist(User user);
}
package com.websystique.springboot.service;

import java.util.List;

import com.websystique.springboot.model.User;
import com.websystique.springboot.repositories.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;



@Service("userService")
@Transactional
public class UserServiceImpl implements UserService{

	@Autowired
	private UserRepository userRepository;

	public User findById(Long id) {
		return userRepository.findOne(id);
	}

	public User findByName(String name) {
		return userRepository.findByName(name);
	}

	public void saveUser(User user) {
		userRepository.save(user);
	}

	public void updateUser(User user){
		saveUser(user);
	}

	public void deleteUserById(Long id){
		userRepository.delete(id);
	}

	public void deleteAllUsers(){
		userRepository.deleteAll();
	}

	public List<User> findAllUsers(){
		return userRepository.findAll();
	}

	public boolean isUserExist(User user) {
		return findByName(user.getName()) != null;
	}

}

8. Controllers

We have two controllers in our application. One for handling the view and other for handling the REST API calls, coming from Our AngularJS based Front-end.

package com.websystique.springboot.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class AppController {

	@RequestMapping("/")
	String home(ModelMap modal) {
		modal.addAttribute("title","CRUD Example");
		return "index";
	}

	@RequestMapping("/partials/{page}")
	String partialHandler(@PathVariable("page") final String page) {
		return page;
	}

}

package com.websystique.springboot.controller;

import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.util.UriComponentsBuilder;

import com.websystique.springboot.model.User;
import com.websystique.springboot.service.UserService;
import com.websystique.springboot.util.CustomErrorType;

@RestController
@RequestMapping("/api")
public class RestApiController {

	public static final Logger logger = LoggerFactory.getLogger(RestApiController.class);

	@Autowired
	UserService userService; //Service which will do all data retrieval/manipulation work

	// -------------------Retrieve All Users---------------------------------------------

	@RequestMapping(value = "/user/", method = RequestMethod.GET)
	public ResponseEntity<List<User>> listAllUsers() {
		List<User> users = userService.findAllUsers();
		if (users.isEmpty()) {
			return new ResponseEntity(HttpStatus.NO_CONTENT);
			// You many decide to return HttpStatus.NOT_FOUND
		}
		return new ResponseEntity<List<User>>(users, HttpStatus.OK);
	}

	// -------------------Retrieve Single User------------------------------------------

	@RequestMapping(value = "/user/{id}", method = RequestMethod.GET)
	public ResponseEntity<?> getUser(@PathVariable("id") long id) {
		logger.info("Fetching User with id {}", id);
		User user = userService.findById(id);
		if (user == null) {
			logger.error("User with id {} not found.", id);
			return new ResponseEntity(new CustomErrorType("User with id " + id 
					+ " not found"), HttpStatus.NOT_FOUND);
		}
		return new ResponseEntity<User>(user, HttpStatus.OK);
	}

	// -------------------Create a User-------------------------------------------

	@RequestMapping(value = "/user/", method = RequestMethod.POST)
	public ResponseEntity<?> createUser(@RequestBody User user, UriComponentsBuilder ucBuilder) {
		logger.info("Creating User : {}", user);

		if (userService.isUserExist(user)) {
			logger.error("Unable to create. A User with name {} already exist", user.getName());
			return new ResponseEntity(new CustomErrorType("Unable to create. A User with name " + 
			user.getName() + " already exist."),HttpStatus.CONFLICT);
		}
		userService.saveUser(user);

		HttpHeaders headers = new HttpHeaders();
		headers.setLocation(ucBuilder.path("/api/user/{id}").buildAndExpand(user.getId()).toUri());
		return new ResponseEntity<String>(headers, HttpStatus.CREATED);
	}

	// ------------------- Update a User ------------------------------------------------

	@RequestMapping(value = "/user/{id}", method = RequestMethod.PUT)
	public ResponseEntity<?> updateUser(@PathVariable("id") long id, @RequestBody User user) {
		logger.info("Updating User with id {}", id);

		User currentUser = userService.findById(id);

		if (currentUser == null) {
			logger.error("Unable to update. User with id {} not found.", id);
			return new ResponseEntity(new CustomErrorType("Unable to upate. User with id " + id + " not found."),
					HttpStatus.NOT_FOUND);
		}

		currentUser.setName(user.getName());
		currentUser.setAge(user.getAge());
		currentUser.setSalary(user.getSalary());

		userService.updateUser(currentUser);
		return new ResponseEntity<User>(currentUser, HttpStatus.OK);
	}

	// ------------------- Delete a User-----------------------------------------

	@RequestMapping(value = "/user/{id}", method = RequestMethod.DELETE)
	public ResponseEntity<?> deleteUser(@PathVariable("id") long id) {
		logger.info("Fetching & Deleting User with id {}", id);

		User user = userService.findById(id);
		if (user == null) {
			logger.error("Unable to delete. User with id {} not found.", id);
			return new ResponseEntity(new CustomErrorType("Unable to delete. User with id " + id + " not found."),
					HttpStatus.NOT_FOUND);
		}
		userService.deleteUserById(id);
		return new ResponseEntity<User>(HttpStatus.NO_CONTENT);
	}

	// ------------------- Delete All Users-----------------------------

	@RequestMapping(value = "/user/", method = RequestMethod.DELETE)
	public ResponseEntity<User> deleteAllUsers() {
		logger.info("Deleting All Users");

		userService.deleteAllUsers();
		return new ResponseEntity<User>(HttpStatus.NO_CONTENT);
	}

}

Additionally, a helper class to send errors [in-case any] from API in JSON format iso string.

package com.websystique.springboot.util;


public class CustomErrorType {

    private String errorMessage;

    public CustomErrorType(String errorMessage){
        this.errorMessage = errorMessage;
    }

    public String getErrorMessage() {
        return errorMessage;
    }

}

Populate MySQL database

If you look back at application.yml, we have set the hibernate hbm2ddl as ‘create-drop‘ under ‘local’ profile, where as ‘update‘ under ‘prod’ profile, just for demonstration purpose. That mean in ‘local’ [H2], table will be dropped and recreated at application startup so we don’t need to create it manually. But in case of ‘prod’ [MySQL], we need to manually create the table if it does not exist. For MySQL, You can run following SQL to create table and populate dummy data.

create table APP_USER (
   id BIGINT NOT NULL AUTO_INCREMENT,
   name VARCHAR(30) NOT NULL,
   age  INTEGER NOT NULL,
   salary REAL NOT NULL,
   PRIMARY KEY (id)
);
  
/* Populate USER Table */
INSERT INTO APP_USER(name,age,salary)
VALUES ('Sam',30,70000);
  
INSERT INTO APP_USER(name,age,salary)
VALUES ('Tom',40,50000);

commit;

Front-end

Let’s add a view to our MVC app. We would be using Freemarker templates in our app. Spring Boot WebMvcAutoConfiguration adds FreeMarkerViewResolver with id ‘freeMarkerViewResolver’ if freemarker jar is in classpath, which is the case since we are using spring-boot-starter-freemarker. It looks for resources in a loader path (externalized to spring.freemarker.templateLoaderPath, default ‘classpath:/templates/’) by surrounding the view name with a prefix and suffix (externalized to spring.freemarker.prefix and spring.freemarker.suffix, with empty and ‘.ftl’ defaults respectively). It can be overridden by providing a bean of the same name.

Although one can develop a complete FE using freemarker itself with tons of scripts and cryptic expressions with ‘#’ lurking around all over the page, question is should we, knowing that we are not in 1990 anymore? I decided to use AngularJS [with ui-router] instead, using freemarker just as a container, nothing else.

Freemarker Templates

src/main/resources/templates/index.ftl

<!DOCTYPE html>

<html lang="en" ng-app="crudApp">
    <head>
        <title>${title}</title>
        <link href="css/bootstrap.css" rel="stylesheet"/>
        <link href="css/app.css" rel="stylesheet"/>
    </head>
    <body>

        <div ui-view></div>
        <script src="js/lib/angular.min.js" ></script>
        <script src="js/lib/angular-ui-router.min.js" ></script>
        <script src="js/lib/localforage.min.js" ></script>
        <script src="js/lib/ngStorage.min.js"></script>
        <script src="js/app/app.js"></script>
        <script src="js/app/UserService.js"></script>
        <script src="js/app/UserController.js"></script>
    </body>
</html>

src/main/resources/templates/list.ftl

<div class="generic-container">
    <div class="panel panel-default">
        <!-- Default panel contents -->
        <div class="panel-heading"><span class="lead">Specific User </span></div>
		<div class="panel-body">
	        <div class="formcontainer">
	            <div class="alert alert-success" role="alert" ng-if="ctrl.successMessage">{{ctrl.successMessage}}</div>
	            <div class="alert alert-danger" role="alert" ng-if="ctrl.errorMessage">{{ctrl.errorMessage}}</div>
	            <form ng-submit="ctrl.submit()" name="myForm" class="form-horizontal">
	                <input type="hidden" ng-model="ctrl.user.id" />
	                <div class="row">
	                    <div class="form-group col-md-12">
	                        <label class="col-md-2 control-lable" for="uname">Name</label>
	                        <div class="col-md-7">
	                            <input type="text" ng-model="ctrl.user.name" id="uname" class="username form-control input-sm" placeholder="Enter your name" required ng-minlength="3"/>
	                        </div>
	                    </div>
	                </div>

	                <div class="row">
	                    <div class="form-group col-md-12">
	                        <label class="col-md-2 control-lable" for="age">Age</label>
	                        <div class="col-md-7">
	                            <input type="text" ng-model="ctrl.user.age" id="age" class="form-control input-sm" placeholder="Enter your Age." required ng-pattern="ctrl.onlyIntegers"/>
	                        </div>
	                    </div>
	                </div>
	
	                <div class="row">
	                    <div class="form-group col-md-12">
	                        <label class="col-md-2 control-lable" for="salary">Salary</label>
	                        <div class="col-md-7">
	                            <input type="text" ng-model="ctrl.user.salary" id="salary" class="form-control input-sm" placeholder="Enter your Salary." required ng-pattern="ctrl.onlyNumbers"/>
	                        </div>
	                    </div>
	                </div>

	                <div class="row">
	                    <div class="form-actions floatRight">
	                        <input type="submit"  value="{{!ctrl.user.id ? 'Add' : 'Update'}}" class="btn btn-primary btn-sm" ng-disabled="myForm.$invalid || myForm.$pristine">
	                        <button type="button" ng-click="ctrl.reset()" class="btn btn-warning btn-sm" ng-disabled="myForm.$pristine">Reset Form</button>
	                    </div>
	                </div>
	            </form>
    	    </div>
		</div>	
    </div>
    <div class="panel panel-default">
        <!-- Default panel contents -->
        <div class="panel-heading"><span class="lead">List of Users </span></div>
		<div class="panel-body">
			<div class="table-responsive">
		        <table class="table table-hover">
		            <thead>
		            <tr>
		                <th>ID</th>
		                <th>NAME</th>
		                <th>AGE</th>
		                <th>SALARY</th>
		                <th width="100"></th>
		                <th width="100"></th>
		            </tr>
		            </thead>
		            <tbody>
		            <tr ng-repeat="u in ctrl.getAllUsers()">
		                <td>{{u.id}}</td>
		                <td>{{u.name}}</td>
		                <td>{{u.age}}</td>
		                <td>{{u.salary}}</td>
		                <td><button type="button" ng-click="ctrl.editUser(u.id)" class="btn btn-success custom-width">Edit</button></td>
		                <td><button type="button" ng-click="ctrl.removeUser(u.id)" class="btn btn-danger custom-width">Remove</button></td>
		            </tr>
		            </tbody>
		        </table>		
			</div>
		</div>
    </div>
</div>

Static resources

Static resources like images/css/JS in a Spring boot application are commonly located in a directory called /static (or /public or /resources or /META-INF/resources) in the classpath or from the root of the ServletContext. In this example, we are using bootstrap.css which is located in src/main/resources/static/css.

Error Page

By default, Spring Boot installs a ‘whitelabel’ error page that is shown in browser client if you encounter a server error. You can override that page, based upon the templating technology you are using. For freemarker, you can create a page with name ‘error.ftl’ which would be shown in case an error occurred.

src/main/resources/templates/error.ftl

<!DOCTYPE html>

<html lang="en">
<head>
    <link rel="stylesheet" type="text/css" href="css/bootstrap.css" />
</head>
<body>
	<div class="container">
	    <div class="jumbotron alert-danger">
			<h1>Oops. Something went wrong</h1>
			<h2>${status} ${error}</h2>
	    </div>
	</div>
</body>

</html>

AngularJs [ui-router based app]

src/main/resources/static/js/app.js

var app = angular.module('crudApp',['ui.router','ngStorage']);

app.constant('urls', {
    BASE: 'http://localhost:8080/SpringBootCRUDApp',
    USER_SERVICE_API : 'http://localhost:8080/SpringBootCRUDApp/api/user/'
});

app.config(['$stateProvider', '$urlRouterProvider',
    function($stateProvider, $urlRouterProvider) {

        $stateProvider
            .state('home', {
                url: '/',
                templateUrl: 'partials/list',
                controller:'UserController',
                controllerAs:'ctrl',
                resolve: {
                    users: function ($q, UserService) {
                        console.log('Load all users');
                        var deferred = $q.defer();
                        UserService.loadAllUsers().then(deferred.resolve, deferred.resolve);
                        return deferred.promise;
                    }
                }
            });
        $urlRouterProvider.otherwise('/');
    }]);

src/main/resources/static/js/UserService.js

'use strict';

angular.module('crudApp').factory('UserService',
    ['$localStorage', '$http', '$q', 'urls',
        function ($localStorage, $http, $q, urls) {

            var factory = {
                loadAllUsers: loadAllUsers,
                getAllUsers: getAllUsers,
                getUser: getUser,
                createUser: createUser,
                updateUser: updateUser,
                removeUser: removeUser
            };

            return factory;

            function loadAllUsers() {
                console.log('Fetching all users');
                var deferred = $q.defer();
                $http.get(urls.USER_SERVICE_API)
                    .then(
                        function (response) {
                            console.log('Fetched successfully all users');
                            $localStorage.users = response.data;
                            deferred.resolve(response);
                        },
                        function (errResponse) {
                            console.error('Error while loading users');
                            deferred.reject(errResponse);
                        }
                    );
                return deferred.promise;
            }

            function getAllUsers(){
                return $localStorage.users;
            }

            function getUser(id) {
                console.log('Fetching User with id :'+id);
                var deferred = $q.defer();
                $http.get(urls.USER_SERVICE_API + id)
                    .then(
                        function (response) {
                            console.log('Fetched successfully User with id :'+id);
                            deferred.resolve(response.data);
                        },
                        function (errResponse) {
                            console.error('Error while loading user with id :'+id);
                            deferred.reject(errResponse);
                        }
                    );
                return deferred.promise;
            }

            function createUser(user) {
                console.log('Creating User');
                var deferred = $q.defer();
                $http.post(urls.USER_SERVICE_API, user)
                    .then(
                        function (response) {
                            loadAllUsers();
                            deferred.resolve(response.data);
                        },
                        function (errResponse) {
                           console.error('Error while creating User : '+errResponse.data.errorMessage);
                           deferred.reject(errResponse);
                        }
                    );
                return deferred.promise;
            }

            function updateUser(user, id) {
                console.log('Updating User with id '+id);
                var deferred = $q.defer();
                $http.put(urls.USER_SERVICE_API + id, user)
                    .then(
                        function (response) {
                            loadAllUsers();
                            deferred.resolve(response.data);
                        },
                        function (errResponse) {
                            console.error('Error while updating User with id :'+id);
                            deferred.reject(errResponse);
                        }
                    );
                return deferred.promise;
            }

            function removeUser(id) {
                console.log('Removing User with id '+id);
                var deferred = $q.defer();
                $http.delete(urls.USER_SERVICE_API + id)
                    .then(
                        function (response) {
                            loadAllUsers();
                            deferred.resolve(response.data);
                        },
                        function (errResponse) {
                            console.error('Error while removing User with id :'+id);
                            deferred.reject(errResponse);
                        }
                    );
                return deferred.promise;
            }

        }
    ]);

src/main/resources/static/js/UserController.js

'use strict';

angular.module('crudApp').controller('UserController',
    ['UserService', '$scope',  function( UserService, $scope) {

        var self = this;
        self.user = {};
        self.users=[];

        self.submit = submit;
        self.getAllUsers = getAllUsers;
        self.createUser = createUser;
        self.updateUser = updateUser;
        self.removeUser = removeUser;
        self.editUser = editUser;
        self.reset = reset;

        self.successMessage = '';
        self.errorMessage = '';
        self.done = false;

        self.onlyIntegers = /^\d+$/;
        self.onlyNumbers = /^\d+([,.]\d+)?$/;

        function submit() {
            console.log('Submitting');
            if (self.user.id === undefined || self.user.id === null) {
                console.log('Saving New User', self.user);
                createUser(self.user);
            } else {
                updateUser(self.user, self.user.id);
                console.log('User updated with id ', self.user.id);
            }
        }

        function createUser(user) {
            console.log('About to create user');
            UserService.createUser(user)
                .then(
                    function (response) {
                        console.log('User created successfully');
                        self.successMessage = 'User created successfully';
                        self.errorMessage='';
                        self.done = true;
                        self.user={};
                        $scope.myForm.$setPristine();
                    },
                    function (errResponse) {
                        console.error('Error while creating User');
                        self.errorMessage = 'Error while creating User: ' + errResponse.data.errorMessage;
                        self.successMessage='';
                    }
                );
        }


        function updateUser(user, id){
            console.log('About to update user');
            UserService.updateUser(user, id)
                .then(
                    function (response){
                        console.log('User updated successfully');
                        self.successMessage='User updated successfully';
                        self.errorMessage='';
                        self.done = true;
                        $scope.myForm.$setPristine();
                    },
                    function(errResponse){
                        console.error('Error while updating User');
                        self.errorMessage='Error while updating User '+errResponse.data;
                        self.successMessage='';
                    }
                );
        }


        function removeUser(id){
            console.log('About to remove User with id '+id);
            UserService.removeUser(id)
                .then(
                    function(){
                        console.log('User '+id + ' removed successfully');
                    },
                    function(errResponse){
                        console.error('Error while removing user '+id +', Error :'+errResponse.data);
                    }
                );
        }


        function getAllUsers(){
            return UserService.getAllUsers();
        }

        function editUser(id) {
            self.successMessage='';
            self.errorMessage='';
            UserService.getUser(id).then(
                function (user) {
                    self.user = user;
                },
                function (errResponse) {
                    console.error('Error while removing user ' + id + ', Error :' + errResponse.data);
                }
            );
        }
        function reset(){
            self.successMessage='';
            self.errorMessage='';
            self.user={};
            $scope.myForm.$setPristine(); //reset Form
        }
    }
    ]);

Run the application

Finally, Let’s run the application, firstly with ‘local’ profile [H2]. Next shot will be with ‘prod’ profile [MySQL].

Via Eclipse:: Run it directly, in that case default profile will be used. In case you want a different profile to be used, create a Run configuration for you main class, specifying the profile. To do that from toolbar, select Run->Run Configurations->Arguments->VM Arguments. Add -Dspring.profiles.active=local or -Dspring.profiles.active=prod]

Via Command line::
On project root
$> java -jar target/SpringBootCRUDApplicationExample-1.0.0.jar –spring.profiles.active=local

Please take special note of two ‘-’ in front of spring.profiles.active. In the blog it might be appearing as single ‘-’ but there are in fact two ‘-’ of them.


  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v1.4.3.RELEASE)

2016-12-26 17:15:35.114  INFO 4496 --- [           main] c.w.springboot.SpringBootCRUDApp         : Starting SpringBootCRUDApp on dragon with PID 4496 (D:\Development\workspaces\workspace_websystique\SpringBootCRUDApplicationExample\target\classes started by ADMIN in D:\Development\workspaces\workspace_websystique\SpringBootCRUDApplicationExample)
2016-12-26 17:15:35.121  INFO 4496 --- [           main] c.w.springboot.SpringBootCRUDApp         : The following profiles are active: local
2016-12-26 17:15:35.225  INFO 4496 --- [           main] ationConfigEmbeddedWebApplicationContext : Refreshing org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@45b4c3a9: startup date [Mon Dec 26 17:15:35 CET 2016]; root of context hierarchy
2016-12-26 17:15:38.422  INFO 4496 --- [           main] trationDelegate$BeanPostProcessorChecker : Bean 'org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration' of type [class org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration$$EnhancerBySpringCGLIB$$e0f3dd92] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2016-12-26 17:15:39.458  INFO 4496 --- [           main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat initialized with port(s): 8080 (http)
2016-12-26 17:15:39.484  INFO 4496 --- [           main] o.apache.catalina.core.StandardService   : Starting service Tomcat
2016-12-26 17:15:39.486  INFO 4496 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet Engine: Apache Tomcat/8.5.6
2016-12-26 17:15:39.692  INFO 4496 --- [ost-startStop-1] o.a.c.c.C.[.[.[/SpringBootCRUDApp]       : Initializing Spring embedded WebApplicationContext
2016-12-26 17:15:39.693  INFO 4496 --- [ost-startStop-1] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 4474 ms
2016-12-26 17:15:40.031  INFO 4496 --- [ost-startStop-1] o.s.b.w.servlet.ServletRegistrationBean  : Mapping servlet: 'dispatcherServlet' to [/]
2016-12-26 17:15:40.037  INFO 4496 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'characterEncodingFilter' to: [/*]
2016-12-26 17:15:40.038  INFO 4496 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'hiddenHttpMethodFilter' to: [/*]
2016-12-26 17:15:40.038  INFO 4496 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'httpPutFormContentFilter' to: [/*]
2016-12-26 17:15:40.038  INFO 4496 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'requestContextFilter' to: [/*]
2016-12-26 17:15:40.334  INFO 4496 --- [           main] j.LocalContainerEntityManagerFactoryBean : Building JPA container EntityManagerFactory for persistence unit 'default'
2016-12-26 17:15:40.360  INFO 4496 --- [           main] o.hibernate.jpa.internal.util.LogHelper  : HHH000204: Processing PersistenceUnitInfo [
	name: default
	...]
2016-12-26 17:15:40.488  INFO 4496 --- [           main] org.hibernate.Version                    : HHH000412: Hibernate Core {5.0.11.Final}
2016-12-26 17:15:40.490  INFO 4496 --- [           main] org.hibernate.cfg.Environment            : HHH000206: hibernate.properties not found
2016-12-26 17:15:40.492  INFO 4496 --- [           main] org.hibernate.cfg.Environment            : HHH000021: Bytecode provider name : javassist
2016-12-26 17:15:40.569  INFO 4496 --- [           main] o.hibernate.annotations.common.Version   : HCANN000001: Hibernate Commons Annotations {5.0.1.Final}
2016-12-26 17:15:40.786  INFO 4496 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Started.
2016-12-26 17:15:41.228  INFO 4496 --- [           main] com.zaxxer.hikari.pool.PoolBase          : HikariPool-1 - Driver does not support get/set network timeout for connections. (org.h2.jdbc.JdbcConnection.getNetworkTimeout()I)
2016-12-26 17:15:41.311  INFO 4496 --- [           main] org.hibernate.dialect.Dialect            : HHH000400: Using dialect: org.hibernate.dialect.H2Dialect
2016-12-26 17:15:41.908  INFO 4496 --- [           main] org.hibernate.tool.hbm2ddl.SchemaExport  : HHH000227: Running hbm2ddl schema export
Hibernate: 
    drop table APP_USER if exists
Hibernate: 
    create table APP_USER (
        id bigint generated by default as identity,
        AGE integer not null,
        NAME varchar(255) not null,
        SALARY double not null,
        primary key (id)
    )
2016-12-26 17:15:41.927  INFO 4496 --- [           main] org.hibernate.tool.hbm2ddl.SchemaExport  : HHH000230: Schema export complete
2016-12-26 17:15:41.987  INFO 4496 --- [           main] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
2016-12-26 17:15:42.983  INFO 4496 --- [           main] s.w.s.m.m.a.RequestMappingHandlerAdapter : Looking for @ControllerAdvice: org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@45b4c3a9: startup date [Mon Dec 26 17:15:35 CET 2016]; root of context hierarchy
2016-12-26 17:15:43.109  INFO 4496 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/]}" onto java.lang.String com.websystique.springboot.controller.AppController.home(org.springframework.ui.ModelMap)
2016-12-26 17:15:43.110  INFO 4496 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/partials/{page}]}" onto java.lang.String com.websystique.springboot.controller.AppController.partialHandler(java.lang.String)
2016-12-26 17:15:43.116  INFO 4496 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/api/user/{id}],methods=[GET]}" onto public org.springframework.http.ResponseEntity<?> com.websystique.springboot.controller.RestApiController.getUser(long)
2016-12-26 17:15:43.117  INFO 4496 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/api/user/],methods=[GET]}" onto public org.springframework.http.ResponseEntity<java.util.List<com.websystique.springboot.model.User>> com.websystique.springboot.controller.RestApiController.listAllUsers()
2016-12-26 17:15:43.117  INFO 4496 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/api/user/],methods=[POST]}" onto public org.springframework.http.ResponseEntity<?> com.websystique.springboot.controller.RestApiController.createUser(com.websystique.springboot.model.User,org.springframework.web.util.UriComponentsBuilder)
2016-12-26 17:15:43.117  INFO 4496 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/api/user/{id}],methods=[PUT]}" onto public org.springframework.http.ResponseEntity<?> com.websystique.springboot.controller.RestApiController.updateUser(long,com.websystique.springboot.model.User)
2016-12-26 17:15:43.118  INFO 4496 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/api/user/{id}],methods=[DELETE]}" onto public org.springframework.http.ResponseEntity<?> com.websystique.springboot.controller.RestApiController.deleteUser(long)
2016-12-26 17:15:43.118  INFO 4496 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/api/user/],methods=[DELETE]}" onto public org.springframework.http.ResponseEntity<com.websystique.springboot.model.User> com.websystique.springboot.controller.RestApiController.deleteAllUsers()
2016-12-26 17:15:43.122  INFO 4496 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error]}" onto public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.BasicErrorController.error(javax.servlet.http.HttpServletRequest)
2016-12-26 17:15:43.123  INFO 4496 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error],produces=1}" onto public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)
2016-12-26 17:15:43.185  INFO 4496 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/webjars/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2016-12-26 17:15:43.185  INFO 4496 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2016-12-26 17:15:43.261  INFO 4496 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**/favicon.ico] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2016-12-26 17:15:43.708  INFO 4496 --- [           main] o.s.w.s.v.f.FreeMarkerConfigurer         : ClassTemplateLoader for Spring macros added to FreeMarker configuration
2016-12-26 17:15:44.002  INFO 4496 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
2016-12-26 17:15:44.004  INFO 4496 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Bean with name 'dataSource' has been autodetected for JMX exposure
2016-12-26 17:15:44.014  INFO 4496 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Located MBean 'dataSource': registering with JMX server as MBean [com.zaxxer.hikari:name=dataSource,type=HikariDataSource]
2016-12-26 17:15:44.102  INFO 4496 --- [           main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8080 (http)
2016-12-26 17:15:44.110  INFO 4496 --- [           main] c.w.springboot.SpringBootCRUDApp         : Started SpringBootCRUDApp in 9.731 seconds (JVM running for 10.325)

Open your browser and navigate to http://localhost:8080/SpringBootCRUDApp/

springbootcrudapp_img1

Add few users.

springbootcrudapp_img2

Try to add a user with same name as an existing user, should get an error [this is backend throwing the error, you can change the logic on backend based on your business rules].

springbootcrudapp_img3

Reset the form.Remove a user, edit the other one.

springbootcrudapp_img4

Now shutdown your app and restart it using ‘prod’ profile this time.


  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v1.4.3.RELEASE)

2016-12-26 18:58:18.947  INFO 6828 --- [           main] c.w.springboot.SpringBootCRUDApp         : Starting SpringBootCRUDApp on dragon with PID 6828 (D:\Development\workspaces\workspace_websystique\SpringBootCRUDApplicationExample\target\classes started by ADMIN in D:\Development\workspaces\workspace_websystique\SpringBootCRUDApplicationExample)
2016-12-26 18:58:18.952  INFO 6828 --- [           main] c.w.springboot.SpringBootCRUDApp         : The following profiles are active: prod
2016-12-26 18:58:19.073  INFO 6828 --- [           main] ationConfigEmbeddedWebApplicationContext : Refreshing org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@45b4c3a9: startup date [Mon Dec 26 18:58:19 CET 2016]; root of context hierarchy
2016-12-26 18:58:21.827  INFO 6828 --- [           main] trationDelegate$BeanPostProcessorChecker : Bean 'org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration' of type [class org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration$$EnhancerBySpringCGLIB$$e0f3dd92] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2016-12-26 18:58:22.808  INFO 6828 --- [           main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat initialized with port(s): 8080 (http)
2016-12-26 18:58:22.833  INFO 6828 --- [           main] o.apache.catalina.core.StandardService   : Starting service Tomcat
2016-12-26 18:58:22.835  INFO 6828 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet Engine: Apache Tomcat/8.5.6
2016-12-26 18:58:23.052  INFO 6828 --- [ost-startStop-1] o.a.c.c.C.[.[.[/SpringBootCRUDApp]       : Initializing Spring embedded WebApplicationContext
2016-12-26 18:58:23.053  INFO 6828 --- [ost-startStop-1] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 3986 ms
2016-12-26 18:58:23.386  INFO 6828 --- [ost-startStop-1] o.s.b.w.servlet.ServletRegistrationBean  : Mapping servlet: 'dispatcherServlet' to [/]
2016-12-26 18:58:23.395  INFO 6828 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'characterEncodingFilter' to: [/*]
2016-12-26 18:58:23.396  INFO 6828 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'hiddenHttpMethodFilter' to: [/*]
2016-12-26 18:58:23.397  INFO 6828 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'httpPutFormContentFilter' to: [/*]
2016-12-26 18:58:23.397  INFO 6828 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'requestContextFilter' to: [/*]
2016-12-26 18:58:23.640  INFO 6828 --- [           main] j.LocalContainerEntityManagerFactoryBean : Building JPA container EntityManagerFactory for persistence unit 'default'
2016-12-26 18:58:23.675  INFO 6828 --- [           main] o.hibernate.jpa.internal.util.LogHelper  : HHH000204: Processing PersistenceUnitInfo [
	name: default
	...]
2016-12-26 18:58:23.823  INFO 6828 --- [           main] org.hibernate.Version                    : HHH000412: Hibernate Core {5.0.11.Final}
2016-12-26 18:58:23.826  INFO 6828 --- [           main] org.hibernate.cfg.Environment            : HHH000206: hibernate.properties not found
2016-12-26 18:58:23.830  INFO 6828 --- [           main] org.hibernate.cfg.Environment            : HHH000021: Bytecode provider name : javassist
2016-12-26 18:58:23.912  INFO 6828 --- [           main] o.hibernate.annotations.common.Version   : HCANN000001: Hibernate Commons Annotations {5.0.1.Final}
2016-12-26 18:58:24.110  INFO 6828 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Started.
2016-12-26 18:58:24.823  INFO 6828 --- [           main] org.hibernate.dialect.Dialect            : HHH000400: Using dialect: org.hibernate.dialect.MySQLDialect
2016-12-26 18:58:25.607  INFO 6828 --- [           main] org.hibernate.tool.hbm2ddl.SchemaUpdate  : HHH000228: Running hbm2ddl schema update
2016-12-26 18:58:25.774  INFO 6828 --- [           main] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
2016-12-26 18:58:26.707  INFO 6828 --- [           main] s.w.s.m.m.a.RequestMappingHandlerAdapter : Looking for @ControllerAdvice: org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@45b4c3a9: startup date [Mon Dec 26 18:58:19 CET 2016]; root of context hierarchy
2016-12-26 18:58:26.833  INFO 6828 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/]}" onto java.lang.String com.websystique.springboot.controller.AppController.home(org.springframework.ui.ModelMap)
2016-12-26 18:58:26.835  INFO 6828 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/partials/{page}]}" onto java.lang.String com.websystique.springboot.controller.AppController.partialHandler(java.lang.String)
2016-12-26 18:58:26.840  INFO 6828 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/api/user/],methods=[GET]}" onto public org.springframework.http.ResponseEntity<java.util.List<com.websystique.springboot.model.User>> com.websystique.springboot.controller.RestApiController.listAllUsers()
2016-12-26 18:58:26.841  INFO 6828 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/api/user/{id}],methods=[GET]}" onto public org.springframework.http.ResponseEntity<?> com.websystique.springboot.controller.RestApiController.getUser(long)
2016-12-26 18:58:26.841  INFO 6828 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/api/user/],methods=[POST]}" onto public org.springframework.http.ResponseEntity<?> com.websystique.springboot.controller.RestApiController.createUser(com.websystique.springboot.model.User,org.springframework.web.util.UriComponentsBuilder)
2016-12-26 18:58:26.842  INFO 6828 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/api/user/{id}],methods=[PUT]}" onto public org.springframework.http.ResponseEntity<?> com.websystique.springboot.controller.RestApiController.updateUser(long,com.websystique.springboot.model.User)
2016-12-26 18:58:26.842  INFO 6828 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/api/user/{id}],methods=[DELETE]}" onto public org.springframework.http.ResponseEntity<?> com.websystique.springboot.controller.RestApiController.deleteUser(long)
2016-12-26 18:58:26.842  INFO 6828 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/api/user/],methods=[DELETE]}" onto public org.springframework.http.ResponseEntity<com.websystique.springboot.model.User> com.websystique.springboot.controller.RestApiController.deleteAllUsers()
2016-12-26 18:58:26.846  INFO 6828 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error]}" onto public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.BasicErrorController.error(javax.servlet.http.HttpServletRequest)
2016-12-26 18:58:26.847  INFO 6828 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error],produces=1}" onto public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)
2016-12-26 18:58:26.903  INFO 6828 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/webjars/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2016-12-26 18:58:26.904  INFO 6828 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2016-12-26 18:58:27.008  INFO 6828 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**/favicon.ico] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2016-12-26 18:58:27.363  INFO 6828 --- [           main] o.s.w.s.v.f.FreeMarkerConfigurer         : ClassTemplateLoader for Spring macros added to FreeMarker configuration
2016-12-26 18:58:27.604  INFO 6828 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
2016-12-26 18:58:27.606  INFO 6828 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Bean with name 'dataSource' has been autodetected for JMX exposure
2016-12-26 18:58:27.614  INFO 6828 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Located MBean 'dataSource': registering with JMX server as MBean [com.zaxxer.hikari:name=dataSource,type=HikariDataSource]
2016-12-26 18:58:27.711  INFO 6828 --- [           main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8080 (http)
2016-12-26 18:58:27.720  INFO 6828 --- [           main] c.w.springboot.SpringBootCRUDApp         : Started SpringBootCRUDApp in 9.478 seconds (JVM running for 10.023)

Provided your mysql is up and running, you should get following, right from MySQL database this time :

springbootcrudapp_img5

Conclusion

Although the post was bit long, Spring Boot and associated concepts are fairly trivial. Spring Boot reduces the development time by many-fold, worth giving a try. The application we developed here is fully loaded and can be used in a live environment or as the base application for your own projects. Feel free to write your thoughts in comment section.

Download Source Code



References

If you like tutorials on this site, why not take a step further and connect me on Facebook , Google Plus & Twitter as well? I would love to hear your thoughts on these articles, it will help me improve further our learning process.

If you appreciate the effort I have put in this learning site, help me improve the visibility of this site towards global audience by sharing and linking this site from within and beyond your network. You & your friends can always link my site from your site on www.websystique.com, and share the learning.

After all, we are here to learn together, aren’t we?

  • Phelan Lemieux

    Hey awesome tutorial. Any idea why I would be getting a Whitelabel Error Page when trying to access from the web browser?

    • websystique

      Hi Phelan, We overrode the Whitelabel error page using error.ftl in this example, so as per the official documentation, you shouldn’t be getting that error page. I wonder if you have some changes wrt download?

      • Phelan Lemieux

        Hi! I figured out the issue. I was using a newer version of spring boot and I guess the Maven references had not completely updated so I removed everything from the repository folder and re-updated and it worked. My fault!

  • Pingback: Spring MVC 4 RESTFul Web Services CRUD Example+RestTemplate - WebSystique()

  • Akash Agrawal

    Hey Websystique, I followed this tutorial to build my application, but after I run the project I get the following error:
    org.springframework.beans.factory.BeanCreationException: Error creating bean with name ‘entityManagerFactory’ defined in class path resource [bills/configuration/JpaConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean]: Factory method ‘entityManagerFactory’ threw exception; nested exception is java.lang.IllegalStateException: required key [datasource.sampleapp.hibernate.dialect] not found

    I get the same error when I try to run your code as is. I understand that this issue has been discussed before, but the solutions recommended there don’t work. I double checked the application.yml file for syntax errors, also tried changing the profiles, but same error always.
    Am I missing something here? Please help

    • Roberto Londei

      Me same!

      • websystique

        Roberto, the code shown here is a working app on Eclipse Mars with no known issue. As i asked above, please share a copy of your code which is not working for you [ a GITHUB e.g.]

        • Roberto Londei

          Issue is that on paragraph “Run the application –> Via Command line”
          the command seems to have just one minus sign for the schema, so if you type in:

          $> java -jar target/SpringBootCRUDApplicationExample-1.0.0.jar -spring.profiles.active=local

          you got a BeanCreationException and if you copy and paste the command as it is you also get an error because there is a strange sign before the minus sign.

          Obviously adding the default schema flag partially corrected the issue because it will newer start with no schema to load the bean from, but the correct solution here is to add two minus sign before the schema call while running the application, as follows:

          $> java -jar target/SpringBootCRUDApplicationExample-1.0.0.jar –spring.profiles.active=local

          or

          $> java -jar target/SpringBootCRUDApplicationExample-1.0.0.jar –spring.profiles.active=prod

          users that setup the application on eclipse didnt noticed that because eclipse automatically set the correct flag.

          Dear websystique, really thanks again for your work, i learned a lot from this post, it is always a pleasure to read updates from your site. ciao

          • websystique

            Hello Roberto, The command mentioned in the blog

            java -jar target/SpringBootCRUDApplicationExample-1.0.0.jar –spring.profiles.active=local

            indeed contains two ‘-’, compare the one before 1.0.0 with the one before ‘spring’. But i admit i should have been explicit in that. Thanks a ton for pointing it out.

    • websystique

      Hello Akash, this error was indeed comming due to absence of a default profile, which i then added.So you should not get this issue anymore. Now, if you are still getting the same issue, it could be related to application.yml. The YML files are too picky when it comes to spaces or any extra character.May i ask you to share your code somehow [through GITHUB e.g.]

    • websystique

      Hello Akash, look at the reply from Roberto, probably this was the issue you were facing.

  • Luis Troya

    Hi. I am new to Spring, when I run my app I got this exception

    org.springframework.beans.factory.BeanCreationException: Error creating bean with name ‘entityManagerFactory’

    any ideas? I have the same dependencies as you but I got this exception

    • websystique

      Hello Luis, Could you please share your app which is not working for you, maybe a github link? The download in this post is a working app, and i can not reproduce this issue.

    • websystique

      Hello Luis, look at the reply from Roberto above, probably this was the issue you were facing.

  • Marko Uljarevic

    I have a big problem, when i start in NetBeans project, i have a message
    Failed to execute goal org.codehaus.mojo:exec-maven-plugin:1.2.1:exec, and i did everything and google but i can’t resolve my problem.
    When i put project in Eclipse, and start i got message HTTP Status 404, and can’t start.

    Can someone help me, or go on Teamweaver and resolve my problem?

    Thanks a lot :)

    • websystique

      Hello Marko, did you additionally once try to build the proect [mvn clean package] on command line and see if you still have this issue?This example has been tested on Eclipse without getting such issues.

      • Marko Uljarevic

        Yes, I see you write this code on Eclipse, but i still have problem, and i don’t know how to resolve him :( I try everything and i can’t start project :( Can you come on TW and try to see where is problem?

        Thanks :)

        • websystique

          Hello Marko, did you have your project somewhere on Github? I would give a try on my Eclipse MARS.1 version. Please note that the download in this example is a working App with no known issue.

  • Umar Hussain

    Hello Websystique : in JpaConfiuration class the dataSource() Method keeps giving error

    Required:
    org.apache.tomcat.jdbc.pool.DataSource
    Found:
    com.zaxxer.hikari.HikariDataSource

    • websystique

      Hi Umar, We are using Hikari pools here, so make sure that Hikari Dependency is included in pom.xml. By default, Spring boot gives first priority to Tomcat pooling if it is present, else next it looks for HikariCP. If you don’t want to use HikariCP, then you need to configure your own datasource bean with respective properties.

  • Umar Hussain

    Hello websystique : when ever i try to donwload the source code for
    http://websystique.com/spring-boot/spring-boot-angularjs-spring-data-jpa-crud-app-example/

    windows defender doesnt allows it and keeps sedning popup for trojan virus in the downloaded file

    • websystique

      Hi Umar, It’s a false flag from Windows-defender. I use paid version of anti-virus on all my systems, and this is not the first time someone reported about windows-defender issue. May be its time to turn it off and look for other anti-virus solutions.

  • kishore

    Hello websystique:

    Runtime application.yml file is not recognizing ,Because of that for I did hard code the DB parameters.

    Any quick hint.

    • websystique

      The one from post should work as it is. If your application.yml has been changed, make sure it is valid. Yml’s are very picky about the syntax; sometimes even a single space here or there can create a problem. You can use the online yml validator i mentioned in the post above. You can also use some text editor like Notepad++ , enabling the ‘show white spaces’ to see if you have any special characters in your yml file.

  • Neha

    Hello,

    I am getting the Caused by: java.lang.IllegalStateException: required key [datasource.sampleapp.hibernate.dialect] not found error. What is the issue ?

    • websystique

      Hello Neha, This property is defined in application.yml. Please make sure that your application.yml [or application.properties] file contains this value.

      • Neha

        Yes, value already contains there. I’ve not changed anything. It looks to me that file is not loading. What you say ?

        • websystique

          Did you use one of the mentioned profile?

          • Neha

            yes

        • websystique

          Hello Neha, I’ve updated the post [application.yml] to additionally use default profile as well. This way while running form within IDE we don’t have to specify the profile, default would be used. But you are still free to specify a different profile as shown in the post. Hope it clarifies.

          • Neha

            Thank you so mjuch.

  • manjush b

    Hi websystique, How this example can be achieved , by using Maven Multimodule, I tried , but i couldn’t make it working

    • websystique

      Hi Manjush,

      For multi-module, your parent-pom can extend from spring-boot-starter-parent. Rest of the module-level [which has the parent as your parent-pom] setup won’t change at all. In case you don’t want to extend, you can use ‘import’ instead. Reference doc explains it very well.


      org.springframework.boot
      spring-boot-dependencies
      1.4.3.RELEASE
      pom
      import

  • Alonso Quijano

    HI websystique
    In first THANKS (from a dinosaur avoiding her extinction)
    2°..is possible (recomendable) to run thats examples in the STS?…any link? (sorry by my English)

    • websystique

      Hi Alonso, the example itself has been created on Eclipse, but there is no reason it would not work on STS. Please make sure that your setup is fine. Let me know if you face any issue.

  • Roberto Londei

    Hello websystique,

    thanks a lot, this is really cool!!
    Can you please update this project with a token based authentication system and maybe separate the front-end Angularjs on a project using nodejs and npm http-server.

    Thanks!!!

  • Pingback: Hibernate Many-To-Many Bidirectional (Annotation) - WebSystique()

  • Pingback: Spring Batch & Quartz Scheduler Example (Tasklet usage) - WebSystique()

  • Pingback: Spring Batch- MultiResourceItemReader & HibernateItemWriter example - WebSystique()

  • Pingback: Spring Batch- Read an XML file and write to MySQL Database - WebSystique()

  • Pingback: Spring Batch- Read an XML file and write to a CSV file - WebSystique()

  • Pingback: Spring Batch- Read a CSV file and write to an XML file - WebSystique()

  • Pingback: Spring Security 4 Custom Login Form Annotation+XML Example - WebSystique()

  • Pingback: Spring Security 4 Logout Example - WebSystique()

  • Pingback: Spring Security 4 Secure View Fragments using taglibs - WebSystique()

  • Pingback: Spring Security 4 Hibernate Integration Annotation+XML Example - WebSystique()

  • Pingback: Spring Security 4 Hibernate Password Encoder Bcrypt Example - WebSystique()

  • Pingback: Secure Spring REST API using Basic Authentication - WebSystique()

  • Pingback: Secure Spring REST API using OAuth2 - WebSystique()

  • Pingback: Spring 4 Email Integration Tutorial - WebSystique()

  • Pingback: Spring 4+JMS+ActiveMQ example with @JmsListener & @EnableJms - WebSystique()

  • Pingback: Spring Job Scheduling with @Scheduled & @EnableScheduling Annotations - WebSystique()

  • Pingback: Spring @PropertySource & @Value annotations example - WebSystique()

  • Pingback: Spring Auto-detection autowire & Component-scanning Example With Annotations - WebSystique()

  • Pingback: Spring Dependency Injection Example with Constructor and Property Setter (XML) - WebSystique()

  • Pingback: Spring 4 Hello World Example - WebSystique()

  • Pingback: Spring 4 Email using Velocity,Freemarker Template library - WebSystique()

  • Pingback: Spring 4 MVC+Apache Tiles 3 Example - WebSystique()

  • Pingback: Spring 4 MVC ContentNegotiatingViewResolver example - WebSystique()

  • Pingback: Spring 4 MVC Form Validation and Resource Handling (Annotations) - WebSystique()

  • Pingback: Spring 4 MVC+AngularJS CRUD Application using ngResource - WebSystique()

  • Pingback: Spring MVC 4 File Download Example - WebSystique()

  • Pingback: Spring 4 MVC + JMS + ActiveMQ annotation based Example - WebSystique()

  • Pingback: Spring 4 MVC HelloWorld Tutorial - Annotation/JavaConfig Example - WebSystique()

  • Pingback: Spring 4 MVC HelloWorld Tutorial - Full XML Example - WebSystique()