Список контактов — работаем с базой данных

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

  1. Реализовать многоязычную версию для нашего приложения
  2. Создать DAO, который будет взаимодействовать с базой данных
  3. Использовать понятие reflection для возможности конфигурировать DAO

Многоязычная версия

Хочу сразу предупредить — мой вариант не надо рассматривать, как единственный в своем роде. Вы можете найти и другие интересные решения, которые вполне вероятно, вам могут понравится больше моего.
То, что я хочу реализовать, имеет следующую идею — создать класс, у которого можно будет спросить строку для компонента по определенной системе именования. В нашем случае у нас есть две формы, в которых мы выводим сообщения. Также у нас есть модель для таблицы. Логично, если у нас имя компонента будет складываться из двух частей:

  1. Имя (идентификатор) формы/модели
  2. Имя (идентификатор) компонента на этой форме/модели

Также есть смысл сразу создать небольшой конфигурационный файл, который нам пригодится для нескольких целей. Во-первых — это установка языка. Во-вторых — нам наверняка потребуется набор параметров для соединения с базой данных. Поэтому имеет смысл сразу предусмотреть такой файл. Что мы и сделаем. Для его обозначения будем использовать термин «глобальный». Может слишком громко для такого небольшого файла, но в данном случае «глобальный» означает — он для всех компонентов нашей программы и не говорит о его размерах.

Давайте приступим. Первым мы сделаем класс, который будет отвечать за нашу «глобальную» конфигурацию. Его задача — загрузить данные из конфигурационного файла и потом отдавать нужные параметры по запросу. Он вряд ли у нас будет сложный — вот такой:

Наверно самое время понять, что же я тут написал. Потому как некоторые моменты могут вас заинтересовать. Наверняка сразу возник вопрос — почему я сделал два метода initGlobalConfig. Резонно. Ответ простой — я сделал это для удобства.
Рассуждал я следующим образом: во-первых, не иметь возможности подставить программе свой конфигурационный файл — не очень хорошая идея. Во всяком случае мне не нравится. Поэтому появился метод с входным параметром — initGlobalConfig(String name). Как можно видеть, если передали имя, то его и будем использовать. Если же параметр равен null или пустой, то берем файл по умолчанию.
Во-вторых — передавать в метод null выглядит не очень красиво. Поэтому и появился второй метод без параметров. Его задача — вызывать основной метод и передать туда тот самый «некрасивый» null.
Что касается остального кода, то он достаточно простой и если вы все-таки не понимаете, то рекомендую посмотреть раздел Коллекции — продолжение. Там есть раздел про класс Properties.

Итак, класс, который загружает «глобальную конфигурацию», мы написали. Настало время для создания класса, который будет отдавать нам строки для наших графических компонентов. Его задача — использовать механизм ресурсов (о котором мы говорили опять же в разделе Коллекции — продолжение).
Наши ресурсы — это набор файлов с базовым именем ContactResources. Я не стал создавать много файлов — сделал всего два. Один по умолчанию с русскими именами, второй — с английскими.

  1. ContactResources.properties
  2. ContactResources_en.properties

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

Содержимое на самом деле будет интерпретироваться вот так:

Наши ресурсы разбиты на три группы. Первая (frame) касается основной формы со списком контактов. Вторая (dialog) — диалога для ввода данных. Ну и третья (model) — для модели отображения наших данных в таблице.
Теперь посмотрим на код нашего класса для работы с этими именами.

У нас здесь два метода. Первый — initComponentResources — предназначен для загрузки наших ресурсов. В первой строке метода мы получаем из «глобальной конфигурации» язык, который сдираемся использовать и потом загружаем ресурсы для выбранного языка. Если язык не указан. то мы загружаем ресурсы по умолчанию (без указания локализации).
Теперь мы должны во всех местах, где раньше жестко прописывали строки, заменить их на вызовы GuiResource.getLabel(formId, componentId). Эти изменения мы должны сделать в наших файлах ContactFrame.java, RditContactDialog.java и ContactModel.java.
Т.к. код этих классов достаточно громоздкий, а изменения небольшие, я не стану приводить его в тексте статьи. Вы сможете посмотреть в полном примере. Просто поищите по коду строку GuiResource.getLabel и сможете разобраться что мы поменяли.

