목차

  1. 소개
  2. 참고 자료

소개

Java에서는 보통 웹 개발을 진행 시 Log관련된 부분들은 어떤 방식으로 사용할까요?

자바 웹 백엔드개발을 하고 있는 주니어 레벨의 개발자분들은 ( 저 포함 ) 제가 느꼈을 때 라이브러리를 단순 사용하기만 하며 로그 라이브러리를 직접 뜯어보고 응용을 해보신 분들은 없을 거라 생각합니다.

하지만 이러한 로그관련된 라이브러리도 자바에 내장된 로그 기능을 이용해서 개발되었다고 이해할 수 있는데요, 오늘은 이 내부 Log관련된 기능들이 어떤 식으로 정의되고 사용되는지에 대하여 깊이 알아보려고 합니다.

우선 Log4j2에 대한 설명으로 시작하려고 합니다.

Log4j2

  • Log4j2 는 자바 로깅 라이브러리입니다. Log4j2 를 설명하기 이전 여러 로깅 라이브러리에 대해 간단하게 소개하고 넘어가겠습니다.
  • 자바의 로깅 라이브러리는 다음과 같이 있습니다.
  • java.util.logging.Logger
  • Log4J, log4j2
  • Logback
  • SLF4j
자바의 로깅 라이브러리는 다음과같은 시간순으로 개발되었습니다.

- Log4j → Logback → Log4j2 

여기서 java.util.logging.Logger는 java언어의 java.util.logging 패키지에 속해있는 로깅용 유틸 클래스입니다.

외부 라이브러리 사용이 필요 없으며 파일이나 콘솔에 로그 내용을 출력할 수 있습니다.

​ 이후 log4j는 가장 오래된 로깅 프레임워크로써 Apache의 Java기반 로깅 프레임 워크입니다.

​ 이런 Log4j는 다음과 같은 구성을 나타내며 구성내의 Appender 부분을 해당 문서에서 Custom하여 적용하는 방법에 대한 설명을 이후 설명드릴 예 정입니다.

요소 설명
Logger 출력할 메시지를 Appender에게 전달
Appender 전달된 로그를 어디에 출력할 것인지 결정(Console, File, JDBC 등)
Layout 로그를 어떤 형식으로 출력할 것인지 결정

 

이런 Log4j 는 다음과같이 사용할 수 있습니다.

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

class Log4jLogger {
        private static Logger logger = LogManager.getLogger(Log4jLogger.class);

        void method() {
            logger.info("info log : {}", 1);
        }
}

 

이러한 Log4j의 버전업이 Log4j2 입니다.

 

여러 차이점이 존재하지만 그중에서도 큰 차이라고 생각되는 부분은 Sl4j의 지원 여부라고 생각됩니다.

이어서 Appender에 대한 자세한 설명을 이어가겠습니다.

 

SAS WIKI에서는 Console Appender와 RollingFileAppender, FileAppender에 대한 설명이 나와있지만 그 밖에도 여러 Appender가 존재하며 제가 설정한 방식은 객체를 만들고 적용하였지만 꼭 객체를 만들어야 하는 것은 아닌 걸로 생각됩니다.

 

Appender는 여러 개가 존재하며 공식문서상 나와있는 부분은 다음과 같습니다. log4j2 공식문서

 

Appenders :: Apache Log4j

Jakarta EE 8 and all Java EE applications servers use the legacy javax package prefix instead of jakarta. If you are using those application servers, you should replace the dependencies above with: We assume you use log4j-bom for dependency management. org

logging.apache.org

 

 

이러한 Appender들 중 이번 글에서는 가장 자주 사용되는 RollingFileAppender에 대한 간단한 설명정도만 하고 넘어가려고 합니다.

 

이후 필요에 의해 다른 로그가 필요할 경우 appender를 공식문서를 참고하여 작성 및 등록하면 적용시킬 수 있을 거라 생각합니다.

RollingFileAppender는 FileAppender를 상속하여 로그 파일을 rollover 합니다.(File Appender는 단순히 파일에 로그를 쓰는 것이라 생각하시면 됩니다.) 여기서 rollover는 타깃 파일을 바꾸는 것으로 이해할 수 있습니다. 예를 들어, RollingFileAppender가 타깃 파일로 log.txt에 로그 메시지를 append 하다가 어느 지정한 조건에 다다르면, 타깃 파일을 다른 파일로 바꿀 수 있습니다.

 

