社内で2年間使用していたコードをOSSとしてMaven Centralに公開しました
スマートニュース株式会社は、社内で2年間使用してきたJPAエンティティ自動生成ライブラリ「jpa-entity-generator」をOSSとしてMaven Centralに公開し、データベーススキーマ管理とエンティティクラス共通化の課題解決を実現した。
キーポイント
社内課題の解決から生まれたツール
データベース変更レビューの統一化とエンティティクラスの共通化という2つの課題を解決するために開発され、2年間の実運用を経て成熟した。
柔軟なカスタマイズ対応
既存のエンティティクラスが実装するインターフェースや追加メソッドを維持しながらコード生成できる点が既存ツールとの差別化要素となっている。
実用的な開発プロセス改善
GitHubリポジトリでのDDL管理、PRテンプレートによるレビュー統一、CIでのDDL検証という具体的なプロセス改善と連動して導入された。
OSSとしての公開
GradleとMavenプラグインとして公開され、Java/JVM系技術スタックを持つ他組織でもすぐに利用可能な形で提供されている。
Lombokの使用状況と将来展望
社内では主に@DataアノテーションでJava beans定義を簡略化するためにLombokを使用しており、JDKアップデート対応の課題は認識しているが現時点では問題なく、将来の互換性に注意が必要。
jpa-entity-generatorの設定と機能
YAML形式の設定ファイルを使用し、JDBC経由でデータベースメタデータを取得し、JPA1/JPA2互換エンティティ生成や出力先パッケージ指定などの設定が可能。
テーブル除外ルールの設定
特定のテーブルをエンティティクラス生成から除外するための部分一致ルールを設定できる。例として「_tmp」で終わるテーブルを除外する設定が示されている。
影響分析・編集コメントを表示
影響分析
この公開は、企業が内部で培った開発プラクティスとツールをOSSとして還元する好事例であり、特にJava/JVM系の開発現場におけるデータベース駆動開発の効率化に貢献する。スマートニュースのエンジニアリング文化の成熟度を示すとともに、同様の課題に直面する他組織の参考となる。
編集コメント
企業内で実戦投入され成熟したツールのOSS公開は、開発プラクティスの共有として価値が高く、特にデータベース中心開発を行う組織にとって参考になる実践例。
package com.example.repository;
// -------------------------------------------------------------------
@Repository
public interface PostMetaRepository extends CrudRepository<PostMeta, Long> {
}
// -------------------------------------------------------------------
@Repository
public interface PostRepository extends CrudRepository<Post, Long> {
}
// -------------------------------------------------------------------
@Configuration
@PropertySource("application.properties")
@EnableAutoConfiguration
@EntityScan("com.example.entity")
@EnableJpaRepositories("com.example.repository")
public class TestConfig {
}
// -------------------------------------------------------------------
@SpringBootTest
@RunWith(SpringRunner.class)
@ContextConfiguration(classes = TestConfig.class)
public class RepositoriesTest {
@Autowired
private PostRepository postRepository;
@Autowired
private PostMetaRepository postMetaRepository;
@Test
public void testPosts() {
Post p = new Post();
// 値の設定部分は省略
postRepository.save(p);
PostMeta pm = new PostMeta();
pm.setPostId(p.getId());
// 値の設定部分は省略
postMetaRepository.save(pm);
Optional<Post> foundPost = postRepository.findById(p.getId());
assertThat(foundPost.isPresent(), is(true));
foundPost.ifPresent(post -> {
assertThat(post.getPostMetaList().size(), is(greaterThan(0)));
});
long count = postRepository.count();
assertThat(count, is(greaterThan(0L)));
}
}
以上、簡単ですがjpa-entity-generator(JPAエンティティジェネレータ)のご紹介でした。
この種のツールを扱ったことがある方には、よくあるカスタマイズ要求に対応しており、ある程度便利だと感じていただけたのではないでしょうか。
もし「気に入った」「実際にプロジェクトで使い始めた」という方は、ぜひGitHub上でStarを付けていただければ幸いです。GitHubリポジトリはこちらです。
一方、現状は今後の拡張性を考慮しつつも、主に自社のユースケースに基づいて開発してきたため、他の環境では不足している機能もあるかもしれません。
オープンソース化しましたので、対応が望ましいユースケースがあれば、ぜひお気軽にご連絡ください。プルリクエストによる貢献もお待ちしています。なお、ライブラリのバージョンは、この記事公開時点では0.99.xですが、早期に1.0.0をリリースする予定です。
Javaサーバーサイドエンジニアを募集しています
このように、実際の業務から生まれた成果をコミュニティに還元することに興味のあるエンジニアの方、ぜひ一緒に働きませんか?
Javaでのサーバーサイド開発に携わりたい方には、特に以下のポジションが適しています。ぜひご覧ください。
Software Engineer, Ads Service Backend - 当社の広告サーバーのメイン担当
Software Engineer, Service Backend - 当社のニュース配信サービス群のメイン担当
その他にも、多くのポジションで新しいメンバーを募集しています。
正式な応募前に、カジュアル面談や社食でのランチなどを希望される方は、私または他のスタッフまでお気軽にお問い合わせください。
原文を表示
スマートニュース株式会社の瀬良(@seratch) と申します。
普段は VP of Engineering として、横断的な取り組みやエンジニアリング組織の課題解決、プロジェクトのスケジュール管理などに注力していますが、元々はサーバサイドエンジニアで、今もコードレビューなどで開発に関わっています。
スマートニュースは、創業以来、サーバサイドにおいては Java や JVM 系の技術を多く使っている企業ですが、Java に関連した自社で開発したコードを OSS として公開することはあまり行なっていませんでした。
最近、groupId com.smartnews
公開したライブラリは「jpa-entity-generator」というものです。その名の通り JPA の仕様に準拠したエンティティクラスを自動生成するライブラリです。Gradle、Maven のプラグインとして公開しており、この二つの build tool を使っているプロジェクトであればすぐに使うことができます。
https://github.com/smartnews/jpa-entity-generator
この手のものに馴染みのある方であれば、どのようなものかおわかりかとは思いますが、すでに存在するデータベースのテーブル群に対してテーブルの情報、カラムの情報を取得し、それに基づいてエンティティクラスのソースコードを自動生成するものです。
近年、弊社においても RDBMS 以外のデータストアを使うケースも多いですが、RDB も引き続き重要なデータストアの一つです。
私が入社したのは 2016 年 3 月で、気がつけば 2 年 2 ヶ月前ですが、当時、所属していたチームの中で RDB の管理において、以下のような課題がありました。
課題1: データベース変更のレビューのやり方が統一されていない
課題2: エンティティクラスが共通化されていない
課題1 については、当時、非常に少人数のチームであったため、差し当たりの支障はない状況でしたが、チーム・サービス数の拡大に伴って難しくなることは予想されました。特に過去の経緯・レビュー内容が記録として残っている状態にすることは、将来のメンテナにとって非常に重要ですので、プロセスを改善したいと考えていました。
課題2 については、もちろん意図的に共通化しないケースはありえますし、実際にそのようなケースもありました。一方で「本当は共通化されたものを使いたいが、単にコピーされている」というケースもありました。
課題1 の解決策としては、以下のやり方を導入しました。このチームでは DB マイグレーションを仕組み化していないので、そこは変えずに適用前の開発プロセスを改善しました。
データベーススキーマ管理用の GitHub レポジトリを用意(Flyway で差分の DDL を管理)
pull request で変更内容レビューを行うようチーム内の認識合わせ(PR テンプレートも用意)
その DDL が実行可能かどうかというレベルの検証の実行を CI(CircleCI)設定
これによって「レビュアーに依存せずレビューの観点が揃うようになる」「最新のスキーマの再現が楽になる」「カラムコメントをつける人が増える」などの効果がありました。
課題2 の解決策としては、上記の GitHub リポジトリに対して追加で以下のことを行うようにしました。
複数のリポジトリで管理されていたエンティティクラスのソースコードを merge して、共通リポジトリ管理に徐々に移行
データベース変更の pull request で同時にエンティティも更新するように統一
と、方針を決めるところまではよかったのですが、できれば手動で更新するのではなく、ミスが発生しないためにもツールによるコード生成に移行したいと考えていました。
しかし、それを実現するためには、生成ツールが柔軟な対応をできるようにする必要がありました。例えば、既存のエンティティクラスでは共通の interface を実装していたり、アクセサで何か必要な処理が実装されていたり、追加でメソッドや定数が定義していたり、といったことがありました。これらをスムーズに移行できるようなツールが必要で、当時私が調べた限り、既存のものでそれを満たすものは見つかりませんでした。
私自身はこのような自動生成ツールを作った経験は何度かあったので、時間を見つけて数日程度で実装し、ある程度できたタイミングからこの管理リポジトリに組み込み、徐々にチームの中で使っていくよう促しました。
次のセクションでは、具体的な利用例とともに、どのような点を工夫し、設定可能にしたかを紹介します。
基本的には GitHub リポジトリの README が最新なのでそちらを参考にしていただければと思いますが、簡単に紹介します。
build.gradle or pom.xml
src/main/resources/entityGenConfig.yml
Gradle または Maven の設定で entity generator のプラグインを有効にします。この状態で ./gradelw entityGen
buildscript { dependencies { classpath 'com.h2database:h2:1.4.197' // アクセスする DB の JDBC ドライバ classpath 'com.smartnews:jpa-entity-generator:0.99.2' } } // plugin を有効にします apply plugin: 'entitygen' entityGen { // ツール実行時に使う YAML 設定ファイル configPath = 'src/main/resources/entityGenConfig.yml' } // 生成したコードは Lombok に依存しています dependencies { providedCompile 'org.projectlombok:lombok:1.16.20' providedCompile 'org.hibernate.javax.persistence:hibernate-jpa-2.1-api:1.0.0.Final' }
src/main/resources/entityGenConfig.yml
最低限必要な情報は以下の 2 つだけです。jdbcSettings
// DB への接続情報 jdbcSettings: url: "jdbc:h2:file:./db/blog;MODE=MySQL" username: "user" password: "pass" driverClassName: "org.h2.Driver" // 生成したコードを置く package を指定 packageName: "com.example.entity"
src/main/java/com/example/entity/*
指定された package 配下に以下のようなコードが生成されます。このコードは Lombok に依存しています。
package com.example.entity; import java.sql.*; import javax.persistence.*; import lombok.Data; @Data @Entity(name = "com.example.entity.Blog") @Table(name = "blog") public class Blog { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "\"id\"") private Integer id; @Column(name = "\"name\"") private String name; @Column(name = "\"active\"") private Byte active; @Column(name = "\"created_at\"") private Timestamp createdAt; }
弊社では、私が入社してから広く Lombok が使われるようになりました。
あまり特殊な使い方はしておらず、 @Data などを使って Java beans の定義を楽にする用途がほとんどです。それよりも便利な言語機能を使いたいケースでは Kotlin や Scala など他の JVM 上で動作する言語を使う方がよいというコンセンサスが社内にあるように思います。
最近、Lombok は JDK アップデート対応に関する課題がみられるようになりました。現時点において、当社では特に問題は発生しておらず、広く使われているライブラリなので、現時点では将来を悲観する必要はあまりないと考えています。しかし、今まで以上に JDK のバージョンとの整合性についてアンテナを高くしておく必要はありそうです。
jpa-entity-generator は、現状 Lombok に依存しないコードを生成する機能を提供していませんが、オープンソースになりましたし、将来、そのようなオプションのニーズがあれば対応できるかもしれません。
GitHub リポジトリに entityGenConfig.yml のサンプルが置いてありますので、全ての設定項目については、そちらを見ていただければと思いますが、代表的なものを日本語で紹介していきます。
ちなみにフォーマットに YAML を選択した理由はそれなりに普及しており、簡潔さから見ても妥当な選択肢であろうということと、コメントが書けるという JSON に対する優位性が決め手でした。
データベースの情報を取得するために JDBC 経由で接続する必要があります。以下の通り url
driverClassName
もし指定した JDBC ドライバーのクラスが見つからないエラーが発生したら Gradle のビルド設定を見直してみてください。buildscript.dependencies
余談ですが、以下の H2 の trace 関係のパラメータは細かい情報が出力されて便利なので H2 を使ったテストでハマったりした場合は試してみてください。
--------------------------------------------------------- # * JDBC configuration to fetch metadata * jdbcSettings: url: "jdbc:h2:file:./db/blog;MODE=MySQL;TRACE_LEVEL_FILE=2;TRACE_LEVEL_SYSTEM_OUT=2" username: "user" password: "pass" driverClassName: "org.h2.Driver"
コード生成における共通基本設定
以下にある通り、コード生成の出力先や package 名などですが、それ以外に興味深いものとして jpa1SupportRequired
packageNameForJpa1
これは深遠なる事情により、一部 JPA1 互換の entity が必要だったので実装したものです。JPA1 と JPA2 のエンティティ定義は微妙に異なるので、それに対応するために追加しました。早く不要になるとよいですね。
デフォルトでは JPA2 の entity のみを生成しますので、もし JPA1 向けの実装が必要な場合は jpa1SupportRequired
--------------------------------------------------------- # * Basic/global configuration * # If you need to specify non-standard source directory, set the following setting as needed # - string value: the relative path from the project root directory. outputDirectory: "src/main/java" # The package name used when generating entity classes # - string value: full package name packageName: "com.example.entity" # If you need to set a specific strategy attribute for @GeneratedValue, specify the name here. # - string value: "TABLE", "SEQUENCE", "IDENTITY", "AUTO", or null generatedValueStrategy: "IDENTITY" # If you need to generate JPA 1 compatible entity classes as well, set the following attributes # - boolean value: true if you need to generate JPA 1 compatible entities as well jpa1SupportRequired: true # The package to put JP 1 compatible entity classes. # - string value: full package name separate from the "packageName" packageNameForJpa1: "com.example.entity.jpa1"
歴史のあるデータベースというのは色々な事情やユースケースがあります。何らかの事情からエンティティクラスを生成する必要のないテーブルについては部分一致で除外する設定を入れることができます。以下の例だと xxx_tmp
--------------------------------------------------------- # * Rules for exclusion * # Define the following rules if you'd like to exclude specific tables when generating entity classes # - array of TableExclusionRule objects # - tableName (string) / tableNames (array): string value that partially matches table names (case sensitive) tableExclusionRules: - tableNames: ["_tmp"]
テーブル名をそのままキャメルケースでクラス名にしたくないというケースはそれなりにあるかと思います。例えば abtest_config
考えられるニーズとしては Ruby on Rails のような inflector が欲しいというケースもあるかもしれません。そのようなルールを実装することも可能ですが、弊社ではニーズがなかったため、現在は未対応です。
--------------------------------------------------------- # * Rules for table/class name conversion * # If you need some rules that convert table names to entity class names, list the mapping rules as below. # - array of ClassNameRule objects # - tableName: table name (full name, case sensitive) # - className: Java class name to be used (you cannot include package namme in front of the class name) classNameRules: - tableName: "article" className: "BlogArticle" - tableName: "article_tag" className: "BlogArticleTag" - tableName: "abtest" className: "ABTest"
クラスに関する設定(アノテーション、実装するインターフェース、追加のクラスコメント)
生成したエンティティクラスに対するいくつかの設定です。まず、一つ目はクラスに追加でアノテーションを付与したい場合です。
対象のクラス名を classNames
@Something(name = "foo")
--------------------------------------------------------- # * Rules on how to attach class annotations * # - array of ClassAnnotationRule objects # - (optional) className (string) / classNames (array): target Java class names (case sensitive) # - annotations: array of Annotation objects # - className: the annotation class name # - (optional) attributes (array of AnnotationAttribute objects): attributes if exist # - name (string): the name of the attribute # - (optional) value (string): the value of the attribute # or # - code (string): writing code in a string value instead of specifying name + value # or # - code (string): write the whole code in a string value instead of specifying className + attributes classAnnotationRules: # If you just specify the annotations, all the generated classes'll have them. - annotations: - className: "lombok.ToString" # You can specify the classes to have the class annotations. - {classNames: ["ABTest",], annotations: [className: "Deprecated"]} - className: "Blog" annotations: - className: "lombok.Builder" attributes: - name: "toBuilder" value: "true"
次に、先ほど背景のところで説明した通り、エンティティに特定の interface を implement してほしいケースもあろうかと思います。その場合は FQDN でその名前を複数指定することができます。こちらも classNames
--------------------------------------------------------- # * Java interfaces to let generated classes implement * # - array of InterfaceRule objects # - (optional) className (string) / classNames (array): target Java class names (case sensitive) # - interfaces: array of Interface objects # - name: FQDN of the interface # - (optional) genericsClassNames: array of string values if the interface has generics interfaceRules: # If you just specify the interfaces, all the generated classes'll implement the interfaces. - interfaces: [{name: "java.io.Serializable"}] - classNames: ["ABTest"] interfaces: [{name: "com.example.util.ExpirationPredicate"}]
最後にクラスコメントのカスタマイズです。jpa-entity-generator は table に設定されているコメントがあれば、それを読み取り自動的にクラスコメントとして書き出しますが、それに加えて何かコメントを加えたい場合は以下のようにルールを設定します。こちらも classNames
--------------------------------------------------------- # * Rules on how to append class comments * # - array of ClassAdditionalCommentRule objects # - (optional) className (string) / classNames (array): target Java class names (case sensitive) # - comment (string): comment value to be appended to the class definition classAdditionalCommentRules: # If you just specify the classNames, all the generated classes'll have them. - comment: "Note: auto-generated by jpa-entity-generator" - classNames: [ "ABTest", ] comment: "TODO: This A/B testing mechanism is no longer used"
フィールドに関する設定(型、アノテーション、デフォルト値、追加のフィールドコメント)
フィールドについての設定項目は、型、アノテーション、デフォルト値、コメントの 4 つです。
まず、型ですが、例えばよくあるケースとしては tinyint
以下のコメントにあるように fieldName
--------------------------------------------------------- # * Rules to convert types of the fields in generated classes * # - array of FieldTypeRule objects # - (optional) className (string) / classNames (array): target Java class names (case sensitive) # - fieldName (string) / fieldNames (array): the field name to convert its type # - typeName (string): the type name to be converted fieldTypeRules: - {classNames: ["ABTest"], fieldName: "config", typeName: "String"} # If you don't specify classNames in a rule, all the generated classes will be affected. - { fieldName: "active", typeName: "boolean"}
フィールドに追加でアノテーションを付与したい場合は以下のようにします。クラスの方と基本的には同様です。
--------------------------------------------------------- # * Rules on how to attach field annotations * # - array of FieldAnnotationRule objects # - (optional) className (string) / classNames (array): target Java class names (case sensitive) # - fieldName (string) / fieldNames (array): the field name to attach annotations # - annotations: array of Annotation objects # - className: the annotation class name # - (optional) attributes (array of AnnotationAttribute objects): attributes if exist # - name (string): the name of the attribute # - (optional) value (string): the value of the attribute # or # - code (string): writing code in a string value instead of specifying name + value # or # - code (string): write the whole code in a string value instead of specifying className + attributes fieldAnnotationRules: - className: "BlogArticle" fieldNames: ["tags"] annotations: [{className: "Deprecated"}] - classNames: ["ABTest"] fieldNames: ["config"] annotations: [{ className: "com.example.annotation.Experimental", attributes: [{name: "comment", value: '"The expected data format is JSON"'}], # code: '@com.example.annotation.Experimental(comment = "The expected data format is JSON")', }]
フィールドにデフォルト値を設定したい場合は、以下の通り設定します。何も指定しない場合、各フィールドのデフォルト値は明には指定されません。Lombok の @Builder を使うときは追加で @Builder.Default アノテーションを指定する必要があります(Lombok が警告してくれます)。
--------------------------------------------------------- # * Rules on how to set default values to the fields * # - array of FieldDefaultValueRule objects # - (optional) className (string) / classNames (array): target Java class names (case sensitive) # - fieldName (string) / fieldNames (array): the field name to attach annotations # - defaultValue (string): the default value part in source code (specify '"something"' if you have a string value) fieldDefaultValueRules: # If you don't specify classNames in a rule, all the generated classes will be affected. - { fieldNames: ["name"], defaultValue: '"Anonymous"'} - {classNames: ["ABTest"], fieldNames: ["active"], defaultValue: "0"}
最後はクラスの時と同様ですが、テーブルコメントを反映してくれたクラスと同様、フィールドにもカラムのコメントは自動で挿入されます。それに加えて、何らかの追加コメントをつけたい場合は以下のように指定します。className
--------------------------------------------------------- # * Rules on how to append field comments * # - array of FieldAdditionalCommentRule objects # - (optional) className (string) / classNames (array): target Java class names (case sensitive) # - fieldName (string) / fieldNames (array): the field name to attach annotations # - comment (string): comment value to be appended to the field definition fieldAdditionalCommentRules: - {className: "ABTest", fieldName: "active", comment: "true if the AB test is still active."} - {className: "BlogArticleTag", fieldNames: ["articleId", "tagId"], comment: "The field is non-null value"}
最後は力技な感じですが、とにかくコードを付け加えたい時に使います。例えば、@OneToMany、@ManyToOne などのリレーション定義、追加でメソッドを定義したいとき、アクセサや既存のメソッドを override したいときなどがあります。
デフォルトではクラスのソースコードの末尾に追記する挙動ですが、クラスの先頭に追記したい場合は position: "Top"
また、JPA1 互換のコードは JPA2 のものとは異なることも多いので、jpa1Code
--------------------------------------------------------- # * Rules to append additional code to generated classes * # - array of AdditionalCodeRule objects # - (optional) className (string) / classNames (array): target Java class names (case sensitive) # - code (string): writing code in a string value # - (optional) position (string): "Top" or "Bottom" (default: "Bottom") # - (optional) jpa1Code (string): writing code in a string value if you need to overwrite code only for JPA 1 compatible entities additionalCodeRules: - classNames: ["BlogArticle", "BlogArticleTag"] position: "Top" code: | public Integer getId() { return this.id; } - className: "BlogArticle" # position: "Bottom" code: | @ManyToOne @JoinColumn(name = "blog_id", insertable = false, updatable = false) private Blog blog; jpa1Code: | @lombok.Setter(lombok.AccessLevel.NONE) @ManyToOne @JoinColumn(name = "\"blog_id\"", referencedColumnName = "\"id\"", insertable = false, updatable = false) private Blog blog;
利用例 - WordPress のデータベースで試してみる
参考までに実際のデータベースを例に実行してみましょう。ここでは皆さんご存知の WordPress のテーブル定義を使って実行してみました。
すべての動作するコードはこちらで公開していますので、興味のある方は動かしてみてください。
src/main/resources/entityGenConfig.yml
最低限の設定としては jdbcSettings
--------------------------------------------------------- # * JDBC configuration to fetch metadata * jdbcSettings: url: "jdbc:mysql://127.0.0.1:3306/wordpress?useSSL=false&useInformationSchema=true" username: "wordpress" password: "wordpress" driverClassName: "com.mysql.jdbc.Driver" # --------------------------------------------------------- # * Basic/global configuration * # The package name used when generating entity classes # - string value: full package name packageName: "com.example.entity" # --------------------------------------------------------- # * Rules for table/class name conversion * # If you need some rules that convert table names to entity class names, list the mapping rules as below. # - array of ClassNameRule objects # - tableName: table name (full name, case sensitive) # - className: Java class name to be used (you cannot include package namme in front of the class name) classNameRules: - tableName: "wp_commentmeta" className: "CommentMeta" - tableName: "wp_comments" className: "Comment" - tableName: "wp_ec3_schedule" className: "Ec3Schedule" - tableName: "wp_links" className: "Link" - tableName: "wp_options" className: "Option" - tableName: "wp_postmeta" className: "PostMeta" - tableName: "wp_posts" className: "Post" - tableName: "wp_terms" className: "Term" - tableName: "wp_term_relationships" className: "TermRelationship" - tableName: "wp_term_taxonomy" className: "TermTaxonomy" - tableName: "wp_usermeta" className: "UserMeta" - tableName: "wp_users" className: "User" # --------------------------------------------------------- # * Rules to append additional code to generated classes * # - array of AdditionalCodeRule objects # - (optional) className (string) / classNames (array): target Java class names (case sensitive) # - code (string): writing code in a string value # - (optional) position (string): "Top" or "Bottom" (default: "Bottom") # - (optional) jpa1Code (string): writing code in a string value if you need to overwrite code only for JPA 1 compatible entities additionalCodeRules: - className: "Post" code: | @OneToMany(fetch = FetchType.EAGER, mappedBy = "post", cascade = CascadeType.ALL) private java.util.List<PostMeta> postMetaList; - className: "PostMeta" code: | @ManyToOne @JoinColumn(name = "\"post_id\"", insertable = false, updatable = false) private Post post;
src/main/java/com/example/entity/Post.java
./gradlew entityGen
package com.example.entity; import java.sql.*; import javax.persistence.*; import lombok.Data; @Data @Entity(name = "com.example.entity.Post") @Table(name = "wp_posts") public class Post { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "\"ID\"") private Long id; @Column(name = "\"post_author\"") private Long postAuthor; @Column(name = "\"post_date\"") private Timestamp postDate; @Column(name = "\"post_date_gmt\"") private Timestamp postDateGmt; @Column(name = "\"post_content\"") private String postContent; @Column(name = "\"post_title\"") private String postTitle; @Column(name = "\"post_excerpt\"") private String postExcerpt; @Column(name = "\"post_status\"") private String postStatus; @Column(name = "\"comment_status\"") private String commentStatus; @Column(name = "\"ping_status\"") private String pingStatus; @Column(name = "\"post_password\"") private String postPassword; @Column(name = "\"post_name\"") private String postName; @Column(name = "\"to_ping\"") private String toPing; @Column(name = "\"pinged\"") private String pinged; @Column(name = "\"post_modified\"") private Timestamp postModified; @Column(name = "\"post_modified_gmt\"") private Timestamp postModifiedGmt; @Column(name = "\"post_content_filtered\"") private String postContentFiltered; @Column(name = "\"post_parent\"") private Long postParent; @Column(name = "\"guid\"") private String guid; @Column(name = "\"menu_order\"") private Integer menuOrder; @Column(name = "\"post_type\"") private String postType; @Column(name = "\"post_mime_type\"") private String postMimeType; @Column(name = "\"comment_count\"") private Long commentCount; @OneToMany(fetch = FetchType.EAGER, mappedBy = "post", cascade = CascadeType.ALL) private java.util.List<PostMeta> postMetaList; }
全ての生成されたファイルは以下の通りです。
$ ls src/main/java/com/example/entity/ Comment.java Ec3Schedule.java Option.java PostMeta.java TermRelationship.java User.java CommentMeta.java Link.java Post.java Term.java TermTaxonomy.java UserMeta.java
Spring Data JPA コード例
このエンティティが本当に動作するのか、試してみました。以下のようなビルド設定で
dependencies { compile 'mysql:mysql-connector-java:5.1.46' compile 'org.hibernate.javax.persistence:hibernate-jpa-2.1-api:1.0.0.Final' providedCompile 'org.projectlombok:lombok:1.16.20' testCompile 'org.springframework.data:spring-data-jpa:2.0.6.RELEASE' testCompile 'org.springframework.boot:spring-boot-starter-data-jpa:2.0.1.RELEASE' testCompile 'org.springframework.boot:spring-boot-starter-test:2.0.1.RELEASE' }
以下のテストが動作します。正しく使える JPA エンティティクラスになっているようです。
package com.example.repository; // ------------------------------------------------------------------- @Repository public interface PostMetaRepository extends CrudRepository<PostMeta, Long> { } // ------------------------------------------------------------------- @Repository public interface PostRepository extends CrudRepository<Post, Long> { } // ------------------------------------------------------------------- @Configuration @PropertySource("application.properties") @EnableAutoConfiguration @EntityScan("com.example.entity") @EnableJpaRepositories("com.example.repository") public class TestConfig { } // ------------------------------------------------------------------- @SpringBootTest @RunWith(SpringRunner.class) @ContextConfiguration(classes = TestConfig.class) public class RepositoriesTest { @Autowired private PostRepository postRepository; @Autowired private PostMetaRepository postMetaRepository; @Test public void testPosts() { Post p = new Post(); // 値の設定部分は省略 postRepository.save(p); PostMeta pm = new PostMeta(); pm.setPostId(p.getId()); // 値の設定部分は省略 postMetaRepository.save(pm); Optional<Post> foundPost = postRepository.findById(p.getId()); assertThat(foundPost.isPresent(), is(true)); foundPost.ifPresent(post -> { assertThat(post.getPostMetaList().size(), is(greaterThan(0))); }); long count = postRepository.count(); assertThat(count, is(greaterThan(0L))); } }
以上、簡単ですが jpa-entity-generator のご紹介でした。
この手のことをやろうとしたことがある方には、よくある必要なカスタマイズに対応していて、一定便利だと感じていただけたのではないでしょうか。
もし、「気に入った」「実際にプロジェクトで使い始めた」という方は、ぜひ GitHub 上で Star ボタンを押していただければ嬉しく思います。GitHub リポジトリはこちらです。
一方で、現状は今後の拡張性は考慮しつつも、弊社でのユースケースに絞って開発してきたものなので、他の環境では足りない機能もあると思います。
オープンソースになりましたので、対応できるとよさそうなユースケースがあれば、ぜひお気軽にご連絡ください。pull request による貢献もお待ちしています。なお、ライブラリのバージョンについては、この記事公開時点では 0.99.x となっていますが、早々に 1.0.0 をリリースする予定です。
Java サーバサイドエンジニアを募集しております
このように実際の仕事の中で生まれた成果をコミュニティに貢献することに興味のあるエンジニアの方、ぜひ一緒に働きませんか?
Java でサーバサイド開発をしたい方は特に以下のポジションが最適です。ぜひアクセスしてみてください。
Software Engineer, Ads Service Backend - 弊社の広告サーバのメイン担当
Software Engineer, Service Backend - 弊社の News 配信サービス群のメイン担当
それ以外にも多くのポジションで新しいメンバーを募集しております。
正式応募の前にカジュアル面談、社食ランチなどご希望の方は、私・他の当社スタッフまでお気軽にご連絡ください。
関連記事
今日のまとめ
AI日報で今日の重要ニュースをまとめ読み