반응형

처음 Spring에 입문했을 때, Spring Data JPA를 바로 사용하게 되면서 Repository에서 구현 클래스 없이 Interface만으로 개발을 할 수 있었다. 공부를 하면서 JPA, ORM, Hibernate, JDBC와 같은 개념들이 등장했고, 이들이 어떻게 구현되고 있는지, 위 사진을 이해하는 것이 목표이다.

 

가장먼저 ORM을 알아야 하지만, 이해를 돕기위해 RDB를 먼저 짚고 넘어가자.

 

 

 

0. RDB와 OOP


관계형 데이터베이스는 테이블, 행, 열의 정보를 구조화하여 데이터 간의 관계를 설정할 수 있는 데이터베이스 모델이다.

 

전반적으로 관계형 데이터베이스는 강력하고 효율적인 데이터 저장 및 검색 기능을 제공하지만 복잡한 객체 지향 개념을 모델링(상속, 다형성, 레퍼런스, 오브젝트)하는 데 적합하지 않다.

 

우리는 객체 지향 언어(Java)를 사용하고 있는데 RDB는 객체 지향을 모델링하는데 적합하지 않다?

 

이 말은 관계형 데이터베이스와 객체 지향 언어가 서로 다른 개념을 기반으로 설계되었기 때문에 직접적인 매핑이 어렵다는 의미이다.

 

객체 지향 프로그래밍(OOP) 관계형 데이터베이스
(RDB)
불일치 문제
객체(Object) 테이블(Table) 객체는 상태와 행동을 가지지만, 테이블은 행동 없이 데이터(값)만 저장
필드(Field) 컬럼(Column) 기본적으로 매핑 가능하지만, 복잡한 구조(예: 리스트, 객체 포함)는 변환이 필요
객체 참조(Reference) 외래 키(Foreign Key) 객체의 참조는 포인터 개념이지만, RDB에서는 FK로 조인해야 함
상속(Inheritance) 테이블에는 직접적인 개념 없음 OOP의 상속 구조를 테이블에 표현하려면 별도 설계(싱글 테이블 전략, 조인 전략 등)가 필요
다형성(Polymorphism) SQL에서 지원 X SQL에서 다형적 관계를 표현하려면 테이블 구조를 맞춰야 함 (지원 X)

 

 

결론적으로 왜 Java + RDB 조합이 흔한가?

 

Java와 RDB는 서로 다른 패러다임이지만, 기업 환경에서 가장 안정적이고 확장성이 뛰어난 조합이기 때문이다.

1️⃣ RDB는 표준 데이터 저장 방식이고, Java는 기업 시스템에서 가장 많이 쓰이는 언어

2️⃣ ORM을 통해 객체-관계 불일치 문제를 해결할 수 있음

3️⃣ Java의 성능, 안정성, 멀티쓰레딩RDB의 강력한 무결성, 확장성이 만나 기업 시스템에 최적화됨

4️⃣ Spring, Hibernate, MyBatis 같은 프레임워크가 Java + RDB 조합을 더욱 강력하게 지원

 

결국, 현실적인 이유로 Java와 RDB는 궁합이 잘 맞는 조합이 되었다.

 

따라서 Java와 RDB의 패러다임 차이(객체-관계 불일치 문제)를 해결하기 위해 ORM이 등장했다.

 

 

 

1. ORM


ORM(Object Relational Mapping)의 핵심은 ‘객체와 데이터베이스 테이블을 매핑하여, SQL을 직접 작성하지 않고 객체를 통해 데이터를 조작할 수 있도록 하는 기술’이다. 즉, 내가 코드 상에서 생성한 객체가 DB상에 어떤 테이블과 연결이 된다는 것을 의미한다. 이렇게 되면 내가 객체를 조작함으로써 DB를 조작할 수 있게 된다.

 

우리는 JPA에서 DB에 대한 접근을 시도할 때 직접 sql 쿼리문을 만들지 않는다. 다만 객체를 이용한 메소드를 통해 이를 관리할 뿐이다. 예를 들어, SELECT * FROM user 대신 userRepository.findAll() 같은 메서드를 호출하면, ORM이 내부적으로 SQL을 생성하여 실행해 준다.

 

ORM을 사용하면 객체와 테이블 간의 매핑을 통해 객체를 조작하는 것만으로 자동으로 SQL이 생성되며, 개발자는 비즈니스 로직에 집중할 수 있어 생산성이 매우 높아진다.

 

ORM 프레임워크 종류

  • JAVA : JPA, Hibernate, EclipseLink, DataNucleus, Ebean 등

 

 

위 사진을 보면 Data Access Layer들이 곧 Service Layer와 DataBase사이를 연동해주고 있다. JPA, Hibernate은 Java의 ORM 프레임워크인 셈이다. 그렇다면 JDBC API는 ORM인가?

 

 

 

2. JDBC


JDBC(Java Database Connectivity)는 ORM이 아닌, 자바에서 데이터베이스에 연결하고 작업을 수행하기 위한 표준 인터페이스이다.

 

Java는 DBMS의 종류에 상관없이 JDBC API를 통해 데이터베이스 작업을 처리할 수 있다. JDBC를 사용하면 데이터베이스에 접근하여 SQL을 실행하고, 데이터를 삽입(INSERT), 조회(SELECT), 수정(UPDATE), 삭제(DELETE)하는 CRUD 작업을 효율적으로 수행할 수 있다.

 

과거에는 MySQL, Oracle, PostgreSQL 등 각 DBMS마다 다른 방식의 SQL을 사용해야 했기 때문에 개발이 번거로웠다.

이러한 문제를 해결하기 위해 JDBC는 메서드와 전역 변수를 하나의 표준 문법으로 통일하여, DBMS에 관계없이 일관된 방식으로 데이터베이스를 다룰 수 있도록 했다.

 

JDBC 핵심: DBMS의 종류에 상관없이 하나의 JDBC API를 이용하여 일관된 문법으로 데이터베이스 작업을 처리할 수 있다.

JDBC Architecture

JDBC는 Java 애플리케이션과 다양한 DBMS를 연결하는 계층적인 구조로 이루어져 있다.

 

 

JDBC 아키텍처 구성 요소

1️⃣ JDBC API (Application Programming Interface)

  • 개발자가 직접 사용하는 JDBC 인터페이스
  • Connection, Statement, ResultSet 등의 클래스를 제공하여 데이터베이스와 통신

2️⃣ JDBC Driver Manager (드라이버 관리자)

  • 애플리케이션과 각 DBMS용 드라이버를 연결하는 역할
  • 여러 개의 JDBC 드라이버를 관리하며, 적절한 드라이버를 선택하여 연결을 수행

3️⃣ JDBC Driver (JDBC 드라이버)

  • 각 DBMS(MySQL, Oracle 등)에서 제공하는 JDBC 구현체
  • JDBC API를 사용해 작성된 코드가 각 DBMS에 맞는 SQL로 변환되도록 지원

4️⃣ DBMS (Database Management System)

  • MySQL, Oracle, PostgreSQL, MS SQL Server 등의 데이터베이스
  • JDBC 드라이버를 통해 SQL을 실행하고 데이터를 반환

 

 

JDBC와 ORM의 차이점

구분 JDBC ORM (JPA, Hibernate)
SQL 작성 여부 직접 SQL 작성 필요 SQL 자동 생성
데이터 조작 방식 SQL을 이용한 절차적 접근 객체를 이용한 선언적 접근
생산성 SQL을 직접 작성해야 하므로 상대적으로 낮음 자동화된 SQL 처리로 생산성이 높음
유연성 SQL을 직접 제어 가능 ORM 설정에 따라 자동 최적화됨
러닝 커브 쉽고 직관적 ORM 개념을 익혀야 함

 