ex. 일자별로 로그 파일을 저장하고 싶은 경우, 파일의 크기별로 로그파일을 저장하고 싶은 경우 등등..

 

RollingFileAppender와 함께 동작하는 두 가지 component가 존재합니다. 첫 번째는 RollingPolicy로 rollover에 필요한 action을 정의합니다. 두 번째는 TriggeringPolicy로 어느 시점에 rollover가 발생할지 정의합니다. 간단히 RollingPolicy는 what, TriggeringPolicy는 when을 담당하고 있다 보시면 될 것 같습니다.

 

RollingFileAppender를 사용하기 위해서는 RollingPolicy와 TriggeringPolicy가 모두 필수적으로 필요합니다. 하지만, RollingPolicy는 TriggeringPolicy 인터페이스의 구현체로, 하나의 RollingPolicy만 지정하여도 사용 가능합니다.

RollingFileAppender는 아래의 parameter를 갖습니다. 자세한 내용은 공식문서를 참고해 주시면 좋겠습니다.

 

이 중에서 제가 작성한 코드의 일부분을 통해 설정한 파라미터들을 보여드리겠습니다.

 

public static void rollingFileAppenderInitialize(CustomFileAppender config) {
        ConfigurationBuilder<BuiltConfiguration> builder = ConfigurationBuilderFactory.newConfigurationBuilder();

        builder.setStatusLevel(Level.ALL);
        builder.setConfigurationName("CustomFileAppenderConfiguration");

        // Adding policies and strategy
        ComponentBuilder<?> triggeringPolicy = builder.newComponent("Policies")
            .addComponent(builder.newComponent("TimeBasedTriggeringPolicy")
                .addAttribute("interval", 1)
                .addAttribute("modulate", true))
            .addComponent(builder.newComponent("SizeBasedTriggeringPolicy")
                .addAttribute("size", "10MB"));

        AppenderComponentBuilder appenderBuilder = builder.newAppender("CustomFileAppender", "RollingFile")
            .addAttribute("fileName", config.setFileName())
            .addAttribute("filePattern", config.setFilePattern())
            .addAttribute("immediateFlush", true)
            .addAttribute("append", true)
            .addComponent(triggeringPolicy);

        appenderBuilder.add(builder.newLayout("PatternLayout")
            .addAttribute("pattern", config.setPattern().getConversionPattern()));

        appenderBuilder.addComponent(builder.newComponent("DefaultRolloverStrategy")
            .addAttribute("max", "30"));

        builder.add(appenderBuilder);

//        // Adding logger with filter if available
        LoggerComponentBuilder loggerBuilder = builder.newLogger("WriteLogger", config.setLogLevel());

//        FilterComponentBuilder filterBuilder = builder.newFilter("RegexFilter", Filter.Result.ACCEPT,
//                Filter.Result.DENY)
//            .addAttribute("pattern", ".*(error|fail).*");
//
//        loggerBuilder.add(filterBuilder);

        loggerBuilder.add(builder.newAppenderRef("CustomFileAppender"));
        builder.add(loggerBuilder);

        // Adding root logger
//        builder.add(builder.newRootLogger(config.setLogLevel())
//            .add(builder.newAppenderRef("CustomFileAppender")));

        Configuration configuration = builder.build();

        ((LoggerContext) LogManager.getContext(false)).start(configuration);
        Configurator.initialize(configuration);
        LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
        Configurator.setRootLevel(Level.ALL);

        // Start the appender manually if necessary
        Appender appender = ctx.getConfiguration().getAppender("CustomFileAppender");
        if (appender != null) {
            appender.start();
        }
        configuration.addAppender(appender);
        configuration.getRootLogger().addAppender(appender, null, null);

        // Update the logger context
        ctx.updateLoggers();
    }

해당 이니셜라이즈 메서드에 파라미터로 넘어오는 것은 SAS WIKI에서 설명하고 있는 CustomFileAppender입니다. 해당 객체를 그대로 넘겨주었을 시 다음과 같이 LoggerContext에 Appender를 추가 및 업데이트합니다.

 

