如何在Spring JPA存储库中联接多个表的结果

时间:2019-02-03 21:03:21

标签: java spring spring-boot spring-data-jpa

我是Spring的新手,我无法弄清楚如何联接多个表以返回一些结果。我试图实现一个小型的Library应用程序,如下所示。

我的实体类别-图书,客户,预订

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;
    }
}

Booking Service类:

@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",
    }
]

有人可以帮忙吗?我被困住了,因为我无法从这里继续。

编辑: 我在预订类中添加了这些单向的onetoone关联:

@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对象中。那么,如何在预订对象中返回书名和客户名呢?这是我的预订对象返回的样子:

[
    {
        "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
    }
]

现在我的预订控制器中的save()api也无法正常工作,因为当我向其发送类型为Booking的对象时,bookId和customerId以某种方式变为0,这在我之前没有发生添加了这些更改。

3 个答案:

答案 0 :(得分:0)

您拥有的查询不是联接表的最佳方法。 更直观的方式就是这样

{{1}}

答案 1 :(得分:0)

您所做的是错误的。您正在返回预订,并且希望它神奇地反序列化为包含诸如书名之类的联接信息的实体。但是,在对存储库的选择查询中,您已经选择了预订。实施过程中的方式预订不包含有关预订的信息。

首先,您需要分离将反序列化为JSON的内容和将用作对Spring数据的持久化层的内容。

1.Make a @OneToOne/@OneToMany relationship from Booking to Book as a start. 
2.Change your query to do eager fetching on the entity/collection you have mapped as Book.
3. Make a POJO and annotate it with JSON annotations the way you want it to be returned by the controller.
4. Map between your persistence object / Booking with hidrated collection on Book and your newly created 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

答案 2 :(得分:0)

您可以按照以下步骤实施。

  1. 使用吸气剂为您需要响应的所有字段创建一个新接口。
  2. 在@Query中的查询字符串中,您需要为select中的列提供名称。注意:这些名称必须与您在界面中创建的getter保持同步。
  3. 将此接口用作存储库方法的返回类型。

有关更多信息,您可以参考Spring数据表中的Projections。 https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#projections