Как сделать вызов репозитория с разбивкой на страницы из объекта столбца в Spring Boot JPA

У меня есть этот класс модели объекта (Книга), где автор может написать несколько книг.

@Entity
@Table(name="book")
public class Book {
 
    @Id
    @GeneratedValue
    private Long id;
     
    @Column(name="book_name")
    private String bookName;
     
    @Column(name="author_id")
    private Long authorId;
     
    //Setters and getters
}

В моем проекте Spring Boot я не хочу иметь таблицу авторов, поскольку есть сторонняя служба, которая определяет авторов и их идентификаторы, как я могу вызвать репозиторий с разбивкой на страницы для всех идентификаторов авторов и их книг?

Я хотел бы иметь конечную точку, которая принимает (страницу, размер) и возвращает разбитый на страницы список AuthorDTO следующим образом:

public abstract class AuthorDTO implements Serializable {
    public abstract Long authorId();

    public abstract List<Book> books();
}
[
    {
        "authorId": 123,
        "books": [...]
    },
    ...

]

Моя первая мысль - создать вызов репозитория, не зная, как мы можем получить страницу пользовательского объекта. Это недопустимо ниже, но я хотел бы сделать что-то вроде следующего.

Page<AuthorDTO> findAllBooksGroupedByAuthorId(Pageable pageable);


person GusGus    schedule 13.11.2020    source источник


Ответы (1)


Ваш код, кажется, предполагает, что вы пытаетесь показать отношение внешнего ключа в классе как идентификатор. JPA на самом деле этого не делает. JPA = Java Persistence Language, т. е. вы представляете отношения между классами Java, которые отражают базу данных.

Таким образом, в базе данных у вас может быть внешний ключ, например 'author_id' в таблице книг, но на стороне JPA/Java это будет класс Author, а не просто long/int.

Я надеюсь, что ниже поможет. Я только что добавил его в main() моего кода, так что он может быть не идеальным, но я также оставил несколько комментариев.

Когда у вас есть Page<Book>, вы можете сопоставить его с DTO в java.

Поскольку запрос получает книги по идентификатору автора, мы можем предположить, что все они имеют один и тот же идентификатор автора... поэтому нет реальной необходимости пытаться получить эту проекцию в базе данных.

РЕДАКТИРОВАТЬ: Разве вообще невозможно получить ссылку на автора от третьего лица?

т.е. Я не знаю, как вы заполняете Книгу ... но не могли бы вы, когда вы получаете Книгу от третьей стороны, посмотреть, есть ли у вас объект Автора с книгами 'author_id', и не сохранять нового Автора с этим идентификатором, если он не существует уже не существует?

В этом случае вы можете сделать AuthorRepo и просто запросить, например:

Page<Author> findAllBy(Pageable page)

==========================================================================

Кажется, что вы получаете страницу книг по идентификатору автора... у вас действительно должно быть отношение JPA, чтобы показать, что:

    @Entity
    private class Book{

        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private long id;

        @Column(name = "book_name")
        private String name;

        //Technically this could be Many:Many as a book could have 2 authors? If so....@ManyToMany
        //For simplicity (and what you seem to want) Many Books have ONE author.
        @ManyToOne(fetch = FetchType.LAZY)
        private Author author;

    }

    @Entity
    private class Author{

        //ID here - omitted for clarity

        @Column(name = "authors_name")
        String name;

        //The Author has many books.
        // Mapped by shows the bi-direction relationship. You can then do 'Author.getAuthorsBooks()'
        //Lazy means it wont fetch all the books from database/(hibernate wont) when you do AuthorRepo.get()
        //and will only do the `JOIN ON Books where` if you do Author.getAuthorsBooks()
        @OneToMany(fetch = FetchType.LAZY,mappedBy = "author")
        private Set<Book> authorsBooks = new HashSet<>();
    }

    private interface AuthorRepo extends JpaRepository<Author,Long>{
        //Note the JPA syntax.
        Page<Book> findAll(Pageable pageable);
    }


РЕДАКТИРОВАТЬ: Я написал это только в пустой файл... так что может потребоваться настройка или опечатки и т. д.

Если по какой-то причине у вас НЕ может быть отдельного объекта для автора, вам нужно сохранить свой объект в том виде, в котором он есть в настоящее время... Я бы сделал 2 запроса.

Я чувствую, что вы можете сделать это разными способами.