그렇다면 한 가지 의문이 들 수 있을 것 같습니다. 해당 클래스객체를 꼭 안 만들어도 적용시킬 수 있는 것이 아닌가 라는 생각이 들 수 있는데 해당 파라미터의 어팬더를 꼭 안 만들어도 되지만 객체의 구현된 내용 일부만을 조금 수정하는 것이 앞으로 여러 개의 Appender 가 늘어남에 따라 속성값을 관리해야 하는 측면에서는 개발하기 수월해질 것 같습니다.

 

ex) Message 국제화와 같이 반복사용되는 문자내용의 관리적인 측면이라 생각하셔도 좋을 것 같습니다.

 

 

이런 식으로 rollingfileappender에 대한 설명은 넘어가겠습니다.

이어서 Sl4j의 간단한 설명을 진행하겠습니다.

 

Sl4j

SLF4J(Simple Logging Facade for Java)는 java.util.logging, logback 및 log4j와 같은 다양한 로깅 프레임 워크에 대한 추상화(인터페이스) 역할을 하는 라이브러리입니다.

 

logger의 추상체로써, 인터페이스 이므로 SLF4j 인터페이스를 사용해서 로깅하게 되면 구현체만 갈아 끼우면 logback이나 log4j 등으로

마이그레이션 할 수 있습니다.

 

구현체로는 logback, log4j2 등이 있습니다.

 

디자인 패턴 중 퍼사드패턴처럼 인터페이스만을 제공되게 하여 보다 더 편리하게 사용할 수 있도록 지원되는 라이브러리입니다.

초기의 Spring은 JCL(Jakarta Commons Logging)을 이용하여 로깅하였으나, GC가 제대로 동작하지 않는다는 단점이 있었고

이를 해결하기 위해 도입한 것이 SLF4j이며 JCL의 문제를 해결하기 위해 클래스 로더 대신에 컴파일 시점에 구현체를 선택하도록 하였습니다.

참고 자료

'JAVA' 카테고리의 다른 글

[JAVA & Database] JDBC, MyBatis, JPA 의 차이점  (1) 2022.09.26
[Java] 언어의 특징  (2) 2022.09.09
[JAVA]직렬화 - Serializable란?  (0) 2022.05.16
[JAVA]DAO, DTO, VO 정리.  (0) 2021.12.25

<!doctype html>

[Java & DataBase] JDBC, JPA, Mybatis 차이점


스프링이나 이클립스를 통한 자바 개발을 진행하는 개발자라면 데이터베이스와 연동하는 과정을 개발해 본 경험이 있을 것입니다.

그중 관계형 데이터베이스(RDBMS)를 이용할 때 JDBC, JPA, Mybatis를 이용해 볼 수 있는데 구체적으로 어떠한 차이점이 있는지 궁금증이 생겨 개념 정리 및 학습을 진행하기 위해 한번 정리해볼까 합니다.

 

순서는 제가 학습했던 순서인 JDBC -> Mybatis -> JPA 순으로 설명하겠습니다

 

1. JDBC(Java Database Connectivity)


 

JDBC는 말 그대로 자바 프로그램이 데이터베이스와 연결할 수 있는 기능을 제공하는 프로그래밍 인터페이스입니다.

 

이러한 JDBC를 이용하기 위해서는 java.sql.Driver, java.sql.Connection, java.sql.Statement, ResultSet, PreparedStatement, CallableStatement 등을 이용 볼 수 있습니다. 실제 개발을 진행할 때는 import java.sql.*; 로 한 번에 가져와서 구현했습니다.

 

간단한 순서는 다음과 같습니다.

  1. Driver Load
  2. Connector Object Create -> Connection
  3. Statement Object Create
  4. Return Value Save Result Set Object
  5. Close

 

import java.sql.*;

