웹 개발 Spring Day4 Repository, Connection, PreparedStatement, ResultSet, Try-With-Resource

김지원·2022년 7월 30일
0

WebDevelop2

목록 보기
23/34

view를 담당하는 Repository를 생성하자.

view

@Repository

value : 저장소 이름
  • 해당 클래스가 저장소로 사용될 것임을 스프링 부트에게 알린다.
  • 데이터베이스에 접근할 수 있는 유일한 것.
  • 저장소는 종류가 많은데 그 중 DAO(Date Access Object) 형식으로 만들자.
  • 저장소가 가지는 모든 메서드는 DBMS의 키워드인 것이 좋다.(select, delete~~)

Dao

  • Dao 생성하고 value 로 이름을 붙여준다. Entity를 매개변수로 받는다.

  • 서비스에 add메서드 생성.

-> 의존성 주입

  • 서비스가 저장소(다오) 에 의존적이기 때문에 Dao에 대한 생성자만들고 @Autowired 걸어주면 의존성 주입이 된다.

이렇게 되면 InquiryService가 InquiryDao에 의존적이다 라고 말할 수 있다.

홈컨트롤러가 객체화 될 유일한 방법은 InquiryService를 객체화해야한다.
InquryService를 객체화하는 방법은 생성자를 만들어야한다.

다오 -> 서비스 -> 컨트롤러 순으로 객체화가 진행이된다.
그래서 autowired를 사용하고 필요한 의존성 객체화를 하도록 했다.

다오 | 서비스 | 컨트롤러 이 세개 사이에 움직이는 데이터들을 일일히 다 적어줄수 없기 때문에 주거니 받거니 하는 것들을 담은 Entity, Vo, Dto 를 사용한다.


컨트롤러 Post

  • inquiryService 의 add메서드 호출해서 전달받은 inquiry(Inquiry Entity 타입)객체를 담아 전달해주면 된다.

서비스에서 구현하는 것들은 많지 않은데 정규화 로직을 구현해준다.

  • DB에 해가 되는 값들은 서비스에서 다 쳐내야한다.
    정규화에 맞지 않으면 insert를 해주면 안된다!

  • Dao에서는 insert 되면 1, 안되었으면 0으로 값이 들어간다.
    즉, insert 된 갯수가 반환될 것이기 때문에 반환타입을 int로 설정하는 것이다.

  • return this.inquiryDao.insert(inquiry) > 0;
    이란 것에 만족하면 insert가 된 것이다.

다시 Dao로 넘어와서...

Connection

: Java ←→ DBMS 연결을 한다.

  • 얘가 없으면 java와 DB는 독립적이게 된다. 얘네를 연결한거 위로 작성한 코드가 돌아가게 된다.

PreparedStatement

: 실행한 쿼리(Query) 및 변수의 지정 및 접근

execute() 메서드는 boolean을 반환하는데, 
해당 실행 결과가 ResultSet 일 수 있는가에 대한 여부를 반환한다.
executeUpdate() 는 int를 반환하는데, 
해당 실행 결과가 영향을 미친 레코드의 개수를 반환한다.
executeQuery()는 ResultSet을 반환하는데 
해당 실행 결과(레코드들)를 담고 있다.

ResultSet

: 실행 결과 값을 포함. SELECT 쿼리에 대해서만 사용한다.


Java가 가지고 있는 JDBC를 DB와 연결하기 위해 최소한에 정보와 구현해야하는 인터페이스들을 JDBC가 가지고 있다.
JDBC위에 Connector을 올려서 연결을 하게 된다.

  • Connection을 직접 객체화하려면 이것들을 직접 다 구현해야되니 남이 만든 것을 가져다 사용하자. => 의존성 추가

https://mvnrepository.com 에서 의존성을 가지고 온다.

  • 3.0.6
  • pom.xml에 붙여넣고 동기화해준다.
  • 이렇게 되면 mariadb 가 주는 응답값에 대해 번역을 할 수 있게 된다.

JDBC (Java Database Connector)

