shirohoo
795
2021-05-31 20:23:27 작성 2021-06-01 16:19:24 수정됨
0
812

[JPA] 데이터베이스 기반 엔티티 클래스 원터치로 자동 생성하기 !


안녕하세요.

주니어 서버개발자입니다.

개인 블로그에 작성한 글을 그대로 가져왔으며,

주니어 개발자라 틀린 부분이 있을 수 있으니 가급적 비판적인 견해로 봐주세요.


이 글이 많은분들께 도움이 되길 바랍니다.






JPA를 사용하다 보면 데이터베이스의 테이블 명세를 보면서 엔티티 클래스를 작성하는 일이 자주 생긴다.

심지어 필드가 수십 개 정도 되면 엔티티 클래스를 만드는 일 자체가 무지막지한 노가다가 돼버리기 십상이다.

intelliJ는 이 과정을 지원해준다.

필요한 것은 오로지 groovy script뿐이다.




1. IntelliJ database tool 연동



자신이 사용하고 있는 데이터베이스를 연동해준다.



접속 정보를 모두 알맞게 입력한 후 Test Connection을 눌러준 후 통과하면 OK를 눌러준다.




2. Groovy script 적용



테이블을 우클릭하여 위의 순서대로 클릭해준다.

그러면 Generate POJOs.groovy 라는 파일이 열린다.



기본적인 script가 작성돼있는데, 해당 script를 그대로 사용하면 굉장히 이상한 엔티티 클래스가 만들어지므로

이를 입맛에 맞게 커스터마이징 해야 한다.

필자가 커스터마이징한 script는 MSSQL에 맞추긴 했는데 (회사가 MSSQL을 쓴다 😥)

그래도 대부분의 DB에서도 사용할 수 있을 것이라 여겨진다.(아닐 수도 있다.)

혹시라도 잘 맞지 않는다면 입맛대로 튜닝해서 사용하도록 하시라.

기존의 script를 모두 제거하고 아래의 script를 통째로 붙여 넣고 저장한다(CTRL + S).



import com.intellij.database.model.DasTable
import com.intellij.database.model.ObjectKind
import com.intellij.database.util.Case
import com.intellij.database.util.DasUtil
import com.intellij.psi.codeStyle.NameUtil

import javax.swing.*

/**
 * @author shirohoo 
 * @link https://github.com/shirohoo/create-automation-jpa-entity
 * @param pakageName , primaryKey
 * 
 * <pre>
 *
 *     this script's default primary key strategy is @GeneratedValue(strategy = GenerationType.IDENTITY)
 *     and specialized in Microsoft SQL Server
 *     and finally implemented Serializable so recommend that create serial version UID
 *
 *     first. enter your project package name. for example:
 *     >  com.intelliJ.psi
 *
 *     second. enter primary key column name of target database table.
 *     this script is convert input to camel case. for example 1:
 *     >  table primary key column name = MEMBER_ID
 *     >  enter primary key = memberId
 *
 *     example 2:
 *     >  table primary key column name = ID
 *     >  enter primary key = id
 *
 * </pre>
 */

columnType = [
        (~/(?i)bigint/)            : "Long",
        (~/(?i)int/)               : "Integer",
        (~/(?i)bit/)               : "Boolean",
        (~/(?i)decimal/)           : "BigDecimal",
        (~/(?i)float|double|real/) : "Double",
        (~/(?i)datetime|timestamp/): "LocalDateTime",
        (~/(?i)time/)              : "LocalTime",
        (~/(?i)date/)              : "LocalDate",
        (~/(?i)nvarchar/)          : "nvarchar",
        (~/(?i)varchar/)           : "varchar",
        (~/(?i)char/)              : "String"
]
def input = {
    JFrame jframe = new JFrame()
    String answer = JOptionPane.showInputDialog(jframe, it)
    jframe.dispose()
    answer
}
packageName = input("Enter your package name")
primaryKey = input("Enter column name of primary key ")
FILES.chooseDirectoryAndSave("Choose directory", "Choose where to store generated files") { dir ->
    SELECTION.filter {
        it instanceof DasTable && it.getKind() == ObjectKind.TABLE
    }.each {
        generate(it, dir)
    }
}

def generate(table, dir) {
    def tableName = table.getName()
    def className = convertFieldName(tableName, true)
    def fields = categorizeFields(table)
    new File(dir, className + ".java").withPrintWriter {
        out -> generate(out, tableName, className, fields)
    }
}

