asentinel-orm을 사용한 런타임으로 정의된 열

Asentinel-orm은 Spring JDBC의 상단에 구축된 가벼운 ORM 도구로, 특히 JdbcTemplate을 사용합니다. 따라서 SQL 생성, 지연 로딩 등 기본 ORM에서 기대할 수 있는 대부분의 기능을 갖추고 있습니다.

JdbcTemplate을 활용함으로써 Spring에서 관리하는 트랜잭션에 참여할 수 있으며, 이미 JdbcTemplate을 사용하여 데이터베이스와 상호 작용하는 프로젝트에 쉽게 통합할 수 있습니다.

2015년 이후 asentinel-orm은 여러 애플리케이션에서 성공적으로 사용되어 왔으며 필요에 따라 지속적으로 개선되었습니다. 2024년 여름에는 공식적으로 오픈 소스 프로젝트로 선포되었으며, 이는 진화를 가속화시키고 기여자 수를 늘릴 것으로 예상됩니다.

본 문서에서는 ORM의 주요 기능을 개요로 설명하는 샘플 애플리케이션이 구축되었습니다:

  • 간단한 구성
  • 사용자 정의 주석을 통한 직관적인 도메인 엔티티 모델링
  • 일반 SQL 문 작성 및 안전한 실행
  • 자동 SQL 문 생성
  • 동적 스키마(엔티티가 추가적인 런타임 속성으로 보강되어 코드 변경 없이 저장되고 읽힘)

애플리케이션

설정

  • Java 21
  • Spring Boot 3.4.0
  • asentinel-orm 1.70.0
  • H2 데이터베이스

구성

asentinel-orm와 상호 작용하고 그 기능을 활용하기 위해서는 OrmOperations 인스턴스가 필요합니다.

JavaDoc에 명시된 대로, 이 인터페이스는 ORM 작업을 수행하기 위한 중심 인터페이스이며, 클라이언트 코드에서 구체적으로 구현할 필요가 없습니다.

샘플 응용 프로그램에는 이 유형의 빈을 생성하기 위한 구성 코드가 포함되어 있습니다.

Java

 

OrmOperations 에는 두 개의 슈퍼 인터페이스가 있습니다:

  • SqlBuilderFactorySqlBuilder 인스턴스를 생성하여 SQL 쿼리를 만들 수 있습니다. SqlBuilder는 쿼리의 일부를 자동으로 생성할 수 있으며, 예를 들어 열을 선택하는 부분을 생성할 수 있습니다. Where 절, order by 절, 다른 조건 및 실제 열은 SqlBuilder 클래스의 메서드를 사용하여 추가할 수 있습니다. 이 섹션의 다음 부분에서는 SqlBuilder로 생성된 쿼리 예제가 표시됩니다.
  • Updater – 해당 데이터베이스 테이블에 엔티티를 저장하는 데 사용됩니다. 엔티티가 새로 생성되었는지 이미 존재하는지에 따라 삽입 또는 업데이트를 수행할 수 있습니다. NewEntityDetector 라는 전략 인터페이스가 있으며, 엔티티가 새로운지 여부를 결정하는 데 사용됩니다. 기본적으로 SimpleNewEntityDetector 이 사용됩니다.

ORM에 의해 생성된 모든 쿼리는 SqlQueryTemplate 인스턴스를 사용하여 실행되며, 이는 추가로 Spring JdbcOperations/JdbcTemplate 이 필요합니다. 결국 모든 쿼리는 좋은 오래된 JdbcTemplate에 도달하여 Spring 트랜잭션에 참여하면서 실행되며, 이는 모든 JdbcTemplate 직접 실행.

데이터베이스 특정 SQL 구조 및 로직은 JdbcFlavor 인터페이스의 구현을 통해 제공되며, 이는 위에서 언급한 대부분의 빈에 주입됩니다. 이 기사에서는 H2 데이터베이스가 사용되므로 H2JdbcFlavor 구현이 구성됩니다.

샘플 애플리케이션의 일부로서 ORM의 완전한 구성은 OrmConfig입니다.

구현

샘플 애플리케이션에서 노출된 실험적 도메인 모델은 간단하며 두 개의 엔티티로 구성됩니다 – 자동차 제조사와 자동차 모델입니다. 그들의 이름이 나타내는 바를 정확히 나타내며, 이들 간의 관계는 명백합니다: 한 자동차 제조사는 여러 자동차 모델을 소유할 수 있습니다.

자동차 제조사는 이름 외에도 애플리케이션 사용자가 런타임에 동적으로 입력하는 속성(열)으로 풍부해집니다. 예시된 사용 사례는 간단합니다:

  • 사용자에게 동적 속성의 목표 이름과 유형을 제공하도록 요청합니다.
  • 몇몇 자동차 제조업체가 이전에 추가된 동적 속성의 구체적인 값으로 생성되고,
  • 엔티티는 초기 및 런타임 정의 속성으로 설명되어 다시 로드됩니다.

초기 엔티티는 아래의 데이터베이스 테이블을 사용하여 매핑됩니다:

SQL

 

해당 도메인 클래스는 위의 데이터베이스 테이블에 대한 매핑을 구성하기 위해 ORM 특정 주석으로 장식됩니다.

Java

 

Java

 

몇 가지 고려 사항:

  • @Table – 클래스를 데이터베이스 테이블에 매핑(연결)합니다.
  • @PkColumnid(고유 식별자)를 테이블 기본 키에 매핑합니다.
  • @Column – 클래스 멤버를 테이블 열에 매핑합니다.
  • @Child – 다른 엔티티와의 관계를 정의합니다.
  • @Child 주석이 달린 멤버 – 지연 로드되도록 구성됩니다.
  • type 테이블 열 – enum 필드인 CarType에 매핑됩니다.

