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

в 11:47, , рубрики: database, grails, java, Веб-разработка, велосипедостроение, метки: , , ,

Два простых примера создания файлового хранилища в СУБДПрактически в каждом веб-проекте требуется собственное хранилище файлов. Назначений у него множество. Сегодня мы рассмотрим 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<DataObject> listObjects();
    //Получить объект по “фамилии”, видимому узеру имени.
    public DataObject getObjectBySurname(String surname);
    //Поиск объектов в базе по имени.
    public List<DataObject> 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<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 + "%");
	}

}

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

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<DataObject> listObjects();

    public DataObject getObjectBySurname(String surname);
    
    public List<DataObject> 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<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);
    }

}

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

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<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 не работает адекватно. Поэтому разобьем наш файл на кластеры и сохраним по-отдельности.

В принципе тут всё делается практически так же, как и в предыдущем примере. Приложение доступно здесь.

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

Автор: boiler5

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js