понеділок, 29 жовтня 2012 р.

Використовуємо Java Database Connectivity

Представляю вирізку з Java для чайників Баррі Берда по роботі з базою даних.Розглядається з'єднання з базою даних, запис даних та подання запитів. Далі оригінальний текст.

JDBC та Java DB

Коли я вперше розпочав роботу з базами даних, моєю найбільшою проблемою було з’єднання з базою даних. Я написав весь необхідний Java код. (Насправді, я скопіював весь Java код з якоїсь книги.) Програмна частина була простою. Найважчою частиною було змусити мій код знайти базу даних в системі.

Частиною проблеми було те, що шлях в який ваш код взаємодії з базою даних залежить від виду вашої системи та виду бази даних, що встановлена в вашій системі. Книга, якою я користувався не могла описати в деталях усе, тому що деталі  відрізнялися від комп’ютера до комп’ютера. Зараз же я пишу мій власний розділ про з’єднання з базою даних. Що ж я як автор робитиму?

На моє щастя, набір розробника Java (JDK) поставляється з власною вбудованою базою даних – Java DB. Java DB базується на базі даних Apache Derby і є доволі безпечною, полегшеною і стандартизованою. Java DB виконується самостійно від решти Java JDK. Java гуру представили Java DB з випуском Java 6.

Тож Java DB робить життя легшим для мене, надаючи загальнодоступну базу даних, яку можуть застосовувати всі мої читачі. Вона вільнодоступна і не вимагає встановлення.

А що якщо ви не використовуєте Java DB? Що якщо всі ваші дані зберігаються в іншому типі бази даних; як то MySQL, PostgreSQL, SQLLite, Oracle, Microsoft Access, DB2 або в будь-якій іншій? Тоді Java має рішення для вас! Класи Java Database Connectivity (JDBC) надають загальний доступ до більшості систем управління базами даних. Просто візьміть драйвер для вашої улюбленої системи, модифікуйте два рядочки коду в прикладах даного розділу і ваш код готовий до виконання.


Створення даних

JDBC розміщується у двох пакетах: java.sql та javax.sql, які є частиною Java API. Приклади даного розділу використовують класи java.sql. Перший приклад наведений в роздруківці 16-1.

Роздруківка 16-1: Створення бази даних, таблиці та вставлення даних.

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;

class CreateTable {
public static void main(String args[]) {
    
    final String DRIVER =
        "org.apache.derby.jdbc.EmbeddedDriver";
    final String CONNECTION =
        "jdbc:derby:AccountDatabase;create=true";
    try {
        Class.forName(DRIVER).newInstance();
    } catch (InstantiationException e) {
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    }
    try (Connection connection =
         DriverManager.getConnection(CONNECTION);
        Statement statement =
         connection.createStatement()) {
        statement.executeUpdate("create table ACCOUNTS"
                +"(NAME VARCHAR(32) NOT NULL PRIMARY KEY,"
                +" ADDRESS VARCHAR(32), BALANCE FLOAT) ");
        statement.executeUpdate("insert into ACCOUNTS"
                +" values ('Barry Burd', '222 Cyber Lane', 24.02)");
        statement.executeUpdate("insert into ACCOUNTS values"
                +" ('Joe Dow', '111 Luddite Street', 55.63)");
        
    } catch (SQLException e) {
        e.printStackTrace();
    }
    }
}

Щоб використовувати MySQL замість Java DB, зробіть деякі зміни в лістингу 16-1. Змініть значення DRIVER на "com.mysql.jdbc.Driver". Змініть значення CONNECTION на "jdbc:mysql://localhost/AccountDatabase;
create=true". Зробіть подібні зміни для DB2, Oracle та інших баз даних

Щоб запустити наведений код, вам необхідно мати файл, що містить відповідний драйвер бази даних, і даний файл повинен бути розміщений там, де Java зможе його знайти. В прикладах даного розділу я з’єднуюсь з базою даних Java DB, знаної як база даних Apache Derby. Драйвер знаходиться у файлі з назвою derby.jar, який зазвичай знаходиться в директорії JDK db/lib. Щоб зробити db/lib/derby.jar доступним для моєї програми, я додаю даний файл до Java classpath.