🚀 JDBC는 ORM의 기반이 되는 기술이며, ORM은 JDBC를 활용하여 더 높은 수준의 데이터 접근 방식을 제공하는 기술이다.

 

 

그럼 또 다시 아래 그림을 보자, 이제 Service, Data Aceess, DB와의 연관 관계를 어느정도 알았다.

 

그러면 JPA와 Hibernate 는 무엇이고 어떻게 객체를 DB와 연동하는 것인가?

 

 

 

 

3. JPA


JPA(Java Persistence API)는 Hibernate 기반의 자바 ORM 기술 표준으로, 자바 애플리케이션에서 관계형 데이터베이스를 다루는 방식을 정의한 인터페이스이다. JPA는 어떻게 동작할까?

JPA는 영속성 컨텍스트(EntityManager)를 통해 Entity를 관리한다. 사용자가 Entity에 대한 CRUD 작업을 수행하면, JPA는 해당 Entity와 매핑된 테이블에 대한 SQL 쿼리를 자동 생성하여, 적절한 시점에 JDBC API를 통해 데이터베이스에 반영한다.

 

 

이해를 돕기 위해 영속성 컨텍스트 개념을 먼저 살펴보자.

영속성 컨텍스트(Persistence Context)란 엔티티를 저장하고 관리하는 논리적 메모리 공간이다.

 

왜 영속성 컨텍스트를 사용할까?

 

데이터베이스와의 통신 횟수를 줄이고, 성능을 최적화하기 위해 사용한다. 마치 메모리 캐시를 활용하여 중복 연산을 줄이는 것과 같은 개념이다. 이를 통해 불필요한 쿼리 실행을 최소화하고, 성능을 향상시킬 수 있다.

 

영속성 컨텍스트를 활용하면 얻을 수 있는 주요 이점은 다음과 같다.

 

 

1️⃣ 1차 캐시 (First Level Cache)

JPA는 엔티티를 조회할 때 1차 캐시에 저장해두고, 이후 동일한 엔티티를 조회할 경우 DB가 아닌 1차 캐시에서 가져온다.

java

// A. 캐시 없이 매번 DB 조회하는 경우
User user1 = entityManager.find(User.class, 1L); // DB에서 조회
User user2 = entityManager.find(User.class, 1L); // DB에서 다시 조회 (비효율적)

// B. JPA 1차 캐시 활용
User user1 = entityManager.find(User.class, 1L); // DB에서 조회 후 1차 캐시에 저장
User user2 = entityManager.find(User.class, 1L); // 1차 캐시에서 조회 (DB 쿼리 발생 X)

 

 

 

2️⃣ 쓰기 지연 (Write Behind, Write Delay)

빈번한 쓰기 작업을 하게 되면 context switching이 여러번 발생할 것이다. 따라서 이러한 Context Switching에 대한 overhead를 줄여주는게 쓰기 지연이다.

 

즉, 쓰기를 원하는 entity를 저장해 두었다가 한번에 처리한다는 것을 의미한다.

 

JPA는 INSERT/UPDATE/DELETE 쿼리를 즉시 실행하지 않고, 트랜잭션을 커밋하는 시점에 한꺼번에 처리한다. JPA는 여러 개의 변경 작업을 모아뒀다가 한 번에 처리하여 성능을 최적화한다.

java

EntityManager entityManager = emf.createEntityManager();
EntityTransaction transaction = entityManager.getTransaction();
transaction.begin(); // 트랜잭션 시작

entityManager.persist(memberA);
entityManager.persist(memberB);
entityManager.persist(memberC);

// INSERT SQL이 즉시 실행되지 않음

transaction.commit(); // 트랜잭션을 커밋하는 순간, 한꺼번에 INSERT 실행

 

 

 

3️⃣ 변경 감지 (Dirty Checking)

JPA는 엔티티의 변경 사항을 자동으로 감지하고, 트랜잭션이 커밋될 때 변경된 부분만 UPDATE 쿼리를 실행한다. 이를 위해 JPA는 영속성 컨텍스트(1차 캐시)에서 엔티티의 스냅샷(초기 상태)을 유지하며, 트랜잭션이 끝날 때 변경 여부를 비교한다.

 

이는 다음과 같은 장점이 있다.

  • 자동 변경 감지: 직접 UPDATE SQL을 작성하지 않아도 자동으로 변경 사항이 반영됨
  • 성능 최적화: 변경된 필드만 UPDATE 실행 (전체 컬럼 업데이트 방지)
  • 객체 중심 개발 가능: 데이터 변경을 신경 쓰지 않고 객체 필드만 수정하면 자동 반영
  • 트랜잭션 기반 처리: 하나의 트랜잭션 내에서 변경 사항을 모아두었다가 한 번에 처리

java

// A. Dirty Checking 사용 (자동 감지)
User user = entityManager.find(User.class, 1L);
user.setName("New Name"); // 자동 변경 감지
transaction.commit(); // 변경 감지 후 UPDATE 실행

// B. 명시적인 UPDATE 사용
User user = entityManager.find(User.class, 1L);
user.setName("New Name");

entityManager.createQuery("UPDATE User u SET u.name = :name WHERE u.id = :id")
    .setParameter("name", "New Name")
    .setParameter("id", 1L)
    .executeUpdate(); // 명시적으로 UPDATE 쿼리 실행

 

 

 

영속성 컨텍스트의 주요 개념을 살펴보았으니, 이제 이를 관리하는 핵심 객체인 EntityManagerFactoryEntityManager에 대해 알아보자.

 

JPA에서 EntityManagerFactory는 영속성 컨텍스트를 생성하고 관리하는 역할을 하며, EntityManager는 개별 트랜잭션 단위에서 엔티티의 생명주기를 관리하는 중요한 객체이다.

 

그렇다면 EntityManager는 언제, 어떻게 생성되고 사용될까?

 

 

EnitityManagerFactory ➡ WAS가 시작되는 시점에 만들어지고 각 트랜잭션에 대해 새로운 EntityManager를 생성하고 관리하는 Object이다. Transaction 단위를 수행할 때마다 생성한다. 즉, 고객의 요청이 올 때마다 사용했다가 닫는다. 따라서 Thread당 생성이 되고 이에 따라 Thread당 공유가 되지 않는다.

 

 

EntityManager ➡ Entity를 생명주기를 관리하는 context로 Persistence Context역할을 수행해준다. 우리가 Entity를 관리한다는 것을 EntityManger가 Entity에 대한 생명주기를 관리한다는 것을 의미할 것이다. 마치 메모리에서 데이터를 언제 올리고 어떻게 내릴지에 관해 관리하는 것처럼 말이다. 이러한 Entity에 대한 Life Cycle을 정리하면 (비영속, 영속, 준영속, 삭제)가 있다.

 

 

❓그러면 JPA는 모두 트랜잭션일 때 사용이 가능한가?

A: 아니다. Spring에서 트랜잭션 없이 JPA를 사용할 수 있지만 일반적으로 데이터 일관성과 무결성을 보장하기위해 트랜잭션을 활성화 하는 것이 좋다.

 

❓그러면 거의 모든 CRUD 서비스 단에서는 트랜잭션을 활성화 해야겠네?

