[TIL] Day24 - SELECT in Java / POJO

JIONY·2022년 8월 28일

TIL - DBMS & SQL

목록 보기
5/5
post-thumbnail

.class 이자식.. 여기저기서 짜깁기해서 이해하느라 힘들었다.. 아 근데 RowMapper랑 ResultSeltExtractor 비교가 더 오래걸림ㅠㅠㅋㅋ 자바에서 DML 처리하는 게 익숙해질 수 있도록 예제를 많이 주셔서 오전 시간 대부분을 문풀에 썼더니 시간이 빨리감. 처음 두 문제에서 계속 에러가 났는데 SQL Developer에서 INSERT에 대한 Commit을 안한 게 문제였음 휴 ^^.. 아니 왜 안돼!!!를 한 5천 번 하니까 발견함 ㅋㅋㅋ


문자열 유사 검색

INSTR()

  • 탐색할 문자열을 바인딩 변수 ?로 홀더 처리
//문자열 유사 검색 INSTR()
JdbcTemplate template = JdbcUtil.getTemplate();
		
String sql = "SELECT * FROM POCKET_MONSTER " 
							+ "WHERE INSTR(NAME, ?) > 0";
Object[] param = {"이상해"};
		
RowMapper<PocketMonsterDto> mapper = (rs, idx) -> {
	PocketMonsterDto dto = new PocketMonsterDto();
	dto.setNo(rs.getInt("no"));
	dto.setName(rs.getString("name"));
	dto.setType(rs.getString("type"));
	return dto;
};
		
List<PocketMonsterDto> list = template.query(sql, mapper, param);
//sql을 먼저 써야함. mapper, param은 순서 바뀌어도 됨
System.out.println("검색 결과 : " + list.size() + "개");
for(PocketMonsterDto dto : list) {
	System.out.println(dto);
}

LIKE

  • 잘못된 홀더 사용 예시
    SELECT * FROM POCKET_MONSTER WHERE NAME LIKE '%?%
    • 홀더가 문자열로 인식되어버리는 문제 발생
    • 문자열과 홀더를 분리하고 문자열 더하기 연산을 사용해서 해결
    • 오라클 문자열 연결 연산자 : || → ‘%’||?||’%’
  • SQL 쿼리만 달라지고, 나머지는 INSTR() 예시코드와 동일한 방식으로 수행
//문자열 유사 검색 LIKE
String sql = "SELECT QUERY: SELECT * FROM POCKET_MONSTER "
							+ "WHERE NAME LIKE '%'||'?'||'%’";
Object[] param = {"이상해"};

집계함수 조회

  • 집계함수는 ResultSet이 1행 1열 (데이터가 1개)
    - queryForObject() 사용

queryForObject()

  • 한 개의 컬럼만 선택할 때
    • 인자1: SQL문(String 변수에 담아 전달)
    • 인자2: 컬럼을 읽어올 때 사용할 타입을 지정
      • Class<T> requiredType(ex. Integer.class)
      • 참고1) 원시형과 래퍼 클래스 간의 오토박싱이 가능해서 count와 같이 null이 나올 수 없는 집계함수의 결과를 조회할 때는 원시형으로 타입을 지정할 수 있음 (max와 같이 null이 나올 수 있는 경우에 대비해 래퍼 클래스 타입을 사용하면 안전하므로 이 방식을 권장)
      • 참고2) 인자를 래퍼 클래스 타입으로 지정할 경우, 결과를 저장할 변수도 래퍼 클래스 타입으로 선언해야 함
  • 두 개 이상의 컬럼을 선택할 때:
    • 인자1: SQL문(String 변수에 담아 전달)
    • 인자2: RowMapper <T> rowMapper
  • RowMapper 호출 시점에 자동으로 첫 번째 행을 가리키고 있으므로 rs.next()를 호출할 필요 없음
  • 결과값이 하나가 아니라면 예외를 던짐(EmptyResultDataAccessException)
    • 예외처리는 성능 저하 이슈를 야기할 수 있음
JdbcTemplate template = JdbcUtil.getTemplate();
		
String sql = "SELECT COUNT(*) FROM MUSIC";
		
//읽어올 데이터의 자료형을 인자로 넣어야 함
int count = template.queryForObject(sql, Integer.class);
		
System.out.println("count = " + count);

* https://roadofdevelopment.tistory.com/49



[참고] Class 클래스

  • 리플렉션(Reflection): 객체를 통해 클래스의 정보를 분석해내는 프로그램 기법
    • 클래스에 대한 정보가 없다면 그 클래스의 메소드를 실행할 수 없는데, 리플렉션을 활용하면 클래스 파일의 위치나 이름만으로 class 파일에 대한 정보를 읽어와 내가 그 클래스에 대한 정보가 없더라도 접근 할 수 있음. 객체 생성도 가능
  • Class 클래스: 리플렉션의 기초가 되는 클래스. 자바에서 사용되는 클래스들에 대한 구조를 가지고 있음
    • 클래스의 구조 자체를 하나의 클래스로 표현해놓은 클래스
    • 리플렉션 클래스들을 반환하는 메소드들을 제공 (아래 이미지에서 반환형인 Field, Method, Package, Constructor 등은 모두 java.lang.reflect 패키지에서 제공해주는 클래스임을 알 수 있음)

  • 클래스명.class: 이 형태로 호출 시, 해당 클래스의 정보를 담은 Class 클래스가 반환됨

* https://joont.tistory.com/165

* https://nam-ki-bok.github.io/java/Java_reflection/



