Mongo Repository & Mongo Template

Caesars·2024년 7월 14일
0

MongoDB

목록 보기
2/2

Mongo Repository를 써보고 후기를 정리합니다. 오랬동안 Mongo Template 만을 써와서 쓸 필요를 느끼지 못했습니다. 하지만 팀 내에서 MongoDB에 대해 잘 모르는 사람도 사용할 필요가 생겼기에 쉽게 사용할 수 있도록 하기 위해 기능의 일정 부분을 Repository로 전환했습니다.


MongoTemplate vs MongoRepository

  • MongoTemplate: MongoDB의 고급 기능을 사용할 때 유용합니다. 커스텀 쿼리, 복잡한 업데이트 작업, 그리고 MongoDB 집계 파이프라인을 쉽게 구현할 수 있습니다.
  • MongoRepository: Spring Data MongoDB에서 제공하는 리포지토리 추상화를 사용하여 기본적인 CRUD 작업을 쉽게 수행할 수 있습니다. 쿼리 메서드와 자동 생성되는 쿼리를 통해 생산성을 높일 수 있습니다.

코드

구성

Template
└── Service // service db를 사용하는 MongoTemplate
└── Api // api db를 사용하는 MongoTemplate
repository
└── ServiceRepository // service db와 연결해야하는 repo
└── UserRepository // service db와 연결해야하는 repo
└── ApiRepository // api db와 연결해야하는 repo

MongoConfig 설정

@Configuration
@EnableMongoAuditing
public class MongoConfig{

	// MongoProperties 에 설정한 주소로 접속
	@Bean
    public MongoClient mongoClient(MongoProperties mongoProperties){
    	MongoCredential credential = MongoCredential.createCredential(mongoProperties.getUsername(),
				mongoProperties.getAuthDatabase(), mongoProperties.getPassword().toCharArray());
		MongoClientSettings settings = MongoClientSettings.builder().credential(credential)
				.applyToClusterSettings(builder -> builder
						.hosts(Arrays.asList(new ServerAddress(mongoProperties.getHost(), mongoProperties.getPort()))))
				.build();

		return MongoClients.create(settings);
    }
    
    @Bean
	public MongoTemplate serviceTemplate(MongoClient mongoClientService) {
    	// service DB 사용
		MongoTemplate mongoTemplate = new MongoTemplate(mongoClientService, "service");
		((MappingMongoConverter) mongoTemplate.getConverter()).setTypeMapper(new DefaultMongoTypeMapper(null));
		return mongoTemplate;
	}
    
    @Bean
	public MongoTemplate apiTemplate(MongoClient mongoClientService) {
       	// api DB 사용
		MongoTemplate mongoTemplate = new MongoTemplate(mongoClientService, "api");
		((MappingMongoConverter) mongoTemplate.getConverter()).setTypeMapper(new DefaultMongoTypeMapper(null));
		return mongoTemplate;
	}
    
    //service.repository 패키지에 있는 repository는 serviceTemplate을 쓰도록 설정
	@EnableMongoRepositories(basePackages = "com.test.mongodb.service.repository", mongoTemplateRef = "serviceTemplate")
    public static class ServiceMongoConfig {}

	//api.repository 패키지에 있는 repository는 apiTemplate을 쓰도록 설정
    @EnableMongoRepositories(basePackages = "com.test.mongodb.api.repository", mongoTemplateRef = "apiTemplate")
    public static class ApiMongoConfig {}
}

(설정 부분에서 시간이 꽤 걸렸습니다)

위 코드에서는 하나의 MongoClient 인스턴스만 있지만, 여러대의 서버로 연결이 필요하다면 그만큼의 MongoClient 인스턴스가 필요합니다.

처음에는 여러개의 configuration 클래스를 만들고 @Configuration 어노테이션을 붙여 각각의 DB로 연결했었습니다. 추후 하나의 MongoConfig 안에 서브 클래스로 DB를 연결하는 방식이 더 깔끔해 보여 수정했습니다.

MongoTemplate 등록

@Bean
public MongoTemplate serviceTemplate(MongoClient mongoClientService) {
   	// service DB 사용
	MongoTemplate mongoTemplate = new MongoTemplate(mongoClientService, "service");
	((MappingMongoConverter) mongoTemplate.getConverter()).setTypeMapper(new DefaultMongoTypeMapper(null));
	return mongoTemplate;
}

MongoTemplate는 초기화 시점에 mongoClinet 인스턴스를 받습니다. 생성된 MongoTemplate은 바로 사용 가능합니다.

repository에 mongoTemplateRef 지정

Spring data는 @Repository 어노테이션이 붙은 인터페이스를 기준으로 자동 생성된 CRUD 기능을 제공한다.

여러 DB를 하는데 Repostiory들이 각자 사용할 mongoTemplate 인스턴스를 알려주기위해 mongoTemplateRef를 통해 bean의 이름을 지정하면 됩니다.

만약 MongoTemplate을 따로 지정해주지 않았다면 기본 템플릿(MongoTemplate) bean을 찾게 되는데 없다면 에러가 나게 됩니다.

@EnableMongoRepositories(basePackageClasses = com.test.mongodb.service.MyMongo.class, 
mongoTemplateRef = "serviceTemplate")