A: 맞다. 대부분 CRUD 작업을 수행할 때 데이터 일관성 무결성을 보장하기 위해 트랜잭션이 필요하다. 하지만 Read 와 같이 읽기만 하는 경우 @Transactional(readOnly = true)를 설정하여 조회용으로 가져온 Entity의 예상치 못한 수정을 방지할 수 있다. 또한, readOnly = true를 설정하게 되면 JPA는 해당 트랜잭션 내에서 조회하는 Entity는 조회용임을 인식하고 변경 감지를 위한 Snapshot을 따로 보관하지 않으므로 메모리가 절약되는 성능상 이점 역시 존재한다.

 

 

JPA는 영속성 컨텍스트인 EntityManager를 통해 Entity를 관리하고 이러한 Entity가 DB와 매핑되어 사용자가 Entity에 대한 CRUD를 실행을 했을 때 Entity와 관련된 테이블에 대한 적절한 SQL 쿼리문을 생성하고 이를 관리하였다가 필요시 JDBC API를 통해 DB에 날리게 된다.

 

다시 위 문장을 보고 정리를 하자면,

  • JPA는 영속성 컨테스트를 사용하여 db와의 통신을 효율적으로 관리한다.
  • 이러한 영속성 컨텍스트에 대한 JPA상에서 구현체를 EntityManger라고 한다.
  • 이러한 EntityManger는 Entity에 대한 생명주기를 관리하고 db와의 연결정보를 저장해둔다.
  • Entity에 대한 CRUD는 연결정보를 바탕으로 JPA가 자동으로 생성해준다.
  • 이러한 쿼리문은 EntityManger가 관리를 하다가 필요시 처리하게 된다.

 

JPA는 애플리케이션과 JDBC 사이에서 동작한다. JPA는 데이터베이스와 객체를 매핑하는 기술일 뿐, 내부적으로는 데이터베이스와의 통신을 위해 JDBC를 사용한다. 개발자가 JPA를 사용하면, JPA 내부에서 JDBC API를 사용하여 SQL을 호출하여 DB와 통신한다.

 

 

 

또한, JPA도 JDBC와 마찬가지로 인터페이스이기 때문에 구현체가 필요하고, 그 구현체 중 하나가 Hibernate이다.

 

 

 

지금까지의 내용을 그림을 통해 다시 정리를 하고 Hibernate로 넘어가보자.

 

잠깐 그러면 Spring Data JPA는 뭐지 ?

 

 

 

4. Spring Data JPA


Spring Data JPA는 JPA를 더 쉽게 사용하기 위해 JPA 위에 구축된 상위 프레임워크로 데이터 접근 계층을 더욱 간편하게 구성할 수 있도록 돕는 기술이다.

 

1️⃣ Repository 인터페이스를 활용한 쿼리 자동 생성

  • Spring Data JPA는 Repository의 메소드를 통해 쿼리를 날릴 수 있다.
  • 이는 JPA를 한 단계 추상화시킨 Repository라는 인터페이스를 제공함으로써 이루어진다.
  • 사용자가 Repository 인터페이스에 정해진 규칙대로 메소드를 입력하면, Spring이 알아서 해당 메소드 이름에 적합한 쿼리를 날리는 구현체를 만들어서 Bean으로 등록해준다.

2️⃣ 복잡한 쿼리 작성 지원

  • 단순한 Method 기반 쿼리 생성 외에도 @Query, QueryDSL를 사용하여 SQL을 구현할 수 있다.

java

interface UserRepository extends JpaRepository<User, Long> {
    // 1. Method
    List<User> findByName(String name);  // SELECT * FROM user WHERE name = ?

    // 2. @Query
    @Query("SELECT u FROM User u WHERE u.email = :email")
    User findByEmail(@Param("email") String email);
}

 

간단하게 설명하면

  • 메서드명 규칙을 기반으로 JPA 쿼리를 자동 생성하여 코드량을 줄이고 생산성을 향상시키는 프레임워크
  • 추가적인 SQL 작성이 필요한 경우 @Query 애너테이션 또는 Querydsl을 사용하여 해결 가능

 

 

 

5. Hibernate


Hibernate는 Java 기반의 ORM 프레임워크이며, JPA의 구현체 중 하나이다.

 

즉, JPA의 인터페이스를 직접 구현하며, 내부적으로 JDBC API를 활용하여 동작한다.

 

JPAHibernate는 마치 Java의 Interface와 이를 구현하는 class의 관계와 유사하다.

JPA는 규칙(인터페이스)을 정의하고, Hibernate는 이를 구현(Implementation)한 라이브러리이다.

JPA와 Hibernate의 상속 및 구현 관계

 

JPA의 핵심인EntityManagerFactory, EntityManager, EntityTransaction을 Hibernate에서는 각각 SessionFactory, Session, Transaction으로 상속받고 각각 Impl로 구현하고 있음을 확인할 수 있다.

 

“Hibernate는 JPA의 구현체이다”로부터 도출되는 중요한 결론 중 하나는 JPA를 사용하기 위해서 반드시 Hibernate를 사용할 필요가 없다는 것이다.

 

Hibernate의 작동 방식이 마음에 들지 않는다면 언제든지 DataNucleus, EclipseLink 등 다른 JPA 구현체를 사용해도 되고, 심지어 본인이 직접 JPA를 구현해서 사용할 수도 있다.

 

다만 그렇게 하지 않는 이유는 단지 Hibernate가 굉장히 성숙한 라이브러리이기 때문일 뿐이다.(오랜 기간 검증된 안정적인 라이브러리, JPA의 표준을 기반으로 동작)

JPA, Hibernate Architecture

 

쉽게 얘기하면 Hibernate는 JPA에서 메서드로 요청된 정보들을 JDBC가 잘 알아들을 수 있게 한번 더 표준화 작업을 해주고 JDBC에게 넘겨준다.

 

스프링 부트 프로젝트의 External Liberies에 hibernate-core 을 찾아보면 구경할 수 있다.

 

이후 실무에서 JPA를 쓰지않으면 어떻게할까 궁금하여 찾아보았다.

2019.3.3 - "현재 저는 회사에서 각기 다른 환경을 가지고 있는 세 시스템을 동시에 다루고있습니다."

  • 유통시스템 : 마이플랫폼 + Spring framerwork(java) - 옛 DI구조 + mybatis + oracle
  • SCM시스템 : JSP,js,css + Spring framework(java) - 어노테이션구조 + mybatis + oracle
  • 자판기시스템 : 넥사크로 + Spring framework(java) + hibernate + postegreSql

 

전세계적으로는 hibernate가 가장 많았고, 유난히 동아시아 3국에서 mybatis 점유율이 높았고 JPA의 점유율도 점점 상승하고 있는 것 같다. 

 

JPA가 처음 나온 16~17년도 당시에는 JPA와 mybatis를 혼용해서 쓰는 경우가 많았지만, 현재는 JPA + QueryDSL을 많이 쓰는 추세이다. 아무래도 mybatis 같은경우는 직접 XML로 쿼리를 작성을 해야하는데 오류나 실수로인한 에러를 통해 서비스가 장애를 일으킬 수 있기 때문에 SI 쪽이 아니라면 JPA와 mybatis, hibernate와 같은걸 같이 사용하지 않는 추세인 것 같다. 


+

그렇게 Spring Data JPA과 QueryDSL을 공부하고

SI 회사에 입사해서 JSP,js,css + Spring framework(java) + mybatis + oracle을 하게되었다

반응형

