[TIL] Day23 - JDBC / CRUD in Java

JIONY·2022년 8월 28일

TIL - DBMS & SQL

목록 보기
4/5
post-thumbnail

외부 라이브러리 추가도 해보구.. 이클립스에서 뭐라뭐라하면 디비에서 짜잔~!되는 거 신기방기구..


자바-오라클 연동

  • JDBC(Java DataBase Connectivity): DB에 접근할 수 있는 자바 API

준비

  • 이클립스에서 자바 프로젝트를 생성

    오라클 드라이버 & 라이브러리를 buildPath에 추가


라이브러리 사용이 필요한 이유

  • 로그인 > 외부 프로그램에서 명령문 작성 > 오라클에서 실행
  • 자바에서는 위 과정을 직접 처리해야 함
    • 외부 라이브러리 사용 필요
    • Spring 학습 예정이므로 이와 가장 유사하게 사용 가능한 라이브러리를 추가

라이브러리 추가

  1. 라이브러리 파일 다운로드 > 압축 해제
  2. 새로 생성한 자바 프로젝트 아래 library용 폴더 생성
  3. library 폴더에 .jar 파일을 모두 copy
  4. 자바 프로젝트 > 우클릭 > Build Path > Configure Build Path > Libraries 탭 이동
  5. Classpath > Add JAR > library 폴더 내 파일 전체 선택 > OK > Apply
  6. 프로젝트 아래에 Referenced Libraries 생김 > commit
[참고]
- 라이브러리를 만든 업체를 가야 api 문서 볼 수 있음
- .jar: java archive 자바로 만들어진 프로그램
- 모듈패스: 모듈에서만 쓸 수 있음
- 클래스패스: 클래스 전체가 쓸 수 있음
- 프로젝트 만들 때마다 라이브러리 import 필요


진행 순서

  1. 데이터베이스 로그인

    1. 로그인을 도와주는 도구를 생성(Spring 제공)
      • 드라이버 클래스/접속/계정/비밀번호 정보를 설정(setter 메소드)
    2. 구문 실행 도구 생성(Spring 제공)
  2. SQL문 준비

    insert into guest_book(no, name, memo)
    values(6, '피카츄', '하이');
  3. 구문 전송 및 실행

  4. 데이터베이스 로그아웃(생략)

//목표: c##academy 계정의 fifa_ranking 테이블에 데이터 insert
//1. 데이터베이스 로그인
	//a. 로그인 도구 생성
	//SQL Developer에서 접속 설정하는 것을 직접 입력하는 것
	DriverManagerDataSource dataSource = new DriverManagerDataSource();
	dataSource.setUsername("khacademy");
	dataSource.setPassword("khacademy");
	dataSource.setUrl("jdbc:oracle:thin:@localhost:1521:xe");
	dataSource.setDriverClassName("oracle.jdbc.OracleDriver");
		
	//b. 구문 실행 도구 생성
	//template은 dataSource를 통해 로그인 정보를, sql을 통해 실행할 구문을 전달받음
	JdbcTemplate template = new JdbcTemplate(dataSource);
		
//2. 구문 준비
	//sql 구문을 자바가 sql문으로 알아들을 수 있도록 문자열로 저장
	//[주의] 줄바꿈 시 sql문 공백 위치 확인 필요
	String sql = "insert into guest_book(no, name, memo) "
						+ "values(6, '피카츄', '하이')";
		
//3. 구문 전송 및 실행
	template.update(sql);
	
//확인용 출력 구문	
//System.out.println("완료");