라고 class로 설정도 가능합니다. 하지만 class 자체와 연결되는 것이 아니라, 해당 class가 속한 패키지 내의 리포지토리들이MongoTemplate과 연결됩니다.

Repository 인터페이스의 주요 기능

추가 코드 없이 MongoRepository를 상속하면 CRUD 기능이 제공된다.
@Query 어노테이션을 이용하여 사용자 정의 쿼리 사용이 가능하다. @Query 어노테이션을 통해서 네이티브 형태의 쿼리 조회가 가능하다.
@Update 어노테이션을 이용하여 조회된 도큐먼트에 대해서 데이터 업데이트가 가능하다.
페이징과 정렬 기능 사용이 가능하다.

Repository를 이용한 CRUD

//1
@Update("{ '$inc' : { 'visits' : 1 } }")
long findAndIncrementVisitsByLastname(String lastname);

//2
@Update("{ '$inc' : { 'visits' : ?1 } }")
void findAndIncrementVisitsByLastname(String lastname, int increment); 

//3
@Update("{ '$inc' : { 'visits' : ?#{[1]} } }")
long findAndIncrementVisitsUsingSpELByLastname(String lastname, int increment); 

//4
@Update(pipeline = {"{ '$set' : { 'visits' : { '$add' : [ '$visits', ?1 ] } } }"})
void findAndIncrementVisitsViaPipelineByLastname(String lastname, int increment);

//5
@Update("{ '$push' : { 'shippingAddresses' : ?1 } }")
long findAndPushShippingAddressByEmail(String email, Address address);

//6
@Query("{ 'lastname' : ?0 }")
@Update("{ '$inc' : { 'visits' : ?1 } }")
void updateAllByLastname(String lastname, int increment); 

//7
@Query("{ 'lastname' : ?0, 'firstname': ?#{#firstname == null ? {$exists:true} : $firstname} }")
List<Person> findByLastname(String lastname, String firstname); 
}

//8
@Query(value = "{ 'lastname' : ?0, 'firstname': ?#{#firstname == null ? {$exists:true} : $firstname} }", count=true)
int countByLastname(String lastname, String firstname); 
}

--->1 : 업데이트에 대한 필터 쿼리는 메서드 이름에서 파생된다. 즉, find~ByLastname(String lastname)과 같이 lastname 필드에 대한 필터 쿼리를 수행한 결과에 visit 필드의 값을 1 증가시키는 동작을 한다.

--->2 : visits 필드에 추가되는 증분 값은 1번째 파라미터인 increment의 값으로 '?1'이 대체된다. (placeholder는 ?0 부터 시작)

--->3 : 매개변수 바인딩에 SpEL 표현식을 사용할 수 있다.

--->4 : 파이프라인 속성을 사용하여 aggregation 업데이트를 수행한다.

--->5 : email 필드에 대한 필터 쿼리를 수행하여 나온 Document에서 array field인 shippingAddress에 Address 타입의 element를 추가한다. $push 연산자는 array field에서 사용가능한 연산자다.

--->6 : @Query 어노테이션과 결합하여 사용할 수 있다. @Query 어노테이션을 통해 lastname 필드가 일치하는 문서를 필터링하여 visits 필드의 값을 1 증가시키는 작업을 수행한다.

--->7 : SpEL 표현식을 사용하여 동적으로 쿼리를 만들 수 있다. firstname이 null이 아니면 쿼리에 포함하고 null이면 미포함.

--->8 : 개수만 필요한 경우라면 마지막에 count = true 옵션으로 설정.

Repository 등록

package com.test.mongodb.service.repository;

public interface ServiceRepository extends JpaRepository<Data, Long>{
	
    List<Data> findAllBy();
    Optional<Data> findByRequestId(Long id);
    ....
}

..............................

package com.test.mongodb.api.repository;
public interface ApiRepository extends JpaRepository<ApiEntity, Long>{
	
    Optional<ApiEntity> findByApiId(Long id);
    ....
}

Service

@Service
public class ApiService {

    @Autowired
    private ApiRepository apiRepository;
    
    @Autowired
    private MongoTemplate apiTemplate;

	public Optional<ApiEntity> findById(Long id) {
    	/* apiTemplate.findOne(Query.query(Criteria.where("id")
        .is(id)), ApiEntity.class);
        */
        return apiRepository.findById(id);
    }

    public void deleteById(Long id) {
        apiRepository.deleteById(id);
    }

    
    ...
    
}

후기

간단한 CRUD 기능은 repository로 변환했습니다. 팀원이 mongo를 다루는데 거부감을 줄일것이라 생각합니다. 다만 복잡한 쿼리는 repository로 변환할 수가 없었기에 그대로 뒀습니다.

요약

MongoTemplate은 복잡한 쿼리가 필요한 경우에 사용합니다.
MongoRepository는 기본적인 CRUD 작업과 간단한 쿼리에 유용합니다.
두 가지를 상황에 따라 유연하게 사용하면 편합니다. 끝


참고

https://velog.io/@ur2e/Spring-Data-MongoDB-Multiple-database-config-0hco07aw
https://velog.io/@hanblueblue/MongoDB-MongoTemplate-vs.-MongoRepository
https://devel-repository.tistory.com/77
https://javatechonline.com/spring-boot-mongodb-query-examples/

profile
잊기전에 저장

0개의 댓글