stepsToBuildSpringbootRESTAPI

  • Using http://start.spring.io to initialize: web, jpa, mysql, devtools for basic dependency

  • Configure application.properties file

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    ## Spring DATASOURCE (DataSourceAutoConfiguration & DataSourceProperties)
    spring.datasource.url = jdbc:mysql://localhost:3306/notes_app?useSSL=false
    spring.datasource.username = root
    spring.datasource.password = root


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

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

The last two properties are for hibernate. Spring Boot uses Hibernate as the default JPA implementation.

The property spring.jpa.hibernate.ddl-auto is used for database initialization. I’ve used the value “update” for this property.

It does two things -

  • When you define a domain model, a table will automatically be created in the database and the fields of the domain model will be mapped to the corresponding columns in the table.

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

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

  • Set up data model mapping to table
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package com.example.easynotes.model;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import javax.persistence.*;
import javax.validation.constraints.NotBlank;
import java.util.Date;

@Entity
@Table(name = "notes")
@EntityListeners(AuditingEntityListener.class)
@JsonIgnoreProperties(value = {"createdAt", "updatedAt"},
allowGetters = true)
public class Note implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@NotBlank
private String title;

@NotBlank
private String content;

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

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

// Getters and Setters ... (Omitted for brevity)
}

annotated createdAt and updatedAt fields with @CreatedDate and @LastModifiedDate annotations respectively.

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

To achieve this, we need to do two things -

  1. Add Spring Data JPA’s AuditingEntityListener to the domain model.

    1
    @EntityListeners(AuditingEntityListener.class)
  2. Enable JPA Auditing in the main application.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.example.easynotes;

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

@SpringBootApplication
@EnableJpaAuditing
public class EasyNotesApplication {

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

Creating NoteRepository to access data from the database

  1. Spring Data JPA with paRepository interface with methods for CRUD operations, First, Create a new package called repository inside the base package com.example.easynotes. Then, create an interface called NoteRepository and extend it from JpaRepository.
    This is different from nodejs or golang. but why we need this interface. apparently it give us the method, which manipulate the java object in a collection. in mongodb, it is the schema!
1
2
3
4
5
6
7
8
9
package com.example.easynotes.repository;

import com.example.easynotes.model.Note;
import org.springframework.data.jpa.repository.JpaRepository;

@Repository
public interface NoteRepository extends JpaRepository<Note, Long> {

}

Spring Data JPA has a bunch of other interesting features like Query methods (dynamically creating queries based on method names), Criteria API, Specifications, QueryDsl etc.
strongly recommend to checkout the Spring Data JPA’s documentation to learn more.

Creating Custom Business Exception

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

Following is the definition of ResourceNotFoundException. (I’ve created a package named exception inside com.example.easynotes to store this exception class) -

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

package com.example.easynotes.exception;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;

@ResponseStatus(value = HttpStatus.NOT_FOUND)
public class ResourceNotFoundException extends RuntimeException {
private String resourceName;
private String fieldName;
private Object fieldValue;

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

public String getResourceName() {
return resourceName;
}

public String getFieldName() {
return fieldName;
}

public Object getFieldValue() {
return fieldValue;
}
}

Notice the use of @ResponseStatus annotation in the above exception class. This will cause Spring boot to respond with the specified HTTP status code whenever this exception is thrown from your controller.

Creating NoteController

This is final step as router will wrap inside and mapping with the controller.
which is almost the same as nodejs rest api or golang api. but using anotation.

First, create a new package controller inside com.example.easynotes. Then, create a new class NoteController.java with the following contents.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package com.example.easynotes.controller;

import com.example.easynotes.exception.ResourceNotFoundException;
import com.example.easynotes.model.Note;
import com.example.easynotes.repository.NoteRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.List;

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

@Autowired
NoteRepository noteRepository;

// Get All Notes

// Create a new Note

// Get a Single Note

// Update a Note

// Delete a Note
}

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

The @Controller annotation is used to define a controller and the @ResponseBody annotation is used to indicate that the return value of a method should be used as the response body of the request.

@RequestMapping(“/api”) declares that the url for all the apis in this controller will start with /api.

Let’s now look at the implementation of all the apis one by one.

1. Get All Notes (GET /api/notes)

1
2
3
4
5
// Get All Notes
@GetMapping("/notes")
public List<Note> getAllNotes() {
return noteRepository.findAll();
}

The above method is pretty straightforward. It calls JpaRepository’s findAll() method to retrieve all the notes from the database and returns the entire list.

Also, The @GetMapping(“/notes”) annotation is a short form of @RequestMapping(value=”/notes”, method=RequestMethod.GET).

2. Create a new Note (POST /api/notes)

1
2
3
4
@PostMapping("/notes")
public Note createNote(@Valid @RequestBody Note note){
return nodeRepository.save(note);
}

The @RequestBody annotation is used to bind the request body with a method parameter.
The @Valid annotation makes sure that the request body is valid. Remember, we had marked Note’s title and content with @NotBlank annotation in the Note model?
so if the req body does not have a title or content, then spring will return a 400 badrequest error to client.

3. get a single Note(get/api/note/{id})

@GetMapping(“/notes/{id}”)
public Note getNoteById(@PathVarible(value = “id”) Long nodeId){
return noteRespository.findById(noteId).
orElseThrow(() -> new ResourceNotFoundException(“Note”, “id”, noteId));
}

The @PathVariable annotation, as the name suggests, is used to bind a path variable with a method parameter.

we are throwing a ResourceNotFoundException whenever a Note with the given id is not found in the above method.

This will cause Spring Boot to return a 404 Not Found error to the client (Remember, we had added a @ResponseStatus(value = HttpStatus.NOT_FOUND) annotation to the ResourceNotFoundException class).

4. Update a item(PUT /api/notes/{id})

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Update a Note
@PutMapping("/notes/{id}")
public Note updateNote(@PathVariable(value = "id") Long noteId,
@Valid @RequestBody Note noteDetails) {

Note note = noteRepository.findById(noteId)
.orElseThrow(() -> new ResourceNotFoundException("Note", "id", noteId));

note.setTitle(noteDetails.getTitle());
note.setContent(noteDetails.getContent());

Note updatedNote = noteRepository.save(note);
return updatedNote;
}

5. Delete a Note (DELETE /api/notes/{noteId})

1
2
3
4
5
6
7
8
@DeleteMapping("notes/{id}")
public Repository<?> deleteNote(@PathVariable(value = "id") Long noteId){
Note note noteRepository.findById(noteId)
.orElseThrow(() -> new ResourceNotFoundException("Note", "id", noteId));
noteRepository.delete(note);

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

Running the app

Command:

1
$ mvn spring-boot:run

Test the api with postman, which is same with golang as well as nodejs and python.

Still, having query about tranction! what if the sql operation involve in transaction
and need to rollback! that will be next….

reference link