===== 채팅서버 구현하기 시리즈 =====
- ✔️ [Web-Network] - 채팅 서버 설계를 위한 배경지식 정리 (HTTP, WebSocket, WebRTC)
- ✔️ [Spring/Spring Boot] - Spring WebSocket 공식문서 가이드 살펴보기
- ✔️ [Spring/Spring Boot] - Spring WebSocket STOMP 채팅 서버 구현하기 (with. JWT, Exception Handling)
- 👉🏻 [Spring/Spring Boot] - Spring Data mongoDB + mysql 사용하기 (with. queries)
채팅서버를 구현하면서, 채팅 내역을 저장할 DB로 nosql 을 사용하기로 했고 그 중에 mongoDB를 사용하고자 했습니다.
기존의 API 서버에서는 JPA를 이용해 mysql 을 사용하고 있었고, 기존 프로젝트에 mongoDB를 추가 사용해 mysql + mongoDB의 구조를 가져가고자 했습니다.
연결자체는 생각보다 간단했고, spring-data-mongoDB를 별도로 사용해 jpa 와 충돌할것도 수정할 것도 없었습니다.
1. spring data mongoDB 설정하기
✔️ build.gradle
//mongo
implementation 'org.springframework.boot:spring-boot-starter-data-mongodb'
✔️ application.yaml
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql:-
username: -
password: -
data:
mongodb:
host: -
port: 27017
authentication-database: admin
username: username
password: password
database: my_db
+ 추가적으로 날라가는 mongoDB 쿼리가 보고싶다면 아래처럼 로깅을 설정해주시면 됩니다.
logging:
level:
org:
hibernate:
SQL: DEBUG
orm.jdbc.bind: TRACE
springframework:
data:
mongodb:
core:
MongoTemplate: DEBUG
✔️ MongoDBConfig
- 사실 별도의 config 설정이 필수는 아닙니다. 이거 없어도 잘 저장하고 조회하는데
- 저장할때 컬럼에 자동적으로 _class 가 생기는걸 방지하기 위한 설정 파일입니다.
//_class 컬럼이 자동 생성 방지
@Configuration
@EnableMongoRepositories("com.example.chatserver.repository")
@EnableMongoAuditing
public class MongoDBConfig {
@Bean
public MappingMongoConverter mappingMongoConverter(
MongoDatabaseFactory mongoDatabaseFactory,
MongoMappingContext mongoMappingContext
) {
DbRefResolver dbRefResolver = new DefaultDbRefResolver(mongoDatabaseFactory);
MappingMongoConverter converter = new MappingMongoConverter(dbRefResolver, mongoMappingContext);
converter.setTypeMapper(new DefaultMongoTypeMapper(null));
return converter;
}
}
2. mongoDB Document Entity
👏🏻 JPA 를 사용하는 RDMS는 spring 에서 @Entity 를 사용하지만 nosql인 mongoDB는 @Document를 사용해야 합니다.
- Spring Data MongoDB에서도 JPA Auditting 기능을 사용하고 싶다면 @EnalbeMongoAuditing 어노테이션을 Application 클래스나 Bean으로 등록되는 Config 파일에 명시해 주면 사용할 수 있습니다.
- 간혹 블로그를 보다보면 QClass 생성을위해 "@Document 와 @Entity를 같이 사용" 하는 글이 있는데, 이는 권장되지 않는 방법입니다.
- 만약 document 의 idx를 mongoDB의 ObjectID를 이용했다면(default) @Field 어노테이션의 targetType 속성을 사용하여 spring data mongoDB 사용시 objectID로 사용할 수 있습니다.
@Getter
@Document(collection = "collection_name")
@NoArgsConstructor
@EntityListeners(AuditingEntityListener.class)
public class TestDocument {
@Id
@Field(value = "_id", targetType = FieldType.OBJECT_ID)
private String id;
@Field("room_idx")
private Long roomIdx;
@Field("sender_name")
private String senderName;
@Field("msg")
private String msg;
@Field("img_url")
private String imgUrl;
@Field("created_at")
@CreatedDate
private LocalDateTime createdAt;
@Field("updated_at")
@LastModifiedDate
private LocalDateTime updatedAt;
}
3. MongoDB Repository
JpaRepository처럼 기본적인 CRUD는 MongoRepository를 이용하여 사용할 수 있습니다.
하지만 spring data mongoDB 는 queryDsl 을 사용하지 않기 때문에 Custom한 mongo query를 날리는 부분이 jpa 와 다릅니다.
- 저는 ObjectID를 사용하기때문에 명시적으로 조건절에 new ObjectId 객체를 생성하여 파라미터로 넘겨주었지만,
- Entity 설정에 targetType = OBJECT_ID를 했다면 생략하셔도 무방합니다.
@Repository
public interface ChatRepository extends MongoRepository<ChatHistory, Long>, ChatCustomRepository {
}
public interface ChatCustomRepository {
List<ChatHistory> findAllCursorPagingBy(final long chatRoomId, final String chatIdx,
final int size);
}
@RequiredArgsConstructor
public class ChatCustomRepositoryImpl implements ChatCustomRepository{
private final MongoTemplate mongoTemplate;
@Override
public List<ChatHistory> findAllCursorPagingBy(final long chatRoomId, final String chatIdx,
final int size) {
Query query = new Query();
query.addCriteria(Criteria.where("room_idx").is(chatRoomId));
if(Objects.nonNull(chatIdx)) {
query.addCriteria(Criteria.where("_id").lt(new ObjectId(chatIdx)));
}
query.with(Sort.by(Direction.DESC, "_id")).limit(size);
return mongoTemplate.find(query, TestDocument.class);
}
}
📌 검색 시 다양한 방법이 나왔지만, 별도의 QClass 를 만들어주지 않아도 되고 명시적으로 쿼리를 작성할 수 있는 위의 방법이 저는 좋아보였습니다.
(즉, 이게 정답은 아니니 더 권장되는 방법이 있다면 저에게 알려주세요 ㅎㅎㅎ)
↓
👏🏻 아래처럼 save() 를 바로 사용해도 되고, custom 한 mongo query 를 마치 querydsl 처럼 사용할 수 도 있습니다.
@Service
@Transactional
@RequiredArgsConstructor
public class ChatService {
private final ChatRepository chatRepository;
public ChatResponse recordHistory(final String chatRoomNo, final ChatRequest request) {
final ChatHistory save = chatRepository.save(
ChatHistory.of(Long.parseLong(chatRoomNo), request.sender(), request.senderUuid(),
request.msg(), request.imgUrl())
);
return ChatResponse.of(save);
}
public List<ChatHistory> readHistory(final long chatRoomId, final String chatIdx,
final int size) {
return chatRepository.findAllCursorPagingBy(chatRoomId, chatIdx, size);
}
}
그럼 이런 쿼리가 날아감
참고
- spring mongo custom query :https://www.baeldung.com/queries-in-spring-data-mongodb
- spring data mongoDB Reference : https://docs.spring.io/spring-data/mongodb/docs/current/reference/html/#repositories.custom-implementations
'Spring > Spring Boot' 카테고리의 다른 글
Spring Security Exception Handling - Filter 단 예외 처리하기 (5) | 2023.07.06 |
---|---|
[Spring] oneToMany, 일 대 다 관계 조회 성능 테스트 (Jpa, QueryDsl, Java Stream, 단일 DB 조회 쿼리 성능 비교) (0) | 2023.06.30 |
Spring WebSocket STOMP 채팅 서버 구현하기 (with. JWT, Exception Handling) (2) | 2023.06.14 |
Spring WebSocket 공식문서 가이드 살펴보기 (4) | 2023.06.01 |
Spring Security 가이드 (with. Spring boot 3.0) - 스프링 시큐리티란, 동작 과정, 사용 방법, JWT 발급 (4) | 2023.05.04 |