Список контактов — работаем с базой данных
В этом разделе мы объединим большую часть накопленных знаний по технологиям и сделаем новый вариант нашего списка контактов. Я собираюсь сделать несколько вещей:
- Реализовать многоязычную версию для нашего приложения
- Создать DAO, который будет взаимодействовать с базой данных
- Использовать понятие reflection для возможности конфигурировать DAO
Многоязычная версия
Хочу сразу предупредить — мой вариант не надо рассматривать, как единственный в своем роде. Вы можете найти и другие интересные решения, которые вполне вероятно, вам могут понравится больше моего.
То, что я хочу реализовать, имеет следующую идею — создать класс, у которого можно будет спросить строку для компонента по определенной системе именования. В нашем случае у нас есть две формы, в которых мы выводим сообщения. Также у нас есть модель для таблицы. Логично, если у нас имя компонента будет складываться из двух частей:
- Имя (идентификатор) формы/модели
- Имя (идентификатор) компонента на этой форме/модели
Также есть смысл сразу создать небольшой конфигурационный файл, который нам пригодится для нескольких целей. Во-первых — это установка языка. Во-вторых — нам наверняка потребуется набор параметров для соединения с базой данных. Поэтому имеет смысл сразу предусмотреть такой файл. Что мы и сделаем. Для его обозначения будем использовать термин «глобальный». Может слишком громко для такого небольшого файла, но в данном случае «глобальный» означает — он для всех компонентов нашей программы и не говорит о его размерах.
Давайте приступим. Первым мы сделаем класс, который будет отвечать за нашу «глобальную» конфигурацию. Его задача — загрузить данные из конфигурационного файла и потом отдавать нужные параметры по запросу. Он вряд ли у нас будет сложный — вот такой:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
package edu.javacourse.contact.config; import java.io.FileReader; import java.io.IOException; import java.util.Properties; public class GlobalConfig { private static final String CONFIG_NAME = "contacts.properties"; private static final Properties GLOBAL_COFIG = new Properties(); // Сделать начальную загрузку параметров из файла по умолчанию public static void initGlobalConfig() throws IOException { initGlobalConfig(null); } // Сделать загрузку данных из конфигурационного файла, имя которого передано в виде параметра // Если имя null или пустое - берем файл по умолчанию. public static void initGlobalConfig(String name) throws IOException { if (name != null && !name.trim().isEmpty()) { GLOBAL_COFIG.load(new FileReader(name)); } else { GLOBAL_COFIG.load(new FileReader(CONFIG_NAME)); } } // Получить значение параметра из глобальной конфигурации по имени public static String getProperty(String property) { return GLOBAL_COFIG.getProperty(property); } } |
Наверно самое время понять, что же я тут написал. Потому как некоторые моменты могут вас заинтересовать. Наверняка сразу возник вопрос — почему я сделал два метода initGlobalConfig. Резонно. Ответ простой — я сделал это для удобства.
Рассуждал я следующим образом: во-первых, не иметь возможности подставить программе свой конфигурационный файл — не очень хорошая идея. Во всяком случае мне не нравится. Поэтому появился метод с входным параметром — initGlobalConfig(String name). Как можно видеть, если передали имя, то его и будем использовать. Если же параметр равен null или пустой, то берем файл по умолчанию.
Во-вторых — передавать в метод null выглядит не очень красиво. Поэтому и появился второй метод без параметров. Его задача — вызывать основной метод и передать туда тот самый «некрасивый» null.
Что касается остального кода, то он достаточно простой и если вы все-таки не понимаете, то рекомендую посмотреть раздел Коллекции — продолжение. Там есть раздел про класс Properties.
Итак, класс, который загружает «глобальную конфигурацию», мы написали. Настало время для создания класса, который будет отдавать нам строки для наших графических компонентов. Его задача — использовать механизм ресурсов (о котором мы говорили опять же в разделе Коллекции — продолжение).
Наши ресурсы — это набор файлов с базовым именем ContactResources. Я не стал создавать много файлов — сделал всего два. Один по умолчанию с русскими именами, второй — с английскими.
- ContactResources.properties
- ContactResources_en.properties
В конце я конечно же дам ссылку на код всего приложения, но тем не менее, хочу напомнить, что русские буквы в файлах properties заменяются на их код в Юникоде, поэтому выглядеть это будет не очень красиво. Вот такой вот некрасивый у нас файл ContactResources.properties
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
frame.refresh=\u041e\u0431\u043d\u043e\u0432\u0438\u0442\u044c frame.add=\u0414\u043e\u0431\u0430\u0432\u0438\u0442\u044c frame.update=\u0418\u0437\u043c\u0435\u043d\u0438\u0442\u044c frame.delete=\u0423\u0434\u0430\u043b\u0438\u0442\u044c dialog.givenname=\u0418\u043c\u044f dialog.surname=\u0424\u0430\u043c\u0438\u043b\u0438\u044f dialog.phone=\u0422\u0435\u043b\u0435\u0444\u043e\u043d dialog.email=e-mail model.id=ID model.givenname=\u0418\u043c\u044f model.surname=\u0424\u0430\u043c\u0438\u043b\u0438\u044f model.phone=\u0422\u0435\u043b\u0435\u0444\u043e\u043d model.email=e-mail |
Содержимое на самом деле будет интерпретироваться вот так:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
frame.refresh=Обновить frame.add=Добавить frame.update=Изменить frame.delete=Удалить dialog.givenname=Имя dialog.surname=Фамилия dialog.phone=Телефон dialog.email=e-mail model.id=ID model.givenname=Имя model.surname=Фамилия model.phone=Телефон model.email=e-mail |
Наши ресурсы разбиты на три группы. Первая (frame) касается основной формы со списком контактов. Вторая (dialog) — диалога для ввода данных. Ну и третья (model) — для модели отображения наших данных в таблице.
Теперь посмотрим на код нашего класса для работы с этими именами.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
package edu.javacourse.contact.gui; import edu.javacourse.contact.config.GlobalConfig; import java.util.Locale; import java.util.PropertyResourceBundle; public class GuiResource { private static final String RESOURCES = "edu.javacourse.contact.gui.ContactResources"; private static final String LANGUAGE = "language"; private static PropertyResourceBundle components = null; // Загрузка ресурсов для компонентов public static void initComponentResources() { String lang = GlobalConfig.getProperty(LANGUAGE); if (lang != null && !lang.trim().isEmpty()) { components = (PropertyResourceBundle) PropertyResourceBundle.getBundle(RESOURCES, new Locale(lang)); } else { components = (PropertyResourceBundle) PropertyResourceBundle.getBundle(RESOURCES); } } // Получение строки для отображения компонента public static String getLabel(String formId, String componentId) { return components.getString(formId + "." + componentId); } } |
У нас здесь два метода. Первый — initComponentResources — предназначен для загрузки наших ресурсов. В первой строке метода мы получаем из «глобальной конфигурации» язык, который сдираемся использовать и потом загружаем ресурсы для выбранного языка. Если язык не указан. то мы загружаем ресурсы по умолчанию (без указания локализации).
Теперь мы должны во всех местах, где раньше жестко прописывали строки, заменить их на вызовы GuiResource.getLabel(formId, componentId). Эти изменения мы должны сделать в наших файлах ContactFrame.java, RditContactDialog.java и ContactModel.java.
Т.к. код этих классов достаточно громоздкий, а изменения небольшие, я не стану приводить его в тексте статьи. Вы сможете посмотреть в полном примере. Просто поищите по коду строку GuiResource.getLabel и сможете разобраться что мы поменяли.
DAO для работы с базой данных
Разобравшись с многоязычностью, перейдем к написанию DAO для работы с базой данных. Всю необходимую информацию, которая потребуется нам для написания кода, мы уже разбирали в статьях, посвященных работе с базами данных. Так что в данном разделе мы будем просто писать код. Хотя кое-какие моменты нам все-таки потребуется разобрать. Если вы какие-то моменты вы уже не помните — рекомендую вам их освежить в памяти.
Для начала нам надо написать несложный скрипт для создания таблицы для наших контактов. Я сделал этот скрипт для PostreSQL — если вам захочется попробовать другую базы данных, то придется что-то поменять. В этом скрипте две части. первая удаляет таблицу в случае, если она есть. Вторая часть — создает. Мы уже встречались с этим скриптом в разделе. посвященном PostgreSQL — Установка PostgreSQL.
1 2 3 4 5 6 7 8 9 10 11 |
DROP TABLE IF EXISTS JC_CONTACT; CREATE TABLE JC_CONTACT ( CONTACT_ID SERIAL, FIRST_NAME VARCHAR(50) NOT NULL, LAST_NAME VARCHAR(50) NOT NULL, PHONE VARCHAR(50) NOT NULL, EMAIL VARCHAR(50) NOT NULL, PRIMARY KEY (CONTACT_ID) ); |
Теперь мы можем приступить к созданию нашего DAO для работы с базой данных. Как вы возможно помните, нам необходимо реализовать интерфейс ContactDAO. Но прежде чем мы погрузимся в создание нашего класса, я хочу заострить ваше внимание на одном очень непростом моменте. Это работа с соединением с базой данных. Точнее, это реализация интерфейса java.sql.Connection. Приведу для демонстрации пример получения коннекта:
1 2 3 4 5 |
Class.forName("org.postgresql.Driver"); String url = "jdbc:postgresql://localhost:5432/contactdb"; String login = "postgres"; String password = "postgres"; Connection con = DriverManager.getConnection(url, login, password); |
С одной стороны может показаться, что нет никаких подводных камней. Но давайте задумаемся вот над каким вопросом: Когда мы должны создавать наше соединение ?.
Как вы возможно слышали, получение соединения — операция достаточно дорогая. Сразу возникает мысль — а давайте создадим один коннект и будем им пользоваться. На первый взгляд идея кажется вполне здравой. Но не торопитесь хвалить себя. Идея крайне опасна. И опасность заключается в одном слове — МНОГОПОТОЧНОСТЬ.
Пока наше приложение работает как однопоточное (а на данный момент оно так и есть), то проблем не возникает. Но что может случиться, если соединением начнут пользоваться два и более потоков одновременно? Даже если предположить, что все компоненты для работы с базой данных потокобезопасны, то остается проблема транзакций. Представьте себе, что произойдет, если один поток начнет транзакцию и поставит свойство autoCommit в false, а второй чуть позже сделает установку на true? Будет такая неразбериха, что попытка понять что и как у вас не так отнимет кучу времени.
Поэтому мой вам совет: Не пытайтесь использовать одно соединение на все приложение.
Но что же тогда делать, спросите вы? Как все-таки добиться более производительного решения? Ответ на этот вопрос существует — это так называемый пул соединений (ConnectionPool), но я вернусь к нему позже. А пока сделаем не совсем оптимально — каждый раз будем создавать соединение. Чтобы мы могли легко модифицировать наш код после знакомства с ConnectionPool, я сделаю отдельный метод для получения коннекта. Также я использую нашу «глобальную конфигурацию» для настройки параметров соединения. Вопрос в общем спорный — правильно ли прописывать параметры соединения в глобальную конфигурацию, но я думаю, что в рамках учебного проекта можно так поступить. Хотя если вам захочется усовершенствовать — дерзайте.
Кроме отдельного метода для получения коннекта, я собираюсь сделать также отдельный класс для создания соединения. Причина банальна — мы чуть выше говорили, что соединение можно создавать по-разному. Значит для этого нам потребуются разные реализации. А где есть разные реализации одной и той же функциональности — возникают шаблоны проектирования. Для начала мы будем просто каждый раз создавать новое соединение. Класс в общем не сложный — чуть ниже вы его увидите. Но т.к. я хочу в будущем использовать пул соединений, который улучшит производительность, то есть смысл подумать над более легко расширяемой реализацией. Мы можем воспользоваться шаблоном «Абстрактная фабрика». Для этого создадим интерфейс ConnectionBuilder, простую реализацию этого интерфейса SimpleConnectionBuilder и наконец класс-фабрику ConnectionBuilderFactory. Давайте смотреть код.
Первым мы делаем интерфейс ConnectionBuilder. Здесь все очень просто:
1 2 3 4 5 6 7 8 9 |
package edu.javacourse.contact.dao; import java.sql.Connection; import java.sql.SQLException; public interface ConnectionBuilder { Connection getConnection() throws SQLException; } |
Реализация будет несколько сложнее, но тоже не займет много места. Загружаем драйвер и используем конфигурацию для соединения.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
package edu.javacourse.contact.dao; import edu.javacourse.contact.config.GlobalConfig; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; public class SimpleConnectionBuilder implements ConnectionBuilder { public SimpleConnectionBuilder() { try { Class.forName(GlobalConfig.getProperty("db.driver.class")); } catch (ClassNotFoundException ex) { ex.printStackTrace(); } } @Override public Connection getConnection() throws SQLException { String url = GlobalConfig.getProperty("db.url"); String login = GlobalConfig.getProperty("db.login"); String password = GlobalConfig.getProperty("db.password"); return DriverManager.getConnection(url, login, password); } } |
Как можно видеть, в методе getConnection мы получаем данные из нашей глобальной конфигурации и создаем новое соединение с базой данных. В принципе, мы это уже видели при разборе способов работы с базой данных на Java — ничего нового. И наконец фабрика:
1 2 3 4 5 6 7 8 |
package edu.javacourse.contact.dao; public class ConnectionBuilderFactory { public static ConnectionBuilder getConnectionBuilder() { return new SimpleConnectionBuilder(); } } |
На данный момент мы сделали очень простую фабрику — я думаю, что после того, как мы закончим подключение нашего нового DAO к проекту, у вас появятся идеи по ее усовершенствованию.
Кроме вышеприведенных изменений, мы должны сделать еще кое-какие действия. Раньше мы с вами объявили интерфейс ContactDAO и не сделали достаточно важный шаг — не позволили порождать исключения при работе с источником данных. В начале нам это не сильном мешало, но теперь мы работает с базой данных и эта работа порождает исключения. Игнорировать их было бы не самым хорошим решением. Поэтому я изменил наш интерфейс, добавив туда исключения. Выглядит это так:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
package edu.javacourse.contact.dao; import edu.javacourse.contact.entity.Contact; import edu.javacourse.contact.exception.ContactDaoException; import java.util.List; /** * Интерфейс для определения функций хранлиза данных о контактах */ public interface ContactDAO { // Добавление контакта - возвращает ID добавленного контакта public Long addContact(Contact contact) throws ContactDaoException; // Редактирование контакта public void updateContact(Contact contact) throws ContactDaoException; // Удаление контакта по его ID public void deleteContact(Long contactId) throws ContactDaoException; // Получение контакта public Contact getContact(Long contactId) throws ContactDaoException; // Получение списка контактов public List<Contact> findContacts() throws ContactDaoException; } |
Данное изменение порождает дальнейшее шаги. Во-первых, нам надо создать класс исключения. Мы сделаем сразу два класса — для DAO и для бизнес-логики. Пока они вот такие — не будем усложнять себе жизнь.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
package edu.javacourse.contact.exception; public class ContactDaoException extends Exception~ { public ContactDaoException() { } public ContactDaoException(String message) { super(message); } public ContactDaoException(Throwable cause) { super(cause); } public ContactDaoException(String message, Throwable cause) { super(message, cause); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
package edu.javacourse.contact.exception; public class ContactBusinessException extends Exception { public ContactBusinessException() { } public ContactBusinessException(String message) { super(message); } public ContactBusinessException(Throwable cause) { super(cause); } public ContactBusinessException(String message, Throwable cause) { super(message, cause); } } |
Т.к. теперь наш DAO порождает исключения, то наш класс ContactManager тоже слегка изменится — мы теперь обрабатываем исключения от DAO и порождаем исключения ContactBusinessException. Теперь он выглядит кот так:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
package edu.javacourse.contact.business; import edu.javacourse.contact.dao.ContactDAO; import edu.javacourse.contact.dao.ContactDAOFactory; import edu.javacourse.contact.entity.Contact; import edu.javacourse.contact.exception.ContactBusinessException; import edu.javacourse.contact.exception.ContactDaoException; import java.util.List; /** * Класс для реализации функций над списком контактов */ public class ContactManager { private final ContactDAO dao; public ContactManager() { dao = ContactDAOFactory.getContactDAO(); } // Добавление контакта - возвращает ID добавленного контакта public Long addContact(Contact contact) throws ContactBusinessException { try { return dao.addContact(contact); } catch (ContactDaoException ex) { throw new ContactBusinessException(ex); } } // Редактирование контакта public void updateContact(Contact contact) throws ContactBusinessException { try { dao.updateContact(contact); } catch (ContactDaoException ex) { throw new ContactBusinessException(ex); } } // Удаление контакта по его ID public void deleteContact(Long contactId) throws ContactBusinessException { try { dao.deleteContact(contactId); } catch (ContactDaoException ex) { throw new ContactBusinessException(ex); } } // Получение одного контакта public Contact getContact(Long contactId) throws ContactBusinessException { try { return dao.getContact(contactId); } catch (ContactDaoException ex) { throw new ContactBusinessException(ex); } } // Получение списка контактов public List<Contact> findContacts() throws ContactBusinessException { try { return dao.findContacts(); } catch (ContactDaoException ex) { throw new ContactBusinessException(ex); } } } |
Нам также придется исправить класс ContactFrame — он тоже должен обрабатывать исключения. Смотрим код. Я очень надеюсь, что разобрать его самостоятельно вы сможете.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 |
package edu.javacourse.contact.gui; import edu.javacourse.contact.business.ContactManager; import edu.javacourse.contact.entity.Contact; import edu.javacourse.contact.exception.ContactBusinessException; import java.awt.BorderLayout; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.List; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTable; import javax.swing.ListSelectionModel; public class ContactFrame extends JFrame implements ActionListener { private static final String FRAME = "frame"; private static final String C_REFRESH = "refresh"; private static final String C_ADD = "add"; private static final String C_UPDATE = "update"; private static final String C_DELETE = "delete"; private static final String LOAD = "LOAD"; private static final String ADD = "ADD"; private static final String EDIT = "EDIT"; private static final String DELETE = "DELETE"; private final ContactManager contactManager = new ContactManager(); private final JTable contactTable = new JTable(); // В конструкторе мы создаем нужные элементы public ContactFrame() { // Выставляем у таблицы свойство, которое позволяет выделить // ТОЛЬКО одну строку в таблице contactTable.setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION); // Используем layout GridBagLayout GridBagLayout gridbag = new GridBagLayout(); GridBagConstraints gbc = new GridBagConstraints(); // Каждый элемент является последним в строке gbc.gridwidth = GridBagConstraints.REMAINDER; // Элемент раздвигается на весь размер ячейки gbc.fill = GridBagConstraints.BOTH; // Но имеет границы - слева, сверху и справа по 5. Снизу - 0 gbc.insets = new Insets(5, 5, 0, 5); // Создаем панель для кнопок JPanel btnPanel = new JPanel(); // усанавливаем у него layout btnPanel.setLayout(gridbag); // Создаем кнопки и укзаываем их загловок и ActionCommand btnPanel.add(createButton(gridbag, gbc, GuiResource.getLabel(FRAME, C_REFRESH), LOAD)); btnPanel.add(createButton(gridbag, gbc, GuiResource.getLabel(FRAME, C_ADD), ADD)); btnPanel.add(createButton(gridbag, gbc, GuiResource.getLabel(FRAME, C_UPDATE), EDIT)); btnPanel.add(createButton(gridbag, gbc, GuiResource.getLabel(FRAME, C_DELETE), DELETE)); // Создаем панель для левой колокни с кнопками JPanel left = new JPanel(); // Выставляем layout BorderLayout left.setLayout(new BorderLayout()); // Кладем панель с кнопками в верхнюю часть left.add(btnPanel, BorderLayout.NORTH); // Кладем панель для левой колонки на форму слева - WEST add(left, BorderLayout.WEST); // Кладем панель со скролингом, внутри которой нахоится наша таблица // Теперь таблица может скроллироваться add(new JScrollPane(contactTable), BorderLayout.CENTER); // выставляем координаты формы setBounds(100, 200, 900, 400); // При закрытии формы заканчиваем работу приложения setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // Загружаем контакты try { loadContact(); } catch (ContactBusinessException ex) { ex.printStackTrace(); } } // Метод создает кнопку с заданными харктеристиками - заголовок и действие private JButton createButton(GridBagLayout gridbag, GridBagConstraints gbc, String title, String action) { // Создаем кнопку с заданным загловком JButton button = new JButton(title); // Действие будетп роверяться в обработчике и мы будем знать, какую // именно кнопку нажали button.setActionCommand(action); // Обработчиком события от кнопки являемся сама форма button.addActionListener(this); // Выставляем свойства для размещения для кнопки gridbag.setConstraints(button, gbc); return button; } @Override // Обработка нажатий кнопок public void actionPerformed(ActionEvent ae) { // Получаем команду - ActionCommand String action = ae.getActionCommand(); // В зависимости от команды выполняем действия try { switch (action) { // Перегрузка данных case LOAD: loadContact(); break; // Добавление записи case ADD: addContact(); break; // Исправление записи case EDIT: editContact(); break; // Удаление записи case DELETE: deleteContact(); break; } } catch (ContactBusinessException ex) { // Очень простой способ показать сообщение JOptionPane.showMessageDialog(this, ex.getMessage()); } } // Загрухить список контактов private void loadContact() throws ContactBusinessException { // Обращаемся к классу для загрузки списка контактов List<Contact> contacts = contactManager.findContacts(); // Создаем модель, которой передаем полученный список ContactModel cm = new ContactModel(contacts); // Передаем нашу модель таблице - и она может ее отображать contactTable.setModel(cm); } // Добавление контакта private void addContact() throws ContactBusinessException { // Создаем диалог для ввода данных EditContactDialog ecd = new EditContactDialog(); // Обрабатываем закрытие диалога saveContact(ecd); } // Редактирование контакта private void editContact() throws ContactBusinessException { // Получаем выделеннуб строку int sr = contactTable.getSelectedRow(); // если строка выделена - можно ее редактировать if (sr != -1) { // Получаем ID контакта Long id = Long.parseLong(contactTable.getModel().getValueAt(sr, 0).toString()); // получаем данные контакта по его ID Contact cnt = contactManager.getContact(id); // Создаем диалог для ввода данных и передаем туда контакт EditContactDialog ecd = new EditContactDialog(contactManager.getContact(id)); // Обрабатываем закрытие диалога saveContact(ecd); } else { // Если строка не выделена - сообщаем об этом JOptionPane.showMessageDialog(this, "Вы должны выделить строку для редактирования"); } } // Удаление контакта private void deleteContact() throws ContactBusinessException { // Получаем выделеннуб строку int sr = contactTable.getSelectedRow(); if (sr != -1) { // Получаем ID контакта Long id = Long.parseLong(contactTable.getModel().getValueAt(sr, 0).toString()); // Удаляем контакт contactManager.deleteContact(id); // перегружаем список контактов loadContact(); } else { JOptionPane.showMessageDialog(this, "Вы должны выделить строку для удаления"); } } // Общий метод для добавления и изменения контакта private void saveContact(EditContactDialog ecd) throws ContactBusinessException { // Если мы нажали кнопку SAVE if (ecd.isSave()) { // Получаем контакт из диалогового окна Contact cnt = ecd.getContact(); if (cnt.getContactId() != null) { // Если ID у контакта есть, то мы его обновляем contactManager.updateContact(cnt); } else { // Если у контакта нет ID - значит он новый и мы его добавляем contactManager.addContact(cnt); } loadContact(); } } } |
Теперь нам осталось создать код для работы с базой данных. Всю необходимую информацию мы уже изучали, так что не буду повторяться — просто приведу код нашего класса. Итак, давайте смотреть наш новый DAO. Единственный момент, который может вызвать вопросы — это блоки try … catch, которые используют возможность, появившуюся в Java 7. Она позволяет автоматически освобождать ресурсы, которые вы задействовали в рамках этого блока. При такой конструкции вам не надо думать об освобождении/закрытии этого ресурса — это произойдет автоматически при любом исходе. Будет все хорошо или вы поймаете исключение — в любом случае ресурс будет закрыт. Причем обратите внимание, что можно задействовать сразу несколько ресурсов, чем я и воспользовался. Очень-очень удобная конструкция — обязательно запомните ее, но учтите, что это работает только начиная с Java 7.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 |
package edu.javacourse.contact.dao; import edu.javacourse.contact.entity.Contact; import edu.javacourse.contact.exception.ContactDaoException; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.LinkedList; import java.util.List; public class ContactDbDAO implements ContactDAO { private static final String SELECT = "SELECT contact_id, first_name, last_name, phone, email FROM jc_contact ORDER BY first_name, last_name"; private static final String SELECT_ONE = "SELECT contact_id, first_name, last_name, phone, email FROM jc_contact WHERE contact_id=?"; private static final String INSERT = "INSERT INTO jc_contact (first_name, last_name, phone, email) VALUES (?, ?, ?, ?)"; private static final String UPDATE = "UPDATE jc_contact SET first_name=?, last_name=?, phone=?, email=? WHERE contact_id=?"; private static final String DELETE = "DELETE FROM jc_contact WHERE contact_id=?"; private ConnectionBuilder builder = new SimpleConnectionBuilder(); private Connection getConnection() throws SQLException { return builder.getConnection(); } @Override public Long addContact(Contact contact) throws ContactDaoException { try (Connection con = getConnection(); PreparedStatement pst = con.prepareStatement(INSERT, new String[]{"contact_id"})) { Long contactId = -1L; pst.setString(1, contact.getFirstName()); pst.setString(2, contact.getLastName()); pst.setString(3, contact.getPhone()); pst.setString(4, contact.getEmail()); pst.executeUpdate(); ResultSet gk = pst.getGeneratedKeys(); if (gk.next()) { contactId = gk.getLong("contact_id"); } gk.close(); return contactId; } catch (Exception e) { throw new ContactDaoException(e); } } @Override public void updateContact(Contact contact) throws ContactDaoException { try (Connection con = getConnection(); PreparedStatement pst = con.prepareStatement(UPDATE)) { pst.setString(1, contact.getFirstName()); pst.setString(2, contact.getLastName()); pst.setString(3, contact.getPhone()); pst.setString(4, contact.getEmail()); pst.setLong(5, contact.getContactId()); pst.executeUpdate(); } catch (Exception e) { throw new ContactDaoException(e); } } @Override public void deleteContact(Long contactId) throws ContactDaoException { try (Connection con = getConnection(); PreparedStatement pst = con.prepareStatement(DELETE)) { pst.setLong(1, contactId); pst.executeUpdate(); } catch (Exception e) { throw new ContactDaoException(e); } } @Override public Contact getContact(Long contactId) throws ContactDaoException { Contact contact = null; try (Connection con = getConnection()) { PreparedStatement pst = con.prepareStatement(SELECT_ONE); pst.setLong(1, contactId); ResultSet rs = pst.executeQuery(); if (rs.next()) { contact = fillContact(rs); } rs.close(); pst.close(); } catch (Exception e) { throw new ContactDaoException(e); } return contact; } @Override public List<Contact> findContacts() throws ContactDaoException { List<Contact> list = new LinkedList<>(); try (Connection con = getConnection(); PreparedStatement pst = con.prepareStatement(SELECT); ResultSet rs = pst.executeQuery()) { while (rs.next()) { list.add(fillContact(rs)); } rs.close(); } catch (Exception e) { throw new ContactDaoException(e); } return list; } private Contact fillContact(ResultSet rs) throws SQLException { Contact contact = new Contact(); contact.setContactId(rs.getLong("contact_id")); contact.setFirstName(rs.getString("first_name")); contact.setLastName(rs.getString("last_name")); contact.setPhone(rs.getString("phone")); contact.setEmail(rs.getString("email")); return contact; } } |
Не писал комментарии, т.к. полагаю, что они не требуются — вся необходимая информация уже была.
Reflection для возможности конфигурировать DAO<
Нам остался последний пункт из нашего плана — сделать выбор DAO конфигурируемым. Для этого мы воспользуемся технологией Reflection, о которой мы уже говорили в разделе Reflection — основы.
Здесь нет ничего сложного — нам надо прописать в конфигурации (я предлагаю использовать глобальную) имя нужного нам класса. Все очень просто.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
package edu.javacourse.contact.dao; import edu.javacourse.contact.config.GlobalConfig; /** * Фабрика для создания экземпляра ContactDAO */ public class ContactDAOFactory { public static ContactDAO getContactDAO() { try { Class dao = Class.forName(GlobalConfig.getProperty("dao.class")); return (ContactDAO)dao.newInstance(); } catch (ClassNotFoundException | InstantiationException | IllegalAccessException ex) { ex.printStackTrace(); } return null; } } |
Для загрузки конфигурации мы дополним наш запускающий класс ContactTest. Особо комментировать здесь нечего — код прилагается.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
package edu.javacourse.contact.test; import edu.javacourse.contact.config.GlobalConfig; import edu.javacourse.contact.gui.ContactFrame; import edu.javacourse.contact.gui.GuiResource; public class ContactTest { public static void main(String[] args) { // Загружаем конфигурацию из файла и загружаем ресурсы для разных языков try { GlobalConfig.initGlobalConfig(); GuiResource.initComponentResources(); } catch (Exception ex) { ex.printStackTrace(System.out); return; } ContactFrame cf = new ContactFrame(); cf.setVisible(true); } } |
Ну и под конец посмотрим на файл contact.properties, который содержит нашу глобальную конфигурацию.
1 2 3 4 5 6 7 8 |
language=en dao.class=edu.javacourse.contact.dao.ContactDbDAO db.driver.class=org.postgresql.Driver db.url=jdbc:postgresql://localhost:5432/contactdb db.login=postgres db.password=postgres |
Как и всегда, вы можете скачать наш проект здесь: Список контактов.
ВАЖНО !!! Что конечно же необходимо учесть — вам надо подключить JAR-файл с драйвером для работы с PostgreSQL. Как это сделать, мы уже разбирали в предыдущих статьях — Что такое JAR-файлы. Удачи.
И теперь нас ждет следующая статья: Переезжаем на Maven