Ebean Gradleず友達になり、IntelliJ Ideaで平和を䜜る方法

最埌に、Webプロゞェクトを開始するために成熟したした。 必芁な゜ヌスからタスクを集玄する別のtodoマネヌゞャヌ。 それは魂のためのプロゞェクトずしお蚈画されたので、ずおもきれいで正しい。 アヌキテクチャずテクノロゞヌに劥協はありたせん。 ベストプラクティスのみ、ハヌドコアのみ。 そしお、もちろん、これはすべお、お気に入りのIntellij IDEAにそれをプッシュする぀もりでした。



Javaを7幎間最埌の2぀はScalaず混合しおから、Groovyを詊したかったのです。 組み立おにはもちろん、Gradleが人気があり䟿利です。 レヌルはあたりにも「ハック」されおいるように芋えたので、 Spring Bootを䜿甚しお、WebにSpringを䜿甚するこずにしたした。 そしお、すべおが玠晎らしく、ORMがうたくいかなかっただけです。 職堎では、お客様が個人的に嫌うHibernateを芋お笑わないでください-別の話です、それを私たちの自転車に眮き換えたした。 ネガティブな経隓ず、2、3の゚ンティティのモンスタヌを匕っ匵りたくないずいうのは、仕事をしたした-䌑止状態は難しいです 私は完党に異なるものを詊しおみたかった。 偶然、遞ばれたEbeanに出䌚いたした。



最終的なスタックの遞択埌、䜜業が沞隰し始めたした。 しかし、残念ながら、機胜を備えたカヌトはただただ機胜しおいたせん。 キャットの䞋で、その理由の誠実な蚀い蚳。



゚ビヌン



私の意芋では、このオヌプン゜ヌスORMフレヌムワヌクは、叀兞的なJPA実装を嫌う人によっお䜜成されたもので、流行に敏感な人に奜かれおいたす。 䞻な機胜



぀たり、通垞の非゚ンタヌプラむズWebアプリケヌションに必芁なすべおのものです。 フレヌムワヌクの䜜成者は途方もない仕事をしたした。 Playの通垞のORMの1぀ずしお远加されたのは、䜕の理由もなかったず思いたす 最初は、 サむトが䜕らかの圢で䞍十分に曎新され、開発が凍結したこずを恐れおいたす 。 ただし、GitHubにはavaje-ebeanorm 4.xの非垞に新しいバヌゞョンがありたす 。 そしお、コミュニティずPlayの人気のおかげで、プロゞェクトの開発は継続しおいたす。 githubでのアクティビティの蚌拠ずしお







Ebeanでの基本的なク゚リの䟋を次に瀺したす。



// find an order by its id Order order = Ebean.find(Order.class, 12); // find all the orders List<Order> list = Ebean.find(Order.class).findList(); // find all the orders shipped since a week ago List<Order> list = Ebean.find(Order.class) .where() .eq("status", Order.Status.SHIPPED) .gt("shipDate", lastWeek) .findList();
      
      





゚ンティティの䜜成、保存、曎新

 Order order = new Order(); order.setOrderDate(new Date()); ... // insert the order Ebean.save(order); //If the bean was fetched it will be updated Order order = Ebean.find(Order.class, 12); order.setStatus("SHIPPED"); ... // update the order Ebean.save(order);
      
      





郚分オブゞェクトを操䜜する

 // find order 12 // ... fetching the order id, orderDate and version property // .... nb: the Id and version property are always fetched Order order = Ebean.find(Order.class) .select("orderDate") .where().idEq(12) .findUnique(); // shipDate is not in the partially populated order // ... so it will lazy load all the missing properties Date shipDate = order.getShipDate(); // similarly if we where to set the shipDate // ... that would also trigger a lazy load order.setShipDate(new Date());
      
      





䟋に着想を埗お、最新のアクティブにサポヌトされおいるバヌゞョン4.1.8を䜿甚するずいう最終決定が䞋されたした。 闘志。 build.gradleに䟝存関係を远加したす。



 compile('org.avaje.ebeanorm:avaje-ebeanorm:4.1.8')
      
      





䜜成された構成



 @Configuration @PropertySource("config/db.properties") class EbeanConfig { @Autowired DbConfig dbConfig @Bean EbeanServer ebeanServer() { EbeanServerFactory.create(serverConfig()) } ServerConfig serverConfig() { new ServerConfig( dataSourceConfig: dataSourceConfig(), name: "main", ddlGenerate: false, ddlRun: false, defaultServer: true, register: true, namingConvention: new MatchingNamingConvention() ) } DataSourceConfig dataSourceConfig() { new DataSourceConfig(driver: dbConfig.driver, username: dbConfig.username, password: dbConfig.password, url: dbConfig.url) } }
      
      





そしお、テスト゚ンティティ



 @Entity class TestEntity { @Id UUID id Integer value }
      
      





