410013796724260
• Webmoney
R335386147728
Z369087728698
Чтение и запись LOB объектовВопросам использования больших объектов LOB (Large Object Bynary) зачастую приходится уделять пристальное внимание. Связано это с хранением различных файлов в таблицах базы данных (БД). Сразу же возникает вопрос, зачем хранить в БД файл, если потом придется извлекать его назад опять же в виде в файла? Ведь можно создать отдельное файловое хранилище. Ответ также оказывается простым и тривиальным - для файлового хранилища требуется дополнительно решать вопросы репликации и синхронизации, предоставление прав доступа и создание backup'a. Таким образом, если нельзя обойтись без LOB-объектов, то остается выбрать их тип - бинарный (BLOB Binary Large Object ) или символьный, называемый в БД различных провайдеров по-разному (CLOB, TEXT). ПРИМЕЧАНИЕ : при работе с символьными данными необходимо учитывать кодировку. Это вопрос будет учтен при записи текстового файла в таблицы БД и выгрузки из БД. Описание примераВ статье рассматривается пример записи и чтения нескольких типов файлов в поля BLOB и CLOB (TEXT). В качестве серверов БД были использованы Oracle 10g Enterprise Edition, Oracle 10g Express Edition и MySQL 5.1. Пример представляет собой проект Eclipse, реализующий все принципы ООП (объектно-ориентированное программирование) - наследование, инкапсуляция и полиморфизм. Структура проекта представлена на следующем скриншоте. В директории «lib» размещаются библиотеки JDBC для подключения к серверам БД. При подключении к серверу 10g Enterprise Edition использовалась библиотека ojdbc7.jar. В случае использования сервера 10g Express Edition были использованы библиотеки ojdbc14_g.jar, ojdbc14.jar. Для подключения в IDE Eclipse библиотеки к проекту необходимо ее выделить и в контекстном меню выбрать "Build Path/Add to Build Path". Чтобы отключить использование библиотеки в проекте используйте вкладку "Java Build Path" в окне свойств проекта. Пример включает следующие программные модули :
Приложение в двух разнотипных БД создает таблицы с полями BLOB и CLOB (TEXT), в которые сначала файлы записываются, потом выполняется их чтение. Представленные процедуры записи и чтения больших объектов Вы можете использовать в своих приложениях практически без доработок. Создание подключений Connection к различным серверам БД описано здесь и в данной статье не рассматривает (код представлен). Пример можно скачать здесь. Описание базового модуля, DAOBase.javaБазовый модуль включает следующие основные методы :
Параметры процедур записи и чтения больших объектов LOB включают наименование таблиц table, наименование LOB-полей field, наименование поля первичного ключа pk, идентификатор записи id и путь к файлу fpath. При необходимости Вы можете доработать данные процедуры и включить в качестве параметра наименование схемы Schema(Oracle) или базы данных Database (MySQL). Не исключено, что и первичный ключ может содержать несколько полей. ПРИМЕЧАНИЕ : Ниже представлен листинг DAOBase.java, где методы работы с полями BLOB и CLOB не включают код. Это сделано преднамеренно для наглядного представления общей структуры модуля DAOBase.java. В противном случае листинг сильно увеличится и «за деревьями нельзя будет увидеть лес». Код методов работы с полями LOB описан далее. Листинг DAOBase.javaimport java.io.IOException; import java.sql.Connection; import java.sql.SQLException; import java.sql.Statement; public class DAOBase { protected Connection connection = null; protected final int BUFFER_length = 2 * 1024; private final String TABLE_DROP = "DROP TABLE %s"; //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ public void createConnection(){} //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ public Connection getConnection() { return connection; } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ public void closeConnection() { if(connection != null){ try { connection.close(); } catch(SQLException e){ System.out.println(e.getMessage()); } } } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ /** * Функция выполнения SQL-запроса * @param sql текст запроса * @return результат выполнения запроса */ public boolean execSQL (final String sql) { boolean result = false; try { Statement statement = null if (connection != null) { statement = connection.createStatement(); statement.execute(sql); statement.close(); statement = null; result = true; } } catch (SQLException e) { System.err.println ("SQLException : code = " + String.valueOf(e.getErrorCode()) + " - " + e.getMessage()); System.err.println ("\tSQL : " + sql); } return result; } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ public boolean writeBlob(final String table, final String field, final String pk, final int id, final String fpath) { boolean result = false; // ... см. код ниже return result; } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ public long readBlobToFile(final String table, final String field, final String pk, final int id, final String path) throws IOException, SQLException { long size = 0; // ... см. код ниже return size; } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ public boolean writeClob(final int id, final String fpath) { boolean result = false; // ... см. код ниже return result; } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ public long readClobToFile(final String table, final String field, final int id, final String path) throws IOException, SQLException { long size = 0; // ... см. код ниже return size; } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ public String readClobData(final String table, final String field, final int id) { StringBuffer buffer = new StringBuffer(); // ... см. код ниже return buffer.toString(); } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ public boolean createTable(final String sql) { return execSQL (sql); } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ public boolean dropTable(final String table) { return execSQL (String.format(TABLE_DROP, table)); } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ } Процедуры создания createTable (sql) и удаления dropTable(sql) таблицы БД не включены в общий список методов DAOBase.java, поскольку в примере выполняют вспомогательную функцию и используется для «подыгрыша». Как правило, структура БД меняется редко и нет необходимости включать в приложение процедуры, которые не используются. Листинг методов чтения и записи данных в поле BLOBДве вспомогательные функции readBlobField и writeFromBlob2Stream имеют модификаторы private и используются основным методом readBlobToFile при выгрузке файла из БД. Для записи файла в поле BLOB используется метод setBinaryStream, получающий в качестве параметра поток FileInputStream. При выгрузке файла из БД в методе readBlobToFile сначала читается объект BLOB (readBlobField), после этого он записывается в выходной поток OutputStream в методе writeFromBlob2Stream, где используется метод чтения в бинарный поток getBinaryStream() объекта BLOB. private String INSERT_blob = "insert into %s(%s) values(%d)" ; private String UPDATE_blob = "update %s set %s = ? where %s = ?"; private String SELECT_blob = "select %s from %s where %s = %d" ; public boolean writeBlob(final String table, final String field, final String pk, final int id, final String fpath) { String sql = String.format(INSERT_blob, table, pk, id); boolean result = execSQL (sql); if (result) { PreparedStatement ps = null; File file = new File(fpath); try { sql = String.format(UPDATE_blob, table, field, pk); FileInputStream is = new FileInputStream(file); ps = connection.prepareStatement(sql); ps.setBinaryStream(1, is, (int)file.length()); ps.setInt(2, id); ps.executeUpdate(); connection.commit(); ps.close(); } catch (FileNotFoundException e) { result = false; e.printStackTrace(); } catch (SQLException e) { result = false; e.printStackTrace(); } } return result; } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ private Blob readBlobField(final String table, final String field, final String pk, final int id) throws SQLException { String sql = String.format(SELECT_blob, field, table, pk, id); Statement stmt = connection.createStatement(); ResultSet rs = stmt.executeQuery(sql); Blob blob = null; if (rs.next()) blob = rs.getBlob(1); return blob; } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ private long writeFromBlob2Stream(Blob blob, OutputStream out) throws SQLException, IOException { InputStream is = blob.getBinaryStream(); int length = -1; long size = 0; byte[] buf = new byte[BUFFER_length]; while ((length = is.read(buf)) != -1) { out.write(buf, 0, length); size += length; } is.close(); return size; } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ public long readBlobToFile(final String table, final String field, final String pk, final int id, final String fpath) throws IOException, SQLException { long size = 0; OutputStream fwriter = new FileOutputStream(fpath); Blob blob = readBlobField(table, field, pk, id); size = writeFromBlob2Stream(blob, fwriter); fwriter.close(); return size; } Листинг процедур чтения и записи данных в поле CLOBДве вспомогательные функции readClobField и readFromClob2Stream имеют модификаторы private и используются основными методами readClobToFile, readClobData при выгрузке файла из БД. Чтобы учесть кодировку символов при записи текстового файла в БД используется InputStreamReader и BufferedReader. Для записи файла в поле CLOB/TEXT используется метод setCharacterStream объекта CLOB, которому передается BufferedReader. При выгрузке файла из БД в методе readClobToFile сначала читается объект CLOB (readClobField ), после этого он записывается в выходной поток BufferedWriter в методе readFromClob2Stream, где используется метод чтения в символьный поток getCharacterStream() объекта CLOB. protected String INSERT_clob = "insert into %s(%s) values(%d)" ; protected String UPDATE_clob = "update %s set %s = ? where %s = ?"; private String SELECT_clob = "select %s from %s where id = %d" ; public boolean writeClob(final String table, final String field, final String pk, final int id, final String fpath) { String sql = String.format(INSERT_clob, table, pk, id); boolean result = execSQL (sql); if (result) { PreparedStatement ps = null; File file = new File(fpath); try { FileInputStream fis=new FileInputStream (file); InputStreamReader isr=new InputStreamReader(fis,"UTF-8"); BufferedReader br =new BufferedReader (isr); sql = String.format(UPDATE_clob, table, field, pk); ps = connection.prepareStatement(sql); ps.setCharacterStream(1, br, (int)file.length()); ps.setInt(2, id); ps.executeUpdate(); connection.commit(); ps.close(); } catch (UnsupportedEncodingException e) { result = false; e.printStackTrace(); } catch (FileNotFoundException e) { result = false; e.printStackTrace(); } catch (SQLException e) { result = false; e.printStackTrace(); } } return result; } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ public long readClobToFile(final String table, final String field, final int id, final String fpath) throws IOException, SQLException { long size = 0; BufferedWriter fwriter=new BufferedWriter(new FileWriter(fpath)); Clob clob = readClobField (table, field, id); size = readFromClob2Stream(clob, fwriter); fwriter.close(); return size; } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ private Clob readClobField(final String table, final String field, final int id) throws SQLException { String sql = String.format(SELECT_clob, field, table, id); Statement stmt = connection.createStatement(); ResultSet rs = stmt.executeQuery(sql); Clob clob = null; if (rs.next()) clob = rs.getClob(1); return clob; } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ private long readFromClob2Stream(Clob clob, Writer out) throws SQLException, IOException { BufferedReader breader; breader; = new BufferedReader(clob.getCharacterStream()); int length = -1; long size = 0; char[] buf = new char[BUFFER_length]; while ((length = breader.read(buf, 0, BUFFER_length)) != -1) { out.write(buf, 0, length); size += length; } breader.close(); return size; } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ public String readClobData(final String table, final String field, final int id) { StringBuffer buffer = new StringBuffer(); try { Clob clob = readClobField (table, field, id); BufferedReader reader; reader = new BufferedReader(clob.getCharacterStream()); char[] buf = new char[BUFFER_length]; int length = -1; try { while ((length=reader.read(buf,0,BUFFER_length)) != -1){ if (length == BUFFER_length) buffer.append(String.valueOf(buf)); else { String tmp = String.valueOf(buf) .substring(0, length); buffer.append(tmp); } } reader.close(); } catch (IOException e) { e.printStackTrace(); } } catch (SQLException e) { e.printStackTrace(); } return buffer.toString(); } Листинг OracleDAO.javaЛистинг модуля OracleDAO.java включает переопределенные метод createConnection() и метод создания процедур БД createTable. Структуры таблиц, описанные в константах TABLE_blobs и TABLE_files в виде SQL-скриптов, включают только поля идентификаторов id и поля data соответствующих типов LOB. По умолчанию поля дата имеют значения NULL, поскольку используются в методах при обновлении. При создании объекта в конструкторе сразу же создается подключение к серверу БД. В качестве свойства объекту подключения передается кодировка устанавливаемого соединения "utf8". private final String DRIVER_oracle = "oracle.jdbc.OracleDriver"; private final String URL_oracle = "jdbc:oracle:thin:@%s:%d:%s"; private final String URL_host = "localhost"; private final int PORT_oracle = 1521; private final String SCHEMA_oracle = "..."; private final String login = "..."; private final String password = "..."; private final String TABLE_blobs = "CREATE TABLE blobs( " + "id INT PRIMARY KEY, " + "data BLOB NULL)" ; private final String TABLE_files = "CREATE TABLE files( " + "id INT PRIMARY KEY, " + "data CLOB NULL)" ; //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ public OracleDAO() { createConnection(); } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @Override public void createConnection() { try { // Регистрация драйвера Class.forName(DRIVER_oracle).newInstance(); // Определение свойств подключения Connection Properties properties = new Properties(); properties.setProperty("password" , password); properties.setProperty("user" , login ); properties.setProperty("useUnicode" , "true" ); properties.setProperty("characterEncoding", "utf8" ); String url = String.format(URL_oracle, URL_host, PORT_oracle, SCHEMA_oracle); connection_ora = (OracleConnection) DriverManager.getConnection(url, properties); connection_ora.setAutoCommit(false); } catch (InstantiationException e) { } catch (IllegalAccessException e) { } catch (ClassNotFoundException e) { } catch (SQLException e) { connection_ora = null; } }; //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @Override public boolean createTable(final String table) { if (table.equalsIgnoreCase("blobs")) return execSQL (TABLE_blobs); else if (table.equalsIgnoreCase("files")) return execSQL (TABLE_files); else return false; } Листинг MySQLDAO.javaЛистинг модуля MySQLDAO.java включает переопределенные метод createConnection(), метод создания процедур БД createTable и метод writeClob, который демонстрирует другой подход записи символьного файла в поле TEXT. В данном методе файловый поток FileInputStream сразу же перенаправляется в поток setAsciiStream. Но чтобы не исказить символы использовалась соответствующая кодировка в таблице БД и подключении к серверу. Родительский метод writeClob класса DAOBase.java также сработал без нареканий. Таким образом, в Вашем распоряжении 2 подхода записи текстового файла в БД MySQL. Структуры таблиц, описанные в константах TABLE_blobs и TABLE_files в виде SQL-скриптов, включают только поля идентификаторов id и поля data соответствующих типов LOB. Для таблицы "files" установлена кодировка "UTF8". По умолчанию поля дата имеют значения NULL, т.к. используются в методах при обновлении. При создании объекта в конструкторе сразу же создается подключение к серверу БД, которому в качестве свойства передается кодировка устанавливаемого соединения "utf8". private String DRIVER_MySQL = "com.mysql.jdbc.Driver"; private String URL_mysql = "jdbc:mysql://%s:%d/%s"; private String URL_host = "localhost" ; private int PORT_mysql = 3306 ; private String DATABASE = "test" ; private String login = "..." ; private String password = "..." ; private String TABLE_blobs = "CREATE TABLE blobs( " + "id INT PRIMARY KEY, " + "data BLOB NULL)"; private String TABLE_files = "CREATE TABLE files( " + "id INT PRIMARY KEY, " + "data TEXT NULL) " + "ENGINE = INNODB " + "CHARACTER SET utf8 " + "COLLATE utf8_general_ci;"; //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ public MySQLDAO() { createConnection(); } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @Override public void createConnection() { try { // Регистрация драйвера Class.forName(DRIVER_MySQL).newInstance(); // Определение свойств подключения Connection Properties properties = new Properties(); properties.setProperty("password" , password); properties.setProperty("user" , login ); properties.setProperty("useUnicode" , "true" ); properties.setProperty("characterEncoding", "utf8" ); String url = String.format(URL_mysql, URL_host, PORT_mysql, DATABASE); connection = DriverManager.getConnection(url, properties); connection.setAutoCommit(false); } catch (InstantiationException e) { } catch (IllegalAccessException e) { } catch (ClassNotFoundException e) { } catch (SQLException e) { connection = null; } }; //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @Override public boolean createTable(final String table) { if (table.equalsIgnoreCase("blobs")) return execSQL (TABLE_blobs); else if (table.equalsIgnoreCase("files")) return execSQL (TABLE_files); else return false; } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @Override public boolean writeClob(final String table, final String field, final String pk, final int id, final String fpath) { String sql = String.format(INSERT_clob_rec, table, pk, id); boolean result = execSQL (sql); if (result) { PreparedStatement ps = null; File file = new File(fpath); try { FileInputStream fis = new FileInputStream (file); sql = String.format(UPDATE_clob_rec, table, field, pk); ps = connection.prepareStatement(sql); ps.setAsciiStream (1, fis, (int) file.length()); ps.setInt (2, id); ps.executeUpdate(); connection.commit(); ps.close(); } catch (FileNotFoundException e) { result = false; e.printStackTrace(); } catch (SQLException e) { result = false; e.printStackTrace(); } } return result; } Тестирование примераГлавный модуль приложения MainTest.java выполняет тестирование методов записи файлов в БД и выгрузки в файл. Основной метод тестирования testDAO в качестве параметра принимает базовый класс (полиморфизм). В методе testDAO сначала проверяется установка подключения к серверу БД. Если подключение установлено, то создаются таблицы, в которые записываются и извлекаются файлы. private boolean TEST_mysql = false; private boolean TEST_oracle = true; private final String TEXT_tbl_create = "\nTable <%s> created"; private final String TEXT_tbl_insert = "%s inserted into table"; private final String TEXT_tbl_upload = "%s (size %d byte) uploaded"; private final String TEXT_tbl_drop = "Table <%s> dropped"; //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ public MainTest() { if (TEST_oracle) testOracle(); if (TEST_mysql) testMySQL(); } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ private void testDAO(DAOBase dao, final String caption) { String text; if (dao.getConnection() != null) { System.out.println(caption); System.out.println("Connection to server"); if (dao.createTable("blobs")) { text = String.format(TEXT_tbl_create, "blobs"); System.out.println(text); if (dao.writeBlob("blobs", "data", "id", 1, "aircraft.jpg")) { text = String.format(TEXT_tbl_insert, "Image"); System.out.println(text); try { long size = dao.readBlobToFile("blobs", "data", "id", 1, "aircraft1.jpg"); text = String.format(TEXT_tbl_upload, "Image", size); System.out.println(text); } catch (IOException e) { } catch (SQLException e) {} } if (dao.writeBlob("blobs", "data", "id", 2, "отчет.xlsx")) { text = String.format(TEXT_tbl_insert, "XLSX"); System.out.println(text); try { long size = dao.readBlobToFile("blobs", "data", "id", 2, "отчет1.xlsx"); text = String.format(TEXT_tbl_upload, "XLSX", size); System.out.println(text); } catch (IOException e) { } catch (SQLException e) {} } if (dao.dropTable("blobs")) { text = String.format(TEXT_tbl_drop, "blobs"); System.out.println(text); } } if (dao.createTable("files")) { text = String.format(TEXT_tbl_create, "files"); System.out.println(text); if (dao.writeClob("files", "data", "id", 1, "текст.txt")) { text = String.format(TEXT_tbl_insert, "File"); System.out.println(text); try { // Запись значения поля CLOB в файл long size = dao.readClobToFile("files", "data", 1, "текст1.txt"); text = String.format(TEXT_tbl_upload, "File", size); System.out.println(text); } catch (IOException e) { } catch (SQLException e) {} } if (dao.dropTable("files")) { text = String.format(TEXT_tbl_drop, "files"); System.out.println(text); } } dao.closeConnection(); } else System.out.println("Connection is NULL"); } private void testMySQL() { DAOBase dao = new MySQLDAO(); testDAO(dao, "----- TEST MySQL -----\n"); } private void testOracle() { DAOBase dao = new OracleDAO(); testDAO(dao, "----- TEST Oracle -----\n"); } public static void main(String[] args) { new MainTest(); System.exit(0); } Результаты тестированияПриложение выводит сообщения и результатах тестирования методов записи и чтения LOB-объектов в консоль. ----- TEST Oracle ----- Connection to server Table <blobs> created Image inserted into table Image (size 3573 byte) uploaded XLSX inserted into table XLSX (size 9929 byte) uploaded Table <blobs> dropped Table <files> created File inserted into table File (size 2191 byte) uploaded Table <files> dropped Скачать примерИсходный код рассмотренного примера записи и чтения больших объектов LOB с использованием JDBC можно скачать здесь (7.27 Мб). При тестировании примера необходимо определить параметры подключения к Вашему серверу БД - схема (база данных), логин и пароль. |