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

#spring #spring-boot #hibernate #jpa

Вопрос:

У меня есть две модели, Account и Transaction . Account Выглядит это следующим образом:

 public class Account {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Integer id;

    private BigDecimal balance;

    @OneToMany(fetch = FetchType.LAZY)
    private List<Transaction> transactions;

    public Account(final BigDecimal balance) {
        this.balance = balance;
        this.transactions = new ArrayList<>();
    }

    public void addTransaction(Transaction transaction) {
        this.transactions.add(transaction);
    }
}
 

Transaction Сущность заключается в следующем:

 public class Transaction {
    @Id
    @GeneratedValue(strategy= GenerationType.AUTO)
    private Integer id;

    @OneToOne(fetch=FetchType.LAZY)
    private Account sender;

    @OneToOne(fetch=FetchType.LAZY)
    private Account recipient;

    private BigDecimal amount;

    public Transaction(Account sender, Account recipient, BigDecimal amount) {
        this.sender = sender;
        this.recipient = recipient;
        this.amount = amount;
    }
}
 

Как вы можете видеть, a Transaction находится между a Sender и Recipient , оба они представлены Account сущностью. В этом случае a Transaction будет иметь сопоставление Один к одному для Отправителя и то же самое для Получателя. Однако, с другой стороны, Account организация должна иметь возможность представлять все транзакции со счета, независимо от того, является ли она отправителем или получателем. Использование приведенного выше сопоставления позволяет мне вставлять транзакции, однако я не могу вернуть их в сопоставление. По сути, я не уверен, какие аннотации лучше всего использовать в этом случае.

Я должен добавить, что, используя приведенный выше код, я получаю следующую ошибку hibernate:

 Caused by: org.hibernate.AnnotationException: A Foreign key refering com.xxx..Account from com.xxx.Transaction has the wrong number of column. should be 1
 

Комментарии:

1. Я бы полностью опустил Account.transactions сторону отношений и извлек транзакции учетной записи с помощью чего-то вроде SELECT t FROM Transaction t WHERE t.sender=:account OR t.recipient=:account (JPQL).

Ответ №1:

Я предполагаю, что an Account может быть отправителем/получателем в нескольких транзакциях:

  1. Отправитель и получатель на самом деле являются @ManyToOne :
     public class Transaction {
     @Id
     @GeneratedValue(strategy= GenerationType.AUTO)
     private Integer id;
    
     @ManyToOne(fetch=FetchType.LAZY, optional = false)
     private Account sender;
    
     @ManyToOne(fetch=FetchType.LAZY, optional = false)
     private Account recipient;
    
     private BigDecimal amount;
    
     public Transaction(Account sender, Account recipient, BigDecimal amount {
         this.sender = sender;
         this.recipient = recipient;
         this.amount = amount;
     }
    }
     

    Это потому, что один Account может быть отправителем для многих транзакций, но у одного Transaction может быть только отправитель. То же самое касается приемника.
    Я также предполагаю, что у всех транзакций должны быть отправитель и получатель.

  2. @OneToMany в двух разных колумах.

    Вы создаете соотношение «один ко многим Account «, но в каком столбце вы ожидаете отобразить ассоциацию Transaction ? Поскольку существует два возможных столбца, содержащих учетную запись, Hibernate ORM не знает, какой из них использовать, и выдает исключение.

Удаление связи с Account должно решить вашу проблему:

 public class Account {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Integer id;

    private BigDecimal balance;

    public Account(final BigDecimal balance) {
        this.balance = balance;
    }
}
 

Когда вам понадобятся все транзакции для определенной учетной записи, вы можете выполнить следующий запрос JPQL. Например, с помощью EntityManager:

 entityManager
    .createQuery("FROM Transaction t WHERE t.sender=:account OR t.recipient=:account")
    .setParameter("account", account )
    .getResultList();
 

Комментарии:

1. Это имеет смысл, однако, я попробовал другой подход, который заключается в @OneToOne сопоставлении как отправителя, так и получателя, Transaction и вместо этого создал два списка в Account : @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "sender") List<Transaction> sender_transactions и то же самое для транзакций получателей. В этом случае я затем создал Временное поле, которое объединяет эти два списка при запросе всех транзакций, Account и это, похоже, работает нормально. Тем не менее, мне интересно, является ли хорошей практикой делать это таким образом или, как предлагается, только одним отображением…

2. Вам нужны два отдельных запроса, а затем вам нужно объединить результаты. Кажется, что это большая дополнительная работа, которой вы могли бы избежать с помощью одного запроса к БД. Но это зависит от вас. Вам все равно нужно изменить сопоставление на @ManyToOne (вместо @OneToOne ), если учетная запись может быть отправителем/получателем для более чем одной транзакции.