Из Oracle в Java. Личный опыт

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

К написанию статьи меня побудил интерес разработчиков Oracle к изучению Java. Статья не носит обучающий характер и не является инструкцией для перехода с одной технологии на другую. Цель — рассказать, как я переходил на Java и с какими трудностями столкнулся.

О себе

В свое время я отучился на программиста. Начинал карьеру на Delphi. Что такое ООП, помню, хотя на Delphi оно использовалось достаточно условно. С 2005 года начал плотно программировать на Oracle. Получил несколько сертификатов. Работал в компаниях Luxoft, РДТЕХ и CUSTIS. С последней сотрудничаю до сих пор, уже более 11 лет. Участвовал в проектах по автоматизации торговых сетей и банков.

С чего все началось

Два года назад я вернулся в команду разработки банковских продуктов, в которой уже когда-то работал и потому хорошо знал большинство систем. Но работы на PL\SQL было недостаточно для full-time, поэтому мне было предложено присоединиться к разработке на Java. В результате у меня появилась возможность и, главное, время для изучения Java в рамках производственного процесса.

На самом деле это не первая моя попытка изучения Java. Была еще одна, но она не увенчалась успехом. Особой производственной необходимости тогда не было, а желание учить ради изучения быстро прошло.

Обучение

Я посмотрел проекты, «потыкал» код… И быстро осознал, что нужно учить матчасть. Даже очевидный синтаксис был не всегда понятен, начиная от непривычных операторов && и || и заканчивая конструкциями:

List<LedgerAccount> accList = new ArrayList<LedgerAccount>();
DealGrid dealGrid = (DealGrid)grid;

Интуитивно понятно, что это некоторая типизация, но как это работает и чем они различаются, было неясно. А такие конструкции вообще вводили в ступор:

protected <E extends Editor<?>> E add(String id, E editor) {}

Особенно если учесть, что типов E и ? не существует.

Ходить по курсам не было ни времени, ни желания, поэтому начал с простого: купил книгу Барри Бёрда «Java 8 для чайников». Она не очень большая, 400 страниц, читается легко. После прочтения стало более-менее понятно, что такое Java и как на ней программировать. По крайней мере, отпали совсем глупые вопросы.

Далее, в процессе разработки, стали появляться более осознанные вопросы. Очевидно, не хватало знаний. По совету коллеги купил книгу Брюса Эккеля «Философия Java». Эта книга довольно объемная — 1168 страниц. Затрагиваются практически все темы, необходимые для работы, начиная от понятий «класс» и «объект» и заканчивая многопоточностью. Автор доступным языком на примерах объясняет материал. Я бы эту книгу рекомендовал как начинающим, так и опытным разработчикам.

Сложности восприятия

Объекты

Первое, с чем я столкнулся в Java после многолетней разработки на Oracle, — это объекты. Простой и понятный, казалось бы, код поначалу вызывал недоумение.

Deal deal = new Deal();
deal.setDealNum(1L);

После этого кода каким-то магическим образом данные появляются в таблице БД. Если заглянуть в класс Deal, то там просто некоторое описание атрибутов и методов.

@Entity
@Table(name = "T_DEAL_EXAMPLE")
@SequenceGenerator(name = "SEQ_DEAL", sequenceName = "SEQ_DEAL", allocationSize = 1)
  
public class Deal {

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "SEQ_DEAL")
    @Column(name = "ID_DEAL")
    private Long id;

    /**
     * Номер сделки
     */
    @Column(name = "DEAL_NUM")
    private Long dealNum;

Где инсерты? Где апдейты? Мне как человеку, привыкшему работать непосредственно с данными, было странно, что теперь за меня это делает некий фреймворк. Конечно, если посмотреть в лог, то можно увидеть SQL, который генерирует Hibernate.

Hibernate: select SEQ_DEAL.nextval from dual
Hibernate: insert into T_DEAL_EXAMPLE (ID_CONTRACTOR, DEAL_CODE, DEAL_NUM, DT_DEAL, ID_DEAL) values (?, ?, ?, ?, ?)

Все есть класс

Еще одним открытием стало то, что в Java все типы, за исключением примитивных (boolean, byte, char, short, int, long, float, double), являются классами. Даже String и BigDecimal — это классы. Из-за этого возникают некоторые особенности. Нельзя просто так взять и сравнить два числа.

BigDecimal bigDecimal1 = new BigDecimal("1");
BigDecimal bigDecimal2 = new BigDecimal("1");
System.out.println(bigDecimal1==bigDecimal2);
System.out.println(bigDecimal1.compareTo(bigDecimal1));
----
false
0

Метод compareTo() сравнивает значения объектов и, если они равны, возвращает 0.

При операторе сравнения == сравниваются ссылки на объекты: равны будут только ссылки на один и тот же объект. В общем случае объекты должны сравниваться при помощи метода equals(), который сравнивает их по содержимому.

Если мы просто присвоим один объект другому, то они станут равны. То есть два объекта будут иметь одинаковые ссылки.

BigDecimal bigDecimal1 = new BigDecimal("1");
BigDecimal bigDecimal2 = bigDecimal1;
System.out.println(bigDecimal1==bigDecimal2);
---
true