CarManufacturer 클래스가 런타임 정의 속성(런타임 정의 테이블 열에 매핑됨)을 지원하기 위해 아래와 같은 서브클래스가 정의됩니다:

Java

 

이 클래스는 런타임 정의 속성(필드)을 Map에 저장합니다. 런타임 필드 값과 ORM 간의 상호작용은 DynamicColumnEntity  인터페이스의 구현을 통해 이루어집니다.

Java

 

  • setValue() – 테이블에서 읽을 때 런타임으로 정의 된 열의 값을 설정하는 데 사용됩니다.
  • getValue() – 테이블에 저장될 때 런타임으로 정의 된 열의 값을 검색하는 데 사용됩니다.

DynamicColumn 은 컴파일 타임 알려진 멤버를 매핑하는 @Column 주석과 유사한 방식으로 런타임으로 정의 된 속성을 해당 열에 매핑합니다.

애플리케이션을 실행할 때 CfRunner이 실행됩니다. 사용자는 CarManufacturer 엔티티를 풍부하게 하는 원하는 동적 사용자 지정 속성의 이름과 유형을 입력하도록 요청됩니다(단순화를 위해 intvarchar 유형 만 지원됨). 

각 이름-유형 쌍에 대해 새 열이 CarManufacturer 데이터베이스 테이블에 추가 될 수 있도록 DML 명령이 실행됩니다. 다음 메서드(CarService에 선언됨)가 작업을 수행합니다.

Java

 

각 입력 속성은 DefaultDynamicColumn, DynamicColumn 참조 구현으로 기록됩니다.

모든 속성이 정의되면 사용자가 각 속성에 대한 값들을 제공하여 두 자동차 제조업체를 데이터베이스에 추가합니다.

Java

 

아래 메서드(CarService에 선언됨)는 실제로 ORM을 통해 엔티티를 생성합니다.

Java

 

OrmOperations update() 메서드의 2 매개변수 버전이 호출되며, 이는 UpdateSettings 인스턴스를 전달하고 실행 시 ORM에 런타임 정의된 값이 영속화되어야 함을 전달할 수 있게 해줍니다.

마지막으로, 이전에 추가된 자동차 제조업체 중 하나에 해당하는 두 개의 자동차 모델이 생성됩니다.

Java

 

아래 메서드(CarService에 선언됨)는 실제로 ORM을 통해 엔티티를 생성하며, 이번에는 동적 속성이 없는 엔티티를 영속화하기 위해 OrmOperations의 update() 메서드를 사용합니다. 편의를 위해 한 번의 호출로 여러 개의 엔티티가 생성됩니다.

Java

 

마지막 단계로, 생성된 제조업체 중 하나가 ORM 생성 쿼리를 사용하여 이름으로 다시 로드됩니다.

Java

 

위에서 정의한 메서드에 대한 몇 가지 설명이 필요합니다.

OrmOperations newSqlBuilder() 메서드는 SqlBuilder 인스턴스를 생성하며, 이름에서 알 수 있듯이 SQL 쿼리를 생성하는 데 사용할 수 있습니다. SqlBuilder select() 메서드는 쿼리의 테이블에서 선택 부분을 생성하며, 나머지 부분(where, order by)은 추가해야 합니다. 쿼리 선택 부분은 EntityDescriptorNodeCallback 인스턴스를 전달하여 사용자 정의할 수 있습니다(상세한 내용은 EntityDescriptorNodeCallback에 대한 향후 기사에서 다룰 수 있습니다).

ORM이 런타임 정의된 열을 선택하고 매핑할 계획임을 알리기 위해 DynamicColumnsEntityNodeCallback 를 전달해야 합니다. 함께 제공되는 AutoEagerLoader 는 ORM이 제조업체와 관련된 CarModel 목록을 즉시 로드해야 함을 이해하도록 돕습니다. 그럼에도 불구하고 이는 런타임 정의 속성과는 관련이 없지만, 자식 멤버가 즉시 로드될 수 있는 방법을 보여줍니다.

결론

관계형 데이터베이스에 데이터가 저장될 때 런타임 정의 열을 처리하는 다른 방법이 있을 수 있지만, 이 기사에서 제시한 접근 방식은 ORM에 의해 직접 생성된 표준 SQL 쿼리를 사용하여 읽고 쓰는 표준 데이터베이스 열을 사용하는 이점이 있습니다.

우리는 “커뮤니티”에서 asentinel-orm과 같은 도구를 개발해야 했던 이유에 대해 논의할 기회를 가졌던 것은 드물지 않았습니다. 일반적으로 첫눈에 개발자들은 커스텀 ORM에 대해 주저하고 조심스러워하며 Hibernate나 다른 JPA 구현을 사용하지 않는 이유를 물었습니다.

우리의 경우, 주된 요인은 비즈니스 도메인의 일부인 엔티티에 대해 때때로 꽤 많은 런타임 정의 속성(열)을 다루는 빠르고 유연하며 쉬운 방법의 필요성이었습니다. 우리에게는 이것이 올바른 방법으로 입증되었습니다. 애플리케이션은 생산 환경에서 원활하게 실행되고 있으며, 고객들은 속도와 성능에 만족하고 있으며, 개발자들은 직관적인 API 덕분에 편안하고 창의적입니다.

프로젝트가 이제 오픈 소스가 되었기 때문에, 관심 있는 누구나 쉽게 살펴보고, 객관적인 의견을 형성하며, 왜 안 되겠어요, 포크하고 PR을 열고 기여할 수 있습니다.

리소스

  • 오픈 소스 ORM 프로젝트는 여기에 있습니다.
  • 샘플 애플리케이션의 소스 코드는 여기에 있습니다.

Source:
https://dzone.com/articles/runtime-defined-columns-with-asentinel-orm