Spring DI, AOP

14. DAO의 작성과 적용(1) - 패스트캠퍼스 백엔드 부트캠프 3기

gkss2tpt 2025. 2. 26. 21:03

1. DAO(Data Access Object)란?

  • 데이터(data)에 접근(access)하기 위한 객체(object)
  • Database에 저장된 데이터를 읽기, 쓰기, 삭제, 변경을 수행
  • DB테이블당 하나의 DAO를 작성

 

2. 계층(layer)의 분리

  • 컨트롤러가 직접 데이터베이스에 접근하면 메서드의 중복이 일어난다.
  • 중간에 UserDao를 통해 간접적으로 DB에 접근
  • 분리 - 관심사, 변하는것과 변하지 않는것의 분리, 중복

  • UserDao 클래스
public class UserDao {
    @Autowired
    DataSource ds;
    final int FAIL = 0;

    public int deleteUser(String id) {
        int rowCnt = FAIL; //  insert, delete, update

        Connection conn = null;
        PreparedStatement pstmt = null;

        String sql = "delete from user_info where id= ? ";

        try {
            conn = ds.getConnection();
            pstmt = conn.prepareStatement(sql);
            pstmt.setString(1, id);
//        int rowCnt = pstmt.executeUpdate(); //  insert, delete, update
//        return rowCnt;
            return pstmt.executeUpdate(); //  insert, delete, update
        } catch (SQLException e) {
            e.printStackTrace();
            return FAIL;
        } finally {
            // close()를 호출하다가 예외가 발생할 수 있으므로, try-catch로 감싸야함.
//            try { if(pstmt!=null) pstmt.close(); } catch (SQLException e) { e.printStackTrace();}
//            try { if(conn!=null)  conn.close();  } catch (SQLException e) { e.printStackTrace();}
            close(pstmt, conn); //     private void close(AutoCloseable... acs) {
        }
    }

    public User selectUser(String id) throws Exception {
        User user = null;

        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;

        String sql = "select * from user_info where id= ? ";

        try {
            conn = ds.getConnection();
            pstmt = conn.prepareStatement(sql); // SQL Injection공격, 성능향상
            pstmt.setString(1, id);

            rs = pstmt.executeQuery(); //  select

            if (rs.next()) {
                user = new User();
                user.setId(rs.getString(1));
                user.setPwd(rs.getString(2));
                user.setName(rs.getString(3));
                user.setEmail(rs.getString(4));
                user.setBirth(new Date(rs.getDate(5).getTime()));
                user.setSns(rs.getString(6));
                user.setReg_date(new Date(rs.getTimestamp(7).getTime()));
            }
        } catch (SQLException e) {
            return null;
        } finally {
            // close()를 호출하다가 예외가 발생할 수 있으므로, try-catch로 감싸야함.
            // close()의 호출순서는 생성된 순서의 역순
//            try { if(rs!=null)    rs.close();    } catch (SQLException e) { e.printStackTrace();}
//            try { if(pstmt!=null) pstmt.close(); } catch (SQLException e) { e.printStackTrace();}
//            try { if(conn!=null)  conn.close();  } catch (SQLException e) { e.printStackTrace();}
            close(rs, pstmt, conn);  //     private void close(AutoCloseable... acs) {
        }

        return user;
    }

    // 사용자 정보를 user_info테이블에 저장하는 메서드
    public int insertUser(User user) {
        int rowCnt = FAIL;

        Connection conn = null;
        PreparedStatement pstmt = null;

//        insert into user_info (id, pwd, name, email, birth, sns, reg_date)
//        values ('asdf22', '1234', 'smith', 'aaa@aaa.com', '2022-01-01', 'facebook', now());
        String sql = "insert into user_info values (?, ?, ?, ?,?,?, now()) ";

        try {
            conn = ds.getConnection();
            pstmt = conn.prepareStatement(sql); // SQL Injection공격, 성능향상
            pstmt.setString(1, user.getId());
            pstmt.setString(2, user.getPwd());
            pstmt.setString(3, user.getName());
            pstmt.setString(4, user.getEmail());
            pstmt.setDate(5, new java.sql.Date(user.getBirth().getTime()));
            pstmt.setString(6, user.getSns());

            return pstmt.executeUpdate(); //  insert, delete, update;
        } catch (SQLException e) {
            e.printStackTrace();
            return FAIL;
        } finally {
            close(pstmt, conn);  //     private void close(AutoCloseable... acs) {
        }
    }

    // 매개변수로 받은 사용자 정보로 user_info테이블을 update하는 메서드
    public int updateUser(User user) {
        int rowCnt = FAIL; //  insert, delete, update

//        Connection conn = null;
//        PreparedStatement pstmt = null;

        String sql = "update user_info " +
                "set pwd = ?, name=?, email=?, birth =?, sns=?, reg_date=? " +
                "where id = ? ";

        // try-with-resources - since jdk7
        try (
                Connection conn = ds.getConnection();
                PreparedStatement pstmt = conn.prepareStatement(sql); // SQL Injection공격, 성능향상
        ){
            pstmt.setString(1, user.getPwd());
            pstmt.setString(2, user.getName());
            pstmt.setString(3, user.getEmail());
            pstmt.setDate(4, new java.sql.Date(user.getBirth().getTime()));
            pstmt.setString(5, user.getSns());
            pstmt.setTimestamp(6, new java.sql.Timestamp(user.getReg_date().getTime()));
            pstmt.setString(7, user.getId());

            rowCnt = pstmt.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
            return FAIL;
        }

        return rowCnt;
    }