Арифметические действия также необходимо производить при помощи методов. Нельзя просто взять и сложить два объекта BigDecimal.

BigDecimal bigDecimal1 = new BigDecimal("1");
BigDecimal bigDecimal2 = new BigDecimal("2");
//BigDecimal bigDecimal3 = bigDecimal1 + bigDecimal2; //Не допустимо
BigDecimal bigDecimal3 = bigDecimal1.add(bigDecimal2);
System.out.println(bigDecimal3);
---
3

Передача значений по ссылке

В Oracle по умолчанию значения параметров передаются по ссылке (режим IN), и значения этих параметров внутри процедуры неизменны. То есть если в процедуру передали массив, то за пределами этой процедуры мы уверены в его неизменности. Разумеется, если это не OUT-параметр.

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

private void changeDealNum(Deal deal) {
    deal.setDealNum(2L);
}

/**
 * Изменение значения по ссылке
 */
public void changeLinkDeal() {
    Deal deal = new Deal();
    deal.setDealNum(1L);
    System.out.println("Номер сделки до изменения по ссылке "+deal.getDealNum());
    changeDealNum(deal);
    System.out.println("Номер сделки после изменения по ссылке "+deal.getDealNum());
}
---
Номер сделки до изменения по ссылке 1
Номер сделки после изменения по ссылке 2

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

Например, у сделки Deal есть ссылка на контрагента Contractor.

/**
 * Контрагент
 */
@ManyToOne()
@JoinColumn(name = "ID_CONTRACTOR")
private Contractor contractor;

Можно легко менять контрагента через сделку.

Deal deal = new Deal();
deal.setDealNum(1L);
// Создаем контрагента с номером 100
Contractor contractor = new Contractor("100");
deal.setContractor(contractor);
System.out.println("Номер созданного контрагента: " + contractor.getRegisterNum());
// Изменяем контрагента через сделку
deal.getContractor().setRegisterNum("200");
System.out.println("Номер контрагента через сделку: " + deal.getContractor().getRegisterNum());
System.out.println("Номер контрагента в исходном объекте: " + contractor.getRegisterNum());
 ---
Номер созданного контрагента: 100
Номер контрагента через сделку: 200
Номер контрагента в исходном объекте: 200

Как видно из примера, мы изменили значение одного объекта через другой.

Функциональное программирование

Пожалуй, для меня это было самым сложным. В функциональном программировании функция по сути является объектом. Ее можно передавать в качестве аргументов, присваивать переменным, вызывать методы и так далее. Например, нам нужно вывести идентификаторы всех сделок через разделитель ;. Само собой напрашивающееся решение — перебрать сделки в цикле и вывести ID.

// Получаем список сделок
List<Deal> deals = dealService.findAll();
// Результирующая строка
StringBuilder result = new StringBuilder();
boolean first = true;
for (Deal deal : deals) {
    // Условие добавления разделителя
    if (first) {
        first = false;
    } else {
        result.append("; ");
    }
    // Собираем строку с ID
    result.append(deal.getId());
}
System.out.println(result);

В функциональном программировании подход немного иной. Пример для Java 7.

// Получаем список ID сделок
List<Long> dealIds = newArrayList(Iterables.transform(deals, new Function<Deal, Long>() {
    @Nullable
    public Long apply(@Nullable Deal input) {
        return input.getId();
    }
}));


// Выводим список через разделитель
System.out.println(Joiner.on("; ").join(dealIds));

Метод Iterables.transform() получает в качестве аргументов два интерфейса Iterable<F> и Function<F, T>. Интерфейс Iterable реализован у списка deals, а вот готовой реализации функции у нас нет. Поэтому реализовываем ее в анонимном классе.

Метод Iterables.transform() берет поэлементно значения из коллекции deals, преобразует их с помощью анонимной функции и возвращает список результатов преобразования в том же порядке. Далее методом Joiner.join() собираем список в строку с разделителями.

На первый взгляд конструкция с функцией кажется немного громоздкой и сложной для понимания. Перебор массива проще и понятней. Но со временем привыкаешь, и такие конструкции уже не кажутся страшными. Более того, они оказываются удобными. IntelliJ IDEA эти конструкции красиво схлопывает, показывая, что на входе и на выходе.

В Java 8 появились лямбда-выражения, которые еще больше упрощают запись. Пример получения списка ID сделок с использованием лямбда-выражения:

List<Long> dealIds = deals.stream().map(Deal::getId).collect(Collectors.toList());

В целом функциональное программирование хорошо подходит, например, для работы с коллекциями. Получается простая и лаконичная запись. Но если применять его для реализации бизнес-логики, то, на мой взгляд, сложность восприятия кода сильно возрастает. Но это достаточно спорная тема, и углубляться в нее я не буду. Скажу лишь, что если вы начинающий Java-разработчик и попали на проект, где сложная бизнес-логика реализована в функциональном стиле, то вам не повезло.

Case sensitivity

В Java код регистрозависим. После Oracle это кажется избыточным, да и вообще ненужным. Но немного поработав с Java, начинаешь понимать всю прелесть этого решения. Не нужно ломать голову над тем, как назвать переменную: называешь ее так же, как класс, только с маленькой буквы.

