[Из песочницы] Два простых примера создания файлового хранилища в СУБД
Практически в каждом веб-проекте требуется собственное хранилище файлов. Назначений у него множество. Сегодня мы рассмотрим 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
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
@Override
public DataObject getObjectByID (Integer id) {
List
@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
@Override
public List
} В сервисе делегируем все методы из дао
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
public DataObject getObjectBySurname (String surname);
public List
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
@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
} Всё, что нужно у нас есть. Теперь осталось только сделать контроллер:
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
//Добавление файла
@RequestMapping (»/admin/dataObjects/private/add»)
public String add (
HashMap
//Глушим файлы по айдишнику @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 не работает адекватно. Поэтому разобьем наш файл на кластеры и сохраним по-отдельности.В принципе тут всё делается практически так же, как и в предыдущем примере. Приложение доступно здесь.
Спасибо всем за внимание! Надеюсь, что мои примеры будут вам полезны.