브라우저의 내부 구성도

 

JavaScript는 싱글 스레드 언어라는 말을 많이 들어보았을 것이다. 하지만 자바스크립트는 웹 브라우저나 NodeJS와 같은 멀티 스레드 환경에서 실행된다. 싱글스레드인 자바스크립트가 어떻게 멀티 스레드 처럼 비동기적으로 실행되는지 알아보고자 한다.

 

 

0. JS Engine


JavaScript 엔진은 자바스크립트 코드를 해석하고 실행하는 인터프리터이고, 브라우저마다 다른 엔진을 사용한다.

그중에서도 가장 대표적인 엔진으로는 Google의 V8 엔진이다. 가장 많이 사용되는 Chrome과 Node.js에서 상요되는 엔진이기도 하기 때문이다.

  • Chrome : V8
  • FireFox : SpiderMonkey
  • Safari : Webkit
  • Explorer, Edge : Chakra

 

Javascript는 싱글 스레드 언어이다.

"싱글 스레드"라는 말은 자바스크립트 엔진이 단일 호출 스택 = 하나의 Call Stack을 사용한다. 라는 뜻이다.

JavaScript 엔진은 크게 Memory Heap과 Call Stack으로 이루어져 있다.

  • Memory Heap : 메모리 할당이 일어나는 장소
  • Call Stack : 코드가 실행될 경우 하나씩 stack의 형태로 쌓이는 장소

 

그런데, 웹 애플리케이션에서는 네트워크 요청이나 이벤트 처리, 타이머와 같은 작업을 멀티로 처리해야 하는 경우가 많다. 따라서 오래 걸리고 반복적인 작업들은 자바스크립트 엔진이 아닌 브라우저 내부의 멀티 스레드인 Web APIs에서 비동기 + 논블로킹으로 처리된다. 비동기 + 논블로킹(Async + Non blocking)Visit Website는 메인 스레드가 작업을 다른 곳에 요청하여 대신 실행하고, 그 작업이 완료되면 이벤트나 콜백 함수를 받아 결과를 실행하는 방식을 말한다.

 

 

이러한 싱글 스레드인 JavaScript의 작업을 멀티 스레드로 작업을 동시에 처리시키게 하던가, 또는 여러 작업 중 어떤 작업을 우선으로 동작시킬 것인지 결정하는 세심한 컨트롤을 하기 위해 존재하는 것이 바로 이벤트 루프(Event Loop) 이다.

💡 비동기로 동작하는 핵심요소는 자바스크립트 언어가 아니라 브라우저라는 소프트웨어가 가지고 있다.

 

 

 

1. Event Loop : 브라우저 동작을 제어하는 관리자


Event Loop의 역할:

  • 비동기 함수 작업을 Web API에 옮기는 역할
  • 작업이 완료된 콜백을 큐(Queue)에 적재
  • Call Stack이 빈 상태가 되면 자바스크립트 엔진에 적재

따라서 이벤트 루프는 Call Stack에 현재 실행 중인 작업이 있는지 그리고 Callback Queue에 대기 중인 작업이 있는지 반복적으로 확인하는 일종의 무한 루프만을 돌고, (그래서 이벤트 '루프' 이다)

일종의 '작업을 옮기는 역할' 만을 한다. 작업을 처리하는 주체는 자바스크립트 엔진과 웹 API 이다.

 

 

2. Web API


그림의 오른쪽에 있는 Wep API는 JS Engine의 밖에 그려져 있다. 즉, 자바스크립트 엔진이 아니다.

Web API 는 브라우저에서 제공하는 API 로, DOM, Ajax, Timeout 등이 있다. Call Stack에서 실행된 비동기 함수는 Web API를 호출하고, Web API는 콜백함수를 Callback Queue에 밀어 넣는다.

 

 

Web APIs는 타이머, 네트워크 요청, 파일 입출력, 이벤트 처리 등 브라우저에서 제공하는 다양한 API를 포괄하고 있다. Web API는 브라우저에서 멀티 스레드로 구현되어 있다. 그래서 브라우저는 비동기 작업에 대해 메인 스레드를 차단하지 않고 다른 스레드를 사용하여 동시에 처리할수 있는 것이다.

 

 

Web APIs의 대표적인 종류

  • DOM : HTML 문서의 구조와 내용을 표현하고 조작할 수 있는 객체
  • XMLHttpRequest : 서버와 비동기적으로 데이터를 교환할 수 있는 객체 AJAX기술의 핵심
  • Timer API : 일정한 시간 간격으로 함수를 실행하거나 지연시키는 메소드들을 제공
  • Console API : 개발자 도구에서 콘솔 기능을 제공
  • Canvas API : <canvas> 요소를 통해 그래픽을 그리거나 애니메이션을 만들 수 있는 메소드들을 제공
  • Geolocation API :웹 브라우저에서 사용자의 현재 위치 정보를 얻을 수 있는 메소드들을 제공

 

이때 오해하지 말아야 할 것은 모든 Web API들이 비동기로 동작되는 것이 아니다. Web API에는 동기적으로 처리되는 것과 비동기적으로 처리되는 것이 모두 있다. 예를 들어 DOM API나 Console API는 동기적으로 처리되고, XMLHttpRequest나 Timer API는 비동기적으로 처리된다.

 

 

3. Callback Queue


비동기적으로 실행된 콜백함수가 보관 되는 영역이다.

  • Task Queue: setTimeout, setInterval, fetch, addEventListener 와 같이 비동기로 처리되는 함수들의 콜백 함수가 들어가는 큐 (macrotask queue 는 보통 task queue 라고 부른다)
  • Microtask Queue: promise.then, process.nextTick, MutationObserver 와 같이 우선적으로 비동기로 처리되는 함수들의 콜백 함수가 들어가는 큐 (처리 우선순위가 높음)

 

같은 queue 안에 적재되는 콜백이라도 어떠한 비동기 작업이냐에 따라 우선순위가 다른 태스크들이 있을 수 있다.

→ Promise.then 결과가 setTimeout보다 우선 처리되는 것 처럼

ex) 해당 링크에서 [3.2.2. MicroTask Queue 처리 과정]을 참고해보자

 

 

4. JavaScript는 왜 Single Thread를 채택했을까?


  • 웹 브라우저 환경:
    • JavaScript는 원래 웹 브라우저에서 실행되도록 설계된 언어이다. 웹 브라우저는 사용자 인터페이스를 담당하므로, 동시성 문제를 최소화하는 것이 중요하다. 싱글 스레드 모델은 복잡한 동시성 문제를 줄여준다.
    • 사용자 인터페이스와 관련된 동작에서, 두 개 이상의 스레드가 동일한 DOM을 동시에 조작할 경우 충돌이 발생할 수 있다. 싱글 스레드 모델은 이러한 충돌을 방지한다.
  • 언어 설계의 단순성:
    • 싱글 스레드 모델은 다중 스레드와 비교했을 때 구현이 더 단순하고 오류가 적다. 스레드 간의 데이터 공유와 같은 동기화 문제를 고려할 필요가 없으므로, JavaScript의 설계와 구현이 단순해진다.
    • 초창기 웹의 요구사항은 현재와 달리 상대적으로 단순했기 때문에, 복잡한 멀티스레딩보다 단순한 모델이 적합했다.
  • 성능과 안전성:
    • 싱글 스레드 모델은 코드 실행의 예측 가능성을 높여준다. 모든 코드가 순차적으로 실행되므로, 디버깅이 상대적으로 쉬워진다.
    • 멀티스레드 환경에서 발생할 수 있는 데드락, 레이스 컨디션 등의 문제를 방지할 수 있다.

 

