This commit is contained in:
Zakharov_Rostislav 2024-06-07 18:00:02 +04:00
parent f97d1984e4
commit c8b26c8e24
14 changed files with 190 additions and 56 deletions

1
.gitignore vendored
View File

@ -1,2 +1,3 @@
SpringApp/data.mv.db
SpringApp/library/data.mv.db
SpringApp/library/data.trace.db

View File

@ -56,10 +56,10 @@ public class LibraryApplication implements CommandLineRunner {
final var author2 = authorService.create(new AuthorEntity("author2"));
log.info("Create default books values");
final var book1 = bookService.create(new BookEntity("book1", type1));
final var book2 = bookService.create(new BookEntity("book2", type1));
final var book3 = bookService.create(new BookEntity("book3", type2));
final var book4 = bookService.create(new BookEntity("book4", type2));
final var book1 = bookService.create(new BookEntity("book1", type1), new ArrayList<>());
final var book2 = bookService.create(new BookEntity("book2", type1), new ArrayList<>());
final var book3 = bookService.create(new BookEntity("book3", type2), new ArrayList<>());
final var book4 = bookService.create(new BookEntity("book4", type2), new ArrayList<>());
bookService.addAuthor(author1.getId(), book1.getId());
bookService.addAuthor(author2.getId(), book2.getId());

View File

@ -50,7 +50,6 @@ public class AuthorService {
throw new IllegalArgumentException("Updating AuthorEntity is null");
}
final AuthorEntity existsEntity = get(id);
checkNameUniqueness(entity.getName());
existsEntity.setName(entity.getName());
return repository.save(existsEntity);
}

View File

@ -0,0 +1,6 @@
package com.ip.library.controllers.authors_books;
import org.springframework.data.repository.CrudRepository;
public interface AuthorsBooksRepository extends
CrudRepository<AuthorsBooksEntity, AuthorsBooksId> {}

View File

