Spring/Spring Boot

Spring Data mongoDB + mysql 사용하기 (with. queries)

민돌v 2023. 6. 14. 18:10

===== 채팅서버 구현하기 시리즈  =====


 

채팅서버를 구현하면서,  채팅 내역을 저장할 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);
    }
}

그럼 이런 쿼리가 날아감

 

 

 

 

 


참고