Спосіб в який додати .jar файл до classpath залежить від виду середовища розробки (IDE) яким ви користуєтесь та виду вашої операційної системи. В Eclipse, я вибираю Project->Properties->Java Build Path. Тоді я клацаю на кнопці Add External JARs і переходжу до директорії db/lib. Для інших IDEs, кроки є дещо відмінними.

Коли ви виконуєте код в роздруківці 16-1, то здається що нічого не стається. Програма запускається і зупиняється. Так і має бути. Код не має видимого виводу, тому що весь вивід направляється в базу даних. Тож, щоб побачити результат коду в роздруківці 16-1, ви повинні переглянути зміни в самій базі даних. Тож продовжуйте читати!

В попередньому абзаці, я написав що виконання роздруківки 16-1 є жахливо непримітним. Я написав що «здається нічого не сталося» і що «Код не має видимого виводу». Проте, якщо ви розглянете ближче, то зможете знайти деякі докази виконання коду роздруківки 16-1. Зокрема, ваш жорсткий диск матиме декілька додаткових файлів після виконання цього коду. Один з цих файлів, називатиметься derby.log. Він міститиме текст опису запуску і зупинки програмного забезпечення бази даних Derby.  Ви також можете знайти нову папку з назвою derbyDB, який міститиме log файли, файл service.properties та папку повну .dat файлів. (Ці .dat файли містять увесь матеріал збережений в базі даних.) Якщо ви використовуєте Eclipse, ви можете зробити дані файли і папки видимими вибравши відповідну гілку оглядача пакетів та далі вибравши File->Refresh.

Викорисовуємо SQL команди

В роздруківці16-1 суть коду лежить в трьох викликах executeUpdate. Кожен виклик executeUpdate містить рядок символів узятих в лапки. Щоб зробити код читабельним, я порізав кожен рядок на частини та розділив дані частини знаком плюс (оператором конкатенації). Плюс у Java виконує подвійну роботу. Для чисел, плюс виконує додавання. Для рядків, плюс – це знак для поєднання двох рядків докупи, утворюється один з’єднаний рядок (string).

Ви можете зробити рядок в подвійних лапках як завгодно довгим. Коли ви добираєтесь до правого краю екрану, перестаньте друкувати. Якщо ви хочете побачити цілий рядок без прокрутки, то вам необхідно розділити його на частини, як це зроблено в роздруківці 16-1. Просто розділіть частини знаком плюс.

