Java Spring Boot Rest

RestController

Design Pattern - Service Layer Pattern

--/your/app/
 |-- configuration/ 
   |-- SecurityConfiguration.java
 |-- controller/ 
   |-- EntitĂ Controller.java  
 |-- entity/ 
   |-- EntitĂ .java
 |-- repository/
   |-- EntitĂ Repository.java
 |-- service/
   |-- EntitĂ Service.java

Alcune annotazioni da ricordare:

  • @RestController
  • @GetMapping("/{name}/delete")
  • Name nel punto precedente si ottiene specificando nell’input del metodo @PathVariable
  • Supponendo di avere @GetMapping("/add"), che richiede l’attributo “name”, nell’input del metodo della classe andrò ad aggiungere: @RequestParam("name") String name
  • @ExceptionHandler({ IndexOutOfBoundsException.class, EmptyResultDataAccessException.class }) - Per gestire le eccezioni

Esempio completo di RestController

Fonte: đź”— Github - krsh - insecuresite

Say i have this entity made with Hibernate (JPA)

package xyz.krsh.insecuresite.rest.dao;
 
import java.util.HashSet;
import java.util.Set;
 
import javax.persistence.*;
 
/* 
    This class represents a boardgame.
    A board 
    - must have a name
    - could have a price, a quantity and a description
*/
 
@Entity
@Table(name = "Boardgame")
public class Boardgame {
 
    @Id
    private String name = null;
 
    @Column
    private float price = 0.0f;
 
    @Column
    private int quantity = 0;
 
    @Column
    private String description = null;
 
    @ManyToMany
    private Set<Order> orders = new HashSet<>();
 
    public Boardgame() { // Required by JPA
 
    }
 
    public Boardgame(String name) {
        this.name = "";
        if (name != null) {
            this.name = name;
        }
        this.description = "";
    }
 
    public Boardgame(String name, float price, int quantity, String description) {
        this.name = name;
        this.price = price;
        this.quantity = quantity;
        this.description = description;
    }
 
    public String getId() {
        return this.getName();
    }
 
    public String getName() {
        return name;
    }
 
    public float getPrice() {
        return price;
    }
 
    public int getQuantity() {
        return quantity;
    }
 
    public String getDescription() {
        return description;
    }
 
    public Set<Order> getOrders() {
        return orders;
    }
 
    public void setPrice(float p) {
        this.price = p;
    }
 
    public void setQuantity(int q) {
        this.quantity = q;
    }
 
    public void setDescription(String desc) {
        this.description = desc;
    }
 
    public String toString() {
        return "Boardgame{" + "name(id)=" + name +
                ", price: " + price +
                ", quantity: " + quantity +
                ", description: " + description +
                "}  ";
    }
 
}

This is the repository

 
/*
 * Implements the Repository pattern for Boardgame by extending the CrudRepository
 */
@Repository
public interface BoardgameRepository extends CrudRepository<Boardgame, String> {
    List<Boardgame> findByNameContaining(String name);
 
    List<Boardgame> findAll();
 
}

This is the controller

@RestController
@RequestMapping("/api/boardgames")
public class BoardgameController {
 
    @Autowired
    BoardgameService boardgameService;
 
    /*
     * Returns every boardgame or the ones that match the query value
     */
    @GetMapping
    @ResponseBody
    public List<Boardgame> find(@RequestParam(name = "q", defaultValue = "") String queryTerm)
            throws ItemNotFoundException {
        return boardgameService.findByNameContaining(queryTerm);
    }
 
    /*
     * Get an existing boardgame querying using his name (primary key)
     * Returns only the first result
     */
    @GetMapping("/{name}")
    @ResponseBody
    Boardgame getById(@PathVariable String name) throws IndexOutOfBoundsException {
        return boardgameService.getById(name);
    }
 
    /*
     * Add a new boardgame to the database by REST call
     * Request parameters are: name, price, quantity and description
     */
    @GetMapping("/add")
    @ResponseBody
    public Boardgame addBoardgameReq(@RequestParam("name") String name,
            @RequestParam("price") float price,
            @RequestParam("quantity") int quantity,
            @RequestParam("description") String description,
            HttpServletRequest request) throws MissingServletRequestParameterException {
 
        return boardgameService.addBoardgame(name, price, quantity, description);
    }
 
    /*
     * Edit an existing boardgame by specifying his name and optional parameters
     * that will replace the older ones
     * Return the Boardgame with newest values
     */
 
    @GetMapping(value = "/{name}/edit")
    @ResponseBody
    public Boardgame ediBoardgame(@PathVariable String name,
            @RequestParam(value = "price", required = false) Float price,
            @RequestParam(value = "quantity", required = false) Integer quantity,
            @RequestParam(value = "description", required = false) String description,
            HttpServletRequest request) throws ItemNotFoundException {
 
        return boardgameService.editBoardgame(name, price, quantity, description, request);
    }
 
