본문 바로가기

프로그래밍/이것저것 일하면서

데이터베이스에 파일을 blob으로 저장하는 방법

반응형

 

File Upload

파일을 서버 파일시스템에 저장할 것인지 데이터베이스에 저장할 것인지는 대개는 고려대상이 아닙니다. 데이터베이스는 서버 용량에 비해 매우 비싼 자원입니다. 그럼에도 불구하고 다양한 비즈니스 요구사항에 따라서 데이터베이스에 파일을 저장하는 것을 고려해보아야 하는 경우도 있습니다.

오늘은 다른 회사로 이직한 후배가, 이직한 회사에서는 데이터베이스에 Blob으로 파일을 저장한다며, 이렇게 만드는 경우도 있냐고 해서 관련 내용을 정리해보았습니다. 제가 취준생일 때 이런식으로 설계를 해본 경험이 있는데 여러모로 많은 번거로움에 비해 큰 이점은 느끼지 못했던 방법이었습니다. 제가 아직 그런 경우가 필요한 비즈니스 프로세스를 경험해보지 못했을 수도 있습니다.

데이터베이스에 파일을 Blob 저장할 때 장단점

파일을 데이터베이스에 저장할 때 Blob(Binary Large Object) 데이터 타입을 사용하는 경우 장단점은 다음과 같습니다.

장점:

  1. 데이터 일관성 유지: 파일을 데이터베이스에 저장하면 데이터 일관성을 유지할 수 있습니다. 파일과 관련된 모든 데이터를 한 번에 저장하므로 파일과 관련된 정보가 분리되지 않고 동시에 관리됩니다. 이것은 파일을 분리하고 서로 다른 위치에 저장하는 경우 데이터 일관성을 유지하기 어려울 수 있습니다.
  2. 높은 보안성: 파일을 데이터베이스에 저장하면 데이터베이스에서 제공하는 보안 기능을 활용할 수 있습니다. 파일을 데이터베이스 내에서 암호화하거나 엑세스 권한을 설정하여 더욱 높은 보안성을 제공할 수 있습니다.
  3. 효율적인 관리: 데이터베이스는 데이터를 관리하기 위한 많은 도구와 기능을 제공합니다. Blob으로 파일을 저장하면 이러한 기능을 사용하여 파일을 효율적으로 관리할 수 있습니다. 예를 들어, 파일의 크기, 생성일, 최근 수정일 등과 같은 정보를 쉽게 검색하고, 관리할 수 있습니다.

단점:

  1. 성능 이슈: Blob으로 파일을 저장하는 경우 파일을 읽어오기 위해 데이터베이스에 접근해야 합니다. 이는 파일을 직접 읽는 것보다 느리게 동작할 수 있습니다. 또한, 대용량 파일을 저장할 때는 Blob으로 저장하는 것이 성능에 영향을 줄 수 있습니다.
  2. 용량 한계: 데이터베이스에는 Blob을 저장할 수 있는 용량 한계가 있습니다. 이러한 제한으로 인해 대용량 파일을 저장하는 데 제한이 있을 수 있습니다. 이 경우 파일을 저장할 때 분할하여 저장하거나, 다른 저장소 솔루션을 고려할 필요가 있습니다.
  3. 복잡성: 파일을 Blob으로 저장하는 것은 파일 시스템에서 파일을 저장하는 것보다 더 복잡할 수 있습니다. 이를 구현하려면 데이터베이스와 프로그래밍 언어를 더욱 깊이 이해하고 있어야 합니다. 또한, 파일과 관련된 모든 정보를 데이터베이스에서 관리해야 하므로 데이터베이스 설계를 더욱 신중하게 고민해야 합니다.

 

구현 예제

파일을 Blob으로 저장하기

Java Spring Boot와 Oracle을 사용하여 SampleFile.jpg를 DB에 Blob으로 저장하는 예제입니다.

Oracle 데이터베이스에서 Blob을 저장할 테이블을 생성합니다. 

CREATE TABLE sample_files (
  id NUMBER PRIMARY KEY,
  file_name VARCHAR2(100),
  file_data BLOB
);

 

Spring Boot 애플리케이션에서 JDBC 드라이버를 사용하여 Oracle 데이터베이스에 연결합니다. 이를 위해 pom.xml 파일에 Oracle JDBC 드라이버 의존성을 추가해야 합니다. 여기서 이 과정은 생략하겠습니다.

다음은 SampleFile.jpg를 읽어들이고, Oracle Blob 객체를 생성합니다.

