알고리즘
별찍기 - 정사각형
*****
*****
*****
*****
위와 같이 정사각형 모형의 별을 출력하시오.
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을 생성해줍니다.