Рубрики
Без рубрики

Как сопоставить ассоциацию @ManyToOne с использованием столбца непервичного ключа с JPA и гибернацией

Автор оригинала: Vlad Mihalcea.

Отвечая на вопросы на форуме Hibernate , я наткнулся на следующий вопрос об использовании аннотации @ManyToOne , когда столбец внешнего ключа на стороне клиента ссылается на столбец непервичного ключа на родительской стороне.

В этой статье вы увидите, как использовать аннотацию @JoinColumn для размещения ассоциаций “многие к одному”, не относящихся к первичному ключу.

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

Столбец isbn в таблицах публикация и книга связаны с помощью ограничения внешнего ключа, которое является основой нашей @ManyToOne ассоциации:

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

@Entity(name = "Book")
@Table(name = "book")
public class Book 
    implements Serializable {

    @Id
    @GeneratedValue
    private Long id;

    private String title;

    private String author;

    @NaturalId
    private String isbn;

    //Getters and setters omitted for brevity
}

Столбец isbn отображается как @Natural , поскольку его также можно использовать в качестве бизнес-ключа.

Для получения более подробной информации об аннотации @Natural ознакомьтесь с этой статьей .

Публикация представляет дочернюю структуру ассоциации, поэтому она будет отображена следующим образом:

@Entity(name = "Publication")
@Table(name = "publication")
public class Publication {

    @Id
    @GeneratedValue
    private Long id;

    private String publisher;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(
        name = "isbn", 
        referencedColumnName = "isbn"
    )
    private Book book;

    @Column(
        name = "price_in_cents", 
        nullable = false
    )
    private Integer priceCents;

    private String currency;

    //Getters and setters omitted for brevity
}

По умолчанию ассоциация @ManyToOne предполагает, что идентификатор сущности на стороне родителя должен использоваться для соединения со столбцом внешнего ключа сущности на стороне клиента.

Однако при использовании связи с непервичным ключом referencedColumnName следует использовать для указания Hibernate, какой столбец следует использовать на родительской стороне для установления связи базы данных “многие к одному”.

Предполагая, что в нашей базе данных есть следующие объекты:

Book book = new Book();
book.setTitle( "High-Performance Java Persistence" );
book.setAuthor( "Vlad Mihalcea" );
book.setIsbn( "978-9730228236" );
entityManager.persist(book);

Publication amazonUs = new Publication();
amazonUs.setPublisher( "amazon.com" );
amazonUs.setBook( book );
amazonUs.setPriceCents( 4599 );
amazonUs.setCurrency( "$" );
entityManager.persist( amazonUs );

Publication amazonUk = new Publication();
amazonUk.setPublisher( "amazon.co.uk" );
amazonUk.setBook( book );
amazonUk.setPriceCents( 3545 );
amazonUk.setCurrency( "&" );
entityManager.persist( amazonUk );

После извлечения Публикации вместе с связанной с ней Книгой мы видим , что @ManyToOne ассоциация работает должным образом:

Publication publication = entityManager.createQuery(
    "select p " +
    "from Publication p " +
    "join fetch p.book b " +
    "where " +
    "   b.isbn = :isbn and " +
    "   p.currency = :currency", Publication.class)
.setParameter( "isbn", "978-9730228236" )
.setParameter( "currency", "&" )
.getSingleResult();

assertEquals(
    "amazon.co.uk",
    publication.getPublisher()
);

assertEquals(
    "High-Performance Java Persistence",
    publication.getBook().getTitle()
);

При выполнении приведенного выше запроса JPQL Hibernate генерирует следующую инструкцию SQL:

SELECT 
        p.id AS id1_1_0_, b.id AS id1_0_1_,
        p.isbn AS isbn5_1_0_, p.currency AS currency2_1_0_,
        p.price_in_cents AS price_in3_1_0_,
        p.publisher AS publishe4_1_0_,
        b.author AS author2_0_1_, b.isbn AS isbn3_0_1_,
        b.title AS title4_0_1_
FROM    publication p
INNER JOIN 
        book b ON p.isbn = b.isbn
WHERE   b.isbn = '978-9730228236'
        AND p.currency = '&'

Как вы можете видеть, referencedColumnName позволяет настроить предложение JOIN | ON таким образом, чтобы столбец isbn использовался вместо идентификатора сущности по умолчанию.

Если вы хотите представить ассоциацию @ManyToOne , не являющуюся первичным ключом, вам следует использовать атрибут referencedColumnName аннотации @JoinColumn|/.

Для более сложных ситуаций, например, когда вам нужно использовать пользовательскую функцию SQL в предложении JOIN | ON , вы можете использовать специальную аннотацию @JoinFormula для спящего режима .