-
Notifications
You must be signed in to change notification settings - Fork 0
김수현 4주차 학습일지
김수현 edited this page Jul 23, 2024
·
2 revisions
- h2 database 경량의 데이터베이스. inmemory rdmbs로 사용할 수 있어 테스트 및 개발용으로 사용됨.
- in memory mode : 메모리에 저장하여 매우 빠른 읽기 및 쓰기 주로 테스트 및 개발 용으로 사용
jdbcUrl : jdbc:h2:mem:databsename
- embedded mode: 데이터베이스가 어플리케이션 프로세스 내부에서 실행되는 모드, 데이터베이스 파일은 로컬 파일 시스템에 저장됨. 어플리케이션이 종료되면 db 서버도 종료.
jdbcUrl: jdbc:h2:~/databsename
- server mode: 서버가 독립적으로 실행되어 네트워크를 통해 접근. 여러 어플리케이션에서 데이터베이스를 공유. h2 서버를 실행해두고 연결. 어플리케이션이 종료되도 서버는 독립적으로 실행되기 때문에 종료되지 않음
jdbc:h2:tcp://loclahost/~testdb
- h2-version.jar에는 h2 database + h2 jdbc driver가 포함되어있음
- database : db 서버
- jdbc driver: db와 통신하기 위한 인터페이스 구현체
- JDBC : 어플리케이션과 관계형 데이터베이스와 상호작용할 수 있도록 하는 표준 API 표준 라이브러리의 일부로,인터페이스를 정의.
- JDBC Driver JDBC API를 를 구현한 라이브러리. 각 데이터베이스 벤더는 JDBC 드라이버를 제공
- jdbc 관련 작업을 간소화하고 추상화
- connection을 열고 닫고,
- statement, resultset을 관리
- 예외처리
- RowMapper인터페이스를 사용해서 ResultSet의 행을 도메인 객체로 변환하는 작업을 추상화
- jdbc를 사용한 코드
public class UserDao {
private String url;
private String username;
private String password;
public UserDao() {
this.url = "jdbc:h2:mem:testdb";
this.username = "sa";
this.password = "";
}
public List<User> findAll() {
List<User> users = new ArrayList<>();
Connection connection = null;
Statement statement = null;
ResultSet resultSet = null;
try {
// 데이터베이스 연결
connection = DriverManager.getConnection(url, username, password);
// 쿼리 실행
statement = connection.createStatement();
resultSet = statement.executeQuery("SELECT * FROM USERS");
// 결과 처리
while (resultSet.next()) {
User user = new User();
user.setId(resultSet.getLong("id"));
user.setName(resultSet.getString("name"));
users.add(user);
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
// 리소스 해제
try {
if (resultSet != null) resultSet.close();
if (statement != null) statement.close();
if (connection != null) connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
return users;
}
- jdbcTemplate을 사용한 코드
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import javax.sql.DataSource;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
public class UserDao {
private JdbcTemplate jdbcTemplate;
public UserDao() {
DataSource dataSource = new DriverManagerDataSource("jdbc:h2:mem:testdb", "sa", "");
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
public List<User> findAll() {
return jdbcTemplate.query("SELECT * FROM USERS", new RowMapper<User>() {
@Override
public User mapRow(ResultSet rs, int rowNum) throws SQLException {
User user = new User();
user.setId(rs.getLong("id"));
user.setName(rs.getString("name"));
return user;
}
});
}
// User 클래스 정의
public static class User {
private Long id;
private String name;
// getters and setters
}
}
- was의 역할? JDNI를 통해 중앙집중적으로 연결 풀링
- ArrayblokcingQueue로 커넥션 관리. 멀티스레드에서 접근 가능. → 커넥션 모두 사용중이라면 array blocking queue 내부 대기 큐에 대기.
- HikariCP: 매우 높은 성능과 낮은 메모리 사용량을 목표로 설계된 커넥션. Spring Boot는 HikariCP를 기본 커넥션 풀로 사용하며, 이를 통해 애플리케이션의 데이터베이스 연결을 효율적으로 관리합.
- Tomcat Connection Pool: Apache Tomcat에서 제공하는 커넥션 풀 라이브러리로, Tomcat을 WAS로 사용하는 경우에 자주 사용됩니다. Spring Boot는 기본적으로 HikariCP를 사용하지만, 필요한 경우 Tomcat Connection Pool을 사용할 수 있도록 설정할 수 있음
- ArrayblokcingQueue는 고정 크기의 배열을 기반으로 구현된 블로킹 큐임. head, tail을 추적.
- timeout
- InterruptedException or null 주어진 시간동안 요소를 얻지 못하면 null을 반환. 블로킹 메서드로 현재 스레드가 기다리는 동안 인터럽트 될 경우 interrupted exception이 발생함.
- offer : 넣을 공간이 생길때까지 기다림 지나면 false.
- minIdle로 잡는게 적당할까 아니면 maxSize로 잡는게 적당할까? → maxSize
-
maximum-pool-size
는 커넥션 풀에서 유지할 수 있는 최대 커넥션 수. 이 값으로ArrayBlockingQueue
의 용량을 설정하면, 커넥션 풀의 최대 크기만큼의 커넥션을 수용할 수 있습니다. 이는 풀의 크기를 완전히 활용할 수 있게 함. - Connection pool 인터페이스 및
- pooled connection 구현: connection proxy.
- close()
- getConnection()
- IdleTimeoutHandler: daemon 으로 설정. 주기적으로 검사, idleTimeoutd의 반만큼 기다린 후 커넥션 풀을 검사.
Java 인터페이스는 변수를 가질 수 있지만, 그 변수들은 암묵적으로 public
, static
, 그리고 final
로 선언됩니다. 즉, 인터페이스에 선언된 변수는 상수(constant)로 취급됩니다.
-
시스템 호출:
System.currentTimeMillis()
는 운영체제의 시스템 시계를 참조하기 위해 시스템 호출을 사용할 수 있습니다. 그러나 JVM 구현에 따라 내부적으로 캐시된 값을 사용할 수도 있습니다. 대부분의 경우, 이 메서드는 운영체제의 시스템 시계를 직접 호출하므로 시스템 호출로 간주될 수 있습니다. -
System.nanoTime()
을 사용할 수도 있습니다.System.nanoTime()
은 고해상도의 시간 측정을 위해 주로 사용되며, 시스템 호출을 피하고 더 정확한 타이밍 측정을 제공합니다.
-
AtomicInteger
는 lock-free 방식으로 원자적 연산을 제공합니다. - 내부적으로 CAS(Compare-And-Swap) 연산을 사용하여 성능을 높이고, 경쟁 상황에서 더 나은 성능을 보입니다.
-
AtomicInteger.incrementAndGet()
,AtomicInteger.decrementAndGet()
등의 메서드를 통해 간단하게 원자적 증가/감소 연산을 수행할 수 있습니다.
-
synchronized
블록은 특정 코드 블록을 한번에 하나의 스레드만 접근할 수 있도록 보장합니다. -
synchronized
블록 내에서는 모든 연산이 일관성을 유지하며 실행됩니다. - 경쟁 상황이 적은 경우 성능에 큰 영향을 미치지 않지만, 경쟁이 많은 경우 lock을 획득하고 해제하는 데 있어 오버헤드가 발생할 수 있습니다.
- Http11Processor: HTTP/1.1 요청을 파싱하고 처리하는 주요 클래스입니다.
- Http11InputBuffer: 요청 바디를 읽고 버퍼링하는 클래스입니다.
- Request: 파싱된 요청 데이터를 보유하는 클래스입니다.
- RequestParser: 요청 라인을 파싱하는 유틸리티 클래스입니다.
- "요청 바디를 읽고 버퍼링한다"는 말은, 클라이언트가 보낸 HTTP 요청의 본문 데이터를 네트워크 소켓으로부터 읽어 메모리나 디스크의 버퍼에 임시로 저장하는 것을 의미합니다. 이 작업은 네트워크 통신의 비동기적 특성으로 인해 데이터를 효율적으로 처리할 수 있도록 도와줍니다. 이 과정은 데이터가 도착하는 즉시 처리할 수 있도록 하기 위함이며, 여러 번의 작은 네트워크 호출을 줄여 성능을 향상시킵니다.
- 그냥 socket의 stream자체를 request 객체에 전달하지 않고 RequestInputBuffer으로 읽어서 전달하는 이유
- 네트워크 지연 최소화: 네트워크에서 데이터를 읽는 작업은 비교적 느릴 수 있습니다. 데이터를 한 번에 큰 덩어리로 읽어들여 버퍼에 저장하면, 여러 번의 작은 네트워크 호출을 줄일 수 있어 성능이 향상됩니다.
- 버퍼에 저장된 데이터를 필요할 때 여러 번 읽거나 가공할 수 있습니다. 소켓 스트림은 일회성으로 읽기 때문에, 데이터를 여러 번 읽거나 가공하기 어려울 수 있습니다.
- 큰 데이터 처리: 요청 본문이 큰 경우, 이를 메모리에 한 번에 바이트 배열로 저장하면 메모리 사용량이 급격히 증가할 수 있습니다. 특히, 동시에 여러 요청을 처리하는 서버 환경에서는 큰 요청 본문을 다수 처리해야 할 수 있으므로 메모리 부족 상황이 발생할 수 있습니다.
- 스트리밍 방식: 스트림을 사용하면 요청 본문을 필요할 때만 읽어들일 수 있습니다. 이는 메모리를 한꺼번에 소비하지 않도록 하여 효율성을 높입니다.
- 파싱의 유연성: 데이터를 메모리 버퍼에 저장하면, 필요할 때 다양한 방식으로 데이터를 파싱할 수 있습니다. 예를 들어, JSON 데이터, XML 데이터, 멀티파트 데이터 등을 적절한 시점에 처리할 수 있습니다.
-
HttpServletRequest
객체가 제공하는InputStream
은 소켓의InputStream
과 직접적으로 연결되어 있지만, 여러 단계의 처리를 거칩니다. 기본적으로, 서블릿 컨테이너는 소켓의InputStream
을 읽고 이를 통해 데이터를 효율적으로 처리할 수 있도록 다양한 버퍼링과 래핑을 수행합니다. 하지만,HttpServletRequest
의getInputStream()
메서드로 얻은 스트림은 여전히 실시간으로 소켓에서 데이터를 읽는 스트림입니다. - Request의 요소별 파싱 시점
-
HttpServletRequest
객체의getParameter()
메서드가 호출되면, 요청 본문을 파싱하여 파라미터를 추출합니다. 파라미터는 URL 쿼리 스트링 또는 요청 본문에서 추출됩니다. 서블릿 컨테이너는getParameter()
메서드가 호출될 때 파라미터를 추출하여 반환합니다. -
application/x-www-form-urlencoded
와 같은 단순한 폼 데이터는 바로 파싱됩니다. -
multipart/form-data
와 같은 복잡한 데이터는 추가 라이브러리(Apache Commons FileUpload 등)를 사용하여 파싱됩니다.multipart/form-data
요청 본문을 파싱하여 파라미터를 추출하는 작업은 일반적으로 서블릿 컨테이너나 서블릿 레벨에서 수행됩니다. Tomcat에서는 기본적으로 이러한 파싱 작업을 지원하지 않으므로, Apache Commons FileUpload와 같은 라이브러리를 사용해야 합니다.
-
public class Request implements HttpServletRequest {
// 파라미터 저장소
private Map<String, String[]> parameters = new HashMap<>();
@Override
public String getParameter(String name) {
// 요청 파라미터를 처음 접근할 때 파싱
parseParameters();
String[] values = parameters.get(name);
if (values != null && values.length > 0) {
return values[0];
} else {
return null;
}
}
private void parseParameters() {
// 이미 파싱된 경우 리턴
if (parametersParsed) {
return;
}
// 파라미터 파싱 로직
String queryString = getQueryString();
if (queryString != null) {
// 쿼리 스트링 파싱
parseQueryString(queryString);
}
if (isPostMethod() && isFormUrlEncoded()) {
// 폼 데이터 파싱
parseFormData();
}
parametersParsed = true;
}
private void parseQueryString(String queryString) {
// 쿼리 스트링을 파싱하여 파라미터 맵에 저장
// 예: name=John&age=30 -> parameters.put("name", new String[]{"John"});
}
private void parseFormData() {
// 폼 데이터를 파싱하여 파라미터 맵에 저장
// 예: name=John&age=30 -> parameters.put("name", new String[]{"John"});
}
private boolean isPostMethod() {
return "POST".equalsIgnoreCase(getMethod());
}
private boolean isFormUrlEncoded() {
return "application/x-www-form-urlencoded".equalsIgnoreCase(getContentType());
}
// 기타 메서드 생략
}
Tomcat의 요청 처리 과정에서 ServletRequest
, HttpServletRequest
, 그리고 Request
객체는 각각 다른 역할을 수행합니다. 이들 간의 차이와 역할을 이해하는 것은 Tomcat 내부 동작을 이해하는 데 매우 중요합니다.
-
정의:
javax.servlet.ServletRequest
는 서블릿 컨테이너가 클라이언트의 요청 정보를 서블릿에 제공하기 위해 사용하는 인터페이스입니다. - 역할: HTTP 프로토콜에 구애받지 않고, 일반적인 요청 정보를 제공하는 역할을 합니다. 예를 들어, 요청 파라미터, 속성, 프로토콜 정보 등을 제공합니다.
-
주요 메서드:
-
getAttribute(String name)
: 요청의 특정 속성을 반환합니다. -
getParameter(String name)
: 요청의 특정 파라미터를 반환합니다. -
getInputStream()
: 요청 본문을 읽기 위한ServletInputStream
을 반환합니다.
-
-
정의:
javax.servlet.http.HttpServletRequest
는ServletRequest
를 확장한 인터페이스로, HTTP 프로토콜을 사용하는 클라이언트 요청의 정보를 서블릿에 제공하는 데 사용됩니다. - HttpServletRequest extensd ServletRequest
- 역할: HTTP 요청에 특화된 메서드를 추가로 제공하여 HTTP 요청의 상세한 정보를 제공합니다. 예를 들어, HTTP 메서드(GET, POST 등), 헤더 정보, 쿠키, 세션 등을 다룹니다.
-
주요 메서드:
-
getMethod()
: HTTP 메서드를 반환합니다. -
getHeader(String name)
: 특정 헤더의 값을 반환합니다. -
getCookies()
: 요청에 포함된 모든 쿠키를 반환합니다. -
getSession()
: 현재 세션을 반환하거나, 세션이 없으면 새로 생성합니다.
-
- 구현 클래스 Request가 HttpServletREquest의 구현
-
정의:
org.apache.catalina.connector.Request
클래스는 Tomcat에서HttpServletRequest
인터페이스를 구현한 클래스입니다. -
역할: Tomcat 내부에서 HTTP 요청을 처리하고, 이를 서블릿 API의
HttpServletRequest
인터페이스로 변환하는 역할을 합니다. Tomcat의 내부 요청 처리 로직과 서블릿 컨테이너의 상호작용을 관리합니다.
- Tomcat의 경우, 서블릿에 전달되는
HttpServletRequest
구현체는RequestFacade
객체입니다.RequestFacade
는 내부적으로Request
객체를 사용하여 요청 정보를 제공합니다.
coyote
패키지는 저수준의 네트워크 작업을 처리하고, catalina
패키지는 서블릿 컨테이너의 상위 수준의 작업을 처리합니다.
-
HTTP 커넥터:
coyote
패키지는 HTTP 프로토콜을 구현하며, 네트워크 소켓을 통해 클라이언트의 HTTP 요청을 수신합니다. - 저수준 프로세싱: 요청 라인, 헤더, 본문 등의 기본적인 HTTP 요청을 파싱합니다.
-
서블릿 컨테이너:
catalina
패키지는 서블릿 API를 구현하고, 서블릿 라이프사이클을 관리합니다. - 상위 수준 프로세싱: 서블릿, 필터, 리스너 등의 컴포넌트를 관리하고, 요청을 이들 컴포넌트로 전달하여 처리합니다.
- Statement는 정적 query, 실행할때마다 새로 파싱되고 실행됨
- SQL indection 공격 가능
- PreparedStatement 미리 컴파일된 SQL 쿼리를 실행. 쿼리에 매개변수를 바인딩할 수 있음.
- 반복적으로 실행되는 쿼리나, 사용자 입력값을 포함하는 쿼리
- ResultSet 객체를 닫아줘야하는 이유:
ResultSet
객체는 반드시 명시적으로 닫아줘야 합니다.ResultSet
은Connection
과Statement
객체처럼 리소스를 많이 소모할 수 있으므로, 사용이 끝난 후에는 반드시 닫아주는 것이 좋습니다.ResultSet
을 닫지 않으면 데이터베이스 연결이 해제되지 않고 계속 유지될 수 있어 메모리 누수 및 기타 리소스 누수 문제가 발생할 수 있습니다. ResultSet을 명시적으로 닫지 않으면 데이터베이스 연결 자체가 닫히지 않는 것은 아님, ResultSet과 관련된 리소스가 해제되지 않아 리소스 누수가 발생할 수 있음.ResultSet
객체는Statement
객체에 종속적입니다.Statement
를 닫으면 관련된ResultSet
도 닫힙니다. 하지만, 명시적으로 각각의 객체를 닫는 것이 권장됩니다. 이렇게 하면 리소스를 명확하게 관리할 수 있고, 코드의 가독성 및 유지보수성이 높아집니다. 닫히지 않은ResultSet
객체가 많아지면, 세션당 열 수 있는 커서 수 제한을 초과할 수 있습니다. 이로 인해 새로운 쿼리를 실행할 수 없게 됩니다. - 데이터베이스 시스템에서 커서(Cursor)는 쿼리 결과 집합을 순회하는 데 사용되는 데이터베이스 객체입니다. 커서는 특정 위치에서 결과 집합의 행을 가리키며, 순차적으로 행을 처리할 수 있게 해줍니다.
- 둘다 데이터 엑세스 레이어에서 데이터베이스 작업을 수행
- DAO: 데이터베이스 테이블과 직접 매핑되는 클래스와 메서드를 정의 데이터베이스의 CRUD를 수행
- Repository: 보다 높은 추상화, 도메인 객체의 컬렉션을 관리하는 것처럼 동작, 데이터 소스와 상관없이 동작. ORM 기술을 사용해서 도메인 모델과 데이터베이스 간의 매핑을 관리
- if (!parameters.get("posts") instanceof List) 처럼 컬렉션에 대해서 class type을 체크할 후 없다.
- Java에서는 제네릭 타입은 런타임에 타입 정보를 유지하지 않기 때문에 직접적으로
instanceof
를 사용할 수 없습니다. 대신 컬렉션의 원소 타입을 확인하기 위해서는instanceof
를 사용하여 컬렉션 타입을 확인한 후, 해당 컬렉션의 각 원소를 검사해야 합니다.
- connection과 관련된 timeout 여러개가 있다
- connection timeout : 연결 시도 시 일정 시간 내에 연결이 이루어지지 않으면 타임아웃 발생
- command timeout: query timeout, read timeout 데이터베이스 명령이 일정 시간 내에 완료되지 않을때 발생하는 타임아웃
- session timeout: 일정 시간 동안 활동이 없으면 연결을 닫는 타임아웃
- transaction timeout: 트랜잭션이 일정시간 내에 완료되지 않으면 타임아웃
- connection pool과 관련된 timeout
- connection timeout: 새로운 연결을 얻기 위해 풀에서 연결을 기다리는 최대 시간. db connection을 생성하기를 기다리거나, 이미 최대로 생성되어있다면 커넥션이 큐에 반환되기를 기다리는 시간
- idle timeout: 풀에 있는 유휴 연결이 얼마나 오래 유지될 수 있는지에 대한 시간. 마지막으로 커넥션이 사용된 시간 이후로 idle timeout이 지나면 해제됨
- 소켓도 타임아웃이 있음
-
ServerSocket.accept()
는 클라이언트 연결 요청이 들어올 때까지 무기한 블록됩니다. -
ServerSocket.setSoTimeout(int timeout)
메서드를 사용하여 블로킹 타임아웃을 설정할 수 있습니다. 타임아웃이 설정된 경우, 지정된 시간 내에 연결 요청이 없으면SocketTimeoutException
이 발생합니다. -
socket.setSoTimeout
: 소켓이 데이터를 읽는 동안 지정된 시간이 초과되면SocketTimeoutException
을 발생시킵니다. 그러나, 이 방법은 데이터 읽기 동안만 작동하며, 다른 경우에는 소켓을 닫지 않습니다.
- 역할: boundary로 구획된 구조체 형식을 통해서 복수개의 파일 업로드 등, 다양한 데이터를 송신
- form 태그에 enctype = multitype /form -data를 보내면 header에는 multipart/form-data; boundary = “”로 들어옴
- 경계선을 정의하는 문자열은 브라우저에서 랜덤으로 생성. 입력된 파라미터 값에 사용되지 않은 문자를 조합하여
- 바운더리 WebKitFormBoundary~
- multipart 요청 형식
POST /upload HTTP/1.1
Host: example.com
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="description"
Sample file upload
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="file"; filename="sample.txt"
Content-Type: text/plain
(content of the file)
------WebKitFormBoundary7MA4YWxkTrZu0gW--
-
각 파트 시작:
-----WebKitFormBoundary7MA4YWxkTrZu0gW
(앞에-
가 붙음) -
종료 boundary:
-----WebKitFormBoundary7MA4YWxkTrZu0gW--
(앞뒤에-
가 붙음) - content disposition 파트의 성격, 파일인지 일반 폼 필드인지 나타냄
- form-data: 폼 데이터의 일부임을 나타냄
- name: 해당 파트의 이름 폼 필드의 이름
- filename: 파일일 경우, 파일 이름
- content-type은 포함될수도 포함되지 않을 수도 있음.
- 기본적으로 content-type이 지정되지 않은경우 text/plain으로 간주. 폼 필드 데이터가 일반 텍스트로 전송되기 때문. 파일 확장자의 유형으로 생각.
- 미리 초기화
public HttpRequest(ServerContext context) {
this.context = context;
this.cookies = new ArrayList<>();
this.parameters = new HashMap<>();
this.parts = new ArrayList<>();
}
- 지연 초기화
public HashMap getParameters(){
if(this.parameters == null){
this.parateters = new HashMap();
}
}
return this.parameters;
-
안전성: 초기화되지 않은 컬렉션에 접근하려고 하면
NullPointerException
이 발생할 수 있습니다. 미리 초기화하면 이러한 예외를 방지할 수 있습니다. - 메모리 사용: 각 컬렉션 객체는 초기화될 때 메모리를 차지합니다. 만약 해당 컬렉션이 빈 상태로 남아 있게 되면, 이는 불필요한 메모리 사용이 될 수 있습니다.
- 필드들이 항상 사용되는 것이 아니라면, 지연 초기화(lazy initialization) 패턴을 사용할 수 있습니다. 지연 초기화는 해당 컬렉션이 실제로 필요할 때까지 초기화를 미루는 방법입니다.
-
~/upload
경로를 사용하면 Java 애플리케이션에서 제대로 작동하지 않는 이유는 Java가 일반적으로 쉘 확장자(예:~
를 홈 디렉토리로 확장하는 것)를 인식하지 못하기 때문입니다.~
를 사용하여 홈 디렉토리를 참조하는 것은 Unix 계열 쉘(Bash, Zsh 등)의 기능이며, Java에서는 직접적으로 지원되지 않습니다. - user.~ 시스템 속성은 모든 주요 운영 체제(Windows, macOS, Linux)에서 지원됩니다.
-
사용자 홈 디렉토리: 각 사용자마다 별도의 디렉토리가 필요한 경우 사용자 홈 디렉토리 아래에 저장하는 것이 유용합니다.
System.getProperty("user.home")
-
애플리케이션 디렉토리: 일반적으로 애플리케이션이 실행되는 디렉토리.
System.getProperty("user.dir")
-
사용자 홈 디렉토리: 각 사용자마다 별도의 디렉토리가 필요한 경우 사용자 홈 디렉토리 아래에 저장하는 것이 유용합니다.