public class ConnectDB{
  public static void main(String[] args)
  {
    try{
      //Connection Object Create
      Connection conn = null;

      //Connection
      String url = "jdbc:mysql://localhost/DB_Name";

      conn = DriverManager.getConnection(url, "DB_user", "DB_user_password");
      System.out.println("연결 성공");
    }
    catch(ClassNotFoundException e) // Error Case 1
    {
      System.out.println("Driver Loading Fail");
    }
    catch(SQLException e) // Error Case 2
    {
      System.out.println("Error : " + e);
    }
    finally
    {
      try
      {
        if(conn != null && !conn.isClosed())
        {
          conn.close();
        }
      }
      catch(SQLException e) // Error Case 3
      {
        e.printStackTrace();
      }
    }
   }
}

이러한 불편은 점차 jdbc의 역사가 발전해나가며 SQL Mapper와 ORM을 이용할 경우 코드상 활용하는 방식이 간결해졌습니다

추가로 jdbc를 이용할 때 Driver는 DataBase회사별 다르기 때문에 "com.mysql.jdbc.Driver" 와 같이 직접 찾아서 입력해야 합니다.

 

쿼리를 요청하는 코드를 확인해보겠습니다.

 

import java.sql.*;

public class SelectCase
{
  public static void main(String[] args)
  {
    Connection conn = null;
    Statement stmt = null;
    ResultSet rs = null;
    
    try{
      // 1. Drvier Load
      Class.forName("com.mysql.jdbc.Driver");
      
      // 2. Connection
      String url = "jdbc:mysql://localhost/DB_Name";
      conn = DriverManager.getConnection(url, "DB_User", "DB_Password");
      
      // 3. Statement Object Create
      stmt = conn.createStatement();
      
      // 4. Write Query
        // 1) Select 할 때 * 로 모든 칼럼을 가져오기보단 특정 컬럼을 가져오는것이 좋다.
        // 2) 원하는 결과는 쿼리로써 정리하고 후작업은 권장되지않음
        // 3) 쿼리를 한 줄로 쓰기 어려운 경우 들여쓰기를 사용해도 되지만 띄어쓰기에 유의해야한다.
      String sql = "SELECT name, owner, data_format(birth, '%Y년%m월%d일' date FROM table)";
      
      // 5. Execute Query
      rs = stmt.executeQuery(sql);
      
      // 6. 실행결과 출력하기
      while(rs.next())
      {
        //Record colunm은 1부터 시작하며 DataType에 맞게 getInt, getString등을 이용
        String name = rs.getString(1);
        String owner = rs.getString(2);
        String date = rs.getString(3);
        
        System.out.println(name + " " + owner + " " + date);
      }
    }
    catch( ClassNotFoundException e){
      System.out.println("드라이버 로딩 실패");
    }
    catch( SQLException e){
      System.out.println("에러 " + e);
    }
    finally // Close Phase
    {
      try
      {
          if( conn != null && !conn.isClosed())
          {
            conn.close();
          }
          if( stmt != null && !stmt.isClosed())
          {
            stmt.close();
          }
          if( rs != null && !rs.isClosed())
          {
            rs.close();
          }
      }
      catch( SQLException e)
      {
        e.printStackTrace();
      }
  }
}

 

 

 

물론 간결하다고 항상 좋은 것은 아니고 장, 단점이 존재합니다

위 코드는 MySQL 일 경우의 과정이고 이 JDBC 역시 Java에서 제공하는 API이기에 개발자 입장에서는 데이터베이스마다 사용 쿼리를 각 DB마다 작성해주어야 합니다(DB의존적인 쿼리문).

 

또한 연결, 연결 해제 등의 과정의 코드를 반복적으로 작성해야 하기에 코드가 많이 길어집니다.

 

이 방식도 역시나 기존 애플리케이션의 디비 연동 과정 보다도 간소화되었지만 여기서 끝이 아닌 새로운 2가지 기술이 또 등장합니다.

 

SQL Mapper , ORM

 

SQL Mapper

SQL Mapper의 대표적인 기술로는 아래에서 설명할 MyBatis가 있으며 쿼리문을 파일로 관리할 수 있다는 점에서 장점이 있습니다.

또 위 JDBC의 단점인 반복적인 코드를 줄이고 응답 결과 자체를 객체로 반환합니다.

 

ORM(Object-Relational Mapping)

ORM은 SQL문을 작성하지 않아도 동적으로 생성해주며 DB마다 다르게 사용하는 SQL도 동일하게 작성 가능합니다.

단점으로는 러닝 커브가 존재하고, SQL 작성이 없지만 테스트 과정이나 기타 여러 상황을 고려해 개발자는 알아야 합니다.

자세한 내용은 밑에 3. JPA에서 더 설명하겠습니다

 

2. Mybatis


 

