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

Я новичок в Spring и не могу понять, как объединить несколько таблиц, чтобы получить какой-то результат. Я попытался реализовать небольшое приложение библиотеки, как показано ниже.

Мои классы сущностей - книга, клиент, заказы


Book.java - книги, доступные в библиотеке

@Entity
@Table(name = "books")
public class Book {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id", columnDefinition = "int")
    private int id;

    @NotNull(message = "Book name cannot be null")
    @Column(name = "book_name", columnDefinition = "VARCHAR(255)")
    private String bookName;

    @Column(name = "author", columnDefinition = "VARCHAR(255)")
    private String author;

    // getters and setters

    public Book() {}

    public Book(String bookName, String author) {
        this.bookName = bookName;
        this.author = author;
    }
}

Customer.java - Клиенты, зарегистрированные в библиотеке

@Entity
@Table(name = "customer", uniqueConstraints = {@UniqueConstraint(columnNames = {"phone"})})
public class Customer {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id", columnDefinition = "int")
    private int id;

    @NotNull(message = "Customer name cannot be null")
    @Column(name = "name", columnDefinition = "VARCHAR(255)")
    private String name;

    @Column(name = "phone", columnDefinition = "VARCHAR(15)")
    private String phone;

    @Column(name = "registered", columnDefinition = "DATETIME")
    private String registered;

    // getters and setters

    public Customer() {}

    public Customer(String name, String phone, String registered) {
        this.name = name;
        this.phone = phone;
        this.registered = registered;
    }
}

Booking.java - Все бронирования, сделанные клиентами

@Entity
@Table(name = "bookings")
public class Booking {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id", columnDefinition = "int")
    private int id;

    @NotNull(message = "Book id cannot be null")
    @Column(name = "book_id", columnDefinition = "int")
    private int bookId;

    @NotNull(message = "Customer id cannot be null")
    @Column(name = "customer_id", columnDefinition = "int")
    private int customerId;

    @Column(name = "issue_date", columnDefinition = "DATETIME")
    private String issueDate;

    @Column(name = "return_date", columnDefinition = "DATETIME")
    private String returnDate;

    // getters and setters

    public Booking() {}

    public Booking(int bookId, int customerId, String issueDate) {
        this.bookId = bookId;
        this.customerId = customerId;
        this.issueDate = issueDate;
    }
}

Теперь схемы таблиц для соответствующих сущностей выглядят следующим образом:

books:
+-----------+--------------+------+-----+---------+----------------+
| Field     | Type         | Null | Key | Default | Extra          |
+-----------+--------------+------+-----+---------+----------------+
| id        | int(11)      | NO   | PRI | NULL    | auto_increment |
| book_name | varchar(255) | NO   |     | NULL    |                |
| author    | varchar(255) | YES  |     | NULL    |                |
+-----------+--------------+------+-----+---------+----------------+
id - primary key

customer:
+------------+--------------+------+-----+-------------------+-------------------+
| Field      | Type         | Null | Key | Default           | Extra             |
+------------+--------------+------+-----+-------------------+-------------------+
| id         | int(11)      | NO   | PRI | NULL              | auto_increment    |
| name       | varchar(255) | NO   |     | NULL              |                   |
| registered | datetime     | YES  |     | CURRENT_TIMESTAMP | DEFAULT_GENERATED |
| phone      | varchar(15)  | YES  | UNI | NULL              |                   |
+------------+--------------+------+-----+-------------------+-------------------+
id - primary key

bookings:
+-------------+----------+------+-----+-------------------+-------------------+
| Field       | Type     | Null | Key | Default           | Extra             |
+-------------+----------+------+-----+-------------------+-------------------+
| id          | int(11)  | NO   | PRI | NULL              | auto_increment    |
| book_id     | int(11)  | NO   | MUL | NULL              |                   |
| customer_id | int(11)  | NO   | MUL | NULL              |                   |
| issue_date  | datetime | YES  |     | CURRENT_TIMESTAMP | DEFAULT_GENERATED |
| return_date | datetime | YES  |     | NULL              |                   |
+-------------+----------+------+-----+-------------------+-------------------+
id - primary key
book_id - foreign key references books.id
customer_id - foreign key references customer.id

