[Из песочницы] Два простых примера создания файлового хранилища в СУБД

6658249db48eff0fb7a8c5d443ac8ba3.jpgПрактически в каждом веб-проекте требуется собственное хранилище файлов. Назначений у него множество. Сегодня мы рассмотрим 2 простых варианта его создания: первый — с использованием типа данных blob средствами Java, Spring MVC, Hibernate, MySQL и второй — с кластеризацией (разбиением файла на кусочки) средствами groovy, grails, hibernate, PostgreSQL.Зачем нужен этот велосипед? Зачастую нужно отдавать пользователю сформированные на стороне сервера файлы и предусмотреть возможность самому выкладывать туда что-нибудь. К тому же, мы работаем с СУБД, к которой можно подключиться по JDBC с других хостов, и если сделать реплицируемую базу с несколькими нодами, то получится хорошая балансировка нагрузки на скачивание.

Вариант 1 — Использование типа данных blobИтак, у нас есть Spring MVC. Создаём бин с persistence слоя, в котором заложен функционал «объекта», хранимого в базеDataObject.java package ru.cpro.uchteno.domain.attach;

import java.sql.Blob;

import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.Lob; import javax.persistence.Table;

import org.hibernate.annotations.Type;

@Entity @Table (name = «data_object») public class DataObject { @Id @GeneratedValue (strategy=GenerationType.IDENTITY) @Column (name=«id») private Integer id; @Column (name=«data_name») private String name; @Column (name=«data_data», columnDefinition=«longblob», length=2×1024*1024×1024) @Lob () private Blob data; @Column (name=«contype») private String contentType; @Column (name=«surname») private String surname; @Column (name=«access_count») private Integer accessCount;

public Integer getId () { return id; }

public void setId (Integer id) { this.id = id; }

public String getName () { return name; }

public void setName (String name) { this.name = name; }

public Blob getData () { return data; }

public void setData (Blob data) { this.data = data; }

public String getContentType () { return contentType; }

public void setContentType (String contentType) { this.contentType = contentType; }

public String getSurname () { return surname; }

public void setSurname (String surname) { this.surname = surname; }

public Integer getAccessCount () { return accessCount; }

public void setAccessCount (Integer accessCount) { this.accessCount = accessCount; } } private Integer id — идентификатор, первичный ключ нашего объекта; private String name — имя загруженного объекта; private Blob data — данные объекта; private String contentType — тип данных объекта; private String surname — видимое пользователем имя объекта; private Integer accessCount — количество скачиваний;

Теперь опишем интерфейс дао слоя для нашего объекта.

AttachmentDAO.java package ru.cpro.uchteno.dao.attach;

import java.util.List;

import ru.cpro.uchteno.domain.attach.DataObject;