    public void deleteAll() throws Exception {
        Connection conn = ds.getConnection();

        String sql = "delete from user_info ";

        PreparedStatement pstmt = conn.prepareStatement(sql); // SQL Injection공격, 성능향상
        pstmt.executeUpdate(); //  insert, delete, update
    }

    private void close(AutoCloseable... acs) {
        for(AutoCloseable ac :acs)
            try { if(ac!=null) ac.close(); } catch(Exception e) { e.printStackTrace(); }
    }
}
  • 사용하고 나면 Connection, PreparedStatement, ResultSet과 같은 메모리를 차지하는 변수들을 close 해줘야하는데사용한 순서의 역순으로 close를 해줘야 한다.
  • try-width-resources : 예외가 발생하건 안하건 자동으로 close된다. - java7이상, AutoCloseable을 구현한 객체만
try (
        Connection conn = ds.getConnection();
        PreparedStatement pstmt = conn.prepareStatement(sql); // SQL Injection공격, 성능향상
){
    pstmt.setString(1, user.getPwd());
    pstmt.setString(2, user.getName());
    pstmt.setString(3, user.getEmail());
    pstmt.setDate(4, new java.sql.Date(user.getBirth().getTime()));
    pstmt.setString(5, user.getSns());
    pstmt.setTimestamp(6, new java.sql.Timestamp(user.getReg_date().getTime()));
    pstmt.setString(7, user.getId());

    rowCnt = pstmt.executeUpdate();
} catch (SQLException e) {
    e.printStackTrace();
    return FAIL;
}
  • sql문에 넣어주기 위해 updateUser메서드에서 Date를 java.sql.Date로 형변환
  • 시간과 날짜를 둘다 넣어주기위해 Timestamp를 사용
  • UserDao를 인터페이스로 만들어보자

  • class UserDao에서 오른쪽 마우스 클릭 Refactor -> Extract Interface 클릭

  • 원래 UserDao클래스가 UserDaoImpl로 바뀌고 UserDao가 인터페이스가 되어 생성되었다.

  • 인터페이스의 모든 메서드는 public abstract가 생략 가능

  • Test

  • Test자동생성

class UserDaoImplTest{

    @Test
    void deleteUser() {
    }

    @Test
    void selectUser() {
    }

    @Test
    void insertUser() {
    }

    @Test
    void updateUser() {
    }
}
  • Test양식이 만들어졌다. 여기에 UserDao를 구현한 UserDaoImpl을 사용하는게 아니라 UserDao를 통해 간접적으로 사용해준다.

  • @Autowired가 안된다고 나오는데 @Repository (@Conponent가 메타에너테이션으로 들어가있다)를 추가

  • 에러가 사라졋다.
  • updateUser부터 Test를 해보면...
@Test
void updateUser() {
    User user = new User("asdf", "1234", "abc", "aaa@aaa.com", new Date(), "fb", new Date());
    int rowCnt = UserDao.insertUser(user);
    assertTrue(rowCnt == 1);
}
  • 에러가 발생하는데...

    • 첫번째, root-context.xml의 경로를 WEB-INF/spring/root-context.xml로 만들어준다.
    • 두번째, @ContextConfiguration(locations={"file:src/main/webapp/WEB-INF/spring/**/root-context.xml"})
      경로에 하위경로추가
    • 세번째, pom.xml을 고쳐줬다.
    • 네번째, 모든 Test는 public으로 돌아간다.
    • 이렇게 해주니....

  • 에러가 사라졌다.
@Test
public void updateUser() throws Exception{
    userDao.deleteUser("asdf");
    User user = new User("asdf", "1234", "abc", "aaa@aaa.com", new Date(), "fb", new Date());
    int rowCnt = userDao.insertUser(user);
    assertTrue(rowCnt == 1);

    user.setPwd("4321");
    user.setEmail("bbb@bbb.com");
    rowCnt = userDao.updateUser(user);
    assertTrue(rowCnt==1);

    User user2 = userDao.selectUser(user.getId());
    System.out.println("user = " + user);
    System.out.println("user2 = " + user2);
    assertTrue(user.equals(user2));

}
  • updateUser메서드를 조금 더 추가해주면...

  • 마지막에 user와 user2가 달라서 에러가 나오지만 나머지는 잘 된 모습이다.
  • user2의 Birth의 시간이 안나오는 이유는 SQL의 Date 타입은 날짜만 들어가기때문에  자바의 Util의 Date의 시간정보가 짤린것.
@Test
public void updateUser() throws Exception{

    Calendar cal = Calendar.getInstance();
    cal.clear();
    cal.set(2025,1,26);

    userDao.deleteUser("asdf");
    User user = new User("asdf", "1234", "abc", "aaa@aaa.com", new Date(cal.getTimeInMillis()), "fb", new Date());
    int rowCnt = userDao.insertUser(user);
    assertTrue(rowCnt == 1);

    user.setPwd("4321");
    user.setEmail("bbb@bbb.com");
    rowCnt = userDao.updateUser(user);
    assertTrue(rowCnt==1);

    User user2 = userDao.selectUser(user.getId());
    System.out.println("user = " + user);
    System.out.println("user2 = " + user2);
    assertTrue(user.equals(user2));

}
  • Calendar를 사용해서 Date를 만들어준다.

  • 다 같은데 실패가 나온다