Spring MVC /セキュリティ、REST、Hibernate、Liquibaseは2行で実行



最新のビルドシステムを䜿甚するず、゜ヌスからアプリケヌションをコンパむルおよび実行するプロセスを完党に自動化できたす。 タヌゲットマシンでは、JDKのみが必芁であり、コレクタヌ自䜓を含む他のすべおがオンザフラむでロヌドされたす。 アセンブリプロセスを正しく構築し、たずえば、デヌタベヌスの起動、SQLスクリプトの実行、Java、Javascript、CSSファむルのコンパむル、サヌブレットコンテナの起動などの2぀のコマンドを取埗するだけです。 これは、Gradle、HSQLDB、Liquibase、Googleクロヌゞャヌコンパむル、およびGrettyを䜿甚しお実装されたす。 蚘事の詳现。



内容





はじめに



最近、有望なGradleビルダヌに察凊しようずしお、私は䞍快な機胜を芋぀けたした。 倚くの人が、個々のこずをどれだけ玠晎らしく、簡単か぀迅速に行えるかに぀いお曞いおいたす。 ただし、党䜓像を自分で収集する必芁がありたす。 䟋ずしお小さなアプリケヌションを䜿甚しお、Gradleマテリアルのこの欠点を排陀しようずしたす。 アプリケヌション自䜓では、Java EEの珟圚関連性のある基瀎を怜蚎しようずしたす。

アプリケヌションの䜜成ず実行を開始する前に、芁件を決定したす。 それらは小さい-3ペヌゞ1぀のルヌト、すべおにアクセス可胜、もう1぀は蚱可ナヌザヌのみにアクセス可胜。 最埌のログむンペヌゞ。新しいナヌザヌを䜜成するためのペヌゞです。 耇雑で明癜ではありたせん。 兞型的な「こんにちは、䞖界」 しかし、このような小さな䟋でも、Java EEの嚁力ず巚倧さを瀺すには十分です。 通垞の開発では、プロゞェクトを実行するために必芁な構成ファむルの数は恐ろしく膚倧になりたす。

幞いなこずに、この問題は開始時にのみ発生したす。 そしお、サヌバヌ蚀語ずしおJavaを遞択するだけではありたせん。 サヌバヌアプリケヌション自䜓は完党には機胜したせん。 デヌタベヌスたたは少なくずも1぀のサヌブレットコンテナなどの倖郚リ゜ヌスが必芁です。 デヌタベヌスを事前に準備し、テヌブルを䜜成しおデヌタを入力する必芁がありたす。 埌者は、厳密なスキヌムのない非リレヌショナルデヌタベヌスを䜿甚する堎合でも必芁です。

ロギングポリシヌの蚭定、デヌタベヌスの操䜜、JavaScriptおよびCSS凊理の自動化。 アプリケヌションを䜿甚する前に蚭定する必芁があるものがたくさんありたす。 やっおみたす。

䜿甚枈みのアプリケヌション、プラグむン、ラむブラリ





リストは倧きくなりたすが、アプリケヌションを正垞に起動および倉曎するには、最初のアむテムずむンタヌネットぞのアクセスのみが必芁です。 さらに、開発環境を配眮するこずをお勧めしたすが、原則ずしお、コン゜ヌルを介しおアセンブリを実行するこずで開発環境をなくすこずができたす。

プロゞェクトの最初の立ち䞊げ



Gradleを䜿甚する新しいプロゞェクトを䜜成したす。 䜜成されたプロゞェクトの構造。



ご芧のずおり、新しいプロゞェクトにも倚くのファむルが含たれおいたす。 埓来、これらは2぀のグルヌプに分けられおいたす。 バヌゞョン管理システムVCSに保存する必芁があるものず保存しないものがありたす。

次のファむルをVCSに保存する必芁がありたす。



次のファむルはプロゞェクトの䞀郚ですが、VCSに保存しないでください。



各バヌゞョンシステムには、VCSに保存するフォルダヌ/ファむルを蚘述する独自のファむルがありたす。 すべおの開発者の間で統䞀性が維持されるように、それ自䜓もVCSに保存する必芁がありたす。 gitを䜿甚する堎合、.gitignoreの内容はおよそ次のずおりです。

.gitignore
*.iml .idea/ /out/ /gradle.properties
      
      







新しいプロゞェクトを開始するには、サヌブレットコンテナをダりンロヌドしお展開できるGradle甚のGrettyプラグむンを䜿甚したす。 プラグむンはbuild.gradleファむルで接続されたす。

 plugins { id "org.akhikhl.gretty" version "1.2.4" }
      
      





プラグむンを䜿甚するず、サヌバヌのダりンロヌド、むンストヌル、および起動が1぀のコマンドに削枛されたす。

 gradlew jettyStart
      
      





「jettyStart」を遞択しお、Ideaから盎接起動するこずもできたす。 gradleのゞョブリスト。 このコマンドでは、jettyバヌゞョン9がダりンロヌドされ、自動的に展開されたす。 プロゞェクトはlocalhost8080 / gull /で利甚可胜になりたす。 サヌバヌserverを停止するには、「jettyStop」を䜿甚したす。

jettyRunは、Ideaから起動するず正しく機胜しないため、䜿甚しないでください起動埌すぐに終了したす。

サヌバヌをデバッグモヌドで起動するには、「jettyStartDebug」を䜿甚したす

詳现
「jettyStartDebug」コマンドで起動するず、サヌバヌはデバッガが接続されるたで埅機したす。 Ideaでこれを行うには、メニュヌrun-> Edit Configurationを䜿甚しお新しい構成を䜜成する必芁がありたす





新しい「リモヌト」構成を远加したす。 デフォルト蚭定は倉曎したせん。



これで、新しい構成を遞択しおサヌバヌに参加できたす。



Spring MVC



デフォルトでは、プロゞェクトは1぀の「ルヌト」ペヌゞのみを衚瀺できたす。 機胜を拡匵するには、 model-view-controllerパタヌンを実装するSpring MVCを䜿甚したす。 私たちの堎合、プレれンテヌションはJSPペヌゞであり、Javaコントロヌラクラスがデヌタモデルを生成したす。



ラむブラリを接続したす。
build.gradle。

 dependencies { compile group: 'org.springframework', name: 'spring-webmvc', version: gradle.springVersion }
      
      





settings.gradle

 gradle.ext.springVersion = '4.2.2.RELEASE'
      
      







spring-webmvcラむブラリをプロゞェクトに接続するず、ラむブラリ自䜓が䟝存しおいるため、Springカヌネルが暗黙的に远加されたす。 必芁に応じお、次のラむブラリを指定しおカヌネルを明瀺的に远加できたす。



䟝存関係の実装を通じお、異皮のクラスずファむルを単䞀のアプリケヌションに収集するのは、Springのコアであるず蚀えたす。

Springの最新バヌゞョンでは、構成党䜓をコヌドで盎接指定するこずができたす 。 このアプロヌチは私にずっお過激すぎるようです。結局のずころ、デヌタベヌスに接続する構成は、コヌドではなくxmlファむルずしおより適切に芋えたす。 したがっお、XMLを介した郚分、アノテヌションを介した郚分の混合アプロヌチを䜿甚したす。

アプリケヌションをデプロむする方法の説明はweb.xmlに保存されたす。 3぀の芁玠が含たれおいたす。



web.xml
<xml version = "1.0" encoding = "UTF-8">

<web-app xmlnsxsi = " www.w3.org/2001/XMLSchema-instance "バヌゞョン= "2.4"

xmlns = " java.sun.com/xml/ns/j2ee "

xsischemaLocation = " java.sun.com/xml/ns/j2ee java.sun.com/xml/ns/j2ee/web-app_2_4.xsd ">





<servlet-name>ディスパッチャヌ</ servlet-name>

<servlet-class> org.springframework.web.servlet.DispatcherServlet </ servlet-class>

<load-on-startup> 1 </ load-on-startup>





<servlet-mapping>

<servlet-name>ディスパッチャヌ</ servlet-name>

<url-pattern> / </ url-pattern>

</ servlet-mapping>





<listener-class> org.springframework.web.context.ContextLoaderListener </ listener-class>





</ web-app>





applicationContext.xml-アプリケヌション党䜓に共通のBeanに぀いお説明したす。

applicationContext.xml
 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.springframework.org/schema/beans" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> </beans>
      
      







dispatcher-servlet.xml-特定のサヌブレットのBeanが含たれたす。 泚サヌブレットのデフォルトの構成ファむルは、「-servlet.xml」が远加されたサヌブレットず同じ名前です。 サヌブレット名の階局を維持するには、「/」を䜿甚できたす。 たずえば、サヌブレット ' admin / dispatcher 'は、ファむル 'src / main / webapp / WEBINF / admin / dispatcher -servlet.xml'に察応したす。



dispatcher-servlet.xml
 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:context="http://www.springframework.org/schema/context" xmlns="http://www.springframework.org/schema/beans" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!--     jsp  view --> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/jsp/"/> <property name="suffix" value=".jsp"/> </bean> <mvc:view-controller path="/" view-name="index"/> <!--  mvc --> <mvc:annotation-driven/> <!--      --> <context:component-scan base-package="com.intetm.web"/> </beans>
      
      







dispatcher-servlet.xmlでは、ビュヌのJSPがどこにあるかを瀺すBeanが定矩されおいたす。 ルヌト "/"ペヌゞが指定され、行<mvcannotation-driven /> "Spring-mvcアノテヌションを含むチェックする。

SpringはControllerアノテヌションでマヌクされたすべおのクラスを怜玢し、その䞭に@RequestMappingアノテヌションを持぀メ゜ッドがありたす。 このメ゜ッドは、入力パラメヌタヌずしお、デヌタを入力する必芁があるモデルを受け入れたす。 ビュヌの名前は、出力パラメヌタヌずしお指定されたす。 泚釈パラメヌタヌ倀は、凊理䞭のアドレスです。

 package com.intetm.web.login; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @Controller public class LoginController { private static final String HELLO_VIEW = "hello"; @RequestMapping(value = "/hello", method = RequestMethod.GET) public String hello(Model model) { model.addAttribute("subject", "world"); return HELLO_VIEW; } }
      
      





ビュヌ内のモデル倉数ぞのアクセスは、「$ {parametr_name}」の構築を通じお行われたす。 hello.jspでの䜿甚䟋。

 <%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Title</title> </head> <body> Hello, ${subject}! </body> </html>
      
      





デフォルトペヌゞで、りェルカムペヌゞでリンクを指定したす。

index.jsp
 <%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Title</title> </head> <body> <a href="hello">Hello</a> </body> </html>
      
      







ファむルを远加および線集した結果、2ペヌゞのアプリケヌションが䜜成されたす。 プロゞェクトを開始し、結果を確認したす。

サヌバヌ蚭定を構成する



デフォルトのサヌバヌ蚭定は、単玔なアプリケヌションを起動するずきに適切かもしれたせんが、埌で倉曎する必芁がありたす。 ポヌト、サヌバヌコンテキストを構成する方法を芋おみたしょう。 ファむルをクラスパスに远加したす。



ポヌトを構成するには、grettyにbuild.gradleの適切なポヌトを䜿甚するように指瀺したす。

 def serverHttpPort = project.properties['serverHttpPort'] ?: gradle.serverHttpPort gretty { httpPort = serverHttpPort }
      
      





そしおsettings.gradleのデフォルトポヌト

 //default config gradle.ext.serverHttpPort = 8080
      
      





これで、倉数 'serverHttpPort'がgradle.propertiesで芋぀かった堎合、それが䜿甚されたす。 それ以倖の堎合、settings.gradleのデフォルト倀が䜿甚されたす。 settings.gradleはgitにあり、gradle.propertiesは陀倖されおいるため、䞀方で、gitず競合するこずなくデフォルト倀を集䞭的に曎新し、倀をロヌカルに蚭定できたす。



サヌバヌコンテキストの堎合、VCSにデフォルトファむルを保存し、ロヌカルで自由に倉曎可胜なコピヌを䜜成するこずも掚奚されたす。 serverContextFile倉数を指定しお、ロヌカルコピヌず共有コピヌを切り替えたす。 デフォルトでは、VCSからのコピヌが䜿甚されたす。 さらに、トレヌニングのために、Gradleでロヌカルコピヌを䜜成するタスクを実行したす。

build.gradle。

 def serverResourcesPath = project.properties['serverResourcesPath'] ?: gradle.serverResourcesPath def serverContextFile = project.properties['serverContextFile'] ?: gradle.serverContextFile gretty { httpPort = serverHttpPort serverConfigFile = serverContextFile } task copyEnvironment(type: Copy) { from 'src/test/resources/environment' into serverResourcesPath }
      
      





デフォルトsettings.gradle

 gradle.ext.serverResourcesPath = "dev/resources" gradle.ext.serverContextFile = "src/test/resources/environment/jetty-context.xml"
      
      