def generate(out, tableName, className, fields) {
    out.println "package $packageName;"
    out.println ""
    out.println "import javax.persistence.*;"
    out.println "import java.io.Serializable;"
    out.println ""
    out.println "@Entity"
    out.println "@Getter"
    out.println "@Builder"
    out.println "@ToString"
    out.println "@AllArgsConstructor"
    out.println "@NoArgsConstructor(access = AccessLevel.PROTECTED)"
    out.println "@Table(name = \"$tableName\")"
    out.println "public class $className implements Serializable {"
    out.println ""
    fields.each() {
        if (it.annos != "") {
            out.println " ${it.annos}"
        }
        if (it.name == primaryKey) {
            out.println " @Id"
            out.println " @GeneratedValue(strategy = GenerationType.IDENTITY)"
        }
        if (it.type == 'nvarchar') {
            out.println " @Nationalized"
            out.println " @Column(name = \"${it.colName}\")"
            out.println " private String ${it.name}"
        } else if (it.type == 'varchar') {
            out.println " @Column(name = \"${it.colName}\")"
            out.println " private String ${it.name}"
        } else {
            out.println " @Column(name = \"${it.colName}\")"
            out.println " private ${it.type} ${it.name}"
        }
        out.println ""
    }
    out.println "}"
}

def categorizeFields(table) {
    DasUtil.getColumns(table).reduce([]) { fields, col ->
        def spec = Case.LOWER.apply(col.getDataType().getSpecification())
        def typeStr = columnType.find {
            p, t -> p.matcher(spec).find()
        }.value
        fields += [[
                           colName: col.getName(),
                           name   : convertFieldName(col.getName(), false),
                           type   : typeStr,
                           annos  : ""]]
    }
}

def convertFieldName(str, capitalize) {
    def s = NameUtil.splitNameIntoWords(str)
            .collect {
                Case.LOWER.apply(it).capitalize()
            }
            .join("")
            .replaceAll(/[^\p{javaJavaIdentifierPart}[_]]/, "_")
    capitalize || s.length() == 1 ? s : Case.LOWER.apply(s[0]) + s[1..-1]
}





3. 엔티티 클래스 생성



이제 엔티티 클래스를 생성하고자 하는 테이블을 우클릭하여 위의 순서대로 클릭해준다.

그럼 입력창이 두 번 뜨고, 생성된 엔티티 클래스를 어떤 위치에 저장할 것인지 물을 것이다.

처음은 자신의 프로젝트 패키지명을 입력해주고,

두 번째는 테이블의 기본키 컬럼을 camel case로 변환한 이름을 입력해준다.

일반적으로 데이터베이스의 컬럼명은 대문자나 소문자의 snake case를 이용하는 게 관례이므로,

오로지 이 경우만 완벽하게 고려하여 작성하였다.

만약 다른 방식으로 사용하고 있다면 script를 변경해야 할 수도 있다.

// 예제1
테이블명: MEMBER
기본키 컬럼명: MEMBER_ID
입력해야 할 값: memberId


// 예제2
테이블명: MEMBER
기본키 컬럼명: ID
입력해야 할 값: id





그러면 이처럼 엔티티 클래스가 생성된다.

이 파일을 열어보면 아래와 같은 형식으로 작성돼있음을 확인할 수 있을 것이다. 

package io.sirohoo.groovy;

import javax.persistence.*;
import java.io.Serializable;

@Entity
@Getter
@Builder
@ToString
@AllArgsConstructor
@NoArgsConstructor(access = AccessLevel.PROTECTED) @Table(name = "request_log") public class RequestLog implements Serializable { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id") private Long id @Column(name = "mod_date") private LocalDateTime modDate @Column(name = "reg_date") private LocalDateTime regDate @Column(name = "client_ip") private String clientIp @Column(name = "http_method") private String httpMethod @Column(name = "request_uri") private String requestUri }




4. serialVersionUID 생성


Hibernate docs에서는 모든 엔티티 클래스에 대해 Serializable 인터페이스를 구현하는 걸 권장하고 있다.

대략 엔티티 매핑 방법에 따라 DB에 파라미터를 보낼 때 직렬화하여 보내야 하는 경우가 있기 때문이란다.

이 권고사항을 지키지 않고 상황이 맞아떨어질 경우 간혹 Composite-id class must implement Serializable error 같은걸 만날 수 있긴 한데, 솔직히 아주 가끔 나오는 상황이라 굳이 해당 인터페이스를 구현하지 않아도 된다고 생각하긴 한다.

그래도 공식문서 권고사항이니 가급적 지키기 위해 goorvy script에 끼워넣어뒀다.

intelliJ는 직렬화시 필요한 serialVersionUID를 랜덤으로 생성해주는 기능이 있다.


먼저 Shift를 두 번 연속 빠르게 입력한다.

그러면 아래와 같은 창이 뜬다.



Serializable class without 'se

위의 문자열을 붙여 넣어 검색하면 위의 기능이 검색되는데

저 기능을 ON으로 변경해주면 된다.


그리고 엔티티 클래스 이름에 마우스를 갖다 대면...



5
  • 댓글 0

  • 로그인을 하시면 댓글을 등록할 수 있습니다.