spring Boot 和jpa:simple oneToMany join不返回任何数据

4ngedf3f  于 7个月前  发布在  Spring
关注(0)|答案(2)|浏览(104)

一个客户可以有多个帐户。一个帐户只属于一个客户。一个客户可以没有帐户,但一个帐户总是有一个客户。
我找不到如何在spring Boot 3.1.5(我想)中使用JPA来实现这一点。
它创建了正确的表,并插入工程,但阅读帐户的客户永远不会找到客户的帐户。
我使用的是H2 DB,它会在每次重启时自动生成模式(这是开发阶段所需的方法)
下面是客户实体:

@Data
@Entity
@NoArgsConstructor
public class Customer extends BaseEntity {

    @Id
    @SequenceGenerator(name= "customer_seq", sequenceName = "customer_seq", allocationSize = 1, initialValue = 100)
    @GeneratedValue(strategy = GenerationType.SEQUENCE,generator = "customer_seq")
    private Long id;

    @Column(length = 256, nullable = false, unique = false)
    private String email;

    @NotNull(message = "Password cannot be null")
    @Size(min = 8, max = 16, message = "Password must be between 8 and 16 characters long")
    private String password;

    // Where do you tell it the table to map to (i.e. Account) and the FK column in that table (i.e. customer_id)?
    // If it can "guess" the table by the "Account" object name, why cant it guess the FK column name is "customer_id"?
    @OneToMany(mappedBy = "customer", fetch = FetchType.EAGER)
    private Set<Account> accounts;

    public Customer(String email, String password) {
        this.email = email;
        this.password = password;
    }

    @Temporal(TemporalType.DATE) // not needed any more?
    private Date dob;  // or use sql.Date?

// other fields omitted for brevity.

}

字符串
下面是我尝试过的各种组合的帐户实体:

@Data
@Entity
@NoArgsConstructor
public class Account  {

    @Id
    @SequenceGenerator(name= "account_seq", sequenceName = "account_seq", allocationSize = 1, initialValue = 100)
    @GeneratedValue(strategy = GenerationType.SEQUENCE,generator = "account_seq")
    private Long id;

    BigDecimal balance = new BigDecimal(0);

    // How can you tell it that "customer" is the FK to the ID of the Customer Table?  It should be able to guess that.
    @ManyToOne(optional = false)

// runs, but doesnt find any accounts for any customer.
//    @OneToOne(optional = false)

// runs, but doesnt find any accounts for any customer.
//    @JoinColumn

//  This one runs, but never finds accounts for any customer.
//    @JoinColumn(name = "customer_id", nullable = false)

//  Fails with: Column 'id' is duplicated in mapping for entity 'com.ohds.bomj.entity.Account'
//    @JoinColumn(name = "id", nullable = false)

//  This one also runs, but never finds accounts for any customer.
    @JoinColumn(name = "customer_id", referencedColumnName = "id", nullable = false) // doesnt run

//  This one fails with: references a column named 'customer_id' but the target entity 'com.ohds.bomj.entity.Customer' has no property which maps to this column
//    @JoinColumn(name = "customer_id", referencedColumnName = "customer_id", nullable = false)
    Customer customer;

    public Account(Customer c) {
        this.customer = c;
    }
}


如果您查看H2控制台的以下屏幕截图,我们可以看到它在Account上创建了预期的“CUSTOMER_ID”列,并正确地使用ID为100的客户填充了该列。
然而,FK看起来不像它指向customer.id FK应该指向客户表中的ID。


的数据
下面我们可以看到customer表中的客户,其中沿着一个ID为100的客户。



要尝试读取客户帐户,我使用以下控制器:

@RestController
class CustomerController {

    Logger log = LoggerFactory.getLogger(CustomerController.class);

    private final CustomerRepository repository;

    CustomerController(CustomerRepository repository) {
        this.repository = repository;
    }

    @GetMapping("/customers")
    List<Customer> all() {

        List<Customer> customers = repository.findAll();

        // for each customer, get the accounts
        for (Customer customer : customers) {
            log.info("customer: " + customer);
            log.info("accounts: " + customer.getAccounts());
        }

        return customers;
    }
}


输出如下所示:

