You will be fine

<Spring & JPA 웹서비스 만들기 > 구현 (3) - Redis 자동완성

by BFine
반응형

가. 검색어 자동완성 기능(AutoComplete)

 a. 어떻게 구현할까?

  -  거창하게 쓰긴 했지만 사실 단순 검색하는 기능으로 만들었다.

  -  제대로 하려면 한글 형태소 분석하고 성능 문제도 해결 해야하지만

        Redis로 하는 자료들이 없기도 하고 JPA 외에 시간을 쓰는 것 같아서 단순 검색으로 만들었다.  

  -  Redis로 검색어 자동완성(autoComplete)을 만들어보자 

 

 b. 테스트 작성

@SpringBootTest
public class UserServiceTest {
    
    @Autowired
    RedisTemplate<String,Object> redisTemplate;


    @Test
    public void 유저이름검색테스트() {

        String key = "USER_NAME";
       
        HashOperations<String,String,Integer> hashOperations= 
                                     redisTemplate.opsForHash();
                                     
        Set<String> a = hashOperations.keys(key);

        hashOperations.put(key, "김철수", 0);
        hashOperations.put(key, "김수철", 0);

        ScanOptions scanOptions = ScanOptions.scanOptions().match("김*").build();
        Cursor<Entry<String,Integer>> cursor= hashOperations.scan(key, scanOptions);
        List<String> searchList = new ArrayList<>();

        while(cursor.hasNext()){
            Entry<String,Integer> entry = cursor.next();
            searchList.add(entry.getKey());
        }

        assertEquals(searchList.size(), 2);
    }
}

  -  HashOperation을 이용해서 Hash Key에는 이름, value는 0(이부분은 ID값)으로 처리했다.

  -  검색어가 들어오면 Redis Scan으로 몽땅 검색해서 가져온다.

 

 c. 서비스 작성

@Service
@RequiredArgsConstructor
public class UserService {
    
    private final UserRepository userRepository;
    private final RedisTemplate<String,Object> redisTemplate;

    private static final String SEARCH_KEY = "USER_NAME";
    private static final String ASTERISK  = "*";
    private static final int TIME_LIMIT = 5;

    public List<String> getSearchNameList(RequestDTO requestDTO){

        HashOperations<String,String,Long> hashOperations= redisTemplate.opsForHash();
        
        if(redisTemplate.getExpire(SEARCH_KEY) < 0){
            List<User> nameList = userRepository.findAll();
            Map<String,Long> nameDataMap = nameList.stream()
            .collect(Collectors.toMap(User::getName,User::getId));
            hashOperations.putAll(SEARCH_KEY, nameDataMap);
            redisTemplate.expire(SEARCH_KEY,TIME_LIMIT,TimeUnit.SECONDS);
        }
               
        ScanOptions scanOptions = ScanOptions.scanOptions()
        							.match(requestDTO.getName()
                                    +ASTERISK).build();
                                    
        Cursor<Entry<String,Long>> cursor= 
        					hashOperations.scan(SEARCH_KEY, scanOptions);
        List<String> searchList = new ArrayList<>();

        while(cursor.hasNext()){
            Entry<String,Long> entry = cursor.next();
            searchList.add(entry.getKey());
        }

        return searchList;
    }
}

  -  실제 코드에서는 DB에서 모든 User를 가져와서 Redis에 저장하고 5초동안 유지하는 해괴한 코드를 만들었다.

  -  테스트 해보고 싶어서 만들었고 5초인 이유는 새로운 유저에 대한 정보 그리고 타이핑 하는 동안은 

        DB에 데이터를 가져와서 Redis 처리하는 부분 없이 Redis에서만 가져오도록 했다.

  -  저 value 부분에 KEY의 형태소를 리스트 형태로 저장하면 형태소 검색이 되지 않을까란 생각도 든다.

  -  추후에 검색엔진(엘라스틱서치, 솔라)로 만들어 봐야겠다.

 

나. 동작화면

 a. 설정하기 부분

  -  Keyup 이벤트를 걸어서 모든 입력에 대해서 서버로 요청하여 이름목록을 가져오도록 만들었다.

  

반응형

블로그의 정보

57개월 BackEnd

BFine

활동하기