Spring Boot JPA + MySQL (2024)
(Spring Boot + JPA + REST + MYSQL + Lombok + Swagger + Junit Test Case)
In this tutorial, will show you how to develop Spring Boot Rest Crud Services using JPA, MYSQL database, with junit test cases.
We used swagger to test REST services and lombok library, which will reduce our boilerplate JAVA code like : setter/getter/constructors/lombak log4j, it'll give a way to use annotation.
Technologies Used:
- Java 1.8.x
- Spring Boot
- Maven
- Mysql
- lombok Click Here to install lombok
- springfox-swagger2
Note : The full source code for Spring Boot Complete CRUD example can be downloaded at the end of this article.
Unzip the file and import into Eclipse as "Existing Maven Project" by providing unzipped project (pom.xml) path.
Now let's get started with creating the application.
Project Directory
Maven
Below dependencies would be required to build the application.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.notes.app</groupId>
<artifactId>user-notes</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<name>NoteApp</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.4.RELEASE</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<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>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.18</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.6.1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.6.1</version>
<scope>compile</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Let's create below model classes for our Notes application.
- User: It contains user specific fields like user id , password and email.
It extend Auditable class, to add auditing details like user creation and updation time.
package com.notes.model;
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name = "users")
@JsonPropertyOrder({"userMail", "userPass"})
@JsonIgnoreProperties(value = {"userId"}, allowGetters = true)
public class User extends Auditable<String> implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "user_Id")
@JsonIgnore
private Long userId;
@NotNull
@Email
@Size(max = 100)
@Pattern(regexp=".+@.+\\..+", message="Wrong email!")
@Column(name = "user_mail", unique = true)
@JsonProperty("Mail Id")
private String userMail;
@NotNull
@Size(min=8, message="Password should have atleast 8 characters")
@Column(name = "user_pass")
@JsonProperty("Password")
private String userPass;
}
It also extend NoteBase class, which provide remaining details like notes title.
It also extend Auditable class, to add auditing details like notes created and update time.
package com.notes.model;
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.swagger.annotations.ApiParam;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name="Notes")
public class Note extends NoteBase implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
@ApiParam(hidden = true)
@Column(name="note_Id")
private Long noteId;
/*In case of we need to maintain many notes to one User relation through User Entity*/
@JsonIgnore
@ManyToOne
@JoinColumn(name="user_id")
private User userDetails;
@Builder
public Note(String title, String message, User userDetails) {
super(title, message);
this.userDetails = userDetails;
}
}
updating/deleting the notes
It contains title and message field.
package com.notes.model;
import javax.persistence.MappedSuperclass;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.Size;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import com.notes.util.ApplicationConstants;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
@JsonPropertyOrder({ "title", "noteMsg" })
@MappedSuperclass
public class NoteBase extends Auditable<String> {
@NotEmpty(message = ApplicationConstants.VALIDATION_TITLE_EMPTY)
@NotBlank(message = ApplicationConstants.VALIDATION_TITLE_EMPTY)
@Size(max = 50)
private String title;
@Size(max = 1000)
private String message;
}
Spring JPA Auditing
We have used jpa AuditingEntityListener, to capture auditing information on persisting and updating entities.
To enable this feature, add @EnableJpaAuditing
annotation in main class
(NotesApplication) at the top , and you are done , you can use this without writing boilerplate code in every class to track/update time,
just extend this Auditable class wherever you want to capture any auditing information for any entity.
package com.notes.model;
import static javax.persistence.TemporalType.TIMESTAMP;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.EntityListeners;
import javax.persistence.MappedSuperclass;
import javax.persistence.Temporal;
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.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
@Data
@EqualsAndHashCode
@ToString
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
@JsonIgnoreProperties(value = {"creationDate", "lastModifiedDate"}, allowGetters = true)
public abstract class Auditable<U> {
@Column(name = "created_date", updatable = false)
@Temporal(TIMESTAMP)
@JsonIgnore
@CreatedDate
protected Date creationDate;
@Column(name = "lastMod_date")
@LastModifiedDate
@Temporal(TIMESTAMP)
@JsonIgnore
protected Date lastModifiedDate;
}
- NotesController : Let's create th end point as given in the below class. You can see we have
used swagger api at the
top of every end point method to document or show that information in viewable format.
To enable Swagger, you can see we have added it's dependency in pom.xml and created configuration class
(SwaggerConfig) in this project
package com.notes.controller;
import java.util.List;
import javax.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.Errors;
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.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.notes.exception.NotesException;
import com.notes.exception.NotesExceptionHandler;
import com.notes.model.Note;
import com.notes.model.NoteBase;
import com.notes.service.NotesService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiModelProperty;
import io.swagger.annotations.ApiOperation;
import lombok.extern.log4j.Log4j2;
@RestController
@RequestMapping("/api")
@Api("Notes Crud App")
@Log4j2
public class NotesController extends NotesExceptionHandler {
@Autowired
private NotesService notesService;
/**
* Method to add note Validate Note Request object format/values in case of
* null/empty
*
* @param userId
* @param note
* @param errors
* @return ResponseEntity
*/
@ApiOperation(value = "Add New Note")
@PostMapping("/notes/{userId}")
@ApiModelProperty(hidden = true)
public ResponseEntity<String> addNote(@PathVariable(value = "userId") Long userId,
@Valid @RequestBody NoteBase note, Errors errors) {
log.info("Inside Add Note controller.");
userIdNullCheck(userId);
validateRequest(errors);
String response = notesService.addNote(userId, note);
log.info("Response of Add Note : " + response);
return new ResponseEntity<>(response, HttpStatus.OK);
}
/**
* Method to get all Notes details
*
* @param userId
* @return ResponseEntity
*/
@ApiOperation(value = "View list of Notes by User Id")
@GetMapping("/notes/{userId}")
public ResponseEntity<List<Note>> getAllNotesByUser(@PathVariable(value = "userId") Long userId) {
log.info("Inside getAllNotesByUser controller.");
userIdNullCheck(userId);
List<Note> notes = notesService.getAllNotesByUserId(userId);
log.info("Response of getAllNotesByUser : " + notes);
return new ResponseEntity<>(notes, HttpStatus.OK);
}
/**
* Method to delete Note
*
* @param userId
* @param noteId
* @return ResponseEntity
*/
@ApiOperation(value = "Delete Note")
@DeleteMapping("/notes/{userId}/{noteId}")
public ResponseEntity<String> deleteNote(@PathVariable(value = "userId") Long userId,
@PathVariable(value = "noteId") Long noteId) {
log.info("Inside deleteNote controller.");
userIdNullCheck(userId);
String response = notesService.deleteNote(userId, noteId);
log.info("Response of deleteNote : " + response);
return new ResponseEntity<>(response, HttpStatus.OK);
}
/**
* Method to update employee details
*
* @param employee
* @param id
* @param errors
* @return ResponseEntity
*/
@ApiOperation(value = "Update Note")
@PutMapping("/notes/{userId}/{noteId}")
public ResponseEntity<Note> updateNote(@PathVariable(value = "userId") Long userId,
@PathVariable(value = "noteId") Long noteId, @Valid @RequestBody NoteBase note, Errors errors) {
validateRequest(errors);
log.info("Inside controller:: updateNote.");
Note updatedNote = notesService.updateNote(userId, noteId, note);
log.info("Response of updateNote : " + updatedNote);
return new ResponseEntity<>(updatedNote, HttpStatus.OK);
}
/**
* @param userId
*/
private void userIdNullCheck(Long userId) {
if (userId == null)
throw new NotesException("Provide user Id");
}
/**
* Validate Request
*
* @param errors
*/
private void validateRequest(Errors errors) {
if (errors.hasErrors()) {
errors.getAllErrors().stream().forEach(errorMsg -> {
log.debug("Error Message : " + errorMsg);
throw new NotesException(errorMsg.getDefaultMessage());
});
}
}
}
Service Layer: Let's expose the services. Using NoteService Interface and It's implementation
called NotesServiceImpl.
package com.notes.service;
import java.util.List;
import javax.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.notes.exception.NotesException;
import com.notes.model.Note;
import com.notes.model.NoteBase;
import com.notes.model.User;
import com.notes.repository.NotesRepository;
import com.notes.repository.UserRepository;
import com.notes.util.ApplicationConstants;
import lombok.extern.log4j.Log4j2;
/**
*
* Service Implementation class for note crud operations.
*/
@Service
@Log4j2
public class NotesServiceImpl implements NotesService {
@Autowired
private NotesRepository noteRepository;
@Autowired
private UserRepository userRepository;
/**
* Get all notes associated by user id. check for valid user.
*
* @param userId
* @return object
*/
@Override
public List<Note> getAllNotesByUserId(Long userId) {
log.info("Inside get all notes by user id service");
validateUser(userId);
return noteRepository.findNotesByUserId(userId);
}
/**
* Adding note by validating user id
*
* @param note
* @param userId
* @return
*/
@Override
public String addNote(Long userId, NoteBase note) {
log.info("Inside add note service");
User user = validateUser(userId);
return saveUser(user, note);
}
@Override
public Note updateNote(Long userId, Long noteId, @Valid NoteBase note) {
Note dbNote = validateNoteByUser(userId, noteId);
dbNote.setTitle(note.getTitle());
dbNote.setMessage(note.getMessage());
return noteRepository.save(dbNote);
}
/**
* Delete Note
*
* @param noteId
* @return
*/
public String deleteNote(Long userId, Long noteId) {
Note dbNote = validateNoteByUser(userId, noteId);
noteRepository.deleteById(noteId);
return String.format(ApplicationConstants.DELETE_ID, noteId);
}
/**
* Method to save Note
*
* @param user
* @param note
* @return
*/
private String saveUser(User user, NoteBase note) {
Note dbNote = Note.builder().title(note.getTitle()).message(note.getMessage()).userDetails(user).build();
noteRepository.save(dbNote);
log.info("Successfully added new Note.");
return ApplicationConstants.ADDED_NOTE_DESC;
}
private User validateUser(Long userId) {
return userRepository.findById(userId)
.orElseThrow(() -> new NotesException(ApplicationConstants.VALIDATION_USER_ID));
}
private Note validateNoteByUser(Long userId, Long noteId) {
return noteRepository.findByNoteIdAndUserId(noteId, userId)
.orElseThrow(() -> new NotesException(ApplicationConstants.NOTE_ID_NOTEXIST));
}
}
JPA Repository (Data Layer) :This repository allows us to access the information stored in the database by using JPA.
We have used JpaRepository and CrudRepository.
CrudRepository mainly provides CRUD functions while JpaRepository provide some JPA related methods such as flushing the persistent context, deleting records in a batch.
- UserRepository : JPA repository to perform users operations
package com.notes.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import com.notes.model.User;
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
}
package com.notes.repository;
import java.util.List;
import java.util.Optional;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.query.Param;
import com.notes.model.Note;
public interface NotesRepository extends CrudRepository<Note, Long>{
@Query("select note from Note note where note.userDetails.userId = :userId")
List<Note> findNotesByUserId(@Param("userId") Long userId);
@Query("select note from Note note where note.userDetails.userId = :userId and note.noteId = :noteId")
Optional<Note> findByNoteIdAndUserId(@Param("noteId") Long noteId, @Param("userId") Long userId);
}
Configuration Files
- SwaggerConfig : Class to manage swagger configuration
package com.notes.config;
import static springfox.documentation.builders.PathSelectors.regex;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
@Configuration
@EnableSwagger2
@EnableWebSecurity
@ComponentScan("com.notes.controller.*")
public class SwaggerConfig {
/**
* Method to set paths to be included through swagger
*
* @return Docket
*/
@Bean
public Docket empConfigApi() {
return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo()).pathMapping("/").select()
.paths(regex("/api.*")).build();
}
/**
* Method to set swagger info
*
* @return ApiInfoBuilder
*/
private ApiInfo apiInfo() {
return new ApiInfoBuilder().title("").description("").version("1.0").build();
}
}
package com.notes.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.AuthenticationEntryPoint;
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private AuthenticationEntryPoint authEntryPoint;
@Value("")
private String authUser;
@Value("")
private String authPassword;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().authorizeRequests()
.anyRequest().authenticated()
.and().httpBasic()
.authenticationEntryPoint(authEntryPoint);
}
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser(authUser).password("{noop}"+authPassword).roles("USER");
}
}
package com.notes.config;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint;
import org.springframework.stereotype.Component;
@Component
public class AuthenticationEntryPoint extends BasicAuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authEx)
throws IOException, ServletException {
response.addHeader("WWW-Authenticate", "Basic realm=" +getRealmName());
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
PrintWriter writer = response.getWriter();
writer.println("HTTP Status 401 - " + authEx.getMessage());
}
@Override
public void afterPropertiesSet() throws Exception {
setRealmName("notes");
super.afterPropertiesSet();
}
}
Custom Exceptions:We have created our custom Exeception.
- NotesException : Creating Runtime exception for note application
package com.notes.exception;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(HttpStatus.BAD_REQUEST)
public class NotesException extends RuntimeException {
public NotesException(String exception) {
super(exception);
}
}
package com.notes.exception;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.WebRequest;
import com.notes.exception.NotesException;
import lombok.extern.log4j.Log4j2;
@Log4j2
public class NotesExceptionHandler {
public NotesExceptionHandler() {
super();
}
/**
* Method to return Notes specific exception.
*
* @param ex
* @param request
* @return ResponseEntity
*/
@ExceptionHandler(NotesException.class)
private final ResponseEntity<Object> handleUserNotFoundException(NotesException ex, WebRequest request) {
log.error(ex.getMessage());
return new ResponseEntity<>(ex.getMessage(), HttpStatus.BAD_REQUEST);
}
/**
* Generic Exception
*
* @param ex
* @param request
* @return ResponseEntity
*/
@ExceptionHandler(Exception.class)
private final ResponseEntity<Object> handleException(Exception ex, WebRequest request) {
log.error(ex.getMessage());
return new ResponseEntity<>(ex.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
}
}
@SpringBootApplication
annotation and it enable auding feature ny adding
@EnableJpaAuditing
annotation.
NOTE: We have added default user in main class.
package com.notes;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
import com.notes.model.Note;
import com.notes.model.User;
import com.notes.repository.NotesRepository;
import com.notes.repository.UserRepository;
@SpringBootApplication
@EnableJpaAuditing
public class NotesApplication implements CommandLineRunner {
@Autowired
private UserRepository userRepo;
@Autowired
private NotesRepository noteRepo;
public static void main(String[] args) {
SpringApplication.run(NotesApplication.class, args);
}
@Override
public void run(String... arg0) throws Exception {
User user = new User();
user.setUserMail("techgeeknext@gmail.com");
user.setUserPass("****");
userRepo.save(user);
}
}
Running the Application:
Run the Spring Boot Notes application by using IDE Run option or by using below command
mvn spring-boot:run
Testing the CRUD operaion using Swagger
The app will start running at
http://localhost:8080
The app use basic authentication, user need to enter below credential to access the api
User name: root
Password:root
Note : Once user is authenticated browser will not ask for password again.
Application defines following CRUD APIs/Operations. You can go from here or performs single operation.
Goto this url
http://localhost:8080/swagger-ui.html
Add Note: url :
http://localhost:8080/swagger-ui.html#!/notes45controller/addNoteUsingPOST
Request
Response
Get Note: url :
http://localhost:8080/swagger-ui.html#!/notes45controller/getAllNotesByUserUsingGET
Request/Response
Update Note: url :
http://localhost:8080/swagger-ui.html#!/notes45controller/updateNoteUsingPUT
Request/Response
Delete Note: url :
http://localhost:8080/swagger-ui.html#!/notes45controller/deleteNoteUsingDELETE
Request/Response
Download Source Code
The full source code for this article can be found on below.Download it here - Spring Boot Complete CRUD example