Spring Boot

SpringBoot + MariaDB 6-2 구독 구독정보 (스칼라쿼리)

svdjcuwg4638 2023. 6. 13. 16:11

1. 그 페이지의 유저가 구독을 누구를 했지는 리스트로 가져와야한다

2. 페이지 유저의 구독정보를 눌렀을 때 로그인한사람의 기준으로 구독여부에따라 버튼표시

3. 만약 구독정보에 내가 있다면 버튼을 안보이게 해야함

 

DTO생성

SubscribeDto.java

package com.cos.photogramstart.web.dto.subscribe;

@Builder
@AllArgsConstructor
@NoArgsConstructor
@Data
public class SubscribeDto {

	private int id;
	private String userName;
	private String profileImageUrl;
	private Integer subscribeState;
	private Integer equalUserState;
	
}

구독 버튼을 눌렀을때 당하는 사람을 알아야하니 userId를받고

구독을 하였는지 여부와 나인지 판단여부를 필드에 만들었다 내 이름에 구독하기 버튼이 뜨면 안되니까

 

이제 api를 만들어야하는데 

주소를 만들때 /user/1/subscribe 형식으로 만드는게 좋다

읽을때 유저 1번이 구독을한다 형태로 이해가 되기때문에 위형태로 api를 만들어보자

 

UserApiController.java

	private final SubscribeService subscribeService; 
	
	@GetMapping("/api/user/{pageUserId}/subscribe")
	public ResponseEntity<?> subsribeList(@PathVariable int pageUserId,@AuthenticationPrincipal PrincipalDetails principalDetails){
		
		List<SubscribeDto> subsribeDto = subscribeService.구독리스트(principalDetails.getUser().getId(),pageUserId);
		return new ResponseEntity<>(new CMRespDto<>(1,"구독자 정보 리스트 가져오기 성공",subsribeDto),HttpStatus.OK);
	}

 

SubscribeService.java

	public List<SubscribeDto> 구독리스트(int id, int pageUserId) {
		return null;
	}

서비스 까지 생성해주고 지금 받아야할 자료가 SubscribeDto에 데이터를 모두 받아야하는데 여러가지의 정보를 합해서 가져와야한다 

 

스칼라쿼리

예를 들어

로그인을 1번유저가 했고 구독정보는 2번을 보는중이다 그럼 2번의 구독정보가 보일것인데 

2번은 1번과 3번을 구독하고있다 그럼 나와 3번이 구독정보에 보여야하는것이다.

그럼 1번과 3번이 나오게 sql문을 작성할려면 어떻게 작성 해야할까?

select * from subscribe where fromUserId = 2;

위와같이 작성하였다면 2번이 구독한 정보는 모두 가져왔다 그럼 유저테이블도 조인을해서 user테이블과 구독테이블을 참조하여 조회를 해보자

 

select u.id, u.username, u.profileImageUrl

from user u inner join subscribe s

on u.id = s.toUserId

where s.fromUserid = 2;

 

위와같이하면 3개의 필요한 값은 가져왔다

로그인(1)번이고 구독정보모달엔 1,3이있다는 가정하에 구독여부를 판독할때 3만 신경쓰면된다 1은 본인이니까

그럼 3이 구독이되었는지 판별할때의 sql문은 이러하다

 

select true form subscribe where fromUserId = 1 AND toUserId = 3;

하게되면 구독이되어있다면 1을 반환할것이고 아니라면 아무값도 반환을 안할것이다.

 

그럼 위 두개의 sql문을 합쳐야하는데

select u.id, u.username, u.profileImageUrl,
(select true from subscribe where fromUserId = 1 AND toUserId = u.id) subscribeState
from user u inner join subscribe s
on u.id = s.toUserId
where s.fromUserid = 2;

 

위 select문에 (select ...) 하고 쿼리를 스칼라 쿼리라고 하는데 해석해보면 UserId에 로그인한 사람의 id가 들어가게 되고

toUserId에는 각각의 id를 입력되어 값이 나오게되는데 결과는 밑과 같다

그럼 이제 동일 유저인지 판단하는 쿼리가 필요하다 추가를 하게되면

select u.id, u.username, u.profileImageUrl,
if((select true from subscribe where fromUserId = 1 AND toUserId = u.id),1,0) subscribeState,
if((1 = u.id),1,0) equalUserState
from user u inner join subscribe s
on u.id = s.toUserId
where s.fromUserid = 2;

3번째 줄에 1자리에 로그인한 사람의 id가 오고 u.id가 같냐고 물어보게 되면 모든 row에 비교하게되어 맞으면 1을 틀리면 0을 기입하게 될것이다. 

 

