검색

레이블이 lob인 게시물을 표시합니다. 모든 게시물 표시
레이블이 lob인 게시물을 표시합니다. 모든 게시물 표시

2023년 8월 24일

코딩 - Spring DATA JPA 에서 LOB 데이터를 포함하는 엔터티 조회 성능 개선

 ◼︎ 환경 

  • Model : MacBook Pro (14-inch, 2021)
  • CPU : Apple M1 Pro
  • MENORY : 16GB
  • DISK : 512 GB SSD
  • OS : macOS 13.2.4 (22F66)
  • TOOLS : Visual Studio Code, Java 11, Gradle, Docker
  • Version Control : GitHub
  • Programming Language : Java
  • Framework : Spring Boot 2.7.12
  • DBMS : MySql 8.0.33 


문제

첨부파일 데이터를 데이터베이스에 저장하는 구조의 첨부파일 모듈을 아래와 같이 구성하고 있다. 

첨부파일 엔터티 클래스는 아래와 같이 ❶ LocalAttachment 와 ❷ LocalAttachmentData로 구성되어 있으며 이들은 one-to-one  으로 매핑되어 있다.


@Data
@NoArgsConstructor
@Entity
@Table(name = "AC_UI_ATTACHMENT")
public class LocalAttachment implements Attachment, PropertyAware {

@Id // tell persistence provider 'id' is primary key
@Column(name = "ATTACHMENT_ID", nullable = false)
@GeneratedValue( // tell persistence provider that value of 'id' will be generated
strategy = GenerationType.IDENTITY // use RDBMS unique id generator
)
private long attachmentId;

@Column(name = "OBJECT_TYPE", nullable = false)
private int objectType;

@Column(name = "OBJECT_ID", nullable = false)
private long objectId;

@Column(name = "CONTENT_TYPE", nullable = false)
private String contentType;

@Column(name = "FILE_NAME", nullable = false)
private String name;

@Column(name = "FILE_SIZE", nullable = false)
private int size;

@Transient // tell persistence provider ignore 'downloadCount' field.
private int downloadCount = 0;

@Transient // tell persistence provider ignore 'tags' field.
private String tags;

@JsonIgnore
@Transient // tell persistence provider ignore 'inputStream' field.
private InputStream inputStream;

@JsonIgnore
@OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY )
@JoinColumn(name = "ATTACHMENT_ID")
private LocalAttachmentData data;

@Transient // tell persistence provider ignore 'sharedLink' field.
private SharedLink sharedLink;
@CreatedBy
@OneToOne(targetEntity = LocalUser.class)
@JoinColumn(name = "USER_ID", nullable = false)
@JsonDeserialize(using = JsonUserDeserializer.class)
private User user;

@CreatedDate
@Column(name = "CREATION_DATE", updatable = false)
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")
private Date creationDate;

@LastModifiedDate
@Column(name = "MODIFIED_DATE")
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")
private Date modifiedDate;

@ElementCollection(fetch = FetchType.EAGER)
@CollectionTable(name = "AC_UI_ATTACHMENT_PROPERTY", joinColumns = {
@JoinColumn(name = "ATTACHMENT_ID", referencedColumnName = "ATTACHMENT_ID") })
@MapKeyColumn(name = "PROPERTY_NAME")
@Column(name = "PROPERTY_VALUE")
private Map<String, String> properties;

}


@Data
@Entity
@Table(name = "AC_UI_ATTACHMENT_DATA")
@NoArgsConstructor
@AllArgsConstructor
public class LocalAttachmentData {
@Id
@Column(name = "ATTACHMENT_ID")
private Long attachmentId;

@Lob
@Column(name = "ATTACHMENT_DATA")
private Blob blob;

}

이경우 문제가 되는 것은 LocalAttachmentData 는 Blob 데이터 이기 때문에 데이터 크기에 따라 목록을 조회하는 경우 성능 저하 이슈가 발생하게 된다.  FetchType.LAZY 옵션을 사용하여 보았지만 성능에 개선은 없었다. 


