謎言語使いの徒然

適当に気になった技術や言語を流すブログ。

MyBatis の定義がクソ面倒なので、生成を狙う

A5:SQL Mk-2 - フリーの汎用SQL開発ツール/ER図ツール .. 松原正和 使って作ったテーブルのマッピングを書くのがだるくなったので、自動生成を狙った。

まずはこんな感じの ER を書いて、CSV でエクスポートする。

f:id:white-azalea:20171003224641p:plain

一応設定は UTF-8 で吐く。
そうすると、a5m2_COLUMNS.csv なるファイルが出来上がる。

TABLE_CATALOG,TABLE_SCHEMA,TABLE_NAME,COLUMN_NAME,LOGICAL_NAME,ORDINAL_POSITION,COLUMN_DEFAULT,IS_NULLABLE,DATA_TYPE,KEY_POSITION,DESCRIPTION
Example,,example_entity,id,ID,1,,NO,BIGINT IDENTITY,1,
Example,,example_entity,name,名前,2,,NO,NVARCHAR(64),,
Example,,example_entity,price,価格,3,,NO,@INT,,
Example,,example_entity,example_field,その他適当,4,,NO,"@DECIMAL(10, 3)",,"これが
複数行"
Example,,seller,id,ID,1,,NO,BIGINT IDENTITY,1,
Example,,seller,example_entry_id,商品ID,2,,NO,BIGINT,,
Example,,seller,name,店名,3,,NO,NVARCHAR(64),,
Example,,seller,created_at,作成ビ,4,CURRENT_TIMESTAMP,NO,@DATETIME,,
Example,,seller,updated_at,更新日,5,,YES,@DATETIME,,

あとはこれを自動生成できりゃいい。
ちなみに SQL Server 用に設定したけど、他のものを使いたければちょっと改変すればいい。

Python3 で読み込んで、マッピング java bean (Lombok) と mapping 用 XML を吐き出した。

import csv

def to_camel_name(name):
    components = name.split('_')
    return components[0] + "".join(x.title() for x in components[1:])

def to_upper_camel_name(name):
    components = name.split('_')
    return "".join(x.title() for x in components[:])

def starts_with(string_value, arr):
    for v in arr:
        if string_value.startswith(v):
            return True
    return False

class Field:
    def __init__(self, name, id, dataType, description):
        self.name = name
        self.id = id
        self.dataType = dataType
        self.description = description

    def db_type(self):
        stn = self.dataType.lower()
        convert = {
            'INTEGER'  : ['@int', 'int'],
            'BIGINT'   : ['bigint', '@bigint'],
            'DECIMAL'  : ['@decimal', 'decimal'],
            'VARCHAR'  : ['@char', 'char', 'varchar'],
            'NVARCHAR' : ['nchar', 'nvarchar'],
            'BOOLEAN'  : ['bit'],
            'TIMESTAMP': ['date', 'datetime', 'timestamp', '@datetime']
        }
        for key in convert.keys():
            if starts_with(stn, convert[key]):
                return key
        return None

    def type_name(self):
        stn = self.dataType.lower()
        convert = {
            'Integer'   : ['@int', 'int'],
            'Long'      : ['bigint', '@bigint'],
            'BigDecimal': ['@decimal', 'decimal'],
            'String'    : ['@char', 'char', 'varchar', 'nchar', 'nvarchar'],
            'Boolean'   : ['bit'],
            'Date'      : ['date', 'datetime', 'timestamp', '@datetime']
        }
        for key in convert.keys():
            if starts_with(stn, convert[key]):
                return key
        return 'Object'

    def get_field_definition(self):
        changed_type = self.type_name()
        field_name = to_camel_name(self.id)
        str = ''
        str = str + f'    /**\n'
        str = str + f'     * {self.name}.\n'
        str = str + f'     *\n'
        str = str + f'     * {self.description}.\n'
        str = str + f'     */\n'
        str = str + f'    private {changed_type} {field_name};\n\n'
        return str

class_field = {}  # クラス辞書

try:
    with open('a5m2_COLUMNS.csv', 'r') as csvfile:
        spamreader = csv.reader(csvfile, delimiter=',', quotechar='"')
        for row in spamreader:
            if len(row) == 0:
                continue
            class_name = to_upper_camel_name(row[2])
            field = Field(row[4], row[3], row[8], row[10])
            if class_name in class_field:
                class_field[class_name].append(field)
            else:
                class_field[class_name] = [field]

# 起こりそうな例外をキャッチ
except FileNotFoundError as e:
    print(e)
except csv.Error as e:
    print(e)

for class_name in class_field.keys():
    file = open(f'{class_name}.java', 'w', encoding='UTF-8')
    file.write('\nimport lombok.*;')
    file.write('\nimport java.util.Date;\n')
    file.write('\n@Data\n@NoArgsConstructor\n@AllArgsConstructor\n')
    file.write('public class ' + class_name + ' {\n')
    for field in class_field[class_name]:
        file.write(field.get_field_definition())
    file.write('}\n')
    file.close()

    file = open(f'{class_name}.mapping.xml', 'w', encoding='UTF-8')
    file.write(f'<resultMap id="{class_name}Map" type="{class_name}">\n')
    for field in class_field[class_name]:
        cur_db_type = field.db_type()
        db_str = ('jdbcType="' + cur_db_type + '"') if cur_db_type != None else ''
        file.write(f'    <result property="{to_camel_name(field.id)}" column="{field.id}" {db_str} />\n')
    file.write('</resultMap>\n')
    file.close()

このスクリプトは好きに使って。

CSV の1行目呼んで変なの出るけどご愛嬌で!