Skip to content

김수현 4주차 학습일지

김수현 edited this page Jul 23, 2024 · 2 revisions

1. h2 database mode

  • h2 database 경량의 데이터베이스. inmemory rdmbs로 사용할 수 있어 테스트 및 개발용으로 사용됨.
  1. in memory mode : 메모리에 저장하여 매우 빠른 읽기 및 쓰기 주로 테스트 및 개발 용으로 사용 jdbcUrl : jdbc:h2:mem:databsename
  2. embedded mode: 데이터베이스가 어플리케이션 프로세스 내부에서 실행되는 모드, 데이터베이스 파일은 로컬 파일 시스템에 저장됨. 어플리케이션이 종료되면 db 서버도 종료.jdbcUrl: jdbc:h2:~/databsename
  3. server mode: 서버가 독립적으로 실행되어 네트워크를 통해 접근. 여러 어플리케이션에서 데이터베이스를 공유. h2 서버를 실행해두고 연결. 어플리케이션이 종료되도 서버는 독립적으로 실행되기 때문에 종료되지 않음jdbc:h2:tcp://loclahost/~testdb
  • h2-version.jar에는 h2 database + h2 jdbc driver가 포함되어있음
    • database : db 서버
    • jdbc driver: db와 통신하기 위한 인터페이스 구현체

2. jdbc

2-1. jdbc vs jdbc dvier

  • JDBC : 어플리케이션과 관계형 데이터베이스와 상호작용할 수 있도록 하는 표준 API 표준 라이브러리의 일부로,인터페이스를 정의.
  • JDBC Driver JDBC API를 를 구현한 라이브러리. 각 데이터베이스 벤더는 JDBC 드라이버를 제공

2-2.jdbc template

  • 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
    }
}

3.connection pool

3-1.HikariCP와 Tomcat Connection Pool

  • was의 역할? JDNI를 통해 중앙집중적으로 연결 풀링
  • ArrayblokcingQueue로 커넥션 관리. 멀티스레드에서 접근 가능. → 커넥션 모두 사용중이라면 array blocking queue 내부 대기 큐에 대기.
  • HikariCP: 매우 높은 성능과 낮은 메모리 사용량을 목표로 설계된 커넥션. Spring Boot는 HikariCP를 기본 커넥션 풀로 사용하며, 이를 통해 애플리케이션의 데이터베이스 연결을 효율적으로 관리합.
  • Tomcat Connection Pool: Apache Tomcat에서 제공하는 커넥션 풀 라이브러리로, Tomcat을 WAS로 사용하는 경우에 자주 사용됩니다. Spring Boot는 기본적으로 HikariCP를 사용하지만, 필요한 경우 Tomcat Connection Pool을 사용할 수 있도록 설정할 수 있음

3-2.DBDB의 ArrayBlockingQueue의 capacity

  • ArrayblokcingQueue는 고정 크기의 배열을 기반으로 구현된 블로킹 큐임. head, tail을 추적.
  • timeout
    • InterruptedException or null 주어진 시간동안 요소를 얻지 못하면 null을 반환. 블로킹 메서드로 현재 스레드가 기다리는 동안 인터럽트 될 경우 interrupted exception이 발생함.
    • offer : 넣을 공간이 생길때까지 기다림 지나면 false.

3-3.connection pool 구현시 고민사항

  • minIdle로 잡는게 적당할까 아니면 maxSize로 잡는게 적당할까? → maxSize
  • maximum-pool-size는 커넥션 풀에서 유지할 수 있는 최대 커넥션 수. 이 값으로 ArrayBlockingQueue의 용량을 설정하면, 커넥션 풀의 최대 크기만큼의 커넥션을 수용할 수 있습니다. 이는 풀의 크기를 완전히 활용할 수 있게 함.
  • Connection pool 인터페이스 및
    • pooled connection 구현: connection proxy.
    • close()
    • getConnection()
  • IdleTimeoutHandler: daemon 으로 설정. 주기적으로 검사, idleTimeoutd의 반만큼 기다린 후 커넥션 풀을 검사.

