ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 11. Query문을 통한 CRUD 구현
    Back-end Developer/Spring Framework, 설정 및 실습 2019. 1. 7. 16:32

    어떤 포스팅에서 'CRUD'에 대한 얘기를 가볍게 했던거 같은데, 다시 살짝 맛보고 실제로 구현해 봅시다.

    CRUD는 Create, Read, Update, Delete 요 네개를 의미합니다.


    각각 설정할 메소드를 정해보면, 아래와 같이 생각 해볼 수 있습니다.

     Create

     Insert 

     Read

     Select 

     Update

     Update 

     Delete

     Delete


    Create는 새로 생성을 한다는 의미이고, Update는 수정의 의미가 강하죠.

    근데 조금 생각해 보시면 알겠지만, 결국은 같은 기능을 수행합니다.

    구조상으로는 따로 정의하는게 맞지만 메소드 내부는 거의 동일 할 것 같아요.

    혹시나 코드를 보고 헷갈리거나 잔상이라고 여기지 마시길 바랍니다. ^___^



    Select Method 생성 (R: read)


    데이터 수정이나 생성 코드 보다는 간단하게 테이블에 아무런 영향 없이

    간단히 내부에 저장된 데이터를 확인 할 수 있는 Select 메소드 부터 정의해볼게요.


    OfferDAO 클래스에서, Offer 객체를 가져와서 해당 테이블에 저장되어있는 사용자의 이름을

    가져오는 메소드를 하나 정의해주세요. 이름은 getOffer로 설정하겠습니다.

    데이터 하나만 가져오는 방법과 여러개를 가져오는 방법 두 가지를 차례로 해볼게요.

    하나만 가져올 땐 Offer를 반환하면 될 것 같구요. 여러개라면 List에 Offer들을 담아오면 될 것 같아요.


     OfferDAO.java, getOffer 메소드(Single)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    public Offer getOffer(String name) {
            String sqlStatement = "SELECT * from Offers WHERE name = ?";
            
        return jdbcTemplateObject.queryForObject(sqlStatement, new Object[] {name}, 
                new RowMapper<Offer>() {
                    public Offer mapRow(ResultSet res, int rowNumber) throws SQLException {
                        Offer offer = new Offer();
                    
                        offer.setId(res.getInt("id"));
                        offer.setName(res.getString("name"));
                        offer.setEmail(res.getString("email"));
                        offer.setText(res.getString("text"));
                        
                        return offer;
                    }
        });        // Anonymous Class
    }
     
    cs


    쿼리문을 문자열로 하나 선언해주세요.

    쿼리문 내부의 '?'는 name 인자를 받아와 입력해주는 place holder 역할을 합니다.


    반환값은 queryForObject 메소드를 사용합니다. 근데 내부 파라미터가 조금 희한하게 생겼죠?

    우선 우리가 입력한 쿼리문이 인자로 들어가고 객체타입 배열은 사실 그냥 'name'이라는 인자만 받아도 됩니다.

    굳이 저렇게 쓴 이유는 일반화 하기 위해서 해둔것이니, 귀찮으시면 그냥 name 써주세요. ㅎㅅㅎ


    그리고 마지막으로 'RowMapper'라는 객체가 하나 인자로 들어오네요.

    이를 'Anonymous Class'라고 합니다.


    우선 RowMapper라는 mapper는 Interface 입니다.

    우리가 생성한 Offer라는 테이블에서 데이터를 가져 올 때그 클래스에 포함된 필드와 mapping 해주고,

    따라서 데이터에 접근 할 수 있도록 해주는 역할을 합니다.

    또한 Anonymous Class란 클래스 생성과 정의를 동시에 할 수 있도록 해주는데요.

    일회성의 성격을 가집니다. 따라서 일반적으론 따로 클래스 파일을 생성해주진 않습니다.


    인터페이스니까요. 우린 그에 포함된 RowMapper라는 메소드를 사용하기위해 재정의 해야합니다.

    'ResultSet 인자를 통해 SQL문을 실행한 결과를 받아오고, 옆의 정수형 변수로 순번을 받아 올 예정이다.'

    이렇게 알고 코드를 보시면 이해가 될 겁니다.

    Offer 객체를 하나 생성해서, 해당 객체에 데이터를 설정해야하니까 set 메소드를 호출하구요.

    내부에 어떤 필드값을 이용할지 적어주세요. 즉, id, name, email, text 이렇게 입력하면 되겠죠.


    흐음... 근데 생각해보니 우린 단일(Single) 데이터 말고 다수(Multiple) 데이터도 조회해보기로 했으니까요.

    아마도 RowMapper가 재사용 될 것 같아요.

    그래서 'OfferMapper' 클래스 파일을 하나 새로 생성해서, 받아서 쓰는 방식으로 중복된 코드를 없애봅시다.


     OfferMapper.java

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    package kr.ac.snut.spring.csemall;
     
    import java.sql.ResultSet;
    import java.sql.SQLException;
     
    import org.springframework.jdbc.core.RowMapper;
     
    public class OfferMapper implements RowMapper<Offer> {
     
        @Override
        public Offer mapRow(ResultSet res, int rowNum) throws SQLException {
            Offer offer = new Offer();
            
            offer.setId(res.getInt("id"));
            offer.setName(res.getString("name"));
            offer.setEmail(res.getString("email"));
            offer.setText(res.getString("text"));
        
            return offer;
        }
        
    }
    cs


    OfferMapper를 받아오는 방식의 OfferDAO.java

    1
    2
    3
    4
    5
    public Offer getOffer(String name) {
        String sqlStatement = "SELECT * from Offers WHERE name = ?";
            
        return jdbcTemplateObject.queryForObject(sqlStatement, new Object[] {name}, new OfferMapper());
    }
    cs


    확실히 코드가 간단해졌네요.

    이제 Multiple 데이터에 대한 준비도 완료가 되었으니 혼자서 한번 만들어보세요.

    힌트는 위에서 다 드렸습니다.


    OfferDAO.java, getOffers 메소드(Multiple)

    1
    2
    3
    4
    5
    public List<Offer> getOffers() {
        String sqlStatement = "SELECT * from Offers";
                
        return jdbcTemplateObject.query(sqlStatement, new OfferMapper());
    }
    cs


    이렇게 구현해주시면 됩니다. 

    사실 반환 메소드도 다른것을 쓰고, 

    받아오는 인자나 쿼리문도 다르기 때문에 쉽진 않았을거 같아요.


    그래도 코드만 보면 어떤 의미의 코드인지 차이점에 따라 잘 이해가 가실 것 같아요.

    JDBC Template에 대한 메소드는 상당히 많고, 또 버전이 바뀜에따라 없어진 메소드도 많아요.

    사실상 스프링을 쓸 때 (졸업 작품 때 살짝 건드려봤습니다 ^^..) JDBC 템플릿 자체도 엄청많이 사용하진 않았던걸로 기억해요

    귀찮으시면 패스... 하지만 저같이 하나하나 알아가고 싶으시다면, 꼭 찾아보세요.



    Select 결과 확인 (조회)


    MainApp 클래스에서 동작하는지 한번 봅시다.

    단일로 뽑는건 간단하니까 여러개를 동시에 뽑아볼게요.


    MainApp.java 데이터 출력

    1
    2
    3
    4
    List<Offer> offerList = offerDAO.getOffers();
    for(Offer rows: offerList) {
        System.out.println(rows);
    }
    cs



    향상된 for문은 모를리 없다고 생각이 듭니다.

    어쨌든 List에 저장된 여러개의 Offer 객체를 하나씩 받아와 출력하는 형태의 코드입니다.


    이렇게 색칠된 3줄이 우리가 뽑아낸 결과 입니다.

    저번에 입력해두었던 데이터들이 모두 잘 나왔네요.



    Insert Method 생성 (C: create)


    이제 새로운 데이터를 추가시키는 동작을 구현해 봅시다.

    OfferDAO 클래스에 반환값을 boolean으로 잡고 insert 메소드를 정의해주세요.


     OfferDAO.java, Insert 메소드

    1
    2
    3
    4
    5
    6
    7
    8
    public boolean insert(Offer offer) {        
        String name = offer.getName();
        String email = offer.getEmail();
        String text = offer.getText();
     
        String sqlStatement = "INSERT into Offers (name, email, text) values (?, ?, ?)";
        return (jdbcTemplateObject.update(sqlStatement, new Object[] {name, email, text}) == 1);
    }
    cs


    데이터를 새로 추가해야하기 때문에, 하지만 id는 auto increment 설정이 되어있었죠.

    그래서 나머지 값들 name, email, text를 get 메소드를 통해 값을 받을 수 있도록하고,

    입력한 값들을 place holder를 통해 받아 쿼리문으로 테이블에 추가할 수 있도록 명령문을 씁니다.

    그리고 처음 말씀드렸던 것처럼 의도는 'Create'지만 같은 기능을 하는 update 메소드를 사용해서 데이터를 넣습니다.


    그리고 데이터가 한개 입력 되었을 때, true 값을 반환하도록 유도했습니다.

    하하하... 너무 성의없지만 뭐.. 연습이니까 간단하게 이렇게 구성했습니다.



    Insert 결과 확인 (추가)


    다시 MainApp 클래스로 가서 추가 데이터를 Offer 객체를 받아와 직접 입력해 생성합니다.

    그리고 해당 데이터를 insert 메소드를 통해 추가시켜주는데,

    그때 입력된 데이터가 내가 만든 1개의 데이터가 맞으면 성공 아니면 실패를 출력하도록 구성했습니다.


     MainApp.java, 데이터 생성 및 추가

    1
    2
    3
    4
    Offer offerObject = new Offer("rudy""rudy@snut.ac.kr""shield bug");
            
    if(offerDAO.insert(offerObject)) System.out.println("Object is Inserted!");
    else System.out.println("insert: failed!");
    cs



    위의 결과들을 무시하고 색칠한 부분만 봐주세요. 이렇게 뜨시면 성공입니다.

    저같은 경우엔 왜 데이터가 여러개 붙어있냐면, 조금씩 수정하면서 결과보기위해 실행하다가

    계속 Insert가 발생하는 불상사가 생겼기 때문입니다...ㅠㅠ!!


    ※ Error 주의!!

    update 메소드 호출시 'NoClassDefFoundError'가 발생할 경우

    위의 내용을 잘 따라오셨다면, 문제는 저와 같을 것이라 생각됩니다. 

    Spring Framework와 JDBC 버전을 통일해주세요. 저는 JDBC 버전에 맞춰 4.2.5로 통일했습니다.


     pom.xml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
      <properties>
     
            <!-- Generic properties -->
            <java.version>1.8</java.version>
            <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
            <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
     
            <!-- Spring -->
            <spring-framework.version>4.2.5.RELEASE</spring-framework.version>
     
            <!-- Hibernate / JPA -->
            <hibernate.version>4.2.1.Final</hibernate.version>
     
            <!-- Logging -->
            <logback.version>1.0.13</logback.version>
            <slf4j.version>1.7.5</slf4j.version>
     
            <!-- Test -->
            <junit.version>4.11</junit.version>
     
        </properties>
    cs


    저처럼 바꾸실 분들은 색칠한 부분을 수정하고 저장 누르시면 됩니다.



    Update Method 생성 (U: update)


    역시나 말씀드린대로 insert 메소드와 거의 동일하게 구현합니다.

    대부분의 코드는 그대로 두시고, int로 id만 새로 정의해서 추가할게요.

    그리고 쿼리문도 Update에 맞춰 수정해주세요.


     OfferDAO.java, Update 메소드

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public boolean update(Offer offer) {
        int id = offer.getId();
        String name = offer.getName();
        String email = offer.getEmail();
        String text = offer.getText();
     
        String sqlStatement = "UPDATE Offers set name = ?, email = ?, text = ? WHERE id = ?";
        return (jdbcTemplateObject.update(sqlStatement, new Object[] {name, email, text, id}) == 1);
    }
    cs



    Update 결과 확인 (수정)


    우리가 아까 새로 생성한 'rudy'의 데이터를 수정해 봅시다.

    우선 변경 전 'rudy'의 데이터를 뽑아서 확인하고, 저는 방덱을 싫어하기 때문에

    'rudy' -> 'trash'로 변경해서 변경된 루디의 이름을 출력해볼게요.


    이때도 변경된 데이터가 1개가 아니면 수정 실패라는 메시지를 출력할게요.


     MainApp.java, 데이터 수정

    1
    2
    3
    4
    5
    6
    offerObject = offerDAO.getOffer("rudy");
    System.out.println(offerObject);
            
    offerObject.setName("trash");
    if(offerDAO.update(offerObject)) System.out.println("update name: " + offerObject);
    else System.out.println("update: failed!");
    cs



    우리의 개10 날먹 방덱충 (ㅂㄷㅂㄷ) 루디의 이름이 쓰레기로 변경이 잘 되었습니다. ^^



    Delete Method 생성 (D: delete)


    이렇게해도 분이 풀리지 않아요..^^ 눈 앞에서 없애버립시다.

    삭제 메소드를 정의해 볼게요.


     OfferDAO.java, Delete 메소드

    1
    2
    3
    4
    5
    public boolean delete(int id) {
        String sqlStatement = "DELETE from Offers WHERE id = ?";
            
        return (jdbcTemplateObject.update(sqlStatement, new Object[] {id}) == 1);
    }
    cs


    삭제는 id 기준으로 할게요. 테이블 내에서 유일한 값인 P-key로 설정해 지워야 실수도 없을 테니까요.

    그리고 또한 삭제할 데이터가 한개인 경우 참을 반환해줍시다.



    Delete 결과 확인 (삭제)


    이전 update를 위한 코드랑 매우 유사하기 때문에 자세한 설명은 생략한다.


    MainApp.java, 데이터 삭제

    1
    2
    if(offerDAO.delete(offerObject.getId())) System.out.println("deleted!");
    else System.out.println("delete: failed!");
    cs



    Table 현황 확인

    이미 지워졌기 때문에, 저는 데이터를 하나 더 생성해서 제가 terminal에서 삭제해서 결과를 찍어봤습니다.

    어쨌든 맨 밑의 표처럼 나오시면 됩니다.


    사실 혼자 무턱대고 갖다 박으면... 쉽진 않겠지만 이렇게 배우면서 해보니 꽤 수월하네요.

    이렇게 배운 내용들이 추후에 큰 도움이 되길 바라면서.. 오늘은 여기서 끝!!

    반응형

    댓글

Designed by minchoba.