Почему Hibernate не требует конструктора аргументов?

105

Конструктор без аргументов является обязательным (такие инструменты, как Hibernate, используют отражение в этом конструкторе для создания экземпляров объектов).

Я получил этот волнистый ответ, но не мог бы кто-нибудь объяснить дальше? Спасибо

unj2
источник
7
К вашему сведению: утверждение, The no-argument constructor is a requirement которое неверно , и все ответы, которые объясняют, почему это так, без сомнения, так ли это на самом деле (включая принятый ответ, который даже получил награду) , неверны . См. Ответ: stackoverflow.com/a/29433238/773113
Майк Накис,
2
Это ОБЯЗАТЕЛЬНО, если вы используете спящий режим в качестве провайдера для JPA.
Амальговинус
1
@MikeNakis Ты не прав, Майк. Для Hibernate требуется конструктор по умолчанию для создания экземпляров объектов, если вы используете hibernate в качестве провайдера для JPA (Amalgovinus), в противном случае Hibernate будет сообщать, Caused by: org.hibernate.InstantiationException: No default constructor for entity: : hibernate.tutorial.Studentкак в случае, с которым я только что столкнулся
Mushy
@Mushy, вопрос помечен тегами "hibernate" и "orm", а не тегом "jpa". В вопросе нет упоминания о JPA.
Майк Накис,
1
@MikeNakis Я согласен, Майк, но Hibernate используется как реализация «JPA» и не используется при отсутствии «JPA» или «ORM». Поэтому предполагается, что спящий режим реализует «JPA».
Mushy

Ответы:

138

Hibernate и код в целом, который создает объекты посредством отражения, используются Class<T>.newInstance()для создания нового экземпляра ваших классов. Этот метод требует, чтобы общедоступный конструктор без аргументов мог создать экземпляр объекта. В большинстве случаев использование конструктора без аргументов не является проблемой.

Существуют хаки, основанные на сериализации, которые могут обойтись без конструктора без аргументов, поскольку сериализация использует магию jvm для создания объектов без вызова конструктора. Но это доступно не для всех виртуальных машин. Например, XStream может создавать экземпляры объектов, у которых нет общедоступного конструктора no-arg, но только в так называемом «расширенном» режиме, который доступен только на определенных виртуальных машинах. (Подробности см. По ссылке.) Разработчики Hibernate, безусловно, решили поддерживать совместимость со всеми виртуальными машинами и поэтому избегают таких уловок и используют официально поддерживаемый метод отражения, Class<T>.newInstance()требующий конструктора без аргументов.

mdma
источник
31
К вашему сведению: конструктор не обязательно должен быть публичным. У него может быть видимость пакета, и Hibernate должен быть setAccessible(true)на нем.
Грей
Могу ли я создать пользовательский тип пользователя с конструктором, отличным от конструктора по умолчанию, чтобы установить необходимое поле для его операций.
L-Samuels
1
Для справки ObjectInputStreamделает что-то вроде sun.reflect.ReflectionFactory.getReflectionFactory().newConstructorForSerialization(classToGetInstanceOf, Object.class.getConstructor()).newInstance()создания экземпляров объектов без конструктора по умолчанию (JDK1.6 для Windows)
SamYonnou 02
Re: It can have package visibility and Hibernate should setAccessible(true). Означает ли itэто, что класс создается через отражение? А что Hibernate should setAccessible(true)значит?
Кевин Мередит
Objenesis делает это и широко используется многими фреймворками, такими как spring-data и mockito github.com/easymock/objenesis
ltfishie
47

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

В документации по гибернации говорится:

4.1.1. Реализуйте конструктор без аргументов

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

Божо
источник
6
Что касается видимости конструктора, если вы используете JPA v2.0, обратите внимание, что JSR-317 говорит: Конструктор no-arg должен быть общедоступным или защищенным .
Хосе Андиас
@Bozho привет, сэр, у меня есть одно сомнение, что если внутри спящего режима использовать Constructor.newInstance () для создания экземпляра объекта, то как спящий режим устанавливает значения в поля без каких-либо определенных сеттеров?
Викас Верма
Я не понимаю, почему я вижу это предупреждение для неприватного подкласса @Embeddable с общедоступным конструктором без аргументов ...
Амальговинус,
Constructor.newInstance () принимает аргументы, проблема (на самом деле не проблема) отображает эти аргументы. Понятия не имею, почему спящий режим не решил эту проблему. Для сравнения: аннотация @JsonCreator в Jackson делает это, и было много преимуществ неизменных объектов.
drrob
44

Эмм, извините всех, но Hibernate не требует, чтобы ваши классы имели конструктор без параметров. Спецификация JPA 2.0 требует этого, и это очень неубедительно для JPA. Другие фреймворки, такие как JAXB, также требуют этого, что также очень неубедительно для этих фреймворков.