모듈화

  • 로그인과 구문실행 도구를 생성하는 코드는 매번 같은 내용을 타이핑 해야 함. 다른 클래스에 메소드화해서 언제든 불러낼 수 있도록 하면 전체 코드가 간소화됨

    /*
    - 접근제한: public
    - 객체 없이 사용: static
    - 반환형: JdbcTemplate
        - JdbcTemplate 타입을 반환해서 DB에 넘겨야 함
    - 준비물(매개변수): 변할 수 있는 데이터의 범위에 따라 달라짐(계정 정보, 디비 종류 등)
    */
    package util;
    
    import org.springframework.jdbc.core.JdbcTemplate;
    import org.springframework.jdbc.datasource.DriverManagerDataSource;
    
    public class JdbcUtil {
        public static JdbcTemplate getTemplate() {
            DriverManagerDataSource dataSource = new DriverManagerDataSource();
            dataSource.setUsername("c##academy");
            dataSource.setPassword("c##academy");
            dataSource.setUrl("jdbc:oracle:thin:@localhost:1521:xe");
            dataSource.setDriverClassName("oracle.jdbc.OracleDriver");
    
            JdbcTemplate template = new JdbcTemplate(dataSource);
    
            return template;
        }
    }
  • main에서 JdbcUtil 메소드 호출(코드 간소화)

    JdbcTemplate template = JdbcUtil.getTemplate();
    
    String sql = "";
    Object[] param = {};
    
    template.update(sql, param);




데이터 삽입 / 수정 / 삭제

  • 변수를 생성해 데이터가 문자열 sql에 전달될 수 있도록 함
  • [참고] 자바는 자동 커밋됨

정적 쿼리

  • 정적 바인딩: Object의 타입이 컴파일러에 의해 컴파일 타임에 결정되는 것

  • 문자열 더하기 연산 사용
    - String sql 구문에 필요한 데이터를 변수로 치환

    //변수
    
    int no = 6;
    String name = "피카츄";
    String name = "하이";
    
    String sql = "insert into guest_book(no, name, memo) "
                            + "values("+ no +", '"+ name +"', '"+ memo"')";

단점

  • SQL Injection 공격에 취약(대입할 구문을 문자열이 아니라 sql 구문으로 인식하기 때문)
  • 자료형에 따라 “”를 직접 작성해야 함

동적 쿼리

  • 동적 바인딩: Object의 타입이 런타임 중에 결정되는 것

  • sql문을 미완성으로 작성하고 입력받을 데이터를 ?(플레이스 홀더: 자리 잡는 역할) 처리

    • 변수 바인딩을 통해 데이터를 입력받음
  • 배열 생성

    • 여러 개의 데이터를 받을 수 있음
    • 자료형이 모두 다름 → Object 타입으로 생성
  • [주의] Object[] param 배열 데이터와 위치홀더의 데이터 순서가 같아야 함

    String sql = "insert into guest_book(no, name, memo) "
                            + "values(?, ?, ?)";
    
    //Object[] param = new Object[] {no, name, memo};
    Object[] param = {no, name, memo}; //약식 표현 가능
    
    // 구문 전송 및 실행
            template.update(sql, param);

장점

  • 정적 쿼리의 단점을 해소할 수 있음


데이터 삽입

  • sql 문자열에 insert into 구문을 대입

데이터 수정

  • sql 문자열에 update 구문을 대입

  • 수정해야 하는 값을 홀더 처리

    String sql = "UPDATE GUEST_BOOK SET NAME = ? MEMO = ? WHERE NO = ?";
    Object[] param = {name, memo, no};

데이터 삭제

  • sql 문자열에 delete 구문을 대입
  • 삭제해야 하는 값을 홀더 처리
  • 주로 PK를 이용해 삭제

DML 수행 성공 여부 확인

  • update 메소드의 반환형: int

  • 업데이트된 행의 개수를 반환
  • 0이면 실패
  • insert into는 항상 1을 반환하기 때문에 사용하지 않음

  • 구문 실행 코드를 변수화해서 반환값을 출력
    int result = template.update(sql, param);
    // System.out.println("result = " + result);
    		
    if(result > 0) {
    	System.out.println("변경 성공");
    }else {
    	System.out.println("해당 데이터가 테이블에 없음");
    }


데이터 조회

  • INSERT, UPDATE, DELETE와 같은 방법으로 수행 불가
    • DB에는 표 형태로 저장되어 있지만 자바에서는 객체 형태로 봐야 하기 때문
    • 테이블을 객체 형태로 변환해서 List로 추출해야 함
  • SELECT절 실행 결과 = ResultSet
    • ResultSet: 테이블의 데이터를 복사해서 가져온 복사본
    • 이 결과 집합에서 한 줄씩 데이터를 읽어와야 함
    • 테이블의 컬럼을 객체의 변수와 매핑해줘야 함(setter())
  • JdbcTemplate 클래스가 제공하는 query() 활용
    • query(): SELECT 쿼리 실행을 위한 메소드, 여러 개의 객체를 반환