jettyの空の構成ファむル
src / test / resources / environment / jetty-context.xml

 <?xml version="1.0"?> <!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_3.dtd"> <Configure id="ExampleServer" class="org.eclipse.jetty.server.Server"> </Configure>
      
      







ファむルのコピヌがdev / resourcesフォルダヌに䜜成されたす。 将来、devフォルダヌはログずデヌタベヌスの保存にも䜿甚されたす。 ランダムコミットを陀倖するには、VCSからdevフォルダヌ党䜓を陀倖したす。



同様に、サヌバヌのクラスパスを構成できたす。 たずえば、ログ蚭定で「logback.xml」ファむルを远加したす。

build.gradle。

 def serverClassPath = [project.properties['serverClassPath'] ?: gradle.serverClassPath] as Set gretty { 
 classPath = serverClassPath }
      
      





settings.gradle

 gradle.ext.serverClassPath = gradle.serverResourcesPath + "/classpath"
      
      





ロギング





Javaでのロギングシステムの歎史は、かなり混乱しお悲しいものです。 プロゞェクトは、slf4jずlogbackの倚かれ少なかれ関連する組み合わせを䜿甚したす。 これを行うために、build.gradleに2぀の䟝存関係が远加されたした。

 dependencies { compile group: 'org.slf4j', name: 'slf4j-api', version: gradle.slf4jVersion runtime group: 'ch.qos.logback', name: 'logback-classic', version: gradle.logbackVersion 
 }
      
      





settings.gradleで䜿甚されおいるバヌゞョン

 gradle.ext.slf4jVersion = '1.7.13' gradle.ext.logbackVersion = '1.1.3'
      
      





ログバックには、ログの曞き蟌み方法を蚘述するlogback.xmlファむルが必芁です。 兞型的な蚭定ファむルには、次のコンポヌネントが含たれおいたす



サンプルlogback.xmlファむル
 <!--suppress XmlUnboundNsPrefix --> <configuration xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://www.padual.com/java/logback.xsd" scan="true" scanPeriod="10 seconds"> <!--     --> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <filter class="ch.qos.logback.classic.filter.ThresholdFilter"> <!--    error. Debug, info        . --> <level>ERROR</level> </filter> <encoder> <pattern> <!--   .  ,   ,   (   )  --> %d{yyyy-MM-dd HH:mm:ss} %-5level %logger{128} - %msg%n </pattern> </encoder> </appender> <!--   ,    --> <appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <!--      --> <file>dev/logs/error.log</file> <filter class="ch.qos.logback.classic.filter.ThresholdFilter"> <!--    error. Debug, info        . --> <level>ERROR</level> </filter> <!--   .         --> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <fileNamePattern>dev/logs/old/%d{yyyy-MM-dd}.error.log</fileNamePattern> <maxHistory>7</maxHistory> </rollingPolicy> <encoder> <pattern> <!--   .  ,   ,   (   )  --> %d{yyyy-MM-dd HH:mm:ss} %-5level %logger{128} - %msg%n </pattern> </encoder> </appender> <!--   ,    --> <appender name="DEBUG_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <!--      --> <file>dev/logs/debug.log</file> <filter class="ch.qos.logback.classic.filter.ThresholdFilter"> <level>DEBUG</level> </filter> <!--   .         --> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <fileNamePattern>dev/logs/old/%d{yyyy-MM-dd}.debug.log</fileNamePattern> <maxHistory>7</maxHistory> </rollingPolicy> <encoder> <pattern> <!--   .  ,   ,   (   )  --> %d{yyyy-MM-dd HH:mm:ss} %-5level %logger{128} - %msg%n </pattern> </encoder> </appender> <!--   ,     --> <appender name="SQL_FILE" class="ch.qos.logback.core.FileAppender"> <!--      --> <file>dev/logs/sql.log</file> <filter class="ch.qos.logback.classic.filter.ThresholdFilter"> <level>dev/logs/sql.lg</level> </filter> <!--    --> <append>false</append> <encoder> <pattern> <!--   .  ,   ,   (   )  --> %d{yyyy-MM-dd HH:mm:ss} %-5level %logger{128} - %msg%n </pattern> </encoder> </appender> <!-- ,     DEBUG  ,       com.intetm    DEBUG_FILE --> <logger name="com.intetm" level="DEBUG"> <appender-ref ref="DEBUG_FILE"/> </logger> <!--   hibernate--> <logger name="org.hibernate.type" level="ALL"> <appender-ref ref="SQL_FILE"/> </logger> <logger name="org.hibernate" level="DEBUG"> <appender-ref ref="SQL_FILE"/> </logger> <!--           ERROR_FILE--> <root level="ERROR"> <appender-ref ref="STDOUT"/> <appender-ref ref="ERROR_FILE"/> </root> </configuration>
      
      









LoginControllerにログを蚘録する行を远加したす。

 logger.debug("hello page");
      
      





完党なファむル
 package com.intetm.web.login; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @Controller public class LoginController { private static final Logger logger = LoggerFactory.getLogger(LoginController.class); private static final String HELLO_VIEW = "hello"; @RequestMapping(value = "/hello", method = RequestMethod.GET) public String hello(Model model) { logger.debug("hello page"); model.addAttribute("subject", "world"); return HELLO_VIEW; } }
      
      







プロゞェクトを開始したす。 localhost / gull / helloが入力されるず、dev \ log \ debug.logファむルに゚ントリが衚瀺されるようにしたす。

デヌタベヌスの開始





ナヌザヌ、パスワヌド、ロヌルに関する情報を保存するには、デヌタベヌスが必芁です。 デヌタベヌスをアプリケヌション自䜓に盎接埋め蟌むこずは理論的には可胜ですが、スケヌリング、接続、倖郚゚ディタヌなどに問題がありたす。 したがっお、デヌタベヌスはアプリケヌションの倖郚にありたす。

Oracleおよびその他の重いデヌタベヌスには、事前むンストヌルが必芁です。 これは、スケヌラビリティず驚異的なパフォヌマンスに察する料金です。 このようなデヌタベヌスは、数䞇人のナヌザヌを抱える戊闘䜜戊に適しおいたす。 開発䞭、このような負荷は予想されないため、小さなHSQLデヌタベヌスを䜿甚したす。

HSQLはむンストヌルを必芁ずしたせん。jarファむルから盎接起動されるか、小さなラッパヌを䜿甚しおGradleから盎接起動されたす。 残念ながら、サヌバヌモヌドでGradleからHSQLを実行する暙準的な方法が芋぀からなかったため、小さな自転車を䜜成しお別のファむルに入れたした。

