Skip to main content
查利博客

HTTP Request Methods

The most commonly-used HTTP request methods are GET, POST, PUT, DELETE, PATCH. They represent the read, create, create/replace, delete and update operations, respectively.

An HTTP method is safe if it is an read-only opeartion. For example, the GET method is safe because it never alters the state of the server.

An HTTP method is idempotent if an HTTP method can be made many times without any side-effects. For instance, the PUT method is idempotent because it replaces the same resource repeatedly.

Below is a table showing the saftey and idempotency of some HTTP method.

HTTP Method Safe Idempotent
GET Yes Yes
POST No No
PUT No Yes
DELETE No Yes
PATCH No No

Let's create a simple web application to demonstrate these five HTTP request methods. The code for this article is available over on GitHub.

Create a Spring Boot project #

First, we can use Spring Initializer to generate spring boot project quickly. The following dependecies are required:

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
	<groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
	<groupId>org.postgresql</groupId>
  <artifactId>postgresql</artifactId>
  <scope>runtime</scope>
</dependency>
<dependency>
	<groupId>org.modelmapper</groupId>
  <artifactId>modelmapper</artifactId>
  <version>2.4.4</version>
</dependency>

Set up PostgreSQL & Spring Data JPA #

Spring Boot automatically configures a DataSource for us when we add the postgresql dependency. Add the following properties to the application.properties file and change the url, username and password.

spring.datasource.url=jdbc:postgresql://localhost:5432/postgres
spring.datasource.username=caizhenhua
spring.datasource.password=123456

Add this property to the application.properties file to automatically create tables for us. Never use this property in production and you should always let DBA review your DDL scripts before deployment.

spring.jpa.hibernate.ddl-auto=update

Create a user entity #

import java.time.LocalDateTime;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.PrePersist;
import javax.persistence.Table;

@Entity
@Table(name = "t_user")
public class User {
  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  private Long id;

  private String username;
  private String password;
  private LocalDateTime createdDate;

  @PrePersist
  protected void onCreate() {
    createdDate = LocalDateTime.now();
  }

  public User() {
  }

  // getters and setters
}

Now run the spring boot project, you should see your table t_user is created in database postgres.

Create service and repository #

IUserService:

import java.util.Map;
import java.util.Optional;

public interface IUserService {
  Optional<User> getUserById(Long id);

  User saveUser(User user);

  User saveOrReplaceUserById(Long id, User newUser);

  void deleteUser(User user);

  User updateUser(Map<String, Object> updates, Long id);
}

UserService:

import java.util.Map;
import java.util.Optional;

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

@Service
public class UserService implements IUserService {
  @Autowired
  private UserRepository userRepository;

  @Override
  public Optional<User> getUserById(Long id) {
    return userRepository.findById(id);
  }

  @Override
  public User saveUser(User user) {
    return userRepository.save(user);
  }

  @Override
  public User saveOrReplaceUserById(Long id, User newUser) {
    return this.getUserById(id)
        .map(user -> {
          user.setUsername(newUser.getUsername());
          user.setPassword(newUser.getPassword());
          return this.saveUser(user);
        })
        .orElseGet(() -> {
          return this.saveUser(newUser);
        });
  }

  @Override
  public void deleteUser(User user) {
    userRepository.delete(user);
  }

  @Override
  public User updateUser(Map<String, Object> updates, Long id) {
    User user = this.getUserById(id).get();
    String username = (String) updates.get("username");
    String password = (String) updates.get("password");
    if (StringUtils.hasLength(username)) {
      user.setUsername(username);
    }
    if (StringUtils.hasLength(password)) {
      user.setPassword(password);
    }
    return this.saveUser(user);
  }
}

UserRepository:

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

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

}

Define the ModelMapper bean #

import org.modelmapper.ModelMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class Config {
  @Bean
  public ModelMapper modelMapper() {
    return new ModelMapper();
  }
}

Create a web controller #

UserController:

import java.util.Map;

import org.modelmapper.ModelMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping;
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;

