commit 9d69a95a5b864641c2a2cf74b0d998da47d31a9c Author: kihwan.song Date: Fri May 29 10:45:56 2026 +0900 init diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..950a96d Binary files /dev/null and b/.DS_Store differ diff --git a/README.md b/README.md new file mode 100644 index 0000000..39cb4ac --- /dev/null +++ b/README.md @@ -0,0 +1,101 @@ +# Telosys JPA Starter (config-driven entity & repository generator) + +DB 스키마 → JPA **엔티티 + 레포지토리** 자동 생성기. +- 컬럼명 기반 `@Convert`/enum 매핑은 **설정 파일 한 개(`converters.csv`)** 로 관리 +- DB 컬럼 코멘트는 필드 Javadoc 으로 자동 삽입 +- 모든 출력 경로는 `telosys-tools.cfg` 의 4개 변수로 통제 + +새 프로젝트에 이 폴더를 풀고 **3개 파일 + cfg 의 4개 경로 변수** 만 채우면 됩니다. + +``` +TelosysTools/ +├── telosys-tools.cfg ← ① 4개 패키지 경로 수정 +├── databases.yaml ← ② DB 접속정보 수정 (커밋 금지) +├── lib/ ← ③ JDBC 드라이버 jar 넣기 +├── models/ (자동 생성됨) +└── templates/ + ├── jpa-entities/ Base/Concrete 엔티티 생성 bundle + │ ├── templates.cfg (출력 경로 규칙) + │ ├── converters.csv ← ④ 컨버터 매핑 키 입력 (핵심) + │ ├── include/ (수정 불필요) + │ └── main-java/ + │ ├── XxxBase_java.vm @MappedSuperclass 부모 생성 + │ └── XxxConcrete_java.vm @Entity 자식 생성 + └── jpa-repositories/ Base/Concrete 레포지토리 생성 bundle + ├── templates.cfg + ├── include/ + └── main-java/ + ├── XxxRepositoryBase_java.vm @NoRepositoryBean 부모 인터페이스 생성 + └── XxxRepositoryConcrete_java.vm @Repository 자식 인터페이스 생성 +``` + +## 생성 구조 (Base / Concrete 상속) + +### 엔티티 — `jpa-entities` bundle +각 테이블마다 2개 클래스 생성: +- `Base.java` — `@MappedSuperclass`, **DB와 항상 1:1** (매번 재생성, 절대 수정 금지). 모든 컬럼·`@Convert` 매핑·DB 컬럼 코멘트(Javadoc)가 여기. +- `.java` — `@Entity extends Base`, **커스텀 영역** (없을 때만 1회 생성 → 재생성해도 보존). 비즈니스 로직·예외 매핑(`@Convert(attributeName=...)`, `@AttributeOverride`) 은 여기. + +``` +${ENTITY_BASE_PKG}/Base.java (재생성됨, 손대지 마세요) +${ENTITY_CONCRETE_PKG}/.java (커스텀, 보존됨) +``` + +### 레포지토리 — `jpa-repositories` bundle +각 테이블마다 2개 인터페이스 생성: +- `JpaRepositoryBase.java` — `@NoRepositoryBean`, `JpaRepository + JpaSpecificationExecutor` 상속 (매번 재생성, 수정 금지). 주요 컬럼 코멘트 Javadoc 포함. +- `JpaRepository.java` — `@Repository extends JpaRepositoryBase`, **커스텀 쿼리 영역** (없을 때만 1회 생성 → 재생성해도 보존). 모든 커스텀 메소드 / `@Query` 는 여기. + +``` +${REPO_BASE_PKG}/JpaRepositoryBase.java (재생성됨, 손대지 마세요) +${REPO_CONCRETE_PKG}/JpaRepository.java (커스텀 쿼리, 보존됨) +``` + +## 0. Telosys CLI 설치 (PC당 1회) +```bash +mkdir -p ~/tools/telosys && cd ~/tools/telosys +curl -sL -o t.zip https://github.com/telosys-tools-bricks/telosys-cli/releases/download/4.3.0-001/telosys-cli-4.3.0-001.zip +unzip -o t.zip && chmod +x tt +# 실행 별칭: alias tt='~/tools/telosys/tt' +``` + +## 1. 새 프로젝트에 풀기 +이 zip 을 프로젝트 루트에서 풀면 `TelosysTools/` 가 생깁니다. + +## 2. 파일 채우기 +- `telosys-tools.cfg` : **4개 경로 변수** 수정 + - `ProjectVariable.ENTITY_BASE_PKG` (예: `com.daewoong.platform.file.infrastructure.entity`) + - `ProjectVariable.ENTITY_CONCRETE_PKG` (예: `com.daewoong.platform.file.infrastructure.entity.concrete`) + - `ProjectVariable.REPO_BASE_PKG` (예: `com.daewoong.platform.file.infrastructure.persistence`) + - `ProjectVariable.REPO_CONCRETE_PKG` (예: `com.daewoong.platform.file.infrastructure.persistence.concrete`) +- `databases.yaml` : `HOST/DBNAME/USER/PASSWORD` 수정 +- `lib/` : DB용 JDBC 드라이버 jar 넣기 +- `templates/jpa-entities/converters.csv` : `컬럼명 ; enumFQN ; 컨버터FQN` 입력 + +## 3. 생성 +```bash +cd +~/tools/telosys/tt +telosys> h . # home = 프로젝트 루트 +telosys> cdb db # DB 연결 확인 +telosys> nm mymodel db # DB에서 모델 추출 (한 번만 — .entity 는 손으로 관리) +telosys> m mymodel +telosys> b jpa-entities # 엔티티 bundle 선택 +telosys> gen * * # → y (전 엔티티 Base/Concrete 생성) +telosys> b jpa-repositories # 레포지토리 bundle 선택 +telosys> gen * * # → y (전 레포지토리 Base/Concrete 생성) +``` + +## 동작 원리 +- **컬럼 매핑** : `converters.csv` 의 컬럼명과 일치하는 Base 필드는 `@Convert(converter=...) private ...` 로 생성. 매핑 없는 컬럼은 표준 타입. +- **컬럼 코멘트** : DB 의 column comment 가 있으면 자동으로 `/** comment */` Javadoc 으로 Base 필드에 삽입. 레포지토리 Base 의 클래스 Javadoc 에도 컬럼별 요약이 들어감. +- **개별 예외** : 특정 테이블만 다르게 → 그 `.entity` 컬럼에 `#javaType(...) #converter(...)` 태그로 override. +- **새 컨버터 추가** = `converters.csv` 한 줄. 템플릿·엔티티 무수정. +- **PK 타입** : Base 의 키 attribute 에서 자동 추출 (`@Id` 컬럼의 Java 타입). 단일 키 가정. +- **DB 스키마 변경 시** 재생성 → Base 만 갱신 (새 컬럼·코멘트 반영), Concrete 의 커스텀은 보존. + +## 주의 +- `.entity` 모델 파일은 한 번 추출 후 **손으로 관리하는 소스** 입니다 (DB 재추출이 덮어쓰지 않음). git 에 커밋해 관리하세요. +- `databases.yaml` 은 비밀번호 때문에 `.gitignore` 에 추가 권장. +- `ENTITY_CONCRETE_PKG` / `REPO_CONCRETE_PKG` 는 `ENTITY_BASE_PKG` / `REPO_BASE_PKG` 의 서브패키지가 아니어도 됩니다 — 프로젝트 구조에 맞게 자유롭게 지정 가능. +- **이전 버전에서 마이그레이션** : 자식 엔티티 위치가 `template` → `concrete` 로 변경됐습니다. 기존 `*.template/` 폴더는 다음 재생성 시 orphan 되므로 직접 삭제 또는 이동 필요. diff --git a/databases.yaml b/databases.yaml new file mode 100644 index 0000000..a386862 --- /dev/null +++ b/databases.yaml @@ -0,0 +1,35 @@ +# Telosys 데이터베이스 설정 (★ 접속정보 채우기 ★) +# ⚠ 비밀번호가 들어가므로 이 파일은 git 에 커밋하지 마세요 (.gitignore 권장). +# JDBC 드라이버 jar 는 TelosysTools/lib/ 에 넣으면 자동 인식됩니다. + +databases : + + - id: db + name: my database + type: MYSQL + driver: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://DB:3306/DATABASE?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Seoul&allowPublicKeyRetrieval=true + user: ID + password: PW + # MySQL 은 DB명이 catalog 역할 + catalog: DATABASE + schema: '!' + tableNamePattern: '%' + tableTypes: TABLE + linksManyToOne: true + linksOneToMany: false + linksManyToMany: false + dbComment: true + dbName: true + dbType: true + +# PostgreSQL 예시 +# - id: pg +# type: POSTGRESQL +# driver: org.postgresql.Driver +# url: jdbc:postgresql://HOST:5432/DBNAME +# user: USER +# password: PASSWORD +# catalog: '!' +# schema: public +# tableTypes: TABLE diff --git a/lib/PUT-JDBC-DRIVER-HERE.txt b/lib/PUT-JDBC-DRIVER-HERE.txt new file mode 100644 index 0000000..07123f7 --- /dev/null +++ b/lib/PUT-JDBC-DRIVER-HERE.txt @@ -0,0 +1,3 @@ +JDBC 드라이버 jar 를 이 폴더에 넣으세요. +예: mysql-connector-j-9.2.0.jar (Gradle/Maven 캐시에서 복사) +Telosys 가 lib/ 의 jar 를 자동으로 클래스패스에 올립니다. diff --git a/lib/mysql-connector-j-9.2.0.jar b/lib/mysql-connector-j-9.2.0.jar new file mode 100644 index 0000000..9794444 Binary files /dev/null and b/lib/mysql-connector-j-9.2.0.jar differ diff --git a/models/README.txt b/models/README.txt new file mode 100644 index 0000000..fc78c5c --- /dev/null +++ b/models/README.txt @@ -0,0 +1 @@ +# 모델은 DB에서 추출되어 여기 생성됩니다 (nm ). diff --git a/telosys-tools.cfg b/telosys-tools.cfg new file mode 100644 index 0000000..508e920 --- /dev/null +++ b/telosys-tools.cfg @@ -0,0 +1,19 @@ +# ------------------------------------------- +# Telosys project configuration (새 프로젝트에서 이 값들만 바꾸면 됨) +# ------------------------------------------- + +# --- 추가 키가 필요하면 ProjectVariable.XXX=값 으로 정의하고 +# 템플릿에서 ${XXX} 로 사용 가능 +ProjectVariable.DB_JPA_DIALECT=org.hibernate.dialect.MySQLDialect + +# ------------------------------------------- +# --- Base/Concrete 분리 경로 (★ 프로젝트에 맞게 수정 ★) +# ENTITY_BASE_PKG : 부모 엔티티 (@MappedSuperclass) - 매번 재생성 +# ENTITY_CONCRETE_PKG : 자식 엔티티 (@Entity) - 1회 생성 후 보존 +# REPO_BASE_PKG : 부모 레포지토리 (@NoRepositoryBean) - 매번 재생성 +# REPO_CONCRETE_PKG : 자식 레포지토리 (@Repository) - 1회 생성 후 보존 +# ------------------------------------------- +ProjectVariable.ENTITY_BASE_PKG=com.example.persistence.entity +ProjectVariable.ENTITY_CONCRETE_PKG=com.example.persistence.entity.concrete +ProjectVariable.REPO_BASE_PKG=com.example.persistence.repository +ProjectVariable.REPO_CONCRETE_PKG=com.example.persistence.repository.concrete diff --git a/telosys.env b/telosys.env new file mode 100644 index 0000000..92c126e --- /dev/null +++ b/telosys.env @@ -0,0 +1,4 @@ +# +#Fri May 29 10:35:38 KST 2026 +bundle=jpa-repositories +model=mymodel diff --git a/templates/jpa-entities/converters.csv b/templates/jpa-entities/converters.csv new file mode 100644 index 0000000..5b563a9 --- /dev/null +++ b/templates/jpa-entities/converters.csv @@ -0,0 +1,16 @@ +# ===================================================================== +# CONVERTER 매핑 (이 프로젝트 전용 키를 여기에 전부 입력) +# 형식(세미콜론 구분) : DB컬럼명 ; enum타입(FQN) ; AttributeConverter(FQN) +# - 같은 컬럼명을 가진 모든 엔티티에 자동 적용됨 +# - 새 컨버터가 생기면 이 파일에 한 줄만 추가. 템플릿/엔티티 수정 불필요. +# - 특정 테이블만 예외 처리하려면 그 .entity 컬럼에 태그로 override : +# myCol : string { ... #javaType(com.x.MyEnum) #converter(com.x.MyEnumConverter) }; +# ===================================================================== + +# ---- shared_kernel 공통 ------------------------------------------------ +exposure_status ; com.daewoong.shared_kernel.domain.model.Exposure ; com.daewoong.shared_kernel.domain.converter.ExposureStringConverter +langcd ; com.daewoong.shared_kernel.domain.model.Lang ; com.daewoong.shared_kernel.domain.converter.LangConverter + +# ---- newsroom.media 도메인 -------------------------------------------- +media_file_use ; com.daewoong.contexts.newsroom.media.domain.model.UseFlag ; com.daewoong.contexts.newsroom.media.domain.convert.UseFlagConverter +media_file_type ; com.daewoong.contexts.newsroom.media.domain.model.MediaFileType ; com.daewoong.contexts.newsroom.media.domain.convert.MediaFileTypeConverter diff --git a/templates/jpa-entities/include/auto_imports.vm b/templates/jpa-entities/include/auto_imports.vm new file mode 100644 index 0000000..f8da813 --- /dev/null +++ b/templates/jpa-entities/include/auto_imports.vm @@ -0,0 +1,63 @@ +## ===================================================================== +## AUTO IMPORTS SCANNER +## type-mapping.csv + converters.csv 의 매칭 행에서 FQN 을 추출해 +## $extraImports 에 모은다. XxxBase 에서 import 로 emit. +## - CSV 는 FQN 으로 작성, 짧은 이름 변환은 자동. +## - java.lang.* 는 import 안 함. +## Velocity 1.x 산술 미지원 → $foreach.index 사용. +## ===================================================================== +#set( $extraImports = [] )## +#foreach( $attribute in $entity.nonKeyAttributes )## +#foreach( $row in $typeMapRows )## +#if( $row.size() >= 2 && $row.get(0).trim().equals($attribute.databaseName) )## +#set( $tStr = $row.get(1).trim() )## +#set( $ltI = $tStr.indexOf("<") )## +#if( $ltI >= 0 )#set( $bFqn = $tStr.substring(0, $ltI) )#else#set( $bFqn = $tStr )#end## +#if( $bFqn.contains(".") && !$bFqn.startsWith("java.lang.") && !$extraImports.contains($bFqn) )## +#set( $void = $extraImports.add($bFqn) )## +#end## +#if( $row.size() >= 3 )## +#set( $aStr = $row.get(2).trim() )## +#set( $tokens = $aStr.split("[^A-Za-z0-9_.]+") )## +#foreach( $tok in $tokens )## +#if( $tok.contains(".") )## +#set( $segs = $tok.split("[.]") )## +#set( $upIdx0 = -1 )## +#foreach( $seg in $segs )## +#if( $upIdx0 < 0 && $seg.length() > 0 )## +#set( $c0 = $seg.substring(0,1) )## +#if( !$c0.equals($c0.toLowerCase()) )#set( $upIdx0 = $foreach.index )#end## +#end## +#end## +#if( $upIdx0 > 0 )## +#set( $f = "" )## +#foreach( $seg in $segs )## +#if( $foreach.index == 0 )#set( $f = $seg )#elseif( $foreach.index <= $upIdx0 )#set( $f = "${f}.${seg}" )#end## +#end## +#if( !$f.startsWith("java.lang.") && !$extraImports.contains($f) )#set( $void = $extraImports.add($f) )#end## +#end## +#end## +#end## +#end## +#end## +#end## +#foreach( $row in $convRows )## +#if( $row.size() >= 3 && $row.get(0).trim().equals($attribute.databaseName) )## +#set( $ct = $row.get(1).trim() )## +#if( $ct.contains(".") && !$ct.startsWith("java.lang.") && !$extraImports.contains($ct) )#set( $void = $extraImports.add($ct) )#end## +#set( $cc = $row.get(2).trim() )## +#if( $cc.contains(".") && !$cc.startsWith("java.lang.") && !$extraImports.contains($cc) )#set( $void = $extraImports.add($cc) )#end## +#end## +#end## +#if( $attribute.hasTag("javaType") )## +#set( $jt = $attribute.tagValue("javaType") )## +#set( $jtBase = $jt )## +#set( $ltJ = $jt.indexOf("<") )## +#if( $ltJ >= 0 )#set( $jtBase = $jt.substring(0, $ltJ) )#end## +#if( $jtBase.contains(".") && !$jtBase.startsWith("java.lang.") && !$extraImports.contains($jtBase) )#set( $void = $extraImports.add($jtBase) )#end## +#end## +#if( $attribute.hasTag("converter") )## +#set( $cv = $attribute.tagValue("converter") )## +#if( $cv.contains(".") && !$cv.startsWith("java.lang.") && !$extraImports.contains($cv) )#set( $void = $extraImports.add($cv) )#end## +#end## +#end## diff --git a/templates/jpa-entities/include/converters_mapping.vm b/templates/jpa-entities/include/converters_mapping.vm new file mode 100644 index 0000000..0ff1032 --- /dev/null +++ b/templates/jpa-entities/include/converters_mapping.vm @@ -0,0 +1,12 @@ +## ===================================================================== +## CONVERTER MAPPING LOADER +## 외부 설정 파일 'converters.csv' 를 읽어 행 리스트($convRows)로 보관. +## 형식(세미콜론 구분): DB컬럼명 ; enum타입FQN ; 컨버터FQN +## -> 이 .vm 은 수정 불필요. 매핑은 converters.csv 에서만 관리. +## ===================================================================== +#set( $convFile = $fn.fileFromBundle("converters.csv") ) +#if( $convFile.exists() ) +#set( $convRows = $convFile.loadValues(";") ) +#else +#set( $convRows = [] ) +#end diff --git a/templates/jpa-entities/include/init_var_entity.vm b/templates/jpa-entities/include/init_var_entity.vm new file mode 100644 index 0000000..b310f7e --- /dev/null +++ b/templates/jpa-entities/include/init_var_entity.vm @@ -0,0 +1,9 @@ +## #set($recordClass = "${entity.name}Record" ) +## #set($recordInstance = $fn.uncapitalize($entity.name) ) +## +#set($jpaEntityClass = "${entity.name}" ) +#set($jpaEntityIdClass = "${entity.name}Id" ) +## +## #set($jpaMapperClass = "${entity.name}RecordMapper" ) +## #set($jpaMapperInstance = "${recordInstance}Mapper" ) +## diff --git a/templates/jpa-entities/include/java_header.vm b/templates/jpa-entities/include/java_header.vm new file mode 100644 index 0000000..313ba99 --- /dev/null +++ b/templates/jpa-entities/include/java_header.vm @@ -0,0 +1,4 @@ +/* + * Created on $now.date ( $now.time ) + * Generated by Telosys ( http://www.telosys.org/ ) version $generator.version + */ diff --git a/templates/jpa-entities/include/type_mapping.vm b/templates/jpa-entities/include/type_mapping.vm new file mode 100644 index 0000000..941a9fc --- /dev/null +++ b/templates/jpa-entities/include/type_mapping.vm @@ -0,0 +1,12 @@ +## ===================================================================== +## TYPE MAPPING LOADER +## 외부 설정 파일 'type-mapping.csv' 를 읽어 행 리스트($typeMapRows)로 보관. +## 형식(세미콜론 구분): DB컬럼명 ; Java타입(FQN) ; 추가 어노테이션(선택) +## -> 이 .vm 은 수정 불필요. 매핑은 type-mapping.csv 에서만 관리. +## ===================================================================== +#set( $typeMapFile = $fn.fileFromBundle("type-mapping.csv") ) +#if( $typeMapFile.exists() ) +#set( $typeMapRows = $typeMapFile.loadValues(";") ) +#else +#set( $typeMapRows = [] ) +#end diff --git a/templates/jpa-entities/main-java/XxxBase_java.vm b/templates/jpa-entities/main-java/XxxBase_java.vm new file mode 100644 index 0000000..a5726ed --- /dev/null +++ b/templates/jpa-entities/main-java/XxxBase_java.vm @@ -0,0 +1,145 @@ +## -------------------------------------------------- +## BASE 엔티티 (@MappedSuperclass) — DB에서 매번 재생성 (덮어씀) +## + concrete( @Entity )는 "없을 때만" 1회 생성 (있으면 보존) +## 경로는 telosys-tools.cfg 의 ENTITY_BASE_PKG / ENTITY_CONCRETE_PKG 로 결정 +## +## CSV 들 (converters.csv, type-mapping.csv) 의 FQN 은 자동으로 짧은 이름 +## 으로 치환되고, 필요한 import 가 자동 추가됨 (auto_imports.vm 참고). +## +## Lombok : +## @Getter @Setter @SuperBuilder @NoArgsConstructor @AllArgsConstructor +## (@Builder 가 아닌 @SuperBuilder — Base/Concrete 상속에서 builder() 가 +## Concrete 타입으로 동작하도록) +## +## Swagger : @Schema(description = "DB 컬럼 코멘트") 자동 부착 +## -------------------------------------------------- +#if ( $entity.isJoinEntity() ) +#cancel("No JPA class for join entity") +#end +#checkId($entity) +#parse("include/init_var_entity.vm") +#parse("include/java_header.vm") +#parse("include/converters_mapping.vm") +#parse("include/type_mapping.vm") +#parse("include/auto_imports.vm") +##--------------------------------------------------------------------------------------- +#set( $entityClass = "${entity.name}JpaEntity" ) +#set( $entityBaseClass = "${entity.name}JpaEntityBase" ) +#set( $basePath = $ENTITY_BASE_PKG.replace(".", "/") ) +#set( $concretePath = $ENTITY_CONCRETE_PKG.replace(".", "/") ) +#set( $concreteFolder = "${SRC}/${concretePath}" ) +#set( $concreteFile = "${concreteFolder}/${entityClass}.java" ) +#if( ! $fn.file($concreteFile).exists() ) +$generator.generate($entity.name, "${entityClass}.java", $concreteFolder, "main-java/XxxConcrete_java.vm") +#end +##--------------------------------------------------------------------------------------- +package ${ENTITY_BASE_PKG}; + +import java.io.Serializable; +#foreach( $import in $java.imports($entity) ) +import $import; +#end +#foreach( $imp in $extraImports ) +import $imp; +#end +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.experimental.SuperBuilder; + +/** + * BASE class for "${entityClass}" — generated from DB, DO NOT EDIT (regenerated). + * 비즈니스 로직은 ${ENTITY_CONCRETE_PKG}.${entityClass} 에 작성하세요. +#if( $entity.databaseComment && $entity.databaseComment != "" ) + * + * Table comment: ${entity.databaseComment} +#end + */ +#if( $entity.databaseComment && $entity.databaseComment != "" ) +@Schema(description = "${entity.databaseComment}") +#end +@MappedSuperclass +@Getter +@Setter +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +public abstract class ${entityBaseClass} implements Serializable { + + private static final long serialVersionUID = 1L; + + //--- ENTITY PRIMARY KEY +#foreach( $attribute in $entity.keyAttributes ) +#if( $attribute.databaseComment && $attribute.databaseComment != "" ) + @Schema(description = "${attribute.databaseComment}") +#end +$jpa.fieldAnnotations(4, $attribute) + private $attribute.formattedType(10) $attribute.name #if($attribute.hasInitialValue())= ${attribute.ini} #end; + +#end + //--- ENTITY DATA FIELDS +#foreach( $attribute in $entity.nonKeyAttributes ) +## ---- 1) type-mapping.csv 우선 매칭 +#set( $mapType = false ) +#set( $mapAnno = false ) +#foreach( $row in $typeMapRows ) +#if( $row.size() >= 2 && $row.get(0).trim().equals($attribute.databaseName) ) +#set( $mapType = $row.get(1).trim() ) +#if( $row.size() >= 3 ) +#set( $mapAnno = $row.get(2).trim() ) +#end +#end +#end +## ---- 2) converters.csv 매칭 (type-mapping 매칭 없을 때만) +#set( $convType = false ) +#set( $convClass = false ) +#if( !$mapType ) +#if( $attribute.hasTag("converter") ) +#set( $convType = $attribute.tagValue("javaType") ) +#set( $convClass = $attribute.tagValue("converter") ) +#else +#foreach( $row in $convRows ) +#if( $row.size() >= 3 && $row.get(0).trim().equals($attribute.databaseName) ) +#set( $convType = $row.get(1).trim() ) +#set( $convClass = $row.get(2).trim() ) +#end +#end +#end +#end +## ---- 3) FQN → 짧은 이름 치환 +#set( $dispType = false ) +#set( $dispAnno = false ) +#set( $dispConv = false ) +#if( $mapType )#set( $dispType = $mapType )#end +#if( $mapAnno )#set( $dispAnno = $mapAnno )#end +#if( $convType )#set( $dispType = $convType )#end +#if( $convClass )#set( $dispConv = $convClass )#end +#foreach( $imp in $extraImports ) +#set( $tail = $imp.substring($imp.lastIndexOf(".")) ) +#set( $sn = $tail.substring(1) ) +#if( $dispType )#set( $dispType = $dispType.replace($imp, $sn) )#end +#if( $dispAnno )#set( $dispAnno = $dispAnno.replace($imp, $sn) )#end +#if( $dispConv )#set( $dispConv = $dispConv.replace($imp, $sn) )#end +#end +## ---- 4) 필드 출력 +#if( $attribute.databaseComment && $attribute.databaseComment != "" ) + @Schema(description = "${attribute.databaseComment}") +#end +#if( $dispAnno && $dispAnno != "" ) + ${dispAnno} +#end +#if( $dispConv ) + @Convert(converter = ${dispConv}.class) +#end +$jpa.fieldAnnotations(4, $attribute) +#if( $dispType ) + private ${dispType} $attribute.name ; +#else + private $attribute.formattedType(10) $attribute.name #if($attribute.hasInitialValue())= ${attribute.ini} #end; +#end + +#end +} diff --git a/templates/jpa-entities/main-java/XxxConcrete_java.vm b/templates/jpa-entities/main-java/XxxConcrete_java.vm new file mode 100644 index 0000000..6e76939 --- /dev/null +++ b/templates/jpa-entities/main-java/XxxConcrete_java.vm @@ -0,0 +1,27 @@ +## -------------------------------------------------- +## CONCRETE 엔티티 (@Entity) — "없을 때만" 1회 생성. 재생성해도 보존됨. +## 여기에 커스텀 비즈니스 로직을 추가하세요. +## 경로 : telosys-tools.cfg 의 ENTITY_CONCRETE_PKG +## +## @SuperBuilder : Base 의 모든 필드를 포함한 builder() 가 Concrete 타입을 +## 반환하도록 함. @NoArgsConstructor 는 JPA proxy 생성용. +## -------------------------------------------------- +#parse("include/init_var_entity.vm") +#set( $entityClass = "${entity.name}JpaEntity" ) +#set( $entityBaseClass = "${entity.name}JpaEntityBase" ) +package ${ENTITY_CONCRETE_PKG}; + +import ${ENTITY_BASE_PKG}.${entityBaseClass}; +import jakarta.persistence.*; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +@Entity +@Table(name="${entity.databaseTable}"#if($entity.databaseCatalog && $entity.databaseCatalog != ""), catalog="${entity.databaseCatalog}"#end) +@SuperBuilder +@NoArgsConstructor +public class ${entityClass} extends ${entityBaseClass} { + + // ================= 커스텀 영역 (재생성해도 보존) ================= + +} diff --git a/templates/jpa-entities/templates.cfg b/templates/jpa-entities/templates.cfg new file mode 100644 index 0000000..6a5e802 --- /dev/null +++ b/templates/jpa-entities/templates.cfg @@ -0,0 +1,16 @@ +#--------------------------------------------------------- +# Bundle configuration : label ; generated file ; target folder ; template ; once-flag(1) +#--------------------------------------------------------- +# +# Base/Concrete (상속) 구조로 생성됩니다 : +# - Base.java : @MappedSuperclass, DB 와 항상 1:1 (매번 재생성, 수정 금지) +# 위치: telosys-tools.cfg 의 ENTITY_BASE_PKG +# - .java : @Entity extends Base, 커스텀 영역 (없을 때만 1회 생성, 보존) +# 위치: telosys-tools.cfg 의 ENTITY_CONCRETE_PKG +# ↑ Base 템플릿이 내부에서 자동 생성하므로 여기엔 Base 만 등록. +# +# 컬럼 -> enum/@Convert 매핑은 converters.csv 가 결정. +# 컬럼 코멘트(DB column comment)는 자동으로 /** ... */ Javadoc 으로 삽입됩니다. +#--------------------------------------------------------- + +Base (MappedSuperclass) ; ${ENT}JpaEntityBase.java ; ${SRC}/${ENTITY_BASE_PKG} ; main-java/XxxBase_java.vm ; * diff --git a/templates/jpa-entities/type-mapping.csv b/templates/jpa-entities/type-mapping.csv new file mode 100644 index 0000000..8109323 --- /dev/null +++ b/templates/jpa-entities/type-mapping.csv @@ -0,0 +1,15 @@ +# ===================================================================== +# TYPE MAPPING (converter 가 아닌 커스텀 타입 매핑) +# 형식(세미콜론 구분) : DB컬럼명 ; Java타입(FQN) ; 추가 어노테이션(선택) +# +# - converter 가 아니라 Hibernate @JdbcTypeCode / @Type / 사용자 정의 UserType 등 +# "필드 타입 자체를 바꾸고 특수 어노테이션을 같이 붙여야 하는" 케이스용. +# - 같은 컬럼명을 가진 모든 엔티티에 자동 적용됨. +# - converters.csv 와 매칭이 겹치면 → 이 파일이 우선. +# - 새 커스텀 타입이 생기면 이 파일에 한 줄만 추가. 템플릿/엔티티 수정 불필요. +# - 추가 어노테이션은 FQN 으로 작성 (import 자동 추가 안 됨). +# - 어노테이션이 필요 없으면 세 번째 칸을 비워둘 수 있음. +# ===================================================================== + +# ---- JSON 컬럼 --------------------------------------------------------- +tag_ids ; java.util.List ; @org.hibernate.annotations.JdbcTypeCode(org.hibernate.type.SqlTypes.JSON) diff --git a/templates/jpa-repositories/include/init_var_repo.vm b/templates/jpa-repositories/include/init_var_repo.vm new file mode 100644 index 0000000..42d9a98 --- /dev/null +++ b/templates/jpa-repositories/include/init_var_repo.vm @@ -0,0 +1,26 @@ +## -------------------------------------------------- +## 공통 변수 초기화 (Repository 템플릿용) +## -------------------------------------------------- +#set( $entityName = $entity.name ) +#set( $entityClass = "${entity.name}JpaEntity" ) +#set( $repoBaseClass = "${entity.name}JpaRepositoryBase" ) +#set( $repoConcreteClass = "${entity.name}JpaRepository" ) +## +## PK 타입 추출 (단일 키 가정; 복합키는 별도 처리 필요) +## primitive 는 wrapper 로 변환 (제네릭 인자에 사용) +#set( $pkType = "Long" ) +#foreach( $a in $entity.keyAttributes ) +#if( $a.hasTag("javaType") ) +#set( $pkType = $a.tagValue("javaType") ) +#else +#set( $pkType = $a.type ) +#end +#end +#if( $pkType == "int" )#set( $pkType = "Integer" )#end +#if( $pkType == "long" )#set( $pkType = "Long" )#end +#if( $pkType == "short" )#set( $pkType = "Short" )#end +#if( $pkType == "byte" )#set( $pkType = "Byte" )#end +#if( $pkType == "boolean" )#set( $pkType = "Boolean" )#end +#if( $pkType == "char" )#set( $pkType = "Character" )#end +#if( $pkType == "float" )#set( $pkType = "Float" )#end +#if( $pkType == "double" )#set( $pkType = "Double" )#end diff --git a/templates/jpa-repositories/include/java_header.vm b/templates/jpa-repositories/include/java_header.vm new file mode 100644 index 0000000..313ba99 --- /dev/null +++ b/templates/jpa-repositories/include/java_header.vm @@ -0,0 +1,4 @@ +/* + * Created on $now.date ( $now.time ) + * Generated by Telosys ( http://www.telosys.org/ ) version $generator.version + */ diff --git a/templates/jpa-repositories/main-java/XxxRepositoryBase_java.vm b/templates/jpa-repositories/main-java/XxxRepositoryBase_java.vm new file mode 100644 index 0000000..52747f5 --- /dev/null +++ b/templates/jpa-repositories/main-java/XxxRepositoryBase_java.vm @@ -0,0 +1,59 @@ +## -------------------------------------------------- +## BASE Repository (@NoRepositoryBean) — DB 와 1:1, 매번 재생성 (덮어씀) +## + Concrete( @Repository )는 "없을 때만" 1회 생성 (있으면 보존) +## 경로는 telosys-tools.cfg 의 REPO_BASE_PKG / REPO_CONCRETE_PKG / ENTITY_CONCRETE_PKG 로 결정 +## -------------------------------------------------- +#if ( $entity.isJoinEntity() ) +#cancel("No JPA repository for join entity") +#end +#checkId($entity) +#parse("include/init_var_repo.vm") +#parse("include/java_header.vm") +##--------------------------------------------------------------------------------------- +## 경로 계산 : 모두 telosys-tools.cfg 의 변수 기반 +## Base -> REPO_BASE_PKG +## Concrete -> REPO_CONCRETE_PKG (없으면 1회 생성) +##--------------------------------------------------------------------------------------- +#set( $concretePath = $REPO_CONCRETE_PKG.replace(".", "/") ) +#set( $concreteFolder = "${SRC}/${concretePath}" ) +#set( $concreteFile = "${concreteFolder}/${repoConcreteClass}.java" ) +#if( ! $fn.file($concreteFile).exists() ) +$generator.generate($entity.name, "${repoConcreteClass}.java", $concreteFolder, "main-java/XxxRepositoryConcrete_java.vm") +#end +##--------------------------------------------------------------------------------------- +package ${REPO_BASE_PKG}; + +import ${ENTITY_CONCRETE_PKG}.${entityClass}; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; +import org.springframework.data.repository.NoRepositoryBean; + +/** + * BASE repository for "${entityClass}" — generated from DB, DO NOT EDIT (regenerated). + * 커스텀 쿼리는 ${REPO_CONCRETE_PKG}.${repoConcreteClass} 에 작성하세요. +#if( $entity.databaseComment && $entity.databaseComment != "" ) + * + * Table comment: ${entity.databaseComment} +#end + * + *