  • Mybatis는 Persistence framework입니다.
  • MyBatis는 위 사용했던 JDBC를 통해 데이터베이스에 액세스 하는 작업을 캡슐화하고 일반 SQL 쿼리, 저장 프로 시저 및 고급 맵핑을 지원해 중복작업을 제거합니다.
  • 또 SQL Query문은 한 파일에 구성해서 프로그램 코드와 SQL을 분리해 개발할 수 있는 장점이 있습니다.
  • 이렇게 파일에 구성하기 때문에 복잡한 쿼리나 다이내믹한 쿼리에 강하며 단점으로는 비슷한 쿼리가 많아질 수 있는 단점이 있습니다.

 

JDBC와는 다르게 ResultType, ResultClass 등 VO를 사용하지 않고 결과를 DTO, MAP에 맵핑해 사용할 수 있습니다.

mybatis

공식 문서는 다음과 같습니다. - https://mybatis.org/mybatis-3/getting-started.html

 

 

3. JPA


아래 사진은 NoSQL인 MongoDB를 JPA인 MongoRepository를 활용하기 위해 제가 그렸던 Class Diagram입니다.

mongo

사실 간결하게 표현되어 이해하기 어려울 수 있으나 실제 공식문서를 들어가 보면 인터페이스들이 많이 엮여있는 구조로 되어있다는 걸 알 수 있습니다. (비어있는 interface도 존재합니다)

JPA는 위 Mybatis와 JDBC와는 다르게 사용 명세를 많이 참조해야 한다는 단점이 있었습니다.

개인적으로 프로젝트에 적용하기 위해 쿼리문을 작성해보고 이를 함수 명세로 표현하기 위해서 다시 JPA공식문서를 뒤져보는 작업에 시간을 많이 소요했습니다.

이러한 JPA는 익숙해지기에는 시간이 오래 걸렸으나 한번 익숙해진다면 개발 속도는 빨랐기에 저도 가장 많이 사용해보았습니다.

이런 JPA의 특징으로는 ORM 기술 표준의 인터페이스 모음이며 인터페이스이기 때문에 Hibernate, OpenJPA 등이 JPA를 구현합니다.

또 프로젝트의 규모가 커질 경우 속도 저하, 일관성을 무너뜨리는 문제점, 학습 비용이 비싼 점 등이 존재합니다.

추가로 제가 구현할 때 중첩 쿼리문처럼 복잡할 경우에는 @Query 어노테이션으로 직접 Query를 작성할 수 있어 매우 유연한 구현 방식을 제공하고, 팀원들과 협업 시에도 JPA를 이용하기 위한 함수 네이밍 기준이 있어 가독성도 좋았습니다.

 

코드를 하나씩 다 쳐보며 설명하고 싶으나 시간적인 여유가 없어 현재는 특징 정리 정도로만 하고 추후 재 업데이트를 진행할 예정입니다

 

Reference


 

 

'JAVA' 카테고리의 다른 글

Java - Logging 이란?  (1) 2024.11.12
[Java] 언어의 특징  (2) 2022.09.09
[JAVA]직렬화 - Serializable란?  (0) 2022.05.16
[JAVA]DAO, DTO, VO 정리.  (0) 2021.12.25

[JAVA]JAVA 언어의 특징


자바언어를 이용한 자바 프로그래밍을 생각해보면 객체지향 프로그래밍 OOP(Object Oriented Programming)이라 해도 상관없습니다. 그 이유는 자바언어의 특징을 소개하며 설명을 이어나가겠습니다.

자바 언어는 크게 5가지 특징을 가지는데 아래와 같습니다

  • 객체 지향 프로그래밍(OOP)
  • 자동 메모리 관리(GC)(가비지 컬렉터)
  • 운영체제에 독립적
  • 멀티쓰레드 지원
  • 동적 로딩 지원

첫번째로 객체 지향 프로그래밍의 대표적인 특징은 아래 4가지가 존재합니다.

