REST API with Spring guide

Simple guide to help you on your web-dev journey

View the Project on GitHub KeaCluster/bookstoreAPI-spring-guide

Part 4 API Development

This section will focus on the code-aspect of our API. Take note that due to the considerable size of the project, we won’t be able to write every single line of code here. Most of the entities will be left out. But necessary annotations and concepts are definitely going to be shown here.

Take this as what we are really doing here: A guide. So practice, research, and put your knowledge and understanding to the test.

Models

Models are the OOP representation of the relational entities inside the database. This includes: foreign keys, attributes and primary keys, alongside some extra things we could come across.

So how exactly do we translate all of this to a java class? Very simple:

Book


// imports
@Entity
@Table(name = "books")
public class Book {
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  @Column(name = "book_id")
  private int id;

  @Column(name = "name", nullable = false, length = 150)
  private String name;

  @Column(name = "author", nullable = false, length = 150)
  private String author;

  @Column(name = "stock", nullable = false)
  private int stock;

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

  @OneToMany(mappedBy = "book")
  private Set<OrderDetails> orderDetail;

  public Book () {
    // default
  }
  // Getters and setters...

  // Utility method to add order detail to a book
  public void addOrderDetail(OrderDetail orderDetail) {
    this.orderDetails.add(orderDetail);
    orderDetail.setBook(this);
  }

  // Utility method to remove order detail from a book
  public void removeOrderDetail(OrderDetail orderDetail) {
    this.orderDetails.remove(orderDetail);
    orderDetail.setBook(null);
  }
}

Note the following:

User


// imports
@Entity
@Table(name = "users")
public class User {
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  @Column(name = "user_id")
  private int id;

  @Column(name = "username", nullable = false, length = 45, unique = true)
  private String username;

  @OneToMany(mappedBy = "user")
  private Set<Order> orders = new HashSet<>();

  public User() {}

  // Constructor with parameters
  public User(String username) {
    this.username = username;
  }

  // Methods to add and remove orders if necessary
  public void addOrder(Order order) {
    orders.add(order);
    order.setUser(this);
  }

  public void removeOrder(Order order) {
    orders.remove(order);
    order.setUser(null);
  }

  // Getters and setters
  // Other methods...
}

Order

// imports

@Entity
@Table(name = "orders")
public class Order {
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  @Column(name = "order_id")
  private int id;

  @ManyToOne
  @JoinColumn(name = "user_id", nullable = false)
  private User user;

  @OneToMany(mappedBy = "order")
  private Set<OrderDetails> orderDetail;

  public Order() {}
  // Constructor with user parameter
  public Order(Long id, User user, Set<OrderDetails> OrderDetails) {
    super();
    this.id = id;
    this.user = user;
    this.OrderDetails = OrderDetails;
  }

  // Utility method to add order detail to a book
  public void addOrderDetail(OrderDetails orderDetail) {
    this.orderDetails.add(orderDetail);
    orderDetail.setBook(this);
  }

  // Utility method to remove order detail from a book
  public void removeOrderDetail(OrderDetails orderDetail) {
    this.orderDetails.remove(orderDetail);
    orderDetail.setBook(null);
  }

  // Getters and setters
}

Order details

This entity can be adjusted to include a composite primary key. It can simplify the logic of a @ManyToMany relationship as well as handling custom attributes (quantity).

// imports

@Entity
@Table(name = "order_details")
public class OrderDetails {
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  @Column(name="order_detail_id")
  private int id;

  @ManyToOne
  @JoinColumn(name = "order_id")
  private Order order;

  @ManyToOne
  @JoinColumn(name = "book_id")
  private Book book;

  @Column(name = "quantity", nullable = false)
  private int quantity;

  public OrderDetails() {}

  // Getters and setters

  // Embeddable key class but only necessary for certain cases
  /*
  @Embeddable
  public static class OrderBookId implements Serializable {

    @Column(name = "Order_order_id")
    private Long orderId;

    @Column(name = "Book_book_id")
    private Long bookId;

    // Default constructor
    public OrderBookId() {}

    // Constructor with parameters
    public OrderBookId(Long orderId, Long bookId) {
      this.orderId = orderId;
      this.bookId = bookId;
    }

    // Getters and setters
  }
  */

  // Other methods...
}

A composite key implementation is more of an opinion instead of a good practice. You can add it through the code above if necessary, else, keep it simple.

Repositories

The next step is to setup our BookRepository so we can work with JPA's methods. Fortunately that’s a quick solution and these files have few loc.

Create a new interface called BookRepository inside a package dedicated solely for repositories. Now we’ll have our interface extend JPA methods making them available for us.

BookRepository

// This marker is used to indicate that the interface is a repository
// and a component of the Spring context.
@Repository
public interface BookRepository extends JpaRepository<Book, Long> {
  // No need to define findById(Long id) as it's already provided.
  // Example custom query method
  Optional<Book> findBookByIsbn(String isbn);

  // Additional custom methods as required for your application.
}