2023-11-13T23:32:28.770+01:00  INFO 19908 --- [nio-8085-exec-1] c.o.bomj.controller.CustomerController   : customer: Customer(id=1, firstName=null, lastName=null, [email protected], password=haha123, phone=null, accounts=[], dob=null)
2023-11-13T23:32:28.770+01:00  INFO 19908 --- [nio-8085-exec-1] c.o.bomj.controller.CustomerController   : accounts: []
2023-11-13T23:32:28.770+01:00  INFO 19908 --- [nio-8085-exec-1] c.o.bomj.controller.CustomerController   : customer: Customer(id=2, firstName=null, lastName=null, [email protected], password=foobarr123, phone=null, accounts=[], dob=null)
2023-11-13T23:32:28.770+01:00  INFO 19908 --- [nio-8085-exec-1] c.o.bomj.controller.CustomerController   : accounts: []
2023-11-13T23:32:28.770+01:00  INFO 19908 --- [nio-8085-exec-1] c.o.bomj.controller.CustomerController   : customer: Customer(id=100, firstName=bob, lastName=null, email=haha@blabla, password=12345678, phone=null, accounts=[], dob=1969-07-05)
2023-11-13T23:32:28.770+01:00  INFO 19908 --- [nio-8085-exec-1] c.o.bomj.controller.CustomerController   : accounts: []
2023-11-13T23:32:28.770+01:00  INFO 19908 --- [nio-8085-exec-1] c.o.bomj.controller.CustomerController   : customer: Customer(id=101, firstName=null, lastName=null, email=ano[email protected], password=12345678, phone=null, accounts=[], dob=null)
2023-11-13T23:32:28.770+01:00  INFO 19908 --- [nio-8085-exec-1] c.o.bomj.controller.CustomerController   : accounts: []


正如我们所看到的,它未能找到客户100的帐户。
仓库看起来像这样:

public interface CustomerRepository extends JpaRepository<Customer, Long> {}

and

public interface AccountRepository extends JpaRepository<Account, Long> {}


在Grails中,两个简单的注解将创建所需的FK并自动进行连接。

dfuffjeb

dfuffjeb1#

问题是,我猜,因为你说插入工作,当你持久化数据时,你实际上并没有将你的Account示例添加到Customer.accounts集合中。这些是POJO,JPA维护这些对象的缓存,所以如果你不维护关系的两端并保持它与数据库同步,你会得到一个空的集合,直到你刷新对象或重新启动应用程序。你不认为后者是一个解决方案,因为你一直在重新创建表。
您可以通过在其中一个示例上尝试entityManager.refresh(account)并验证调用后的状态是否与数据库中的预期状态匹配来证明这一点。
要解决这个问题而不强制查询数据库,只需确保将Account添加到Customer,并使Account引用Customer。

bkhjykvo

bkhjykvo2#

@Chris是对的,这是lombok造成的问题。似乎你不能使用**@Data**,尽管我已经看到使用它的例子。然而,@Getter和@Setter似乎是好的。
当lombok @Data被删除时,我得到了无限递归,即使是懒惰加载,因为返回一个Customer对象包括Accounts,其中包括Customers,其中包括Accounts等。为了解决这个问题,我在Account中的Customer字段中添加了**@JsonIgnore**。
我还没有检查@ToString是否能与JPA一起工作。我不知道我是否需要实现hashcode或equals。Equals有点无实际意义,因为所有客户帐户都是唯一的(即不能有两个相同的电子邮件)。
下面是工作代码(与原始问题不同的地方)

账户.java

public class Account  {

    @Id
    @SequenceGenerator(name= "account_seq", sequenceName = "account_seq", allocationSize = 1, initialValue = 100)
    @GeneratedValue(strategy = GenerationType.SEQUENCE,generator = "account_seq")
    private Long id;

    BigDecimal balance = new BigDecimal(0);

    @ManyToOne(optional = false)
    @JoinColumn(name = "customer_id", nullable = false)
    @JsonIgnore  // this is required to avoid recursion when returning customer json.
    Customer customer;

    public Account(Customer c) {
        this.customer = c;
    }

    // NOTE: this is not used by the controller to output the json.
    @Override
    public String toString() {
        return "{" +
                "id=" + id +
                ", balance=" + balance +
                '}';
    }
}  // end class Account

字符串

客户端.java

@Entity
@NoArgsConstructor
@Getter
@Setter
public class Customer extends BaseEntity {