  • 상속 (Inheritance) : 부모클래스의 속성과 기능을 그대로 이어받아 사용할 수 있게 하고 기능의 일 부분을 변경해야 할 경우 상속받은 자식클래스에서 해당기능을 재정의(수정)해 사용할 수 있는 것입니다.

  • 다형성 (Polymorphism) : 다형성은 상속을 통해 기능을 확장하거나 변경하는 것을 가능하게 해줍니다. 이를 통해 코드의 재사용, 코드길이 감소, 유지보수가 용이해지는 특징이 있습니다. 여기서 크게 두가지 개념으로 오버라이딩(Overriding)과 오버로딩(Overloading)이 있습니다.

    • /*
      **오버라이딩 (Overriding)
      ** - 상위 클래스가 가지고 있는 메소드를 하위 클래스가 재정의해서 사용합니다.
      */
      
      //부모클래스
      class Parent{
        public String name;
        public int age;
        public void Speak(){
          System.out.println("일어나 ~ 학교 가야지 ~");
        }
      }
      //자식클래스
      class Child extends Parent{
        public String name;
        public int age;
        public void Speak(){
          System.out.println("10분만 더 잘게요 ~ ");
        }
      }
      public class Test{
        public static void main(String[] args){
          //자식 클래스의 객체 생성
          Child ch = new Child();
      
          //자식 객체의 변수 설정
          ch.name = "아무개";
          ch.age = 10;
      
          //오버라이딩 함수 호출
          ch.Speak();
        }
      }
      /*
      **오버로딩 (Overloading)
      ** - 같은 이름의 메서드 여러개를 가지면서 매개변수의 유형과 개수가 다르도록 하는 기술
      */
      
      class marine
      {
        public void attack(){
          System.out.println("일어나 ~ 학교 가야지 ~");
        }
        public void attack(int damage){
          System.out.println(damage + "만큼의 피해를 입힙니다.");
        }
        public void attack(String class_name, int damage){
          System.out.println(class_name+"이"+damage"만큼의 피해를 입힙니다.");
        }
      }
      public class Test{
        public static void main(String[] args){
          //marine 클래스의 객체 생성
          marine m = new marine();
      
          //매개변수가 없는 attack 메소드 호출
          m.attack();
      
          //매개변수가 1개 있는 attack 메소드 호출
          m.attack(10);
      
          //매개변수가 2개 있는 attack 메소드 호출
          m.attack("warrior", 30);
        }
      }
  • 캡슐화 (Encapsulation, information Hiding) : 캡슐화는 한 객체에 대해 그 객체가 특정 목적을 위해 필요한 변수나 메소드를 하나로 묶는것을 의미합니다. 즉 접근제한자로 변수를 설정해 함부로 변수의 값을 변경할 수 없게 만들고 대신 getter, setter와 같은 메소드를 통해서 접근할 수 있도록 구현할 수 있습니다.

  • 추상화 (Abstraction) : 공통의 속성, 기능들을 묶어 클래스로구성해 확장성을 넓히는 것을 의미합니다. 추상클래스(추상 메소드를 하나이상 포함한 클래스)를 구성하고 추후의 이 class 또는 interface를 상속받아 구현하여 사용하는 것입니다. 공통 기능으로 묶어서 처음 클래스 또는 인터페이스를 구성했기 때문에 설계에 따라서 확장성이 크게 올라갈 수 있습니다. ex)동물이라는 추상 클래스는 행동 이라는 추상 메소드를 가지고 있고 추후에 양서류 등등으로 나뉘어지더라도 이 행동이라는 추상메소드는 계속 상속받아 각 종류에따라서 다르게 구현될 수 있습니다.

그렇다면 이러한 객체지향 프로그래밍을 쓰는 이유는 무엇인가요? 라는 궁금증이 생길 수 있습니다.

객체지향 프로그래밍은 프로그래밍에서 필요한 데이터를 추상화시켜 상태와 행위를 가진 객체를 만들고 그 객체들 간의 유기적인 상호작용을 통해 로직을 구성하는 프로그래밍 방법입니다.