Ви не можете розділити Java рядок просто натиснувши Enter або перемістивши на новий рядок. Коли ви розпочинаєте рядок з подвійної лапки("), то заключна подвійна лапка має бути на одному текстовому рядку коду.

Якщо ви знайомі з SQL (Structured Query Language – мова структурованих запитів), командний рядок у виклику executeUpdate зрозумілий для вас. Якщо ні, візьміть примірник SQL для Чайників, 7-ме видання, Алена Тейлора (SQL For Dummies, 7th Edition, by Allen G. Taylor.). Дані командні рядки не є частиною мови Java. В Java ці команди є лише рядками символів, які ви згодовуєте методу executeUpdate.  Дані рядки,  написані на SQL, створюють нову таблицю і додають ряд даних до таблиці. Коли ви пишете програму для бази даних на Java, ви просто пишете звичайні команди SQL і вставляєте їх у виклики методів Java.

В коді даного рядка строго дотримано технічні прийоми описані в JDBC версії 1.0. Пізніші версії класів JDBC підтримують інший спосіб запису даних (scrollable result sets), що надає вам метод insertRow, який заощаджує ваші зусилля, щодо повного написання командних рядків SQL.



З’єднання та роз’єднання

Окрім викликів методу executeUpdate, код в роздруківці 16-1 має ще деякий матеріал. Далі подається скорочений виклад  значення кожної частини коду:

  •   Class.forName: знаходить драйвер бази даних
Говорить до бази даних, що вам необхідний програмний посередник (драйвер бази даних). Драйвери можуть бути різних видів та розмірів, багато з них доволі дорогі. Роздруківка 16-1 застосовує невеликий, безкоштовний драйвер — Derby JDBC Embedded driver. Код для даного драйвера розміщується в Java класі EmbeddedDriver. Даний клас розміщений всередині пакету org.apache.derby.jdbc.

Щоб використати клас  EmbeddedDriver, ви викликаєте метод Class.forName. Хочете вірте, хочете ні проте Java API має клас під назвою Class. Клас Class містить інформацію про класи, що доступні в віртуальній машині Java (JVM). В лістингу 16-1, виклик Class.forName заглядає в клас org.apache.derby.jdbc.EmbeddedDriver. Після того як екземпляр EmbeddedDriver завантажений, ви можете приступити до з’єднання з базою даних.

  •   DriverManager.getConnection: Встановлює сесію з зазначеною базою даних
Метод getConnection проживає в Java класі з назвою DriverManager.  В роздруківці 16-1 виклик getConnection створює базу даних AccountDatabase та відкриває з’єднання з базою даних, Звичайно, що AccountDatabase можливо  вже існує перед стартом коду роздруківки 16-1. Якщо це так, то текст ;create=true у виклику getConnection не матиме ефекту.

В параметрі getConnection зверніть увагу на двокрапку. В коді не просто вказується назва бази даних AccountDatabase, крім того для класу DriverManager вказується, який протокол застосовувати для з’єднання з базою даних. Код jdbc:derby:  — теж саме, що http: у веб-адресах, тобто ми вказуємо комп’ютеру застосовувати jdbc протокол для спілкування з derby протоколом,  який передасть усе до вашої AccountDatabase.

  •     Connection.createStatement: створити інструкцію
Це виглядає дивно, проте в Java Database Connectivity, ви створюєте простий об’єкт statement. Після того як ви його створили, ви можете використовувати об’єкт багато раз з різноманітними SQL рядками, щоб посилати різні команди базі даних. Тож, перед  тим як викликати метод statement.executeUpdate, вам необхідно мати відповідний об’єкт statement.  Виклик connection.createStatement створює об’єкт statement для вас.

  •   try…catch…:  розпізнаємо винятки, що можуть виникнути в коді

Ви мабуть знаєте, що деякі виклики методів викидають контрольовані винятки. Контрольований виняток – це виняток який має бути розпізнаний у коді виклику. Тож, виклик Class.forName може викинути три типи винятків і згодом код в роздруківці 16-1 може викинути SQLException. Щоб розпізнати дані винятки, я додав інструкцію try-catch в мій код.

  •  try-with-resources:  вивільняє, які б не були, ресурси
Кожне з’єднання і кожна інструкція бази даних бере деякі системні ресурси. Коли ви закінчуєте використовувати ці ресурси, ви звільняєте їх.  Ви маєте явно вказати це викликом методу close, і ви мусите зробити це в інструкції try-catch.

Але там же є ще інструкція catch (зловити)! Коли щось іде неправильно, це не може просто так відбуватися. Це завжди трапляється через недбалість в коді. Тож, що трапиться, якщо catch викине власні винятки? І що станеться згодом, де ваш код пробує закрити усе з’єднання? Чи буде усе правильно закрито?

Щоб зробити усе заразом, Java 7 має нову інструкцію try-with-resources (try з ресурсами). Try-with-resources діє як давніша інструкція try. Проте в новій інструкції ви додаєте дужки після слова try. Всередині дужок, ви розміщуєте деякі інструкції, що створюють ресурси. (В роздруківці 16-1 інструкціями в дужками є виклики getConnection та createStatement.) Інструкції розділяються крапкою з комою.

Інструкція try-with-resources автоматично закриває і звільняє ваші ресурси вкінці виконання інструкцій. Більш того, try-with-resources, бере на себе усю брудну роботу щодо рішення усіх помилок при закритті таких ресурсів.

Видобування даних

Що доброго в базі даних, якщо ви не можете отримати дані з неї?  В даній секції, ви задаватимете запити до бази даних, що була створена в роздруківці 16-1. Код задання запитів показаний в роздруківці 16-2.

Роздруківка 16-2: Створення запитів

import static java.lang.System.out;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.text.NumberFormat;

class GetData {
public static void main(String args[]) {
    NumberFormat currency =
     NumberFormat.getCurrencyInstance();
    final String DRIVER =
        "org.apache.derby.jdbc.EmbeddedDriver";
    final String CONNECTION =
        "jdbc:derby:AccountDatabase";
    try {
        Class.forName(DRIVER).newInstance();
    } catch (InstantiationException e) {
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    }
    try (Connection connection =
        DriverManager.getConnection(CONNECTION);
        Statement statement =
          connection.createStatement();
        ResultSet resultset =
         statement.executeQuery(
          "select * from ACCOUNTS")) {

            while(resultset.next()) {
            out.print(resultset.getString("NAME"));
            out.print(", ");
            out.print(resultset.getString("ADDRESS"));
            out.print(" ");
            out.println(currency.format(
            resultset.getFloat("BALANCE")));
            }
    } catch (SQLException e) {
        e.printStackTrace();
    }
}
}


Щоб використовувати MySQL замість Java DB, зробіть наступні зміни в роздруківці 16-2: змініть значення DRIVER на “com.mysql.jdbc.Driver”. Змініть значення  CONNECTION на “jdbc:mysql://localhost/ AccountDatabase;create=true”. Зробіть подібні зміни для DB2, Oracle та інших баз даних.

Результат виконання коду з роздруківки 16-2 зображено на рисунку 16-1. В коді задаються запити в базу даних і згодом поступово кожен рядок з бази даних роздруковується.

Рисунок 16-1 – Отримання даних з бази даних
________________________________
Barry Burd, 222 Cyber Lane 24,02 грн.
Joe Dow, 111 Luddite Street 55,63 грн.
________________________________

Роздруківка 16-2 починається із звичного вже виклику forName, getConnection та createStatement. Далі в коді викликається executeQuery і подається виклик з SQL командою. Для тих хто знає SQL команди, той знає, що дана особлива команда бере всі дані з таблиці ACCOUNT (таблиці створеної в роздруківці 16-1).

Сутність, що повертається з виклику executeQuery має тип java.sql.ResultSet. (Це різниця між executeUpdate та executeQuery — excuteQuery повертає множину даних,  executeUpdate ні.) Дана множина-результат виглядає  так же як і таблиця бази даних. Подібно до оригінальної таблиці, множина розділена на ряди і стовпці. Кожен ряд містить дані одного облікового запису: ім’я, адрес і об’єм балансу.

Після того як ви викликали executeQuery і отримали вашу множину-результат, ви можете проходити покроково по рядах множини-результату.  Щоб зробити це потрібно створити цикл і перевіряти на початку кожної ітерації умову resultset.next(). Кожен раз коли викликається resultset.next() робиться дві речі:

  •     відбувається перехід до наступного ряду множини-результату (наступного облікового запису), звичайно, якщо він існує
  •   вам говориться чи існує даний ряд через повертається булевого значення – true або false

Якщо resultset.next() повертає true, множина-результат має інший ряд. Комп’ютер переходить до наступного ряду і в циклі поступово вибираються дані з даного ряду. Якщо ж resultsetset.next() повертає false, множина-результат не має більше рядів. Ви стрибаєте за межі циклу і починаєте усе закривати.

Тож, якщо ряд існує, то переходимо всередину циклу роздруківки 16-2.  Ви видобуваєте дані з ряду множини-результату викликаючи методи getString та getFloat. Раніше в роздруківці 16-1 ви створили таблицю ACCOUNTS із стовпцями NAME, ADDRESS та BALANCE.  Тут же в роздруківці 16-2 ви отримуєте дані з даних стовпців викликаючи методи зразка getДеякийТип і згодовуєте оригінальні назви стовпців даним методам. Після того як ви отримали дані, ви виводити їх на екран комп’ютера.

Кожен екземпляр ResultSet має декілька непоганих методів getДеякийТип. В залежності від того який тип даних поміщений в стовпчику, ви можете викликати методи: getArray, getBigDecimal, getBlob, getInt, getObject, getTimestamp і декілька інших.