JDBC는 DBMS와 얘기하는 방법을 모른다. DBMS 번역을 해서 JDBC에 전달해줄 것이 필요하다. DBMS의 종류에 따라 번역의 방법이 다 다른데 방금 추가한 의존성이 번역가의 역할을 하게 된다.


  • org.mariadb.jdbc 자동완성 사용한다.

  • Class.forName("org.mariadb.jdbc.Driver")
    : 이렇게 쓰는 것은 JDBC 및 DBMS 간의 번역을 담당할 커넥터 드라이버로 MariaDB Java Client 드라이버를 쓰겠다는 의미이다.

  • forName에서 Unhandled exception: java.lang.ClassNotFoundException 이라는 오류가 발생하는데 ClassNotFoundException이라는 오류를 던질 수 있다는 시그니처를 가지고 있기 때문에 뜨는 오류이다.

  • Dao에서 ClassNotFoundException를 던져준다. 그렇게 되면 Dao에 의존적인 서비스가(준비가 되어있지 않음) 행복해 하지 않는다.
  • 서비스에도 똑같이 ClassNotFoundException를 던저준다.
  • 똑같은 이유로 Controller에서도 예외를 던져주면 오류는 사라진다.

  • Connection connection = DriverManager.getConnection();
    여기에 전달인자를 3개(url, user:사용자 이름, password)를 준다.

  • "jdbc:mariadb://localhost:3306/" jdbc를 써서 이 종류(mariadb)의 DB를 사용해서 접근하겠다는 의미로 적어주는 것이다.
  • getConnection 또한 SQLExceotion 예외를 던질수있다는 시그니처가 있기 때문에 3군데서 예외를 던져주자.

다시 Dao로 와서!

  • PreparedStatement prepareStatement = connection.prepareStatement("");
    이 문자열 자리에 쿼리를 작성한다.

  • console에서 INSERT문을 작성하여 세미콜론을 포함하지 않은 내용을 복사한다.

  • 복사한 내용을 쿼리가 들어갈 자리에서 엔터를 친 후 복사해주자.
    그리고 아래에 ?에 들어갈 1~3번쨰 값을 적어주면 된다.

그러고 값을 불러올 차례다.
첫번째 순서인 날짜부터 짜보자.

  • (정수, 실제 값)으로 적으면 되는데 여기쓰는 정수는 몇번째 ?에 값을 집어넣을 것인지 대한 인덱스이다.

  • Date를 원하는 문자열로 쓸 수 있는 SimpleDateFormat을 사용한다.
String createdAt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(inquiry.getCreatedAt());

직역하자면 새로운 문자열 createdAt 에 문의내용을 보낸 날짜를
"yyyy-MM-dd HH:mm:ss") 형태로 만들어 달라는 의미가 된다.
( format : 만들어달라는 의미 )

  • 방법이 한가지 더 있는데 우리는 이방법을 사용한다.
  • new Date(); 는 SQLDate로 객체화해서 사용한다.

  • executeUpdate() 해당 실행 결과가 영향을 미친 레코드의 개수를 반환한다.
    그리고 insert 메서드가 반환하는 타입도 int이기 때문에 바로 return 때리면 된다.

  • 문의 내용을 남기면 DB에 뜨면 성공.
  • 그런데 년 / 월 / 일은 들어가는데 시 / 분 / 초가 들어가지 앟는다.
  • Date를 해서 년/월/일만 들어왔던 것이다. Timestamp 로 변경.

  • 변수로 분리해서 return을 하자.

  • preparedStatement.executeUpdate(); 처럼 객체를 만들어서 사용을 헀으면 무조건 close를 해줘야한다.

close를 해주지 않으면 그 connection은 열려있는 상태로 유지가 되고 차곡 차곡 쌓이다 보면 열려있는 connection의 갯수가 점점 늘어나게 될 것이다.
늘어나다가 Db의 max_connecttions라는 값(지금 151)을 초과하게 되면 ( ==151번 열리고 그 다음 부터) 더 이상 커넥션이 만들어지지 않고 오류가 터진다.

connection에 들어오면 1로 올라가고 close를 해줘야 0으로 내려가게 된다.
메모리 확보 차원에서도 좋다.

메서드 실행 중간에 예외가 터지면 그 자리에서 메서드 실행이 종료시켜서 아래의 코드가 실행되지 않는다. 그에 따라 close 또한 실행될 기회가 없으며 그말인 즉슨 객체가 유출이 되고 예외가 터질때 마다 가용 connection이 한개 씩 점점 줄어들게 된다.

따라서 모든 상황에서 close가 호출될 수 있도록 유도를 해야한다.
그때 사용하는 것이 Try-With-Resource 이다.


우리가 알고있는 try-catch처럼 사용하는데 try() 안에 Closeable을 상속받는 객체를 집어넣는다.