(На самом деле, JAXB предположительно разрешает фабрики сущностей, но он настаивает на создании экземпляров этих фабрик самостоятельно, требуя, чтобы у них был конструктор без параметров - угадай , что - в моей книге точно так же хорошо, как и не разрешать фабрики; !)

Но Hibernate этого не требует.

Hibernate поддерживает механизм перехвата (см. «Перехватчик» в документации ), который позволяет вам создавать экземпляры ваших объектов с любыми параметрами конструктора, которые им нужны.

По сути, вы делаете то, что когда вы настраиваете спящий режим, вы передаете ему объект, реализующий org.hibernate.Interceptorинтерфейс, а затем спящий режим будет вызывать instantiate()метод этого интерфейса всякий раз, когда ему нужен новый экземпляр вашего объекта, поэтому ваша реализация этого метода может newваши объекты так, как вам нравится.

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

Hibernate кажется несколько небезопасным в этом отношении, поскольку во время запуска он выдает информационное сообщение для каждого из моих классов сущностей, сообщая мне INFO: HHH000182: No default (no-argument) constructor for classи class must be instantiated by Interceptor, но позже я создаю их экземпляры с помощью перехватчика, и он доволен этим.

Чтобы ответить на часть вопроса «почему» для инструментов, отличных от Hibernate , ответ будет «без всякой уважительной причины», и это подтверждается существованием перехватчика гибернации. Существует множество инструментов, которые могли бы поддерживать аналогичный механизм для создания экземпляров клиентских объектов, но они этого не делают, поэтому они создают объекты сами по себе, поэтому им требуются конструкторы без параметров. У меня есть соблазн поверить, что это происходит потому, что создатели этих инструментов думают о себе как о системных программистах-ниндзя, которые создают фреймворки, полные магии, для использования невежественными программистами, которые (как они думают) никогда бы в своих самых смелых мечтах не имели потребность в таких продвинутых конструкциях, как ... Factory Pattern . (Ладно,так думать. Я не на самом деле так думаю. Я шучу.)

Майк Накис
источник
1
Наконец-то тот, кто это получит! Я потратил больше времени, чем мне нравится, имея дело с этими фреймворками, скрывая процесс создания экземпляра объекта (что абсолютно необходимо для правильного внедрения зависимостей и для расширенного поведения объекта). Кроме того, отражение Java позволяет создавать объекты без использования newInstance (). Метод getDeclaredConstructors присутствует в API отражения с JDK 1.1. Ужасно, что разработчики спецификации JPA пренебрегли этим.
drrob
Это не верно. Если Hibernate используется в качестве поставщика JPA для постоянства, он требует конструктора по умолчанию, в противном случае последует следующее, Caused by: org.hibernate.InstantiationException: No default constructor for entity: : hibernate.tutorial.Studentкоторое недавно произошло, потому что javax.persistence.*;используется и только org.hibernateпри созданииSession, SessionFactory, and Configuration
Mushy
2
@Mushy Это совершенно правильно, потому что а) речь идет о спящем режиме, без единого упоминания JPA, и б) я тем не менее явно упоминаю во втором предложении своего ответа, что JPA требует конструкторов по умолчанию, хотя спящий режим этого не делает.
Майк Накис,
36

Гибернация - это структура ORM, которая поддерживает стратегию доступа к полям или свойствам. Однако он не поддерживает сопоставление на основе конструктора - может быть, что вы хотите? - из-за некоторых проблем, таких как

Что произойдет, если ваш класс содержит много конструкторов

public class Person {

    private String name;
    private Integer age;

    public Person(String name, Integer age) { ... }
    public Person(String name) { ... }
    public Person(Integer age) { ... }

}

Как видите, вы сталкиваетесь с проблемой несогласованности, потому что Hibernate не может предположить, какой конструктор следует вызвать. Например, предположим, что вам нужно получить сохраненный объект Person

Person person = (Person) session.get(Person.class, <IDENTIFIER>);

Какой конструктор должен вызывать Hibernate для получения объекта Person? Видишь ?

И, наконец, используя отражение, Hibernate может создать экземпляр класса через конструктор без аргументов. Итак, когда вы звоните

Person person = (Person) session.get(Person.class, <IDENTIFIER>);

Hibernate создаст экземпляр вашего объекта Person следующим образом

Person.class.newInstance();

Что согласно документации API

Экземпляр класса создается как новое выражение с пустым списком аргументов.

Мораль истории

Person.class.newInstance();

похоже на

new Person();

Ничего больше