if를 사용한 이유는 밑과같이 어떤건 0이고 어떤건 null이 반환되면 혼란이 오게된다 이를 방지하기위해 if문을 사용함으로서 0과 1을 반환하도록 통일시킨다

 

위 정도의 코드는 작성할 줄 알아야 springBoot에서의 코드가 간결해질것이다 만약 안쓴다고 가정하면 for문과 if문이 난무해서 굉장히 복잡해질것이다 이러한 사고능력을 늘리기위해선 한번에 가져올려하지말고 한개씩 구현해보면서 붙이며 만들어야한다.

 

서비스 코드 작성

그럼 원하는 데이터를 모두 뽑았으니 적용시키러 가보자

public interface SubscribeRepository extends JpaRepository<Subscribe, Integer> {

위는 우리가 계속 이용하던 SbuscribeRepository이다 여기서 방금 위에서 만든 코드를 사용할 수 없다 왜?

반환형이 Subscribe이기 때문이다 그럼 어디서 해야할까 서비스에서 네이티브 쿼리를 작성해야한다 바로해보자.

 

SubscribeService.java

서비스에서 어떻게 db에서 데이터를 받고 사용이 가능한걸까

만들었던 Repository인터페이스들은 모두 EntityManager을 구현해서 만들어진 구현체이다 그렇다보니 서비스에서 EntityManager을 구현시키면 되는것이라서 서비스에서도 사용 가능하게되는것이다.

  
    private final EntityManager em; // Repository는 EntityManager를 구현해서 만들어져 있는 구현체
	
	@Transactional(readOnly = true)
	public List<SubscribeDto> 구독리스트(int principalId, int pageUserId) {
		
		StringBuffer sb = new StringBuffer();
		sb.append("select u.id, u.username, u.profileImageUrl, "); // 모든 줄 마지막엔 띄어쓰기 해주기
		sb.append("if((select true from subscribe where fromUserId = ? AND toUserId = u.id),1,0) subscribeState, ");
		sb.append("if((? = u.id),1,0) equalUserState ");
		sb.append("from user u inner join subscribe s ");
		sb.append("on u.id = s.toUserId ");
		sb.append("where s.fromUserid = ? ");// 세키콜론 첨부하면 안됨
		
		// 2줄 ? principalId
		// 3줄 ? principalId
		// 6줄 ? pageUserId
		
		// import javaPersistence 
		Query query =em.createNativeQuery(sb.toString())
				.setParameter(1, principalId)
				.setParameter(2, principalId)
				.setParameter(3, pageUserId);
		// 위처럼 ? 자리에 값을 기입하는것을 바인딩이라고 한다.
		
		// 쿼리 실행
		JpaResultMapper result = new JpaResultMapper();
		List<SubscribeDto> subscribeDtos =  result.list(query, SubscribeDto.class);
		// 한건만 반화받을거면 uniqueResult함수사용
		// qlrm의존성이 알아서 매핑을 해준다
		
		return subscribeDtos;
	}

위처럼 원하는 자리에 ? 를 이용하여 바인딩을 한뒤 명령을 실행

JpaResultMapper의 list함수를 이용하여 왼쪽엔 쿼리를 실행시켜 결과를 받고 어떤 클래스의 형태로 반환 받을것인지 작성해준다.

만약 한건의 데이터만 반환 받는다면 uniqueResult함수를 사용한다. (list가 아닌)

 

원래는 Repository의 인터페이스를 이용해 결과를 반환받아 처리하였지만 구독같이 여러 테이블을 참조하고 자주 쓰이지 않을것 같은 클래스 데이터는 위와같이 Service에서 쿼리문을 바로 실행하여 결과를 반환받는게 효율적이다

 

JpaResultMapper은 스프링부트의 프레임워크가 아니라 의존성에 의해 생긴 외부 라이브러리이다

// qlrm이란 데이터베이스에서 result된 결과를 자바클래스에 매핑을 해준다.

import org.qlrm.mapper.JpaResultMapper;

없다면 mavenRepository에서 qlrm을 검색하여 받아 pom.xml에 추가해주자.

.Repository에서 int만 반환 받아서 사용했는데요? 그것은 제네릭타입안에 클래스,Integer 두개의 데이터 타입이 있습니다 그래서 가능한거였습니다.

 

위와같이 임의로 커스텀해서 만든 클래스를 데이터 채우는 경우는 EntityManager을 이용하여 바인딩과 버퍼로 쿼리를완성시킨뒤 JpaResultMapper로 결과를 담아와 사용하자.

 

확인해보기

postman으로요청을 해보니 값이 잘들어온 모습이다.