장단점은 무엇이 있을까? 생각해볼 수 있습니다.

  • 장점
    • 코드 재사용성 증가 - 상속을 통한 확장성 증가
    • 유지보수가 쉬워짐 - 절차 지향 프로그래밍 에서는 코드를 수정해야할 때 하나하나 수정해야하지만 객체지향프로그래밍은 상속받은 부모 클래스의 내용만 수정하면 하위 상속받은 자식클래스의 코드수정을 하지 않아도 됩니다.
    • 대형 프로젝트에 적합 - 클래스 단위로 모듈화시켜 개발할 수 있기 때문에 업무분담하기 쉬워집니다.
  • 단점
    • 처리속도가 상대적으로 느려집니다.
    • 객체가 많으면 용량이 커질 수 있습니다.
    • 설계시 많은 시간과 노력이 필요합니다.

두번째로 자동메모리관리(GC) 가비지 컬렉션을 설명하겠습니다.

Garbage collection(GC)는 메모리관리 기법 중 하나로 프로그램이 동적으로 할당했던 메모리 영역 중 필요없게 된 영역을 해제하는 기능입니다.

C,C++언어같은 경우 동적할당을 받아 배열또는 객체를 생성하게 될 경우 힙메모리 영역에 저장되고 이 부분의 메모리가 할당된 걸 해제하기위해 개발자가 직접 free해주어야합니다. 그러나 자바언어의 경우에는 모든 객체가 클래스기반이며 참조변수 같은경우에는 기본으로 힙에 저장되기때문에 애초에 힙에 저장되는 값들이 많습니다. 이러한 값들을 주기적으로 관리해주기위해 GC가 존재하기도하며 직접 개발자가 System.gc() 를 호출해서 해제할 수 있으나 권장되지 않는것으로 알고있습니다.

세번째로는 운영체제에 독립적입니다.

이말의 의미를 명확하게 이해하려면 JVM(Java Virtual Machine)을 이해하고 있어야 합니다. 자바의 개발 환경과 배포환경이 다를 경우 프로그램을 다시 컴파일 할 필요 없이 실행가능합니다. 그 이유는 JVM은 별도의 Java Compiler를 통해 사용자의 코드를 Byte코드로 변환하고 모든 자바 프로그램은 이론적으로 CPU나 운영체제의 종류와 무관하게 동일하게 동작합니다.

마지막으로 동적로딩, 멀티쓰레드 지원 입니다.

자바는 한 프로그램 내에서 여러 쓰레드가 동작할 수 있는 환경을 제공합니다. C,C++은 운영체제의 도움을 받아 멀티쓰레드를 지원하지만 자바는 이런 운영체제의 지원없이 멀티쓰레드를 이용한 프로그래밍이 가능합니다.

멀티쓰레드를 구현하는 방식음 다음과 같이 두가지가 있습니다.

1) Thread Class (화이트박스 방식)

  • 자바에서 쓰레드를 만들기위해 Thread Class 를 상속받아 쓰레드를 생성합니다.
    2) Runnable Interface (블랙박스 방식)
  • 위 화이트박스 방식과 비슷하게 Thread class와 같이 자바에서 스레드를 실행시키는 인터페이스 이며 다중상속이 지원되기 때문에 Thread class보다 많이 사용됩니다.

추가로 저는 C,C++에서 철학자 문제를 가지고 멀티쓰레드와 멀티 프로세스개념을 이해하며 학습한 적이 있는데 이러한 멀티쓰레드와 멀티프로세싱은 교착상태(데드락)문제를 중요하게 다루고있습니다. 여러개의 쓰레드 또는 프로세스가 공유자원을 이용할 때의 문제점을 다루는 것인데 그당시는 뮤텍스, 세마포어를 활용해 이러한 공유자원문제를 해결하는 방식을 학습했던 적이 있었습니다. 중요한 개념인 만큼 밑에 깃허브 링크를 남겨두겠습니다.

지금은 이런게 있구나 기억하고 넘어가시는게 좋을 것 같습니다.

철학자 문제 - https://github.com/ukjinlee66/42_Philosophers

