Статьи

Учебник JPA: Составление карт сущностей — часть 2

В моем последнем посте я показал простой способ сохранения сущности. Я объяснил подход по умолчанию, который JPA использует для определения таблицы по умолчанию для объекта. Давайте предположим, что мы хотим переопределить это имя по умолчанию. Мы можем захотеть сделать это, потому что модель данных была разработана и исправлена ​​ранее, а имена таблиц не совпадают с именами наших классов (например, я видел людей, которые создавали таблицы с префиксом «tbl_»). Итак, как мы должны переопределить имена таблиц по умолчанию, чтобы соответствовать существующей модели данных?

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

  1. Мы можем использовать атрибут name аннотации @Entity, чтобы предоставить явное имя сущности, совпадающее с именем таблицы базы данных. В нашем примере мы могли бы использовать @Entity (name = «tbl_address») в нашем классе Address, если бы нашим именем таблицы было tbl_address.
  2. Мы можем использовать аннотацию @Table (определенную в пакете javax.persistence ) чуть ниже аннотации @Entity и использовать ее атрибут name для явного указания имени таблицы.
1
2
3
4
5
@Entity
@Table(name = "tbl_address")
public class Address {
  // Rest of the class
}

Из этих двух подходов аннотация @Table предоставляет больше возможностей для настройки отображения. Например, некоторые базы данных, такие как PostgreSQL, имеют концепцию схем , с помощью которых вы можете дополнительно классифицировать / группировать ваши таблицы. Благодаря этой функции вы можете создать две таблицы с одинаковыми именами в одной базе данных (хотя они будут принадлежать двум разным схемам). Для доступа к этим таблицам вы добавляете имя схемы в качестве префикса таблицы в своем запросе. Таким образом, если база данных PostgreSQL имеет две разные схемы с именами public (что-то вроде схемы по умолчанию для базы данных PostgreSQL) и document , и обе эти схемы содержат таблицы с именем document_collection , тогда оба эти двух запроса вполне допустимы:

1
2
3
4
5
6
7
-- fetch from the table under public schema
SELECT *
FROM   public.document_collection;
 
-- fetch from the table under document schema
SELECT *
FROM   document.document_collection;

Чтобы отобразить сущность в таблицу document_collection в схеме документа , вы затем будете использовать аннотацию @Table с ее атрибутом схемы, установленным в document :

1
2
3
4
5
@Entity
@Table(name="document_collection", schema="document")
public class DocumentCollection {
  // rest of the class
}

Если указано так, имя схемы будет добавлено в качестве префикса к имени таблицы, когда JPA перейдет в базу данных для доступа к таблице, как мы это делали в наших запросах.

Что если вместо указания имени схемы в аннотации @ Table вы добавляете имя схемы в само имя таблицы, например так:

1
2
3
4
5
@Entity
@Table(name = "document.document_collection")
public class DocumentCollection {
  // rest of the class
}

Подобное встраивание имени схемы в имя таблицы не обязательно будет работать во всех реализациях JPA, поскольку поддержка этого не указана в спецификации JPA (нестандартно). Так что лучше, если вы не сделаете это привычкой, даже если ваш постоянный провайдер поддерживает это.

Давайте обратим наше внимание на столбцы дальше. Чтобы определить столбцы по умолчанию, JPA делает что-то похожее на следующее:

  1. Сначала он проверяет, предоставлена ​​ли какая-либо явная информация о сопоставлении столбцов. Если информация о сопоставлении столбцов не найдена, он пытается угадать значения по умолчанию для столбцов.
  2. Чтобы определить значения по умолчанию, JPA необходимо знать тип доступа к состояниям объекта, т. Е. Способ чтения / записи состояний объекта. В JPA возможны два разных типа доступа — поле и свойство. В нашем примере мы использовали поле доступа (на самом деле JPA предполагало это из расположения / размещения аннотации @Id , но об этом позже). Если вы используете этот тип доступа, то состояния будут записываться / считываться непосредственно из полей сущности, используя Reflection API.
  3. После того, как тип доступа известен, JPA пытается определить имена столбцов. Для типа доступа к полю JPA напрямую обрабатывает имя поля как имя столбца, что означает, что если у объекта есть поле с именем status, то оно будет отображено в столбце с именем status .

На этом этапе нам должно быть ясно, как состояния сущностей Address сохраняются в соответствующих столбцах. Каждое из полей объекта Address имеет эквивалентный столбец в таблице базы данных tbl_address , поэтому JPA непосредственно сохранил их в соответствующие столбцы. Поле id было сохранено в столбце id , поле city ​​в столбце city и так далее.

Хорошо, тогда давайте перейдем к переопределению имен столбцов. Насколько я знаю, есть только один способ (если вы знаете какой-либо другой способ, пожалуйста, прокомментируйте!) Переопределить имена столбцов по умолчанию для состояний сущностей, который заключается в использовании @Column (определенного в пакете javax.persistence ) аннотация. Поэтому, если столбец id таблицы tbl_address переименован в address_id, мы можем либо изменить имя поля на address_id , либо использовать аннотацию @Column с атрибутом name, установленным в address_id :

