← Назад к вопросам

Можно ли сохранить PDF в PostgreSQL?

1.2 Junior🔥 141 комментариев
#Базы данных и SQL

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Ответ на вопрос о хранении PDF в PostgreSQL

Да, можно, но нужно выбрать правильный подход

PostgreSQL полностью поддерживает хранение бинарных данных, включая PDF-файлы. Однако решение о том, где хранить файлы (в БД или в файловой системе/облаке), зависит от конкретной задачи и требований приложения.

Способы хранения PDF в PostgreSQL

1. Тип данных BYTEA

BYTEA — это встроенный тип PostgreSQL для хранения бинарных данных (Binary Large Object):

import java.io.FileInputStream;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;

public class PDFStorage {
    public void savePDFToDatabase(Connection conn, String filePath, int documentId) throws Exception {
        byte[] pdfContent = readFileAsBytes(filePath);
        
        String sql = "INSERT INTO documents (id, pdf_content) VALUES (?, ?)";
        try (PreparedStatement pstmt = conn.prepareStatement(sql)) {
            pstmt.setInt(1, documentId);
            pstmt.setBytes(2, pdfContent);
            pstmt.executeUpdate();
        }
    }
    
    public byte[] retrievePDFFromDatabase(Connection conn, int documentId) throws Exception {
        String sql = "SELECT pdf_content FROM documents WHERE id = ?";
        try (PreparedStatement pstmt = conn.prepareStatement(sql)) {
            pstmt.setInt(1, documentId);
            try (ResultSet rs = pstmt.executeQuery()) {
                if (rs.next()) {
                    return rs.getBytes("pdf_content");
                }
            }
        }
        return null;
    }
    
    private byte[] readFileAsBytes(String filePath) throws Exception {
        byte[] fileContent = new byte[1024 * 1024]; // 1MB буфер
        try (FileInputStream fis = new FileInputStream(filePath)) {
            return fis.readAllBytes();
        }
    }
}

2. Использование Large Objects (LO)

Для очень больших файлов используйте встроенную систему Large Objects:

import org.postgresql.largeobject.LargeObject;
import org.postgresql.largeobject.LargeObjectManager;

public class PDFLargeObject {
    public long saveLargeObjectPDF(Connection conn, String filePath) throws Exception {
        LargeObjectManager lom = ((org.postgresql.PGConnection) conn).getLargeObjectAPI();
        long oid = lom.createLO(LargeObjectManager.WRITE);
        
        LargeObject lo = lom.open(oid, LargeObjectManager.WRITE);
        byte[] buffer = new byte[8192];
        try (FileInputStream fis = new FileInputStream(filePath)) {
            int bytesRead;
            while ((bytesRead = fis.read(buffer)) != -1) {
                lo.write(buffer, 0, bytesRead);
            }
        }
        lo.close();
        return oid;
    }
    
    public void retrieveLargeObjectPDF(Connection conn, long oid, String outputPath) throws Exception {
        LargeObjectManager lom = ((org.postgresql.PGConnection) conn).getLargeObjectAPI();
        LargeObject lo = lom.open(oid, LargeObjectManager.READ);
        
        try (FileOutputStream fos = new FileOutputStream(outputPath)) {
            byte[] buffer = new byte[8192];
            int bytesRead;
            while ((bytesRead = lo.read(buffer, 0, 8192)) > 0) {
                fos.write(buffer, 0, bytesRead);
            }
        }
        lo.close();
    }
}

3. С использованием ORM (Hibernate/JPA)

import javax.persistence.*;

@Entity
@Table(name = "documents")
public class Document {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;
    
    private String fileName;
    
    @Lob
    @Column(columnDefinition = "BYTEA")
    private byte[] pdfContent;
    
    private LocalDateTime uploadedAt;
    
    // Getters and Setters
    public Document() {}
    