public interface AttachmentDAO { //Получить объект по айдишнику. public DataObject getObjectByID (Integer id); //Сохранить объект. public void saveOrUpdate (DataObject dao); //удалить объект. public void deleteDataObject (DataObject dao); //Получить список всех объектов. public List listObjects (); //Получить объект по «фамилии», видимому узеру имени. public DataObject getObjectBySurname (String surname); //Поиск объектов в базе по имени. public List searchObjectsByName (String query); } Делаем реализацию интерфейса:

AttachmentDAOImpl.java package ru.cpro.uchteno.dao.attach;

import java.util.ArrayList; import java.util.List;

import org.hibernate.SQLQuery; import org.hibernate.Session; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.orm.hibernate3.HibernateTemplate; import org.springframework.stereotype.Repository;

import ru.cpro.uchteno.domain.attach.DataObject;

@Repository public class AttachmentDAOImpl implements AttachmentDAO { //Внедряем hibernateTemplate @Autowired private HibernateTemplate attachHibernateTemplate;

@Override public List listObjects () { return attachHibernateTemplate.find («from DataObject»); }

@Override public DataObject getObjectByID (Integer id) { List doList = attachHibernateTemplate.find ( «from DataObject where id = ?», id); if (doList == null || doList.size () <= 0) return null; return doList.get(0); }

@Override public void saveOrUpdate (DataObject dao) { attachHibernateTemplate.saveOrUpdate (dao); }

@Override public void deleteDataObject (DataObject dao) { attachHibernateTemplate.delete (dao); }

@Override public DataObject getObjectBySurname (String surname) { List doList = attachHibernateTemplate.find ( «from DataObject where surname = ?», surname); if (doList == null || doList.size () <= 0) return null; return doList.get(0); }

@Override public List searchObjectsByName (String query) { if (query == null || query.trim ().equals (»)) return attachHibernateTemplate .find («from DataObject order by id desc limit 20»); return attachHibernateTemplate.find ( «from DataObject where name like?»,»%» + query + »%»); }

} В сервисе делегируем все методы из дао

AttachmentService.java package ru.cpro.uchteno.service.attach;

import java.util.List;

import ru.cpro.uchteno.domain.attach.DataObject;

public interface AttachmentService {

public DataObject getObjectByID (Integer id); public void saveOrUpdate (DataObject dao); public void deleteDataObject (DataObject dao);

public List listObjects ();

public DataObject getObjectBySurname (String surname); public List searchObjectsByName (String query); } AttachmentServiceImpl.java package ru.cpro.uchteno.service.attach;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service;

import ru.cpro.uchteno.dao.attach.AttachmentDAO; import ru.cpro.uchteno.domain.attach.DataObject;

@Service public class AttachmentServiceImpl implements AttachmentService { @Autowired private AttachmentDAO attachmentDAO; @Override public List listObjects () { return attachmentDAO.listObjects (); }

@Override public DataObject getObjectByID (Integer id) { return attachmentDAO.getObjectByID (id); }

@Override public void saveOrUpdate (DataObject dao) { attachmentDAO.saveOrUpdate (dao); }

@Override public void deleteDataObject (DataObject dao) { attachmentDAO.deleteDataObject (dao); } @Override public DataObject getObjectBySurname (String surname) { return attachmentDAO.getObjectBySurname (surname); }

@Override public List searchObjectsByName (String query) { return attachmentDAO.searchObjectsByName (query); }

} Всё, что нужно у нас есть. Теперь осталось только сделать контроллер:

AttachmentController.java package ru.cpro.uchteno.web.attachment;

import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.sql.Blob; import java.sql.SQLException; import java.util.HashMap; import java.util.List;

import javax.servlet.http.HttpServletResponse;

import org.hibernate.Hibernate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.multipart.MultipartFile;

import ru.cpro.uchteno.domain.attach.DataObject; import ru.cpro.uchteno.service.attach.AttachmentService;

@Controller () @RequestMapping (»/») public class AttachmentController { //Подключаем наш сервис @Autowired private AttachmentService attachmentService; //Вывод на страницу всех файлов в базе @RequestMapping (»/admin/dataObjects/private») public String index (HashMap map) { List doList = attachmentService.listObjects (); //список файлов map.put («dlist», doList);// заталкать его в кодель фтраницы return «admin/attach/attach»;// вернуть путь к вьюхе }

//Добавление файла @RequestMapping (»/admin/dataObjects/private/add») public String add ( HashMap map, @RequestParam (value = «addName», required = true) String name, @RequestParam (value = «addFile», required = true) MultipartFile file, @RequestParam (value = «addSurname», required = true) String surname) { try { DataObject dob = new DataObject ();//Создать новый файл if (name == null || name.trim ().equals (»)) //Если имя файла пустое dob.setName (file.getOriginalFilename ()); // Взять имя из загружаемого файла else dob.setName (name); // иначе дать файлу новое имя Blob blob = Hibernate.createBlob (file.getInputStream ()); //Создаем блоб по входному потоку dob.setData (blob); dob.setContentType (file.getContentType ()); // Контент тайп dob.setSurname (surname); // задаем файлу «фамилию» attachmentService.saveOrUpdate (dob); // сохраняем файл } catch (IOException e) { System.out.println («Не удалось записать объект в базу»); return «redirect:/admin/attach/dataObjects/private/»; } return «redirect:/admin/dataObjects/private/»; }

//Глушим файлы по айдишнику @RequestMapping (»/admin/dataObjects/private/del») public @ResponseBody String del (@RequestParam (value = «id», required = true) Integer id) { try { DataObject dao = attachmentService.getObjectByID (id); attachmentService.deleteDataObject (dao); } catch (Exception ex) { return «FAIL»; } return «SUCCESS»; }

//Скачивание файлов по фамилии. Публичная часть. @RequestMapping (»/public/dataObjects/getObject») public void getObject ( @RequestParam (value = «s», required = false) String surname, HttpServletResponse resp) { if (surname == null || surname.trim ().equals (»)) { try { resp.getOutputStream ().close (); return; } catch (IOException e) { e.printStackTrace (); } } DataObject dao = attachmentService.getObjectBySurname (surname); // Ищем файл по фамилии if (dao == null) { try { resp.getOutputStream ().close (); } catch (Exception ex) { } return; } // наращиваем счетчик скачиваний if (dao.getAccessCount () == null) dao.setAccessCount (1); else dao.setAccessCount (dao.getAccessCount () + 1); attachmentService.saveOrUpdate (dao);

//Отдаем юзеру файл Blob blob = dao.getData (); try { InputStream is = blob.getBinaryStream (); OutputStream os = resp.getOutputStream (); resp.setContentType (dao.getContentType ()); int n = 0; byte buff[] = new byte[1024]; while (n >= 0) { n = is.read (buff); if (n > 0) os.write (buff, 0, n); } is.close (); os.close (); } catch (Exception e) { e.printStackTrace (); } }

} Вариант 2 — Файловое хранилище с кластеризацией Я решил использовать grails. Но когда пишем на groovy, Blob не работает адекватно. Поэтому разобьем наш файл на кластеры и сохраним по-отдельности.В принципе тут всё делается практически так же, как и в предыдущем примере. Приложение доступно здесь.

Спасибо всем за внимание! Надеюсь, что мои примеры будут вам полезны.

© Habrahabr.ru