JavaScript가 싱글 스레드 모델을 채택한 것은 웹 브라우저 환경의 특성과 초기 언어 설계의 단순성, 그리고 성능과 안전성 등의 이유에서 비롯된다. 싱글 스레드 모델은 동시성 문제를 최소화하고, 안정적이고 예측 가능한 코드를 작성할 수 있게 하며, 비동기 프로그래밍을 통해 이러한 한계를 극복할 수 있는 방법도 제공한다. 이러한 이유들로 인해, JavaScript는 여전히 싱글 스레드 모델을 유지하고 있으며, 개발자들에게 직관적이고 효율적인 프로그래밍 환경을 제공한다.

 

'JavaScript' 카테고리의 다른 글

[JavaScript] Full Calendar 속성 및 사용법  (0) 2025.01.02
반응형

0. Apache Tiles란?


Apache Tiles는 Template Composition Framework로, 웹 어플리케이션의 재사용 가능한 템플릿을 관리하고 구성하는데 사용된다.

페이지의 공통 요소(ex, header, footer, sidebar, ..etc)를 별도의 타일로 정의하고 이를 여러 페이지에서 재사용하는 방식으로 사용된다.

이는 주로 Java 기반의 웹 어플리케이션에서 사용되며, JSP(JavaServer Pages)와 함께 사용되는 경우가 많다.

2021.03.02 기준으로 3.0.8버전까지 나왔으며 더 이상 지원하지 않는다고 한다. 공식 문서

 

 

1. System Requirements (3.0.8)


  • Spring Boot 3.x 이상에서는 tiles 3 지원 하지 않으니 주의
  • JDK 7.0 or above
  • Servlet 2.5 or above
  • JSP 2.1 or above
    공식 문서

 

 

2. Directory Structure


dir

src/
├── main/
│   ├── java/
│   │   └── com/
│   │       └── example/
│   │           └── demo/
│   │               ├── DemoApplication.java
│   │               ├── controller/
│   │               │   └── Controller.java
│   │               └── config/
│   │                   └── TilesConfig.java
│   ├── resources/
│   │   ├── static/
│   │   ├── templates/
│   │   └── application.yml
│   └── webapp/
│       ├── resources/
│       │   ├── css/
│       │   └── js/
│       └── WEB-INF/
│           ├── jsp/
│           │   └── index.jsp
│           ├── tiles/
│           │   ├──layout/
│           │   │  └── layout_default.jsp
│           │   ├──view/
│           │   │  ├── footer.jsp
│           │   │  └── header.jsp
│           │   └── tiles.xml
│           └── web.xml
└── test/

 

 

3. 설정 및 사용 (v3.0.5)


3.1 Dependency

gradle

implementation 'org.apache.tiles:tiles-jsp:3.0.5'

maven

<dependency> <!-- tiles -->
    <groupId>org.apache.tiles</groupId>
    <artifactId>tiles-jsp</artifactId>
    <version>3.0.5</version>
</dependency>

<dependency> <!-- javax.jstl -->
    <groupId>javax.servlet</groupId>
    <artifactId>jstl</artifactId>
</dependency>

 

 

3.2 application.yml

yml

spring:
  mvc:
    view:
      prefix: /WEB-INF/jsp/
      suffix: .jsp

Spring MVC는 기본적으로 resources - templates라는 폴더에 있는 index.html을 찾아 렌더링한다.

  • JSP 파일의 위치와 접미사를 정의하여 Spring MVC가 JSP파일을 찾을 수 있도록 설정한다.
  • prefix: JSP 파일이 위치한 디렉토리를 지정
  • suffix: JSP 파일의 접미사를 지정

 

3.3 TilesConfig.java

java

package com.example.demo.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.view.tiles3.TilesConfigurer;
import org.springframework.web.servlet.view.tiles3.TilesView;
import org.springframework.web.servlet.view.tiles3.TilesViewResolver;

@Configuration
public class TilesConfig {

    @Bean
    public TilesConfigurer tilesConfigurer() {
        TilesConfigurer tilesConfigurer = new TilesConfigurer();
        tilesConfigurer.setDefinitions(new String[]{"/WEB-INF/tiles/tiles.xml"});
        tilesConfigurer.setCheckRefresh(true);
        return tilesConfigurer;
    }

    @Bean
    public TilesViewResolver tilesViewResolver() {
        TilesViewResolver viewResolver = new TilesViewResolver();
        viewResolver.setOrder(TilesView.class);
        return viewResolver;
    }
}

TilesConfigurer

  • setDefinitions 메서드를 사용하여 Tiles 정의 파일 경로 설정
  • setCheckRefresh를 true로 설정하여 Tiles 정의 파일이 변경될 때마다 새로고침하도록 설정

TilesViewResolver

  • setViewClass를 사용하여 뷰 리졸버가 사용할 뷰 클래스 타입을 설정 -> Tiles 뷰를 렌더링하는데 사용

 

3.4 layout_default.jsp

jsp

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="tiles" uri="http://tiles.apache.org/tags-tiles" %>
<!DOCTYPE html>
<html>
    <head>
    </head>
    <body>
        <div>
            <!-- header -->
            <tiles:insertAttribute name="header"/>

            <!-- body -->
            <tiles:insertAttribute name="body"/>

            <!-- footer -->
            <tiles:insertAttribute name="footer"/>
          </div>
    </body>
</html>
  • insertAttribute name 은 tiles.xml에서 설정한 put-attribute name과 일치해야한다.

 

3.5 header.jsp / footer.jsp

jsp

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<div class="header">
    <h2>Header</h2>
</div>

jsp

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<div class="footer">
    <h4>Footer</h4>
</div>

 

3.6 tiles.xml

xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE tiles-definitions PUBLIC "-//Apache Software Foundation//DTD Tiles Configuration 3.0//EN" "http://tiles.apache.org/dtds/tiles-config_3_0.dtd">
<tiles-definitions>

<!-- [ layout.default ] -->
    <!-- ( LAYOUT ) -->
    <definition name="layout.default" templateExpression="/WEB-INF/tiles/layout/layout_default.jsp">
        <put-attribute name="header" expression="/WEB-INF/tiles/view/header.jsp"/>
        <put-attribute name="body" expression=""/>
        <put-attribute name="footer" value="/WEB-INF/tiles/view/footer.jsp"/>
    </definition>

        <!-- ( EXTENDS ) -->
        <definition name="index" extends="layout.default">
            <put-attribute name="body" expression="/WEB-INF/jsp/index.jsp"/>
        </definition>
        <definition name="*/*" extends="layout.default">
            <put-attribute name="body" expression="/WEB-INF/jsp/{1}/{2}.jsp"/>
        </definition>
        <definition name="*/*/*" extends="layout.default">
            <put-attribute name="body" expression="/WEB-INF/jsp/{1}/{2}/{3}.jsp"/>
        </definition>
        <definition name="*/*/*/*" extends="layout.default">
            <put-attribute name="body" expression="/WEB-INF/jsp/{1}/{2}/{3}/{4}.jsp"/>
        </definition>

