///////
Search
🦁

김진아

Gradle을 쓰는 이유?

테스트 생성 마법사를 통해 테스트를 쉽게 할 수 있는 환경을 조성하려고 → Test를 하기 위함

Stack

Stack의 구조

Stack: 먼저 들어간 자료가 나중에 나옴 LILO(Last In First Out) 구조
함수에서 함수를 호출하는 기능을 구현하는데 스택을 씀
Queue: FIFO(First In, First Out) 선입선출

Stack 메소드

.push() → Stack에 객체를 저장한다
.pop() → Stack의 맨 위에 저장된 객체를 꺼낸다
.peek() → Stack의 맨 위에 저장된 개겣를 반환
.isEmpty → Stack이 비어있는지 알려준다
1.
Stack01
package _22_10_19; public class Stack01 { // 기본 생성자 public Stack01() { } // 크기가 10000인 객체배열을 생성하고 arr라는 변수에 담는다 private int[] arr = new int[10000]; // pointer 변수는 가장 마지막에 stack에 담긴 데이터를 가리킨다 private int pointer = 0; // 객체배열에 정수 value를 받아서 데이터를 저장한다 public void push(int value) { // this.arr에 포인터 위치에 value를 넣는다 this.arr[this.pointer] = value; // pointer를 1 증가시킨다 this.pointer++; } // 객체 배열에 가장 최근에 담긴 데이터를 삭제 public int pop() { // value에 this.arr의 pointer가 이전 요소를 가리키는 값을 담는다 int value = this.arr[this.pointer - 1]; // 이전 요소를 가리키니까 pointer -1 this.pointer--; return value; // return } // 멤버변수가 private이기 때문에 getter를 통해 값을 접근한다 public int[] getArr() { return arr; } } // class end
Java
복사
2.
Stack01Test
package _22_10_19; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; class Stack01Test { void pushTest() { // Stack01 클래스의 멤버를 사용하기 위해 인스턴스 생성 // 기본 생성자 씀 Stack01 stack01 = new Stack01(); stack01.push(10); stack01.push(20); // Stack은 넣은 순서를 뒤집어서 꺼내기 때문에 Array를 이용함 // push()로 stack에 객체 저장 // assertEquals메소드에서 기댓값과 실제값을 비교하기 위해서 private으로 제한자가 // 설정된 arr의 해당하는 요소값을 가져오기 위해서 getter를 쓴다 int[] arr = stack01.getArr(); // 앞(기댓값)과 뒤(실제값)의 파라미터가 일치하는지 확인 Assertions.assertEquals(10, arr[0]); Assertions.assertEquals(20, arr[1]); } @Test void pushAndPop() { // 객체 생성 Stack01 stack01 = new Stack01(); // 데이터가 중복됨 stack01.push(10); stack01.push(20); // 앞(기댓값)과 뒤(실제값)의 파라미터가 일치하는지 확인 Assertions.assertEquals(20, stack01.pop()); Assertions.assertEquals(10, stack01.pop()); stack01.push(30); Assertions.assertEquals(30, stack01.pop()); } }
Java
복사
2-1. Assertion 클래스의 assertEquals 메소드는 기댓값과 실제값의 인자를 받아서 두 파라미터가 일치하는지 확인하는 기능을 한다.
3.
메세지가 안 뜨면 테스트 성공!
만약 테스트 실패 시 에러 코드 발생 → 에러 코드 분석하여 코드 디버깅한다

Spring

Dao(Data access object) ⇒ DB에 접근할 때 쓰는 객체
따라서 UserDao는 사용자에 대한 데이터를 다루는 객체다.
1.
UserDao
package com.line.dao; import java.sql.*; // EC2 인스턴스의 MySQL에 DB를 관리하는 코드 public class UserDao { AwsConnectionMaker awsConnectionMaker = new AwsConnectionMaker(); public UserDao() { } public UserDao(AwsConnectionMaker awsConnectionMaker) { this.awsConnectionMaker = awsConnectionMaker; } public void add(User user) throws SQLException, ClassNotFoundException { Connection conn = awsConnectionMaker.makeConnection(); PreparedStatement ps = conn.prepareStatement("INSERT INTO users(id, name, password) VALUES(?, ?, ?)"); ps.setString(1, user.getId()); ps.setString(2, user.getName()); ps.setString(3, user.getPassword()); // connection db연결 int status = ps.executeUpdate(); // ctrl + enter System.out.println(status); ps.close(); // 연결을 끊어줘야함 conn.close(); System.out.println("DB insert 완료"); } // select 기능 public User get(String id) throws ClassNotFoundException, SQLException { // id에 해당하는 User를 찾는다. Connection conn = awsConnectionMaker.makeConnection(); PreparedStatement ps = conn.prepareStatement("SELECT id, name, password FROM users WHERE id = ?"); ps.setString(1, id); ResultSet rs = ps.executeQuery(); rs.next(); User user = new User(rs.getString("id"), rs.getString("name"), rs.getString("password")); rs.close(); ps.close(); conn.close(); return user; } // count 기능 // delete 기능 public static void main(String[] args) throws SQLException, ClassNotFoundException { UserDao userDao = new UserDao(); // userDao.add(); User user = userDao.get("1"); // userDao.get() 호출하기 System.out.println(user.getName()); } }
Java
복사
Userdao에는 db와 연결할 수 있는 커넥션 기능, 데이터 추가, 데이터 삭제, 데이터 조회, 데이터를 업데이트하는 기능이 필요하다. 우선 DB를 연동하는 커넥션 기능에는 DB에 접속할 수 잇는 데이터를 담은 변수들을 저장한다. 이 때, JDBC(Java Database Connectivity)라는 자바 API를 이용한다. JDBC로 ADD, GET, DELETE, UPDATE를 통해서 SQL query statement를 통해서 연동을 하도록 한다. 각 메서드들을 구현하고 나면 관심사 분리를 한다. 관심사 분리란, 필요한 기능들을 최대한 작은 단위의 기능들로 쪼개서 느슨한 결합을 통해 분리해 놓는 것이다.
2.
UserDaoAbstract
package com.line.dao; import java.sql.*; // abstract 메소드를 가진 클래스는 abstract 클래스다 public abstract class UserDaoAbstract { // abstract 메소드 public abstract Connection makeConnection() throws ClassNotFoundException, SQLException; public void add(User user) throws SQLException, ClassNotFoundException { Connection conn = makeConnection(); PreparedStatement ps = conn.prepareStatement("INSERT INTO users(id, name, password) VALUES(?, ?, ?)"); ps.setString(1, user.getId()); ps.setString(2, user.getName()); ps.setString(3, user.getPassword()); // connection db연결 int status = ps.executeUpdate(); // ctrl + enter System.out.println(status); ps.close(); // 연결을 끊어줘야 한다. conn.close(); System.out.println("DB insert 완료"); } // select 기능 public User get(String id) throws ClassNotFoundException, SQLException { // id에 해당하는 User를 찾는다. Connection conn = makeConnection(); PreparedStatement ps = conn.prepareStatement("SELECT id, name, password FROM users WHERE id = ?"); ps.setString(1, id); ResultSet rs = ps.executeQuery(); rs.next(); User user = new User(rs.getString("id"), rs.getString("name"), rs.getString("password")); rs.close(); ps.close(); conn.close(); return user; } public static void main(String[] args) throws SQLException, ClassNotFoundException { // UserDaoAbstract userDao = new UserDaoAbstract(); //// userDao.add(); // User user = userDao.get("1"); // userDao.get() 호출하기 // System.out.println(user.getName()); } }
Java
복사
위의 abstract 클래스의 abstract 메소드를 구현하는 구현체를 따로 만든다 (오버라이딩)

관심사 분리 방법

1.
추상클래스 상속
2.
인터페이스 구현
관심사 분리의 장점은, 기능의 수정이 필요할 때 기존의 코드를 최대한 수정하지 않고 재사용할 수 있다.
3.
AWSUserDaoImpl
package com.line.dao; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import java.util.Map; public class AWSUserDaoImpl extends UserDaoAbstract { @Override public Connection makeConnection() throws ClassNotFoundException, SQLException { Map<String, String> env = System.getenv(); String dbHost = env.get("DB_HOST"); String dbUser = env.get("DB_USER"); String dbPassword = env.get("DB_PASSWORD"); // 3가지 값 받아오는 곳 Class.forName("com.mysql.cj.jdbc.Driver"); Connection conn = DriverManager.getConnection(dbHost, dbUser, dbPassword); return conn; } }
Java
복사
이 구현체에서는 로컬의 환경 변수에서 Map의 key값에 해당하는 각각의 값(Values)들을 파라미터에 넣어서 변수에 담아서 jdbc 드라이버를 실행한다.
그 후, 커넥션을 만들고 커넥션을 반환한다.
4.
interface ConnectionMaker
package com.line.dao; import java.sql.Connection; import java.sql.SQLException; // 인터페이스는 구현부가 없음 public interface ConnectionMaker { // 리턴타입은 Connection Connection makeConnection() throws SQLException, ClassNotFoundException; }
Java
복사
추상클래스 상속를 통한 오버라이드의 단점은 다중상속이 불가하다는 것이다. 때문에 SOLID에서 개방폐쇄의 원칙을 위반한다.
상속을 통한 방법은 한번 상속하고 나면 더 이상의 상속이 불가하기 때문에 추가적인 기능에 대한 코드 수정이 불가피하므로 확장성에 문제가 있다.
즉, 변화에 닫혀있고 확장에 열려있어야하는데 확장을 하려면 코드 변경이 불가피하다. → 그래서 인터페이스의 필요성이 대두되었다.
인터페이스는 다중 구현이 가능하다!
인터페이스에서 리턴타입을 Connection으로 선언했다.
5.
AwsConnectionMaker
packagecom.line.dao; importjava.sql.Connection; importjava.sql.DriverManager; importjava.sql.SQLException; importjava.util.Map; public class AwsConnectionMaker implements ConnectionMaker { @Override public Connection makeConnection() throws SQLException, ClassNotFoundException { // Map은 Key와 Value가 페어링해서 저장되고 Value는 중복 가능 // Key, Value의 타입은 String이 올 것임 // Syste.getenv메소드로 환경변수를 env 변수에 담는다 // getenv메소드 안에는 환경변수에 관한 정보를 담고 있다 Map<String, String> env = System.getenv(); // 내 pc의 환경변수에서 get메소드에 키값을 넣으면 key와 페어링 되어 있는 value값을 "" 안의 정보를 변수에 담는다. // 환경변수를 써서 인터넷에 공개되면 민감한 개인정보를 보호한다. ex. 비밀번호, 내 인스턴스 주소 (private) String dbHost = env.get("DB_HOST"); String dbUser = env.get("DB_USER"); String dbPassword = env.get("DB_PASSWORD");// 3가지 값 받아오는 곳 // forname메소드는 클래스를 콜한다 // jdbc: Java Database Connectivity // Class.forName("com.mysql.cj.jdbc.Driver"); // 어떤 클래스를? 자바와 mysql을 연결하는 메소드들이 담긴 클래스를 콜한다 Connection conn = DriverManager.getConnection(dbHost, dbUser, dbPassword); // DriverManager가 가진 getConnection 메소드를 통해 접속 return conn; // 커넥션을 반환 -> 커넥션 -> 즉, mysql에 접속 } }
Java
복사
인터페이스에서 구현 받아서 AWS Connection 메소드를 구현 했다. 그리고 Connection을 반환했다.
설계 단계에서 인터페이스라는 느슨한 결합을 통해 설계하고, 그에 맞는 구현체를 만들어서 해당하는 구현체로 객체생성을 하면 해당하는 객체의 데이터를 용이하게 다룰 수 있다.
→ DB가 달라질 때마다, 커넥션 로직을 변경할 필요없이 구현체만 바꿔서 적용하면 된다.
6.
UserDaoFactory
package com.line.dao; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class UserDaoFactory { @Bean public UserDao awsUserDao() { AwsConnectionMaker awsConnectionMaker = new AwsConnectionMaker(); UserDao userDao = new UserDao(awsConnectionMaker); return userDao; } }
Java
복사
제어 역전(IoC): 스프링에게 IoC를 통해 오브젝트를 관리하게 하는 것 )오브젝트의 생명주기를 스프링이 관리) 하나의 기능을 생성하면 모든 Bean(구현체)을 로딩해서 코드 중복 없이 쓸 수 있다.
객체의 의존성을 역전시켜서 객체 간의 결합도를 줄이고 유연한 코드를 작성할 수 있게 하여 가독성 및 코드 중복을 제거하고 유지 보수를 높인다
예) 의존성 주입
싱글톤 인스턴스? 전역으로 사용되는 인스턴스 → 다른 클래스의 인스턴스들이 접근해서 사용 가능
7.
UserDaoTest
package com.line.dao; import com.line.dao.User; import com.line.dao.UserDao; import com.line.dao.UserDaoFactory; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit.jupiter.SpringExtension; import java.sql.SQLException; @ExtendWith(SpringExtension.class) @ContextConfiguration(classes = UserDaoFactory.class) class UserDaoTest { @Autowired ApplicationContext context; @Test void addAndGet() throws SQLException, ClassNotFoundException { // AWSUserDaoImpl userDao = new AWSUserDaoImpl(); UserDao userDao = new UserDaoFactory().awsUserDao(); User user = new User("13","EternityHwan","123"); userDao.add(user); User selectedUser = userDao.get("12"); Assertions.assertEquals("EternityHwan", selectedUser.getName()); // error } }
Java
복사

요약

초기 UserDao의 클래스에는 중복되는 코드들이 많았다.
1.
따라서 관련있는 것들끼리 내부 메소드로 분리했다.
2.
내부 메소드 → 추상 클래스로 분리했다.
그러나 추상 클래스는 구현부가 없으므로 상속 받아서 오버라이딩하여 구현부를 작성해야한다.
3.
추상클래스는 다중 상속이 안 된다. (개방폐쇄법칙 위반한다)
4.
인터페이스를 이용해서 여러 클래스들에게 느슨한 결합을 하고, 구현체를 구현한다.
5.
구현체를 가지고 커넥션을 만들고 커넥션에다가 JDBC 드라이버를 통해 각 메소드(add, select, delete, update)마다 다른 SQL 쿼리를 날린다.