Теперь то, что я хочу сделать, это дать некоторые критерии бронирования, такие как телефон клиента или имя автора и т. д., Я хочу вернуть все бронирования, связанные с этим заказом. Я покажу пример Booking API, чтобы объяснить.

Контролер бронирования:

@RestController
@RequestMapping("/bookings")
public class BookingController {
    @Autowired
    BookingService bookingService;

    // some booking apis which return Booking objects

    @GetMapping
    public List<Booking> getAllBookingsBy(@RequestParam("phone") String phone,
                                         @RequestParam("authors") List<String> authors) {
        return bookingService.getAllBy(phone, authors);
    }

    @PostMapping
    public Booking addBooking(@RequestBody Booking booking) {
        bookingService.saveBooking(booking);
        return booking;
    }
}

Класс обслуживания бронирования:

@Service
public class BookingService {
    @Autowired
    private BookingRepository bookingRepository;

    // some booking service methods

    // get all bookings booked by a customer with matching phone number and books written by a given list of authors
    public List<Booking> getAllBy(String phone, List<String> authors) {
    return bookingRepository.queryBy(phone, authors);
    }

    public void saveBooking(Booking booking) {
        bookingRepository.save(booking);
    }
}

Класс репозитория бронирования:

@Repository
public interface BookingRepository extends JpaRepository<Booking, Integer> {
    // some booking repository methods

    @Query(value = "SELECT * FROM bookings bs WHERE " +
            "EXISTS (SELECT 1 FROM customer c WHERE bs.customer_id = c.id AND c.phone = :phone) " +
            "AND EXISTS (SELECT 1 FROM books b WHERE b.id = bs.book_id AND b.author IN :authors)",
            nativeQuery = true)
    List<Booking> queryBy(@Param("phone") String phone,
                            @Param("authors") List<String> authors);
}

Теперь нажатие на показанный контроллер бронирования вернет объект бронирования, который выглядит следующим образом:

[
    {
        "id": 3,
        "book_id": 5,
        "customer_id": 2,
        "issue_date": "2019-02-04 01:45:21",
        "return_date": null
    }
]

Но я не хочу так, я хочу вернуть вместе с ними имя заказчика этого бронирования, а также название книги. Итак, я хочу, чтобы объекты бронирования, возвращаемые контроллером, выглядели так:

[
    {
        "id": 3,
        "book_id": 5,
        "customer_id": 2,
        "issue_date": "2019-02-04 01:45:21",
        "return_date": null,
        "customer_name": "Cust 2",
        "book_name": "Book_2_2",
    }
]

Может ли кто-нибудь помочь в этом? Я застрял, так как не могу продолжить.

#################### Обновлено: Я добавил эти однонаправленные связи «один к одному» в свой класс Booking:

@OneToOne
@JoinColumn(name = "book_id", insertable = false, updatable = false)
private Book book;

@OneToOne
@JoinColumn(name = "customer_id", insertable = false, updatable = false)
private Customer customer;

Но теперь, когда я нажимаю на свой контроллер, я получаю все объекты Book и Customer в своем объекте Booking. Итак, что я могу сделать, чтобы просто вернуть название книги и имя клиента в объекте бронирования? Вот как теперь выглядит мой возвращенный объект Booking:

[
    {
        "id": 3,
        "book_id": 5,
        "book": {
            "id": 5,
            "book_name": "Book_2_2",
            "author": "author_2"
        },
        "customer_id": 2,
        "customer": {
            "id": 2,
            "name": "Cust 2",
            "phone": "98765431",
            "registered": "2019-02-04 01:13:16"
        },
        "issue_date": "2019-02-04 01:45:21",
        "return_date": null
    }
]

Кроме того, теперь мой API-интерфейс save() в моем контроллере бронирования не работает, потому что, когда я отправляю ему объект типа Booking, bookId и customerId каким-то образом становятся равными 0, чего не было до того, как я добавил эти изменения .

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