단일(상세) 조회

  • PK를 조건으로 설정해 하나의 결과값을 추출하고 싶을 때 사용
    • 결과값이 0 또는 1개
  • 항목이 존재하지 않을 경우에 대한 코드를 구현해야 함(if-else)
    • EmptyResultDataAccessException 예외를 발생시키거나 출력할 에러메시지를 설정
int musicNo = 1; //이 노래가 있을지 없을지는 모르지만, 있으면 결과가 1개 나옴
		
JdbcTemplate template = JdbcUtil.getTemplate();
		
String sql = "SELECT * FROM MUSIC WHERE MUSIC_NO = ?";
Object[] param = {musicNo};
		
ResultSetExtractor<MusicDto> extractor = new ResultSetExtractor<MusicDto>() {
	//익명중첩클래스로 추출 방법을 알려줘야 함
	//매개변수에 idx(rowNum) 불필요(데이터가 1개이므로)
	@Override
	public MusicDto extractData(ResultSet rs)
    throws SQLException, DataAccessException {
		//데이터가 있는 경우와 없는 경우를 구분해서 처리하는 방법을 알려줌
		if(rs.next()) { //rs에 데이터가 있으면
			MusicDto dto = new MusicDto();
			dto.setMusicNo(rs.getInt("music_no"));
			dto.setMusicArtist(rs.getString("music_artist"));
			dto.setMusicAlbum(rs.getString("music_album"));
			dto.setMusicPlay(rs.getInt("music_play"));
			dto.setReleaseTime(rs.getString("release_time"));
			return dto;
		}else { //rs에 데이터가 없으면					
			return null;
		}
	}
			
};

//sql 구문 실행 시 얻을 것으로 기대되는 자료형은? MusicDto
//[자료형] List: 결과가 여러 개, DTO: 결과가 1개
MusicDto musicDto = template.query(sql, extractor, param);
if(musicDto == null) {
	System.out.println("존재하지 않는 음원 번호");
}else {			
	System.out.println(musicDto);
}

  • RowMapper로도 할 수 있지만 비추천
    • 용도에 맞는 도구를 사용할 것을 권장함(List는 결과가 0개 이상, 객체는 결과가 0 또는 1개)

    • 결과값이 1개인 경우 List로 반환되는 것이 불필요

    • 결과값이 null(검색 결과가 0건)인 경우에는 mapRow 메서드가 호출되기도 전에 스프링 프레임워크가 EmptyResultDataAccessException 예외를 발생시킴. (따라서 RowMapper를 사용할 때는 조회 결과가 반드시 1건 이상 존재한다고 가정하고 구현하면 됨.)

    • 만약 null인 상황에 대해 직접 컨트롤하고 싶다면 상세 코드를 추가로 작성해야 함.

      List<MusicDto> list = template.query(sql, mapper, param);
      //MusicDto musicDto = list.get(0) or null;
      MusicDto musicDto; //객체를 선언만 하고 조건문으로 처리
      if(list.isEmpty()) {
      	musicDto = null;
      }
      else { 
      	musicDto = list.get(0);
      }
      System.out.println(musicDto);


SQL to POJO

POJO

  • Plain Old Java Object
  • 주로 특정 자바 모델 / 기능 / 프레임워크 등을 따르지 않은 자바 오브젝트를 지칭하는 말
  • Spring JDBC는 처리 결과값을 자바가 기본적으로 제공하는 데이터 타입이나 Map, List 같은 컬렉션 타입으로 반환
  • 애플리케이션을 개발 시, 해당 비즈니스에 맞는 데이터 타입을 POJO 형태로 만들어 쓰는 경향이 있기 때문에 데이터를 가공해야 함
    • 원하는 형태로 변환하기 쉽도록 Spring에서 세 가지 인터페이스를 제공함

RowMapper

  • JDBC의 ResultSet을 순차적으로 읽으면서 원하는 POJO 형태로 매핑할 때 사용
  • List 타입으로 결과를 반환하고 싶을 때 적합
  • ResultSet을 다루는 메소드: mapRow(ResultSet rs, int rowNum)
  • ResultSetExtractor와의 차이점
    • 메소드를 SQL 실행결과의 행 수만큼 호출
    • ResultSet의 한 행이 하나의 POJO 객체로 변환되고, ResultSet이 다음 행으로 넘어가는 커서 제어를 스프링 프레임워크가 대신함

ResultSetExtractor

  • JDBC의 ResultSet을 자유롭게 제어하며 원하는 POJO형태로 매핑할 때 사용
  • 임의 타입으로 결과를 반환하고 싶을 때 적합(제네릭 타입을 파라미터로 가짐)
  • ResultSet을 다루는 메소드: extractData(ResultSet rs)
  • RowMapper와의 차이점
    • 메소드를 한 번만 호출함(ResultSet의 모든 행을 다룰 때까지 변환 처리를 반복 if(rs.next()) or while(rs.next()))
    • ResultSet의 여러 행 사이를 자유롭게 이동할 수 있고, 구현 클래스에서 ResultSet의 next() 메소드를 호출해 ResultSet의 여러 행에서 필요한 값을 꺼내서 하나의 POJO 객체에 채워넣을 수 있음
    • 항목이 존재하지 않을 경우에 대한 코드를 구현해야 함

RowCallbackHandler

  • 반환값이 없음
  • ResultSet에서 읽은 데이터를 파일 형태로 출력하거나, 조회된 데이터를 검증하는 용도

* https://changrea.io/spring/spring-jdbc/


0개의 댓글