</tiles-definitions>
  • definition: Tiles 정의를 나타냄. 각 정의는 특정 레이아웃이나 페이지 구성을 나타냄
    • name : 정의의 이름. 이 이름을 사용하여 Tiles를 참조할 수 있다.
    • templates : 기본 템플릿 파일의 경로 (공통 레이아웃을 정의)
  • put-attribute: 템플릿의 각 부분을 정의한다.
    • name : 자리 표시자
    • value : 자리 표시자에 해당하는 파일의 경로 또는 값
  • 위 코드의 경우 /index 로 들어오는 페이지는 layout.default를 확장하여 정의하며 body 부분은 /WEB-INF/jsp/index.jsp 파일로 설정된다.
  • /* 과 {} 를 통해 들어오는 url에 대해 전역적으로 jsp 파일을 맵핑할 수 있다.

 

3.7 Controller.java

java

package com.example.jspprac.auth.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class Controller {

    @GetMapping("/")
    public String home() {
        return "index";
    }    
}

WEB-INF 안에 있는 JSP 파일에 접근하기 위해서는 Spring MVC 컨트롤러나 서블릿을 사용해야 한다.

WEB-INF는 웹 애플리케이션의 보안 및 구조를 위해 사용되며, 직접 브라우저를 통해 접근할 수 없기 때문이다.

반대로 외부 유저가 접근해도 문제없는, 보안성이 중요하지 않을 경우 WEB 디렉토리를 통해 a태그로 이동하도록 설계해도 된다.

Controller의 return값과 Tiles.xml에서 definition에 정의한 name이 반드시 일치해야 해당 tiles를 탄다.

 

3.8 index.jsp

jsp

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib uri="http://tiles.apache.org/tags-tiles" prefix="tiles"%>

 

 

4. Result


최종적으로 index.jsp 는 빈 페이지지만 layout_default를 extend 하고 있기 때문에

jsp

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib uri="http://tiles.apache.org/tags-tiles" prefix="tiles"%>
<html>
    <head>
    </head>
    <body>
        <div>
            <!-- header -->
            <tiles:insertAttribute name="header"/>

            <!-- body -->
            <tiles:insertAttribute name="body"/>

            <!-- footer -->
            <tiles:insertAttribute name="footer"/>
          </div>
    </body>
</html>

위와 같이 페이지가 만들어지며,

tiles.xml에 맵핑해 놓은 jsp파일 또한

jsp

<put-attribute name="header" expression="/WEB-INF/tiles/view/header.jsp"/>
<put-attribute name="body" expression=""/>
<put-attribute name="footer" value="/WEB-INF/tiles/view/footer.jsp"/>

반영되고,

최종적으로 아래와 같이 html이 완성된다.

jsp

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib uri="http://tiles.apache.org/tags-tiles" prefix="tiles"%>
<html>
    <head>
    </head>
    <body>
        <div>
            <!-- header -->
            <div class="header">
                  <h2>Header</h2>
            </div>

            <!-- body -->

            <!-- footer -->
            <div class="footer">
                <h4>Footer</h4>
              </div>
        </div>
    </body>
</html>
반응형

0. Intro


Java와 Kotlin은 compile 방식으로 해석되고 있다.

compiler로 해석된 자바 바이트 코드는 JVM의 interpreter로 기계어로 번역되어 실행된다.

Java나 Kotlin은 compile 방식을 택했고 JVM은 왜 interpreter 방식을 택했을까?

이 둘의 차이점은 무엇일까?

1. Compiler


compiler는 소스코드를 기계어로 해석한다.

  1. 소스 코드 읽기: 컴파일러는 소스 코드 파일을 읽는다.
  2. 어휘 분석: 소스 코드를 토큰이라는 작은 요소로 분해한다.
    • 이 단계에서 컴파일러는 주석, 공백, 탭 문자 등을 제거하고, 키워드, 연산자, 식별자, 리터럴 등의 토큰으로 분류한다.
  3. 구문 분석: 토큰들을 구조화 하여 추상 구문 트리를 생성한다.
    • 이 과정에서 문법에 따라 토큰들을 조합하여 프로그램의 구조를 나타내는 트리 형태의 데이터 구조를 생성한다.
  4. 의미 분석: 추상 구문 트리를 검사하여 프로그램의 의미를 검증한다.
    • 타입 검사 변수의 정의 및 사용 검사, 함 수 호출의 유효성 검사 등이 수행된다. 오류가 발견되면 컴파일러는 에러 메시지를 생성하고 컴파일 과정을 중단한다.
  5. 중간 코드 생성: 의미 분석이 완료되면 추상 구문 트리를 중간 표현으로 변환한다.
    • 중간 코드는 소스 코드와 기계어 사이에 있는 중간 단계로 컴파일러에 의해 추가적인 최적화가 수행될 수 있다.
  6. 최적화: 중간 코드에 대한 다양한 최적화 기법을 적용하여 프로그램의 실행 효율을 향상 시킨다.
    • 이 과정에서 불필요한 코드 제거, 상수 폴딩, 루프 최적화 등의 작업이 수행된다.
  7. 기계어 생성: 최적화된 중간 코드를 기계어 (바이너리 코드)로 변환한다.
    • 이 단계에서 기계어 명령어를 생성하고 메모리 할당, 레지스터 할당 등과 같은 저수준 작업을 수행한다.
  8. 링킹: 기계어로 변환된 코드에 필요한 외부 라이브러리, 함수, 모듈 등을 연결한다.

 

Compiler의 가장 큰 특징은 코드 전체를 통째로 해석한다.
실행 파일을 생성하기 때문에 보안적으로 소스 코드가 직접 외부에 노출 되지 않아 안전하다.
JIT Compiler 같은 경우는 캐싱을 하기 때문에 자주 호출 된다면 Interpreter 보다 좋은 perfomance를 보여줄 수 있다.

 

 

2. Interpreter


  1. 소스 코드 읽기: 소스 코드를 한 줄씩 읽는다.
  2. 어휘 분석 : 소스 코드를 토큰이라는 작은 요소로 분해한다.
    • 이 단계에서 컴파일러는 주석, 공백, 탭 문자 등을 제거하고, 키워드, 연산자, 식별자, 리터럴 등의 토큰으로 분류한다.
  3. 구분 분석: 토큰들을 구조화 하여 추상 구문 트리를 생성한다.
    • 이 과정에서 문법에 따라 토큰들을 조합하여 프로그램의 구조를 나타내는 트리 형태의 데이터 구조를 생성한다.
  4. 의미 분석: 추상 구문 트리를 검사하여 프로그램의 의미를 검증한다.
    • 타입 검사 변수의 정의 및 사용 검사, 함 수 호출의 유효성 검사 등이 수행된다. 오류가 발견되면 컴파일러는 에러 메시지를 생성하고 컴파일 과정을 중단한다.
  5. 실행: 의미가 분석 되면 추상 구문 트리를 순차적으로 실행한다.
    • 이 과정에서 프로그램의 각 문장이 실행되고 변수 값이 계산되며, 함수가 호출된다. 인터프리터는 소스 코드를 한 줄씩 실행하므로, 실행 시점에서 소스 코드에 대한 변경사항이 즉시 반영된다.

 

Interpreter는 소스 코드를 한 줄씩 읽어들이기 때문에 전체적인 속도는 compiler에 비해 느릴 수 있지만 소스 코드의 수정이 빈번하게 발생하는 경우 유리할 수 있다.

 

 

3. Compiler vs Interpreter


컴파일러
  • 장점
    • 컴파일이 완료된 실행 파일은 컴퓨터에서 빠르게 실행할 수 있기 때문에 효율적이다.
    • 0과 1로 된 기계어로 번역되기 때문에 프로그램의 코드가 유출되지 않는다.
    • 컴파일 에러(문법적 에러)와 관련된 에러를 초기에 발견할 수 있다.
  • 단점
    • 코드를 수정하면 컴파일을 다시 해야 한다.
    • 소스 파일 전체를 컴파일해야 하므로 용량이 크다. 따라서 수정 사항이 빈번할 경우 문제가 발생할 수 있다.
    • 모든 소스 파일을 한꺼번에 번역하기 때문에 컴파일 시간이 비교적 느리다.
    • 목적 파일 생성을 위해 메모리를 사용한다.
    • 특정 시스템에서 만들어진 실행 파일이 다른 시스템에서는 실행되지 않는 경우가 많다.
인터프리터
  • 장점
    • 메모리를 사용하지 않는다.
    • 시스템 간의 이식성이 뛰어나다.
    • 전체 코드를 다시 컴파일할 필요가 없기 때문에 코드 수정에 용이하다.
  • 단점
    • 매번 번역 과정을 거쳐야 하기 때문에 실행 속도가 컴파일러에 비해 느리다.
    • 중간 코드로 해석되기 때문에 프로그램의 코드가 유출될 수 있다.

 

 

4. JIT Compile


자바는 코드를 실행하기 위해서 바이트코드로 컴파일하는 과정과 바이트코드를 인터프리트하는 과정을 거쳐야 하기 때문에 컴파일 과정만 필요한 다른 프로그래밍 언어보다 느리다. 거기에 더하여 인터프리터는 컴파일러보다 느리기 때문에 성능 문제기 발생할 수밖에 없었다.

이러한 문제를 개선하기 위해 나온 것이 JIT 컴파일러이다. 원래 자바의 JVM에서는 인터프리터 방식만 사용했다.

하지만 성능 문제가 발생했고 JIT 컴파일러를 추가해서 성능을 올리게 되었다.

JVM 이란?

JVM은 Just-In-Time 이라는 compiler를 이용하여 자바 바이트 코드로 먼저 컴파일 한 뒤 Interpreter를 이용하여 읽어 들이기 때문에 실행 속도를 개선하고, 기계어 번역을 하기 때문에 디버깅이 용이하다.
JIT Compiler는 한 번 컴파일 된 실행파일을 캐싱하여 저장하기 때문에 자주 호출되는 경우 캐시를 가져와 유리하게 성능을 가져갈 수 있다.

 
JIT 컴파일러 동작 방식

JIT 컴파일러는 실행 시점에서는 인터프리터와 같이 기계어 코드를 생성하면서 해당 코드가 컴파일 대상이 되면 컴파일하고 그 코드를 캐싱한다. JIT 컴파일은 코드가 실행되는 과정에 실시간으로 일어나며(그래서 Just-In-Time이다), 전체 코드의 필요한 부분만 변환한다. 기계어로 변환된 코드는 캐시에 저장되기 때문에 재사용 시 컴파일을 다시 할 필요가 없다.

  • JIT 컴파일러가 컴파일하는 조건은 얼마나 자주 코드가 실행됐는가 이다. 일정한 횟수만큼 실행되고 나면 컴파일 임계치에 도달하고 컴파일러는 컴파일하기에 충분한 정보가 쌓였다고 생각한다.
  • 임계치는 메서드가 호출된 횟수, 메서드의 루프를 빠져나오기까지 돈 횟수 두 개를 기반으로 한다. 이 두 수의 합계를 확인하고 메서드가 컴파일될 자격이 있는지 여부를 결정한다. 자격이 있다면 메서드는 컴파일되기 위해 큐에서 대기한다. 이후 메서드들은 컴파일 스레드에 의해 컴파일된다.
  • 아주 오랫동안 돌아가는 루프 문의 카운터가 임계치를 넘어가면 해당 루프는 컴파일 대상이 된다. JVM은 루프를 위한 코드의 컴파일이 끝나면 루프가 다시 반복될 때는 코드를 컴파일된 코드로 교체하고 더 빠르게 실행된다. 이 교체 과정을 "스택 상의 교체(on-stack replacement, ORS)"라고 부른다.

https://blog.kakaocdn.net/dna/ULoUs/btrBCtdZv7h/AAAAAAAAAAAAAAAAAAAAAMbx-YyOU_4tk8RRnn9_JomtCSxbGOS6RlMVr3yjTyDL/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&expires=1782831599&allow_ip=&allow_referer=&signature=GH2n04UfkZOrOhJ7s5jPwCvFWOg%3Dhttps://blog.kakaocdn.net/dna/cYUoxD/btrBynSw8S3/AAAAAAAAAAAAAAAAAAAAAHaaF5GRacptOE6POFzWdLq3SPJqZd_1IH8evx-l-vUs/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&expires=1782831599&allow_ip=&allow_referer=&signature=xxOZtZyp%2FsFyU4E7r9fBbhHm1Lc%3D

그림으로 보자면, 위와 같이 반복되는 코드들을 컴파일하여 캐싱해둠으로써 인터프리터는 반복되는 코드를 읽지 않고 컴파일된 코드를 바로 사용할 수 있는 것이다.

 
JIT 컴파일러의 이점

일반적인 인터프러터 언어는 바이트코드나 소스코드를 최적화 과정이 없이 번역하기 때문에 성능이 낮다. 반면 정적으로 컴파일하는 언어는 실행 전에 무조건 컴파일을 해야 하기 때문에 다양한 플랫폼에 맞게 컴파일을 하려면 시간이 오래 걸린다.

JIT 컴파일러는 실행 과정에서 컴파일을 할 수 있기 위해 만들어졌다. JIT 컴파일러는 정적 컴파일러만큼 빠르면서 인터프러터 언어의 빠른 응답속도를 추구하기 위해 사용한다. 또한 바이트코드 컴파일러가 시간이 많이 소요되는 최적화를 미리 해주기 때문에 바이트코드에서 기계어 번역은 훨씬 빠르게 진행될 수 있어 성능상의 이점이 있다.

반응형

0. 요청사항

  • Header : 월 이동, Toay 날짜 이동 커스텀 버튼
  • 달력에 오늘날짜에 리본 표시하기
  • 달력 이벤트 조회 후, 오늘 날짜 기준으로 가장 가까운 event에 select (select시 해당 날짜에 대한 상세 조회)
  • 날짜 클릭 시, 선택된 날짜를 알 수 있게 표시하기 (하늘색 부분)

1. html

html

<body>
  <div id='calendar'></div>
</body>

2. css

css

/* calendar : resize */
#calendar {
  flex: 1;
  height: 100%;
}
#calendar a {
  color: inherit;
}
#calendar :hover {
  cursor: pointer;
}