진행 과정

  1. DTO(Data Transfer Object) 클래스 생성
    a. 데이터베이스 테이블 안에 있는 한 개의 행 데이터를 저장하기 위한 클래스
    b. DB 컬럼을 변수화해서 필드 선언
    c. getter & setter, 생성자, 출력용 toString() 생성
    d. SQL 쿼리에서도 WHERE절로 조건을 설정하기는 하나, setter에도 조건을 설정하면 더 확실하게 검증된 데이터만 보유할 수 있음(안전성 향상)

  2. 행별 데이터를 복사해서 저장할 DTO 클래스의 객체를 생성

  3. RowMapper 객체 생성(여러 개의 값을 반환받기 위함)
    a. ResultSet에서 한 행의 데이터를 읽어와 setter()를 통해 객체에 저장(복사)
    b. setter()를 통해 변수와 DB 컬럼을 매핑해줬기 때문에 RowMapper가 이 과정을 대신 처리해줄 수 있음
    c. idx(rowNum, 행의 개수)만큼 반복(ResultSet의 행을 알아서 세어줌)
    d. [참고] RowMapper는 인터페이스이므로 반드시 추상 메소드 mapRow()를 재정의해야 하는데, 객체를 하나만 만들기 위해 클래스를 추가로 생성하는 것은 비효율적임. 익명 중첩 클래스를 통해 객체를 만들면서 즉석에서 클래스를 구성해 특정 기능을 재정의하는 방식을 사용.

  4. 행별 데이터를 하나로 모아서 저장할 List 객체를 생성

  5. SELECT 쿼리 실행 결과를 List에 저장
    a. SQL 쿼리에서 ORDER BY로 정렬을 하는 경우에 대비해 순서가 있는 자료구조인 List를 사용
    b. query() 인자값으로 sql, RowMapper객체(mapper), 바인딩변수에 세팅될 값(param) 을 설정

  6. List를 통해 원하는 내용을 출력

public class CountryDto {
	private int ranking;
	private String nation;
	private double score;
	//getter & setter, 생성자, toString() 생략
}
public class Test01 {
	public static void main(String[] args) {
		JdbcTemplate template = JdbcUtil.getTemplate();
				
		String sql = "SELECT * FROM FIFA_RANKING"; //(ResultSet)
				
		//테이블을 객체로 변경하는 방법이 작성된 RowMapper 객체를 구현
		//객체를 생성하고자 하는 클래스를 제네릭에 작성		
		RowMapper<CountryDto> mapper = new RowMapper<CountryDto>() {
			@Override
			//ResultSet을 받아 CountryDto를 만들기
			//= rs에 들어있는 데이터를 꺼내서 CountryDto로 복사
			public CountryDto mapRow(ResultSet rs, int idx) throws SQLException {
				CountryDto dto = new Country();
				//rs에는 한 줄의 데이터가 들어있음
				//rs에 들어있는 데이터 중 ranking 컬럼의 값을 get(꺼내기)해서 ranking 필드에 주입
				dto.setRanking(rs.getInt("ranking"));
				dto.setNation(rs.getString("nation"));
				dto.setScore(rs.getDouble("score"));
							
				return dto; //rs를 dto로 복사한 결과 반환
			}
		};
				
		//List<Country> list = template.query(sql, 변경 방법)		
		List<CountryDto> list = template.query(sql, mapper);	
		for(CountryDto dto : list) {
			System.out.println(dto);
		}
	}
}

RowMapper 람다식

  • RowMapper는 메소드가 하나 뿐인 함수형 인터페이스이므로 람다식으로 표현할 수 있음
RowMapper<CountryDto> mapper = (rs, idx) -> {
	CountryDto dto = new Country();
	dto.setRanking(rs.getInt("ranking"));
	dto.setNation(rs.getString("nation"));
	dto.setScore(rs.getDouble("score"));
				
	return dto;
};

0개의 댓글