アレについて記す

Spring BootのEntityからDDLを生成するGradleプラグインを作る

Posted on February 09, 2015 at 02:19 (JST)

Grailsでは$ schema-exportというコマンドによりDDLを生成する機能があります。
Spring Bootでも同じくらい気軽にCreate文を作成するために、Gradleプラグインを作成してみました。
DDL生成にはHibernate Toolsを使用しています。

作成したサンプルはGithubにて公開しています。

プラグイン呼び出しアプリの設定

まずは呼び出し元の設定から。

[ build.gradle(抜粋)]

buildscript {
    repositories {
        maven { url 'http://onbass-naga.github.io/mvn/' } //(1)
    }
    dependencies {
        classpath 'area-b:gradle-hbm-schema-export-plugin:0.1' //(2)
        classpath fileTree(dir:'build/libs', include:'*.jar')  //(3)
    }
}
apply plugin: 'hbm-schema-export' //(4)
schemaExport { //(5)
    classpath = 'com/example/app/'
    outputFile = 'sample.ddl'
    delimiter = ';'
    dialectClass = PostgreSQL9Dialect.class
}

(1)GithubにたてたMavenリポジトリのURLです。ここからプラグインを取得します。
(2)作成したプラグインを指定。余談ですがHibernateのソースでは略称としてhbmが使われることが多いです。
(3)対象アプリをbuildしたjarを食わせます。
(4)お約束。
(5)プラグインの動作制御用の設定です。
今回作成したプラグインはかなり機能が制限されています。
本当はclassファイルを直接読み込んで生成しようと考えていたんですが、クラスローダーの壁にぶちあたったので今回はスキップします。
Try & Error で制限事項を推測しながら知見を貯めてくのは時間がかかりますね。。。まあ、それも楽しいんですが。
なお、Mavenリポジトリへの登録方法は過去記事をご覧ください。

下記コマンドでDDL生成ができます。(開発環境はMacです。)
$ ./gradlew schemaExport

[ 出来上がったSQL ]

drop table Purchase cascade;  
drop sequence hibernate_sequence;
create table Purchase (
    id int8 not null,
    address varchar(255),
    email varchar(255),
    giftWrapping varchar(255),
    name varchar(255),
    paymentMethod varchar(255),
    prefecture int4,
    tel varchar(255),
    primary key (id)
); 
create sequence hibernate_sequence;

dialectClassに指定したDB用のDDLが生成されます。
使用出来るDBはorg.hibernate.dialect.*Dialectとなります。

DDL生成プラグイン

プラグイン自体はGradleの公式ドキュメント(翻訳版)そのままで作れました。

[ SchemaExportPluginExtension.groovy ]

class SchemaExportPluginExtension {
    String classpath
    String outputFile
    String delimiter
    Class<Dialect> dialectClass
}

呼び出し元から受け取る値を定義したDTOはExtensionと命名するのがスタンダードです。

[ SchemaExportPlugin.groovy ]

class SchemaExportPlugin implements Plugin<Project> { //(1)
    void apply(Project project) {
        project.extensions.create("schemaExport", SchemaExportPluginExtension) //(2)
        project.task('schemaExport') << { //(3)
            def ext = project.schemaExport //(4)
            def resources = findresources(ext.classpath)
            def configuration = createConfiguration(resources, ext.dialectClass)
            new SchemaExport(configuration)
                    .setOutputFile(ext.outputFile)
                    .setDelimiter(ext.delimiter)
                    .create(true, false);
        }
    }
    def findresources(path) {
        def classpath = path.endsWith("/")? path : path + "/"
        def locationPattern = "classpath:<span>$</span>{classpath}**/*.class";
        def resourcePatternResolver = new PathMatchingResourcePatternResolver();
        def resources = resourcePatternResolver.getResources(locationPattern);
        return resources
    }
    def createConfiguration(resources, dialectClass) {
        def dialectName = dialectClass.getCanonicalName()
        def configuration = new Configuration()
        configuration.setProperty(HIBERNATE_DIALECT, dialectName)
        def readerFactory = new SimpleMetadataReaderFactory()
        resources.each({ resource ->
            def metadataReader = readerFactory.getMetadataReader(resource)
            def metadata = metadataReader.getAnnotationMetadata()
            if (metadata.hasAnnotation(Entity.class.getName())) {
                URL[] urls = [resource.URL]
                def loader = URLClassLoader.newInstance(urls, getClass().getClassLoader())
                def clazz = loader.loadClass(metadata.getClassName())
                configuration.addAnnotatedClass(clazz)
            }
        })
        return configuration
    }
}

(1)Gradleプラグイン用インターフェースを実装します。
(2)呼び出しもとアプリのbuild.gradleで設定する際の変数名をマッピングします。
(3)この処理を起動するタスク名を指定します。
(4)呼び出し元から渡された値は[project.(2)で指定した変数名]で読み込みます。
DDL生成のロジックでは、指定されたクラスパスから@Entityの付与されたクラスを探し出して処理しています。
現状のロジックではJavaのClassloaderの制限により、この処理が走っているプロセスが起動したタイミングで読み込んでいたクラス以外は処理出来ません。

[ src/main/resources/META-INF/gradle-plugins/hbm-schema-export.properties ]

implementation-class=com.area_b.gradle.plugins.hbm.tools.SchemaExportPlugin

ファイル名にて呼び出し元のapply plugin: 'hbm-schema-export'表記を決定します。

今回はじめてGradleのプラグインを生成しましたが、自分で作ると見えてくる物がありますね。
公式ドキュメント+Gradle徹底入門 を参考に、今後も遊んでみたいと思います。
AntやMavenにはない、遊び心をくすぐる魅力がGradleの真価だと個人的には思います。

なお、DDLの生成は使用頻度が高くないため、このプラグインの強化機能予定はありません。
下記のプラグインの方が高機能だと思いますので、興味のある方は試してみてください。
divinespear/jpa-schema-gradle-plugin

作った後にプラグインがあることに気づいたのですが、そのおかげで色々学べました。たぶんラッキー♪

以上です。