1.19.2015

4. Описание полей и таблиц сущности. Hibernate



Как я уже говорил выше, по умолчанию имя таблицы совпадает с именем сущности.  Если вам нужно,  переопределить   имя таблицы или схему базы данных, то нужно использовать аннотацию @Table(name = "Persons", schema="test") ,  атрибут name описывает имя таблицы, атрибут schema задает имя схемы базы данных.  Каждое поле  таблицы можно соотнести с полем сущности, при этом используется аннотация @Column().  Рассмотрим  основные атрибуты этой аннотации. Атрибут name  - задает имя колонки в Таблице базы данных,  атрибут unique , если ему присвоить значение true, то  поле будет уникальным, и по этому полю будет создан уникальный индекс,  если  атрибуту nullable выставить значение в false,  то поле станет обязательно для ввода, т.е. создается как поле со значением not null,  атрибут length задает размерность поля,  допустим для строки, количество символов равным  500 символов, смотрим на листинг:

@Column(name = "MyName", unique = true, nullable = false, length = 500)
 private String name;

предыдущий код hibernate транслирует в следующий sql:

create table test.Prersons (… , MyName varchar(500) not null unique, …).  

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

 @Column(name = "MyName", unique = true, nullable = false, length = 500, columnDefinition = "varchar(40)")
 private String name;

при этом hibernate  сгенерирует следующий sql  код создания таблицы:

create table test.Prersons (… , MyName varchar(40) not null unique, …)  

И так, мы подошли к вопросу, а какие типы данных можно использовать в описании полей сущности?  В документации написано, что можно использовать все примитивные типы  языка java:  byte,  int,  short,  long,  boolean,  char,  float,  double,  их обертки плюс к ним два типа  BigInteger, BigDecimal,  массивы байтов и символов:  byte[],  Byte[],  char[],  Character[],  cтроки String,  временные типы данных: java.util.Date, java.util.Calendar, java.sql.Date, java.sql.Time, java.sql.Timestamp,  перечисления  enum,  и любые сериализуемые объекты, реализующие интерфейс  Serializable.  Допустим,  есть у нас большой массив данных, например картинка,  её, нужно сохранять как массив байтов,  для этого есть у нас дополнительная аннотация @Lob ,  которая говорит нам, что поле сущности является большим бинарным объектом (LOB).  И предположим, что у нашей картинки размер в несколько мегабайт. Что бы постоянно при считывании не подгружать картинку в сущность, придумали такое понятие как ленивая загрузка.  Если к полю применена аннотация @Basic(fetch = FetchType.LAZY), то  LOB-объект загружается, тогда, когда он непосредственно запрашивается методом  доступа .get , при этом  hibernate выполняет отдельный sql  запрос к базе данных,  который подгружает нам значение  поля сущности.   Будьте  осторожны, применяя отложенную загрузку (Lazy)  т.к.  при этом  hibernate   будет генерировать  очень много sql запросов  к базе данных.  На самом деле ленивую загрузку поля сущности hibernate по умолчанию  игнорирует, и нужно сделать несколько дополнительных настроек, для того чтобы включить её. Первое прописать дополнительные свойства в файле настроек hibernate.cfg.xml как сказано в документации  по  hibernate:

<property name="hibernate.bytecode.provider">javassist</property>
<property name="hibernate.bytecode.use_reflection_optimizer">true</property>

Первым свойством мы описываем,  какая библиотека будет нам создавать прокси объекты.   Что бы поле грузилось лениво, hibernate надо как-то перехватывать обращения к нему.  Это делается через прокси,  двумя способами.  Либо через Java Proxy,  если у класса определен интерфейс,  либо через javassist,  который создает прокси,  модифицируя байткод объекта.  Мы пойдем вторым путем. Так вы можете спросить,  зачем нам создавать прокси объекты?  Задача Hibernate загрузить LOB  поле только тогда когда кто-то вызовет геттер.  Геттеры нужны для тупого возвращения значения поля.  Но hibernate нужно  в геттер метод  впихнуть логику по загрузке этого поля из базы данных.  Значит, он должен переопределить геттер метод  сущности, и значит, он нам возвратит не чистый объект сущности,  а его фантом, заглушку  - которая называется proxy   объектом.   Вот тут на помощь и приходит javassist,  который может модифицировать байткод объекта сущности.  В документации  hibernate  сказано, что бы работала ленивая загрузка поля, нужно выполнить задачу  сборщика  ant -  instrument к классам сущности.  Поясню, кто не знает  что такое ant  - это инструмент  сборки проектов,  появился задолго до появления maven.  Для  выполнения этой инструкции при сборке проекта  maven,  применим специальный плагин  maven-antrun-plugin который может вызывать задачи ant, в нем  задаем, что при компиляции классов к ним нужно применить специальную сборку, в виде задачи ant instrument,  смотрите листинг:

<plugin>
                <artifactId>maven-antrun-plugin</artifactId>
                <executions>
                    <execution>
                        <phase>compile</phase>
                        <goals>
                            <goal>run</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <tasks>
                        <taskdef name="instrument" classname="org.hibernate.tool.instrument.javassist.InstrumentTask">
                            <classpath>
                                <path refid="maven.runtime.classpath"/>
                                <path refid="maven.plugin.classpath"/>
                                <path refid="maven.compile.classpath"/>
                            </classpath>
                        </taskdef>
                        <instrument verbose="true">
                            <fileset dir="${project.build.outputDirectory}">
                                <include name="**/**.class"/>
                                <exclude name="**/I**.class"/>
                            </fileset>
                        </instrument>
                    </tasks>
                </configuration>
   </plugin>

Хочу рассказать об ошибке, которую часто совершают программисты, они в своих сущностях применяют к LOB  полям метод  доступа,  называемый property access.  Я повторюсь,  это  когда аннотации применяются к методам  геттера или сеттера  поля,  при этом hibernate следуя заложенной логике, тупо использует геттеры и сеттеры сущности,  и переопределение метода геттера не происходит,  и  из чего следует что, hibernate  не может создать proxy  объект для вас. Так если вы применяете, метод  доступа field access  к LOB полю,  у вас будет переопределён метод  .getText()  и будет работать ленивая загрузка по полю,  смотрите листинг:

@Entity
public class Person implements Serializable {
  …..
    @Basic(fetch = FetchType.LAZY)    @Column(name = "MyText",length = 20000,nullable = false)
    @Lob    private byte[] text;

    public byte[] getText() {
        return text;
    }

……
}

А если примените  метод  доступа property access, то метод  .getText() не будет переопределен,   и ленивая загрузка не будет работать, смотрим  листинг:

@Entity
public class Person implements Serializable {
  …..
   
    private byte[] text;

   @Basic(fetch = FetchType.LAZY)    @Column(name = "MyText",length = 20000,nullable = false)
   @Lob
   public byte[] getText() {
        return text;
    }

……
}

Давайте перейдем к временным типам, как же правильно их маппить/описывать  в сущности.  Как я говорил выше hibernate может, работать с  временными типами  данных ( java.util.Date, java.util.Calendar, java.sql.Date, java.sql.Time, java.sql.Timestamp),  но  JDBC  драйвер  имеет дело только  с  тремя типами дат:  java.sql.Date, java.sql.Time, java.sql.Timestamp.  Поэтому  если вы используете  один из типов даты java.util.Date или java.util.Calendar, вам  желательно дать подсказку hibernate, в какой тип данных JDBC  транслировать дату, при взаимодействии с базой данных посредством JDBC  драйвера.  Помогает нам в этом аннотация  @Temporal  с параметром  value.  Этот параметр  может принимать одно из трех значений перечисления TemporalType:  DATE, TIME, TIMESTAMP, которые соответствуют   java.sql  типам:  

@Temporal(value = TemporalType. DATE)
 private Calendar birthDate;
Хочу отметить, что hibernate не выполняет приведение типа дата автоматически, т.е. в нашем случае birthDate хранит и дату и время, и только после сохранения и повторной загрузки время будет усечено.  Осталась одна важная вещь, про которую необходимо упомянуть в этой части – это ”временные” поля,  т.е. поля которые не мапятся и не сохраняются в базе данных.   Допустим, нужно нам загрузить в поле,  строковой ресурс в зависимости от выбранной локали,  хранить такие поля лучше всего во временном поле.  Объявить  поле временным можно двумя способами.  С помощью аннотации @Transient() или  с помощью конструкции языка java, служебного слова transient. 
@Transient()
private String name;

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

1 комментарий:

  1. Добрый день!
    Подскажите, а если необходимо создать таблицу, в которой определенное поле должно быть индексируемым, т.е. что должно быть написано в классе сущности, чтобы это было аналогично sql выражению -->
    alter table [name_table] add key name_index ([name_field](512));

    ОтветитьУдалить