01
02
03
04
05
06
07
08
09
10
@Entity
@Table(name = "tbl_address")
public class Address {
  @Id
  @GeneratedValue
  @Column(name = "address_id")
  private Integer id;
 
  // Rest of the class
}

Вы можете видеть, что для всех вышеперечисленных случаев подходы по умолчанию, которые использует JPA, весьма разумны, и в большинстве случаев вы будете довольны этим. Тем не менее, изменение значений по умолчанию также очень легко и может быть сделано очень быстро.

Что если у нас есть поле в объекте Address, которое мы не хотим сохранять в базе данных? Предположим, что у объекта Address есть столбец с именем transientColumn, который не имеет соответствующего столбца по умолчанию в таблице базы данных:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
@Entity
@Table(name = "tbl_address")
public class Address {
  @Id
  @GeneratedValue
  @Column(name = "address_id")
  private Integer id;
 
  private String street;
  private String city;
  private String province;
  private String country;
  private String postcode;
  private String transientColumn;
 
  // Rest of the class
}

Если вы скомпилируете свой код с указанным выше изменением, вы получите исключение, которое выглядит примерно так:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
Exception in thread “main” java.lang.ExceptionInInitializerError
at com.keertimaan.javasamples.jpaexample.Main.main(Main.java:33)
Caused by: javax.persistence.PersistenceException: Unable to build entity manager factory
at org.hibernate.jpa.HibernatePersistenceProvider.createEntityManagerFactory(HibernatePersistenceProvider.java:83)
at org.hibernate.ejb.HibernatePersistence.createEntityManagerFactory(HibernatePersistence.java:54)
at javax.persistence.Persistence.createEntityManagerFactory(Persistence.java:55)
at javax.persistence.Persistence.createEntityManagerFactory(Persistence.java:39)
at com.keertimaan.javasamples.jpaexample.persistenceutil.PersistenceManager.<init>(PersistenceManager.java:31)
at com.keertimaan.javasamples.jpaexample.persistenceutil.PersistenceManager.<clinit>(PersistenceManager.java:26)
… 1 more
Caused by: org.hibernate.HibernateException: Missing column: transientColumn in jpa_example.tbl_address
at org.hibernate.mapping.Table.validateColumns(Table.java:365)
at org.hibernate.cfg.Configuration.validateSchema(Configuration.java:1336)
at org.hibernate.tool.hbm2ddl.SchemaValidator.validate(SchemaValidator.java:155)
at org.hibernate.internal.SessionFactoryImpl.<init>(SessionFactoryImpl.java:525)
at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1857)
at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl$4.perform(EntityManagerFactoryBuilderImpl.java:850)
at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl$4.perform(EntityManagerFactoryBuilderImpl.java:843)
at org.hibernate.boot.registry.classloading.internal.ClassLoaderServiceImpl.withTccl(ClassLoaderServiceImpl.java:398)
at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.build(EntityManagerFactoryBuilderImpl.java:842)
at org.hibernate.jpa.HibernatePersistenceProvider.createEntityManagerFactory(HibernatePersistenceProvider.java:75)
… 6 more

Исключением является то, что провайдер персистентности не смог найти ни одного столбца в базе данных с именем transientColumn , и мы не сделали ничего, чтобы дать понять провайдеру персистентности, что мы не хотим сохранять это поле в базе данных. Поставщик сохраняемости принял его как любые другие поля в сущности, которые сопоставлены со столбцами базы данных.

Чтобы решить эту проблему, мы можем сделать любое из следующего:

  1. Мы можем аннотировать поле transientColumn аннотацией @Transient (определено в пакете javax.persistence ), чтобы поставщик сохраняемости знал, что мы не хотим сохранять это поле, и у него нет соответствующего столбца в таблице.
  2. Мы можем использовать временное ключевое слово, которое Java имеет по умолчанию.

Разница между этими двумя подходами, которая приходит мне в голову, заключается в том, что если мы используем ключевое слово transient вместо аннотации, то если один из объектов Address будет сериализован из одной JVM в другую, тогда поле transientColumn будет снова инициализировано (как любые другие переходные поля в Java). Для аннотации этого не произойдет, и поле transientColumn сохранит свое значение при сериализации. Как правило, я всегда использую аннотацию, если мне не нужно беспокоиться о сериализации (и в большинстве случаев я этого не делаю).

Таким образом, используя аннотацию, мы можем решить проблему сразу:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
@Entity
@Table(name = "tbl_address")
public class Address {
  @Id
  @GeneratedValue
  @Column(name = "address_id")
  private Integer id;
 
  private String street;
  private String city;
  private String province;
  private String country;
  private String postcode;
 
  @Transient
  private String transientColumn;
 
  // Rest of the class
}

Вот и все на сегодня, ребята. Если вы обнаружите какие-либо ошибки / у вас есть какие-либо комментарии, пожалуйста, не стесняйтесь комментировать

До скорого.

Ссылка: JPA Tutorial: Mapping Entities — часть 2 от нашего партнера JCG Саима Ахмеда в блоге Codesod .