Deal deal = new Deal();

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

Запросы к БД

Как уже, наверное, понятно, в наших проектах для работы с базой используется фреймворк Hibernate. По сути, это ORM, которая маппит объекты Java на таблицы базы. Hibernate использует для построения запросов язык HQL (Hibernate Query Language). В принципе, он похож на SQL, но реализуется в терминах объектов и сильно ограничен по возможностям в сравнении с нативным SQL. Простой запрос к сделке будет выглядеть так:

SQL: 
SELECT * FROM t_deal_example t WHERE t.deal_num = :deal_num;
HQL:
"From Deal where dealNum = :dealNum"

Для человека, знающего SQL, понимание HQL труда не составит. При парсинге HQL в SQL объект Deal заменится на замаппленное значение аннотации @Table(name = "T_DEAL_EXAMPLE"), атрибут dealNum заменится на @Column(name = "DEAL_NUM") и так далее.

В целом неплохая идея. Можно легко менять поля, таблицы и даже базы практически без изменения приложения. Перемаппил объекты — и все заработало. Но есть нюанс. Все это хорошо работает, пока вы используете простые запросы к нескольким табличкам. Как только начинаются относительно сложные запросы, писать их на HQL становится сложно, а иногда и невозможно.

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

Для HQL-запроса "From Deal where dealNum = :dealNum" Hibernate выполнит два запроса: к сделке и контрагенту.

select deal0_.ID_DEAL       as ID_DEAL1_1_,
       deal0_.ID_CONTRACTOR as ID_CONTRACTOR5_1_,
       deal0_.DEAL_CODE     as DEAL_CODE2_1_,
       deal0_.DEAL_NUM      as DEAL_NUM3_1_,
       deal0_.DT_DEAL       as DT_DEAL4_1_
  from T_DEAL_EXAMPLE deal0_
 where deal0_.DEAL_NUM = ?
;
select contractor0_.ID_CONTRACTOR as ID_CONTRACTOR1_0_0_,
       contractor0_.FULL_NAME     as FULL_NAME2_0_0_,
       contractor0_.NAME          as NAME3_0_0_,
       contractor0_.REGISTER_NUM  as REGISTER_NUM4_0_0_
  from T_CONTRACTOR_EXAMPLE contractor0_
 where contractor0_.ID_CONTRACTOR = ?
 ;

Таким образом, при достаточно большом и сложном объекте у вас будет загружаться «полбазы».

Конечно, есть возможность сделать «ленивую» загрузку ссылок (@ManyToOne (fetch = FetchType.LAZY), но в таком случае нельзя будет использовать «ленивый» объект вне контекста Hibernate, если он не был загружен в сессии. Ну и разбираться с производительностью запросов HQL довольно сложно. Мало того что у тебя получается несколько страниц автогенерируемого SQL, так еще и непонятно, что со всем этим делать.

Приятные «плюшки»

Среда разработки

В качестве среды разработки у нас используется IntelliJ IDEA 2019. Когда я начал ею пользоваться после оракловых IDE, меня не покидала мысль: «Надо же, как тут все сделано для людей». Довольно удобные поиски, переходы, подсказки и так далее. Но сравнивать IDE для работы с БД и Java, наверное, не совсем корректно. Все-таки оракловые IDE в первую очередь предназначены для работы с данными.

Debug

Не могу не отметить дебаг, он меня прямо впечатлил. При дебаге в коде сразу видны значения.

Можно посмотреть весь объект со всеми связями.

Или получить значения методов.

Применить какое-то вычисление.

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

Заключение

На самом деле начать разрабатывать на Java не так уж и сложно, как казалось на первый взгляд. Но все-таки стоит учить матчасть. Простого знания ООП или того же Delphi будет недостаточно. В Java очень много своей специфики. Конечно, можно начать что-то писать и без предварительного изучения, интуитивно вы рано или поздно все равно поймете, что к чему. Но, как мне кажется, если стоит задача быстро освоить язык и начать на нем работать, проще прочесть пару книг или прослушать небольшой курс.

Источник: https://habr.com/ru/company/custis/blog/568032/


Интересные статьи

Интересные статьи

Очень заманчивая конфигурация была недавно анонсирована в рамках доступа "Всегда бесплатно". К сожалению, "очень быстро разбирают", а именно - сложно запустить экземпляр...
Качество работы некоторых интернет-провайдеров не выдерживает никакой критики. Подобные компании можно найти в любой стране. Чаще всего проблема в том, что организация является монополи...
Я люблю начинать разные сторонние проекты, считаю, что это один из лучших способов узнавать что-то новое и по-настоящему стоящее. И у меня есть один серьезный недостаток — я почти ник...
Ежечасный беспорядочный скроллинг ленты в соцсетях, беглые взгляды на ТОП-5 новостей и последующее неизбежное залипание на них, а также бесчисленные переходы по подпискам с горячими...
На даче холодно, и вы хотите за несколько часов до своего приезда туда включить обогреватель, или вас беспокоит возможность аварийного отключения системы отопления загородного дома в ваше отсутст...