Если вы ДОЛЖНЫ придерживаться Spring Pageable:

Получите запрос страницы в контроллере и сделайте его новым PageRequest.of(pagenum,size)

и введите его, чтобы выполнить запрос страницы ниже

List<Long> getPageOfUniqueAuthorIds(Pageable pageable);

Это даст страницу идентификаторов авторов.

Затем вы хотите использовать этот список длинных объектов (aithorIds) для выполнения второго запроса.

List<AuthorDTOProjection> getBooksAndAuthorIdsWithAuthorsIdsIn(List<Long> authorIds);
    @Entity
    @Table(name="book")
    public class Book {

        @Id
        @GeneratedValue
        private Long id;

        @Column(name="book_name")
        private String bookName;

        @Column(name="author_id")
        private Long authorId;

        //Setters and getters
    }



    private interface BookRepo extends JpaRepository<Book,Long> {

        //The countQuery is required by Spring Paging.
        //Hibernate will need to use the count query when doing paging on a native query.
        @Query(nativeQuery = true,
        value = "SELECT DISTINCT(author_id) FROM book b ",
        countQuery = "SELECT count(*) \n" +
                "FROM (SELECT DISTINCT(author_id) FROM book b) authorIds ")
        List<Long> getPageOfUniqueAuthorIds(Pageable pageable);

        //This is not paged. You want all books with the author IDs from the page query above.
      List<Book> findAllByAuthorIdIn(List<Long> authorIds);
    }

Затем вам нужно будет сопоставить Entity с DTO на уровне вашего сервиса.

        @Autowired
        BookRepo bookRepo;

        //This would be from the controller method...not declared here...
        Pageable pageableFromController = PageRequest.of(0,10);

        List<Long> pageOfUniqueAuthorIds = bookRepo.getPageOfUniqueAuthorIds(pageableFromController);

        //Get All the books with Author Ids.
        List<Book> books = bookRepo.findAllByAuthorIdIn(pageOfUniqueAuthorIds);

        //Your abstract AuthorDTO.
        abstract class AuthorDTO implements Serializable {
            public abstract Long authorId();

            public abstract List<Book> books();
        }

        //Your Author DTO needs to be implemented so I made a "View".
        @AllArgsConstructor
        class AuthorView extends AuthorDTO{

            private long authorId;
            private List<Book> books;

            @Override
            public Long authorId() {
                return authorId;
            }

            @Override
            public List<Book> books() {
                return books;
            }
        }

        //Get a List of the authorIds in the List<Books>. Could also use the original Page<Long> authorIds...
        //As an author without a book is not possible in your database.
        final List<Long> authorIdsInBooks = books.stream().map(it -> it.authorId).distinct().collect(Collectors.toList());

        //Map the Ids of authors to an Impl of your abstract DTO. Personally I don't see why the AuthorDTO is abstract.
        //I'd have expected just an abstract DTO class called "DTO" or something and then AuthorDTO impl that.
        //But as the way you have it this will work. I guess you may want more impl of the  AuthorDTO so maybe leave the AuthorDTO as abstract.
        //This can be returned to client.
        final List<AuthorView> authorViews = authorIdsInBooks.stream()
                .map(authorId -> new AuthorView(
                        authorId,
                        books.stream().filter(it -> it.authorId.equals(authorId)).collect(Collectors.toList()))
                )
                .collect(Collectors.toList());

person Jcov    schedule 13.11.2020
comment
Привет! Спасибо за помощь! Так что я не уверен, что это то, что я хочу. Я хотел бы иметь конечную точку, которая дает мне страницу уникальных авторов (со всеми их книгами). Как Page<Book> findAllByAuthor_Id(long authorId, Pageable pageable); мне в этом поможет? - person GusGus; 14.11.2020
comment
Чтобы уточнить, мне нужно решение, которое позволяет мне не создавать класс Author. так как я не хочу следить за тем, что существует у Автора. Я хотел бы иметь возможность запрашивать страницы из столбца «author_id» класса сущности «Книга». Разве что нельзя? - person GusGus; 14.11.2020
comment
ХОРОШО. Я думаю, что это сложнее без сущности Автора. Я отредактирую свой ответ. В любом случае, нет ли возможности иметь класс Author, который просто содержит идентификатор автора этой удаленной службы? - person Jcov; 14.11.2020