Практически в каждом веб-проекте требуется собственное хранилище файлов. Назначений у него множество. Сегодня мы рассмотрим 2 простых варианта его создания: первый — с использованием типа данных blob средствами Java, Spring MVC, Hibernate, MySQL и второй — с кластеризацией (разбиением файла на кусочки) средствами groovy, grails, hibernate, PostgreSQL.
Зачем нужен этот велосипед? Зачастую нужно отдавать пользователю сформированные на стороне сервера файлы и предусмотреть возможность самому выкладывать туда что-нибудь. К тому же, мы работаем с СУБД, к которой можно подключиться по JDBC с других хостов, и если сделать реплицируемую базу с несколькими нодами, то получится хорошая балансировка нагрузки на скачивание.
Вариант 1 — Использование типа данных blob
Итак, у нас есть Spring MVC. Создаём бин с persistence слоя, в котором заложен функционал “объекта”, хранимого в базе
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 — количество скачиваний;
Теперь опишем интерфейс дао слоя для нашего объекта.
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<DataObject> listObjects(); //Получить объект по “фамилии”, видимому узеру имени. public DataObject getObjectBySurname(String surname); //Поиск объектов в базе по имени. public List<DataObject> searchObjectsByName(String query); }
Делаем реализацию интерфейса:
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<DataObject> listObjects() { return attachHibernateTemplate.find("from DataObject"); } @Override public DataObject getObjectByID(Integer id) { List<DataObject> 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<DataObject> doList = attachHibernateTemplate.find( "from DataObject where surname = ?", surname); if (doList == null || doList.size() <= 0) return null; return doList.get(0); } @Override public List<DataObject> 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 + "%"); } }
В сервисе делегируем все методы из дао
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<DataObject> listObjects(); public DataObject getObjectBySurname(String surname); public List<DataObject> searchObjectsByName(String query); }
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<DataObject> 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<DataObject> searchObjectsByName(String query) { return attachmentDAO.searchObjectsByName(query); } }
Всё, что нужно у нас есть. Теперь осталось только сделать контроллер:
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<String, Object> map) { List<DataObject> doList = attachmentService.listObjects(); //список файлов map.put("dlist", doList);// заталкать его в кодель фтраницы return "admin/attach/attach";// вернуть путь к вьюхе } //Добавление файла @RequestMapping("/admin/dataObjects/private/add") public String add( HashMap<String, Object> 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 не работает адекватно. Поэтому разобьем наш файл на кластеры и сохраним по-отдельности.
В принципе тут всё делается практически так же, как и в предыдущем примере. Приложение доступно здесь.
Спасибо всем за внимание! Надеюсь, что мои примеры будут вам полезны.
ссылка на оригинал статьи http://habrahabr.ru/post/225085/
Добавить комментарий