///////
Search

별 찍기,스프링 입문_곽철민

1.

알고리즘

별찍기 - 정사각형

***** ***** ***** ***** 위와 같이 정사각형 모형의 별을 출력하시오.
JavaScript
복사
코드

별찍기 - 직사각형

**** **** 행과 열을 입력받고 위와 같은 직사각형 모형의 별을 출력하시오.
Java
복사
코드

별찍기 - 재귀

* ** *** **** ***** 재귀를 활용하여 위와 같은 직각삼각형 모형의 별을 출력하시오.
Java
복사
코드

재귀란?

자기 자신을 호출하는 함수
재귀 함수는 꼭 종료 조건을 넣어주어야 합니다.
그렇지 않으면 무한 loop가 돌아, StackOverFlow가 발생하게 됩니다.
코드 가독성이 올라가거나 재귀를 사용함으로써 코드 구현이 간단해지는 경우 사용한다.

[도전] 사각형 출력하기 - 2

코드

스프링 입문

토비의 스프링3 58pg - User를 받아서 DB에 Insert 하도록 수정

User를 받아서 DB에 Insert 하도록 리팩토링 해보시오.
기존 코드의 SQL 변수 설정 부분
PreparedStatement ps = c.prepareStatement("INSERT INTO users values(?,?,?)"); ps.setString(1, "2"); ps.setString(2, "김길동"); ps.setString(3, "password");
Java
복사
변경된 코드
public void add(User user){ try{ //jdbc 로드 Connection c = connectionMaker.makeConnection(); //2. 쿼리 작성 PreparedStatement ps = c.prepareStatement("INSERT INTO users values(?,?,?)"); ps.setString(1, user.getId()); ps.setString(2, user.getName()); ps.setString(3, user.getPassword()); //3. 쿼리 실행 ps.executeUpdate(); //4. 자원 반납 ps.close(); c.close(); }catch (Exception e) { throw new RuntimeException(e); } }
Java
복사
기존 코드와 변경된 코드의 차이는 SQL 구문의 변수를 설정해주는 부분
기존 코드는 단순히 값을 하드 코딩하여 바로 할당해줍니다.
그러나, 변경된 코드는 외부에서 새로운 User 인스턴스를 생성하여 add의 인자 값으로 전달해주면, add() 에서 get() 메소드를 활용하여 각 필드값들을 가져오도록 구현합니다.
이는, 새로운 유저를 INSERT 할 때 마다 setString() 메소드 내부에서 유저를 직접적으로 추가하고 지우고 다시 작성해야 하는 번거로움을 줄여줍니다.

테스트 코드 작성 - 60pg

package org.example.dao; import org.example.domain.User; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; class UserDaoTest { @Test void addAndSelect(){ //given UserDao userDao = new UserDao(new AwsConnectionMaker()); User user = new User("6", "정준하", "1231231313"); //when userDao.add(user); User savedUser = userDao.findById(user.getId()); //then assertEquals(user.getName(), savedUser.getName()); } }
Java
복사
우리는 기능 검증을 위해 그동안 메인 메소드를 활용하였습니다.
그러나 메인 메소드는 에러가 어디서 발생하는지 일일이 찾아주어야 합니다.
테스트 코드는 명확히 Assertions 클래스를 활용해 기댓값과 실제 반환된 값을 명확히 검증할 수 있습니다.
테스트 코드 작성 요령
given - when - then 구조로 작성한다면, 테스트 코드를 좀 더 명료하게 작성할 수 있습니다.
given 부분에는 어떠한 데이터가 주어지는지?
when 부분에는 주어진 데이터로 어떠한 동작을 수행할 것 인지?
then 부분에는 그러한 동작을 해서 어떠한 결과를 기대하는지?
의 구조로 테스트 코드를 작성하면 좀 더 쉽게 테스트 코드를 작성할 수 있을 것 입니다.

문제가 있는 DAO 클래스

package org.example.dao; import org.example.domain.User; import java.sql.*; import java.util.ArrayList; import java.util.List; import java.util.Map; public class UserDao { private ConnectionMaker connectionMaker; public UserDao(ConnectionMaker connectionMaker) { this.connectionMaker = connectionMaker; } public void add(User user){ try{ String jdbcDriver = "com.mysql.cj.jdbc.Driver"; Class.forName(jdbcDriver); Map<String, String> env = System.getenv(); String dbHost = env.get("DB_HOST"); String dbUser = env.get("DB_USER"); String dbPassword = env.get("DB_PASSWORD"); Connection c = DriverManager.getConnection(dbHost, dbUser, dbPassword); //2. 쿼리 작성 PreparedStatement ps = c.prepareStatement("INSERT INTO users values(?,?,?)"); ps.setString(1, user.getId()); ps.setString(2, user.getName()); ps.setString(3, user.getPassword()); //3. 쿼리 실행 ps.executeUpdate(); //4. 자원 반납 ps.close(); c.close(); }catch (Exception e) { throw new RuntimeException(e); } } public User findById(String id){ User user; try{ String jdbcDriver = "com.mysql.cj.jdbc.Driver"; Class.forName(jdbcDriver); Map<String, String> env = System.getenv(); String dbHost = env.get("DB_HOST"); String dbUser = env.get("DB_USER"); String dbPassword = env.get("DB_PASSWORD"); Connection c = DriverManager.getConnection(dbHost, dbUser, dbPassword); PreparedStatement pstmt = c.prepareStatement("select * from users where id = ?"); pstmt.setString(1, id); ResultSet rs = pstmt.executeQuery(); rs.next(); //하나 읽어오기 user = new User(rs.getString("id"), rs.getString("name"), rs.getString("password")); rs.close(); pstmt.close(); c.close(); }catch (Exception e){ throw new RuntimeException(e); } return user; } }
Java
복사
여기 레코드를 추가해주는 add() 메소드와 id 값으로 레코드를 찾아주는 findById() 메소드가 있다고 해봅시다.
이 코드에는 문제점이 존재합니다.
문제점은 add() 메소드와 findById() 메소드가 공통 관심사를 가지고 있다는 것입니다.
공통 관심사가 의미하는 것?
쉽게 말하면 중복된 코드입니다.
두 코드 모두, 데이터베이스 드라이버를 가져오고 커넥션 생성에 대한 관심공통적으로 가지고 있습니다.
그렇기 때문에, 드라이버를 가져오고 커넥션을 생성하는 공통 관심사 부분에 대한 중복된 코드가 존재하게 되는 것입니다.
공통 관심사는 분리해주어야 합니다.
이제 이 공통 관심사를 분리해주는 작업을 해보도록 합시다.
(강의 시간에 공통 관심사의 분리는 메소드 → 추상 클래스 → 클래스 → 인터페이스 순으로 진행,
필자는 인터페이스로 분리되도록 바로 진행하겠습니다.)

공통 관심사를 인터페이스로 분리

두 메소드의 공통 관심사는 드라이버 로드 및 커넥션 생성이었습니다.
따라서, 이 공통 관심사를 나타낼 수 있도록 ConnectionMaker 라는 인터페이스를 하나 생성하겠습니다.
package org.example.dao; import java.sql.Connection; import java.sql.SQLException; public interface ConnectionMaker { public Connection makeConnection() throws ClassNotFoundException, SQLException; }
Java
복사
ConnectionMaker 인터페이스에는 커넥션을 생성하는 역할인 makeConnection()을 하나 선언해주었습니다.
이제 이 인터페이스를 구현하는 구현체 클래스들을 만들어보겠습니다.
시나리오
DB를 사용하는데, 로컬 DB를 사용할 수도 있고, AWS를 통해 데이터베이스를 띄울 수도 있는 상황이라고 해보자. 이 상황에서 Connection을 생성하는 부분은 각 DB에 따라 달라질 수 있다.

구현체 클래스 만들기

AWS를 통해 Database를 사용하는 경우의 구현체 클래스

package org.example.dao; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import java.util.Map; public class AwsConnectionMaker implements ConnectionMaker{ @Override public Connection makeConnection() throws ClassNotFoundException, SQLException { String jdbcDriver = "com.mysql.cj.jdbc.Driver"; Class.forName(jdbcDriver); Map<String, String> env = System.getenv(); String dbHost = env.get("DB_HOST"); String dbUser = env.get("DB_USER"); String dbPassword = env.get("DB_PASSWORD"); Connection connection = DriverManager.getConnection(dbHost, dbUser, dbPassword); return connection; } }
Java
복사
ConnectionMaker 인터페이스의 구현체인 AWSConnectionMaker 클래스
이 클래스는, 환경 변수를 사용하여 호스트, 유저, 비밀번호 값을 설정해줍니다.
이를 통해, 생성된 커넥션을 반환합니다.

Local DB를 사용하는 경우의 구현체 클래스

package org.example.dao; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; public class LocalDbConnectionMaker implements ConnectionMaker{ @Override public Connection makeConnection() throws ClassNotFoundException, SQLException { String jdbcDriver = "com.mysql.cj.jdbc.Driver"; Class.forName(jdbcDriver); Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/likelion-db", "root", "12345"); return connection; } }
Java
복사
ConnectionMaker 인터페이스의 구현체인 LocalDbConnectionMaker 클래스
이 클래스는 local db의 구현체이므로, host 부분에 localhost로 명시해줍니다. 위와 마찬가지로, 데이터베이스의 유저, 비밀번호 값을 getConnection()의 인자값으로 설정해줍니다.
이를 통해, 생성된 커넥션을 반환합니다.

변경된 UserDAO 클래스

package org.example.dao; import org.example.domain.User; import java.sql.*; import java.util.ArrayList; import java.util.List; public class UserDao { private ConnectionMaker connectionMaker; public UserDao(ConnectionMaker connectionMaker) { this.connectionMaker = connectionMaker; } public void add(User user){ try{ //jdbc 로드 Connection c = connectionMaker.makeConnection(); //2. 쿼리 작성 PreparedStatement ps = c.prepareStatement("INSERT INTO users values(?,?,?)"); ps.setString(1, user.getId()); ps.setString(2, user.getName()); ps.setString(3, user.getPassword()); //3. 쿼리 실행 ps.executeUpdate(); //4. 자원 반납 ps.close(); c.close(); }catch (Exception e) { throw new RuntimeException(e); } } public User findById(String id){ User user; try{ Connection c = connectionMaker.makeConnection(); PreparedStatement pstmt = c.prepareStatement("select * from users where id = ?"); pstmt.setString(1, id); ResultSet rs = pstmt.executeQuery(); rs.next(); //하나 읽어오기 user = new User(rs.getString("id"), rs.getString("name"), rs.getString("password")); rs.close(); pstmt.close(); c.close(); }catch (Exception e){ throw new RuntimeException(e); } return user; } public List<User> findAll(){ List<User> userList = new ArrayList<>(); try { Connection c = connectionMaker.makeConnection(); Statement statement = c.createStatement(); ResultSet rs = statement.executeQuery("select * from users"); while (rs.next()) { User user = new User(rs.getString("id"), rs.getString("name"), rs.getString("password")); userList.add(user); } rs.close(); statement.close(); c.close(); }catch(Exception e){ throw new RuntimeException(e); } return userList; } public int getCount() throws SQLException, ClassNotFoundException { Connection connection = connectionMaker.makeConnection(); PreparedStatement pstmt = connection.prepareStatement("SELECT count(users) as cnt from users"); int result = pstmt.executeUpdate(); return result; } public void deleteAll(){ } }
Java
복사
DB를 활용하여 레코드 값을 조작 혹은 조회할 수 있는 UserDao 클래스입니다.
UserDao는 ConnectionMaker의 구현체가 무엇이든 상관없이, connectionMaker 인스턴스가 만들어주는 커넥션을 활용합니다.
connectionMaker가 무엇인지는 외부에서 UserDao를 사용할 때, 구현체 클래스를 생성자에 주입해주어서 결정해줍니다.
이를 생성자 주입이라고 합니다.
이로써, UserDao는 구체적인 코드(구현체)에 의존하는 것이 아닌 추상에 의존하게 됩니다.

기능 검증을 위한 테스트 코드

package org.example.dao; import org.example.domain.User; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; class UserDaoTest { @Test void addAndSelect(){ //given UserDao userDao = new UserDao(new AwsConnectionMaker()); User user = new User("6", "정준하", "1231231313"); //when userDao.add(user); User savedUser = userDao.findById(user.getId()); //then assertEquals(user.getName(), savedUser.getName()); } }
Java
복사
기능 검증을 위한 테스트 코드 입니다.
userDao 객체 생성 시, ConnectionMaker의 구현체 클래스로 AwsConnectionMaker 객체를 동시에 생성하여 주입해줍니다.
따라서 위 코드의 UserDao는 AWSConnectionMaker 클래스의 구현체가 동작하여 Connection을 생성해줍니다.