    /*
     * Delete a Boardgame by specifing his name (id)
     * Return a success message
     * 
     */
    @GetMapping("/{name}/delete")
    public String deleteBoardgame(@PathVariable String name) throws EmptyResultDataAccessException {
        return boardgameService.deleteBoardgame(name);
    }
 
    /*
     * Exception Handlers
     */
 
    // Occurrs when you can't find any boardgames
    @ExceptionHandler({ ItemNotFoundException.class, IndexOutOfBoundsException.class,
            EmptyResultDataAccessException.class })
    public ApiError handleIndexOutOfBoundsException() {
        return new ApiError("Bordgame not found, retry", HttpStatus.NOT_FOUND);
 
    }
 
    @ExceptionHandler({ MissingServletRequestParameterException.class })
    public ApiError handleMissingParametersException() {
        return new ApiError("Bad Parameters: required name, price, quantity and description", HttpStatus.BAD_REQUEST);
 
    }
 
}

Logic is managed by a Service:

@Service
public class BoardgameService {
 
    @Autowired
    BoardgameRepository boardgameRepository;
 
    @Autowired
    OrderedBoardgamesRepository orderedBoardgameRepository;
 
    public List<Boardgame> findByNameContaining(String queryTerm) throws ItemNotFoundException {
        List<Boardgame> queryResult = boardgameRepository.findByNameContaining(queryTerm);
 
        if (queryResult.isEmpty()) {
            throw new ItemNotFoundException();
        }
 
        return queryResult;
 
    }
 
    public Boardgame getById(String name) {
        Boardgame boardgame = boardgameRepository.findByNameContaining(name).get(0);
        return boardgame;
    }
 
    public Boardgame addBoardgame(String name, float price, int quantity, String description) {
        Boardgame newBoardgame = new Boardgame(name, price, quantity, description);
        boardgameRepository.save(newBoardgame);
        return newBoardgame;
 
    }
 
    public Boardgame editBoardgame(String name, Float price, Integer quantity, String description,
            HttpServletRequest request)
            throws ItemNotFoundException {
        Boardgame boardgame;
 
        List<Boardgame> queryResult = this.findByNameContaining(name);
        if (queryResult.size() == 0 || queryResult.isEmpty() == true) {
            throw new IndexOutOfBoundsException();
        } else {
            boardgame = queryResult.get(0);
        }
 
        // Check existance of params
        boolean priceParamExists = request.getParameterMap().containsKey("price");
        boolean quantityParamExists = request.getParameterMap().containsKey("quantity");
        boolean descriptionParamExists = request.getParameterMap().containsKey("description");
 
        // if price exists as parameter in the HTTP request, change the price
        if (priceParamExists) {
            boardgame.setPrice(price);
        }
        if (quantityParamExists) {
            boardgame.setQuantity(quantity);
        }
        if (descriptionParamExists) {
            boardgame.setDescription(description);
        }
 
        // update the boardgames with new values
        boardgameRepository.update(boardgame);
        return boardgame;
    }
 
    public String deleteBoardgame(String name) {
        Optional<List<OrderedBoardgames>> obQueryResult = orderedBoardgameRepository.findByBoardgameName(name);
        Optional<Boardgame> bQueryResult = boardgameRepository.findById(name);
 
        if (obQueryResult.isPresent() && bQueryResult.isPresent()) {
            List<OrderedBoardgames> list = obQueryResult.get();
            for (OrderedBoardgames ob : list) {
                orderedBoardgameRepository.delete(ob);
            }
 
            Boardgame boardgame = bQueryResult.get();
            boardgameRepository.delete(boardgame);
        }
 
        return "Successfully deleted " + name + " ";
 
    }
 
}

Optional parameters

âť“ How to deal with optional request parameters in Spring Boot?

Consider this code snippet:

@RequestMapping("/searchBar")
public String searchBar(@RequestParam(name="q") String q){
	return repository.findAll();
}

There are three options:

  1. Uso di optional
public String searchBar(@RequestParam(name="q") Optional<String> q){
	if (q.isPresent()){
	} //OK
}
  1. Uso di @RequestParam(name="q", defaultValue="")
  2. Uso di @RequestParam(name="q", required= false)

Example code to avoid:

if (q == null){
  return repository.findByNameContaining(q);
}

Nested @Service

Say you have OneService and TwoService, OneService calls TwoService, both have different repository from different databases.

One hack i find that works is to add both in the controller with the @Autowired like:

@Controller
public class myController {
	@Autowired
	OneService oneService;
	@Autowired
	TwoService twoService;
	//...
}