동적로딩(Dynamic Loading)은 어플리케이션이 실행될 때 모든 객체가 처음부터 생성되지 않고 필요한 시점에서 동적로딩을 통해 생성되는 개념입니다. 이러한 동적 로딩은 클래스를 일부 변경하더라도 재 컴파일을 하지 않아도 되는 장점이 있고 적은 작업으로 처리할 수 있는 유연성을 동적로딩이 제공합니다. 그러나 말 그대로 시점에 따라서 그때그때마다 생성이된다면 당연히 그 시점에 메모리에서 불러오기에 속도가 정적로딩에 비해 느린 편이고 이런 느린 속도를 해결하기위해 Static 키워드를 사용합니다.

Reference

'JAVA' 카테고리의 다른 글

Java - Logging 이란?  (1) 2024.11.12
[JAVA & Database] JDBC, MyBatis, JPA 의 차이점  (1) 2022.09.26
[JAVA]직렬화 - Serializable란?  (0) 2022.05.16
[JAVA]DAO, DTO, VO 정리.  (0) 2021.12.25

JAVA-직렬화란?


Serializable(직렬화)

  • 직렬화(Serializable)는 무엇인가?
    • 직렬화는 자바 시스템 내부에서 사용되는 객체 또는 데이터들을 외부 자바 시스템에서도 사용할 수 있도록 바이트(byte)형태로 데이터를 변환하는 기술과 바이트로 변환된 데이터를 다시 객체로 변환하는 역직렬화를 포함합니다.
    • 시스템적으로 JVM의 Runtime Data Area(Heap and Stack Area)에 상주하고 있는 객체 데이터를 바이트 형태로 변환하는 기술과 직렬화된 바이트 형태의 데이터를 객체로 변환해 JVM으로 상주시키는 형태를 말하기도 합니다.
  • 그렇다면 이런 직렬화는 어떤 경우에 사용되는 것일까?
    • 생성한 객체를 가지고 파일에 저장할 때
    • 저장한 객체를 읽어올 때
    • 다른 서버에서 생성된 객체를 받을 때

즉 우리가 만든 클래스가 파일에 읽거나 쓰고, 다른 서버로 보내거나 받을 경우 반드시 이 직렬화를 이용하기 위해 Serializable interface를 implements해주어야 합니다.

public class Temp implements Serializable{
  //다음과 같은 방식으로 implements하여 사용한다.
}

이러한 Serializable interface를 구현하는 클래스들을 확인해보면 serialVersionUID라는 값이 존재합니다.

이 serialVersionUID는 필수값은 아니지만 호환 가능한 클래스는 serialVersionUID 값이 고정되어있습니다.

serialVersionUID가 선언되어 있지 않으면 클래스는 기본 해쉬값을 사용합니다.

public class HashMap<K, V> extends TempMap<K,V>
  implementes Map<K,V>, Serializable{
  private static final long serialVersionUID = 123456789L;
}

이런식으로 static final long 으로 선언하고 변수명 역시 serialVersionUID로 선언해야 자바에서 인식합니다.

이러한 UID의 값은 언제 사용될까요?

처음 Serializable의 정의쪽을 기억해봅시다. "다른 서버로 보내거나 받을 경우"어떠한 클래스를 보내고자 할 때 받는 쪽 서버에도 역시나 동일한 클래스를 가지고 있을겁니다. 그러나 새로받은 클래스와 원래 존재했던 클래스가 단순히 이름만을 가지고 자바가 같다고 파악하지 않고 위에서 설명한 이 serialVersionUID를 통해 버전을 확인하고 두 버전이 동일 할 경우에만 같은 클래스로 인식하게 됩니다. 또한 UID가 같더라도 클래스 내부 변수의 개수나 타입등이 다르다면 이 경우에도 다른 클래스라 인식합니다.

Reference

'JAVA' 카테고리의 다른 글

Java - Logging 이란?  (1) 2024.11.12
[JAVA & Database] JDBC, MyBatis, JPA 의 차이점  (1) 2022.09.26
[Java] 언어의 특징  (2) 2022.09.09
[JAVA]DAO, DTO, VO 정리.  (0) 2021.12.25

+ Recent posts