Browse Source

Implement Category; add one-to-many relationship; update web forms

Signed-off-by: Pekka Helenius <fincer89@hotmail.com>
v0.0.1-alpha
Pekka Helenius 4 years ago
parent
commit
0e32811e58
8 changed files with 195 additions and 15 deletions
  1. +18
    -4
      bookstore/src/main/java/com/fjordtek/bookstore/BookstoreApplication.java
  2. +42
    -6
      bookstore/src/main/java/com/fjordtek/bookstore/model/Book.java
  3. +84
    -0
      bookstore/src/main/java/com/fjordtek/bookstore/model/Category.java
  4. +14
    -0
      bookstore/src/main/java/com/fjordtek/bookstore/model/CategoryRepository.java
  5. +7
    -0
      bookstore/src/main/java/com/fjordtek/bookstore/web/BookController.java
  6. +16
    -5
      bookstore/src/main/resources/templates/bookadd.html
  7. +12
    -0
      bookstore/src/main/resources/templates/bookedit.html
  8. +2
    -0
      bookstore/src/main/resources/templates/booklist.html

+ 18
- 4
bookstore/src/main/java/com/fjordtek/bookstore/BookstoreApplication.java View File

@ -12,6 +12,8 @@ import org.springframework.context.annotation.Bean;
import com.fjordtek.bookstore.model.Book; import com.fjordtek.bookstore.model.Book;
import com.fjordtek.bookstore.model.BookRepository; import com.fjordtek.bookstore.model.BookRepository;
import com.fjordtek.bookstore.model.Category;
import com.fjordtek.bookstore.model.CategoryRepository;
@SpringBootApplication @SpringBootApplication
public class BookstoreApplication extends SpringBootServletInitializer { public class BookstoreApplication extends SpringBootServletInitializer {
@ -22,17 +24,29 @@ public class BookstoreApplication extends SpringBootServletInitializer {
} }
@Bean @Bean
public CommandLineRunner bookDatabaseRunner(BookRepository repository) {
public CommandLineRunner bookDatabaseRunner(BookRepository bookRepository, CategoryRepository categoryRepository) {
return (args) -> { return (args) -> {
commonLogger.info("Add new categories to database");
Category horror = new Category("Horror");
Category fantasy = new Category("Fantasy");
categoryRepository.save(horror);
categoryRepository.save(fantasy);
commonLogger.info("Add new sample books to database"); commonLogger.info("Add new sample books to database");
repository.save(new Book("Book 1 title", "Book 1 author", 2020, "1231231-12", 40.00));
repository.save(new Book("Book 2 title", "Book 2 author", 2005, "3213221-3", 20.17));
bookRepository.save(new Book("Book 1 title", "Book 1 author", 2020, "1231231-12", 40.00, horror));
bookRepository.save(new Book("Book 2 title", "Book 2 author", 2005, "3213221-3", 20.17, fantasy));
commonLogger.info("------------------------------"); commonLogger.info("------------------------------");
commonLogger.info("Sample categories in the database");
for (Category category : categoryRepository.findAll()) {
commonLogger.info(category.toString());
}
commonLogger.info("Sample books in the database"); commonLogger.info("Sample books in the database");
for (Book book : repository.findAll()) {
for (Book book : bookRepository.findAll()) {
commonLogger.info(book.toString()); commonLogger.info(book.toString());
} }


+ 42
- 6
bookstore/src/main/java/com/fjordtek/bookstore/model/Book.java View File

@ -2,6 +2,8 @@
package com.fjordtek.bookstore.model; package com.fjordtek.bookstore.model;
import javax.persistence.Column;
//import java.sql.Timestamp; //import java.sql.Timestamp;
//import javax.validation.constraints.PastOrPresent; //import javax.validation.constraints.PastOrPresent;
@ -9,6 +11,8 @@ import javax.persistence.Entity;
import javax.persistence.GeneratedValue; import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType; import javax.persistence.GenerationType;
import javax.persistence.Id; import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.validation.constraints.DecimalMax; import javax.validation.constraints.DecimalMax;
import javax.validation.constraints.DecimalMin; import javax.validation.constraints.DecimalMin;
import javax.validation.constraints.Digits; import javax.validation.constraints.Digits;
@ -110,6 +114,9 @@ public class Book {
private int year; private int year;
////////// //////////
@Column(
unique = true
)
@NotBlank( @NotBlank(
message = "Fill the ISBN code form" message = "Fill the ISBN code form"
) )
@ -141,6 +148,16 @@ public class Book {
// TODO: Use BigDecimal to keep exact precision? // TODO: Use BigDecimal to keep exact precision?
private double price; private double price;
@ManyToOne(
//fetch = FetchType.LAZY,
optional = false
)
@JoinColumn(
name = "category_id",
nullable = false
)
private Category category;
//////////////////// ////////////////////
// Attribute setters // Attribute setters
@ -171,6 +188,10 @@ public class Book {
this.price = price; this.price = price;
} }
public void setCategory(Category category) {
this.category = category;
}
//////////////////// ////////////////////
// Attribute getters // Attribute getters
@ -200,6 +221,10 @@ public class Book {
return price; return price;
} }
public Category getCategory() {
return category;
}
//////////////////// ////////////////////
// Class constructors // Class constructors
@ -207,11 +232,21 @@ public class Book {
public Book(String title, String author, int year, String isbn, double price) { public Book(String title, String author, int year, String isbn, double price) {
// super(); // super();
this.title = title;
this.author = author;
this.year = year;
this.isbn = isbn;
this.price = price;
this.title = title;
this.author = author;
this.year = year;
this.isbn = isbn;
this.price = price;
}
public Book(String title, String author, int year, String isbn, double price, Category category) {
// super();
this.title = title;
this.author = author;
this.year = year;
this.isbn = isbn;
this.price = price;
this.category = category;
} }
//////////////////// ////////////////////
@ -224,7 +259,8 @@ public class Book {
"author: " + this.author + ", " + "author: " + this.author + ", " +
"year: " + this.year + ", " + "year: " + this.year + ", " +
"isbn: " + this.isbn + ", " + "isbn: " + this.isbn + ", " +
"price: " + this.price + "]";
"price: " + this.price + ", " +
"category: " + this.category + "]";
} }
} }

+ 84
- 0
bookstore/src/main/java/com/fjordtek/bookstore/model/Category.java View File

@ -0,0 +1,84 @@
//Pekka Helenius <fincer89@hotmail.com>, Fjordtek 2020
package com.fjordtek.bookstore.model;
import java.util.List;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToMany;
@Entity
public class Category {
////////////////////
// Primary key value in database
@Id
@GeneratedValue(
strategy = GenerationType.AUTO
)
private long id;
////////////////////
// Attributes with hard-coded constraints
private String name;
@OneToMany(
mappedBy = "category"
//fetch = FetchType.LAZY,
//cascade = CascadeType.ALL
)
private List<Book> books;
////////////////////
// Attribute setters
public void setId(long id) {
this.id = id;
}
public void setName(String name) {
this.name = name;
}
public void setBooks(List<Book> books) {
this.books = books;
}
////////////////////
// Attribute getters
public long getId() {
return id;
}
public String getName() {
return name;
}
public List<Book> getBooks() {
return books;
}
////////////////////
// Class constructors
public Category() {
}
public Category(String name) {
this.name = name;
}
////////////////////
// Class overrides
@Override
public String toString() {
return "[" + "id: " + this.id +
"name: " + this.name + "]";
}
}

+ 14
- 0
bookstore/src/main/java/com/fjordtek/bookstore/model/CategoryRepository.java View File

@ -0,0 +1,14 @@
//Pekka Helenius <fincer89@hotmail.com>, Fjordtek 2020
package com.fjordtek.bookstore.model;
import java.util.List;
import org.springframework.data.repository.CrudRepository;
public interface CategoryRepository extends CrudRepository<Category, Long> {
// Handles both INSERT and UPDATE queries
List<Category> findById(String name);
}

+ 7
- 0
bookstore/src/main/java/com/fjordtek/bookstore/web/BookController.java View File

@ -21,6 +21,7 @@ import org.springframework.web.bind.annotation.ResponseStatus;
import com.fjordtek.bookstore.model.Book; import com.fjordtek.bookstore.model.Book;
import com.fjordtek.bookstore.model.BookRepository; import com.fjordtek.bookstore.model.BookRepository;
import com.fjordtek.bookstore.model.CategoryRepository;
@Controller @Controller
public class BookController { public class BookController {
@ -37,6 +38,9 @@ public class BookController {
@Autowired @Autowired
private BookRepository bookRepository; private BookRepository bookRepository;
@Autowired
private CategoryRepository categoryRepository;
////////////////////////////// //////////////////////////////
// LIST PAGE // LIST PAGE
@RequestMapping( @RequestMapping(
@ -74,7 +78,9 @@ public class BookController {
); );
Book newBook = new Book(); Book newBook = new Book();
dataModel.addAttribute("book", newBook); dataModel.addAttribute("book", newBook);
dataModel.addAttribute("categories", categoryRepository.findAll());
if (newBook.getYear() == 0) { if (newBook.getYear() == 0) {
newBook.setYear(Year.now().getValue()); newBook.setYear(Year.now().getValue());
@ -150,6 +156,7 @@ public class BookController {
Book book = bookRepository.findById(bookId).get(); Book book = bookRepository.findById(bookId).get();
dataModel.addAttribute("book", book); dataModel.addAttribute("book", book);
dataModel.addAttribute("categories", categoryRepository.findAll());
return bookEditPageURL; return bookEditPageURL;
} }


+ 16
- 5
bookstore/src/main/resources/templates/bookadd.html View File

@ -12,31 +12,31 @@
<form th:object="${book}" action="#" th:action="@{bookadd}" method="post"> <form th:object="${book}" action="#" th:action="@{bookadd}" method="post">
<div class="form-group"> <div class="form-group">
<label for="fname">Author</label>
<label for="BookAuthor">Author</label>
<input class="form-control" type="text" th:field="*{author}" placeholder="Book author"/> <input class="form-control" type="text" th:field="*{author}" placeholder="Book author"/>
<small class="form-text text-muted">Set book author name</small> <small class="form-text text-muted">Set book author name</small>
<div class="alert alert-danger mt-2" th:if="${#fields.hasErrors('author')}" th:errors="*{author}">Invalid author name</div> <div class="alert alert-danger mt-2" th:if="${#fields.hasErrors('author')}" th:errors="*{author}">Invalid author name</div>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="fname">Title</label>
<label for="BookTitle">Title</label>
<input class="form-control" type="text" th:field="*{title}" placeholder="Book title"/> <input class="form-control" type="text" th:field="*{title}" placeholder="Book title"/>
<small class="form-text text-muted">Set book primary title</small> <small class="form-text text-muted">Set book primary title</small>
<div class="alert alert-danger mt-2" th:if="${#fields.hasErrors('title')}" th:errors="*{title}">Invalid title</div> <div class="alert alert-danger mt-2" th:if="${#fields.hasErrors('title')}" th:errors="*{title}">Invalid title</div>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="fname">ISBN</label>
<label for="BookISBN">ISBN</label>
<input class="form-control" type="text" th:field="*{isbn}" placeholder="Book ISBN code"/> <input class="form-control" type="text" th:field="*{isbn}" placeholder="Book ISBN code"/>
<small class="form-text text-muted">Set book ISBN code</small> <small class="form-text text-muted">Set book ISBN code</small>
<div class="alert alert-danger mt-2" th:if="${#fields.hasErrors('isbn')}" th:errors="*{isbn}">Invalid ISBN code</div> <div class="alert alert-danger mt-2" th:if="${#fields.hasErrors('isbn')}" th:errors="*{isbn}">Invalid ISBN code</div>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="fname">Year</label>
<label for="BookYear">Year</label>
<input class="form-control" type="text" th:field="*{year}" placeholder="Book publication year (YYYY)"/> <input class="form-control" type="text" th:field="*{year}" placeholder="Book publication year (YYYY)"/>
<small class="form-text text-muted">Set book publication year</small> <small class="form-text text-muted">Set book publication year</small>
<div class="alert alert-danger mt-2" th:if="${#fields.hasErrors('year')}" th:errors="*{year}">Invalid year</div> <div class="alert alert-danger mt-2" th:if="${#fields.hasErrors('year')}" th:errors="*{year}">Invalid year</div>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="fname">Price</label>
<label for="BookPrice">Price</label>
<div class="input-group mb-2"> <div class="input-group mb-2">
<div class="input-group-prepend"> <div class="input-group-prepend">
<div class="input-group-text"></div> <div class="input-group-text"></div>
@ -46,6 +46,17 @@
<small class="form-text text-muted">Set book price</small> <small class="form-text text-muted">Set book price</small>
<div class="alert alert-danger mt-2" th:if="${#fields.hasErrors('price')}" th:errors="*{price}">Invalid price</div> <div class="alert alert-danger mt-2" th:if="${#fields.hasErrors('price')}" th:errors="*{price}">Invalid price</div>
</div> </div>
<div class="form-group">
<label for="BookCategory">Category</label>
<select class="form-control" th:field="*{category}">
<option
th:each="category : ${categories}"
th:value="${category.getId()}"
th:text="${category.getName()}"
></option>
</select>
<small class="form-text text-muted">Select appropriate category</small>
</div>
<button class="btn btn-primary" type="submit">Add book</button> <button class="btn btn-primary" type="submit">Add book</button>
</form> </form>


+ 12
- 0
bookstore/src/main/resources/templates/bookedit.html View File

@ -46,6 +46,18 @@
<small class="form-text text-muted">Set book price</small> <small class="form-text text-muted">Set book price</small>
<div class="alert alert-danger mt-2" th:if="${#fields.hasErrors('price')}" th:errors="*{price}">Invalid price</div> <div class="alert alert-danger mt-2" th:if="${#fields.hasErrors('price')}" th:errors="*{price}">Invalid price</div>
</div> </div>
<div class="form-group">
<label for="BookCategory">Category</label>
<select class="form-control" th:field="*{category}" th:selected="${book.category}">
<option
th:each="category : ${categories}"
th:value="${category.getId()}"
th:text="${category.getName()}"
></option>
</select>
<small class="form-text text-muted">Select appropriate category</small>
</div>
<button class="btn btn-primary" type="submit">Update book</button> <button class="btn btn-primary" type="submit">Update book</button>
</form> </form>


+ 2
- 0
bookstore/src/main/resources/templates/booklist.html View File

@ -15,6 +15,7 @@
<th>Title</th> <th>Title</th>
<th>ISBN</th> <th>ISBN</th>
<th>Year</th> <th>Year</th>
<th>Category</th>
<th>Price</th> <th>Price</th>
<th>Actions</th> <th>Actions</th>
<th></th> <th></th>
@ -24,6 +25,7 @@
<td th:text="${book.title}"></td> <td th:text="${book.title}"></td>
<td th:text="${book.isbn}"></td> <td th:text="${book.isbn}"></td>
<td th:text="${book.year}"></td> <td th:text="${book.year}"></td>
<td th:text="${book.category.getName()}"></td>
<td th:text="${#numbers.formatDecimal(book.price, 1, 2)}"></td> <td th:text="${#numbers.formatDecimal(book.price, 1, 2)}"></td>
<td><a class="btn btn-danger" <td><a class="btn btn-danger"
th:attr="onclick='javascript:return confirm(\'' + 'Delete book: ' + ${book.title} + '?' + '\');'" th:attr="onclick='javascript:return confirm(\'' + 'Delete book: ' + ${book.title} + '?' + '\');'"


Loading…
Cancel
Save