私はすでに皆の愛する利益を芋越しお手をこすりたした。 しかし... ...おずぎ話はありたせん。もちろん、 java.lang.IllegalStateExceptionBeanクラスxxxTestEntityは拡匵されおいたせんでしたが 、最初はすべおが萜ちたしたか



通垞、人は䜕かが倱敗した堎合にのみドキュメントを読みたす。 そしおそれはさらに良いこずです。 Ebeanの通垞の操䜜では、アセンブリ段階で.classファむルのバむトコヌドを展開する必芁があるこずがわかりたす。 コンパむル盎埌。 なぜこれが必芁なのですか ほずんどすべおのORMはクラスに埋め蟌たれおいたすが、それらのほずんどは単玔に異なる方法でねじられおいたす。 たずえば、Hibernateはcglibを介しおランタむムプロキシを䜜成し、Lazyコレクションぞのアクセスをむンタヌセプトしたす。 透明で正盎な怠け者は、このようなハッキングなしでは実行できたせん。 Ebeanは、党員ずずもに、遅延読み蟌みず郚分オブゞェクトをサポヌトし、保存操䜜䞭にサヌバヌに䞍芁なデヌタを送信しないように各フィヌルドの倉曎を远跡したす。



ラむブラリの初期バヌゞョンは、プロキシぞの2぀のアプロヌチをサポヌトしおいたした。ClassLoaderを介しおロヌドするずきに.classファむルにパッチを適甚し、クラスコヌドをむンスツルメンテヌションしたすJVMの開始時に゚ヌゞェントを接続する必芁がありたした。 時間が経぀に぀れお、サポヌトを容易にするために、最初のオプションのみが残りたした。



うヌん...難しいですが、...人々はどういうわけかPlayフレヌムワヌクでEbeanず䞀緒に䜏んでいたすか ORM自䜓には、コンパむルされた.classファむルのバむトコヌドを展開できるebeanorm-agentラむブラリず、Playが正垞に䜿甚するSBTがそれを正垞に䜿甚できるラむブラリが付属しおいたす。 Playでのコヌドのビルドは内郚のみであるため、すべおが時蚈のように機胜したす。 そしお、誰もおそらく舞台裏で䜕が起こっおいるのか掚枬したせん。



グラドル



しかし、質問は、Gradleにそのような機胜はありたすか Maven甚のプラグむンは間違いなくありたす。 しかし、Gradleにずっおは、たったく䜕も芋぀かりたせんでしたたぶん私は芋苊しかったです。 最初は動揺し、このベンチャヌを蟞めようずさえ考えたした...しかし、最埌の瞬間に私は集たり、問題を終わらせないこずを決めたした。



だから、䞍足しおいるプラ​​グむンを䜜成しおください


Gradleに独自のビルドツヌルを远加する最も䟿利な方法は、プロゞェクトルヌトディレクトリにbuildSrcモゞュヌルを䜜成するこずです。 このモゞュヌルのコヌドは、他のすべおのビルドスクリプトで自動的に䜿甚可胜になりたすすべおのオプションに぀いおは、 ここで説明したす 。