Артур Рональд
источник
1
Это, безусловно, лучшее описание, которое я нашел по этому вопросу. В большинстве найденных мной ответов использовались книжные технические термины, и никто не объяснил это так гибко, как вы. Престижность вам и спасибо!
Темный рыцарь
1
Это вполне могло быть рассуждением команды Hibernate. Но на самом деле проблемы могут быть решены с помощью (1) требования либо аннотации, либо использования только конструктора, отличного от конструктора по умолчанию, если есть только один конструктор, и (2) использования class.getDeclaredConstructors. И используя Constructor.newInstance () вместо Class.newInstance (). Соответствующее отображение в XML / аннотациях потребовалось бы до Java 8, но это вполне выполнимо.
drrob
Итак, hibernate создает объект из конструктора по умолчанию, а затем использует сеттеры для полей nameи age? Если нет, то позже он использует другой конструктор?
tryHard
2
@tryingHard Да, после создания экземпляра Hibernate использует сеттеры или поля - это зависит от стратегии доступа. По умолчанию размещение аннотации Id дает стратегию доступа по умолчанию. См. Docs.jboss.org/hibernate/orm/5.1/userguide/html_single/chapters/…
Артур Рональд,
6

Фактически, вы можете создавать экземпляры классов, у которых нет конструктора с 0 аргументами; вы можете получить список конструкторов класса, выбрать один и вызвать его с фиктивными параметрами.

Хотя это возможно, и я думаю, это сработает и не вызовет проблем, согласитесь, что это довольно странно.

Создание объектов так, как это делает Hibernate (я считаю, что он вызывает конструктор 0-arg, а затем, вероятно, изменяет поля экземпляра напрямую через Reflection. Возможно, он знает, как вызывать сеттеры) немного противоречит тому, как объект должен быть построен в Java - вызовите конструктор с соответствующими параметрами, чтобы новый объект был тем объектом, который вам нужен. Я считаю, что создание экземпляра объекта с последующим его изменением - это своего рода «анти-Java» (или, я бы сказал, анти-чисто теоретическая Java) - и определенно, если вы сделаете это с помощью прямых манипуляций с полями, это будет инкапсуляция и все эти причудливые инкапсуляционные штуки. .

Я думаю, что правильный способ сделать это - определить в отображении Hibernate, как объект должен быть создан из информации в строке базы данных с использованием подходящего конструктора ... но это было бы более сложно, то есть оба Hibernate были бы даже чем сложнее, тем сложнее отображение ... и все будет более "чистым"; и я не думаю, что это будет иметь преимущество перед текущим подходом (кроме того, что вы чувствуете себя хорошо, когда делаете что-то «должным образом»).

Сказав это и видя, что подход Hibernate не очень "чистый", обязательство иметь конструктор с 0 аргументами не является строго необходимым, но я могу частично понять требование, хотя я считаю, что они сделали это чисто "надлежащим образом". "основания, когда они отклонились от" правильного пути "(хотя и по разумным причинам) задолго до этого.

Алекс
источник
5

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

Паскаль Тивент
источник
При каких условиях privateнекорректен конструктор? Я вижу java.lang.InstantiationExceptionдаже с privateконструктором для моей сущности JPA. ссылка .
Кевин Мередит
Я попробовал класс без пустого конструктора (но с конструктором args), и он сработал. Я получил ИНФОРМАЦИЮ из спящего режима "ИНФОРМАЦИЯ: HHH000182: Конструктор по умолчанию (без аргументов) для класса и класса не должен создаваться перехватчиком", но не было исключения, и объект был успешно получен из БД.
lijep dam
2

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

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

Kaerber
источник
Я бы сказал, что принуждение к полной изменчивости того, что может потребоваться для объекта с богатым доменом, еще более хрупко (это не очень похоже на ORM, если ваши объекты должны быть безликими пакетами данных для работы - я здесь, потому что я хочу конструктор, но вместо этого имеет неопределенный порядок вызовов расширенного сеттера) ... Но +1, потому что вы признаете, что отражение может выполняться для конструкторов с аргументами :)
drrob
2

Hibernate использует прокси для отложенной загрузки. Если вы не определяете конструктор или не делаете его закрытым, некоторые вещи могут по-прежнему работать - те, которые не зависят от механизма прокси. Например, загрузка объекта (без конструктора) напрямую с помощью API запросов.

Но, если вы используете метод session.load (), вы столкнетесь с InstantiationException из прокси-генератора lib из-за недоступности конструктора.

Этот парень сообщил о похожей ситуации:

http://kristian-domagala.blogspot.com/2008/10/proxy-instantiation-problem-from.html

haps10
источник
0

Ознакомьтесь с этим разделом спецификации языка Java, в котором объясняется разница между статическими и нестатическими внутренними классами: http://java.sun.com/docs/books/jls/third_edition/html/classes.html#8.1.3

Статический внутренний класс концептуально ничем не отличается от обычного общего класса, объявленного в файле .java.

Поскольку Hibernate должен создавать экземпляр ProjectPK независимо от экземпляра Project, ProjectPK должен быть либо статическим внутренним классом, либо объявлен в собственном файле .java.

ссылка org.hibernate.InstantiationException: нет конструктора по умолчанию

Амитабха
источник