    public Document(String fileName, byte[] pdfContent) {
        this.fileName = fileName;
        this.pdfContent = pdfContent;
        this.uploadedAt = LocalDateTime.now();
    }
}

// Repository
@Repository
public class DocumentRepository extends JpaRepository<Document, Integer> {
    Document findByFileName(String fileName);
}

// Service
@Service
public class DocumentService {
    @Autowired
    private DocumentRepository documentRepository;
    
    public void savePDF(String fileName, byte[] pdfContent) {
        Document doc = new Document(fileName, pdfContent);
        documentRepository.save(doc);
    }
    
    public byte[] getPDF(int documentId) {
        return documentRepository.findById(documentId)
            .map(Document::getPdfContent)
            .orElse(null);
    }
}

Структура таблицы в PostgreSQL

CREATE TABLE documents (
    id SERIAL PRIMARY KEY,
    file_name VARCHAR(255) NOT NULL,
    pdf_content BYTEA NOT NULL,
    file_size INTEGER,
    mime_type VARCHAR(50),
    uploaded_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- Индекс для быстрого поиска по имени
CREATE INDEX idx_documents_file_name ON documents(file_name);

Когда хранить PDF в PostgreSQL

Хорошо хранить в БД:

  • Маленькие файлы (< 10 MB)
  • Файлы, которые часто меняются и требуют версионирования
  • Документы, требующие ACID гарантий
  • Когда нужны транзакции (сохранить метаданные и файл одновременно)
  • Низкая частота доступа (< 100 раз в сек)

Лучше хранить в S3/облаке:

  • Большие файлы (> 100 MB)
  • Высокая частота доступа
  • Нужна масштабируемость (миллиарды файлов)
  • Требуется CDN для быстрой доставки
  • Видео, аудио, большие архивы

Гибридный подход

Схраните метаданные в PostgreSQL, а сам PDF в облаке:

@Entity
@Table(name = "documents")
public class Document {
    @Id
    private String id;
    
    private String fileName;
    private String s3Key;  // Путь в S3
    private String contentType;
    private Long fileSize;
    private LocalDateTime uploadedAt;
}

@Service
public class DocumentService {
    @Autowired
    private DocumentRepository documentRepository;
    @Autowired
    private S3Service s3Service; // AWS S3 или подобное
    
    public void savePDF(String fileName, InputStream pdfStream) {
        // 1. Сохраняем в S3
        String s3Key = s3Service.uploadFile(fileName, pdfStream);
        
        // 2. Сохраняем метаданные в БД
        Document doc = new Document();
        doc.setFileName(fileName);
        doc.setS3Key(s3Key);
        doc.setUploadedAt(LocalDateTime.now());
        documentRepository.save(doc);
    }
    
    public InputStream getPDF(String documentId) {
        Document doc = documentRepository.findById(documentId).orElse(null);
        if (doc != null) {
            return s3Service.downloadFile(doc.getS3Key());
        }
        return null;
    }
}

Производительность и масштабируемость

МетрикаBYTEA в БДLarge ObjectsS3 + БД
Малые файлы (< 5MB)✅ Хорошо⚠️ OK⚠️ Overhead
Большие файлы (> 100MB)❌ Медленно✅ Хорошо✅ Отлично
ACID транзакции✅ Да⚠️ Частично❌ Нет
Масштабируемость⚠️ Limited⚠️ Limited✅ Infinite
Цена хранения❌ Дорого⚠️ Average✅ Дешево

Заключение

Да, PDF можно сохранять в PostgreSQL с помощью типов данных BYTEA или Large Objects. Однако выбор метода зависит от:

  • Размера файлов (маленькие → БД, большие → облако)
  • Частоты доступа (редкий доступ → БД, часто → облако)
  • Требований к транзакциям (нужны ACID → БД)
  • Масштабируемости (миллионы файлов → облако)

Для production систем часто используют гибридный подход: метаданные в PostgreSQL, сами файлы в AWS S3 или подобном хранилище.