4. 기타

4-1.인터페이스의 변수

Java 인터페이스는 변수를 가질 수 있지만, 그 변수들은 암묵적으로 public, static, 그리고 final로 선언됩니다. 즉, 인터페이스에 선언된 변수는 상수(constant)로 취급됩니다.

4-2.nano time vs timemillis

  • 시스템 호출: System.currentTimeMillis()는 운영체제의 시스템 시계를 참조하기 위해 시스템 호출을 사용할 수 있습니다. 그러나 JVM 구현에 따라 내부적으로 캐시된 값을 사용할 수도 있습니다. 대부분의 경우, 이 메서드는 운영체제의 시스템 시계를 직접 호출하므로 시스템 호출로 간주될 수 있습니다.
  • System.nanoTime()을 사용할 수도 있습니다. System.nanoTime()은 고해상도의 시간 측정을 위해 주로 사용되며, 시스템 호출을 피하고 더 정확한 타이밍 측정을 제공합니다.

4-3.AtomicInteger

  • AtomicInteger는 lock-free 방식으로 원자적 연산을 제공합니다.
  • 내부적으로 CAS(Compare-And-Swap) 연산을 사용하여 성능을 높이고, 경쟁 상황에서 더 나은 성능을 보입니다.
  • AtomicInteger.incrementAndGet(), AtomicInteger.decrementAndGet() 등의 메서드를 통해 간단하게 원자적 증가/감소 연산을 수행할 수 있습니다.

4-4.Synchronized

  • synchronized 블록은 특정 코드 블록을 한번에 하나의 스레드만 접근할 수 있도록 보장합니다.
  • synchronized 블록 내에서는 모든 연산이 일관성을 유지하며 실행됩니다.
  • 경쟁 상황이 적은 경우 성능에 큰 영향을 미치지 않지만, 경쟁이 많은 경우 lock을 획득하고 해제하는 데 있어 오버헤드가 발생할 수 있습니다.

5.톰캣 의 요청라인 파싱

  • Http11Processor: HTTP/1.1 요청을 파싱하고 처리하는 주요 클래스입니다.
  • Http11InputBuffer: 요청 바디를 읽고 버퍼링하는 클래스입니다.
  • Request: 파싱된 요청 데이터를 보유하는 클래스입니다.
  • RequestParser: 요청 라인을 파싱하는 유틸리티 클래스입니다.
  • "요청 바디를 읽고 버퍼링한다"는 말은, 클라이언트가 보낸 HTTP 요청의 본문 데이터를 네트워크 소켓으로부터 읽어 메모리나 디스크의 버퍼에 임시로 저장하는 것을 의미합니다. 이 작업은 네트워크 통신의 비동기적 특성으로 인해 데이터를 효율적으로 처리할 수 있도록 도와줍니다. 이 과정은 데이터가 도착하는 즉시 처리할 수 있도록 하기 위함이며, 여러 번의 작은 네트워크 호출을 줄여 성능을 향상시킵니다.
  • 그냥 socket의 stream자체를 request 객체에 전달하지 않고 RequestInputBuffer으로 읽어서 전달하는 이유
    • 네트워크 지연 최소화: 네트워크에서 데이터를 읽는 작업은 비교적 느릴 수 있습니다. 데이터를 한 번에 큰 덩어리로 읽어들여 버퍼에 저장하면, 여러 번의 작은 네트워크 호출을 줄일 수 있어 성능이 향상됩니다.
    • 버퍼에 저장된 데이터를 필요할 때 여러 번 읽거나 가공할 수 있습니다. 소켓 스트림은 일회성으로 읽기 때문에, 데이터를 여러 번 읽거나 가공하기 어려울 수 있습니다.
    • 큰 데이터 처리: 요청 본문이 큰 경우, 이를 메모리에 한 번에 바이트 배열로 저장하면 메모리 사용량이 급격히 증가할 수 있습니다. 특히, 동시에 여러 요청을 처리하는 서버 환경에서는 큰 요청 본문을 다수 처리해야 할 수 있으므로 메모리 부족 상황이 발생할 수 있습니다.
    • 스트리밍 방식: 스트림을 사용하면 요청 본문을 필요할 때만 읽어들일 수 있습니다. 이는 메모리를 한꺼번에 소비하지 않도록 하여 효율성을 높입니다.
    • 파싱의 유연성: 데이터를 메모리 버퍼에 저장하면, 필요할 때 다양한 방식으로 데이터를 파싱할 수 있습니다. 예를 들어, JSON 데이터, XML 데이터, 멀티파트 데이터 등을 적절한 시점에 처리할 수 있습니다.
  • HttpServletRequest 객체가 제공하는 InputStream은 소켓의 InputStream과 직접적으로 연결되어 있지만, 여러 단계의 처리를 거칩니다. 기본적으로, 서블릿 컨테이너는 소켓의 InputStream을 읽고 이를 통해 데이터를 효율적으로 처리할 수 있도록 다양한 버퍼링과 래핑을 수행합니다. 하지만, HttpServletRequestgetInputStream() 메서드로 얻은 스트림은 여전히 실시간으로 소켓에서 데이터를 읽는 스트림입니다.
  • 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());
    }

    // 기타 메서드 생략
}