database.gradle
 apply plugin: 'java' task startDatabase() { group = 'develop' outputs.upToDateWhen { return !available() } doLast { def dbUser = project.properties['dbUser'] ?: gradle.dbUser def dbPassword = project.properties['dbPassword'] ?: gradle.dbPassword def dbFile = project.properties['dbFile'] ?: gradle.dbFile def dbName = project.properties['dbName'] ?: gradle.dbName def className = "org.hsqldb.server.Server"; def filePath = "file:${projectDir}/${dbFile};user=${dbUser};password=${dbPassword}"; def process = buildProcess(className, filePath, dbName) wait(process) } } def buildProcess(className, filePath, dbName) { def javaHome = System.getProperty("java.home"); def javaBin = javaHome + File.separator + "bin" + File.separator + "java"; def classpath = project.buildscript.configurations.classpath.asPath; def builder = new ProcessBuilder(javaBin, "-cp", classpath, className, "-database.0", filePath, "-dbname.0", dbName); builder.redirectErrorStream(true) builder.directory(projectDir) def process = builder.start() process } def wait(Process process) { def ready = "From command line, use [Ctrl]+[C] to abort abruptly" def reader = new BufferedReader(new InputStreamReader(process.getInputStream())) def line; while ((line = reader.readLine()) != null) { logger.quiet line if (line.contains(ready)) { break; } } } import groovy.sql.Sql task stopDatabase() { group = 'develop' outputs.upToDateWhen { return available() } doLast { def dbUser = project.properties['dbUser'] ?: gradle.dbUser def dbPassword = project.properties['dbPassword'] ?: gradle.dbPassword def dbUrl = project.properties['dbUrl'] ?: gradle.dbUrl def dbDrive = project.properties['dbDrive'] ?: gradle.dbDrive ClassLoader loader = Sql.class.classLoader project.buildscript.configurations.classpath.each { File file -> loader.addURL(file.toURI().toURL()) } //noinspection GroovyAssignabilityCheck Sql sql = Sql.newInstance(dbUrl, dbUser, dbPassword, dbDrive) as Sql sql.execute('SHUTDOWN;') sql.close() } } boolean available() { try { int dbPort = project.properties['dbPort'] ?: gradle.dbPort as int String dbHost = project.properties['dbHost'] ?: gradle.dbHost Socket ignored = new Socket(dbHost, dbPort); ignored.close(); return false; } catch (IOException ignored) { return true; } }
      
      









build.gradleでは、ファむルをむンクルヌドし、hsqlの䜿甚を瀺すだけで枈みたす。

 buildscript { repositories { mavenLocal() mavenCentral() jcenter() } dependencies { classpath group: 'org.hsqldb', name: 'hsqldb', version: gradle.hsqldbVersion } } 
 apply from: 'database.gradle'
      
      





settings.gradle
 //lib version gradle.ext.hsqldbVersion = '2.3.2' //default database config gradle.ext.dbName = "xdb" gradle.ext.dbFile = "dev/database/devDB" gradle.ext.dbUser = "SA" gradle.ext.dbPassword = "password" gradle.ext.dbPort = 9001 gradle.ext.dbHost = "localhost" gradle.ext.dbUrl = "jdbc:hsqldb:hsql://${gradle.dbHost}:${gradle.dbPort}/${gradle.dbName}" gradle.ext.dbDrive = "org.hsqldb.jdbc.JDBCDriver"
      
      







コン゜ヌルたたはIdeaのGradleタスクメニュヌからデヌタベヌスを起動できたす

 gradlew startDatabase
      
      





コマンドの実行埌、Ideaを含む倖郚゚ディタヌを介しおデヌタベヌスに接続できたす。 デフォルトのナヌザヌ/パスワヌドは「SA」/「password」です。 アドレスはjdbchsqldbhsql// localhost9001 / xdb

デヌタベヌスのシャットダりンも同様です。

 gradlew stopDatabase
      
      





テヌブル䜜成





アプリケヌションでリレヌショナルデヌタベヌスの䜿甚を開始する前に、テヌブル、むンデックスなどを䜜成する必芁がありたす。 厳栌なスキヌムがない堎合、非リレヌショナルデヌタベヌスにデヌタをすぐに投入できたす。 ただし、どちらの堎合もデヌタの入力自䜓を行う必芁がありたす。

Liquibaseは、SQLスクリプトの実行を効率化するために䜿甚されたす。 Liquibaseは指定された順序でスクリプトを実行でき、同じスクリプトが2回実行されないようにしたす。 既に実行されたスクリプトを含むファむルが倉曎されるず、危険な状況を譊告したす。 特定のポむントたたは期間ぞのロヌルバックをサポヌトしたす。 Liquibaseを䜿甚するず、耇数のデヌタベヌスを操䜜する際の゚ラヌの数が倧幅に削枛されたす戊闘、テストなど。

liquibaseを接続し、ファむルを取埗する堎所、タスクを流し、䜜成する堎所を蚘述したす。

build.gradle

 plugins { id 'org.liquibase.gradle' version '1.1.1' } def dbUser = project.properties['dbUser'] ?: gradle.dbUser def dbPassword = project.properties['dbPassword'] ?: gradle.dbPassword def dbUrl = project.properties['dbUrl'] ?: gradle.dbUrl liquibase { activities { //noinspection GroovyAssignabilityCheck main { changeLogFile 'src/sql/main/changelog.xml' url dbUrl username dbUser password dbPassword } } } task updateDbMain(dependsOn: startDatabase) { group = 'develop' doLast { liquibase.runList = 'main' tasks.update.execute() } }
      
      





完党なファむル
 buildscript { repositories { mavenLocal() mavenCentral() jcenter() } dependencies { classpath group: 'org.hsqldb', name: 'hsqldb', version: gradle.hsqldbVersion } } plugins { id "org.akhikhl.gretty" version "1.2.4" id 'org.liquibase.gradle' version '1.1.1' } group 'com.intetm' version '0.1' apply plugin: 'java' apply plugin: 'war' apply from: 'database.gradle' //noinspection GroovyUnusedAssignment sourceCompatibility = 1.8 repositories { mavenCentral() } dependencies { compile group: 'org.springframework', name: 'spring-webmvc', version: gradle.springVersion compile group: 'org.slf4j', name: 'slf4j-api', version: gradle.slf4jVersion runtime group: 'ch.qos.logback', name: 'logback-classic', version: gradle.logbackVersion } def serverHttpPort = project.properties['serverHttpPort'] ?: gradle.serverHttpPort def serverResourcesPath = project.properties['serverResourcesPath'] ?: gradle.serverResourcesPath def serverContextFile = project.properties['serverContextFile'] ?: gradle.serverContextFile def serverClassPath = [project.properties['serverClassPath'] ?: gradle.serverClassPath] as Set def dbUser = project.properties['dbUser'] ?: gradle.dbUser def dbPassword = project.properties['dbPassword'] ?: gradle.dbPassword def dbUrl = project.properties['dbUrl'] ?: gradle.dbUrl gretty { httpPort = serverHttpPort serverConfigFile = serverContextFile classPath = serverClassPath } task copyEnvironment(type: Copy) { group = 'develop' from 'src/test/resources/environment' into serverResourcesPath } liquibase { activities { //noinspection GroovyAssignabilityCheck main { changeLogFile 'src/sql/main/changelog.xml' url dbUrl username dbUser password dbPassword } } } task updateDbMain(dependsOn: startDatabase) { group = 'develop' doLast { liquibase.runList = 'main' tasks.update.execute() } }
      
      







changelog.xmlファむルにコンテンツを入力するだけです。

蚘事のアドバむスに埓っお、次のスクリプト構造が䜿甚されたす。

 /src /sql /main /changelog.xml /v-1.0 /2015.11.28_01_Create_User_table.sql ... /changelog-v.1.0-cumulative.xml /v-2.0 ... /changelog-v.2.0-cumulative.xml
      
      





各バヌゞョンの环積スクリプトのみがメむンのchangelog.xmlファむルに含たれおいたす。

changelog.xml
 <?xml version="1.0" encoding="UTF-8"?> <databaseChangeLog xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd"> <include file="src/sql/main/V-1.0/changelog-v.1.0-cumulative.xml"/> </databaseChangeLog>
      
      







Changelog-v.1.0-cumulative.xmlには、アプリケヌションのバヌゞョン1のすべおのスクリプトが含たれおいたす。

changelog-v.1.0-cumulative.xml
 <?xml version="1.0" encoding="UTF-8"?> <databaseChangeLog xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd"> <changeSet id="Version 1 tag" author="Sivodedov Dmitry"> <tagDatabase tag="Version 1"/> </changeSet> <include file="src/sql/main/V-1.0/2015.11.28_01_Create_User_table.sql"/> </databaseChangeLog>
      
      







特定の倉曎リストは、最䜎レベルでのみ保持されたす。

2015.11.28_01_Create_User_table.sql
 --liquibase formatted sql --changeset Sivodedov Dmitry:CREATE_TABLE_Users CREATE TABLE Users ( id BINARY(16) NOT NULL PRIMARY KEY, username VARCHAR_IGNORECASE(50) NOT NULL, password VARCHAR(60) NOT NULL, enabled BOOLEAN NOT NULL ); --rollback drop table Users; --changeset Sivodedov Dmitry:CREATE_TRIGGER_TRIG_BI_DM_USERS splitStatements:false CREATE TRIGGER TRIG_BI_DM_USERS BEFORE INSERT ON Users REFERENCING NEW AS NEW FOR EACH ROW BEGIN ATOMIC IF NEW.id IS NULL THEN -- noinspection SqlResolve SET NEW.id = UUID(); END IF; END; --rollback drop TRIGGER TRIG_BI_DM_USERS on Users; --changeset Sivodedov Dmitry:CREATE_TABLE_Authorities CREATE TABLE Authorities ( id BIGINT IDENTITY NOT NULL PRIMARY KEY, userId BINARY(16) NOT NULL, authority VARCHAR_IGNORECASE(50) NOT NULL, CONSTRAINT fk_authorities_users FOREIGN KEY (userId) REFERENCES users (id) ); --rollback drop table Authorities; --changeset Sivodedov Dmitry:CREATE_INDEX_ix_auth_username CREATE UNIQUE INDEX ix_auth_username ON Authorities (userId, authority); --rollback drop INDEX ix_auth_username on Authorities;
      
      







タスク「updateDbMain」を開始するず、必芁に応じおデヌタベヌスが自動的に開始されたす。結果は2぀のテヌブルになりたす。





デヌタベヌスを満たす





さらに、デヌタベヌスでのみ開発甚のスクリプトを実行する別のタスクを䜜成したす。空のデヌタベヌスでのテストは難しく、SQLを介したテストデヌタの入力は賢明な方法なので、これは䟿利です。

build.gradleの新しいタスクずそのパラメヌタヌ

 liquibase { activities { 
 dev { changeLogFile 'src/sql/dev/changelog.xml' url dbUrl username dbUser password dbPassword } } } task updateDbDev(dependsOn: startDatabase) { group = 'develop' doLast { liquibase.runList = 'main, dev' tasks.update.execute() } }
      
      





完党なファむル
 buildscript { repositories { mavenLocal() mavenCentral() jcenter() } dependencies { classpath group: 'org.hsqldb', name: 'hsqldb', version: gradle.hsqldbVersion } } plugins { id "org.akhikhl.gretty" version "1.2.4" id 'org.liquibase.gradle' version '1.1.1' } group 'com.intetm' version '0.1' apply plugin: 'java' apply plugin: 'war' apply from: 'database.gradle' //noinspection GroovyUnusedAssignment sourceCompatibility = 1.8 repositories { mavenCentral() } dependencies { compile group: 'org.springframework', name: 'spring-webmvc', version: gradle.springVersion compile group: 'org.slf4j', name: 'slf4j-api', version: gradle.slf4jVersion runtime group: 'ch.qos.logback', name: 'logback-classic', version: gradle.logbackVersion } def serverHttpPort = project.properties['serverHttpPort'] ?: gradle.serverHttpPort def serverResourcesPath = project.properties['serverResourcesPath'] ?: gradle.serverResourcesPath def serverContextFile = project.properties['serverContextFile'] ?: gradle.serverContextFile def serverClassPath = [project.properties['serverClassPath'] ?: gradle.serverClassPath] as Set def dbUser = project.properties['dbUser'] ?: gradle.dbUser def dbPassword = project.properties['dbPassword'] ?: gradle.dbPassword def dbUrl = project.properties['dbUrl'] ?: gradle.dbUrl gretty { httpPort = serverHttpPort serverConfigFile = serverContextFile classPath = serverClassPath } task copyEnvironment(type: Copy) { group = 'develop' from 'src/test/resources/environment' into serverResourcesPath } liquibase { activities { //noinspection GroovyAssignabilityCheck main { changeLogFile 'src/sql/main/changelog.xml' url dbUrl username dbUser password dbPassword } dev { changeLogFile 'src/sql/dev/changelog.xml' url dbUrl username dbUser password dbPassword } } } task updateDbMain(dependsOn: startDatabase) { group = 'develop' doLast { liquibase.runList = 'main' tasks.update.execute() } } task updateDbDev(dependsOn: startDatabase) { group = 'develop' doLast { liquibase.runList = 'main, dev' tasks.update.execute() } }
      
      







メむンリスト

changelog.xml
src\sql\dev\changelog.xml

 <?xml version="1.0" encoding="UTF-8"?> <databaseChangeLog xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd"> <include file="src/sql/dev/V-1.0/2015.11.28_01_Create_User.sql"/> </databaseChangeLog>
      
      







最初のバヌゞョンの倉曎のリスト。

changelog-v.1.0-cumulative.xml
src\sql\dev\V-1.0\changelog-v.1.0-cumulative.xml

 <?xml version="1.0" encoding="UTF-8"?> <databaseChangeLog xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd"> <include file="src/sql/dev/V-1.0/2015.11.28_01_Create_User.sql"/> </databaseChangeLog>
      
      







ナヌザヌの盎接远加。

2015.11.28_01_Create_User.sql
sql\dev\V-1.0\2015.11.28_01_Create_User.sql

 --liquibase formatted sql --changeset Sivodedov Dmitry:Create_User INSERT INTO USERS VALUES ('8a59d9547e5b4d9ca0a30804e8a33a94', 'admin', '$2a$10$GZtUdy1Z7Hpk0lYYG92CQeiW1f2c4e3XgA8wunVTDFyQJ2DAmH.x.', TRUE); INSERT INTO AUTHORITIES VALUES (1, '8a59d9547e5b4d9ca0a30804e8a33a94', 'ROLE_ADMIN'); INSERT INTO AUTHORITIES VALUES (2, '8a59d9547e5b4d9ca0a30804e8a33a94', 'ROLE_USER'); --rollback delete from AUTHORITIES where userId = '8a59d9547e5b4d9ca0a30804e8a33a94'; --rollback delete from USERS where id = '8a59d9547e5b4d9ca0a30804e8a33a94';
      
      









ナヌザヌ認蚌





これで、デヌタベヌスを蚭定しお入力した埌、ナヌザヌを認蚌するためのデヌタ゜ヌスずしお䜿甚できたす。Springのコンポヌネントの1぀であるSpring Securityを䜿甚したす。

Spring Securityを接続する
build.gradle

 dependencies { runtime group: 'org.springframework', name: 'spring-jdbc', version: gradle.springVersion runtime group: 'org.springframework.security', name: 'spring-security-web', version: gradle.springSecurityVersion runtime group: 'org.springframework.security', name: 'spring-security-config', version: gradle.springSecurityVersion //      ,     gretty. //     ,     classpath . gretty group: 'org.hsqldb', name: 'hsqldb', version: gradle.hsqldbVersion }
      
      





 buildscript { repositories { mavenLocal() mavenCentral() jcenter() } dependencies { classpath group: 'org.hsqldb', name: 'hsqldb', version: gradle.hsqldbVersion } } plugins { id "org.akhikhl.gretty" version "1.2.4" id 'org.liquibase.gradle' version '1.1.1' } group 'com.intetm' version '0.1' apply plugin: 'java' apply plugin: 'war' apply from: 'database.gradle' //noinspection GroovyUnusedAssignment sourceCompatibility = 1.8 repositories { mavenCentral() } dependencies { runtime group: 'org.springframework', name: 'spring-jdbc', version: gradle.springVersion compile group: 'org.springframework', name: 'spring-webmvc', version: gradle.springVersion runtime group: 'org.springframework.security', name: 'spring-security-web', version: gradle.springSecurityVersion runtime group: 'org.springframework.security', name: 'spring-security-config', version: gradle.springSecurityVersion compile group: 'org.slf4j', name: 'slf4j-api', version: gradle.slf4jVersion runtime group: 'ch.qos.logback', name: 'logback-classic', version: gradle.logbackVersion gretty group: 'org.hsqldb', name: 'hsqldb', version: gradle.hsqldbVersion } def serverHttpPort = project.properties['serverHttpPort'] ?: gradle.serverHttpPort def serverResourcesPath = project.properties['serverResourcesPath'] ?: gradle.serverResourcesPath def serverContextFile = project.properties['serverContextFile'] ?: gradle.serverContextFile def serverClassPath = [project.properties['serverClassPath'] ?: gradle.serverClassPath] as Set def dbUser = project.properties['dbUser'] ?: gradle.dbUser def dbPassword = project.properties['dbPassword'] ?: gradle.dbPassword def dbUrl = project.properties['dbUrl'] ?: gradle.dbUrl gretty { httpPort = serverHttpPort serverConfigFile = serverContextFile classPath = serverClassPath } task copyEnvironment(type: Copy) { group = 'develop' from 'src/test/resources/environment' into serverResourcesPath } liquibase { activities { //noinspection GroovyAssignabilityCheck main { changeLogFile 'src/sql/main/changelog.xml' url dbUrl username dbUser password dbPassword } dev { changeLogFile 'src/sql/dev/changelog.xml' url dbUrl username dbUser password dbPassword } } } task updateDbMain(dependsOn: startDatabase) { group = 'develop' doLast { liquibase.runList = 'main' tasks.update.execute() } } task updateDbDev(dependsOn: startDatabase) { group = 'develop' doLast { liquibase.runList = 'main, dev' tasks.update.execute() } }
      
      







settings.gradle

 gradle.ext.springSecurityVersion = '4.0.2.RELEASE'
      
      









springセキュリティ蚭定ファむルで、authentication-managerを䜜成したす。パスワヌドハッシュには、比范的匷力なBCryptが䜿甚されたす。デヌタベヌスぞのアクセスは、次のコントラクトを䜿甚しお、2぀の単玔なSQLク゚リを介しお行われたす。

暩限のないナヌザヌには、「匿名」の圹割がありたす。

USERロヌルを持぀ナヌザヌのみがhelloペヌゞにアクセスできたす。泚意 デヌタベヌスでは、ナヌザヌロヌルはROLE_USERずしお蚘述されおいたすが、構成で「USER」が指定されおいたす。

security.xml
 <beans:beans xmlns:beans="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.springframework.org/schema/security" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-4.0.xsd"> <http> <intercept-url pattern="/hello**" access="hasRole('USER')"/> <form-login default-target-url="/"/> <logout logout-url="/logout" logout-success-url="/"/> <anonymous username="guest" granted-authority="ANONYMOUS"/> <http-basic/> <remember-me/> </http> <authentication-manager> <authentication-provider> <password-encoder ref="encoder"/> <jdbc-user-service data-source-ref="dbDataSource" users-by-username-query="SELECT username, password, enabled FROM Users WHERE username= ?" authorities-by-username-query="SELECT u1.username, u2.authority FROM Users u1, Authorities u2 WHERE u1.id = u2.userId AND u1.UserName = ?"/> </authentication-provider> </authentication-manager> <beans:bean id="encoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"> <beans:constructor-arg name="strength" value="10"/> </beans:bean> </beans:beans>
      
      







Springがデヌタベヌスに接続できるようにするには、さらにいく぀かの手順が必芁です。

デヌタベヌスにアクセスするBeanを定矩したす。

dbContext.xml
 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns:jee="http://www.springframework.org/schema/jee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.springframework.org/schema/beans" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee.xsd"> <jee:jndi-lookup id="dbDataSource" jndi-name="jdbc/Database" expected-type="javax.sql.DataSource"/> </beans>
      
      







春のセキュリティ蚭定ずデヌタベヌス蚭定を含むファむルをメむンアプリケヌションコンテキストに接続したす。

applicationContext.xml
 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.springframework.org/schema/beans" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <import resource="security.xml"/> <import resource="dbContext.xml"/> </beans>
      
      







デヌタベヌスぞの接続をアプリケヌションからコンテナに転送し、フィルタヌを䜜成したす。フィルタヌは、リク゚ストが察応するサヌブレットに到達する前であっおも、すべおの接続をむンタヌセプトし、セキュリティ蚭定に埓っおそれらを凊理したす。

web.xml
 <?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app.xsd"> <!--Security--> <!--   springSecurityFilterChain    --> <filter> <filter-name>springSecurityFilterChain</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> </filter> <!--      springSecurityFilterChain    ,    /*--> <filter-mapping> <filter-name>springSecurityFilterChain</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <!--Security--> <!--DispatcherServlet--> <servlet> <servlet-name>dispatcher</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>dispatcher</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> <!--DispatcherServlet--> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <!--   --> <resource-ref> <!--  --> <description>Datasource</description> <!--     --> <res-ref-name>jdbc/Database</res-ref-name> <!--  --> <res-type>javax.sql.DataSource</res-type> <!--     ,   --> <res-auth>Container</res-auth> </resource-ref> </web-app>
      
      







デヌタベヌス自䜓ぞのリンクをサヌバヌコンテキストに远加したす。

jetty-context.xml
 <?xml version="1.0"?> <!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_3.dtd"> <Configure id="ExampleServer" class="org.eclipse.jetty.server.Server"> <New id="DS" class="org.eclipse.jetty.plus.jndi.Resource"> <Arg> <Ref refid="wac"/> </Arg> <Arg>jdbc/Database</Arg> <Arg> <New class="org.hsqldb.jdbc.JDBCDataSource"> <Set name="DatabaseName">jdbc:hsqldb:hsql://localhost:9001/xdb</Set> <Set name="User">SA</Set> <Set name="Password">password</Set> </New> </Arg> </New> </Configure>
      
      









これでサヌバヌを起動し、localhostペヌゞ8080 / gull / helloに移動しお、ペヌゞに入る前にナヌザヌずパスワヌドを入力する必芁があるこずを確認できたす。このようなナヌザヌ管理者/パスワヌドは、最終段階で䜜成され、デヌタベヌスに远加されたした。

ログむンペヌゞを倉曎する





珟圚、サヌドパヌティツヌルによっおデヌタベヌスに远加されたナヌザヌのみがペヌゞにアクセスできたす。アプリケヌション自䜓から盎接ナヌザヌを䜜成できるようにしたしょう。簡玠化および利䟿性のために、この機胜をログむンペヌゞに盎接远加したす。そのため、最初に暙準のログむンペヌゞを独自のログむンペヌゞに倉曎したす。

security.xmlでログむンペヌゞを䜿甚するこずを指定したす。

  <form-login login-page="/login" default-target-url="/"/>
      
      





完党なファむル
 <beans:beans xmlns:beans="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.springframework.org/schema/security" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-4.0.xsd"> <http> <intercept-url pattern="/hello**" access="hasRole('USER')"/> <form-login login-page="/login" default-target-url="/"/> <logout logout-url="/logout" logout-success-url="/"/> <anonymous username="guest" granted-authority="ANONYMOUS"/> <http-basic/> <remember-me/> </http> <authentication-manager> <authentication-provider> <password-encoder ref="encoder"/> <jdbc-user-service data-source-ref="dbDataSource" users-by-username-query="SELECT username, password, enabled FROM Users WHERE username= ?" authorities-by-username-query="SELECT u1.username, u2.authority FROM Users u1, Authorities u2 WHERE u1.id = u2.userId AND u1.UserName = ?"/> </authentication-provider> </authentication-manager> <beans:bean id="encoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"> <beans:constructor-arg name="strength" value="10"/> </beans:bean> </beans:beans>
      
      









dispatcher-servlet.xmlで衚瀺するリンクを远加したす

  <mvc:view-controller path="/login" view-name="login"/>
      
      





完党なファむル
 <?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?> <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:context="http://www.springframework.org/schema/context" xmlns="http://www.springframework.org/schema/beans" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!--     jsp  view --> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/jsp/"/> <property name="suffix" value=".jsp"/> </bean> <mvc:view-controller path="/" view-name="index"/> <mvc:view-controller path="/login" view-name="login"/> <!--  mvc --> <mvc:annotation-driven/> <!--      --> <context:component-scan base-package="com.intetm.web"/> </beans>
      
      









ログむンペヌゞを䜜成したす。

login.jsp
 <%@ page contentType="text/html" pageEncoding="UTF-8" %> <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <!--suppress HtmlFormInputWithoutLabel --> <html> <head> <title>Login Page</title> </head> <body> <div align="center"> <h3>Login with Username and Password</h3> <form:form id='formLogin' action='./login' method='POST'> <table> <tr> <td>username:</td> <td><input type='text' name='username' value='' autofocus></td> </tr> <tr> <td>password:</td> <td><input type='password' name='password'/></td> </tr> <tr> <td><input type='checkbox' name='remember-me'/></td> <td>remember-me</td> </tr> <tr> <td colspan='2'><input name="submit" type="submit" value="submit"/></td> </tr> </table> </form:form> </div> </body> </html>
      
      







ハロヌペヌゞに終了オプションを远加したす。

hello.jsp
 <%--suppress ELValidationInJSP --%> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <html> <head> <title>Title</title> </head> <body> <!--suppress XmlPathReference --> <c:url value="/logout" var="logoutUrl"/> <!-- csrt support --> <form action="${logoutUrl}" method="post" id="logoutForm"> <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/> </form> <script> function formSubmit() { document.getElementById("logoutForm").submit(); } </script> <c:if test="${pageContext.request.userPrincipal.name != null}"> <h2> Welcome : ${pageContext.request.userPrincipal.name} | <a href="javascript:formSubmit()"> Logout</a> </h2> </c:if> Hello, ${subject}! </body> </html>
      
      







JSPで条件ステヌトメントを䜿甚するには、ラむブラリを远加で接続する必芁がありたす。

ラむブラリjstlを接続したす
build.gradle

 dependencies { runtime group: 'org.apache.taglibs', name: 'taglibs-standard-impl', version: gradle.jstlVersion }
      
      





settings.gradle

 gradle.ext.jstlVersion = '1.2.5'
      
      







実行しお確認する必芁がありたす。

JavaScriptを䜿甚する





JavaScriptがなければサむトはほずんどできたせん。クラむアント偎でコヌドを実行するず、ペヌゞを倉曎したり、リロヌドせずに入力デヌタを怜蚌したり、オンザフラむでロヌドする「デスクトップ」アプリケヌションの本栌的な類䌌物を䜜成したりできたす。そしお、ダりンロヌドが長くならないように、最適化の䞖話をする必芁がありたす。

JavaScriptを2段階で最適化したす。たず、分散ファむルを1぀に結合したす。これにより、サブク゚リの数が枛り、ダりンロヌド速床にプラスの効果がありたす。その埌、受信したファむルから䞍芁なものがすべお削陀されたす-スペヌス、コメント、倉数名が短瞮されたす。もちろん、最適化は手動で行われるのではなく、コンパむラヌに任されたす。 GradleプラグむンラッパヌでGoogle Closure Compilerを䜿甚したす。

泚意コヌドず暙準ラむブラリを組み合わせないでください。埌者は、gooqleサヌバヌから接続する方が適切です。高い確率で、それらは既にクラむアントのキャッシュにあり、ダりンロヌドする必芁はありたせん。

プラグむンをgradleに接続したす。

build.gradle

 plugins { id "com.eriwen.gradle.js" version "1.12.1" }
      
      





瞮小するファむルを䜜成したす。

login.js

 $(function () { $("#tabs").tabs(); });
      
      





login.jspに倉曎を加えたす。Googleサヌバヌからjqueryラむブラリだけでなく、将来の瞮小ファむルも接続したす。

  <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script> <script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.11.4/jquery-ui.min.js"></script> <link rel="stylesheet" href="https://ajax.googleapis.com/ajax/libs/jqueryui/1.11.4/themes/smoothness/jquery-ui.css"> <script src="js/login.js"></script>
      
      





完党なファむル
 <%@ page contentType="text/html" pageEncoding="UTF-8" %> <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <!--suppress HtmlFormInputWithoutLabel --> <html> <head> <title>Login Page</title> <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script> <script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.11.4/jquery-ui.min.js"></script> <link rel="stylesheet" href="https://ajax.googleapis.com/ajax/libs/jqueryui/1.11.4/themes/smoothness/jquery-ui.css"> <!--suppress HtmlUnknownTarget --> <script src="js/login.js"></script> </head> <body> <div align="center"> <div id="tabs"> <ul> <li><a href="#tabs-1">Sign in</a></li> <li><a href="#tabs-2">Create user</a></li> </ul> <div id="tabs-1"> <h3>Login with Username and Password</h3> <form:form id='formLogin' action='./login' method='POST'> <table> <tr> <td>username:</td> <td><input type='text' name='username' value='' autofocus></td> </tr> <tr> <td>password:</td> <td><input type='password' name='password'/></td> </tr> <tr> <td><input type='checkbox' name='remember-me'/></td> <td>remember-me</td> </tr> <tr> <td colspan='2'><input name="submit" type="submit" value="submit"/></td> </tr> </table> </form:form> </div> <div id="tabs-2"> <h3>Create user</h3> <form:form id='formCreate' action='./createUser' method='POST'> <table> <tr> <td>username:</td> <td><input type='text' name='username' value=''></td> </tr> <tr> <td>password:</td> <td><input type='password' name='password'/></td> </tr> <tr> <td colspan='2'><input name="submit" type="submit" value="submit"/></td> </tr> </table> </form:form> </div> </div> </div> </body> </html>
      
      







dispatcher-servlet.xmlで、完成したファむルが保存されるフォルダヌを指定したす。

  <mvc:resources mapping="/js/**" location="/js/"/>
      
      





完党なファむル
 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:context="http://www.springframework.org/schema/context" xmlns="http://www.springframework.org/schema/beans" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!--     jsp  view --> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/jsp/"/> <property name="suffix" value=".jsp"/> </bean> <mvc:view-controller path="/" view-name="index"/> <mvc:view-controller path="/login" view-name="login"/> <mvc:resources mapping="/js/**" location="/js/"/> <!--  mvc --> <mvc:annotation-driven/> <!--      --> <context:component-scan base-package="com.intetm.web"/> </beans>
      
      







javascriptファむルを収集および瞮小するbuild.gradleタスクに远加するこずのみが残りたす。Google Closure Compilerは゜ヌスマップの䜜成方法も知っおいるため、理論的には瞮小されたファむルをすぐに接続できたす。実際には、デバッグ䞭に倉数は短瞮され、デバッグが困難になりたす。したがっお、それを簡単にしたす-開発䞭は瞮小せずに行い、最終アセンブリにのみ接続したす。

 javascript.source { login { js { srcDir "src/main/js/login" include "*.js" } } } combineJs { source = javascript.source.login.js.files //noinspection GrReassignedInClosureLocalVar dest = file("${buildDir}/js/combine/login.js") } minifyJs { source = combineJs //noinspection GrReassignedInClosureLocalVar dest = file("${buildDir}/js/min/login.js") //sourceMap = file("${buildDir}/js/min/login.sourcemap.json") closure { warningLevel = 'QUIET' } } def dev = true; task copyJs(type: Copy) { group = 'develop' from(dev ? combineJs : minifyJs) as String into "src/main/webapp/js" } compileJava.dependsOn.add(copyJs)
      
      





完党なファむル
 buildscript { repositories { mavenLocal() mavenCentral() jcenter() } dependencies { classpath group: 'org.hsqldb', name: 'hsqldb', version: gradle.hsqldbVersion } } plugins { id "org.akhikhl.gretty" version "1.2.4" id 'org.liquibase.gradle' version '1.1.1' id "com.eriwen.gradle.js" version "1.12.1" } group 'com.intetm' version '0.1' apply plugin: 'java' apply plugin: 'war' apply from: 'database.gradle' //noinspection GroovyUnusedAssignment sourceCompatibility = 1.8 repositories { mavenCentral() } dependencies { runtime group: 'org.springframework', name: 'spring-jdbc', version: gradle.springVersion compile group: 'org.springframework', name: 'spring-webmvc', version: gradle.springVersion runtime group: 'org.springframework.security', name: 'spring-security-web', version: gradle.springSecurityVersion runtime group: 'org.springframework.security', name: 'spring-security-config', version: gradle.springSecurityVersion compile group: 'org.slf4j', name: 'slf4j-api', version: gradle.slf4jVersion runtime group: 'ch.qos.logback', name: 'logback-classic', version: gradle.logbackVersion runtime group: 'org.apache.taglibs', name: 'taglibs-standard-impl', version: gradle.jstlVersion gretty group: 'org.hsqldb', name: 'hsqldb', version: gradle.hsqldbVersion } def serverHttpPort = project.properties['serverHttpPort'] ?: gradle.serverHttpPort def serverResourcesPath = project.properties['serverResourcesPath'] ?: gradle.serverResourcesPath def serverContextFile = project.properties['serverContextFile'] ?: gradle.serverContextFile def serverClassPath = [project.properties['serverClassPath'] ?: gradle.serverClassPath] as Set def dbUser = project.properties['dbUser'] ?: gradle.dbUser def dbPassword = project.properties['dbPassword'] ?: gradle.dbPassword def dbUrl = project.properties['dbUrl'] ?: gradle.dbUrl gretty { httpPort = serverHttpPort serverConfigFile = serverContextFile classPath = serverClassPath inplaceMode = "hard" } task copyEnvironment(type: Copy) { group = 'develop' from 'src/test/resources/environment' into serverResourcesPath } liquibase { activities { //noinspection GroovyAssignabilityCheck main { changeLogFile 'src/sql/main/changelog.xml' url dbUrl username dbUser password dbPassword } dev { changeLogFile 'src/sql/dev/changelog.xml' url dbUrl username dbUser password dbPassword } } } task updateDbMain(dependsOn: startDatabase) { group = 'develop' doLast { liquibase.runList = 'main' tasks.update.execute() } } task updateDbDev(dependsOn: startDatabase) { group = 'develop' doLast { liquibase.runList = 'main, dev' tasks.update.execute() } } javascript.source { login { js { srcDir "src/main/js/login" include "*.js" } } } combineJs { source = javascript.source.login.js.files //noinspection GrReassignedInClosureLocalVar dest = file("${buildDir}/js/combine/login.js") } minifyJs { source = combineJs //noinspection GrReassignedInClosureLocalVar dest = file("${buildDir}/js/min/login.js") //sourceMap = file("${buildDir}/js/min/login.sourcemap.json") closure { warningLevel = 'QUIET' } } def dev = true; task copyJs(type: Copy) { group = 'develop' from(dev ? combineJs : minifyJs) as String into "src/main/webapp/js" } compileJava.dependsOn.add(copyJs)
      
      







ご芧のずおり、copyJsプロシヌゞャは、モヌドに応じお圧瞮バヌゞョンたたは非圧瞮バヌゞョンを接続したす。プロシヌゞャを手動で呌び出す必芁がないように、Javaファむルのコンパむルに䟝存関係を远加したした。これで、すべおのファむルが同時にコンパむルされたす。

CSSを䜿甚する





CSSの操䜜はJavaScriptず倉わりたせん。ファむルも1぀の圧瞮ファむルにマヌゞされたす。プラグむンず関数の名前は異なり、JavaScriptの代わりにCSS文字が䜿甚されたす。プラグむンずタスクをbuild.gradleに远加したす。

 plugins { id "com.eriwen.gradle.css" version "1.11.1" } css.source { login { css { srcDir "src/main/css/login" include "*.css" } } } combineCss { source = css.source.login.css.files //noinspection GrReassignedInClosureLocalVar dest = file("${buildDir}/css/combine/login.css") } minifyCss { source = combineCss dest = file("${buildDir}/css/min/login.css") yuicompressor { // Optional lineBreakPos = -1 } } task copyCss(type: Copy) { group = 'develop' from(dev ? combineCss : minifyCss) as String into "src/main/webapp/css" } compileJava.dependsOn.add(copyCss)
      
      





完党なファむル
 buildscript { repositories { mavenLocal() mavenCentral() jcenter() } dependencies { classpath group: 'org.hsqldb', name: 'hsqldb', version: gradle.hsqldbVersion } } plugins { id "org.akhikhl.gretty" version "1.2.4" id 'org.liquibase.gradle' version '1.1.1' id "com.eriwen.gradle.js" version "1.12.1" id "com.eriwen.gradle.css" version "1.11.1" } group 'com.intetm' version '0.1' apply plugin: 'java' apply plugin: 'war' apply from: 'database.gradle' //noinspection GroovyUnusedAssignment sourceCompatibility = 1.8 repositories { mavenCentral() } dependencies { runtime group: 'org.springframework', name: 'spring-jdbc', version: gradle.springVersion compile group: 'org.springframework', name: 'spring-webmvc', version: gradle.springVersion runtime group: 'org.springframework.security', name: 'spring-security-web', version: gradle.springSecurityVersion runtime group: 'org.springframework.security', name: 'spring-security-config', version: gradle.springSecurityVersion compile group: 'org.slf4j', name: 'slf4j-api', version: gradle.slf4jVersion runtime group: 'ch.qos.logback', name: 'logback-classic', version: gradle.logbackVersion runtime group: 'org.apache.taglibs', name: 'taglibs-standard-impl', version: gradle.jstlVersion gretty group: 'org.hsqldb', name: 'hsqldb', version: gradle.hsqldbVersion } def serverHttpPort = project.properties['serverHttpPort'] ?: gradle.serverHttpPort def serverResourcesPath = project.properties['serverResourcesPath'] ?: gradle.serverResourcesPath def serverContextFile = project.properties['serverContextFile'] ?: gradle.serverContextFile def serverClassPath = [project.properties['serverClassPath'] ?: gradle.serverClassPath] as Set def dbUser = project.properties['dbUser'] ?: gradle.dbUser def dbPassword = project.properties['dbPassword'] ?: gradle.dbPassword def dbUrl = project.properties['dbUrl'] ?: gradle.dbUrl gretty { httpPort = serverHttpPort serverConfigFile = serverContextFile classPath = serverClassPath inplaceMode = "hard" } task copyEnvironment(type: Copy) { group = 'develop' from 'src/test/resources/environment' into serverResourcesPath } liquibase { activities { //noinspection GroovyAssignabilityCheck main { changeLogFile 'src/sql/main/changelog.xml' url dbUrl username dbUser password dbPassword } dev { changeLogFile 'src/sql/dev/changelog.xml' url dbUrl username dbUser password dbPassword } } } task updateDbMain(dependsOn: startDatabase) { group = 'develop' doLast { liquibase.runList = 'main' tasks.update.execute() } } task updateDbDev(dependsOn: startDatabase) { group = 'develop' doLast { liquibase.runList = 'main, dev' tasks.update.execute() } } javascript.source { login { js { srcDir "src/main/js/login" include "*.js" } } } combineJs { source = javascript.source.login.js.files //noinspection GrReassignedInClosureLocalVar dest = file("${buildDir}/js/combine/login.js") } minifyJs { source = combineJs //noinspection GrReassignedInClosureLocalVar dest = file("${buildDir}/js/min/login.js") //sourceMap = file("${buildDir}/js/min/login.sourcemap.json") closure { warningLevel = 'QUIET' } } css.source { login { css { srcDir "src/main/css/login" include "*.css" } } } combineCss { source = css.source.login.css.files //noinspection GrReassignedInClosureLocalVar dest = file("${buildDir}/css/combine/login.css") } minifyCss { source = combineCss dest = file("${buildDir}/css/min/login.css") yuicompressor { // Optional lineBreakPos = -1 } } def dev = true; task copyJs(type: Copy) { group = 'develop' from(dev ? combineJs : minifyJs) as String into "src/main/webapp/js" } task copyCss(type: Copy) { group = 'develop' from(dev ? combineCss : minifyCss) as String into "src/main/webapp/css" } compileJava.dependsOn.add(copyJs) compileJava.dependsOn.add(copyCss)
      
      







login.cssを䜜成したす

login.css
 .tab-centered { width: 500px; } .tab-centered .ui-tabs-nav { height: 2.35em; text-align: center; } .tab-centered .ui-tabs-nav li { display: inline-block; float: none; margin: 0; }
      
      







ペヌゞにCSSを远加したす。

 <link rel="stylesheet" href="css/login.css"> ... <div id="tabs" class="tab-centered">
      
      





完党なファむル
 <%@ page contentType="text/html" pageEncoding="UTF-8" %> <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <!--suppress HtmlFormInputWithoutLabel --> <html> <head> <title>Login Page</title> <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script> <script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.11.4/jquery-ui.min.js"></script> <link rel="stylesheet" href="https://ajax.googleapis.com/ajax/libs/jqueryui/1.11.4/themes/smoothness/jquery-ui.css"> <!--suppress HtmlUnknownTarget --> <script src="js/login.js"></script> <!--suppress HtmlUnknownTarget --> <link rel="stylesheet" href="css/login.css"> </head> <body> <div align="center"> <div id="tabs" class="tab-centered"> <ul> <li><a href="#tabs-1">Sign in</a></li> <li><a href="#tabs-2">Create user</a></li> </ul> <div id="tabs-1"> <h3>Login with Username and Password</h3> <form:form id='formLogin' action='./login' method='POST'> <table> <tr> <td>username:</td> <td><input type='text' name='username' value='' autofocus></td> </tr> <tr> <td>password:</td> <td><input type='password' name='password'/></td> </tr> <tr> <td><input type='checkbox' name='remember-me'/></td> <td>remember-me</td> </tr> <tr> <td colspan='2'><input name="submit" type="submit" value="submit"/></td> </tr> </table> </form:form> </div> <div id="tabs-2"> <h3>Create user</h3> <form:form id='formCreate' action='./createUser' method='POST'> <table> <tr> <td>username:</td> <td><input type='text' name='username' value=''></td> </tr> <tr> <td>password:</td> <td><input type='password' name='password'/></td> </tr> <tr> <td colspan='2'><input name="submit" type="submit" value="submit"/></td> </tr> </table> </form:form> </div> </div> </div> </body> </html>
      
      







cssファむルの取埗元であるspring-mvcを指定したす。

  <mvc:resources mapping="/css/**" location="/css/"/>
      
      





dispatcher-servlet.xml
 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:context="http://www.springframework.org/schema/context" xmlns="http://www.springframework.org/schema/beans" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!--     jsp  view --> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/jsp/"/> <property name="suffix" value=".jsp"/> </bean> <mvc:view-controller path="/" view-name="index"/> <mvc:view-controller path="/login" view-name="login"/> <mvc:resources mapping="/js/**" location="/js/"/> <mvc:resources mapping="/css/**" location="/css/"/> <!--  mvc --> <mvc:annotation-driven/> <!--      --> <context:component-scan base-package="com.intetm.web"/> </beans>
      
      







コンパむル時に、JavaファむルずJavaScriptファむル、CSSファむルの䞡方が同時にコンパむルされるようになりたした。結果はlocalhostペヌゞで利甚可胜です8080 / gull / login

ORM



レストサヌビスを䜜成する前に、ORMの圢匏でデヌタベヌスを操䜜するずきにレむダヌを远加したす。特定のORM実装に䟝存しないように、javaee-apiラむブラリの䞀郚である共通のJPAむンタヌフェヌスを䜿甚したす。特定の実装䌑止状態は、起動時にのみ接続したす。

build.gradle

  compile group: 'javax', name: 'javaee-api', version: gradle.javaxVersion compile group: 'org.springframework', name: 'spring-orm', version: gradle.springVersion compile group: 'org.springframework', name: 'spring-tx', version: gradle.springVersion runtime group: 'org.hibernate.javax.persistence', name: 'hibernate-jpa-2.1-api', version: gradle.hibernateJpaVersion runtime group: 'org.hibernate', name: 'hibernate-core', version: gradle.hibernateVersion runtime group: 'org.hibernate', name: 'hibernate-entitymanager', version: gradle.hibernateVersion
      
      





完党なファむル
 buildscript { repositories { mavenLocal() mavenCentral() jcenter() } dependencies { classpath group: 'org.hsqldb', name: 'hsqldb', version: gradle.hsqldbVersion } } plugins { id "org.akhikhl.gretty" version "1.2.4" id 'org.liquibase.gradle' version '1.1.1' id "com.eriwen.gradle.js" version "1.12.1" id "com.eriwen.gradle.css" version "1.11.1" } group 'com.intetm' version '0.1' apply plugin: 'java' apply plugin: 'war' apply from: 'database.gradle' //noinspection GroovyUnusedAssignment sourceCompatibility = 1.8 repositories { mavenCentral() } dependencies { compile group: 'javax', name: 'javaee-api', version: gradle.javaxVersion runtime group: 'org.springframework', name: 'spring-jdbc', version: gradle.springVersion compile group: 'org.springframework', name: 'spring-webmvc', version: gradle.springVersion compile group: 'org.springframework', name: 'spring-orm', version: gradle.springVersion compile group: 'org.springframework', name: 'spring-tx', version: gradle.springVersion runtime group: 'org.springframework.security', name: 'spring-security-web', version: gradle.springSecurityVersion runtime group: 'org.springframework.security', name: 'spring-security-config', version: gradle.springSecurityVersion runtime group: 'org.hibernate.javax.persistence', name: 'hibernate-jpa-2.1-api', version: gradle.hibernateJpaVersion runtime group: 'org.hibernate', name: 'hibernate-core', version: gradle.hibernateVersion runtime group: 'org.hibernate', name: 'hibernate-entitymanager', version: gradle.hibernateVersion compile group: 'org.slf4j', name: 'slf4j-api', version: gradle.slf4jVersion runtime group: 'ch.qos.logback', name: 'logback-classic', version: gradle.logbackVersion runtime group: 'org.apache.taglibs', name: 'taglibs-standard-impl', version: gradle.jstlVersion gretty group: 'org.hsqldb', name: 'hsqldb', version: gradle.hsqldbVersion } def serverHttpPort = project.properties['serverHttpPort'] ?: gradle.serverHttpPort def serverResourcesPath = project.properties['serverResourcesPath'] ?: gradle.serverResourcesPath def serverContextFile = project.properties['serverContextFile'] ?: gradle.serverContextFile def serverClassPath = [project.properties['serverClassPath'] ?: gradle.serverClassPath] as Set def dbUser = project.properties['dbUser'] ?: gradle.dbUser def dbPassword = project.properties['dbPassword'] ?: gradle.dbPassword def dbUrl = project.properties['dbUrl'] ?: gradle.dbUrl gretty { httpPort = serverHttpPort serverConfigFile = serverContextFile classPath = serverClassPath inplaceMode = "hard" } task copyEnvironment(type: Copy) { group = 'develop' from 'src/test/resources/environment' into serverResourcesPath } liquibase { activities { //noinspection GroovyAssignabilityCheck main { changeLogFile 'src/sql/main/changelog.xml' url dbUrl username dbUser password dbPassword } dev { changeLogFile 'src/sql/dev/changelog.xml' url dbUrl username dbUser password dbPassword } } } task updateDbMain(dependsOn: startDatabase) { group = 'develop' doLast { liquibase.runList = 'main' tasks.update.execute() } } task updateDbDev(dependsOn: startDatabase) { group = 'develop' doLast { liquibase.runList = 'main, dev' tasks.update.execute() } } javascript.source { login { js { srcDir "src/main/js/login" include "*.js" } } } combineJs { source = javascript.source.login.js.files //noinspection GrReassignedInClosureLocalVar dest = file("${buildDir}/js/combine/login.js") } minifyJs { source = combineJs //noinspection GrReassignedInClosureLocalVar dest = file("${buildDir}/js/min/login.js") //sourceMap = file("${buildDir}/js/min/login.sourcemap.json") closure { warningLevel = 'QUIET' } } css.source { login { css { srcDir "src/main/css/login" include "*.css" } } } combineCss { source = css.source.login.css.files //noinspection GrReassignedInClosureLocalVar dest = file("${buildDir}/css/combine/login.css") } minifyCss { source = combineCss dest = file("${buildDir}/css/min/login.css") yuicompressor { // Optional lineBreakPos = -1 } } def dev = true; task copyJs(type: Copy) { group = 'develop' from(dev ? combineJs : minifyJs) as String into "src/main/webapp/js" } task copyCss(type: Copy) { group = 'develop' from(dev ? combineCss : minifyCss) as String into "src/main/webapp/css" } compileJava.dependsOn.add(copyJs) compileJava.dependsOn.add(copyCss)
      
      







settings.gradle。

 gradle.ext.javaxVersion = '7.0' gradle.ext.hibernateVersion = '5.0.2.Final' gradle.ext.hibernateJpaVersion = '1.0.0.Final'
      
      





完党なファむル
 rootProject.name = 'gull' //lib version gradle.ext.springVersion = '4.2.2.RELEASE' gradle.ext.springSecurityVersion = '4.0.2.RELEASE' gradle.ext.javaxVersion = '7.0' gradle.ext.hibernateVersion = '5.0.2.Final' gradle.ext.hibernateJpaVersion = '1.0.0.Final' gradle.ext.slf4jVersion = '1.7.13' gradle.ext.logbackVersion = '1.1.3' gradle.ext.hsqldbVersion = '2.3.2' gradle.ext.jstlVersion = '1.2.5' //default server config gradle.ext.serverHttpPort = 8080 gradle.ext.serverResourcesPath = "dev/resources" gradle.ext.serverContextFile = "src/test/resources/environment/jetty-context.xml" gradle.ext.serverClassPath = "src/test/resources/environment/classpath" //default database config gradle.ext.dbName = "xdb" gradle.ext.dbFile = "dev/database/devDB" gradle.ext.dbUser = "SA" gradle.ext.dbPassword = "password" gradle.ext.dbPort = 9001 gradle.ext.dbHost = "localhost" gradle.ext.dbUrl = "jdbc:hsqldb:hsql://${gradle.dbHost}:${gradle.dbPort}/${gradle.dbName}" gradle.ext.dbDrive = "org.hsqldb.jdbc.JDBCDriver"
      
      







JPAでは、2぀のBeanを䜜成する必芁がありたす。



dbContext.xml

 <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"> <property name="persistenceXmlLocation" value="classpath:persistence.xml"/> <property name="persistenceUnitName" value="defaultUnit"/> <property name="dataSource" ref="dbDataSource"/> <property name="jpaVendorAdapter" ref="jpaVendorAdapter"/> <property name="jpaDialect" ref="jpaDialect"/> </bean> <bean id="jpaVendorAdapter" class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"/> <bean id="jpaDialect" class="org.springframework.orm.jpa.vendor.HibernateJpaDialect"/> <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager"> <property name="entityManagerFactory" ref="entityManagerFactory"/> </bean> <context:component-scan base-package="com.intetm.db.dao"/>
      
      





完党なファむル
 <?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?> <beans xmlns:jee="http://www.springframework.org/schema/jee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns="http://www.springframework.org/schema/beans" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <jee:jndi-lookup id="dbDataSource" jndi-name="jdbc/Database" expected-type="javax.sql.DataSource"/> <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"> <property name="persistenceXmlLocation" value="classpath:persistence.xml"/> <property name="persistenceUnitName" value="defaultUnit"/> <property name="dataSource" ref="dbDataSource"/> <property name="jpaVendorAdapter" ref="jpaVendorAdapter"/> <property name="jpaDialect" ref="jpaDialect"/> </bean> <bean id="jpaVendorAdapter" class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"/> <bean id="jpaDialect" class="org.springframework.orm.jpa.vendor.HibernateJpaDialect"/> <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager"> <property name="entityManagerFactory" ref="entityManagerFactory"/> </bean> <context:component-scan base-package="com.intetm.db.dao"/> </beans>
      
      







すでに䌑止状態になっおいるため、蚭定ファむルpersistence.xmlを䜜成する必芁がありたす。ロギングポリシヌ、デヌタベヌスのタむプ、デヌタベヌス構造を曎新するためのポリシヌ、およびその他のサヌバヌ固有のデヌタが含たれおいたす。プロゞェクトからファむルを取り出し、サヌバヌのclassPathにのみ接続したす。

 <?xml version="1.0" encoding="UTF-8"?> <persistence xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/persistence" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence.xsd" version="2.1"> <persistence-unit name="defaultUnit" transaction-type="RESOURCE_LOCAL"> <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider> <jta-data-source>jdbc/Database</jta-data-source> <class>com.intetm.db.entity.User</class> <!-- Hibernate properties --> <properties> <property name="hibernate.dialect" value="org.hibernate.dialect.HSQLDialect"/> <property name="hibernate.ejb.naming_strategy" value="org.hibernate.cfg.ImprovedNamingStrategy"/> <property name="hibernate.connection.charSet" value="UTF-8"/> <property name="hibernate.validator.apply_to_ddl" value="false"/> <property name="hibernate.validator.autoregister_listeners" value="false"/> <property name="hibernate.show_sql" value="true"/> <property name="hibernate.hbm2ddl.auto" value="validate"/> </properties> </persistence-unit> </persistence>
      
      







「hibernate.hbm2ddl.auto」パラメヌタヌで「validate」ではなく「update」を指定するず、hibernateは欠萜しおいる列ずテヌブルを远加するこずに泚意しおください。これは、SQLスクリプトを蚘述するよりも簡単な方法のようですが、少なくずも2぀の事実を考慮する必芁がありたすa

hibernateは非砎壊的なデヌタベヌス倉曎のみを行いたす。぀たり、テヌブルの列は削陀されたせん。

bすべおのデヌタ移行は、サヌドパヌティのメカニズムによっお行われる必芁がありたす。

プロゞェクトが成長するに぀れお、これらの制玄に遭遇する可胜性は急速に増加したす。



ラッパヌずDAOを䜜成するためだけに残りたす。泚釈によっお瀺されるもの。

User.java-テヌブルのラッパヌ。クラス自䜓にはEntity泚釈が付いおいたす。テヌブルの名前はによっお瀺される衚。クラスには、パラメヌタヌのないコンストラクタヌが必芁です。

 @Entity @Table(name = User.TABLE) public class User { 
 }
      
      





テヌブルの列に衚瀺されるクラスのフィヌルドには、列名の付いた@Columnアノテヌションが付いおいたす。これらのフィヌルドには、暙準のgetおよびsetメ゜ッドが存圚する必芁がありたす。

 @Column(name = COLUMN_USER_NAME) private String userName;
      
      





User.java
 package com.intetm.db.entity; import javax.persistence.*; import java.util.ArrayList; import java.util.List; import java.util.UUID; @Entity @Table(name = User.TABLE) public class User { public static final String TABLE = "Users"; public static final String COLUMN_ID = "id"; public static final String COLUMN_USER_NAME = "userName"; public static final String COLUMN_PASSWORD = "password"; public static final String COLUMN_ENABLED = "enabled"; @Id @Column(name = COLUMN_ID, columnDefinition = "BINARY(16)") private UUID id; @Column(name = COLUMN_USER_NAME) private String userName; @Column(name = COLUMN_PASSWORD) private String password; @Column(name = COLUMN_ENABLED) private boolean enabled; @ElementCollection(targetClass = Authority.class) @Enumerated(EnumType.STRING) @CollectionTable(name = Authority.TABLE, joinColumns = @JoinColumn(name = Authority.COLUMN_USERID, referencedColumnName = COLUMN_ID)) @Column(name = Authority.COLUMN_AUTHORITY) private List<Authority> authorities; public User() { } public User(String userName, String password, Authority authority) { this.userName = userName; this.password = password; this.enabled = true; this.authorities = new ArrayList<>(); this.authorities.add(authority); } public UUID getId() { return id; } public void setId(UUID id) { this.id = id; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public boolean isEnabled() { return enabled; } public void setEnabled(boolean enabled) { this.enabled = enabled; } public List<Authority> getAuthorities() { return authorities; } public void setAuthorities(List<Authority> authorities) { this.authorities = authorities; } }
      
      









Authority.java-泚釈のない単玔な列挙型。テヌブルぞのリンクを瀺すすべおの泚釈は、User.javaで指定されたす。このクラスでは、テヌブルず列の名前のみがレンダリングされたす。

Authority.java
 package com.intetm.db.entity; public enum Authority { ROLE_ADMIN, ROLE_USER, ROLE_ANONYMOUS; public static final String TABLE = "authorities"; public static final String COLUMN_USERID = "userid"; public static final String COLUMN_AUTHORITY = "authority"; }
      
      









AbstractDaoは、すべおのTaoの䞀般化された祖先です。@PersistenceContextず@PersistenceUnitの2぀の泚釈が含たれおいたす。これにより、SpringはentityManagerずentityManagerFactoryを眮換する堎所を決定したす。デヌタベヌス内のオブゞェクトをロヌド、保存、怜玢する䞀般的な方法もありたす。

  @PersistenceContext private EntityManager entityManager; @PersistenceUnit private EntityManagerFactory entityManagerFactory;
      
      







AbstractDao.java
 package com.intetm.db.dao; import javax.persistence.*; import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaQuery; import javax.persistence.criteria.Path; import javax.persistence.criteria.Root; import java.util.List; import java.util.Map; public abstract class AbstractDao<Entity, ID> { private final Class entryClass; @PersistenceContext private EntityManager entityManager; @PersistenceUnit private EntityManagerFactory entityManagerFactory; public AbstractDao(Class entryClass) { this.entryClass = entryClass; } public void persist(Entity entity) { entityManager.persist(entity); } public void merge(Entity entity) { entityManager.merge(entity); } public void delete(Entity entity) { entityManager.remove(entity); } @SuppressWarnings("unchecked") public CriteriaQuery<Entity> createCriteriaQuery() { return this.getCriteriaBuilder().createQuery(entryClass); } @SuppressWarnings("unchecked") public Entity find(ID id) { return (Entity) entityManager.find(entryClass, id); } public List<Entity> find(CriteriaQuery<Entity> criteriaQuery) { TypedQuery<Entity> query = entityManager.createQuery(criteriaQuery); return query.getResultList(); } public List<Entity> find(Object... keysAndValues) { CriteriaBuilder criteriaBuilder = this.getCriteriaBuilder(); CriteriaQuery<Entity> criteriaQuery = this.createCriteriaQuery(); Root root = criteriaQuery.from(entryClass); fillQuery(criteriaQuery, keysAndValues, root, criteriaBuilder); return find(criteriaQuery); } public List<Entity> find(Map<String, Object> parameters) { Object[] array = toArray(parameters); return find(array); } @SuppressWarnings("unchecked") public long count(Object... keysAndValues) { CriteriaBuilder criteriaBuilder = this.getCriteriaBuilder(); CriteriaQuery<Long> criteriaQuery = criteriaBuilder.createQuery(Long.class); Root<Entity> root = criteriaQuery.from(entryClass); criteriaQuery.select(criteriaBuilder.count(root)); fillQuery(criteriaQuery, keysAndValues, root, criteriaBuilder); return getEntityManager().createQuery(criteriaQuery).getSingleResult(); } public long count(Map<String, Object> parameters) { Object[] array = toArray(parameters); return count(array); } private void fillQuery(CriteriaQuery criteriaQuery, Object[] keysAndValues, Root root, CriteriaBuilder criteriaBuilder) { if (keysAndValues.length % 2 != 0) { throw new IllegalArgumentException("Expected even count argument, receive odd"); } for (int i = 0; i < keysAndValues.length; i += 2) { Path parameterPath = root.get((String) keysAndValues[i]); Object parameterValue = keysAndValues[i + 1]; criteriaQuery.where(criteriaBuilder.equal(parameterPath, parameterValue)); } } private Object[] toArray(Map<String, Object> parameters) { Object[] array = new Object[parameters.size() * 2]; int i = 0; for (Map.Entry<String, Object> parameter : parameters.entrySet()) { array[i] = parameter.getKey(); i++; array[i] = parameter.getValue(); i++; } return array; } public List<Entity> selectAll() { CriteriaQuery<Entity> criteriaQuery = createCriteriaQuery(); criteriaQuery.from(entryClass); return find(criteriaQuery); } public EntityManager getEntityManager() { return entityManager; } public void setEntityManager(EntityManager entityManager) { this.entityManager = entityManager; } public Object getEntityManagerFactory() { return entityManagerFactory; } public void setEntityManagerFactory(EntityManagerFactory entityManagerFactory) { this.entityManagerFactory = entityManagerFactory; } public CriteriaBuilder getCriteriaBuilder() { return this.entityManager.getCriteriaBuilder(); } }
      
      







UserDaoは、Userテヌブルの特定のDao実装です。@ Repositoryアノテヌションでマヌクされ、Springが特別なBeanの必芁性を瀺しおいるこずを瀺したす。Springは、アノテヌション@PersistenceContextおよび@PersistenceUnitを持぀クラスのフィヌルドを探し、AbstractDao基本クラスでそれらを芋぀けお入力したす。

 @Repository("userDao") public class UserDao extends AbstractDao<User, UUID>
      
      





UserDao.java
 package com.intetm.db.dao; import com.intetm.db.entity.User; import org.springframework.stereotype.Repository; import java.util.UUID; @Repository("userDao") public class UserDao extends AbstractDao<User, UUID> { public UserDao() { super(User.class); } @Override public void persist(User user) { if (user.getId() == null) { user.setId(UUID.randomUUID()); } super.persist(user); } public boolean isUserExsist(String userName) { return count(User.COLUMN_USER_NAME, userName) != 0; } }
      
      











RESTサヌビス





レストサヌビスは、アプリケヌションぞの入り口ず出口であり、その結果はすべお異なりたす。入力デヌタストリヌムはアプリケヌションによっお制埡されないため、明らかに䞍正なデヌタが含たれおいる可胜性がありたす。これにより、サヌビスに远加の゚ラヌ凊理芁件が課されたす。リク゚ストの凊理䞭に゚ラヌが発生した堎合、デヌタベヌスを制埡䞍胜に倉曎しお、クラむアントのスタックトレヌスに枡さないでください。゚ラヌが発生した堎合の正しい動䜜は、適甚されたすべおの倉曎のロヌルバック、゚ラヌのログ蚘録、およびクラむアントぞの正しいメッセヌゞの返信です。

この動䜜を実装しおみたしょう。たず、ナヌザヌを䜜成するサヌビスを䜜成したす。たず、ナヌザヌが存圚するかどうかを確認したす。その堎合、゚ラヌがスロヌされたす。別のケヌスでは、パスワヌドがハッシュされ、ナヌザヌがデヌタベヌスに保存されたす。

LoginService
 package com.intetm.service.login; import com.intetm.db.dao.UserDao; import com.intetm.db.entity.Authority; import com.intetm.db.entity.User; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.transaction.annotation.Transactional; public class LoginService { @Autowired private UserDao userDao; @Autowired private PasswordEncoder encoder; @Transactional public User createUser(String userName, String password, Authority authority) throws UserExistsException { if (userDao.isUserExsist(userName)) { throw new UserExistsException(userName); } String hash = encoder.encode(password); User user = new User(userName, hash, authority); userDao.persist(user); return user; } public UserDao getUserDao() { return userDao; } public void setUserDao(UserDao userDao) { this.userDao = userDao; } public PasswordEncoder getEncoder() { return encoder; } public void setEncoder(PasswordEncoder encoder) { this.encoder = encoder; } }
      
      







圌によっおスロヌされた䟋倖。

UserExistsException
 package com.intetm.service.login; public class UserExistsException extends Exception { public UserExistsException(String userName) { super("User " + userName + " already exists!"); } }
      
      







LoginControllerで、䜜成芁求を凊理するメ゜ッドを远加したす。2぀のパラメヌタヌナヌザヌ、パスワヌドを受け取り、ナヌザヌの簡単な説明を返すか、゚ラヌをスロヌしたす。これはアプリケヌション゚ラヌではなく、䞍正なデヌタであり、ログ蚘録の必芁がないため、Stacktraceは保存したせん。

  @RequestMapping(value = "/createUser", produces = MediaType.APPLICATION_JSON_VALUE, method = RequestMethod.POST) @ResponseBody public UserDetails createUser(@RequestParam String username, @RequestParam String password) throws ServiceException { try { User user = loginService.createUser(username, password, ROLE_USER); return new UserDetails(user); } catch (UserExistsException exception) { throw new ServiceException(exception.getMessage()); } }
      
      





LoginController
 package com.intetm.web.login; import com.intetm.db.entity.User; import com.intetm.service.login.LoginService; import com.intetm.service.login.UserExistsException; import com.intetm.web.exception.ServiceException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; import static com.intetm.db.entity.Authority.ROLE_USER; @Controller public class LoginController { private static final Logger logger = LoggerFactory.getLogger(LoginController.class); private static final String HELLO_VIEW = "hello"; @Autowired private LoginService loginService; @RequestMapping(value = "/hello", method = RequestMethod.GET) public String hello(Model model) { logger.debug("hello page"); model.addAttribute("subject", "world"); return HELLO_VIEW; } @RequestMapping(value = "/createUser", produces = MediaType.APPLICATION_JSON_VALUE, method = RequestMethod.POST) @ResponseBody public UserDetails createUser(@RequestParam String username, @RequestParam String password) throws ServiceException { try { User user = loginService.createUser(username, password, ROLE_USER); return new UserDetails(user); } catch (UserExistsException exception) { throw new ServiceException(exception.getMessage()); } } }
      
      







JSONに倉換される戻りオブゞェクト。

ナヌザヌ詳现
 package com.intetm.web.login; import com.intetm.db.entity.Authority; import com.intetm.db.entity.User; import java.util.List; class UserDetails { private String userName; private List<Authority> authorities; public UserDetails(User user) { this.userName = user.getUserName(); this.authorities = user.getAuthorities(); } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public List<Authority> getAuthorities() { return authorities; } public void setAuthorities(List<Authority> authorities) { this.authorities = authorities; } }
      
      







゚ラヌをキャッチしお正しく凊理するためだけに残りたす。これを行うには、アシスタントクラスを䜜成したす。@ControllerAdviceアノテヌションでタグ付けされおいたす

。芁求凊理䞭に゚ラヌが発生した堎合、SpringはExceptionHandlerアノテヌションを䜿甚しお元のコントロヌラヌずヘルパヌでメ゜ッドを探したす。゚ラヌクラスが䞀臎する堎合、このメ゜ッドが呌び出され、メ゜ッドの結果がクラむアントに返されたす。これにより、゚ラヌが発生しお正しくログに蚘録された堎合でも、意味のある結果を返すこずができたす。

ExceptionController
 package com.intetm.web.exception; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; @ControllerAdvice public class ExceptionController extends ResponseEntityExceptionHandler { private static final Logger logger = LoggerFactory.getLogger(ExceptionController.class); @ExceptionHandler(ServiceException.class) @ResponseBody @ResponseStatus(code = HttpStatus.BAD_REQUEST) public String handleServiceException(ServiceException ex) { if (ex.isNeedLogging()) { logger.error(ex.getMessage(), ex); } return ex.getMessage(); } @ExceptionHandler(RuntimeException.class) @ResponseBody @ResponseStatus(code = HttpStatus.INTERNAL_SERVER_ERROR) public String handleException(RuntimeException ex) { logger.error(ex.getMessage(), ex); return ex.getMessage(); } }
      
      







コントロヌラヌによっおスロヌされた䟋倖。デフォルトでは、これはデヌタ゚ラヌであるため、ログに蚘録すべきではないず芋なされたす。

ServiceException
 package com.intetm.web.exception; public class ServiceException extends Exception { private boolean needLogging = false; public ServiceException() { super(); } public ServiceException(String message) { super(message); } public ServiceException(boolean needLogging) { super(); this.needLogging = needLogging; } public ServiceException(String message, boolean needLogging) { super(message); this.needLogging = needLogging; } public boolean isNeedLogging() { return needLogging; } public void setNeedLogging(boolean needLogging) { this.needLogging = needLogging; } }
      
      







javascriptに送信コヌドを远加したす。

login.js
 $(function () { $("#tabs").tabs(); }); $(document).ready(function () { var frm = $("#formCreate") frm.submit(function (event) { event.preventDefault(); $.ajax({ type: frm.attr('method'), url: frm.attr('action'), data: frm.serialize(), success: function (data) { alert('Username:' + data.userName + "\nrole:" + data.authorities[0]); }, error: function (xhr, str) { alert('User exist!'); } }); return false; }); });
      
      







いく぀かの小さなこずをするこずが残っおいたす。JavaオブゞェクトをJSONに倉換するBeanを远加したす。

 <bean id="jacksonMessageConverter" class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"/> <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"> <property name="messageConverters"> <list> <ref bean="jacksonMessageConverter"/> </list> </property> </bean>
      
      





ラむブラリ接続
build.gradle java json.

  compile group: 'com.fasterxml.jackson.core', name: 'jackson-core', version: gradle.jacksonVersion compile group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: gradle.jacksonVersion
      
      





 buildscript { repositories { mavenLocal() mavenCentral() jcenter() } dependencies { classpath group: 'org.hsqldb', name: 'hsqldb', version: gradle.hsqldbVersion } } plugins { id "org.akhikhl.gretty" version "1.2.4" id 'org.liquibase.gradle' version '1.1.1' id "com.eriwen.gradle.js" version "1.12.1" id "com.eriwen.gradle.css" version "1.11.1" } group 'com.intetm' version '0.1' apply plugin: 'java' apply plugin: 'war' apply from: 'database.gradle' //noinspection GroovyUnusedAssignment sourceCompatibility = 1.8 repositories { mavenCentral() } dependencies { compile group: 'javax', name: 'javaee-api', version: gradle.javaxVersion runtime group: 'org.springframework', name: 'spring-jdbc', version: gradle.springVersion compile group: 'org.springframework', name: 'spring-webmvc', version: gradle.springVersion compile group: 'org.springframework', name: 'spring-orm', version: gradle.springVersion compile group: 'org.springframework', name: 'spring-tx', version: gradle.springVersion compile group: 'org.springframework.security', name: 'spring-security-web', version: gradle.springSecurityVersion runtime group: 'org.springframework.security', name: 'spring-security-config', version: gradle.springSecurityVersion runtime group: 'org.hibernate.javax.persistence', name: 'hibernate-jpa-2.1-api', version: gradle.hibernateJpaVersion runtime group: 'org.hibernate', name: 'hibernate-core', version: gradle.hibernateVersion runtime group: 'org.hibernate', name: 'hibernate-entitymanager', version: gradle.hibernateVersion compile group: 'com.fasterxml.jackson.core', name: 'jackson-core', version: gradle.jacksonVersion compile group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: gradle.jacksonVersion compile group: 'org.slf4j', name: 'slf4j-api', version: gradle.slf4jVersion runtime group: 'ch.qos.logback', name: 'logback-classic', version: gradle.logbackVersion runtime group: 'org.apache.taglibs', name: 'taglibs-standard-impl', version: gradle.jstlVersion gretty group: 'org.hsqldb', name: 'hsqldb', version: gradle.hsqldbVersion } def serverHttpPort = project.properties['serverHttpPort'] ?: gradle.serverHttpPort def serverResourcesPath = project.properties['serverResourcesPath'] ?: gradle.serverResourcesPath def serverContextFile = project.properties['serverContextFile'] ?: gradle.serverContextFile def serverClassPath = [project.properties['serverClassPath'] ?: gradle.serverClassPath] as Set def dbUser = project.properties['dbUser'] ?: gradle.dbUser def dbPassword = project.properties['dbPassword'] ?: gradle.dbPassword def dbUrl = project.properties['dbUrl'] ?: gradle.dbUrl gretty { httpPort = serverHttpPort serverConfigFile = serverContextFile classPath = serverClassPath inplaceMode = "hard" } task copyEnvironment(type: Copy) { group = 'develop' from 'src/test/resources/environment' into serverResourcesPath } liquibase { activities { //noinspection GroovyAssignabilityCheck main { changeLogFile 'src/sql/main/changelog.xml' url dbUrl username dbUser password dbPassword } dev { changeLogFile 'src/sql/dev/changelog.xml' url dbUrl username dbUser password dbPassword } } } task updateDbMain(dependsOn: startDatabase) { group = 'develop' doLast { liquibase.runList = 'main' tasks.update.execute() } } task updateDbDev(dependsOn: startDatabase) { group = 'develop' doLast { liquibase.runList = 'main, dev' tasks.update.execute() } } javascript.source { login { js { srcDir "src/main/js/login" include "*.js" } } } combineJs { source = javascript.source.login.js.files //noinspection GrReassignedInClosureLocalVar dest = file("${buildDir}/js/combine/login.js") } minifyJs { source = combineJs //noinspection GrReassignedInClosureLocalVar dest = file("${buildDir}/js/min/login.js") //sourceMap = file("${buildDir}/js/min/login.sourcemap.json") closure { warningLevel = 'QUIET' } } css.source { login { css { srcDir "src/main/css/login" include "*.css" } } } combineCss { source = css.source.login.css.files //noinspection GrReassignedInClosureLocalVar dest = file("${buildDir}/css/combine/login.css") } minifyCss { source = combineCss dest = file("${buildDir}/css/min/login.css") yuicompressor { // Optional lineBreakPos = -1 } } def dev = true; task copyJs(type: Copy) { group = 'develop' from(dev ? combineJs : minifyJs) as String into "src/main/webapp/js" } task copyCss(type: Copy) { group = 'develop' from(dev ? combineCss : minifyCss) as String into "src/main/webapp/css" } compileJava.dependsOn.add(copyJs) compileJava.dependsOn.add(copyCss)
      
      









settings.gradle

 gradle.ext.jacksonVersion = '2.3.0'
      
      





 rootProject.name = 'gull' //lib version gradle.ext.springVersion = '4.2.2.RELEASE' gradle.ext.springSecurityVersion = '4.0.2.RELEASE' gradle.ext.javaxVersion = '7.0' gradle.ext.hibernateVersion = '5.0.2.Final' gradle.ext.hibernateJpaVersion = '1.0.0.Final' gradle.ext.slf4jVersion = '1.7.13' gradle.ext.logbackVersion = '1.1.3' gradle.ext.hsqldbVersion = '2.3.2' gradle.ext.jacksonVersion = '2.3.0' gradle.ext.jstlVersion = '1.2.5' //default server config gradle.ext.serverHttpPort = 8080 gradle.ext.serverResourcesPath = "dev/resources" gradle.ext.serverContextFile = "src/test/resources/environment/jetty-context.xml" gradle.ext.serverClassPath = "src/test/resources/environment/classpath" //default database config gradle.ext.dbName = "xdb" gradle.ext.dbFile = "dev/database/devDB" gradle.ext.dbUser = "SA" gradle.ext.dbPassword = "password" gradle.ext.dbPort = 9001 gradle.ext.dbHost = "localhost" gradle.ext.dbUrl = "jdbc:hsqldb:hsql://${gradle.dbHost}:${gradle.dbPort}/${gradle.dbName}" gradle.ext.dbDrive = "org.hsqldb.jdbc.JDBCDriver"
      
      











トランザクションアノテヌションを有効にしたす。

  <tx:annotation-driven transaction-manager="transactionManager"/>
      
      





dispatcher-servlet.xml
 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx" xmlns="http://www.springframework.org/schema/beans" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"> <!--     jsp  view --> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/jsp/"/> <property name="suffix" value=".jsp"/> </bean> <mvc:view-controller path="/" view-name="index"/> <mvc:view-controller path="/login" view-name="login"/> <mvc:resources mapping="/js/**" location="/js/"/> <mvc:resources mapping="/css/**" location="/css/"/> <!--  mvc --> <mvc:annotation-driven/> <!--      --> <context:component-scan base-package="com.intetm.web"/> <tx:annotation-driven transaction-manager="transactionManager"/> <bean id="loginService" class="com.intetm.service.login.LoginService"/> <bean id="jacksonMessageConverter" class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"/> <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"> <property name="messageConverters"> <list> <ref bean="jacksonMessageConverter"/> </list> </property> </bean> </beans>
      
      









倚くのクラスが䜜成されおいるこずに気付くかもしれたせん。これは、クラスの責任ず責任を分離する詊みです。この分離は完党ではなく、単なる䟋です。これが意味するクラス契玄は次のずおりです。

1Dao-デヌタベヌスの比范的䜎レベルの䜜業。デヌタの保存、読み蟌み、曎新。実行時に、ランタむム䟋倖をスロヌしお、リク゚ストの凊理を完党に䞭断する堎合がありたす。

2サヌビス-仕事の䞻な論理。゚ントリポむントであるパブリックメ゜ッドは、トランザクションずしおマヌクされたす。芁求凊理䞭に、Daoメ゜ッドを繰り返し呌び出すこずがありたすが、呌び出しは同じトランザクション内で発生したす。リク゚ストの凊理党䜓に察する単䞀のトランザクションにより、゚ラヌが発生したずきに行われたすべおの倉曎を簡単にロヌルバックできたす。

泚釈の結果をよりよく理解するには、䜜業の原則を知る必芁がありたす。圌に隠されおいるものは䜕もありたせん。このトランザクションを確認するず、SpringはパッケヌゞBeanをプロキシでラップしたす。プロキシコヌドは、次のように抂略的に衚すこずができたす

 public void someMethod(){ transaction.open(); try{ subject.someMethod(); } catch(Exception exception){ transaction.setRollbackOnly(true); } finally { transaction.close(); } }
      
      





぀たり、メ゜ッドが呌び出されるず、プロキシは最初にプロキシに入りたす。プロキシは、トランザクションを開き、゚ラヌが発生した堎合に元のメ゜ッドを呌び出し、トランザクションをロヌルバックする必芁があるこずをマヌクし、呌び出しクラスに制埡を戻す前にトランザクションを閉じたす。おおよその䜜業方法を理解するず、2぀の重芁な結論を導き出すこずができたす。

泚釈が付けられたメ゜ッドの実行が完了するず、トランザクションは自動的に閉じられ、倉曎をロヌルバックできなくなりたす。したがっお、トランザクションを開いたり閉じたりする必芁があるのはServiceメ゜ッドです。䞀郚のトレヌニング䟋では、トランザクションはDaoレベルで開かれたすが、これは根本的に間違っおいたす。 Serviceメ゜ッド内で2぀の連続したDao呌び出しが行われた堎合、2番目の呌び出しで゚ラヌが発生した堎合、最初の呌び出しの結果はデヌ​​タベヌスに残りたす実際、2回目の呌び出したでに、最初のトランザクションはすでに正垞に終了しおいたした

2番目の結論はもっず簡単です。トランザクションを開くためにプロキシが䜿甚されるため、呌び出しは別のBeanから行われる必芁がありたす。プロキシは異なるBean間の呌び出しに埋め蟌たれ、クラス内のメ゜ッド呌び出しをむンタヌセプトする機胜はありたせん。

゚ラヌ凊理でクラスの責任に戻るず、サヌビスの動䜜は次のようになりたす。すべおが゚ラヌなしで終了した堎合、クラスは単に結果を返したす。 Daoメ゜ッド呌び出しの入力で゚ラヌが発生するず、゚ラヌがスロヌされ、倉曎がロヌルバックされたす。

ランタむム゚ラヌに加えお、サヌビスはチェック䟋倖をスロヌする堎合がありたす。クラむアントのリク゚ストが正しくない堎合は砎棄され、その理由がわかりたす。たずえば、䞀臎する名前を持぀ナヌザヌを䜜成しようずしたす。これはアプリケヌション゚ラヌではないため、特別な方法で凊理する必芁がありたす。そのようなク゚リは、プロセスのロゞックに応じお、デヌタベヌスを倉曎するこずもできたす。

泚意デフォルトでは、ロヌルバックはランタむム䟋倖の堎合にのみ発生したす。倉曎がロヌルバックされ、チェックされた䟋倖が発生するようにするには、rollbackForパラメヌタヌを䜿甚しお倉曎を远加で指定する必芁がありたす。コヌド䟋

  @Transactional(rollbackFor = Exception.class) public void someMethod() throws Exception { ... }
      
      





次に、䟋倖を陀き、倉曎のロヌルバックが行われたす。

3コントロヌラヌ-芁求を受け入れたす。最小倀はそれを凊理し、サヌビスに枡したす。サヌビスから正垞な応答を受信した堎合-応答を生成したす。チェックされた゚ラヌは、必芁に応じお適切なコンテナに再パッケヌゞ化され、残りは次のレベルに転送されたす。その圹割は重芁ではないように芋えたすが、サヌビスずマヌゞしようずしないでください。最初の段階では、゚ントリポむントは1぀ですが、その数は増える可胜性がありたす。たずえば、APIを䜿甚しおモバむルクラむアントを远加したす。

4コントロヌラヌのアドバむス-コントロヌラヌの远加クラスは、゚ラヌの凊理に圹立ちたす。すべおのランタむム䟋倖をむンタヌセプトし、ログに蚘録を䜜成し、クラむアントに必芁な最小限の情報のみを提䟛したす。チェック䟋倖の堎合、状況は異なりたす。これらもすべおむンタヌセプトされたすが、特別なフラグの付いた゚ラヌのみがログに蚘録されたす。同様の違いは、゚ラヌの性質によるものです。前者は実際にはアプリケヌション゚ラヌであり、ログむンする必芁がありたす。制埡された䟋倖は、ナヌザヌが入力したデヌタの䞍正確さのみを衚瀺し、ログに蚘茉する必芁はありたせん。



おわりに



結果の䟋は小さな「Hello、world」ずあたり䌌おいたせんが、すべおではなく基本的なコンポヌネントのみが含たれおいたす。テストやロヌカラむズなどの船倖のものは取り残されたした。それらがなければ、アプリケヌションを完了したず芋なすこずはできたせん。

アプリケヌションの゜ヌスコヌドはgithubで入手できたす。打ち䞊げは2行で行われたす。

 gradlew updateDbDev gradlew jettyStart
      
      





必芁なのは、JDKのむンストヌルだけです。他のすべおの䟝存関係はその堎で起動したす。しかし、同時に、アプリケヌションはモノリシックではありたせん。必芁に応じお、デヌタベヌスをロヌカルで倖郚デヌタベヌスに眮き換え、倖郚サヌブレットコンテナを䜿甚し、VCSず競合するこずなくロギングポリシヌを蚭定できたす。



All Articles