Nilupul Heshan 13.05.2021 09:29
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
24
1
75 910
3

Ответы 3

Запрос, который у вас есть, - не лучший способ объединения таблиц. Более интуитивный способ такой

SELECT * FROM bookings
WHERE customer_id in (SELECT id FROM customer WHERE phone = :phone)
 AND book_id in (SELECT id FROM books WHERE author IN :authors)

Он спрашивает, как получить конечное представление json. У него нет ни правильных отображений json, ни правильных отображений гибернации. SQL-запрос — его последняя проблема в коде.

Alexander Petrov 03.02.2019 22:24

То, что вы делаете, неправильно. Вы возвращаете Booking и ожидаете, что он волшебным образом десериализуется в сущность, содержащую информацию о присоединении, такую ​​как Book Name. Но в вашем запросе выбора в репозитории вы выбрали файл Booking. Как обстоят дела в вашей реализации, Booking не содержит информации о Книге.

Сначала вам нужно разделить то, что вы будете десериализовать как JSON, и то, что вы будете использовать в качестве уровня сохранения по отношению к вашим данным Spring.

  1. Для начала создайте отношение @OneToOne/@OneToMany от Booking к Book.
  2. Измените свой запрос, чтобы он выполнял активную выборку объекта/коллекции, которую вы сопоставили как книгу.
  3. Создайте POJO и аннотируйте его аннотациями JSON так, как вы хотите, чтобы он возвращался контроллером.
  4. Сопоставьте между вашим объектом сохранения/бронированием с hidrated collection на Book и вашим недавно созданным POJO

На самом деле, если вы сопоставляете как OneToOne, инициализация по умолчанию становится EAGER, поэтому ваш запрос становится немного ненужным.

Если мы предполагаем, что у вас есть сопоставления прямо на постоянном уровне, ваш запрос будет выглядеть так:

@Query(value = "SELECT * FROM bookings bs WHERE " +
            "bs.customer.phone = :phone) " +
            "AND  bs.book.author IN :authors)")

Вот ваша документация по сопоставлению из Hibernate> http://docs.jboss.org/hibernate/orm/5.4/userguide/html_single/Hibernate_User_Guide.html#associations

Я не ожидаю, что он «волшебным образом десериализуется» в другой объект, я знаю, что он вернет объект Booking. Я просто хочу знать, какие изменения я могу добавить, чтобы получить имя клиента и название книги в дополнение к объекту бронирования.

user3248186 03.02.2019 22:28

Читайте выше :) Объяснено, спрашивайте, если что-то не понимаете.

Alexander Petrov 03.02.2019 22:28

@user3248186 user3248186 ваша главная ошибка в том, что вы не используете Hibernate для того, чем он является — Object RElational Mapping. Нанесите на карту все ваши отношения с соответствующими аннотациями.

Alexander Petrov 03.02.2019 22:30

Не могли бы вы добавить какие-либо ресурсы, откуда я могу узнать больше об этом?

user3248186 03.02.2019 22:31

добавил ссылку на официальную документацию.

Alexander Petrov 03.02.2019 22:34

Проверил сопоставления OneToOne, ManyToOne и т. д. и аннотации joinColumn, но я не совсем понимаю аннотации JSON для настройки объекта, отправляемого контроллером.

user3248186 04.02.2019 07:43

@user3248186 user3248186 не смешивайте аннотации json с объектами гибернации. Напишите второй POJO для представления вашего dtos

Alexander Petrov 08.02.2019 22:10

Вы можете реализовать его в соответствии с приведенными ниже шагами.

  1. Создайте новый интерфейс с геттерами для всех полей, которые вам нужны в ответ.
  2. В строке запроса внутри @Query вам нужно указать имена столбцов в select. Примечание. Эти имена должны быть синхронизированы с геттерами, которые вы создаете в своем интерфейсе.
  3. Используйте этот интерфейс в качестве возвращаемого типа вашего метода репозитория.

Для получения дополнительной информации вы можете обратиться к Projects in spring data rest. https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#projections

Другие вопросы по теме