6. ServletRequest, HttpServletRequest, Request의 차이

Tomcat의 요청 처리 과정에서 ServletRequest, HttpServletRequest, 그리고 Request 객체는 각각 다른 역할을 수행합니다. 이들 간의 차이와 역할을 이해하는 것은 Tomcat 내부 동작을 이해하는 데 매우 중요합니다.

6-1. ServletRequest 인터페이스

  • 정의: javax.servlet.ServletRequest는 서블릿 컨테이너가 클라이언트의 요청 정보를 서블릿에 제공하기 위해 사용하는 인터페이스입니다.
  • 역할: HTTP 프로토콜에 구애받지 않고, 일반적인 요청 정보를 제공하는 역할을 합니다. 예를 들어, 요청 파라미터, 속성, 프로토콜 정보 등을 제공합니다.
  • 주요 메서드:
    • getAttribute(String name): 요청의 특정 속성을 반환합니다.
    • getParameter(String name): 요청의 특정 파라미터를 반환합니다.
    • getInputStream(): 요청 본문을 읽기 위한 ServletInputStream을 반환합니다.

6-2. HttpServletRequest 인터페이스

  • 정의: javax.servlet.http.HttpServletRequestServletRequest를 확장한 인터페이스로, HTTP 프로토콜을 사용하는 클라이언트 요청의 정보를 서블릿에 제공하는 데 사용됩니다.
  • HttpServletRequest extensd ServletRequest
  • 역할: HTTP 요청에 특화된 메서드를 추가로 제공하여 HTTP 요청의 상세한 정보를 제공합니다. 예를 들어, HTTP 메서드(GET, POST 등), 헤더 정보, 쿠키, 세션 등을 다룹니다.
  • 주요 메서드:
    • getMethod(): HTTP 메서드를 반환합니다.
    • getHeader(String name): 특정 헤더의 값을 반환합니다.
    • getCookies(): 요청에 포함된 모든 쿠키를 반환합니다.
    • getSession(): 현재 세션을 반환하거나, 세션이 없으면 새로 생성합니다.
  • 구현 클래스 Request가 HttpServletREquest의 구현