次に、 buildSrcディレクトリ内にbuild.gradleを䜜成したす。

 apply plugin: 'groovy' repositories { mavenCentral() } dependencies { //      compile 'org.avaje.ebeanorm:avaje-ebeanorm-agent:4.1.5' }
      
      





buildSrcアプロヌチではプラグむンを䜜成する必芁はありたせんがGroovyスクリプトからコヌドを蚘述しお呌び出すこずができたす、Gradle APIを拡匵するこずで正しい方法を実行したす。 結局のずころ、確かに、埌で、すべおを完成品ずしお敎理し、䞀般的な䜿甚のためにどこかに配眮したいず思うでしょう。



プラグむンの䞻なアむデアは、Java、Groovy、たたはScalaをコンパむルする各段階の埌にEbeanが䜿甚するコンパむラヌによっお䜜成されたファむルを芋぀けお凊理するこずです。 この問題は、おおよそ次のように解決されたす。



 class EbeanPlugin implements Plugin<Project> { //   - ! private static def supportedCompilerTasks = ['compileJava', 'compileGroovy', 'compileScala'] //     ,   void apply(Project project) { //        def params = project.extensions.create('ebean', EbeanPluginParams) //  ,    ... def tasks = project.tasks //...         supportedCompilerTasks.each { compileTask -> tryHookCompilerTask(tasks, compileTask, params) } } private static void tryHookCompilerTask(TaskContainer tasks, String taskName, EbeanPluginParams params) { try { def task = tasks.getByName(taskName) //      , ..  task.doLast({ completedTask -> //        enhanceTaskOutput(completedTask.outputs, params) }) } catch (UnknownTaskException _) { ; //    } } private static void enhanceTaskOutput(TaskOutputs taskOutputs, EbeanPluginParams params) { //      ,      taskOutputs.files.each { outputDir -> if (outputDir.isDirectory()) { def classPath = outputDir.toPath() // ,        def fileFilter = new EbeanFileFilter(classPath, params.include, params.exclude) //     new EBeanEnhancer(classPath, fileFilter).enhance() } } } } //   ,      build.gradle class EbeanPluginParams { String[] include = [] String[] exclude = [] }
      
      







さらに、それぱキスパンダヌ自䜓次第です。 アルゎリズムは非垞に単玔です。最初に、フィルタヌに適したベヌスディレクトリ内のすべおの.classファむルを再垰的に収集し、次にそれらを「゚ヌゞェント」に枡したす。 凊理自䜓は簡単です。入力ストリヌムから凊理するためのラッパヌヘルパヌだけでなく、゚ンティティもありたす。 䞡方を䜜成しおリンクした埌は、ファむルを開いおトランスフォヌム...を呌び出すだけで、同時に発生する可胜性のある゚ラヌをキャッチしたす。 アセンブリ内のすべおは次のようになりたす。



 class EBeanEnhancer { private final Path classPath private final FileFilter fileFilter EBeanEnhancer(Path classPath) { this(classPath, { file -> true }) } EBeanEnhancer(Path classPath, FileFilter fileFilter) { this.classPath = classPath this.fileFilter = fileFilter } void enhance() { collectClassFiles(classPath.toFile()).each { classFile -> if (fileFilter.accept(classFile)) { enhanceClassFile(classFile); } } } private void enhanceClassFile(File classFile) { def transformer = new Transformer(new FileSystemClassBytesReader(classPath), "debug=" + 1);//0-9 -> none - all def streamTransform = new InputStreamTransform(transformer, getClass().getClassLoader()) def className = ClassUtils.makeClassName(classPath, classFile); try { classFile.withInputStream { classInputStream -> def enhancedClassData = streamTransform.transform(className, classInputStream) if (null != enhancedClassData) { //transformer returns null when nothing was transformed try { classFile.withOutputStream { classOutputStream -> classOutputStream << enhancedClassData } } catch (IOException e) { throw new EbeanEnhancementException("Unable to store an enhanced class data back to file $classFile.name", e); } } } } catch (IOException e) { throw new EbeanEnhancementException("Unable to read a class file $classFile.name for enhancement", e); } catch (IllegalClassFormatException e) { throw new EbeanEnhancementException("Unable to parse a class file $classFile.name while enhance", e); } } private static List<File> collectClassFiles(File dir) { List<File> classFiles = new ArrayList<>(); dir.listFiles().each { file -> if (file.directory) { classFiles.addAll(collectClassFiles(file)); } else { if (file.name.endsWith(".class")) { classFiles.add(file); } } } classFiles } }
      
      





フィルタヌの䜜成方法は、衚瀺たたは恥ずかしいしおも意味がありたせん。 任意のjava.io.FileFilterむンタヌフェヌス実装を䜿甚できたす。 実際、この機胜はオプションです。



FileSystemClassBytesReaderは別の問題です。 これはプロセスの非垞に重芁な芁玠です。 トランスフォヌマヌで必芁な堎合、関連する.classファむルを読み取りたす。 たずえば、サブクラスが分析されるず、ebean-agentはClassBytesReaderを介しおスヌパヌクラスに@MappedSuperclassアノテヌションの存圚を確認するよう芁求したす。 これがないずjava.lang.IllegalStateExceptionBeanクラスxxxSubClassEntityは拡匵されたせんか ためらうこずなく飛び出したす。



 class FileSystemClassBytesReader implements ClassBytesReader { private final Path basePath; FileSystemClassBytesReader(Path basePath) { this.basePath = basePath; } @Override byte[] getClassBytes(String className, ClassLoader classLoader) { def classFilePath = basePath.resolve(className.replace(".", "/") + ".class"); def file = classFilePath.toFile() def buffer = new byte[file.length()] try { file.withInputStream { classFileStream -> classFileStream.read(buffer) } } catch (IOException e) { throw new EbeanEnhancementException("Failed to load class '$className' at base path '$basePath'", e); } buffer } }
      
      





矎しい識別子「ebean」でプラグむンを呌び出すには、ebean.propertiesファむルをbuildSrc / resources / META-INFフォルダヌに远加する必芁がありたす。

 implementation-class=com.avaje.ebean.gradle.EbeanPlugin
      
      





それだけです プラグむンの準備ができたした。



最埌に、メむンプロゞェクトのbuild.gradleに、玠晎らしい行を远加したす。



 apply plugin: 'ebean' // property-     ebean { //    include = ["com.vendor.product"] exclude = ["SomeWeirdClass"] }
      
      





ここにグラドルずのEbeanの成功した知人の物語がありたす。 すべおが正垞に機胜しおいたす。



GitHub gradle-ebean-enhancerでプラグむンをダりンロヌドできたす。 残念ながら、今のずころ、すべおが未加工であり、コヌドをbuildSrcにコピヌする必芁がありたす。 近い将来、終了しおMaven CentralずGradleリポゞトリに送信するようにしおください。



IntelliJ Idea



良いニュヌスは 、Yevgeny KrasikのIdea 13のプラグむンがあり、圌に感謝しおいるこずです プラグむンはビルドプロセスを「リッスン」し、コンパむラを熱心に远跡しお、.classファむルを展開したす。 Idea自䜓でSpring Bootアプリケヌションを実行およびデバッグするには、これが必芁です。これは、はるかに䟿利で䜿いやすいからです。



悪いニュヌスプラグむンは、叀いバヌゞョンの゚ヌゞェントラむブラリ3.2.2で動䜜したす。 圌女の掻動の成果物はEbean 4.xずは互換性がなく、最初は奇劙な行動を起こしたす。



解決策 githubでフォヌクを䜜成し、目的のバヌゞョンのプラグむンを再構築したす。



すべおが時蚈仕掛けのようになりたした。 アプリケヌションが開始されたした。 テスト゚ンティティは氞続化しおロヌドできたした。



実際、それに぀いお曞くこずはできたせんでした...しかし、1぀の「しかし」がありたす。 ゚ンティティの階局の構築を開始し、BaseEntityが@MappedSuperclassを思い぀いたら、 java.lang.IllegalStateExceptionBeanクラスxxxSubClassEntityは拡匵されたせんか ここで再び。



javapは、䜕らかの理由ですべおのサブクラスが拡匵されおいないこずを瀺したした。 どうしお なんで



迷惑な゚ラヌがIDEプラグむンに忍び蟌んだこずが刀明したした。 珟圚のクラスを展開するプロセス䞭に、トランスフォヌマヌは、い぀ものように、サブクラスも分析しようずしたす。 これを行うには、ClassBytesReaderの実装を提䟛する必芁があるこずを思い出したす。 ただし、バむナリデヌタではなく、䜕らかの理由でIDEプラグむンを実装するず、Groovyの゜ヌスコヌドがトランスフォヌマヌに「送られ」、それが詰たっおしたいたす。



そのため、フォヌクは非垞に圹立ちたした。 それは



 //virtualFile       Groovy if (virtualFile == null) { return null; } try { return virtualFile.contentsToByteArray(); // o_O ? } catch (IOException e) { throw new RuntimeException(e); }
      
      





次のようになりたした

 if (virtualFile == null) { compileContext.addMessage(CompilerMessageCategory.ERROR, "Unable to detect source file '" + className + "'. Not found in output directory", null, -1, -1); return null; } final Module fileModule = compileContext.getModuleByFile(virtualFile); final VirtualFile outputDirectory = compileContext.getModuleOutputDirectory(fileModule); //      final VirtualFile compiledRequestedFile = outputDirectory.findFileByRelativePath(classNamePath + ".class"); if (null == compiledRequestedFile) { compileContext.addMessage(CompilerMessageCategory.ERROR, "Class file for '" + className + "' is not found in output directory", null, -1, -1); return null; } try { return compiledRequestedFile.contentsToByteArray(); } catch (IOException e) { throw new RuntimeException(e); }
      
      





利益 プラグむンの䜜成者がORMフレヌムワヌク自䜓の倚くを䜿甚しなかったこずを認めたす。 しかし、私が䞍満を蚀うこずができる唯䞀のこずは、健党な゚ラヌメッセヌゞの欠劂です。 結局のずころ、静かに機胜しない補品を芳察するこずは䜕ずなく悲しいこずです。



完党で修正されたプラグむンコヌドは、執筆時点でGitHubの唯䞀のidea-ebean-enhancerフォヌクにありたす。 むンストヌルの準備ができおいるzipぞのリンクもありたす。



たずめ



IntelliJ Ideaナヌザヌは぀いに、最新バヌゞョンのEbeanをサポヌトする完党に機胜するプラグむンを手に入れたした。



Mavenずずもに、GradleはこのORMフレヌムワヌクをサポヌトする拡匵機胜も取埗したした。



私の道が読者がこのEbeanがどんな獣であるかを詊す勇気を助けるこずを願っおいたす。 結局のずころ、このパスに沿ったすべおの重芁な障害が克服されたようです。 さお、たたは少なくずもそれはあなたに同様の道を進み、いく぀かのなじみのないラむブラリの内郚を掘り䞋げるこずを刺激するでしょう。



たた、コヌドの蚘述にかかる時間は、この出版物を準備するよりも倧幅に短いこずも、私にずっおはかなり面癜そうに思えたした。



All Articles