    @Id
    @SequenceGenerator(name= "customer_seq", sequenceName = "customer_seq", allocationSize = 1, initialValue = 100)
    @GeneratedValue(strategy = GenerationType.SEQUENCE,generator = "customer_seq")
    private Long id;

    @Column(length = 64, nullable = true, unique = false)
    private String firstName;

    @Column(length = 64, nullable = true, unique = false)
    private String lastName;

    @Column(length = 256, nullable = false, unique = false)
    private String email;

    @NotNull(message = "Password cannot be null")
    @Size(min = 8, max = 16, message = "Password must be between 8 and 16 characters long")
    private String password;

    private String phone;

    @OneToMany(mappedBy = "customer")
    private Set<Account> accounts;

    public Customer(String email, String password) {
        this.email = email;
        this.password = password;
    }

    @Temporal(TemporalType.DATE) // not needed any more?
    private Date dob;  // or use sql.Date?

    @Override
    public String toString() {
        return "Customer{" +
                "id=" + id +
                ", firstName='" + firstName + '\'' +
                ", lastName='" + lastName + '\'' +
                ", email='" + email + '\'' +
                ", password='" + password + '\'' +
                ", phone='" + phone + '\'' +
                ", dob=" + dob +
                '}';
    }
} // end class Customer

输出到控制台

2023-11-14T19:14:27.747+01:00  INFO 97399 --- [nio-8085-exec-1] c.o.bomj.controller.CustomerController   : customer: Customer{id=1, firstName='null', lastName='null', email='[email protected]', password='haha123', phone='null', dob=null}
2023-11-14T19:14:27.751+01:00  INFO 97399 --- [nio-8085-exec-1] c.o.bomj.controller.CustomerController   : accounts: [Account{id=1, balance=100.00}]
2023-11-14T19:14:27.751+01:00  INFO 97399 --- [nio-8085-exec-1] c.o.bomj.controller.CustomerController   : customer: Customer{id=2, firstName='null', lastName='null', email='[email protected]', password='foobarr123', phone='null', dob=null}
2023-11-14T19:14:27.751+01:00  INFO 97399 --- [nio-8085-exec-1] c.o.bomj.controller.CustomerController   : accounts: [Account{id=2, balance=200.00}]
2023-11-14T19:14:27.751+01:00  INFO 97399 --- [nio-8085-exec-1] c.o.bomj.controller.CustomerController   : customer: Customer{id=100, firstName='bob', lastName='null', email='haha@blabla', password='12345678', phone='null', dob=1969-07-05}
2023-11-14T19:14:27.752+01:00  INFO 97399 --- [nio-8085-exec-1] c.o.bomj.controller.CustomerController   : accounts: [Account{id=100, balance=0.00}]
2023-11-14T19:14:27.752+01:00  INFO 97399 --- [nio-8085-exec-1] c.o.bomj.controller.CustomerController   : customer: Customer{id=101, firstName='null', lastName='null', email='[email protected]', password='12345678', phone='null', dob=null}
2023-11-14T19:14:27.752+01:00  INFO 97399 --- [nio-8085-exec-1] c.o.bomj.controller.CustomerController   : accounts: []

复位端点输出

[{"createdAt":null,"updatedAt":null,"version":null,"id":1,"firstName":null,"lastName":null,"email":"[email protected]","password":"haha123","phone":null,"accounts":[{"id":1,"balance":100.00}],"dob":null},{"createdAt":null,"updatedAt":null,"version":null,"id":2,"firstName":null,"lastName":null,"email":"[email protected]","password":"foobarr123","phone":null,"accounts":[{"id":2,"balance":200.00}],"dob":null},{"createdAt":"2023-11-14T18:14:20.386726Z","updatedAt":"2023-11-14T18:14:20.420915Z","version":1,"id":100,"firstName":"bob","lastName":null,"email":"haha@blabla","password":"12345678","phone":null,"accounts":[{"id":100,"balance":0.00}],"dob":"1969-07-05"},{"createdAt":"2023-11-14T18:14:20.400159Z","updatedAt":"2023-11-14T18:14:20.400166Z","version":0,"id":101,"firstName":null,"lastName":null,"email":"[email protected]","password":"12345678","phone":null,"accounts":[],"dob":null}]

相关问题