Browse Source

Implement Author entity: re-format parts of table structures, add

supporting code
Signed-off-by: Pekka Helenius <fincer89@hotmail.com>
v0.0.2-alpha
Pekka Helenius 4 years ago
parent
commit
3f6db48b70
7 changed files with 288 additions and 24 deletions
  1. +20
    -3
      bookstore/src/main/java/com/fjordtek/bookstore/BookstoreApplication.java
  2. +147
    -0
      bookstore/src/main/java/com/fjordtek/bookstore/model/Author.java
  3. +31
    -0
      bookstore/src/main/java/com/fjordtek/bookstore/model/AuthorJsonSerializer.java
  4. +22
    -0
      bookstore/src/main/java/com/fjordtek/bookstore/model/AuthorRepository.java
  5. +23
    -17
      bookstore/src/main/java/com/fjordtek/bookstore/model/Book.java
  6. +2
    -2
      bookstore/src/main/java/com/fjordtek/bookstore/model/Category.java
  7. +43
    -2
      bookstore/src/main/java/com/fjordtek/bookstore/web/BookController.java

+ 20
- 3
bookstore/src/main/java/com/fjordtek/bookstore/BookstoreApplication.java View File

@ -12,6 +12,8 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import com.fjordtek.bookstore.model.Author;
import com.fjordtek.bookstore.model.AuthorRepository;
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.Category;
@ -26,7 +28,11 @@ public class BookstoreApplication extends SpringBootServletInitializer {
} }
@Bean @Bean
public CommandLineRunner bookDatabaseRunner(BookRepository bookRepository, CategoryRepository categoryRepository) {
public CommandLineRunner bookDatabaseRunner(
BookRepository bookRepository,
CategoryRepository categoryRepository,
AuthorRepository authorRepository
) {
return (args) -> { return (args) -> {
@ -36,11 +42,16 @@ public class BookstoreApplication extends SpringBootServletInitializer {
categoryRepository.save(new Category("Fantasy")); categoryRepository.save(new Category("Fantasy"));
categoryRepository.save(new Category("Sci-Fi")); categoryRepository.save(new Category("Sci-Fi"));
authorRepository.save(new Author("Angela","Carter"));
authorRepository.save(new Author("Andrzej","Sapkowski"));
commonLogger.info("Add new sample books to database"); commonLogger.info("Add new sample books to database");
bookRepository.save(new Book( bookRepository.save(new Book(
"Bloody Chamber", "Bloody Chamber",
"Angela Carter",
authorRepository.findByFirstNameIgnoreCaseContainingAndLastNameIgnoreCaseContaining(
"Angela","Carter"
).get(0),
1979, 1979,
"1231231-12", "1231231-12",
new BigDecimal("18.00"), new BigDecimal("18.00"),
@ -48,7 +59,9 @@ public class BookstoreApplication extends SpringBootServletInitializer {
)); ));
bookRepository.save(new Book( bookRepository.save(new Book(
"The Witcher: The Lady of the Lake", "The Witcher: The Lady of the Lake",
"Andrzej Sapkowski",
authorRepository.findByFirstNameIgnoreCaseContainingAndLastNameIgnoreCaseContaining(
"Andrzej","Sapkowski"
).get(0),
1999, 1999,
"3213221-3", "3213221-3",
new BigDecimal("19.99"), new BigDecimal("19.99"),
@ -60,6 +73,10 @@ public class BookstoreApplication extends SpringBootServletInitializer {
for (Category category : categoryRepository.findAll()) { for (Category category : categoryRepository.findAll()) {
commonLogger.info(category.toString()); commonLogger.info(category.toString());
} }
commonLogger.info("Sample authors in the database");
for (Author author : authorRepository.findAll()) {
commonLogger.info(author.toString());
}
commonLogger.info("Sample books in the database"); commonLogger.info("Sample books in the database");
for (Book book : bookRepository.findAll()) { for (Book book : bookRepository.findAll()) {
commonLogger.info(book.toString()); commonLogger.info(book.toString());


+ 147
- 0
bookstore/src/main/java/com/fjordtek/bookstore/model/Author.java View File

@ -0,0 +1,147 @@
package com.fjordtek.bookstore.model;
import java.util.List;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.SequenceGenerator;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;
import com.fasterxml.jackson.annotation.JsonIgnore;
@Entity
public class Author {
private static final int strMin = 2;
private static final int strMax = 100;
// We format length check in Size annotation, not here
private static final String regexCommon = "^[a-zA-Z0-9\\-:\\s]*$";
////////////////////
// Primary key value in database
@Id
@GeneratedValue(
strategy = GenerationType.IDENTITY,
generator = "authorIdGenerator"
)
@SequenceGenerator(
name = "authorIdGenerator",
sequenceName = "authorIdSequence"
)
@JsonIgnore
private Long id;
//////////
@Column(name = "firstname", nullable = false)
@Size(
min = strMin, max = strMax,
message = "First name length must be " + strMin + "-" + strMax + " characters"
)
@NotBlank(
message = "Fill the first name form"
)
@Pattern(
regexp = regexCommon,
message = "Invalid characters"
)
private String firstName;
//////////
@Column(name = "lastname", nullable = false)
@Size(
min = strMin, max = strMax,
message = "Last name length must be " + strMin + "-" + strMax + " characters"
)
@NotBlank(
message = "Fill the first name form"
)
@Pattern(
regexp = regexCommon,
message = "Invalid characters"
)
private String lastName;
// Omit from Jackson JSON serialization
//@JsonBackReference(value = "books")
@JsonIgnore
@OneToMany(
mappedBy = "author",
// We consider EAGER FetchType for updatable tables, i.e. when adding new author
fetch = FetchType.EAGER,
cascade = CascadeType.ALL,
targetEntity = Book.class
)
private List<Book> books;
////////////////////
// Attribute setters
public void setId(Long id) {
this.id = id;
}
public void setFirstName(String firstName) {
// Delete leading & trailing whitespaces (typos from user)
this.firstName = firstName.trim();
}
public void setLastName(String lastName) {
// Delete leading & trailing whitespaces (typos from user)
this.lastName = lastName.trim();
}
public void setBooks(List<Book> books) {
this.books = books;
}
////////////////////
// Attribute getters
public Long getId() {
return id;
}
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
public List<Book> getBooks() {
return books;
}
////////////////////
// Class constructors
public Author() {}
public Author(String firstName, String lastName) {
// super();
this.firstName = firstName;
this.lastName = lastName;
}
////////////////////
// Class overrides
@Override
public String toString() {
return "[" + "id: " + this.id + ", " +
"firstname: " + this.firstName + ", " +
"lastname: " + this.lastName + "]";
}
}

+ 31
- 0
bookstore/src/main/java/com/fjordtek/bookstore/model/AuthorJsonSerializer.java View File

@ -0,0 +1,31 @@
package com.fjordtek.bookstore.model;
import java.io.IOException;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
public class AuthorJsonSerializer extends StdSerializer<Author> {
private static final long serialVersionUID = 5233819344225306443L;
public AuthorJsonSerializer() {
this(null);
}
public AuthorJsonSerializer(Class<Author> jd) {
super(jd);
}
@Override
public void serialize(Author author, JsonGenerator gen, SerializerProvider provider)
throws IOException {
gen.writeStartObject();
// Author class Id has JsonIgnore annotation
//gen.writeFieldId(author.getId());
gen.writeStringField("firstname", author.getFirstName());
gen.writeStringField("lastname", author.getLastName());
gen.writeEndObject();
}
}

+ 22
- 0
bookstore/src/main/java/com/fjordtek/bookstore/model/AuthorRepository.java View File

@ -0,0 +1,22 @@
package com.fjordtek.bookstore.model;
import java.util.List;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.query.Param;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
import org.springframework.data.rest.core.annotation.RestResource;
@RepositoryRestResource(
path = "authors",
itemResourceRel = "authors",
exported = true
)
public interface AuthorRepository extends CrudRepository<Author,Long> {
@RestResource(path = "author", rel = "author")
public List<Author> findByFirstNameIgnoreCaseContainingAndLastNameIgnoreCaseContaining(
@Param("firstname") String firstName, @Param("lastname") String lastName
);
}

+ 23
- 17
bookstore/src/main/java/com/fjordtek/bookstore/model/Book.java View File

@ -98,19 +98,24 @@ public class Book {
private String title; private String title;
////////// //////////
@Column(nullable = false)
@Size(
min = strMin, max = strMax,
message = "Author length must be " + strMin + "-" + strMax + " characters"
)
@NotBlank(
message = "Fill the book author form"
)
@Pattern(
regexp = regexCommon,
message = "Invalid characters"
// If category is null, we do not print it in JSON output.
@JsonUnwrapped
/*
* There are many ways to filter which category fields we want to JSON output.
* Using a custom JSON serializer is one of them.
*/
@JsonSerialize(using = AuthorJsonSerializer.class)
@ManyToOne(
fetch = FetchType.EAGER,
optional = true,
targetEntity = Author.class
) )
private String author;
@JoinColumn(
name = "author_id",
nullable = true
)
private Author author;
////////// //////////
// TODO: Prefer Timestamp data type // TODO: Prefer Timestamp data type
@ -175,8 +180,9 @@ public class Book {
*/ */
@JsonSerialize(using = CategoryJsonSerializer.class) @JsonSerialize(using = CategoryJsonSerializer.class)
@ManyToOne( @ManyToOne(
fetch = FetchType.EAGER,
optional = true
fetch = FetchType.EAGER,
optional = true,
targetEntity = Category.class
) )
@JoinColumn( @JoinColumn(
name = "category_id", name = "category_id",
@ -198,7 +204,7 @@ public class Book {
this.title = title; this.title = title;
} }
public void setAuthor(String author) {
public void setAuthor(Author author) {
this.author = author; this.author = author;
} }
@ -231,7 +237,7 @@ public class Book {
return title; return title;
} }
public String getAuthor() {
public Author getAuthor() {
return author; return author;
} }
@ -256,7 +262,7 @@ public class Book {
public Book() {} public Book() {}
public Book(String title, String author, int year, String isbn, BigDecimal price, Category category) {
public Book(String title, Author author, int year, String isbn, BigDecimal price, Category category) {
// super(); // super();
this.title = title; this.title = title;
this.author = author; this.author = author;


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

@ -43,8 +43,8 @@ public class Category {
@OneToMany( @OneToMany(
mappedBy = "category", mappedBy = "category",
fetch = FetchType.LAZY, fetch = FetchType.LAZY,
cascade = CascadeType.ALL
//targetEntity = Book.class
cascade = CascadeType.ALL,
targetEntity = Book.class
) )
private List<Book> books; private List<Book> books;


+ 43
- 2
bookstore/src/main/java/com/fjordtek/bookstore/web/BookController.java View File

@ -25,6 +25,8 @@ import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.ResponseStatus;
import com.fjordtek.bookstore.model.Author;
import com.fjordtek.bookstore.model.AuthorRepository;
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; import com.fjordtek.bookstore.model.CategoryRepository;
@ -41,10 +43,13 @@ public class BookController {
} }
@Autowired @Autowired
private BookRepository bookRepository;
private CategoryRepository categoryRepository;
@Autowired @Autowired
private CategoryRepository categoryRepository;
private AuthorRepository authorRepository;
@Autowired
private BookRepository bookRepository;
private static final String RestJSONPageView = "json"; private static final String RestJSONPageView = "json";
private static final String RestAPIRefPageView = "apiref"; private static final String RestAPIRefPageView = "apiref";
@ -76,6 +81,38 @@ public class BookController {
// Security implications of adding these all controller-wide? // Security implications of adding these all controller-wide?
dataModel.addAllAttributes(globalModelMap); dataModel.addAllAttributes(globalModelMap);
dataModel.addAttribute("categories", categoryRepository.findAll()); dataModel.addAttribute("categories", categoryRepository.findAll());
dataModel.addAttribute("authors", authorRepository.findAll());
}
//////////////////////////////
// Private methods
private void detectAndSaveBookAuthor(Book book) {
/*
* Find an author from the current AUTHOR table by his/her first and last name.
* In CrudRepository, if Id attribute is not found, it is stored
* as a new row value. Therefore, it's crucial to identify whether row value already
* exists in AUTHOR table.
*/
try {
Author authorI = authorRepository.findByFirstNameIgnoreCaseContainingAndLastNameIgnoreCaseContaining(
book.getAuthor().getFirstName(),book.getAuthor().getLastName())
.get(0);
/*
* When author is found, use it's Id attribute for book's author Id...
*/
book.getAuthor().setId(authorI.getId());
/*
* ...Otherwise, consider this a new author and store it appropriately.
* Actually, when author is not found, we get IndexOutOfBoundsException.
*/
} catch (IndexOutOfBoundsException e) {
authorRepository.save(book.getAuthor());
}
} }
////////////////////////////// //////////////////////////////
@ -144,8 +181,10 @@ public class BookController {
httpServerLogger.log(requestData, responseData); httpServerLogger.log(requestData, responseData);
detectAndSaveBookAuthor(book);
bookRepository.save(book); bookRepository.save(book);
return "redirect:" + bookListPageView; return "redirect:" + bookListPageView;
} }
@ -231,6 +270,8 @@ public class BookController {
return bookEditPageView; return bookEditPageView;
} }
detectAndSaveBookAuthor(book);
bookRepository.save(book); bookRepository.save(book);
httpServerLogger.log(requestData, responseData); httpServerLogger.log(requestData, responseData);


Loading…
Cancel
Save