6-3. Request 클래스 (Tomcat의 구현체)

  • 정의: org.apache.catalina.connector.Request 클래스는 Tomcat에서 HttpServletRequest 인터페이스를 구현한 클래스입니다.
  • 역할: Tomcat 내부에서 HTTP 요청을 처리하고, 이를 서블릿 API의 HttpServletRequest 인터페이스로 변환하는 역할을 합니다. Tomcat의 내부 요청 처리 로직과 서블릿 컨테이너의 상호작용을 관리합니다.

6-4. RequestFacade 클래스: Request 객체를 래핑하여 서블릿에 전달되는 클래스입니다.

  • Tomcat의 경우, 서블릿에 전달되는 HttpServletRequest 구현체는 RequestFacade 객체입니다. RequestFacade는 내부적으로 Request 객체를 사용하여 요청 정보를 제공합니다.

7. 톰캣 패키지

coyote 패키지는 저수준의 네트워크 작업을 처리하고, catalina 패키지는 서블릿 컨테이너의 상위 수준의 작업을 처리합니다.

  • HTTP 커넥터: coyote 패키지는 HTTP 프로토콜을 구현하며, 네트워크 소켓을 통해 클라이언트의 HTTP 요청을 수신합니다.
  • 저수준 프로세싱: 요청 라인, 헤더, 본문 등의 기본적인 HTTP 요청을 파싱합니다.
  • 서블릿 컨테이너: catalina 패키지는 서블릿 API를 구현하고, 서블릿 라이프사이클을 관리합니다.
  • 상위 수준 프로세싱: 서블릿, 필터, 리스너 등의 컴포넌트를 관리하고, 요청을 이들 컴포넌트로 전달하여 처리합니다.

8. Statement와 PreparedStatement의 차이

  • Statement는 정적 query, 실행할때마다 새로 파싱되고 실행됨
    • SQL indection 공격 가능
  • PreparedStatement 미리 컴파일된 SQL 쿼리를 실행. 쿼리에 매개변수를 바인딩할 수 있음.
    • 반복적으로 실행되는 쿼리나, 사용자 입력값을 포함하는 쿼리
  • ResultSet 객체를 닫아줘야하는 이유: ResultSet 객체는 반드시 명시적으로 닫아줘야 합니다. ResultSetConnectionStatement 객체처럼 리소스를 많이 소모할 수 있으므로, 사용이 끝난 후에는 반드시 닫아주는 것이 좋습니다. ResultSet을 닫지 않으면 데이터베이스 연결이 해제되지 않고 계속 유지될 수 있어 메모리 누수 및 기타 리소스 누수 문제가 발생할 수 있습니다. ResultSet을 명시적으로 닫지 않으면 데이터베이스 연결 자체가 닫히지 않는 것은 아님, ResultSet과 관련된 리소스가 해제되지 않아 리소스 누수가 발생할 수 있음. ResultSet 객체는 Statement 객체에 종속적입니다. Statement를 닫으면 관련된 ResultSet도 닫힙니다. 하지만, 명시적으로 각각의 객체를 닫는 것이 권장됩니다. 이렇게 하면 리소스를 명확하게 관리할 수 있고, 코드의 가독성 및 유지보수성이 높아집니다. 닫히지 않은 ResultSet 객체가 많아지면, 세션당 열 수 있는 커서 수 제한을 초과할 수 있습니다. 이로 인해 새로운 쿼리를 실행할 수 없게 됩니다.
  • 데이터베이스 시스템에서 커서(Cursor)는 쿼리 결과 집합을 순회하는 데 사용되는 데이터베이스 객체입니다. 커서는 특정 위치에서 결과 집합의 행을 가리키며, 순차적으로 행을 처리할 수 있게 해줍니다.

9. Repository vs Dao

  • 둘다 데이터 엑세스 레이어에서 데이터베이스 작업을 수행
  • DAO: 데이터베이스 테이블과 직접 매핑되는 클래스와 메서드를 정의 데이터베이스의 CRUD를 수행
  • Repository: 보다 높은 추상화, 도메인 객체의 컬렉션을 관리하는 것처럼 동작, 데이터 소스와 상관없이 동작. ORM 기술을 사용해서 도메인 모델과 데이터베이스 간의 매핑을 관리