문제 해결 

목록을 조회할 때 바이너리 데이터를 같이 조회하지 않도록 하기 위하여 one-to-one 매핑을 제거하고 LocalAttachment 에 해당하는 ❷ LocalAttachmentData 를 별도의 Repository 을 사용하여 처리하는 것으로 변경하였다. 


@JsonIgnore
@Transient
//@OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY )
//@JoinColumn(name = "ATTACHMENT_ID")
private LocalAttachmentData data;

엔터티간의 매핑을 제거하였기 때문에 저장시 LocalAttachmentData 을 처리하는 로직을 추가할 필요가 있었다.


attachmentRepository.save(attachmentToUse );
if( attachmentToUse.getData() != null){
LocalAttachmentData data = attachmentToUse.getData();
data.setAttachmentId(attachmentToUse.getAttachmentId());
attachmentDataRepository.save(attachmentToUse.getData());
}


참고자료

Spring MVC how to get progress of running async task

2023년 7월 6일

코딩 - Spring DATA JPA 에서 스트림을 이용한 LOB 데이터 처리

 ◼︎ 환경 

  • Model : MacBook Pro (14-inch, 2021)
  • CPU : Apple M1 Pro
  • MENORY : 16GB
  • DISK : 512 GB SSD
  • OS : macOS 13.2.4 (22F66)
  • TOOLS : Visual Studio Code, Java 11, Gradle, Docker
  • Version Control : GitHub
  • Programming Language : Java
  • Framework : Spring Boot 2.7.12
  • DBMS : MySql 8.0.33 


1. BLOB 바이너리 데이터


Spring Data JPA에서 바이너리 데이터는 아래와 같이 byte [] 타입을 사용하여 데이터를 저장한다.

  1. 바이너리 데이터를 저장할 엔터티를 생성.
  2. @Lob 어노테이션을 사용하여 바이너리 데이터 속성을 매핑
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Lob;

@Entity
public class User {

@Id
@Column(name = "USER_ID")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long userId;

@Column(name = "NAME")
private String name;

@Lob
@Column(name = "IMAGE")
private byte[] image;
}

과거 JDBC 프로그래밍의 경우는 성능 이슈가 있어 바이트 배열이 아닌 스트림을 사용했는데 JPA 에서 역시 바이트 배열 이 아닌 스트림을 사용하여 처리할 수 있다. 

  1. image 변수의 타입을 byte[] 가 아닌 Blob 타입으로 정의한다. 
  2. 엔터티를 저장하거나 image 데이터를  꺼내는 경우 추가적으로 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 com.fasterxml.jackson.annotation.JsonIgnore;
import java.sql.Blob;

@Entity
public class User {

@Id
@Column(name = "USER_ID")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long userId;

@Column(name = "NAME")
private String name;

@JsonIgnore
@Lob
@Column(name = "IMAGE")
private Blob image;

}


Spring Data JPA 의 경우 디폴트로 hibernate 을 사용하기 때문에 Blob 데이터는 hibernate 에서 제공하는 BlobProxy 클래스를 사용하여 생성하면 된다.  


Blob blob = org.hibernate.engine.jdbc.BlobProxy.generateProxy(
FileUtils.openInputStream(file), FileUtils.sizeOf(file));
user.setImage(new AvatarImageData(imageToUse.getAvatarImageId(), blob ));



Blob 데이터는 아래와 같이 사용하면 된다.

User user = ...
File file = ...
InputStream inputStream = user.getImage().getBinaryStream();
FileUtils.copyInputStreamToFile(inputStream, file);


2. CLOB 장문 텍스트


CLOB 형식의 경우 @Lob 어노테이션만 추가하면 된다.



@Column(name = "BODY", nullable = false)
@Lob
private String body ;




참고자료


  1. Mapping BLOBs and CLOBs with Hibernate and JPA
  2. How to stream large Blob from database to application using JPA?