주요 컬럼:

+ *
    +#foreach( $attribute in $entity.keyAttributes ) +#if( $attribute.databaseComment && $attribute.databaseComment != "" ) + *
  • (PK) ${attribute.databaseName} — ${attribute.databaseComment}
  • +#else + *
  • (PK) ${attribute.databaseName}
  • +#end +#end +#foreach( $attribute in $entity.nonKeyAttributes ) +#if( $attribute.databaseComment && $attribute.databaseComment != "" ) + *
  • ${attribute.databaseName} — ${attribute.databaseComment}
  • +#end +#end + *
+ */ +@NoRepositoryBean +public interface ${repoBaseClass} extends JpaRepository<${entityClass}, ${pkType}>, + JpaSpecificationExecutor<${entityClass}> { + +} diff --git a/templates/jpa-repositories/main-java/XxxRepositoryConcrete_java.vm b/templates/jpa-repositories/main-java/XxxRepositoryConcrete_java.vm new file mode 100644 index 0000000..02a5017 --- /dev/null +++ b/templates/jpa-repositories/main-java/XxxRepositoryConcrete_java.vm @@ -0,0 +1,17 @@ +## -------------------------------------------------- +## CONCRETE Repository (@Repository) — "없을 때만" 1회 생성. 재생성해도 보존됨. +## 여기에 커스텀 쿼리 / 메소드를 추가하세요. +## 경로 : telosys-tools.cfg 의 REPO_CONCRETE_PKG +## -------------------------------------------------- +#parse("include/init_var_repo.vm") +package ${REPO_CONCRETE_PKG}; + +import ${REPO_BASE_PKG}.${repoBaseClass}; +import org.springframework.stereotype.Repository; + +@Repository +public interface ${repoConcreteClass} extends ${repoBaseClass} { + + // ================= 커스텀 쿼리 영역 (재생성해도 보존) ================= + +} diff --git a/templates/jpa-repositories/templates.cfg b/templates/jpa-repositories/templates.cfg new file mode 100644 index 0000000..df79843 --- /dev/null +++ b/templates/jpa-repositories/templates.cfg @@ -0,0 +1,22 @@ +#--------------------------------------------------------- +# Bundle configuration : label ; generated file ; target folder ; template ; once-flag(1) +#--------------------------------------------------------- +# +# Base/Concrete (인터페이스 상속) 구조로 생성됩니다 : +# - JpaRepositoryBase.java +# @NoRepositoryBean +# interface XxxJpaRepositoryBase extends JpaRepository, JpaSpecificationExecutor +# DB 와 1:1, 매번 재생성, 수정 금지 +# 위치: telosys-tools.cfg 의 REPO_BASE_PKG +# +# - JpaRepository.java +# @Repository +# interface XxxJpaRepository extends XxxJpaRepositoryBase +# 커스텀 쿼리/메소드를 여기에 추가 (없을 때만 1회 생성, 보존) +# 위치: telosys-tools.cfg 의 REPO_CONCRETE_PKG +# ↑ Base 템플릿이 내부에서 자동 생성하므로 여기엔 Base 만 등록. +# +# 엔티티 Concrete 위치(ENTITY_CONCRETE_PKG)를 import 합니다. +#--------------------------------------------------------- + +Base (RepositoryBase) ; ${ENT}JpaRepositoryBase.java ; ${SRC}/${REPO_BASE_PKG} ; main-java/XxxRepositoryBase_java.vm ; *