10.instance of로 generic 타입을 체크할 수 없는 이유

  • if (!parameters.get("posts") instanceof List) 처럼 컬렉션에 대해서 class type을 체크할 후 없다.
  • Java에서는 제네릭 타입은 런타임에 타입 정보를 유지하지 않기 때문에 직접적으로 instanceof를 사용할 수 없습니다. 대신 컬렉션의 원소 타입을 확인하기 위해서는 instanceof를 사용하여 컬렉션 타입을 확인한 후, 해당 컬렉션의 각 원소를 검사해야 합니다.

12. 타임아웃

12-1. DB Connection과 관련된 타입아웃 timeout

  • 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이 지나면 해제됨

12-2.Socket 타임아웃

  • 소켓도 타임아웃이 있음
  • ServerSocket.accept()는 클라이언트 연결 요청이 들어올 때까지 무기한 블록됩니다.
  • ServerSocket.setSoTimeout(int timeout) 메서드를 사용하여 블로킹 타임아웃을 설정할 수 있습니다. 타임아웃이 설정된 경우, 지정된 시간 내에 연결 요청이 없으면 SocketTimeoutException이 발생합니다.
  • socket.setSoTimeout : 소켓이 데이터를 읽는 동안 지정된 시간이 초과되면 SocketTimeoutException을 발생시킵니다. 그러나, 이 방법은 데이터 읽기 동안만 작동하며, 다른 경우에는 소켓을 닫지 않습니다.

13. multipart/form-data

  • 역할: boundary로 구획된 구조체 형식을 통해서 복수개의 파일 업로드 등, 다양한 데이터를 송신
  • form 태그에 enctype = multitype /form -data를 보내면 header에는 multipart/form-data; boundary = “”로 들어옴
  • 경계선을 정의하는 문자열은 브라우저에서 랜덤으로 생성. 입력된 파라미터 값에 사용되지 않은 문자를 조합하여
  • 바운더리 WebKitFormBoundary~
  • multipart 요청 형식

Untitled

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으로 간주. 폼 필드 데이터가 일반 텍스트로 전송되기 때문. 파일 확장자의 유형으로 생각.

14.컬렉션 타입의 지연 초기화

  • 미리 초기화
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) 패턴을 사용할 수 있습니다. 지연 초기화는 해당 컬렉션이 실제로 필요할 때까지 초기화를 미루는 방법입니다.

15. 이미지 저장 경로

  • ~/upload 경로를 사용하면 Java 애플리케이션에서 제대로 작동하지 않는 이유는 Java가 일반적으로 쉘 확장자(예: ~를 홈 디렉토리로 확장하는 것)를 인식하지 못하기 때문입니다. ~를 사용하여 홈 디렉토리를 참조하는 것은 Unix 계열 쉘(Bash, Zsh 등)의 기능이며, Java에서는 직접적으로 지원되지 않습니다.
  • user.~ 시스템 속성은 모든 주요 운영 체제(Windows, macOS, Linux)에서 지원됩니다.
    • 사용자 홈 디렉토리: 각 사용자마다 별도의 디렉토리가 필요한 경우 사용자 홈 디렉토리 아래에 저장하는 것이 유용합니다. System.getProperty("user.home")
    • 애플리케이션 디렉토리: 일반적으로 애플리케이션이 실행되는 디렉토리. System.getProperty("user.dir")

👼 개인 활동을 기록합시다.

개인 활동 페이지

🧑‍🧑‍🧒‍🧒 그룹 활동을 기록합시다.

그룹 활동 페이지

🎤 미니 세미나

미니 세미나

🤔 기술 블로그 활동

기술 블로그 활동

📚 도서를 추천해주세요

추천 도서 목록

🎸 기타

기타 유용한 학습 링크

Clone this wiki locally