DAO для работы с базой данных

Разобравшись с многоязычностью, перейдем к написанию DAO для работы с базой данных. Всю необходимую информацию, которая потребуется нам для написания кода, мы уже разбирали в статьях, посвященных работе с базами данных. Так что в данном разделе мы будем просто писать код. Хотя кое-какие моменты нам все-таки потребуется разобрать. Если вы какие-то моменты вы уже не помните — рекомендую вам их освежить в памяти.

Для начала нам надо написать несложный скрипт для создания таблицы для наших контактов. Я сделал этот скрипт для PostreSQL — если вам захочется попробовать другую базы данных, то придется что-то поменять. В этом скрипте две части. первая удаляет таблицу в случае, если она есть. Вторая часть — создает. Мы уже встречались с этим скриптом в разделе. посвященном PostgreSQL — Установка PostgreSQL.

Теперь мы можем приступить к созданию нашего DAO для работы с базой данных. Как вы возможно помните, нам необходимо реализовать интерфейс ContactDAO. Но прежде чем мы погрузимся в создание нашего класса, я хочу заострить ваше внимание на одном очень непростом моменте. Это работа с соединением с базой данных. Точнее, это реализация интерфейса java.sql.Connection. Приведу для демонстрации пример получения коннекта:

С одной стороны может показаться, что нет никаких подводных камней. Но давайте задумаемся вот над каким вопросом: Когда мы должны создавать наше соединение ?.
Как вы возможно слышали, получение соединения — операция достаточно дорогая. Сразу возникает мысль — а давайте создадим один коннект и будем им пользоваться. На первый взгляд идея кажется вполне здравой. Но не торопитесь хвалить себя. Идея крайне опасна. И опасность заключается в одном слове — МНОГОПОТОЧНОСТЬ.
Пока наше приложение работает как однопоточное (а на данный момент оно так и есть), то проблем не возникает. Но что может случиться, если соединением начнут пользоваться два и более потоков одновременно? Даже если предположить, что все компоненты для работы с базой данных потокобезопасны, то остается проблема транзакций. Представьте себе, что произойдет, если один поток начнет транзакцию и поставит свойство autoCommit в false, а второй чуть позже сделает установку на true? Будет такая неразбериха, что попытка понять что и как у вас не так отнимет кучу времени.
Поэтому мой вам совет: Не пытайтесь использовать одно соединение на все приложение.
Но что же тогда делать, спросите вы? Как все-таки добиться более производительного решения? Ответ на этот вопрос существует — это так называемый пул соединений (ConnectionPool), но я вернусь к нему позже. А пока сделаем не совсем оптимально — каждый раз будем создавать соединение. Чтобы мы могли легко модифицировать наш код после знакомства с ConnectionPool, я сделаю отдельный метод для получения коннекта. Также я использую нашу «глобальную конфигурацию» для настройки параметров соединения. Вопрос в общем спорный — правильно ли прописывать параметры соединения в глобальную конфигурацию, но я думаю, что в рамках учебного проекта можно так поступить. Хотя если вам захочется усовершенствовать — дерзайте.
Кроме отдельного метода для получения коннекта, я собираюсь сделать также отдельный класс для создания соединения. Причина банальна — мы чуть выше говорили, что соединение можно создавать по-разному. Значит для этого нам потребуются разные реализации. А где есть разные реализации одной и той же функциональности — возникают шаблоны проектирования. Для начала мы будем просто каждый раз создавать новое соединение. Класс в общем не сложный — чуть ниже вы его увидите. Но т.к. я хочу в будущем использовать пул соединений, который улучшит производительность, то есть смысл подумать над более легко расширяемой реализацией. Мы можем воспользоваться шаблоном «Абстрактная фабрика». Для этого создадим интерфейс ConnectionBuilder, простую реализацию этого интерфейса SimpleConnectionBuilder и наконец класс-фабрику ConnectionBuilderFactory. Давайте смотреть код.
Первым мы делаем интерфейс ConnectionBuilder. Здесь все очень просто:

Реализация будет несколько сложнее, но тоже не займет много места. Загружаем драйвер и используем конфигурацию для соединения.

Как можно видеть, в методе getConnection мы получаем данные из нашей глобальной конфигурации и создаем новое соединение с базой данных. В принципе, мы это уже видели при разборе способов работы с базой данных на Java — ничего нового. И наконец фабрика:

На данный момент мы сделали очень простую фабрику — я думаю, что после того, как мы закончим подключение нашего нового DAO к проекту, у вас появятся идеи по ее усовершенствованию.
Кроме вышеприведенных изменений, мы должны сделать еще кое-какие действия. Раньше мы с вами объявили интерфейс ContactDAO и не сделали достаточно важный шаг — не позволили порождать исключения при работе с источником данных. В начале нам это не сильном мешало, но теперь мы работает с базой данных и эта работа порождает исключения. Игнорировать их было бы не самым хорошим решением. Поэтому я изменил наш интерфейс, добавив туда исключения. Выглядит это так:

Данное изменение порождает дальнейшее шаги. Во-первых, нам надо создать класс исключения. Мы сделаем сразу два класса — для DAO и для бизнес-логики. Пока они вот такие — не будем усложнять себе жизнь.

Т.к. теперь наш DAO порождает исключения, то наш класс ContactManager тоже слегка изменится — мы теперь обрабатываем исключения от DAO и порождаем исключения ContactBusinessException. Теперь он выглядит кот так:

Нам также придется исправить класс ContactFrame — он тоже должен обрабатывать исключения. Смотрим код. Я очень надеюсь, что разобрать его самостоятельно вы сможете.

Теперь нам осталось создать код для работы с базой данных. Всю необходимую информацию мы уже изучали, так что не буду повторяться — просто приведу код нашего класса. Итак, давайте смотреть наш новый DAO. Единственный момент, который может вызвать вопросы — это блоки try … catch, которые используют возможность, появившуюся в Java 7. Она позволяет автоматически освобождать ресурсы, которые вы задействовали в рамках этого блока. При такой конструкции вам не надо думать об освобождении/закрытии этого ресурса — это произойдет автоматически при любом исходе. Будет все хорошо или вы поймаете исключение — в любом случае ресурс будет закрыт. Причем обратите внимание, что можно задействовать сразу несколько ресурсов, чем я и воспользовался. Очень-очень удобная конструкция — обязательно запомните ее, но учтите, что это работает только начиная с Java 7.

Не писал комментарии, т.к. полагаю, что они не требуются — вся необходимая информация уже была.

Reflection для возможности конфигурировать DAO<

Нам остался последний пункт из нашего плана — сделать выбор DAO конфигурируемым. Для этого мы воспользуемся технологией Reflection, о которой мы уже говорили в разделе Reflection — основы.
Здесь нет ничего сложного — нам надо прописать в конфигурации (я предлагаю использовать глобальную) имя нужного нам класса. Все очень просто.

Для загрузки конфигурации мы дополним наш запускающий класс ContactTest. Особо комментировать здесь нечего — код прилагается.

Ну и под конец посмотрим на файл contact.properties, который содержит нашу глобальную конфигурацию.

Как и всегда, вы можете скачать наш проект здесь: Список контактов.
ВАЖНО !!! Что конечно же необходимо учесть — вам надо подключить JAR-файл с драйвером для работы с PostgreSQL. Как это сделать, мы уже разбирали в предыдущих статьях — Что такое JAR-файлы. Удачи.

И теперь нас ждет следующая статья: Переезжаем на Maven