/* calendar : toolbar, header, title */
.fc .fc-toolbar.fc-header-toolbar {
  margin: 1em 1.5em;
}
.fc .fc-toolbar-title {
  font-size: 1.5em;
  margin: 0 1.25em 0 2em !important;
  white-space: nowrap;
}
.fc-toolbar-chunk {
  display: flex;
  align-items: center;
}
.fc-toolbar-chunk:nth-child(2) {
  flex: 1;
  justify-content: center;
}
.fc-button {
  width: 38.75px;
  height: 33.78px;
  padding: 0;
  display: flex;
  align-items: center;
  justify-content: center;
}
.fc-today-button {
  white-space: nowrap;
  width: 43.22px;
  height: 33.78px;
}

/* today ribon */
.fc-daygrid-day.fc-day-today {
  z-index: 1;
}
.fc-daygrid-day.fc-day-today .fc-daygrid-day-top::before {
  content: "Today";
  position: absolute;
  top: 5px;
  left: 5px;
  background: linear-gradient(45deg, #ff5f5f, #ff9e9e);
  color: white;
  font-size: 12px;
  font-weight: bold;
  padding: 0 10px;
  border-radius: 3px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
}
.fc-daygrid-day.fc-day-today .fc-daygrid-day-top {
  position: relative;
}

/* custom event */
.fc-daygrid-day-events {
  text-align:left;
  border-radius:50%;
  padding:0px 7px;
  margin-right:3px;
  font-size:1rem;
}

3. javascript

  • fullCalendar 원하는 위치에 다운로드 후 소스추가

html

<script src='/resources/fullcalendar/lib/main.js'></script>

javascript

const today = new Date(); // 'yyyy-mm-dd' 형태로 Format 필요
const colorMap = { '계약금': '#42bd04',
                   '중도금': '#0e8fcf',
                   '잔금': '#d4aa02'
                 };
var prevInfo, calendarEvent, calendar; // FullCalendar에서 사용할 변수 생성

createCalendar(); // calendar Initialize
get_Date(today); // calendar 최초 조회

function createCalendar() {
  var calendarEl = document.getElementById("calendar");
  calendar = new FullCalendar.Calendar(calendarEl, {
    initialView: "dayGridMonth",
    views: {
      dayGridMonth: {
        titleFormat: function (date) {
          return date.start.year + "년" + (date.start.month + 1) + "월";
        },
      },
    },
    headerToolbar: {
      start: "",
      center: "prevYear prev title next nextYear today",
      end: "",
    },
    customButtons: {
      today: {
        text: "오늘",
        click: function () {
          calendar.today();
        },
      },
    },
    locale: "ko",
    height: "100%",
    showNonCurrentDates: false,
    fixedWeekCount: false,
    windowResizeDelay: 300,
    eventClick: function (info) {
      let dInfo = {
        date: info.event.start,
        dayEl: $(info.el).closest(".fc-daygrid-day").get(0),
        dateStr: info.event.startStr,
      };

      calendar.trigger("dateClick", dInfo);
    },
    events: function (info, successCallback, failureCallback) {
      calendarEvent = successCallback;
    },
    eventContent: function (arg) {
      let eventTitle = arg.event.title;
      let bgC = colorMap[eventTitle.slice(0, 3).trim()] || "#ffffff";

      return {
        html:
        '<div style="background-color: ' +
        bgC +
        '; padding: 2px;"><span class="fc-custom-event"><small class="fas fa-circle mx-1"></small>' +
        eventTitle +
        "</span><div>",
      };
    },
    dateClick: function (info) {
      if (prevInfo != null && prevInfo.dateStr != info.dateStr) {
        prevInfo.dayEl.style.backgroundColor = "";
      }
      info.dayEl.style.backgroundColor = "rgba(183, 224, 255, 0.3)";
      prevInfo = info || "";

      $("#INFO_TXT2").text("선택날짜 : " + info.dateStr);

      getDate(calendar.getDate()); // 여기서 해당 날짜(info.dateStr)의 상세 조회 호출
    },
    select: function (info) {
      let dInfo = {
        date: info.start,
        dayEl: document.querySelector("[data-date='" + info.startStr + "']"),
        dateStr: info.startStr,
      };

      calendar.trigger("dateClick", dInfo);
    },
    windowResize: function (arg) {
      calendar.updateSize();
    },
  });

  calendar.render();
}

// Ajax function 예시
function getDate(param) {
  $.ajax({
    url: '/get_EventData',
    method: 'get',
    data: JSON.stringify(param),
    contentType: 'application/json',
    success: (rtnData) => {
      const evtData = rtnData.Data.map(data => ({
        title: data.TITLE,
        start: data.DATE,
        end: data.DATE
      }));

      calendarEvent(evtData);

      // 조회 후 가까운 Event에 Select
      let closestEvent = evtData.filter(event => event.start >= today)
                                  .sort((a, b) => a.start.localeCompare(b.start))[0] || evtData.sort((a, b) => a.start.localeCompare(b.start))[0];
      if (closestEvent) {
        calendar.select(closestEvent);
      }
      }
    },
    error: (err) => console.error('AJAX 요청 실패:', err)
  });
}