@RestController
@RequestMapping("/user")
public class UserController {
  @Autowired
  private UserService userService;

  @Autowired
  private ModelMapper modelMapper;

  // GET REQUEST
  @GetMapping("/{id}")
  public UserDTO getUser(@PathVariable("id") Long id) {
    return convertToDTO(userService.getUserById(id).get());
  }

  // POST REQUEST
  @PostMapping("")
  public UserDTO createUser(@RequestBody UserDTO userDTO) {
    User user = convertToEntity(userDTO);
    return convertToDTO(userService.saveUser(user));
  }
  
  // PUT REQUEST
  @PutMapping("{id}")
  public UserDTO createOrReplaceUser(@RequestBody UserDTO userDTO, @PathVariable("id") Long id) {
    return convertToDTO(userService.saveOrReplaceUserById(id, convertToEntity(userDTO)));
  }

  // DELETE REQUEST
  @DeleteMapping("{id}")
  public void deleteUser(@PathVariable("id") Long id) {
    userService.deleteUser(userService.getUserById(id).get());
  }

  // PATCH REQUEST
  @PatchMapping("{id}")
  public UserDTO updateUser(@RequestBody Map<String, Object> updates, @PathVariable("id") Long id) {
    return convertToDTO(userService.updateUser(updates, id));
  }

  private UserDTO convertToDTO(User user) {
    return modelMapper.map(user, UserDTO.class);
  }

  private User convertToEntity(UserDTO userDTO) {
    return modelMapper.map(userDTO, User.class);
  }
}

Test with Spring MockMvc #

Insert a new record into t_user table:

INSERT INTO t_user (id, created_date, username, password) VALUES (1, CURRENT_TIMESTAMP, 'caizhenhua', '123');

UserControllerTest:

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;

import com.fasterxml.jackson.databind.ObjectMapper;

@SpringBootTest
@AutoConfigureMockMvc
public class UserControllerTest {
  @Autowired
  private MockMvc mockMvc;

  @Test
  public void getUserByIdAPI() throws Exception {
    this.mockMvc.perform(get("/user/1"))
      .andDo(print())
      .andExpect(status().isOk())
      .andExpect(content().json("{'username': 'caizhenhua', 'password': '123'}"));
  }

  @Test
  public void createUserAPI() throws Exception {
    UserDTO userDTO = new UserDTO();
    userDTO.setUsername("admin");
    userDTO.setPassword("123");

    ObjectMapper objectMapper = new ObjectMapper();
    String json = objectMapper.writeValueAsString(userDTO);

    this.mockMvc.perform(post("/user")
      .contentType(MediaType.APPLICATION_JSON)
      .content(json))
      .andDo(print())
      .andExpect(status().isOk())
      .andExpect(content().json("{'username': 'admin', 'password': '123'}"));
  }

  @Test
  public void createOrReplaceUserAPI() throws Exception {
    UserDTO userDTO = new UserDTO();
    userDTO.setUsername("test");
    userDTO.setPassword("456");

    ObjectMapper objectMapper = new ObjectMapper();
    String json = objectMapper.writeValueAsString(userDTO);

    this.mockMvc.perform(put("/user/2")
      .contentType(MediaType.APPLICATION_JSON)
      .content(json))
      .andDo(print())
      .andExpect(status().isOk())
      .andExpect(content().json("{'username': 'test', 'password': '456'}"));  
  }

  @Test
  public void deleteUserAPI() throws Exception {
    this.mockMvc.perform(delete("/user/3"))
      .andDo(print())
      .andExpect(status().isOk());
  }

  @Test
  public void updateUserAPI() throws Exception {
    UserDTO userDTO = new UserDTO();
    userDTO.setUsername("test");

    ObjectMapper objectMapper = new ObjectMapper();
    String json = objectMapper.writeValueAsString(userDTO);

    this.mockMvc.perform(patch("/user/5")
      .contentType(MediaType.APPLICATION_JSON)
      .content(json))
      .andDo(print())
      .andExpect(status().isOk())
      .andExpect(content().json("{'username': 'test', 'password': '123'}"));  
  }
}