JPA entity mapping

Главное в любом ORM решении, это описать, как ваши классы (entity) отображаются (maps) на реляционные таблицы. В это делается с помощью аннотаций.

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

  • У класса должен быть конструктор без аргументов, имеющий уровень доступа public или protected. Допускается иметь и другие конструкторы.
  • Класс не должен быть final. Равно не должны быть final его методы и сохраняемые переменные.
  •  Если объект Entity класса будет передаваться по значению как отдельный объект (detached object), например через удаленный интерфейс (through a remote interface), он так же должен реализовывать Serializable интерфейс.
  • Классы сущностей могут быть унаследовано от других классов, которые могут быть jpa сущностями, а могут и не быть. Обратно и от jpa сущностей могут быть унаследованы обычные классы.
  • Сохраняемые переменные не должны быть public и доступ к ним должен предоставляться только через вызовы методов класса.

JPA mapped entity

Поскольку java это, чаще всего, кровавый энтерпрайз, то примерчик будет соответствующий: журнал финансовых операций. Каждая операция имеет счёт, сумму, дату операции, номер транзакции и, опционально, описание и некий код.

Рассмотрим подробно:

@Entity  говорит JPA, что этот класс явно имеет отношение к базе данных и должен быть в ней сохранён и прочитан обратно. Эта аннотация является обязательной.
@Table описывает главную таблицу, в которой должны быть сохранены данные класса. Поле name задаёт имя таблицы, если его опустить, имя таблицы будет совпадать с именем класса. Кроме имени можно дополнительно задать схему и каталог для размещения таблицы. indexes и @Index описывают индексы, которые должны быть созданы для таблицы. uniqueConstraints и @UniqueConstraint — ограничения уникальности значений полей или групп полей для таблицы. И indexes и uniqueConstraints используются только при создании таблицы средствами JPA. В случае, если таблицы создаётся каким-либо другим путём, эти опции будут проигнорированы. Аннотация @Table, равно как и все её опции, не является обязательной.
@SecondaryTable (и @SecondaryTables, если одной недостаточно) сообщают JPA, что класс должен сохраняться в нескольких таблицах. Указывать  name  в данном случае обязательно, так как это задаёт имя дополнительной таблицы. pkJoinColumns описывает связь между основной и дополнительной таблицами. Этот функционал полезен, если у вас есть единая сущность, часть полей которых используется одним способом, а часть другим. Например, в моём примере с финансовой операцией, я выделяю опциональные данные в отдельную таблицу, которая может храниться в отдельном tablespace, который более медленный, но более объёмный.  Либо наоборот, когда у вас уже есть один объект, разбитый на несколько таблиц, в java он может быть автоматически представлен одним классом. Аннотация @SecondaryTable очевидно не является обязательной.
Аннотация @Column  указывает JPA, как именно сохранять это поле в базу. name задаёт имя столбца, если его опустить, по умолчанию используется имя поля. updatable/insertable указывают можно либо значение поля изменять или вставлять при создании записи. nullable сообщает JPA, может ли поле быть null или нет. Значение nullable используется и при создании таблиц и при сохранении изменений. Так же можно задать опцию unique, которая добавляется к описанию uniqueContraints  на уровне всего класса и делает конкретное поле уникальным. Так же, как и uniqueConstraints, unique используется только при создании таблиц.

Аннотация @Column  не является обязательной. По умолчанию все поля класса сохраняются в базе данных. Если поле не должно быть сохранено, оно должно быть проаннотированно аннотацией @Transient.

@Id и @GeneratedValue говорят, что это поле — первичный ключ и что его значения должны создаваться автоматически.
В аннотации @Column можно так же задать и размерности для полей. length для строковых значений задаёт размер соответствующего поля в базе данных, например  CHAR(length) или VARCHAR(length). Для десятичных типов данных, таких как BigDecimal, ипользуются опции scale и precision. scale=2, precision=10 задают десятичное число, у которого десять знаков до запятой и два после: 1234567890.12
Наконец, @Column используется, чтобы указать, в какой таблице сохранять поле, если заданые дополнительные таблицы.

Использование

Создадим операцию, сохраним её в базу и прочитаем обратно:

Обратите внимание, что несмотря на то, что таблица называется journal, запрос делается from Operation, то есть по имени сущности.

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

journal
Таблица JOURNAL и её данные
operations_details
Таблица OPERATIONS_DETAILS и её данные

Код примера доступен на github.