@ -16,6 +16,7 @@ import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import com.ip.library.controllers.authors.AuthorEntity;
import com.ip.library.controllers.authors.AuthorService;
import com.ip.library.controllers.types.TypeService;
import com.ip.library.core.api.PageAttributesMapper;
import com.ip.library.core.configuration.Constants;
@ -31,15 +32,23 @@ public class BookController {
private static final String BOOK_ATTRIBUTE = "book";
private static final String PAGE_ATTRIBUTE = "page";
private static final String AUTHOR_ATTRIBUTE = "authorId";
private static final String AUTHORS_ATTRIBUTE = "authors";
private static final String TYPE_ATTRIBUTE = "typeId";
private static final String TYPES_ATTRIBUTE = "types";
private final BookService bookService;
private final TypeService typeService;
private final AuthorService authorService;
private final ModelMapper modelMapper;
public BookController(BookService bookService, TypeService typeService, ModelMapper modelMapper) {
public BookController(
BookService bookService,
TypeService typeService,
AuthorService authorService,
ModelMapper modelMapper) {
this.bookService = bookService;
this.typeService = typeService;
this.authorService = authorService;
this.modelMapper = modelMapper;
}
@ -60,12 +69,16 @@ public class BookController {
return bookDto;
}
private BookEntity toEntity(BookDto dto) {
private BookEntity toBookEntity(BookDto dto) {
final BookEntity entity = modelMapper.map(dto, BookEntity.class);
entity.setType(typeService.get(dto.getTypeId()));
return entity;
}
private List<Long> toAuthorsIdList(BookDto dto) {
return dto.getAuthorsId();
}
@GetMapping
public String getAll(
@RequestParam(name = TYPE_ATTRIBUTE, defaultValue = "-1") Long typeId,
@ -84,6 +97,8 @@ public class BookController {
@RequestParam(name = PAGE_ATTRIBUTE, defaultValue = "0") int page,
Model model) {
model.addAttribute(BOOK_ATTRIBUTE, new BookDto());
model.addAttribute(TYPES_ATTRIBUTE, typeService.getAll());
model.addAttribute(AUTHORS_ATTRIBUTE, authorService.getAll());
model.addAttribute(PAGE_ATTRIBUTE, page);
return BOOK_EDIT_VIEW;
}
@ -96,11 +111,13 @@ public class BookController {
Model model,
RedirectAttributes redirectAttributes) {
if (bindingResult.hasErrors()) {
model.addAttribute(TYPES_ATTRIBUTE, typeService.getAll());
model.addAttribute(AUTHORS_ATTRIBUTE, authorService.getAll());
model.addAttribute(PAGE_ATTRIBUTE, page);
return BOOK_EDIT_VIEW;
}
redirectAttributes.addAttribute(PAGE_ATTRIBUTE, page);
bookService.create(toEntity(book));
bookService.create(toBookEntity(book), toAuthorsIdList(book));
return Constants.REDIRECT_VIEW + URL;
}
@ -113,6 +130,8 @@ public class BookController {
throw new IllegalArgumentException();
}
model.addAttribute(BOOK_ATTRIBUTE, toBookDto(bookService.get(id)));
model.addAttribute(TYPES_ATTRIBUTE, typeService.getAll());
model.addAttribute(AUTHORS_ATTRIBUTE, authorService.getAll());
model.addAttribute(PAGE_ATTRIBUTE, page);
return BOOK_EDIT_VIEW;
}
@ -126,6 +145,8 @@ public class BookController {
Model model,
RedirectAttributes redirectAttributes) {
if (bindingResult.hasErrors()) {
model.addAttribute(TYPES_ATTRIBUTE, typeService.getAll());
model.addAttribute(AUTHORS_ATTRIBUTE, authorService.getAll());
model.addAttribute(PAGE_ATTRIBUTE, page);
return BOOK_EDIT_VIEW;
}
@ -133,7 +154,7 @@ public class BookController {
throw new IllegalArgumentException();
}
redirectAttributes.addAttribute(PAGE_ATTRIBUTE, page);
bookService.update(id, toEntity(book));
bookService.update(id, toBookEntity(book), toAuthorsIdList(book));
return Constants.REDIRECT_VIEW + URL;
}

View File

@ -29,8 +29,7 @@ public class BookEntity extends BaseEntity {
@JoinColumn(name = "type_id", nullable = false)
@OrderBy("id ASC")
private TypeEntity type;
@OneToMany(mappedBy = "book", cascade = CascadeType.ALL,
orphanRemoval = true, fetch = FetchType.EAGER)
@OneToMany(mappedBy = "book", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
@OrderBy("id ASC")
private Set<AuthorsBooksEntity> authorsBooks = new HashSet<>();
@ -63,7 +62,7 @@ public class BookEntity extends BaseEntity {
return authorsBooks;
}
public void setAuthors(Set<AuthorsBooksEntity> authorsBooks) {
public void setAuthorsBooks(Set<AuthorsBooksEntity> authorsBooks) {
this.authorsBooks = authorsBooks;
}

View File

@ -10,20 +10,28 @@ import org.springframework.transaction.annotation.Transactional;
import com.ip.library.controllers.authors.AuthorEntity;
import com.ip.library.controllers.authors.AuthorService;
import com.ip.library.controllers.authors_books.AuthorsBooksEntity;
import com.ip.library.controllers.authors_books.AuthorsBooksId;
import com.ip.library.controllers.authors_books.AuthorsBooksRepository;
import com.ip.library.core.error.NotFoundException;
@Service
public class BookService {
private final BookRepository repository;
private final AuthorsBooksRepository authorsBooksRepository;
private final BookRepository bookRepository;
private final AuthorService authorService;
public BookService(BookRepository repository, AuthorService authorService) {
this.repository = repository;
public BookService(
BookRepository bookRepository,
AuthorService authorService,
AuthorsBooksRepository authorsBooksRepository) {
this.bookRepository = bookRepository;
this.authorService = authorService;
this.authorsBooksRepository = authorsBooksRepository;
}
private void checkNameUniqueness(String name){
if (repository.findByNameIgnoreCase(name).isPresent()) {
if (bookRepository.findByNameIgnoreCase(name).isPresent()) {
throw new IllegalArgumentException(
String.format("Book with name %s already exists", name)
);
@ -32,70 +40,80 @@ public class BookService {
@Transactional(readOnly = true)
public List<BookEntity> getAll(){
return StreamSupport.stream(repository.findAll().spliterator(), false).toList();
return StreamSupport.stream(bookRepository.findAll().spliterator(), false).toList();
}
@Transactional(readOnly = true)
public Page<BookEntity> getAll(long typeId, long authorId, int page, int size) {
PageRequest pageRequest = PageRequest.of(page, size);
if (typeId <= 0L && authorId <= 0L)
return repository.findAll(pageRequest);
return bookRepository.findAll(pageRequest);
if (authorId <= 0L)
return repository.findByTypeId(typeId, pageRequest);
return bookRepository.findByTypeId(typeId, pageRequest);
if (typeId <= 0L)
return repository.findByAuthorId(authorId, pageRequest);
return repository.findByAuthorIdAndTypeId(authorId, typeId, pageRequest);
return bookRepository.findByAuthorId(authorId, pageRequest);
return bookRepository.findByAuthorIdAndTypeId(authorId, typeId, pageRequest);
}
@Transactional(readOnly = true)
public List<BookEntity> getAll(long typeId, long authorId) {
if (typeId <= 0L && authorId <= 0L)
return StreamSupport.stream(repository.findAll().spliterator(),
return StreamSupport.stream(bookRepository.findAll().spliterator(),
false).toList();
if (authorId <= 0L)
return repository.findByTypeId(typeId);
return bookRepository.findByTypeId(typeId);
if (typeId <= 0L)
return repository.findByAuthorId(authorId);
return repository.findByAuthorIdAndTypeId(authorId, typeId);
return bookRepository.findByAuthorId(authorId);
return bookRepository.findByAuthorIdAndTypeId(authorId, typeId);
}
@Transactional
public List<BookEntity> findByAuthorId(long authorId) {
return bookRepository.findByAuthorId(authorId);
}
@Transactional(readOnly = true)
public BookEntity get(long id) {
return repository.findById(id)
return bookRepository.findById(id)
.orElseThrow(() -> new NotFoundException(BookEntity.class, id));
}
@Transactional
public BookEntity create(BookEntity entity) {
public BookEntity create(BookEntity entity, List<Long> authorsId) {
if (entity == null) {
throw new IllegalArgumentException("Creating BookEntity is null");
}
checkNameUniqueness(entity.getName());
return repository.save(entity);
BookEntity result = bookRepository.save(entity);
addAuthors(
result.getId(),
authorsId);
return get(result.getId());
}
@Transactional
public BookEntity update(long id, BookEntity entity) {
public BookEntity update(long id, BookEntity entity, List<Long> authorsId) {
if (entity == null) {
throw new IllegalArgumentException("Updating BookEntity is null");
}
final BookEntity existsEntity = get(id);
checkNameUniqueness(entity.getName());
existsEntity.setName(entity.getName());
existsEntity.setType(entity.getType());
return repository.save(existsEntity);
bookRepository.save(existsEntity);
updateAuthors(id, authorsId);
return get(id);
}
@Transactional
public BookEntity delete(long id) {
final BookEntity existsEntity = get(id);
repository.delete(existsEntity);
bookRepository.delete(existsEntity);
return existsEntity;
}
@Transactional(readOnly = true)
public int getBookSubscribersNumber(long bookId) {
return repository.getBookSubscribersNumber(bookId);
return bookRepository.getBookSubscribersNumber(bookId);
}
@Transactional
@ -106,7 +124,37 @@ public class BookService {
}
@Transactional
public List<BookEntity> findByAuthorId(long authorId) {
return repository.findByAuthorId(authorId);
public AuthorsBooksEntity removeAuthor(long authorId, long bookId) {
final AuthorsBooksEntity existsEntity = authorsBooksRepository.findById(new AuthorsBooksId(authorId, bookId))
.orElseThrow(() -> new IllegalArgumentException("Invalid id"));
authorsBooksRepository.delete(existsEntity);
return existsEntity;
}
@Transactional
public boolean addAuthors(long bookId, List<Long> authorsId) {
final BookEntity book = get(bookId);
for (Long authorId : authorsId) {
final AuthorEntity existsAuthor = authorService.get(authorId);
if (!existsAuthor.addBook(book)) {
return false;
}
}
return true;
}
@Transactional
public void updateAuthors(long bookId, List<Long> authorsId) {
BookEntity book = get(bookId);
for (AuthorsBooksEntity ab : book.getAuthorsBooks()) {
long currentAuthorId = ab.getId().getAuthorId();
if (!authorsId.contains(currentAuthorId)) {
removeAuthor(currentAuthorId, bookId);
}
else {
authorsId.remove(currentAuthorId);
}
}
addAuthors(bookId, authorsId);
}
}

View File

@ -2,4 +2,5 @@ package com.ip.library.controllers.favorites;
import org.springframework.data.repository.CrudRepository;
public interface FavoriteRepository extends CrudRepository<FavoriteEntity, UserBookId> {}
public interface FavoriteRepository extends
CrudRepository<FavoriteEntity, UserBookId> {}

View File

@ -50,7 +50,6 @@ public class TypeService {
throw new IllegalArgumentException("Updating TypeEntity is null");
}
final TypeEntity existsEntity = get(id);
checkNameUniqueness(entity.getName());
existsEntity.setName(entity.getName());
return repository.save(existsEntity);
}

View File

@ -89,7 +89,6 @@ public class UserService implements UserDetailsService{
@Transactional
public UserEntity update(long id, UserEntity entity) {
final UserEntity existsEntity = get(id);
checkLoginUniqueness(entity.getLogin());
existsEntity.setLogin(entity.getLogin());
return userRepository.save(existsEntity);
}

View File

@ -18,6 +18,24 @@
<input type="text" th:field="*{name}" id="name" class="form-control">
<div th:if="${#fields.hasErrors('name')}" th:errors="*{name}" class="invalid-feedback"></div>
</div>
<div class="mb-3">
<select class="form-control" id="typeId" name="typeId">
<option value="">Выберите жанр</option>
<option th:each="type : ${types}"
th:value="${type.id}"
th:text="${type.name}">
</option>
</select>
</div>
<div class="mb-3">
<label for="name" class="form-label">Авторы:</label>
<select multiple="true" class="form-control" id="authorsId" name="authorsId">
<option th:each="author : ${authors}"
th:value="${author.id}"
th:text="${author.name}">
</option>
</select>
</div>
<div class="mb-3 d-flex flex-row">
<button class="btn btn-primary me-2 button-fixed-width" type="submit">Сохранить</button>
<a class="btn btn-secondary button-fixed-width" th:href="@{/api/1.0/book(page=${page})}">Отмена</a>

View File

@ -1,6 +1,8 @@
package com.ip.library;
import java.util.List;
import java.util.ArrayList;
import java.util.Arrays;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.AfterEach;
@ -46,13 +48,22 @@ class BooksTests {
var type2 = typeService.create(new TypeEntity("type2"));
author1 = authorService.create(new AuthorEntity("author1"));
var author2 = authorService.create(new AuthorEntity("author2"));
book1 = bookService.create(new BookEntity("book1", type1));
book2 = bookService.create(new BookEntity("book2", type1));
book3 = bookService.create(new BookEntity("book3", type2));
bookService.addAuthor(author1.getId(), book1.getId());
bookService.addAuthor(author1.getId(), book3.getId());
bookService.addAuthor(author2.getId(), book1.getId());
bookService.addAuthor(author2.getId(), book2.getId());
book1 = bookService.create(new BookEntity("book1", type1),
Arrays.asList(new Long[] {
author1.getId(),
author2.getId()
}));
book2 = bookService.create(new BookEntity("book2", type1),
Arrays.asList(new Long[] {
author2.getId()
}));
book3 = bookService.create(new BookEntity("book3", type2),
Arrays.asList(new Long[] {
author1.getId()
}));
book1 = bookService.get(book1.getId());
book2 = bookService.get(book2.getId());
book3 = bookService.get(book3.getId());
}
@Test
@ -60,6 +71,7 @@ class BooksTests {
Assertions.assertEquals(3, bookService.getAll().size());
Assertions.assertEquals("book1", book1.getName());
Assertions.assertEquals(type1, book1.getType());
Assertions.assertEquals(2, book1.getAuthorsBooks().size());
Assertions.assertEquals(0, bookService.getBookSubscribersNumber(book1.getId()));
}
@ -74,10 +86,15 @@ class BooksTests {
final String testName = book1.getName() + "TEST";
final TypeEntity testType = typeService.create(
new TypeEntity(book1.getType().getName() + "TEST"));
book1 = bookService.update(book1.getId(), new BookEntity(testName, testType));
AuthorEntity testAuthor = authorService.create(new AuthorEntity("author"));
final List<Long> testAuthors = Arrays.asList(new Long[] {
testAuthor.getId()
});
book1 = bookService.update(book1.getId(), new BookEntity(testName, testType), testAuthors);
Assertions.assertEquals(3, bookService.getAll().size());
Assertions.assertEquals(testName, book1.getName());
Assertions.assertEquals(testType, book1.getType());
Assertions.assertEquals(testAuthors.size(), book1.getAuthorsBooks().size());
}
@Test
@ -85,7 +102,7 @@ class BooksTests {
bookService.delete(book1.getId());
Assertions.assertEquals(2, bookService.getAll().size());
final BookEntity newEntity = bookService.create(
new BookEntity(book1.getName(), book1.getType()));
new BookEntity(book1.getName(), book1.getType()), new ArrayList<>());
Assertions.assertEquals(3, bookService.getAll().size());
Assertions.assertNotEquals(book1.getId(), newEntity.getId());
}
@ -94,11 +111,13 @@ class BooksTests {
void nullNameTest() {
Assertions.assertThrows(
DataIntegrityViolationException.class,
() -> bookService.create(new BookEntity(null, book1.getType()))
() -> bookService.create(new BookEntity(null, book1.getType()),
new ArrayList<>())
);
Assertions.assertThrows(
DataIntegrityViolationException.class,
() -> bookService.update(book1.getId(), new BookEntity(null, book1.getType()))
() -> bookService.update(book1.getId(), new BookEntity(null, book1.getType()),
new ArrayList<>())
);
}
@ -106,11 +125,13 @@ class BooksTests {
void nullTypeTest() {
Assertions.assertThrows(
DataIntegrityViolationException.class,
() -> bookService.create(new BookEntity(book1.getName() + "TEST", null))
() -> bookService.create(new BookEntity(book1.getName() + "TEST", null),
new ArrayList<>())
);
Assertions.assertThrows(
DataIntegrityViolationException.class,
() -> bookService.update(book1.getId(), new BookEntity(book1.getName() + "TEST", null))
() -> bookService.update(book1.getId(), new BookEntity(book1.getName() + "TEST", null),
new ArrayList<>())
);
}
@ -118,11 +139,13 @@ class BooksTests {
void uniqueNameTest() {
Assertions.assertThrows(
IllegalArgumentException.class,
() -> bookService.create(new BookEntity(book1.getName(), book1.getType()))
() -> bookService.create(new BookEntity(book1.getName(), book1.getType()),
new ArrayList<>())
);
Assertions.assertThrows(
IllegalArgumentException.class,
() -> bookService.update(book1.getId(), new BookEntity(book1.getName(), book1.getType()))
() -> bookService.update(book1.getId(), new BookEntity(book1.getName(), book1.getType()),
new ArrayList<>())
);
}
@ -141,4 +164,16 @@ class BooksTests {
Assertions.assertEquals(1, list.size());
Assertions.assertTrue(list.contains(book1));
}
@Test
void removeAuthorTest() {
Assertions.assertTrue(
book1.getAuthorsBooks().stream().map(ab -> ab.getId().getAuthorId())
.toList().contains(author1.getId()));
bookService.removeAuthor(author1.getId(), book1.getId());
book1 = bookService.get(book1.getId());
Assertions.assertTrue(
!book1.getAuthorsBooks().stream().map(ab -> ab.getId().getAuthorId())
.toList().contains(author1.getId()));
}
}

View File

@ -1,6 +1,7 @@
package com.ip.library;
import java.util.List;
import java.util.ArrayList;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
@ -40,8 +41,8 @@ class FavoritesTests {
void createData() {
removeData();
TypeEntity type = typeService.create(new TypeEntity("type1"));
book1 = bookService.create(new BookEntity("book1", type));
book2 = bookService.create(new BookEntity("book2", type));
book1 = bookService.create(new BookEntity("book1", type), new ArrayList<Long>());
book2 = bookService.create(new BookEntity("book2", type), new ArrayList<Long>());
user1 = userService.create(new UserEntity("user3", "aqw2sed45"));
user2 = userService.create(new UserEntity("user1", "123"));
userService.create(new UserEntity("user2", "456"));
@ -63,4 +64,11 @@ class FavoritesTests {
Assertions.assertTrue(list.contains(book1));
Assertions.assertTrue(list.contains(book2));
}
@Test
void removeFavoriteTest() {
Assertions.assertTrue(userService.getUserFavorities(user2.getId()).contains(book1));
userService.removeFavorite(user2.getId(), book1.getId());
Assertions.assertTrue(!userService.getUserFavorities(user2.getId()).contains(book1));
}
}

View File

@ -76,7 +76,7 @@ class UsersTests {
@Test
void changePasswordTest() {
String newPassword = user.getPassword() + "TEST";
String newPassword = "TEST";
user = userService.changePassword(user.getId(), newPassword);
Assertions.assertEquals(newPassword, user.getPassword());
}