Try-With-Resource

try (AutoCloseable instance) {
	...
}
  • AutoCloseable 인터페이스를 구현하는 객체에 대해 Try문이 종료될 경우 반드시 해당 객체에 대해 close() 메서드를 호출함을 보증함.
    ( : instance에 적는 객체에 대해서 try문이 끝나면 적어논 객체의 close를 호출하는 것을 보증한다.)

  • AutoCloseable 상속받고 있으며, AutoCloseable 구현하기 때문에 Connection을 사용할 수 있는 것이다.
    try(String...) : 이렇게 작성하는 것은 안된다는 의미이다.

❗️ try문이 끝나게 되거나 중간에 예외가 발생해서 메서드를 빠져나가더라도 여기 적어놓 Connection 객체의 AutoCloseable 객체의 close를 반드시 실행한다.

  • AutoCloseable 인터페이스

  • throws가 명시되어있기 때문에 catch가 없어도 괜찮다.
  • 적을 순 있지만 의미가 없다.

🐣 여기서 했갈리는게 try가 두개가 쓰였는데
첫번째 try는 mariadb의 값을 번역하기위해 jdbc.Driver을 사용하기 위해 Connection이라는 인터페이스를 사용했다. 이 인터페이스를 사용하기위해서는 close() 메서드를 무조건 호출해줘야하는데 이것을 하기위해 try를 사용했고
두번째 try는 예외를 잡아주기 위해서 사용한 try이다.


  • 얘네는 select를 하든 update를 하던 겹치는 부분이 된다. 어딜가든 똑같이 쓰일 것이기 때문에 따로 뺴는게 좋을 것 같다.

-> DatabaseUtil

  • DatabaseUtil클래스는 더 이상 상속받을 수 없음을 알려주기 위해 final class으로 만들고 접근제한자를 private으로 막는다 = 더 이상 객체화하지 못한다.
  • getConnection 메서드에 담아준다.

  • 호출을 함으로써 코드를 줄인다.

getConnection의 예외를 보면

  • SQLException 은 getConnection메서드를 호출하기위해 사용.
  • ClassNotFoundException 은 forName메서드를 호출하기위해 사용

🐣 여기서 헷갈렸던 점.

Dao의 insert메서드와 DatabaseUtil의 getConnection메서드 는 같은 예외를 던저주고 있다.
insert메서드에서는 getConnection메서드를 호출하고 있는데 왜 똑같은 에외를 던져줘야하는 것인가?
=> 호출을 했으면 동일한 예외를 감당할 수 있어야하기 때문에 똑같은 예외를 던져주어야하기 때문이다.

  • getConnection 메서드

SELECT

여기의 select는 InquiryEntity 객체 하나가 Inquiries 레코드이다.
이것을 다 가지고 오려면 InquiryEntity 배열 혹은 InquiryEntity 제네릭 타입의 ArrayList / List 면 될것 같다.

  • List<InquiryEntity> result = new ArrayList<>(); 객체화를 해주고 return result 해준다.
  • SELECT SQL 작성

setInt, setString... 해야될까? 아니다. (? 없다.)
값을 집어넣을게 없으니 필요없다.

  • ResultSet 객체 사용한다.
  • ResultSet을 반환하는 메서드 : preparedStatement.executeQuery()

  • resultSet.next() 의 결과가 true가 나올 때 (while문 사용)

  • ""에 적어주는 값은 위의 SELECT 쿼리에 적은 별명과 같아야한다.
    별명으로 값을 가져오겠다는 의미이다.

  • InquiryEntity 를 이렇게 객체화만 해두면 안되고 result에 add해줘야한다.

  • InquiryService

컨트롤러에서 받아서 컨틀롤러에서 addObject해서 index.html for문 돌려서 하기 전에 서비스에서 저장소 로직이 잘 작동되는지 test를 먼저해보자.


TEST

  • junit 의존성 추가

  • Assert.assertSame( inquiries.size(), 3 );

  • 숫자 다르게 적으면 이렇게 된다.

  • 주석처리된거에서 아닌 것으로 바꾸면 빨간색 오류가 사라진다.
  • 이렇게 뜬다.

오류를 잡기위해서 사용하는 것이 테스트다.
테스트에서 성공한 것들을 개발하는 곳에 적용시키면 된다.

참고

profile
Software Developer : -)

0개의 댓글