File file = new File("SampleFile.jpg");
FileInputStream fis = new FileInputStream(file);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
byte[] buf = new byte[1024];
for (int readNum; (readNum = fis.read(buf)) != -1;) {
    bos.write(buf, 0, readNum);
}
byte[] fileBytes = bos.toByteArray();
Blob fileData = new SerialBlob(fileBytes);

JDBC를 사용하여 Oracle 데이터베이스에 Blob 데이터를 저장합니다.

Connection connection = dataSource.getConnection();
PreparedStatement ps = connection.prepareStatement("INSERT INTO sample_files (id, file_name, file_data) VALUES (?, ?, ?)");
ps.setInt(1, 1);
ps.setString(2, "SampleFile.jpg");
ps.setBlob(3, fileData);
ps.executeUpdate();

 

위의 코드에서 dataSource는 Spring Boot가 제공하는 데이터베이스 연결 설정으로, application.properties 파일에 데이터베이스 연결 정보를 설정합니다.

위의 코드는 Oracle 데이터베이스에 'sample_files' 테이블을 생성하고, 'SampleFile.jpg' 파일을 읽어들여 Oracle Blob 객체로 변환하고, 이를 데이터베이스에 저장하는 코드입니다. 필요에 따라 코드를 수정하여 다른 파일을 저장하도록 변경할 수 있습니다.

 

파일 형태로 응답하기

다음은 Oracle 데이터베이스에서 Blob으로 저장된 파일을 조회하여 Response로 전송해주는 Controller, Service, Dao의 예시 코드입니다.

Controller:

@RestController
public class FileController {
    
    @Autowired
    private FileService fileService;

    @GetMapping("/files/{id}")
    public void downloadFile(@PathVariable Long id, HttpServletResponse response) throws IOException {
        FileData fileData = fileService.getFileData(id);
        response.setContentType(MediaType.IMAGE_JPEG_VALUE);
        response.setHeader("Content-Disposition", "attachment; filename=" + fileData.getFileName());
        response.getOutputStream().write(fileData.getFileData());
    }
}

Service:

@Service
public class FileService {
    
    @Autowired
    private FileDao fileDao;
    
    public FileData getFileData(Long id) {
        return fileDao.getFileData(id);
    }
}

Dao:

@Repository
public class FileDao {
    
    @Autowired
    private JdbcTemplate jdbcTemplate;
    
    public FileData getFileData(Long id) {
        String sql = "SELECT id, file_name, file_data FROM sample_files WHERE id = ?";
        return jdbcTemplate.queryForObject(sql, new Object[]{id}, (rs, rowNum) -> {
            Long fileId = rs.getLong("id");
            String fileName = rs.getString("file_name");
            Blob fileData = rs.getBlob("file_data");
            byte[] fileBytes = fileData.getBytes(1, (int) fileData.length());
            return new FileData(fileId, fileName, fileBytes);
        });
    }
}

DTO:

public class FileData {
    private Long id;
    private String fileName;
    private byte[] fileData;
    
    public FileData(Long id, String fileName, byte[] fileData) {
        this.id = id;
        this.fileName = fileName;
        this.fileData = fileData;
    }
    
    // getter and setter methods.. 혹은 lombok @Data 어노테이션 사용
}

위의 코드는 '/files/{id}' 경로로 요청이 오면 해당 id에 해당하는 파일 데이터를 가져와서 Response의 body에 파일 데이터를 쓰는 코드입니다. 필요에 따라 Content-Type이나 Content-Disposition 등을 수정하여 다른 파일을 다운로드할 수 있습니다.

혹은 우리나라에서 여전히 많이 사용하는 MyBatis를 쓴다면 다음과 같은 mapper를 만들 수도 있습니다.

FileMapper.xml:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mapper.FileMapper">
  <resultMap id="fileResultMap" type="com.example.dto.FileData">
    <id property="id" column="id"/>
    <result property="fileName" column="file_name"/>
    <result property="fileData" column="file_data" javaType="byte[]"/>
  </resultMap>
  <select id="selectFileData" resultMap="fileResultMap" parameterType="Long">
    SELECT id, file_name, file_data
    FROM sample_files
    WHERE id = #{id}
  </select>
  <insert id="insertFileData" parameterType="com.example.dto.FileData">
    INSERT INTO sample_files (id, file_name, file_data)
    VALUES (#{id}, #{fileName}, #{fileData, jdbcType=BLOB})
  </insert>
</mapper>

 

개인적으로는 데이터베이스에 파일을 직접 저장해야 하는 상황을 맞닥들여본 적이 없으므로 뭐라고 제 경험을 말씀 드리기는 어렵습니다. 비즈니스의 요구사항에 맞추어 득과 실을 잘 따져보아야 할 것입니다. 

반응형

개발자가 그리는 인스타툰 팔로우하세요!