4. 속성

  • FullCalendar - JavaScript Event Calendar
  • initialView:
    • 캘린더가 로드될 때의 초기 모습
    • 사용 가능한 View (ex: 'dayGridWeek', 'timeGridDay', 'listWeek')
  • views
    • 특정 Calendar View에만 적용되는 옵션을 지정할 수 있다.
    • ‘dayGridMonth’ View의 Title을 Format 하기 위해 설정
  • headerToolbar
    • 달력 상단의 버튼과 제목을 정의
    • start, center, end는 기본 위치를 자체 css가 잡아주지만, 위 소스에서는 center에 몰아넣고 css로 새로 적용했다.
    • 기본적으로 ‘title’, ‘prev’, ‘next’, ‘prevYear’, ‘nextYear’, ‘today’ 들을 제공하고있고, 커스텀 가능하다.
  • customButtons
    • headerToolbar/footerToolbar에서 사용할 수 있는 사용자 정의 버튼을 정의한다.
    • 혀용되는 속성으로 text, hint, click, icon, bootstrapFontAwesome이 있다.
  • showNonCurrentDates
    • 월별 보기에서 이전 달이나 다음 달의 날짜를 렌더링할지 여부를 지정
    • false 처리하여 맨 위 사진처럼 회색으로 나타남
  • fixedWeekCount
    • 월별 보기에 표시되는 주 수를 결정
  • eventClick: function
    • 사용자가 이벤트를 클릭하면 발생
    • 위 소스에서는 event클릭 시에도 날짜 클릭(dateClick)과 같은 로직을 하기에 Element를 임의로 만들어 dateClick을 trigger 시켰다.
    • select에서 style.backgroundColor를 바꿔야 하기에 day Element가 필요함.
  • events: function
    • 사용자가 이전/다음을 클릭하거나 뷰를 전환할 때 트리거되며, 해당 영역에서 Ajax 요청을하고, success와 fail에 대한 callback함수를 정의할 수 있다.
    • 위 소스에서는 calendar 함수 밖에서 ajax요청을 보내야 하기 때문에 calendarEvent를 전역변수로 만들어 successCallback 함수를 담아 처리했다.
  • eventContent: function
    • Custom 이벤트를 다양한 위치에서 FullCalendar DOM에 주입시킨다.
    • 위 코드에선 Ajax 요청시 successCallback 함수를 가르키는 calendarEvent를 실행하여 return Data를 담아 발생시켰다.
    • evtData를 colorMap에 맞춰 이벤트 DIV를 DOM에 주입시켰다.
  • dateClick: function
    • 사용자가 날짜나 시간을 클릭하면 발생한다.
    • select한 날짜의 Element를 Full Calendar가 처리가능한 객체로 만들고, 클릭한 날짜의 배경색을 처리한 부분
  • select: function
    • 날짜/시간 선택 시 트리거
  • windowResize: function
    • 브라우저 창 크기가 조정되어 달력의 크기가 변경된 후에 발생하며 windowResize가 트리거되면 달력이 자동으로 새로운 크기에 맞게 조정
    • DOM LOAD가 느려 제대로 동작하지 않을경우 windowResizeDelay 설정이 필요하다.
반응형
반응형

+ Recent posts