Mapstruct
Mapstruct — это библиотека для Java, которая генерирует код для передачи данных между разными сущностями в программе. Она помогает сопоставлять объекты из одной сущности с другой. Этот процесс называется маппингом данных, а инструмент для передачи — маппером, или Java mapper.
Освойте профессию «Java-разработчик»
Маппер в широком смысле — подсистема для преобразования и передачи данных. В случае с Mapstruct разработчик обычно пишет аннотацию маппера — короткий блок кода. Он описывает, какую информацию и в какую сущность нужно передать. А библиотека на основе этой аннотации генерирует код, который и занимается непосредственной передачей данных. Поэтому Mapstruct часто называют генератором кода, который базируется на аннотациях.
Благодаря Mapstruct маппинг можно автоматизировать: не понадобится вручную писать методы, которые обрабатывают данные и передают их в другую сущность, в другой слой программы. Такие методы — большое количество лишнего кода, который надо писать и поддерживать. Mapstruct избавляет от этой проблемы.
Кто и зачем пользуется Mapstruct
Библиотеку используют разработчики, чтобы упростить и ускорить написание кода. Для маппинга есть и другие методы, но конкретно Mapstruct имеет ряд преимуществ: он автоматизирован и потому не отнимает лишнего времени, типобезопасен, быстро работает.
- упростить работу программистов, позволить им сконцентрироваться на бизнес-логике;
- сократить время на написание кода;
- избавиться от ошибок, связанных с человеческим фактором;
- убрать необходимость поддерживать большое количество шаблонного кода мапперов;
- ускорить работу самой программы, так как Mapstruct работает быстро и генерирует код прямо во время компиляции;
- увеличить производительность — по этой же причине.
Приложения на Java обычно масштабные и многоуровневые, а передача данных между уровнями нужна часто. Использовать для нее Mapstruct удобнее, чем писать нужные методы вручную.
Профессия / 14 месяцев
Java-разработчик
Освойте востребованный язык
Как работает Mapstruct
По определению с официального сайта Mapstruct — процессор аннотаций, который подключается к компилятору языка Java. Его можно добавить к проекту в любой популярной среде для языка, он совместим с фреймворками для автоматизированной сборки Java-кода, такими как Maven или Gradle.
Во время компиляции кода, то есть его преобразования в исполняемый файл для системы, Mapstruct «читает» написанные разработчиком аннотации. Это блоки описательного кода: он ничего не делает, но описывает, что и куда надо передать. На их основе Mapstruct создает непосредственную реализацию этих описаний и запускает ее. Это работает только с аннотациями для мапперов: именно для них нужна библиотека.
Обычно передаваемая информация — это объекты с различными полями и свойствами. Если имена и типы полей совпадают у разных объектов, то Mapstruct, вероятно, сможет сгенерировать код для переноса даже без дополнительных настроек.
Станьте Java-разработчиком
и создавайте сложные сервисы
на востребованном языке
Как пользоваться Mapstruct
Подключение к проекту. Мы не будем приводить точные инструкции: они различаются для разных сред и фреймворков. Если вы работаете с Maven, достаточно добавить одну зависимость в файл .pom — это специальный файл, которым фреймворк пользуется для подключения сторонних модулей. Затем понадобится добавить плагин для Mapstruct в раздел : эта часть файла описывает сборку.
В случае с другими инструментами порядок действий примерно такой же: различаются в основном форматы записи и синтаксис. Подробнее о подключении к разным фреймворкам можно почитать на официальном сайте проекта: там даны инструкции для всех популярных инструментов.
Написание аннотации. Инструкция для Mapstruct озаглавливается аннотацией @Mapper; на следующей строке после этого указания разработчик начинает писать интерфейс. По структуре интерфейс похож на класс, но в Java между этими понятиями есть различие: у интерфейса есть только методы, то есть действия, он не хранит никаких свойств.
Описание интерфейса начинается со слов public interface , причем имя может быть любым, но рекомендуется называть сущности понятными именами.
Синтаксис описания зависит от особенностей маппинга. В простейшем случае это строки, которые выглядят примерно так:
Методом называют функцию внутри интерфейса или объекта. Название метода придумывает сам разработчик; рекомендуется делать его понятным и наглядным. Сам код для метода в этом случае писать не нужно: это та самая практическая реализация, которую должен сгенерировать Mapstruct.
Дополнительные объяснения. Разработчик также может дать Mapstruct дополнительные инструкции с объяснениями, как преобразовывать объекты, если их типы или имена полей не совпадают: в изначальной сущности одно, а в целевой должно быть другое. Это делается с помощью аннотации @Mapping внутри интерфейса: после нее в скобочках указываются изначальные и целевые имена.
Выглядит это так:
По похожему принципу можно преобразовать типы.
Получение реализации. Этот шаг не всегда обязателен. Но если вы хотите получить созданный Mapstruct код, записать его куда-то и использовать позже — так можно сделать. В интерфейсе можно объявить переменную INSTANCE и записать туда сгенерированный маппер с помощью специального метода getMapper.
Из этой переменной клиент может считать сгенерированный код и воспользоваться им, если это понадобится.
Компиляция. Скомпилировать проект, в котором используется Mapstruct, понадобится вручную. Для этого в большинстве фреймворков для автоматизации достаточно набрать одну команду в консоли: она запустит компилятор.
После компиляции в папке проекта появятся новые файлы. Один из них — реализация маппера, обычно он располагается в подпапке с классами. Среды разработки для Java позволяют декомпилировать файлы, то есть преобразовать из исполняемых инструкций для системы обратно в исходный код: можно воспользоваться этой возможностью и посмотреть, что сгенерировал Mapstruct.
Тестирование. Чтобы убедиться, что сгенерированный код работает правильно, для него рекомендуют писать unit-тесты: так называются тесты, которые охватывают только один конечный компонент. Это перестраховка: она помогает убедиться, что всё перенеслось правильно. Тесты особенно важны на ранних этапах использования, например при обучении.
Как начать пользоваться Mapstruct
Скачать библиотеку можно с официального репозитория проекта на GitHub. Она бесплатная, с открытым исходным кодом, так что вы сможете просмотреть его в любой момент. Потом останется подключить Mapstruct к проекту — мы уже писали про это выше. Можно просто воспользоваться инструкцией с официального сайта. После этого библиотекой можно пользоваться.
Java-разработчик
Java уже 20 лет в мировом топе языков программирования. На нем создают сложные финансовые сервисы, стриминги и маркетплейсы. Освойте технологии, которые нужны для backend-разработки, за 14 месяцев.
java-object-merger — больше чем просто маппер объектов
Всем привет! Хотел бы представить вам новую библиотеку на java для маппинга/мержинга объектов, которую я “скромно” позиционирую как возможную альтернативу dozer-у. Если вы разрабатываете enterprise приложения на java, вам не безразлична эффективность вашей работы, и хочется писать меньше скучного кода, то приглашаю почитать дальше!
UPD. Выложено в центральный репозиторий мавена
net.sf.brunneng.jom java-object-merger 0.8.5.1
Для чего нужны мапперы объектов?
Простой ответ: чтобы копировать данные автоматически из одного объекта в другой. Но тогда вы можете спросить: зачем нужно это копирование? Можно усомниться, что это нужно очень часто. Значит следует дать более развернутый ответ.
В мире энтерпрайз приложений принято бить внутреннюю структуру на слои: слой доступа к базе, бизнес и представление/веб сервиса. В слое доступа к базе как правило обитают объекты мапящиеся на таблицы в базе. Условимся называть их DTO (от Data transfer object). По хорошему, они только переносят данные из таблиц и не содержат бизнес логики. На слое представления/веб сервисов, находятся объекты, доставляющие данные клиенту (браузер / клиенты веб сервисов). Назовем их VO (от View object). VO могут требовать только часть тех данных, которые есть в DTO, или же агрегировать данные из нескольких DTO. Они могут дополнительно заниматься локализацией или преобразованием данных в удобный для представления вид. Так что передавать DTO сразу на представление не совсем правильно. Так же иногда в бизнес слое выделяют бизнес объекты BO (Business object). Они являются обертками над DTO и содержат бизнес логику работы с ними: сохранение, модифицирование, бизнес операции. На фоне этого возникает задача по переносу данных между объектами из разных слоев. Скажем, замапить часть данных из DTO на VO. Или из VO на BO и потом сохранить то что получилось.
Если решать задачу в лоб, то получается примерно такой “тупой” код:
… employeeVO.setPositionName(employee.getPositionName()); employeeVO.setPerson(new PersonVO()); PersionVO personVO = employeeVO.getPerson(); PersonDTD person = employee.getPerson(); personVO.setFirstName(person.getFirstName()); personVO.setMiddleName(person.getMiddleName()); personVO.setLastName(person.getLastName()); .
Знакомо? 🙂 Если да, то могу вас обрадовать. Для этой проблемы уже придумали решение.
Мапперы объектов
Придуманы конечно-же не мной. Реализаций на java много. Вы можете ознакомится, к примеру тут.
Вкратце, задача маппера — скопировать все свойства одного объекта в другой, а так же проделать все то же рекурсивно для всех чайлдовых объектов, по ходу делая необходимые преобразование типов, если это требуется.
Мапперы из списка выше — все разные, более или менее примитивные. Самый мощный пожалуй dozer, с ним я работал около 2 лет, и некоторые вещи в нем перестали устраивать. А вялый темп дальнейшей разработки дозера побудили написать свой “велосипед” (да, я знакомился с другими мапперами — для наших требовний они еще хуже).
Чем плох dozer
- Бедная поддержка конфигурирования через аннотации (есть только @Mapping ).
- Невозможно мапить из нескольких полей в одно (к примеру собрать полное имя из имени, фамилии и отчества).
- Проблемы с маппингом генерик свойств. Если в родительском абстрактном классе есть геттер возвращающий генерик тип T, где , а в чайлде T определен, то при маппинге чайлда будет учитываться только спецификация типа T. Будто бы он IEntity, а не тот, которым он определен в чайлдовом классе..
Классы свойств хранятся как строки во внутреннем кэше дозера, и чтобы получить класс, используется специальный класс лоадер. Проблемы с этим возникают в osgi окружении, когда dozer находится в одном бандле, а нужный класс бина в другом, не доступным из первого. Проблему мы побороли хоть и стандартным способом — подсунув нужный класс лоадер, но сама реализация: хранить класс в виде строки — выглядит странно. Возможно это для того чтобы не создавать perm gen space мемори ликов. Но все равно не очень понятно.
Если что-то вдруг не мапится, то разобраться в этом очень сложно. Если вы будете дебажить дозер, то поймете почему. Там какое-то… просто сумасшедшее нагромождение ООП паттернов — все запутанно и не явно. Впрочем, это только на мой вкус.
Какими качествами должен обладать маппер?
- Широкая поддержка конфигурации через аннотации.
- Полная поддержка генериков.
- Чистый, понятный код, который сможет подебажить любой не рискуя сломать мозг.
- По умолчанию, без каких либо дополнительных настроек, должно мапить так, как этого скорее всего будет ожидать разработчик.
- Должна быть возможность тонкой настройки (не хуже чем у дозера).
Почему merger а не mapper?
java-object-merger отличает от других мапперов одна особенность. Основополагающая идея была в том, чтобы дать возможность создавать снимки объектов (Snapshot) на некоторый момент времени, и потом, сравнивая их, находить различия (Diff) подобно тому, как находим diff между двумя текстами. Причем должна быть возможность просмотра снапшотов и диффов в понятном для человека текстовом виде. Так, чтобы раз взглянув на дифф сразу стали ясны все отличия, а так же как будет изменен целевой объект после применения диффа. Таким образом добиваемся полной прозрачности процесса. Никакой магии и черных ящиков! Создание снапшотов открывает еще один интересный сценарий. Можно сделать снапшот объекта, потом как-то изменив его, сделать новый снапшот — проверить что изменилось, и, при желании, откатить изменения. Кстати дифф можно обойти специальным visitor-ом, и пометить только те изменения, которые хочется применить, а остальные проигнорировать.
Так что можно сказать, что merger — это нечто большее чем просто mapper.
Использование
Программа “Hello world” выглядит примерно так:
import net.sf.brunneng.jom.IMergingContext; import net.sf.brunneng.jom.MergingContext; public class Main < public static class A1 < private String field1; public String getField1() < return field1; >public void setField1(String field1) < this.field1 = field1; >> public static class A2 < private String field1; public A2(String field1) < this.field1 = field1; >public String getField1() < return field1; >> public static void main(String[] args) < IMergingContext context = new MergingContext(); A2 a2 = new A2("Hello world!"); A1 a1 = context.map(a2, A1.class); System.out.println(a1.getField1()); >>
Во-первых, видим, что для маппинга необходимо, чтобы у свойства был геттер на обоих объектах. Это нужно для сравнения значений. И сеттер у целевого объекта, чтобы записывать новое значение. Сами свойства должны именоваться одинаково.
Посмотрим же как реализован метод map. Это поможет понять многие вещи о библиотеке.
@Override public T map(Object source, Class destinationClass) < Snapshot sourceSnapshot = createSnapshot(source); Snapshot destSnapshot = null; if (sourceSnapshot.getRoot().getType().equals(DataNodeType.BEAN)) < Object identifier = ((BeanDataNode)sourceSnapshot.getRoot()).getIdentifier(); if (identifier != null) < destSnapshot = createSnapshot(destinationClass, identifier); >> if (destSnapshot == null) < destSnapshot = createSnapshot(destinationClass); >Diff diff = destSnapshot.findDiffFrom(sourceSnapshot); diff.apply(); return (T)destSnapshot.getRoot().getObject(); >
Если исходный снапшот это бин, и если у него есть identifier, тогда пытаемся найти целевой бин для класса destinationClass используя IBeanFinder-ы [тут createSnapshot(destinationClass, identifier); ]. Мы такие не регистрировали, да и identifier-а нет, значит идем дальше. В противном случает бин создается используя подходящий IObjectCreator [тут createSnapshot(destinationClass) ]. Мы таких тоже не регистрировали, однако в стандартной поставке имеется создатель объектов конструктором по умолчанию — он и используется. Далее у целевого снапшота берется дифф от снапшота источника и применяется к целевому объекту. Все.
Кстати, дифф, для этого простого случая, будет выглядеть так:
MODIFY < dest object : Main$A1@28a38b58 src object : Main$A2@76f8d6a6 ADD < dest property : String field1 = null src property : String field1 = "Hello world!" >>
Основные аннотации
- @Mapping — задает путь к полю для маппинга на другом конце ассоциации (например “employee.person.firstName” ). Может быть указано на классе целевого объекта или объекта источника.
- @Skip — поле не попадает в снапшот, не сравнивается и не мапится.
- @Identifier — помечает поле которое считаеся идентификатором бина. Таким образом при сравнении коллекций мы будем знать какой объект с каким должен сравниваться. А именно будут сравниваться объекты с совпадающими идентификаторами. Так же, если в процессе применения диффа возникнет потребность создать бин, и при этом известен идентификатор, то будет попытка вначале найти этот бин при помощи зарегистрированных IBeanFinder -ов. Так, реализация IBeanFInder может искать бины к примеру в базе данных.
- @MapFromMany — то же самое что и @Mapping только указывается на классе целевого объекта и позволяет указать массив свойств на объекте источнике которые будут мапится на поле в целевом объекте.
- @Converter — позволяет задать на свойстве класс наследник PropertyConverter . — он выполнит преобразование между свойствами. Конвертер свойств обязателен при маппинге нескольких полей на одно, т.к. он как раз и должен будет собрать все значения из источника воедино и сформировать из них одно значение.
- @OnPropertyChange, @OnBeanMappingStarted, @OnBeanMappingFinished — позволяют пометить методы прослушивающие соответствующие эвенты в жизненном цикле маппинга, которые происходят в данном бине.
- И другие.
Преобразования типов
- примитивных типов в обертки, и наоборот
- преобразования дат
- объектов в строку
- энумы в энумы, и строки в энумы по имени константы энума
Категории объектов
- Объекты значения: примитивные типы, объекты в пакете java.lang , даты, массивы объектов значений. Список классов считающихся значениями можно расширять через IMergingConext .
- Коллекции — массивы, все наследующиеся от java.util.Collection .
- Мапы — все наследующиеся от java.util.Map .
- Бины — все остальные.
Производительность
Честно говоря, пока писал библиотеку — о производительности особо не задумывался. Да и изначально в целях высокой производительности не было. Однако, решил замерять время маппинга N раз на один тестовый объект. Исходный код теста. Объект довольно сложный, с полями значениями, дочерними бинами, коллекциями и мапами. Для сравнения взял dozer последней на текущий момент версии 5.4.0. Ожидал, что дозер не оставит никаких шансов. Но получилось совсем наоборот! dozer замапил 5000 тестовых объектов за 32 секунды, а java-object-merger 50000 объектов за 8 секунд. Разница какая-то дикая — в 40 раз…
Применение
java-object-merger был опробован на текущем проекте с моей основной работы (osgi, spring, hibernate, сотни мапящихся классов). Чтобы заменить им дозер полностью ушло менее 1 дня. По ходу находились некоторые явные косяки, но, после исправления, все основные сценарии работали нормально.
Ленивые снапшоты
Одна из явных проблем, найденных во время прикручивания маппера к реальному проекту было то, что если делать снапшот на DTO у которой есть ленивые списки других сущностей, а те другие ссылаются на третьи и т.д, то за создание одного снапшота можно, ненароком, выкачать пол базы. Поэтому было решено сделать все свойства в снапшоте ленивыми по умолчанию. Это означает, что они не будут вытаскиваться из объектов до момента сравнения с соответствующим свойством при взятии диффа. Или пока явно не вызовем на снапшоте метод loadLazyProperties() . А при вытаскивании свойства происходит автоматическое достраивание снапшота — опять же с ленивыми свойствами, которые ждут пока их догрузят.
Маппинг примитивных типов данных
Во время предыдущих трех уровней мы немного познакомились с Hibernate. Настало время зайти на второй круг. Теперь мы начнем изучать тоже самое, только глубже. И начнем мы с маппинга полей Entity-класса на колонки таблиц в базе данных.
Как ты уже знаешь, маппинг поля в Entity классе на колонку выполняется с помощью аннотации @Column . И теперь вопрос: а поля каких типов можно замапить такой аннотацией?
Все типы данных в Java можно разбить на три группы:
- Тип достаточно простой и его легко сохранить в базу данных.
- Тип сложный и для него нужно писать специальный конвертор.
- Тип очень сложный и для хранения его значений нужна отдельная таблица.
К простым типам, про которые Hibernate знает, как их сохранять, относятся такие:
Типы в языке Java | package | Примеры классов |
---|---|---|
Примитивные типы языка Java | boolean , int , double и т. п. | |
Обертки над примитивами | java.lang | Boolean , Integer , Double и т. п. |
Строки | java.lang | String |
«Продвинутые» числа | java.math | BigInteger и BigDecimal |
Дата и время | java.time | LocalDate , LocalTime , LocalDateTime , OffsetTime , OffsetDateTime , Instant |
Различные вариации даты и времени | java.util | Date и Calendar |
Старые форматы даты и времени | java.sql | Date , Time , Timestamp |
Массив байт или символов | byte[] или Byte[] , char[] или Character[] | |
Перечисления (enum) | Любой enum | |
Сериализуемые объекты | Любая имплементация java.io.Serializable |
Для всех этих типов есть их аналоги в языке SQL, поэтому Hibernate хорошо знает, как их сохранять и загружать из базы.
@Entity @Table(name="user") class User
Задания типа вручную – аннотация @Type
Иногда ты можешь захотеть вмешаться в политику Hibernate и явно указать ему в каком типе нужно хранить данные в базе. Например, у тебя в Entity-классе поле имеет тип Integer, но в базе для него есть колонка с типом VARCHAR.
Для этого есть специальная аннотация – @Type . Выглядит она очень просто:
@Type(type="имя-типа")
Давай, например, попросим Hibernate, чтобы поле createdDate нашего класса User хранилось в базе в виде строки:
@Entity @Table(name="user") class User
Если Hibernate поймет, как конвертировать тип Date в ваш новый тип, то просто сделает это. Если не поймет, тогда тебе нужно будет указать специальной конвертор типов. Но об этом немного позднее.
Список Hibernate-типов для баз данных
Кстати, ты обратил внимание, что мы указали тип org.hibernate.type.StringType, а не String. Это потому, что мы выбрали один из типов, который поддерживает СУБД, а не язык Java. У них у каждого своя система типов. Просто разработчики Hibernate придумали удобные названия в стиле Java, вместо этих VARCHAR’ов.
Кстати, этот список не такой уж и маленький. Приведу тут его часть:
Hibernate type (org.hibernate.type package) | JDBC type | Java type | BasicTypeRegistry key(s) |
---|---|---|---|
StringType | VARCHAR | java.lang.String | string, java.lang.String |
MaterializedClob | CLOB | java.lang.String | materialized_clob |
TextType | LONGVARCHAR | java.lang.String | text |
CharacterType | CHAR | char, java.lang.Character | char, java.lang.Character |
BooleanType | BIT | boolean, java.lang.Boolean | boolean, java.lang.Boolean |
NumericBooleanType | INTEGER, 0 is false, 1 is true | boolean, java.lang.Boolean | numeric_boolean |
YesNoType | CHAR, ‘N’/’n’ is false, ‘Y’/’y’ is true. The uppercase value is written to the database. | boolean, java.lang.Boolean | yes_no |
TrueFalseType | CHAR, ‘F’/’f’ is false, ‘T’/’t’ is true. The uppercase value is written to the database. | boolean, java.lang.Boolean | true_false |
ByteType | TINYINT | byte, java.lang.Byte | byte, java.lang.Byte |
ShortType | SMALLINT | short, java.lang.Short | short, java.lang.Short |
IntegerTypes | INTEGER | int, java.lang.Integer | int, java.lang.Integer |
LongType | BIGINT | long, java.lang.Long | long, java.lang.Long |
FloatType | FLOAT | float, java.lang.Float | float, java.lang.Float |
DoubleType | DOUBLE | double, java.lang.Double | double, java.lang.Double |
BigIntegerType | NUMERIC | java.math.BigInteger | big_integer, java.math.BigInteger |
BigDecimalType | NUMERIC | java.math.BigDecimal | big_decimal, java.math.bigDecimal |
TimestampType | TIMESTAMP | java.sql.Timestamp | timestamp, java.sql.Timestamp |
TimeType | TIME | java.sql.Time | time, java.sql.Time |
DateType | DATE | java.sql.Date | date, java.sql.Date |
CalendarType | TIMESTAMP | java.util.Calendar | calendar, java.util.Calendar |
CalendarDateType | DATE | java.util.Calendar | calendar_date |
CurrencyType | java.util.Currency | VARCHAR | currency, java.util.Currency |
LocaleType | VARCHAR | java.util.Locale | locale, java.utility.locale |
TimeZoneType | VARCHAR, using the TimeZone ID | java.util.TimeZone | timezone, java.util.TimeZone |
UrlType | VARCHAR | java.net.URL | url, java.net.URL |
ClassType | VARCHAR (class FQN) | java.lang.Class | class, java.lang.Class |
Таблица, конечно, большая, но очень полезная. Например, из нее понятно, что тип Boolean можно сохранить в базу как минимум шестью разными способами. Тебе столько не нужно? А кто сказал, что способ сохранения выбираешь ты?
В SQL нет типа Boolean и его часто хранят так:
- 1 или 0
- ‘F’ или ‘T’
- ‘Y’ или ‘N’
Поэтому очень хорошо, когда Hibernate понимает все эти заморочки. Или, например, давай возьмем хранение массивов данных в базе. Есть куча разных вариантов и Hibernate умеет работать с ними со всеми:
Hibernate type (org.hibernate.type package) | JDBC type | Java type | BasicTypeRegistr |
---|---|---|---|
BlobType | BLOB | java.sql.Blob | blog, java.sql.Blob |
ClobType | CLOB | java.sql.Clob | clob, java.sql.Clob |
BinaryType | VARBINARY | byte[] | binary, byte[] |
MaterializedBlobType | BLOB | byte[] | materized_blob |
ImageType | LONGVARBINARY | byte[] | image |
WrapperBinaryType | VARBINARY | java.lang.Byte[] | wrapper-binary, Byte[], java.lang.Byte[] |
CharArrayType | VARCHAR | char[] | characters, char[] |
CharacterArrayType | VARCHAR | java.lang.Character[] | wrapper-characters, Character[], java.lang.Character[] |
UUIDBinaryType | BINARY | java.util.UUID | uuid-binary, java.util.UUID |
UUIDCharType | CHAR, can also read VARCHAR | java.util.UUID | uuid-char |
PostgresUUIDType | PostgreSQL UUID, through Types#OTHER, which complies to the PostgreSQL JDBC driver definition | java.util.UUID | pg-uuid |
А после выпуска JDK 8 в Hibernate добавилось еще несколько типов, связанных со временем. И все с целью упростить тебе жизнь. Тебе больше не нужно думать, поддерживаются ли все эти новомодные типы. Создатели Hibernate уже добавили их поддержку для тебя:
Hibernate type (org.hibernate.type package) | JDBC type | Java type | BasicTypeRegistr |
---|---|---|---|
DurationType | BIGINT | java.time.Duration | Duration, java.time.Duration |
InstantType | TIMESTAMP | java.time.Instant | Instant, java.time.Instant |
LocalDateTimeType | TIMESTAMP | java.time.LocalDateTime | LocalDateTime, java.time.LocalDateTime |
LocalDateType | DATE | java.time.LocalDate | LocalDate, java.time.LocalDate |
LocalTimeType | TIME | java.time.LocalTime | LocalTime, java.time.LocalTime |
OffsetDateTimeType | TIMESTAMP | java.time.OffsetDateTime | OffsetDateTime, java.time.OffsetDateTime |
OffsetTimeType | TIME | java.time.OffsetTime | OffsetTime, java.time.OffsetTime |
OffsetTimeType | TIMESTAMP | java.time.ZonedDateTime | ZonedDateTime, java.time.ZonedDateTime |
Что такое маппинг java
Проверьте, пожалуйста, код в разделе «3. Как отсортировать значения мапы», в нём надо переименовать переменную.
Dmytryi Shubchynskyi Уровень 108 Expert
22 февраля 2022
для чего нужно Создание неизменяемой (immutable) пустой мапы ?
Sergey Kornilov Уровень 39
5 июля 2021
В закладки.
Иван Уровень 41
8 января 2021
Так же есть ошибка в пункте 4, подпункт 3, где говорится про потокобезопасность HashTable. В тексте нужно поменять местами HashMap и HashTable и тогда таблица будет верно читаться.
Yuliia Boklah Уровень 41
6 сентября 2020
В свою очередь, TreeMap гарантирует хранение элементов в порядке добавления или же в соответствии с заданным компаратором. Указана совершенно неправильная информация. Речь идет об LinkedHashMap. В TreeMap элементы хранятся в есттественном порядке (natural ordering) или же в соответствии с заданным компаратором. Исправьте ошибку и не вводите учеников в заблуждение.