{"id":29690814,"url":"https://github.com/daejoon/lets-ko","last_synced_at":"2025-07-23T06:38:02.150Z","repository":{"id":12657801,"uuid":"15329573","full_name":"daejoon/LETS-KO","owner":"daejoon","description":"SpringFramework 연구","archived":false,"fork":false,"pushed_at":"2018-01-23T00:47:43.000Z","size":15297,"stargazers_count":5,"open_issues_count":0,"forks_count":5,"subscribers_count":8,"default_branch":"master","last_synced_at":"2024-04-15T09:09:48.812Z","etag":null,"topics":["java","mvc","spring"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/daejoon.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2013-12-20T04:04:05.000Z","updated_at":"2024-04-15T09:09:48.813Z","dependencies_parsed_at":"2022-09-25T06:30:40.965Z","dependency_job_id":null,"html_url":"https://github.com/daejoon/LETS-KO","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/daejoon/LETS-KO","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/daejoon%2FLETS-KO","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/daejoon%2FLETS-KO/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/daejoon%2FLETS-KO/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/daejoon%2FLETS-KO/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/daejoon","download_url":"https://codeload.github.com/daejoon/LETS-KO/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/daejoon%2FLETS-KO/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":266631718,"owners_count":23959422,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-07-23T02:00:09.312Z","response_time":66,"last_error":null,"robots_txt_status":null,"robots_txt_updated_at":null,"robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["java","mvc","spring"],"created_at":"2025-07-23T06:38:01.009Z","updated_at":"2025-07-23T06:38:02.127Z","avatar_url":"https://github.com/daejoon.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# LET'S KO Project\n\n실제 프로젝트 시작전까지 비지니스 로직를 제외한 개발환경 구축 및 간단한 CRUD Sample을 만들어 보는 것이다.\n\n\n\n\n\n## 차례\n\n1. 개발환경 구성 및 설치\n + 개발환경 구성\n + IntelliJ IDEA 13 설치\n + Git 설치\n + Maven 설치\n + Web Application Server 설치\n + DataBase 설치\n2. 프로젝트 생성 및 설정\n + Java Project 생성\n + Run/Debug Configurations 설정\n + Git 설정\n3. Tomcat 설정\n + UTF-8 설정\n + Jndi 설정\n4. Web Application 설정\n + UTF-8 설정\n + Log4j 설정\n5. SpringFramework\n + SpringFramework 설정\n + user-config.xml, default-config.xml 설정\n6. SpringSecurity\n + SpringSecurity 설정\n7. Hibernate\n + Hibernate 설정\n8. Tiles\n + Tiles 설정\n9. Frontend\n + Web App 구성\n + Bootstrap 설정\n + requirejs 설정\n + jQuery 설정\n + jQueryUi 설정\n + jqGrid 설정\n10. Sample\n + Sample CRUD\n + @ModelAttribute, @SessionAttributs, RESTful\n11. 마치며\n + 마치며\n\n\n\n\n\n## 개발환경 구성 및 설치\n\n### 개발환경 구성\n\nIDE 개발툴은 개발자의 생산성과 편의성을 크게 증대 시킨다고 생각한다.\n\n사실 이제 IDE을 쓰지 않고 자바 개발을 한다는 생각은 하기 힘들다.\n\n자바 IDE는 크게 오픈소스인 Eclipse, NetBeans와 상용 IDE인 IntelliJ IDEA(이하 IntelliJ)가 있다.\n\n이번 프로젝트는 IntelliJ를 사용했다.\n\nIntelliJ는 상용 IDE로써 다양한 프레임워크를 신속히 지원하고 많은 부분이 Bundle로 제공하고 있어 오픈소스인\n\nEclipse보다 설정이 적다. (그렇다고 설정을 안하고 쓸수 있다는 것은 아니다.)\n\n플렛폼은 Windows, Linux, Mac을 지원한다.\n\n여기서는 Windows를 선택했다. Windows로 간 이유중 하나는 자바 버전이다.\n\n지금은 오라클에서 맥용도 지원을 하지만 예전 버전(1.6미만 버전)은 여전히 구하기 쉽지 않다.\n\n그리고 SQLServer를 사용한것도 Windows쪽을 선택하게 한 이유이다.\n\n```\n개발환경\n    ├─ IDE: IntelliJ IDEA13\n    ├─ Java: 1.7\n    ├─ 형상관리: Git, GitHub\n    ├─ 빌드: Maven\n    ├─ Web Application Server: Tomcat7.0\n    └─ DB: SQLServer 2008\n```\n\n```\nBackend 기술 스택\n    ├─ SpringFramework\n    ├─ SpringSecurity\n    ├─ Hibernate\n    └─ Tiles\n```\n\n```\nFrontend 기술 스택\n    ├─ jQuery\n    ├─ jQuery-ui\n    ├─ requirejs\n    ├─ jqgrid\n    └─ bootstrap\n```\n\n설치에 앞서서 개인적으로 프로젝트와 관련된 JDK, IDE, Maven, Web Application Server등은 아래와 같이 한곳에 모아둔다.\n```\nC:\\JavaDE\n    ├─ java\n    │   ├─ jdk1.6.0_45\n    │   ├─ jdk1.7.0_45\n    │   └─ jdk1.7.0_45(x64)\n    ├─ JetBrains\n    │   └─ IntelliJ IDEA 13.0\n    ├─ maven\n    │   └─ apache-maven-3.1.1\n    └─ tomcat\n        ├─ apache-tomcat-6.0.37\n        └─ apache-tomcat-7.0.42\n```\n\n추가 적으로 설명의 편의성을 위해서 각각의 위치를 먼저 기술한다.\n```\n1.  [PROJECT_HOME]: LETS-KO (프로젝트 홈 폴더)\n2.  [MODULE_HOME]: LETS-KO/First (모듈 홈 폴더)\n3.  [WEB_HOME]: LETS-KO/First/web (웹 소스 홈 폴더)\n4.  [WEB_CONFIG_HOME]: LETS-KO/First/web/WEB-INF/config (웹 설정 폴더)\n5.  [JAVA_SRC_HOME]: LETS-KO/First/src/main/java (자바 소스 홈 폴더)\n6.  [CONTEXT_CONFIG_HOME]: LETS-KO/First/src/main/java/resources/config (Context 설정 홈 폴더)\n8.  [TOMCAT_HOME]: ['Tomcat' 설치위치]\n9.  [MAVEN_HOME]: ['Maven' 설치위치]\n10. [JDK_HOME]: ['JDK' 설치위치]\n```\n\n### IntelliJ IDEA13 설치\n\nIntelliJ IDEA13 Windows 용을 [다운로드](http://www.jetbrains.com/idea/download/index.html)한다. IntelliJ 설정은 [기본설정](http://beyondj2ee.wordpress.com/2013/06/01/%EC%9D%B8%ED%85%94%EB%A6%ACj-%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0-part1-getting-start-intellij-%EA%B8%B0%EB%B3%B8-%EC%84%A4%EC%A0%95%ED%8E%B8/),\n[자바설정](http://beyondj2ee.wordpress.com/2013/06/15/%EC%9D%B8%ED%85%94%EB%A6%ACj-%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0-part2-getting-start-intellij-%EC%9E%90%EB%B0%94-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8%ED%8E%B8/)을 참고한다.\n\n### Git 설치\n\nGit은 리눅스 커널을 개발 관리하기 위해서 [리누스 토발즈](http://ko.wikipedia.org/wiki/%EB%A6%AC%EB%88%84%EC%8A%A4_%ED%86%A0%EB%A5%B4%EB%B0%9C%EC%8A%A4)가 만든 형상관리 툴이다.\n\n기본적으로 Linux계열에서 동작하며 Windows에서 사용하려면 [msysgit](https://code.google.com/p/msysgit/downloads/list?q=full+installer+official+git)를 사용하면 된다.\n\n현재 최선 버전은 1.8.4가 최신 버전이다. msysgit설치 후 IntelliJ와 연동 설정을 한다.\n\nIntelliJ와 연동은 [IntelliJ-Git 설정](http://beyondj2ee.wordpress.com/2013/06/28/%ec%9d%b8%ed%85%94%eb%a6%acj-%ec%8b%9c%ec%9e%91%ed%95%98%ea%b8%b0-part4-getting-start-intellij-git/)을 참고한다.\n\n참고: [Learn Git Branching](http://pcottle.github.io/learnGitBranching/)\n\n### Maven 설치\n\n의존성 관리만으로도 Maven을 설치할만한 가치는 충분이 크다. Maven 현재 최신 버전은 3.1.1 이다.\n\nMaven 3.1.1 (Binary zip)을 [다운로드](http://maven.apache.org/download.cgi) 한후, 적당한 폴더에 압축해제한다.\n\n`[IntelliJ Menu]`-\u003e`File`-\u003e`settings...`-\u003e`Maven`-\u003e`Maven Home directory [선택]` 압축해제한 곳을 선택한다.\n\n### Web Application Server\n\nWeb Application Server(이하 WAS)는 여러 밴더가 존재한다.\n\n여러가지 현실적 이유때문에 개발환경과 배포환경의 WAS가 일치하지 않는 경우가 있다.\n\n사실 WAS는 표준 스펙이 존재하기 때문에 밴더별로 스펙만 맞춰주면 동일한 결과를 보장해야 한다.\n\nTomcat 7.0 Version은 [여기](http://tomcat.apache.org/whichversion.html)를 참고한다.\n\n개발은 일반적으로 많이 사용하는 Tomcat을 이용한다. [Tomcat 7.0 다운로드](http://tomcat.apache.org/download-70.cgi)\n\n참고: [Tomcat 7.0 한글문서](http://kenu.github.io/tomcat70/docs/)\n\n### Database 설치\n\n이 프로젝트에서는 Hibernate를 사용하기 때문에 사실 특정 DataBase를 설치할 필요는 없다. 취양에 맞춰 설치하자.\n\n\n\n\n\n## 프로젝트 생성 및 설정\n\n### Java Project 생성\n\n이번 프로젝트는 새로운 프로젝트를 생성하기 보다는 GitHub에서 Clone해서 생성한다.\n\n1. 처음 IntelliJ를 실행하면은 Dashboard가 나타난다. `Check out from version control`-\u003e`GitHub [선택]`한다.\n\n2. 다음 단계로 Clone Repository 다이알로그가 나타나는데 `Git Repository URL`-\u003e`https://github.com/daejoon/LETS-KO.git [입력]`\n-\u003e`Parent Directory`-\u003e`C:\\Users\\{계정이름}\\IdeaProjects [입력]`-\u003e`Directory Name`-\u003e`LETS-KO [입력]`-\u003e`Clone [클릭]` 한다.\n\n3. Import Project 다이알로그 창이 나타나면 `Create project from existing sources`-\u003e`Next [클릭]`-\u003e`Next [클릭]`-\u003e`Unmark All [클릭]`-\u003e`Finish [클릭]` 한다.\n\n4. `File`-\u003e`Import Module`-\u003e`First.iml [선택]`-\u003e`OK [클릭]` 한다.\n\n### Run/Debug Configurations 설정\n\nIntelliJ와 WAS를 연결해야 로컬에서 개인별로 개발이 가능하다.\n\n프로젝트 환경을 구축하는 방법은 크게 2가지 가 있다.\n\n1안\n```\n개인1 WAS + 개인1 DB\n개인2 WAS + 개인2 DB\n개인3 WAS + 개인3 DB\n...\n개인N WAS + 개인N DB\n```\n\n2안\n```\n개인1 WAS ──┐\n개인2 WAS ──┤\n개인3 WAS ──┼── 공용1 DB\n...      ──┤\n개인N WAS ──┘\n```\n\n여기서는 2안을 염두해 두고 만들었다.\n\n1. `Run`-\u003e`Edit Configurations [선택]` 한다.\n\n2. Run/Debug Configurations 다이알로그가 나타난다. `+`-\u003e`Tomcat Server`-\u003e`Local [클릭]` 한다.\n\n3. `Name`-\u003e`First - Tomcat 7.0 [입력]`-\u003e`Application Server`-\u003e`Tomcat 7.0 [선택]` 한다.\n\n4. `Fix`-\u003e`First:war exploded [선택]`-\u003e`OK [클릭]` 한다.\n\n5. `File`-\u003e`Project Structure...`-\u003e`Project Settings`-\u003e`Modules`-\u003e`First [선택]`-\u003e`Dependencies`-\u003e`+ [클릭]`-\u003e`Library...`-\u003e`Application Server Libraries`-\u003e`Tomcat 7.0 [선택]`-\u003e`Add Selected [클릭]` 하여 WAS에 의존적인 라이브러리를 링크한다.\n\n### Git 설정\n\n`File`-\u003e`Settings...`-\u003e`Version Control`-\u003e`Ignored Files`-\u003e`+ [클릭]`-\u003e`Ignore all files under`-\u003e`... [선택]`-\u003e`.idea [선택]`-\u003e`Ok [클릭]`\n\n\n\n\n\n## Tomcat 설정\n\n### UTF-8 설정\n\n`[TOMCAT_HOME]/conf/server.xml`에  useBodyEncodingForURI=\"true\", URIEncoding=\"UTF-8\"을 추가한다.\n``` xml\n\u003cConnector port=\"8080\" protocol=\"HTTP/1.1\"\n           connectionTimeout=\"20000\"\n           redirectPort=\"8443\"\n           useBodyEncodingForURI=\"true\"\n           URIEncoding=\"UTF-8\" /\u003e\n```\n\n### Jndi 설정\n\n`[TOMCAT_HOME]/conf/context.xml`에 Resource 엘리먼트를 추가한다.\n``` xml\n\u003cResource name=\"[jndi이름: 예)jdbc/letsko_ds01]\"\n          auth=\"Container\"\n          type=\"javax.sql.DataSource\"\n          username=\"[DB 로그인 아이디]\"\n          password=\"[DB 로그인 비밀번호]\"\n          driverClassName=\"[DB 드라이버클래스네임: 예)com.microsoft.sqlserver.jdbc.SQLServerDriver]\"\n          url=\"[DB Url: 예)jdbc:sqlserver://localhost:1433;databaseName=lets_ko;integratedSecurity=false;]\"\n          maxActive=\"10\"\n          maxIdle=\"5\"/\u003e\n```\n\n`[CONTEXT_CONFIG_HOME]/default-config.xml`에 Jndi를 설정한다.\n```xml\n\u003cdataSources\u003e\n    \u003cds01\u003e\n        \u003cjndiName\u003ejdbc/letsko_ds01\u003c/jndiName\u003e\n        \u003cresourceRef\u003etrue\u003c/resourceRef\u003e\n    \u003c/ds01\u003e\n\u003c/dataSources\u003e\n```\n\n`[CONTEXT_CONFIG_HOME]/spring/context-datasource.xml`의 Jndi를 설정한다.\n``` xml\n\u003cjee:jndi-lookup id=\"dataSource\" jndi-name=\"${dataSources.ds02.jndiName}\" /\u003e\n```\n\n\n\n\n\n## Web Application 설정\n\n### UTF-8 설정\n\n이 프로젝트의 기본 인코딩은 UTF-8이다.\n\n`[WEB_HOME]/WEB-INF/web.xml`에 Spring CharacterEncodingFilter를 이용해서 UTF-8을 설정한다.\n``` xml\n\u003c!-- Encoding Filter --\u003e\n\u003cfilter\u003e\n    \u003cfilter-name\u003eencodingFilter\u003c/filter-name\u003e\n    \u003cfilter-class\u003eorg.springframework.web.filter.CharacterEncodingFilter\u003c/filter-class\u003e\n    \u003cinit-param\u003e\n        \u003cparam-name\u003eencoding\u003c/param-name\u003e\n        \u003cparam-value\u003eUTF-8\u003c/param-value\u003e\n    \u003c/init-param\u003e\n\u003c/filter\u003e\n\u003cfilter-mapping\u003e\n    \u003cfilter-name\u003eencodingFilter\u003c/filter-name\u003e\n    \u003curl-pattern\u003e/*\u003c/url-pattern\u003e\n\u003c/filter-mapping\u003e\n```\n\n### Log4j 설정\n\nLog4j의 설정 파일을 읽을수 있게 web.xml에 추가한다.\n``` xml\n\u003c!-- log4j ContextLoader --\u003e\n\u003ccontext-param\u003e\n    \u003cparam-name\u003elog4jConfigLocation\u003c/param-name\u003e\n    \u003cparam-value\u003eclasspath:config/log4j/log4j.xml\u003c/param-value\u003e\n\u003c/context-param\u003e\n\u003clistener\u003e\n    \u003clistener-class\u003eorg.springframework.web.util.Log4jConfigListener\u003c/listener-class\u003e\n\u003c/listener\u003e\n```\n\nLog4j 설정 파일은 `[CONTEXT_CONFIG_HOME]/log4j/log4j.xml`을 참고한다.\n\n\n\n\n\n## SpringFramework\n\n참고: [Spring MVC](http://www.slideshare.net/ienvyou/spring-mvc-30209196)\n\n### SpringFramework 설정\n\nSpringFramework(이하 Spring) 설정은 Root Context 설정과, Servlet Context 설정으로 나뉜다.\n\nRoot Context 설정은 Spring 전반적인 설정이고, Servlet Context 설정은 웹과 관련된 설정이다.\n\nRoot Context 설정은 Servlet Context로 상속된다.\n\nServlet Context 설정은 `[SERVLET_CONFIG_HOME]/springmvc` 폴더 아래 파일을 참고한다.\n\nRoot Context 설정은 `[CONTEXT_CONFIG_HOME]/spring` 폴더 아래 파일을 참고한다.\n\n`[WEB_HOME]/WEB-INF/web.xml`의 Root Context 위치 설정\n``` xml\n\u003c!-- SpringFramework ContextLoader --\u003e\n\u003ccontext-param\u003e\n    \u003cparam-name\u003econtextConfigLocation\u003c/param-name\u003e\n    \u003cparam-value\u003eclasspath*:config/spring/context-*.xml\u003c/param-value\u003e\n\u003c/context-param\u003e\n\u003clistener\u003e\n    \u003clistener-class\u003eorg.springframework.web.context.ContextLoaderListener\u003c/listener-class\u003e\n\u003c/listener\u003e\n```\n\n`[WEB_HOME]/WEB-INF/web.xml`의 Servlet Context 위치 설정\n``` xml\n\u003c!-- Servlet Dispatcher --\u003e\n\u003cservlet\u003e\n    \u003cservlet-name\u003eaction\u003c/servlet-name\u003e\n    \u003cservlet-class\u003eorg.springframework.web.servlet.DispatcherServlet\u003c/servlet-class\u003e\n    \u003cinit-param\u003e\n        \u003cparam-name\u003econtextConfigLocation\u003c/param-name\u003e\n        \u003cparam-value\u003e\n            /WEB-INF/config/springmvc/servlet-*.xml\n        \u003c/param-value\u003e\n    \u003c/init-param\u003e\n    \u003cload-on-startup\u003e1\u003c/load-on-startup\u003e\n\u003c/servlet\u003e\n\u003cservlet-mapping\u003e\n    \u003cservlet-name\u003eaction\u003c/servlet-name\u003e\n    \u003curl-pattern\u003e/\u003c/url-pattern\u003e\n\u003c/servlet-mapping\u003e\n```\n\n###  user-config.xml, default-config.xml 설정\n\nSpring의 org.springframework.beans.factory.config.PropertyPlaceholderConfigurer을 사용하면 properties를 스프링 설정에 사용할수 있다.\n\n여기에 org.springmodules을 추가하면 properties를 사용하지 않고 xml로 대체할 수 있다.\n\n이 프로젝트는 org.springmodules를 사용했다.\n\n`[CONTEXT_CONFIG_HOME]/spring/context-common.xml`에 CompositeConfiguration을 이용하여 xml 설정파일을 여러개 올릴수 있다. 두 설정 파일 중에 같은 엘리먼트가 존재하면 먼저 올린 user-config.xml 파일의 엘리먼트가 우선한다.\n``` xml\n\u003c!-- 환경 설정 xml 파일을 로딩한다. --\u003e\n\u003cbean id=\"configuration\" class=\"org.apache.commons.configuration.CompositeConfiguration\"\u003e\n    \u003cconstructor-arg\u003e\n        \u003clist\u003e\n            \u003cbean class=\"org.apache.commons.configuration.XMLConfiguration\"\u003e\n                \u003cconstructor-arg type=\"java.lang.String\"\u003e\n                    \u003cvalue\u003econfig/user-config.xml\u003c/value\u003e\n                \u003c/constructor-arg\u003e\n            \u003c/bean\u003e\n            \u003cbean class=\"org.apache.commons.configuration.XMLConfiguration\"\u003e\n                \u003cconstructor-arg type=\"java.lang.String\"\u003e\n                    \u003cvalue\u003econfig/default-config.xml\u003c/value\u003e\n                \u003c/constructor-arg\u003e\n            \u003c/bean\u003e\n        \u003c/list\u003e\n    \u003c/constructor-arg\u003e\n\u003c/bean\u003e\n```\n\n이렇게 작성한 이유는 개발시에 공통 부분과 개개인 설정이 분리됨으로 해서 개발의 편의성이 증대되고\n\n실제 배포시에는 default-config.xml만 배포함으로써 배포 환경과 개발 환경을 분리하여 관리할수 있기 때문이다.\n\n(* 프레임워크 내부에서 user-config.xml 파일을 사용하기 때문에 파일 자체는 존재해야 한다.)\n\n따라서 여기 GitHub에도 user-config.xml 파일을 업로드 하지 않았다. 차후에 Clone한후 user-config.xml 파일을 추가하면 된다.\n\n사실 user-config.xml 파일과 default-config.xml 파일의 엘리먼트는 일치하지 않아도 상관없으니 경험상 일치하는게 실수 방지에 좋고 편하다.\n\n되도록이면 두 파일의 엘리먼트를 일치시키고 개별적 적용 엘리먼트만 user-config.xml 파일에서 수정하자.\n\n`[CONTEXT_CONFIG_HOME]/spring/context-common.xml`에 CompositeConfiguration을 이용하여 설정 xml 파일들을 PropertyPlaceholderConfigurer에 연결시켜준다.\n``` xml\n\u003cbean class=\"org.springframework.beans.factory.config.PropertyPlaceholderConfigurer\"\u003e\n    \u003cproperty name=\"properties\"\u003e\n        \u003cbean class=\"org.springmodules.commons.configuration.CommonsConfigurationFactoryBean\"\u003e\n            \u003cproperty name=\"configurations\"\u003e\n                \u003clist\u003e\n                    \u003cref bean=\"configuration\" /\u003e\n                \u003c/list\u003e\n            \u003c/property\u003e\n        \u003c/bean\u003e\n    \u003c/property\u003e\n\u003c/bean\u003e\n```\n\n이 설정을 함으로써 context-*.xml 파일들에서 properties를 사용할수 있다.\n\n또한 이렇게 사용한 user-config.xml과 default-config.xml 파일은 dd2.com.util.CofingUtil을 통해서 런타임시에 접근할수 있다.\n\n사용 방법은 xml에서 properties를 사용하듯이 dot 접근방법을 쓴다.\n\n환경설정 xml 파일 작성 예\n``` xml\n\u003c!-- sampel-config.xml --\u003e\n\u003c?xml version=\"1.0\" encoding=\"UTF-8\"?\u003e\n\u003cconfig\u003e\n    \u003c!-- mode --\u003e\n    \u003cmode\u003e\n        \u003c!-- mode.type : debug / release --\u003e\n        \u003ctype\u003edebug\u003c/type\u003e\n    \u003c/mode\u003e\n\u003c/config\u003e\n```\n\n스프링 설정 파일에서 접근\n```xml\n\u003cbean id=\"testSampleBean\" class=\"org.testSample.TestBean\"\u003e\n    \u003cproperty name=\"test\" value=\"${mode.type}\" /\u003e\n\u003c/bean\u003e\n```\n\n자바코드에서 접근\n```java\nString type = ConfigUtil.getString(\"mode.type\");\n```\n\n위의 두 예제에서 봤듯이 최상위 config 엘리먼트는 생략가능하다.\n\n\n\n\n\n## SpringSecurity\n\n로그인과 권한인증(Authentication) 권한허가(Authorization)를 손쉽게 관리할수 있어. 실제 비지니스에 집중할수 있게 해준다.\n\n### SpringSecurity 설정\n\n`[WEB_HOME]/WEB-INF/web.xml`의 DelegatingFilterProxy filter 설정해야 SpringSecurity가 동작한다.\n``` xml\n\u003c!-- Spring Security Filter --\u003e\n\u003cfilter\u003e\n    \u003cfilter-name\u003espringSecurityFilterChain\u003c/filter-name\u003e\n    \u003cfilter-class\u003eorg.springframework.web.filter.DelegatingFilterProxy\u003c/filter-class\u003e\n\u003c/filter\u003e\n\u003cfilter-mapping\u003e\n    \u003cfilter-name\u003espringSecurityFilterChain\u003c/filter-name\u003e\n    \u003curl-pattern\u003e/*\u003c/url-pattern\u003e\n\u003c/filter-mapping\u003e\n```\n\nSpringSecurity의 세부 설정은 `[CONTEXT_CONFIG_HOME]/spring/context-security.xml`을 확인한다.\n\n\n\n\n\n## Hibernate\n\n### Hibernate 설정\n\n`[CONTEXT_CONFIG_HOME]/spring/context-datasource.xml`의 LocalSessionFactoryBean Bean을 설정한다. LocalSessionFactoryBean을 사용하면  Hibernate Annotation을 사용할수 있다.\n``` xml\n\u003c!-- hibernate sessionFactory --\u003e\n\u003cbean id=\"sessionFactory\" class=\"org.springframework.orm.hibernate4.LocalSessionFactoryBean\"\u003e\n    \u003cproperty name=\"dataSource\" ref=\"dataSource\" /\u003e\n    \u003cproperty name=\"packagesToScan\" value=\"dd2.local\" /\u003e\n    \u003cproperty name=\"hibernateProperties\"\u003e\n        \u003cprops\u003e\n            \u003cprop key=\"hibernate.dialect\"\u003e${hibernate.dialect}\u003c/prop\u003e\n            \u003cprop key=\"hibernate.show_sql\"\u003e${hibernate.show_sql}\u003c/prop\u003e\n            \u003cprop key=\"hibernate.format_sql\"\u003e${hibernate.format_sql}\u003c/prop\u003e\n            \u003cprop key=\"hibernate.connection.useUnicode\"\u003e${hibernate.connection.useUnicode}\u003c/prop\u003e\n            \u003cprop key=\"hibernate.connection.characterEncoding\"\u003e${hibernate.connection.characterEncoding}\u003c/prop\u003e\n            \u003cprop key=\"hibernate.connection.charSet\"\u003e${hibernate.connection.charSet}\u003c/prop\u003e\n            \u003cprop key=\"hibernate.hbm2ddl.import_files\"\u003e${hibernate.hbm2ddl.import_files}\u003c/prop\u003e\n            \u003cprop key=\"hibernate.hbm2ddl.auto\"\u003e${hibernate.hbm2ddl.auto}\u003c/prop\u003e\n        \u003c/props\u003e\n    \u003c/property\u003e\n\u003c/bean\u003e\n```\n\nHibernate 설정은 default-config.xml, user-config.xml에서 설정값을 가져온다.\n\nhibernate.dialect는 SQLServer를 사용하기 때문에 `org.hibernate.dialect.SQLServerDialect`로 설정 한다. hibernate.show_sql, hibernate.format_sql을 `true`로 설정하여 개발시에 console 화면에 정렬해서 보여준다.\n``` xml\n\u003c!-- hibernate --\u003e\n\u003chibernate\u003e\n    \u003cdialect\u003eorg.hibernate.dialect.SQLServerDialect\u003c/dialect\u003e\n    \u003cshow_sql\u003etrue\u003c/show_sql\u003e\n    \u003cformat_sql\u003etrue\u003c/format_sql\u003e\n    \u003cconnection\u003e\n        \u003cuseUnicode\u003etrue\u003c/useUnicode\u003e\n        \u003ccharacterEncoding\u003eUTF-8\u003c/characterEncoding\u003e\n        \u003ccharSet\u003eUTF-8\u003c/charSet\u003e\n    \u003c/connection\u003e\n    \u003chbm2ddl\u003e\n        \u003cimport_files\u003econfig/hibernate/sql/initial_data.sql\u003c/import_files\u003e\n        \u003cauto\u003evalidate\u003c/auto\u003e\n    \u003c/hbm2ddl\u003e\n\u003c/hibernate\u003e\n```\n\nhibernate.hbm2ddl.auto의 값은 create, create-drop, update, validate 값이 올수 있다.\n```\ncreate: Entity를 기초로 테이블을 생성한다.\ncreate-drop: Entity를 기초로 테이블을 생성하고 프로그램 종료시에 삭제한다. WAS 종료시에 삭제된다.\nupdate: Entity와 테이블을 비교하여 변경 부분을 적용한다. (권한에 따라 안될수도 있다.)\nvalidate: Entity와 테이블을 비교만 한다.\n```\n\n대부분 프로젝트를 만들때 데이터베이스 테이블을 미리 구성하고 그다음에 Entity를 생성한다. \n\n따라서 이 프로젝트와 같이 테이블을 역으로 생성하는 경우는 드물다.\n\n여기서는 create로 테이블을 생성후에 설정값을 validate로 변경하자.\n\n그렇게 하지 않는다면 WAS가 실행될때마다 테이블이 다시 생성되어서 기존 데이터가 삭제된다.\n\n\n\n\n\n## Tiles\n\nComposite Pattern을 이용한 View 관리를 간편하 화기 위한 Framework이다.\n\n[Tiles 홈페이지](http://tiles.apache.org/)\n\n### Tiles 설정\n\nTiles는 Servlet Context 영역으로 `[WEB_CONFIG_HOME]/springmvc/servlet-tiles.xml`에서 설정한다.\n``` xml\n\u003c!-- tiles configurer --\u003e\n\u003cbean id=\"tilesConfigurer\" class=\"org.springframework.web.servlet.view.tiles3.TilesConfigurer\"\u003e\n    \u003cproperty name=\"completeAutoload\" value=\"true\"/\u003e\n    \u003cproperty name=\"useMutableTilesContainer\" value=\"true\" /\u003e\n    \u003cproperty name=\"checkRefresh\" value=\"true\" /\u003e\n    \u003cproperty name=\"definitions\"\u003e\n        \u003clist\u003e\n            \u003cvalue\u003e/WEB-INF/config/tiles/**/tiles-*.xml\u003c/value\u003e\n        \u003c/list\u003e\n    \u003c/property\u003e\n\u003c/bean\u003e\n```\n\ncompleteAutoload 프로퍼티의 값을 true로 설정하면 tiles 설정파일에서 EL, REGEXP를 사용할수 있다. `[MODULE_HOME]/POM.xml`에 tiles-extras, tiles-el를 추가한다.\n```xml\n\u003cdependency\u003e\n    \u003cgroupId\u003eorg.apache.tiles\u003c/groupId\u003e\n    \u003cartifactId\u003etiles-extras\u003c/artifactId\u003e\n    \u003cversion\u003e${tiles.version}\u003c/version\u003e\n\u003c/dependency\u003e\n\u003cdependency\u003e\n    \u003cgroupId\u003eorg.apache.tiles\u003c/groupId\u003e\n    \u003cartifactId\u003etiles-el\u003c/artifactId\u003e\n    \u003cversion\u003e${tiles.version}\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\nTiles설정은 `[WEB_CONFIG_HOME]/tiles/tiles-definitions.xml`를 참조한다.\n```xml\n\u003ctiles-definitions\u003e\n    \u003c!-- default template --\u003e\n    \u003cdefinition name=\"defaultTpl\" template=\"/WEB-INF/view/tiles/template/default.layout.jsp\"\u003e\n        \u003cput-attribute name=\"title\"         value=\"\" type=\"string\" /\u003e\n        \u003cput-attribute name=\"head\"          value=\"/WEB-INF/view/tiles/attribute/head.attr.jsp\" /\u003e\n        \u003cput-attribute name=\"javascript\"    value=\"/WEB-INF/view/tiles/attribute/javascript.attr.jsp\" /\u003e\n        \u003cput-attribute name=\"top\"           value=\"/WEB-INF/view/tiles/attribute/top.attr.jsp\" /\u003e\n        \u003cput-attribute name=\"left\"          value=\"/WEB-INF/view/tiles/attribute/left.attr.jsp\" /\u003e\n        \u003cput-attribute name=\"contents\"      value=\"/WEB-INF/view/tiles/attribute/contents.attr.jsp\" /\u003e\n        \u003cput-attribute name=\"bottom\"        value=\"/WEB-INF/view/tiles/attribute/bottom.attr.jsp\" /\u003e\n    \u003c/definition\u003e\n\n    \u003c!-- index template --\u003e\n    \u003cdefinition name=\"indexTpl\" template=\"/WEB-INF/view/tiles/template/index.layout.jsp\"\u003e\n        \u003cput-attribute name=\"title\"         value=\"\" type=\"string\" /\u003e\n        \u003cput-attribute name=\"head\"          value=\"/WEB-INF/view/tiles/attribute/head.attr.jsp\" /\u003e\n        \u003cput-attribute name=\"javascript\"    value=\"/WEB-INF/view/tiles/attribute/javascript.attr.jsp\" /\u003e\n        \u003cput-attribute name=\"top\"           value=\"/WEB-INF/view/tiles/attribute/top.attr.jsp\" /\u003e\n        \u003cput-attribute name=\"contents\"      value=\"/WEB-INF/view/tiles/attribute/contents.attr.jsp\" /\u003e\n        \u003cput-attribute name=\"bottom\"        value=\"/WEB-INF/view/tiles/attribute/bottom.attr.jsp\" /\u003e\n    \u003c/definition\u003e\n\u003c/tiles-definitions\u003e\n```\n\ntiles-definitions.xml에서 `\u003cdefinition name=\"defaultTpl\" \\\u003e` 구성 예\n```\n1. defaultTpl의 전체적인 틀(템플릿)은 \"/WEB-INF/view/tiles/template/default.layout.jsp\"에 구성되어 있다.\n2. default.layout.jsp는 title, head, javascript, top, left, contents, bottom 부분으로 구성되어 있다.\n    2.1. title: 웹페이지의 제목이 오는 부분\n    2.2. head: html의 \u003chead\u003e\u003c/head\u003e 태그 안에 부분\n    2.3. javascript: 공통으로 사용되는 자바스크립트가 인클루드 되는 부분\n    2.4. top: 상단 공통메뉴\n    2.5. left: 왼쪽 공통메뉴\n    2.6. contents: 각 페이지별로 화면구성\n    2.7. bottom: 회사소개, Copyright 구성\n```\n\n결국 `\u003cdefinition name=\"defaultTpl\" \\\u003e`을 상속 받는다는 것은 위 공통 구성을 사용한다는 것이고 그중 title, contents부분만 교체해 줌으로 해서 페이지 별 화면 구성을 달리 할 수 있다.\n\n`[WEB_CONFIG_HOME]/tiles/tiles-definitions.xml`의 예외 같이 `\u003cdefinition name=\"defaultTpl\" \\\u003e`을 상속받아 title, contents 부분만 오버라이딩 해주면 된다.\n\n만약 특별한 위와 다른 구조의 레이아웃이 필요하면 tiles-definitions.xml에 기본 템플릿을 추가한후 상속받아 사용하면 된다.\n```xml\n\u003ctiles-definitions\u003e\n    \u003c!-- 왼쪽 메뉴가 있는 템플릿 --\u003e\n    \u003cdefinition name=\"REGEXP:(.*)\\.defaultTpl\" extends=\"defaultTpl\"\u003e\n        \u003cput-attribute name=\"title\"     expression=\"${title}\" /\u003e\n        \u003cput-attribute name=\"contents\"  value=\"/WEB-INF/view/{1}.jsp\" /\u003e\n    \u003c/definition\u003e\n    \u003c!-- 왼쪽 메뉴가 없는 템플릿 --\u003e\n    \u003cdefinition name=\"REGEXP:(.*)\\.indexTpl\" extends=\"indexTpl\"\u003e\n        \u003cput-attribute name=\"title\"     expression=\"${title}\" /\u003e\n        \u003cput-attribute name=\"contents\"  value=\"/WEB-INF/view/{1}.jsp\" /\u003e\n    \u003c/definition\u003e\n\u003c/tiles-definitions\u003e\n```\n\n웹페이지 호출 경로는 아래와 같다.\n```\nURL Call -\u003e Controller -\u003e Service -\u003e DAO  -----\n                                              |\n                                          DataBase\n                                              |\n웹페이지   \u003c- Controller \u003c- Service \u003c- DAO \u003c-----\n```\n\n여기서 Controller에서 웹페이지로 호출될때 Springframework에서는 ViewResolver를 이용해서 출력 포맷을 지정할수 있다.\n\n`[WEB_CONFIG_HOME]/springmvc/servlet-view.xml` 파일에서 tiles를 이용하기 위해서 viewresolver에 등록했다.\n\nSpringFramework에서 뷰를 지정하는 순서는 ContentNegotiatingViewResolver 패턴매칭 알고리즘에 의해서 몇 가지 후보군을 고른후\n\ndefaultViews의 View를 추가하여 총 후보군을 설정 매칭후 결과를 리턴한다.\n\n매칭 결과 일치하는 View를 찾을수 없으면 404에러가 발생한다.\n\nSpring View Resolver 설정\n``` xml\n\u003cbean class=\"org.springframework.web.servlet.view.ContentNegotiatingViewResolver\"\u003e\n    \u003cproperty name=\"order\" value=\"0\" /\u003e\n    \u003cproperty name=\"contentNegotiationManager\"\u003e\n        \u003cbean class=\"org.springframework.web.accept.ContentNegotiationManager\"\u003e\n            \u003cconstructor-arg\u003e\n                \u003cbean class=\"org.springframework.web.accept.PathExtensionContentNegotiationStrategy\"\u003e\n                    \u003cconstructor-arg\u003e\n                        \u003cmap\u003e\n                            \u003centry key=\"html\"   value=\"text/html\"/\u003e\n                            \u003centry key=\"json\"   value=\"application/json\" /\u003e\n                            \u003centry key=\"jsonp\"  value=\"javascript/json\" /\u003e\n                            \u003centry key=\"xml\"    value=\"application/xml\" /\u003e\n                        \u003c/map\u003e\n                    \u003c/constructor-arg\u003e\n                \u003c/bean\u003e\n            \u003c/constructor-arg\u003e\n        \u003c/bean\u003e\n    \u003c/property\u003e\n\n    \u003c!-- ViewResolvers --\u003e\n    \u003cproperty name=\"viewResolvers\"\u003e\n        \u003clist\u003e\n            \u003cbean class=\"org.springframework.web.servlet.view.BeanNameViewResolver\" /\u003e\n            \u003cbean class=\"org.springframework.web.servlet.view.tiles3.TilesViewResolver\"\u003e\n                \u003cproperty name=\"viewClass\"      value=\"org.springframework.web.servlet.view.tiles3.TilesView\" /\u003e\n                \u003cproperty name=\"contentType\"    value=\"text/html\" /\u003e\n            \u003c/bean\u003e\n            \u003cbean class=\"org.springframework.web.servlet.view.InternalResourceViewResolver\"\u003e\n                \u003cproperty name=\"viewClass\"      value=\"org.springframework.web.servlet.view.JstlView\" /\u003e\n                \u003cproperty name=\"prefix\"         value=\"/WEB-INF/view/\" /\u003e\n                \u003cproperty name=\"suffix\"         value=\".jsp\" /\u003e\n            \u003c/bean\u003e\n        \u003c/list\u003e\n    \u003c/property\u003e\n\n    \u003c!-- DefaultViews --\u003e\n    \u003cproperty name=\"defaultViews\"\u003e\n        \u003clist\u003e\n            \u003cref bean=\"jacksonJsonView\" /\u003e\n            \u003cref bean=\"jsonpView\" /\u003e\n            \u003cref bean=\"marshallingView\" /\u003e\n        \u003c/list\u003e\n    \u003c/property\u003e\n\u003c/bean\u003e\n```\n\nController에서 호출하는 방법은 return값으로 tiles name을 넘기는 방법을 사용한다.\n\n`[JAVA_SRC_HOME]/dd2/local/busi/main/web/MainController.java` 참고\n```java\n... 생략\n\n@Controller\n@RequestMapping(\"/main/*\")\npublic class MainController extends CommonController {\n    private static final Log logger = LogFactory.getLog(MainController.class);\n    private static final String BASE_URL = \"main/\";\n\n    @Override\n    public String getBaseUrl() {\n        return BASE_URL;\n    }\n\n    @RequestMapping(\"index\")\n    public String doIndex() {\n        return getBaseUrl() + \"index.indexTpl\";\n    }\n\n    @RequestMapping(\"dashboard\")\n    public String doDashboard() {\n        return getBaseUrl() + \"dashboard.defaultTpl\";\n    }\n}\n```\n\nTiles 템플릿은 indexTpl, defaultTpl 두가지 종류(*종류는 추가 할수 있다 이번 프로젝트에서는 2가지를 사용했다.)가 있고 필요에 따라서 추가한후 사용하면 된다.\n\n추가는 `[WEB_CONFIG_HOME]/tiles/tiles-definitions.xml`에 추가한다.\n\n\n\n\n\n## Frontend\n\n### Web App 구성\n\n전체적인 웹 구성\n```\nROOT-HOME\n    ├─ static\n    │   ├─ css: WebApp에서 사용하는 css파일\n    │   ├─ images: WebApp에서 사용하는 이미지\n    │   ├─ js\n    │   │   ├─ bootstrap\n    │   │   │   └─ bootstrap.local.js: 부트스트랩 설정\n    │   │   ├─ extend\n    │   │   │   └─ extendjs.js: 자바스크립트 확장\n    │   │   ├─ jqgrid\n    │   │   │   └─ jquery.jqgrid.local.js: jqgrid 설정\n    │   │   ├─ jquery\n    │   │   │   └─ jquery.local.js: jquery 설정\n    │   │   ├─ underscore\n    │   │   │   └─ underscore.local.js: underscore 설정\n    │   │   ├─ local.js: WebApp 공통으로 사용하는 기능\n    │   │   └─ main.js: requirejs 설정\n    │   └─ lib\n    │       ├─ bootstrap\n    │       │   └─ 3.0.3: bootstarp 버전\n    │       ├─ font-awesome\n    │       │   └─ 4.0.3: font-awesome 버전\n    │       ├─ jqgrid\n    │       │   └─ 4.5.4: jqgrid 버전\n    │       ├─ jquery\n    │       │   └─ 1.10.2: jquery 버전\n    │       ├─ jquery-ui\n    │       │   └─ 1.10.3: jquery-ui 버전\n    │       ├─ json\n    │       │   └─ 2013.05.26: json 버전\n    │       ├─ require\n    │       │   └─ 2.1.9: require 버전\n    │       └─ underscore\n    │           └─ 1.5.2: underscore 버전\n    ├─ WEB-INF\n    │   ├─ config\n    │   │   ├─ springmvc: SpringFramework Servlet Context 설정\n    │   │   └─ tiles: Tiles 설정\n    │   ├─ view: .jsp 뷰의 모음\n    │   └─ web.xml: WebApp web.xml\n    │\n    └─ welcome.jsp: index 페이지\n```\n\n### Bootstrap 설정\n\n부트스트랩은 css Framework으로 반응형 웹을 지원하며 개발자에게 CSS 고민을 덜고 웹사이트 개발을 할수 있게 해주는 CSS Framework이다.\n\n부트스트랩을 [다운로드](http://getbootstrap.com/)한다. 현재 최신 버전은 3.0.3 이다.\n\n`[WEB_HOME]/static/lib/bootstrap/3.0.3`에 압축을 해제한다.\n\n부트스트랩 개별 설정은 `[WEB_HOME]/static/js/bootstrap/bootstrap.local.js`을 참고한다.\n\n부트스트랩을 사용하기 위해서는 각 페이지 마다 .css, .js를 포함해야 한다.\n\n이 프로젝트에서는 Tiles를 이용하여 부트스트랩 라이브러리를 포함시킨다.\n\n`[WEB_HOME]/WEB-INF/view/tiles/attribute/head.attr.jsp`에 css를 추가한다.\n```html\n\u003c!-- stylesheet --\u003e\n\u003clink href=\"${pageContext.request.contextPath}/static/lib/bootstrap/3.0.3/css/bootstrap.css\" rel=\"stylesheet\"\u003e\n\u003clink href=\"${pageContext.request.contextPath}/static/lib/bootstrap/3.0.3/css/bootstrap-theme.css\" rel=\"stylesheet\"\u003e\n\u003clink href=\"${pageContext.request.contextPath}/static/lib/font-awesome/4.0.3/css/font-awesome.css\" rel=\"stylesheet\"\u003e\n```\n\n부트스트랩 라이브러리와 함께 부트스트랩 기반의 font-awesome 라이브러리도 같이 포함시킨다. [font-awesome 다운로드](http://fontawesome.io/)\n\n`[WEB_HOME]/static/lib/font-awesome/4.0.3`에 압축을 해제한다.\n\n부트스트랩의 사용법은 [Bootstrap Getting Started ](http://getbootstrap.com/getting-started/)문서를 참고한다.\n\n\n### requirejs 설정\n\nrequirejs는 웹을 모듈화 할수 있게 해주는 프레임웍이다. 자바스크립트 파일들을 requirejs 모듈화 규칙에 따라서 제작하면 각 자바스크립들에 대한 의존성 관리를 할 수 있다.\n\nrequirejs을 [다운로드](http://requirejs.org/docs/download.html) 한후 `[WEB_HOME]/static/lib/require/2.1.9`에 압축을 해제한다.\n\n참고: [JavaScript 모듈화를 위한 RequireJS 핵심정리](http://jcf.daewoobrenic.co.kr/blog/?p=235)\n\nrequierjs를 사용하기 위해서는 처음 엔트리 포인트가 필요하다. 이프로젝트에서는 head.attr.js에 작성하고 공통적으로 사용했다.\n\n`[WEB_HOME]/WEB-INF/view/tiles/attribute/head.attr.jsp` require.src.js 파일을 추가하고 requirejs 환경설정 파일인 main.js 파일로 추가한다.\n``` html\n\u003c!-- script --\u003e\n\u003cscript src=\"${pageContext.request.contextPath}/static/lib/require/2.1.9/require.src.js\"\u003e\u003c/script\u003e\n\u003cscript src=\"${pageContext.request.contextPath}/static/js/main.js\"\u003e\u003c/script\u003e\n```\n\n환경설정은 `[WEB_HOME]/static/js/main.js`을 참고한다.\n``` javascript\n... 생략\n;\"use strict\"\nrequire.config({\n    baseUrl: \"/static/js\",\n    paths: {\n        // local paths\n        \"backbone.local\"            : \"backbone/backbone.local\",\n        \"bootstrap.local\"           : \"bootstrap/bootstrap.local\",\n        \"dynatree.local\"            : \"dynatree/jquery.dynatree.local\",\n        \"extend\"                    : \"extend/extendjs\",\n        \"jqgrid.local\"              : \"jqgrid/jquery.jqgrid.local\",\n        \"jquery.local\"              : \"jquery/jquery.local\",\n        \"underscore.local\"          : \"underscore/underscore.local\",\n        \"local\"                     : \"local\",\n\n        // lib paths\n        \"angular\"                   : \"../lib/angular/1.2.6/angular\",\n        \"angular-resource\"          : \"../lib/angular/1.2.6/angular-resource\",\n        \"angular-locale\"            : \"../lib/angular/1.2.6/i18n/angular-locale_ko\",\n        \"backbone\"                  : \"../lib/backbone/1.1.0/backbone\",\n        \"bootstrap\"                 : \"../lib/bootstrap/3.0.3/js/bootstrap\",\n        \"ckeditor\"                  : \"../lib/ckeditor/4.3/ckeditor\",\n        \"domReady\"                  : \"../lib/domReady/2.0.1/domReady\",\n        \"dynatree\"                  : \"../lib/dynatree/1.2.4/src/jquery.dynatree\",\n        \"highlight\"                 : \"../lib/highlight/7.5/highlight.pack\",\n        \"holder\"                    : \"../lib/holder/2.2/holder\",\n        \"html5shiv\"                 : \"../lib/html5shiv/3.7.0/html5shiv.src\",\n        \"jqgrid\"                    : \"../lib/jqgrid/4.5.4/src/jquery.jqGrid\",\n        \"jqgrid-locale\"             : \"../lib/jqgrid/4.5.4/src/i18n/grid.locale-kr\",\n        \"jquery\"                    : \"../lib/jquery/1.10.2/jquery-1.10.2\",\n        \"jquery-form-validator\"     : \"../lib/jquery-form-validator/2.1.27/form-validator/jquery.form-validator\",\n        \"jquery-ui\"                 : \"../lib/jquery-ui/1.10.3/ui/jquery-ui\",\n        \"jquery-ui-datepicker\"      : \"../lib/jquery-ui/1.10.3/ui/jquery.ui.datepicker\",\n        \"jquery-ui-datepicker-lang\" : \"../lib/jquery-ui/1.10.3/ui/i18n/jquery.ui.datepicker-ko\",\n        \"json\"                      : \"../lib/json/2013.05.26/json2\",\n        \"respond\"                   : \"../lib/respond/1.4.0/respond.src\",\n        \"underscore\"                : \"../lib/underscore/1.5.2/underscore\"\n    },\n    shim: {\n        \"angular\": {\n            deps: [\"angular-locale\"],\n            exports: \"angular\"\n        },\n        \"backbone\": {\n            deps: [\"underscore\", \"jquery\"],\n            exports: \"Backbone\",\n            init: function() {\n                return this.Backbone.noConflict();\n            }\n        },\n        \"bootstrap\": {\n            deps: [\"jquery\"]\n        },\n        \"ckeditor\": {\n            deps: [\"jquery\"],\n            exports: \"CKEDITOR\"\n        },\n        \"dynatree\": {\n            deps: [\"jquery\", \"jquery-ui\"]\n        },\n        \"highlight\": {\n            exports: \"hljs\"\n        },\n        \"holder\": {\n            exports: \"HOLDER\"\n        },\n        \"jqgrid\": {\n            deps: [\"jquery\", \"jquery-ui\", \"jqgrid-locale\"]\n        },\n        \"jqgrid-locale\": {\n            deps: [\"jquery\"]\n        },\n        \"jquery\": {\n            deps: [\"json\"],\n            exports: \"jQuery\",\n            init: function() {\n                return this.jQuery.noConflict(true);\n            }\n        },\n        \"jquery-form-validator\": {\n            deps: [\"jquery\"]\n        },\n        \"jquery-ui\": {\n            deps: [\"jquery\"]\n        },\n        \"jquery-ui-datepicker\": {\n            deps: [\"jquery-ui\"]\n        },\n        \"jquery-ui-datepicker-lang\": {\n            deps: [\"jquery-ui-datepicker\"]\n        },\n        \"json\": {\n            exports : \"JSON\"\n        },\n        \"underscore\": {\n            exports: \"_\",\n            init: function() {\n                return this._.noConflict();\n            }\n        }\n    },\n    waitSeconds: 15\n});\n```\n\n참고: [Sim Configuraion 문서](http://gregfranko.com/blog/require-dot-js-2-dot-0-shim-configuration/)\n\n### jQuery 설정\n\n말이 필요없는 자바스크립트 라이브러리이다. [다운로드](http://jquery.com/download/)한다.\n\n`[WEB_HOME]/static/lib/jquery/1.10.2`에 압축을 해제한다.\n\n환경설정은 `[WEB_HOME]/static/js/jquery/jquery.local.js`을 참고한다.\n``` javascript\n;define([\n    \"jquery\",\n    \"json\"\n],\nfunction($, JSON) { \"use strict\";\n    /**\n     * ajax 기본 셋팅\n     */\n    $.ajaxSetup({\n        type        : 'POST',\n        async       : true,\n        cache       : false,\n        contentType : 'application/json; charset=utf-8',\n        dataType    : 'json',\n        error       : function(xhr,e) {\n            if (xhr.status == 0) {\n                alert('You are offline!!\\n Please Check Your Network.');\n            }\n            else if (xhr.status == 404) {\n                alert('Requested URL not found.');\n            }\n            else if (xhr.status == 500) {\n                alert('Internel Server Error.\\n');\n            }\n            else if (e.toLowerCase() == 'parsererror') {\n                alert('Error.\\nParsing JSON Request failed.');\n            }\n            else if (e.toLowerCase() == 'timeout') {\n                alert('Request Time out.');\n            }\n            else {\n                alert('Unknow Error.\\n'+xhr.responseText);\n            }\n        },\n        beforeSend : function(xhr, setting) {\n            if ( setting \u0026\u0026 setting.async == true ) {\n            }\n        },\n        complete : function(xhr, e) {\n        }\n    });//$.ajaxSetup({\n\n    $.ajaxPrefilter(\"json\", function (options, originalOptions, jqXHR) {\n        if (options.type.toUpperCase() == \"POST\" \u0026\u0026 originalOptions[\"data\"] \u0026\u0026 ($.isPlainObject(originalOptions.data) || $.isArray(originalOptions.data)) ) {\n            options.data = JSON.stringify(originalOptions.data);\n        }\n    });\n\n    $.fn.serializeObject = function() {\n        var o = {},\n            a = this.serializeArray();\n\n        $.each(a, function() {\n            if ( o[this.name] ) {\n                if ( !o[this.name].push ) {\n                    o[this.name] = [o[this.name]];\n                }\n                o[this.name].push(this.value || '');\n            } else {\n                o[this.name] = this.value || '';\n            }\n        });\n        return o;\n    };\n\n    /**\n     * 토글 이벤트 구현\n     * @param a 첫번째 클릭 이벤트\n     * @param b 두번째 클릭 이벤트\n     * @returns {*}\n     */\n    $.fn.clickToggle = function(a, b) {\n        return this.each(function() {\n            var clicked = false;\n            $(this).on(\"click\", function() {\n                if (clicked) {\n                    clicked = false;\n                    return b.apply(this, arguments);\n                } else {\n                    clicked = true;\n                    return a.apply(this, arguments);\n                }\n            });\n        });\n    };\n\n    $.createGUID = function() {\n        var S4 = function() {\n            return ((Math.random()+1)*10000|0).toString(16).substring(1);\n        };\n        return \"\" + (new Date()).getTime() + \"-\" + (S4()+S4()+\"-\"+S4()+\"-\"+S4()+\"-\"+S4()+\"-\"+S4()+S4()+S4()).toUpperCase();\n    };\n});\n```\n\n프로젝트에서 jquery API와 관련된 기본적인 설정과 Custom API를 추가해서 사용하면 된다.\n\n참고: [jQuery Documentation](http://api.jquery.com/)\n\n### jQueryUi 설정\n\njquery를 이용한 각종 UI 컴포넌트 집합이다. jqgrid에서 사용하기 때문에 필요하다. [다운로드](http://jqueryui.com/download/)한다.\n\n`[WEB_HOME]/static/lib/jquery-ui/1.10.3`에 압축 해제 한다.\n\njquery-ui는 css와 javascript 파일로 구성되어 있다. css는 head.attr.jsp 파일에 공통적으로 포함시킨다.\n``` html\n\u003clink href=\"${pageContext.request.contextPath}/static/lib/jquery-ui/1.10.3/themes/base/jquery-ui.css\" rel=\"stylesheet\"\u003e\n```\n\njavascript 파일은 `[WEB_HOME]/static/js/main.js` 파일에 설정한다.\n``` javascript\nrequire.config({\n    baseUrl: \"/static/js\",\n    paths: {\n        ... 생략\n        \"jquery-ui\"                 : \"../lib/jquery-ui/1.10.3/ui/jquery-ui\",\n        \"jquery-ui-datepicker\"      : \"../lib/jquery-ui/1.10.3/ui/jquery.ui.datepicker\",\n        \"jquery-ui-datepicker-lang\" : \"../lib/jquery-ui/1.10.3/ui/i18n/jquery.ui.datepicker-ko\",\n        ... 생략\n    },\n    shim: {\n        ... 생략\n        \"jquery-ui\": {\n            deps: [\"jquery\"]\n        },\n        \"jquery-ui-datepicker\": {\n            deps: [\"jquery-ui\"]\n        },\n        \"jquery-ui-datepicker-lang\": {\n            deps: [\"jquery-ui-datepicker\"]\n        },\n        ... 생략\n    },\n    waitSeconds: 15\n});\n\n```\n\n### jqGrid 설정\n\njquery plugin 형식의 웹 그리드 이다. [다운로드](http://www.trirand.com/blog/?page_id=6)한다.\n\n`[WEB_HOME]/static/lib/jqgrid/4.5.4`에 압축 해제한다.\n\n환경설정은 `[WEB_HOME]/static/js/jqgrid/jquery.jqgrid.local.js`을 참고한다.\n``` javascript\n;define([\n    \"jquery\",\n    \"jqgrid\",\n    \"jquery-ui-datepicker-lang\"\n],\nfunction($) { \"use strict\";\n    var _id = $.createGUID();\n    var _guidName = \"_local_jqgrid_guid_\";\n    var _userCacheName = \"userCache\";\n    var _userPostDataName = \"userPostData\";\n\n    var _DataConvert = function(data) {\n        var $t = $(this),\n            datatype  = $t.jqGrid('getGridParam', 'datatype'),\n            userCache = $t.jqGrid('getGridParam', _userCacheName) || false,\n            userPostData  = $t.jqGrid('getGridParam', _userPostDataName) || {},\n            newData   = \"\";\n\n        if ( typeof data[_userCacheName] === \"undefined\" ) {\n            data[_userCacheName] = userCache;\n        }\n\n        if ( typeof data[_guidName] === \"undefined\" ) {\n            data[_guidName] = _id;\n        }\n\n        if ( typeof data[_userPostDataName] === \"undefined\" ) {\n            data[_userPostDataName] = userPostData;\n        }\n\n        switch (datatype.toLowerCase()) {\n            case \"json\":\n                newData = JSON.stringify(data);\n                break;\n            default :\n                newData = data;\n                break;\n        }\n        return newData;\n    };\n\n    /**\n     * jqGrid default options\n     */\n    $.extend($.jgrid.defaults, {\n        userCache: false,\n        autowidth: true,\n        viewrecords: true,\n        gridview: true,// true로 하면은 treeGrid, subGrid, or the afterInsertRow event. 사용할수 없다. 대신 속도는 빠르다.\n        prmNames: {\n            page: \"pageNumber\",\n            rows: \"pageSize\"\n        },\n        jsonReader: {\n            page: \"page\",\n            total: \"total\",\n            records: \"records\",\n            root: \"rows\",\n            id: function(obj) {return (obj.idName||\"0\");},\n            repeatitems: false,// json을 데이터로 사용한다.\n            cell: \"cell\",// repeatitems: false 이면 무시된다.\n            userdate: \"userdata\"\n        },\n        loadError: function(xhr,st,err) {\n            alert(\"Server Failure\" + \"\\nType: \"+st+\"\\nResponse: \"+ xhr.status + \" \"+xhr.statusText );\n        },\n        mtype: \"POST\",//Defines the type of request to make (“POST” or “GET”)\n        datatype: \"local\",\n        ajaxGridOptions: { contentType: \"application/json\" },\n        ajaxRowOptions: { contentType: \"application/json\" },\n        ajaxCellOptions: { contentType: \"application/json\" },\n        serializeGridData: _DataConvert,\n        serializeCellData: _DataConvert,\n        cellsubmit: 'clientArray',// 셀 수정했을때 자동으로 서버에 전송되지 않는다.\n        cellEdit: false,\n        rowNum : 10,\n        rowList: [10, 15, 25, 50, 100, 200]\n    });\n\n    /**\n     * jqGrid edit options\n     */\n    $.extend($.jgrid.edit, {\n        ajaxEditOptions: { contentType: \"application/json\" },\n        recreateForm: true,\n        serializeEditData: _DataConvert\n    });\n\n    /**\n     * jqGrid dell options\n     */\n    $.extend($.jgrid.del, {\n        ajaxDelOptions: { contentType: \"application/json\" },\n        recreateForm: true,\n        serializeDelData: _DataConvert\n    });\n\n    /**\n     * jqGrid 확장 메소드\n     */\n    $.jgrid.extend({\n        addNewRow: function() {\n            var $that = this;\n            return $that.each(function() {\n                var $t = $(this);\n                $t.addRowData(\"\", {});\n            });\n        },\n        getSelectedRowId: function() {\n            var $t = this,\n                selectedId = $t.find(\"tr.selected-row\").prop(\"id\");\n\n            if ( selectedId == undefined  || selectedId == null ) {\n                return null;\n            }\n            return selectedId;\n        },\n        setSelectedRowById: function(rowid) {\n            var $that = this;\n\n            return  $that.each(function() {\n                var $t = $(this),\n                    iRow = 0,\n                    iCol = 0;\n                $.each($t.getDataIDs(), function(idx, value) {\n                    if ( parseInt(value) == parseInt(rowid) ) {\n                        iRow = idx;\n                        return false;\n                    }\n                });\n                var $tr = $t.find(\"tr[id=\" + rowid + \"]\");\n                $tr.addClass(\"selected-row ui-state-hover\");\n                $t[0].p.selrow = rowid;\n                $t[0].p.iRow = iRow+1;\n                $t[0].p.iCol = iCol;\n            });\n        },\n        getSelectedRowData: function() {\n            var $t = this,\n                id = $t.getSelectedRowId();\n\n            if ( id == null ) {\n                return [];\n            }\n            return $t.getRowData(id);\n        },\n        getMultiSelectedRowIDs: function() {\n            var $t = this,\n                isMultiSelect = $t.getGridParam('multiselect'),\n                result = [];\n            if ( isMultiSelect == true ) {\n                $t.find(\"tr[aria-selected=true]\").each(function() {\n                    var $tr = $(this);\n                    var id = $tr.prop(\"id\");\n                    result.push(id);\n                });\n            }\n            return result;\n        },\n        getMultiSelectedRowData: function() {\n            var $t = this,\n                isMultiSelect = $t.getGridParam('multiselect'),\n                result = [];\n            if ( isMultiSelect == true ) {\n                var ids = $t.getMultiSelectedRowIDs();\n                $.each(ids, function(idx, id) {\n                    result.push( $t.getRowData(id));\n                });\n\n            }\n            return result;\n        },\n        getColModel: function(name) {\n            var $t = this,\n                colModel = $t.getGridParam(\"colModel\");\n            if ( arguments.length == 0 ) {\n                return colModel;\n            } else if ( $.isNumeric(name)) {\n                return colModel[name];\n            } else {\n                var colModelRow = {};\n                $.each(colModel, function(idx, value) {\n                    if ( value.name == name ) {\n                        colModelRow = value;\n                        return false;\n                    }\n                });\n                return colModelRow;\n            }\n        },\n        setColModel: function(updateColModel) {\n            // 속도를 빠르게 하기 위해서 해쉬를 이용한다.\n            var mapUpdateColModel = {};\n            $.each(updateColModel, function (idx, value) {\n                mapUpdateColModel[value.name] = value;\n            });\n\n            return this.each(function () {\n                var $t         = $(this),\n                    colModel    = $t.getGridParam(\"colModel\"),\n                    newColModel = [];\n                $.each(colModel, function (idx, value) {\n                    if ( mapUpdateColModel[value.name] ) {\n                        newColModel.push(mapUpdateColModel[value.name]);\n                    } else {\n                        newColModel.push(value);\n                    }\n                });\n                $t.setGridParam(\"colModel\", newColModel);\n            });\n        }\n    })\n\n    /**\n     * jqGrid Static 메소드 모음\n     */\n    $.extend( ($.jqGridStatic = $.jqGridStatic || {}), {\n        /**\n         * 날짜 포맷에 대한 설정\n         */\n        userDateSetting: {\n            sorttype: 'date',\n            formatter: function(cellValue /* cell의 값 */, option /* colModel option  */, rowObject /* 현재 row 값 */) {\n                var date = null;\n\n                if ( cellValue == undefined || cellValue == null ) {\n                    return \"\";\n                }\n\n                if ( /^\\d+$/gi.test(cellValue.toString()) == true ) {\n                    date = new Date( parseInt(cellValue)  );\n                    return date.format(\"yyyy-MM-dd\");\n                } else {\n                    return cellValue.toString();\n                }\n            },\n            editable: true,\n            edittype: 'text',\n            editoptions: {\n                size: 12,\n                maxlengh: 12,\n                dataInit: function (element) {\n                    $(element).datepicker({ dateFormat: 'yy-mm-dd' });\n                }\n            },\n            editrules: {\n                date: true\n            }\n        },\n        /**\n         * jqGrid에서 사용하는 editoptions 중에 Select를 만들어준다.\n         * @param setting {defaultOptionUsed, defaultOption, data, valueName, displayName, defaultValue}\n         * @param option\n         * @returns {*|Object}\n         */\n        selectEditOptions: function(setting , option ) {\n            var ret = $.extend({}, option || {});\n            if ($.isPlainObject(setting)) {\n                var valueString = \"\";\n                var newSetting = $.extend({\n                    defaultOptionUsed: true,\n                    defaultOption: {\n                        value: null,\n                        display: \"◆ 선택해주세요 ◆\"\n                    },\n                    data: [],\n                    valueName: \"\",\n                    displayName: \"\",\n                    defaultValue: null\n                }, setting);\n\n                if ( newSetting.defaultOptionUsed == true ) {\n                    if ( $.isPlainObject(newSetting.defaultOption) ) {\n                        valueString += \"\" + newSetting.defaultOption.value + \":\" + newSetting.defaultOption.display;\n                    } else  {\n                        valueString += newSetting.defaultOption;\n                    }\n                }\n\n                $.each(newSetting.data, function(idx, value) {\n                    valueString += \";\" + value[newSetting.valueName] + \":\" + value[newSetting.displayName];\n                });\n                if ( valueString.charAt(0) == \";\" ) {\n                    valueString = valueString.substring(1);\n                }\n\n                ret[\"value\"] = valueString;\n                ret[\"defaultValue\"] = \"\" + newSetting.defaultValue;\n            }\n            return ret;\n        },\n        /**\n         * [{},{},{},...] 데이터셋 구조에서 해당 Row를 Key Value로 찾아서 리턴한다.\n         * @param data {arrayData}\n         * @param obj {key,value}\n         * @returns {*|Object}\n         */\n        eachRowByKeyValue: function(data, obj) {\n            var ret = {};\n            $.each(data, function(idx, value) {\n                if ( value[obj.key] \u0026\u0026 value[obj.key] == obj.value ) {\n                    ret = value;\n                    return false;\n                }\n            });\n            return ret;\n        }\n    });\n});\n```\n\n참고:\n\n[jqGrid Documentation](http://www.trirand.com/jqgridwiki/doku.php?id=wiki:jqgriddocs)\n[데모페이지](http://trirand.com/blog/jqgrid/jqgrid.html),\n[그리드 옵션](http://www.trirand.com/jqgridwiki/doku.php?id=wiki:options),\n[메소드](http://www.trirand.com/jqgridwiki/doku.php?id=wiki:methods),\n[이벤트](http://www.trirand.com/jqgridwiki/doku.php?id=wiki:events),\n[ColModel 옵션](http://www.trirand.com/jqgridwiki/doku.php?id=wiki:colmodel_options),\n[데이터 조작](http://www.trirand.com/jqgridwiki/doku.php?id=wiki:retrieving_data#json_data),\n[Form Editing](http://www.trirand.com/jqgridwiki/doku.php?id=wiki:form_editing),\n[작성방법](http://www.trirand.com/jqgridwiki/doku.php?id=wiki:conventions)\n\n\n\n\n\n## Sample\n\n### Sample CRUD\n\n예제로 사용할 테이블 이다. 테이블을 생성한다.\n``` sql\nCREATE TABLE SAMPLE (\n\tID INT NOT NULL IDENTITY(1, 1),\n    NAME VARCHAR(255) NOT NULL,\n    AGE INT NUL NULL,\n    DESCRIPTION VARCHAR(255) NULL,\n    PRIMARY KEY(ID)\n);\n```\n\n`[JAVA_SRC_HOME]/dd2/local/entity/SampelEntity.java`을 생성한다.\n``` Java\npackage dd2.local.entity;\n\nimport javax.persistence.*;\n\n@javax.persistence.Table(name = \"SAMPLE\")\n@Entity\npublic class SampleEntity {\n    private Long id;\n    private String name;\n    private Long age;\n    private String description;\n\n    public SampleEntity() {\n    }\n\n    @GeneratedValue(strategy = GenerationType.AUTO)\n    @Column(name = \"ID\")\n    @Id\n    public Long getId() {\n        return id;\n    }\n\n    public void setId(Long id) {\n        this.id = id;\n    }\n\n    @Column(name = \"NAME\", nullable = false)\n    public String getName() {\n        return name;\n    }\n\n    public void setName(String name) {\n        this.name = name;\n    }\n\n    @Column(name = \"AGE\", nullable = false)\n    public Long getAge() {\n        return age;\n    }\n\n    public void setAge(Long age) {\n        this.age = age;\n    }\n\n    @Column(name = \"DESCRIPTION\", nullable = true)\n    public String getDescription() {\n        return description;\n    }\n\n    public void setDescription(String description) {\n        this.description = description;\n    }\n}\n\n```\n참고 문서: [Hibernate Documentation](http://hibernate.org/orm/documentation/)\n\n`[WEB_HOME]/WEB-INF/view/sample` 폴더를 생성한다.\n\n`[WEB_HOME]/WEB-INF/view/sample/list.jsp` 파일을 생성한다.\n``` javascript\n\u003c%@ page contentType=\"text/html;charset=UTF-8\" language=\"java\" %\u003e\n\u003c%@ page trimDirectiveWhitespaces=\"true\" %\u003e\n\u003c%@ taglib uri=\"http://java.sun.com/jsp/jstl/core\"              prefix=\"c\"\t    %\u003e\n\u003c%@ taglib uri=\"http://java.sun.com/jsp/jstl/functions\"         prefix=\"fn\"     %\u003e\n\u003c%@ taglib uri=\"http://java.sun.com/jsp/jstl/fmt\"               prefix=\"fmt\"    %\u003e\n\u003c%@ taglib uri=\"http://tiles.apache.org/tags-tiles\"             prefix=\"tiles\"  %\u003e\n\u003c%@ taglib uri=\"http://www.springframework.org/tags\"            prefix=\"spring\" %\u003e\n\u003c%@ taglib uri=\"http://www.springframework.org/security/tags\"   prefix=\"sec\"    %\u003e\n\u003cdiv class=\"panel panel-default\"\u003e\n    \u003cdiv class=\"panel-heading\"\u003e${title}\u003c/div\u003e\n    \u003cdiv class=\"panel-body\"\u003e\n        \u003ctable id=\"list\"\u003e\u003ctr\u003e\u003ctd\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n        \u003cdiv id=\"pager\"\u003e\u003c/div\u003e\n    \u003c/div\u003e\n\u003c/div\u003e\n\n\u003cscript type=\"text/javascript\"\u003e\n;require([\n    \"jquery\",\n    \"local\"\n],\nfunction($, LOCAL) { $(document).ready(function() {\n    var $grid = $(\"#list\");\n\n    $grid.jqGrid({\n        userCache: true,\n        caption: \"Sample List\",\n        multiselect: true,\n        url: LOCAL.url(\"/sample/records\"),\n        editurl: LOCAL.url(\"/sample/recordEdit\"),\n        cellurl: LOCAL.url(\"/sample/recordEdit\"),\n        height: 300,\n        colNames:['id', 'name', 'age', 'description'],\n        colModel:[\n            {name:'id', index:'id', hidden: true },\n            $.extend(true, {name:'name', index:'name', width:100, editable: true, frozen: true}, $.jqGridStatic.userSearchSetting),\n            $.extend(true, {name:'age', index:'name', width:100, editable: true, frozen: true }, $.jqGridStatic.userSearchSetting),\n            $.extend(true, {name:'description', index:'description', width:200, editable: true}, $.jqGridStatic.userSearchSetting)\n        ],\n        pager : \"#pager\",\n        sortname: \"name\",\n        sortorder: \"desc\",\n        loadComplete: function(data) {\n            var $t = $(this);\n            if ( data.rows.length \u003e 0 ) {\n                $t.setSelectedRowById($t.getDataIDs()[0]);\n            }\n        },\n        onSelectRow: function (id) {\n        },\n        ondblClickRow: function(id, iRow, iCol, e) {\n        }\n    }).jqGrid('navGrid', '#pager', {\n        edit: true,\n        add: true,\n        del: true,\n        search: true,\n        refresh: true\n    });\n\n    $grid.setGridParam({\n        datatype: \"json\"\n    }).trigger(\"reloadGrid\");\n});});\n\u003c/script\u003e\n\n```\n\n`[JAVA_SRC_HOME]/dd2/local/busi/sample/web/SampleController.java` 파일을 생성한다.\n``` java\npackage dd2.local.busi.sample.web;\n\nimport dd2.local.busi.com.web.CommonController;\nimport org.apache.commons.logging.Log;\nimport org.apache.commons.logging.LogFactory;\nimport org.springframework.stereotype.Controller;\nimport org.springframework.web.bind.annotation.RequestMapping;\n\n@Controller\n@RequestMapping(\"/sample/*\")\npublic class SampleController extends CommonController {\n    private static final Log logger = LogFactory.getLog(SampleController.class);\n    private static final String BASE_URL = \"sample/\";\n\n    @Override\n    protected String getBaseUrl() {\n        return BASE_URL;\n    }\n\n    ... 생략\n}\n```\n\n`@RequestMapping(\"/sample/*\")`을 선언 함으로 해서 해당 URL의 `/sample/`로 시작하는 모든\n\nURL은 SampleController.java가 호출된다.\n\nCommonController를 상속 받으면 getBaseUrl 메소드를 오버라이드 해줘야 한다.\n\nCommonController는 `@RequestMapping( value = \"comm/{tilesName}\")`, `@RequestMapping( value = \"comm/{tilesName}/{name}/{value}\") ` 두개지 형태의  URL을 처리해준다.\n\n`[JAVA_SRC_HOME]/dd2/local/busi/com/web/CommonController.java`를 참고한다.\n``` java\n... 생략\n\n/**\n * ../comm/{titles} 의 url 호출\n * @param request\n * @param model\n * @param tilesName\n * @return\n */\n@RequestMapping( value = \"comm/{tilesName}\")\npublic String doCommPage(\n        HttpServletRequest request,\n        ModelMap model,\n        @PathVariable(value = \"tilesName\") String tilesName\n) {\n    return getBaseUrl() + tilesName + \".defaultTpl\";\n}\n\n/**\n * ../comm/{titles}/{name}/{value} 의 url 호출\n * @param request\n * @param model\n * @param tilesName\n * @param name\n * @param value\n * @return\n */\n@RequestMapping( value = \"comm/{tilesName}/{name}/{value}\")\npublic String doCommPageAndSingleVar(\n        HttpServletRequest request,\n        ModelMap model,\n        @PathVariable(value = \"tilesName\") String tilesName,\n        @PathVariable(value = \"name\") String name,\n        @PathVariable(value = \"value\") String value\n) {\n    model.put(name, value);\n    return getBaseUrl() + tilesName + \".defaultTpl\";\n}\n\n... 생략\n```\n\n로컬에서 작업하면 `http://localhost:8080/sample/comm/list` URL로 제대로 호출되는지 확인한다.\n\n호출이 정상적으로 된다면 왼쪽 메뉴에 등록하자\n\n`[WEB_HOME]/WEB-INF/view/tiles/attribute/left.attr.jsp`에 URL을 추가한다.\n``` javascript\n\u003c%@ page contentType=\"text/html;charset=UTF-8\" language=\"java\" %\u003e\n\u003c%@ page trimDirectiveWhitespaces=\"true\" %\u003e\n\u003c%@ taglib uri=\"http://java.sun.com/jsp/jstl/core\"              prefix=\"c\"\t    %\u003e\n\u003c%@ taglib uri=\"http://java.sun.com/jsp/jstl/functions\"         prefix=\"fn\"     %\u003e\n\u003c%@ taglib uri=\"http://java.sun.com/jsp/jstl/fmt\"               prefix=\"fmt\"    %\u003e\n\u003c%@ taglib uri=\"http://tiles.apache.org/tags-tiles\"             prefix=\"tiles\"  %\u003e\n\u003c%@ taglib uri=\"http://www.springframework.org/tags\"            prefix=\"spring\" %\u003e\n\u003c%@ taglib uri=\"http://www.springframework.org/security/tags\"   prefix=\"sec\"    %\u003e\n\u003cdiv id=\"left-menu\" class=\"list-group\"\u003e\n\t... 생략\n    \u003ca class=\"list-group-item\" href=\"\u003cc:url value='/sample/comm/list/title/Sample' /\u003e\"\u003eSample\u003c/a\u003e\n\u003c/div\u003e\n\t... 생략\n```\n\n이제 왼쪽 메뉴에서도 접근 가능하다.\n\n`[JAVA_SRC_HOME]/dd2/local/busi/sample/service/dao/SampleDAO.java` 파일을 생성한다.\n``` java\npackage dd2.local.busi.sample.service.dao;\n\nimport dd2.com.dao.GenericDAO;\nimport dd2.com.jqgrid.JqGridRequest;\nimport dd2.com.jqgrid.JqGridResponseGeneric;\nimport dd2.local.entity.SampleEntity;\n\npublic interface SampleDAO extends GenericDAO\u003cSampleEntity, Long\u003e {\n    JqGridResponseGeneric\u003cSampleEntity\u003e list(JqGridRequest request);\n}\n```\n\nGenericDAO는 Hibernate를 편하게 사용하기 위한 기본적인 메소드를 정의 하고 있다.\n\n`[JAVA_SRC_HOME]/dd2/com/dao/GenericDAO.java`\n``` java\n... 생략\n\npublic interface GenericDAO\u003cT, ID extends Serializable\u003e {\n    T findById(ID id, boolean lock);\n    T findById(ID id);\n    List\u003cT\u003e findAll();\n    List\u003cT\u003e findByExample(T exampleInstance, String... excludeProperty);\n    T save(T entity);\n    T update(T entity);\n    T saveOrUpdate(T entity);\n    void delete(T entity);\n    void deleteById(ID id);\n    void deleteByIds(ID[] ids);\n    void flush();\n    void clear();\n}\n```\nGenericDAO는 인터페이스 이고 구현은 `[JAVA_SRC_HOME]/dd2/com/dao/GenericDAOHibernate.java` 에 구현되어 있다.\n\n`[JAVA_SRC_HOME]/dd2/local/busi/sample/service/dao/SampleDAOHibernate.java` 파일을 생성한다.\n``` java\n... 생략\n\n@Repository\n@SuppressWarnings(\"unchecked\")\npublic class SampleDAOHibernate extends GenericHibernateDAO\u003cSampleEntity, Long\u003e implements SampleDAO {\n    private static final Log logger = LogFactory.getLog(SampleDAOHibernate.class);\n\n    @Override\n    public JqGridResponseGeneric\u003cSampleEntity\u003e list(JqGridRequest request) {\n        JqGridResponseGeneric\u003cSampleEntity\u003e response = new JqGridResponseGeneric\u003c\u003e();\n\n        Criterion criterion = JqGridRestrictionForHibernate.create(SampleEntity.class, request);\n        Order order = JqGridOrderForHibernate.create(request);\n\n        Criteria rowCriteria = this.getCriteria()\n                .setProjection(Projections.projectionList()\n                        .add(Projections.rowCount())\n                );\n        if ( criterion != null ) {\n            rowCriteria = rowCriteria.add(criterion);\n        }\n        if ( order != null ) {\n            rowCriteria = rowCriteria.addOrder(order);\n        }\n        int total = Integer.parseInt(rowCriteria.uniqueResult().toString());\n\n\n        Criteria listCriteria = this.getCriteria();\n        if (criterion != null) {\n            listCriteria = listCriteria.add(criterion);\n        }\n        if (order != null) {\n            listCriteria = listCriteria.addOrder(order);\n        }\n        List\u003cSampleEntity\u003e sampleEntityList = listCriteria\n                .setFirstResult(request.getPageNumber() - 1)\n                .setMaxResults(request.getPageSize())\n                .list();\n\n        response.setTotal(total);\n        response.setPage(request.getPageNumber());\n        response.setRecords(request.getPageSize());\n        response.setRows(sampleEntityList);\n\n        return response;\n    }\n}\n```\n\nHibernate Criteria를 이용해서 작성한다. `JqGridRestrictionForHibernate`, `JqGridOrderForHibernate`\n유틸리티 클래스를 이용해서 작성을 편하게 했다.\n\n조회한 결과물은 JSON으로 리턴하기 위해 `JqGridResponseGeneric\u003cSampleEntity\u003e`에 담는다.\n\n`[JAVA_SRC_HOME]/dd2/local/busi/sample/service/dao/SampleService.java` 인터페이스 파일을 생성한다.\n``` java\npackage dd2.local.busi.sample.service;\n\nimport dd2.com.jqgrid.JqGridRequest;\nimport dd2.com.jqgrid.JqGridResponseGeneric;\nimport dd2.com.service.GenericService;\nimport dd2.local.entity.SampleEntity;\n\npublic interface SampleService extends GenericService\u003cSampleEntity, Long\u003e {\n    JqGridResponseGeneric\u003cSampleEntity\u003e list(JqGridRequest request);\n}\n```\n\n`[JAVA_SRC_HOME]/dd2/local/busi/sample/service/dao/SampleServiceHibernate.java` 구현 파일을 생성한다.\n``` java\n... 생략\n\n@Service\npublic class SampleServiceHibernate extends GenericHibernateService\u003cSampleEntity, Long\u003e implements SampleService  {\n    private static final Log logger = LogFactory.getLog(SampleServiceHibernate.class);\n\n    @Autowired\n    private SampleDAO sampleDAO;\n\n    @Override\n    protected GenericDAO\u003cSampleEntity, Long\u003e getGenericDAO() {\n        return sampleDAO;\n    }\n\n    @Transactional\n    @Override\n    public JqGridResponseGeneric\u003cSampleEntity\u003e list(JqGridRequest request) {\n        return sampleDAO.list(request);\n    }\n}\n```\n\nGenericHibernateService 클래스를 상속받음으로 기본적인 기능을 바로 사용할수 있다.\n```java\n...생략\n\npublic abstract class GenericHibernateService\u003cT, ID extends Serializable\u003e implements GenericService\u003cT, ID\u003e {\n\n    /**\n     * 기본 CRUD 가능하게 하는 엔티티 DAO 이다.\n     * 상속 받은 쪽에서 구현해 줘야 한다.\n     * @return\n     */\n    protected abstract GenericDAO\u003cT, ID\u003e getGenericDAO();\n\n    @Transactional\n    @Override\n    public T findById(ID id) {\n        return this.getGenericDAO().findById(id);\n    }\n\n    @Transactional\n    @Override\n    public T findById(ID id, boolean lock) {\n        return this.getGenericDAO().findById(id, lock);\n    }\n\n    @Transactional\n    @Override\n    public List\u003cT\u003e findAll() {\n        return this.getGenericDAO().findAll();\n    }\n\n    @Transactional\n    @Override\n    public List\u003cT\u003e findByExample(T exampleInstance, String... excludeProperty) {\n        return this.getGenericDAO().findByExample(exampleInstance, excludeProperty);\n    }\n\n    @Transactional\n    @Override\n    public T save(T entity) {\n        return this.getGenericDAO().save(entity);\n    }\n\n    @Transactional\n    @Override\n    public T update(T entity) {\n        return this.getGenericDAO().update(entity);\n    }\n\n    @Transactional\n    @Override\n    public T saveOrUpdate(T entity) {\n        return this.getGenericDAO().saveOrUpdate(entity);\n    }\n\n    @Transactional\n    @Override\n    public void delete(T entity) {\n        this.getGenericDAO().delete(entity);\n    }\n\n    @Transactional\n    @Override\n    public void deleteById(ID id) {\n        this.getGenericDAO().deleteById(id);\n    }\n\n    @Transactional\n    @Override\n    public void deleteByIds(ID[] ids) {\n        this.getGenericDAO().deleteByIds(ids);\n    }\n}\n```\n\nGenericHibernateService 클래스를 상속 받고 다시 SampleService 인터페이스를 상속받은 이유는 템플릿 메소드 패턴을\n\n이용해서 GenericHibernateDAO의 기능을 Controller 단까지 가기 위함이다.\n\n만약 GenericHibernateService 상속 받지 않는다면 엔티티의 간단한 CRUD를 Service 클래스 마다 구현해 주어야 한다.\n\n`[JAVA_SRC_HOME]/dd2/local/busi/sample/web/SampleController.java`를 수정한다.\n``` java\n... 생략\n\n@Controller\n@RequestMapping(\"/sample/*\")\npublic class SampleController extends CommonController {\n    private static final Log logger = LogFactory.getLog(SampleController.class);\n    private static final String BASE_URL = \"sample/\";\n\n    @Autowired\n    private SampleService sampleService;\n\n    @Override\n    protected String getBaseUrl() {\n        return BASE_URL;\n    }\n\n    @RequestMapping( value = \"records\", method = RequestMethod.POST )\n    public @ResponseBody\n    JqGridResponseGeneric\u003cSampleEntity\u003e records( @RequestBody JqGridRequest jqGridRequest ) {\n        return sampleService.list(jqGridRequest);\n    }\n\n    @RequestMapping( value = \"recordEdit\", method = RequestMethod.POST )\n    public @ResponseBody\n    Map\u003cString,Object\u003e recordEdit( @RequestBody Map\u003cString, Object\u003e params ) {\n        if ( params != null ) {\n            JqGridCrudUtil\u003cSampleEntity, Long\u003e\n                    jqgrid = new JqGridCrudUtil\u003c\u003e(SampleEntity.class, Long.class, params);\n\n            switch (jqgrid.getOper()) {\n                case ADD:\n                {\n                    SampleEntity newEntity = jqgrid.createEntityWithDataBinding();\n                    sampleService.save(newEntity);\n                }\n                break;\n                case EDIT:\n                {\n                    Long id = jqgrid.getId();\n                    SampleEntity entity = jqgrid.createEntityWithDataBinding();\n\n                    SampleEntity updateEntity = sampleService.findById(id);\n\n                    updateEntity.setAge(entity.getAge());\n                    updateEntity.setName(entity.getName());\n                    updateEntity.setDescription(entity.getDescription());\n\n                    sampleService.deleteById(id);\n                    sampleService.save(updateEntity);\n                }\n                break;\n                case DELETE:\n                {\n                    Long[] ids = jqgrid.getIds();\n                    sampleService.deleteByIds(ids);\n                }\n                break;\n                default: break;\n            }\n        }// if ( params != null ) {\n\n        return new HashMap\u003c\u003e();\n    }\n}\n```\n\nrecordEdit 메소드 안에서 Insert, Update, Delete를 모두 해준다.\n\nJqGridCrudUtil 클래스는 ajax JSON으로 받은 데이터를 SampleEntity로 변환하는 작업을 해준다.\n\n이렇게 함으로 해서 기본적인 그리드는 list 메소드만 구현을 하고 Insert, Update, Delete는 상위 클래스에\n\n구현해 놓을 것을 바로 사용할수 있다.\n\n다시 페이지를 호출하고 CRUD를 해보자!\n\n### @ModelAttribute, @SessionAttributs, RESTful\n\n웹 어플리케이션을 구현하다 보면 검증 작업을 프로트엔드(JavaScript)와 백엔드(Java) 두 곳에서 해줘야 한다.\n\n그런데 이 검증작업이 참 귀찮고 반복적인 작업이다. 스프링에서는 Spring Form tag와\n\n@ModelAttribute, @SessionAttributs을 이용하여 백엔드의 반복적인 검증 작업을 줄일수 있다. \n\n더불어 RESTful 방식으로 개발해 보자.\n\n참고: [@ModelAttribute, @SessionAttributs 이해](http://springmvc.egloos.com/535572),\n[RESTful API 설계](https://speakerdeck.com/leewin12/rest-api-seolgye),\n[Bean Validation](http://docs.spring.io/spring/docs/3.2.6.RELEASE/spring-framework-reference/htmlsingle/#validation-beanvalidation)\n\n`[JAVA_SRC_HOME]/dd2/local/entity/User.java` 파일 생성\n```Java\npackage dd2.local.entity;\n\nimport org.hibernate.validator.constraints.NotEmpty;\nimport org.springframework.format.annotation.DateTimeFormat;\n\nimport javax.validation.Valid;\nimport javax.validation.constraints.*;\nimport java.util.Date;\n\npublic class User {\n\n    @Size(min = 1, max = 10, message = \"이름은 1자 이상 10자 이하 입니다.\")\n    private String name;\n\n    @Pattern(regexp = \"^[1-9][0-9]{1,2}$\", message = \"나이에는 숫자만 올수 있습니다.\")\n    private String age;\n\n    @Pattern(regexp = \"^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\\\.[A-Za-z]{2,4}$\", message = \"유효하지 않은 이메일 입니다.\")\n    private String email;\n\n    @DateTimeFormat(pattern = \"yyyy-MM-dd\")\n    @NotNull(message = \"값을 입력해 주세요.\")\n    private Date createDate;\n\n    @NotEmpty(message = \"값을 입력해 주세요.\")\n    private String description;\n\n    public String getName() {\n        return name;\n    }\n\n    public void setName(String name) {\n        this.name = name;\n    }\n\n    public String getAge() {\n        return age;\n    }\n\n    public void setAge(String age) {\n        this.age = age;\n    }\n\n    public String getEmail() {\n        return email;\n    }\n\n    public void setEmail(String email) {\n        this.email = email;\n    }\n\n    public Date getCreateDate() {\n        return createDate;\n    }\n\n    public void setCreateDate(Date createDate) {\n        this.createDate = createDate;\n    }\n\n    public String getDescription() {\n        return description;\n    }\n\n    public void setDescription(String description) {\n        this.description = description;\n    }\n}\n```\n\n`[WEB_HOME]/WEB-INF/view/vaid` 폴더를 생성한다.\n`[WEB_HOME]/WEB-INF/view/vaid/user.jsp` 파일을 생성한다.\n``` javascript\n\u003c%@ page contentType=\"text/html;charset=UTF-8\" language=\"java\" %\u003e\n\u003c%@ page trimDirectiveWhitespaces=\"true\" %\u003e\n\u003c%@ taglib uri=\"http://java.sun.com/jsp/jstl/core\"              prefix=\"c\"      %\u003e\n\u003c%@ taglib uri=\"http://java.sun.com/jsp/jstl/functions\"         prefix=\"fn\"     %\u003e\n\u003c%@ taglib uri=\"http://java.sun.com/jsp/jstl/fmt\"               prefix=\"fmt\"    %\u003e\n\u003c%@ taglib uri=\"http://tiles.apache.org/tags-tiles\"             prefix=\"tiles\"  %\u003e\n\u003c%@ taglib uri=\"http://www.springframework.org/security/tags\"   prefix=\"sec\"    %\u003e\n\u003c%@ taglib uri=\"http://www.springframework.org/tags/form\"       prefix=\"form\"   %\u003e\n\n\u003cdiv class=\"panel panel-default\"\u003e\n    \u003cdiv class=\"panel-heading\"\u003eUser\u003c/div\u003e\n    \u003cdiv class=\"panel-body\"\u003e\n        \u003cform:form modelAttribute=\"user\" method=\"POST\"\u003e\n        \u003cdiv class=\"row\"\u003e\n            \u003cdiv class=\"col-md-12\"\u003e\n                \u003cform:input path=\"name\" name=\"name\" cssClass=\"form-control\" placeholder=\"name\" /\u003e\n                \u003cform:errors path=\"name\" cssStyle=\"color: #ff0000\"/\u003e\n                \u003cp\u003e\u003c/p\u003e\n            \u003c/div\u003e\n        \u003c/div\u003e\n        \u003cdiv class=\"row\"\u003e\n            \u003cdiv class=\"col-md-12\"\u003e\n                \u003cform:input path=\"age\" name=\"age\" cssClass=\"form-control\" placeholder=\"age\" /\u003e\n                \u003cform:errors path=\"age\" cssStyle=\"color: #ff0000\"/\u003e\n                \u003cp\u003e\u003c/p\u003e\n            \u003c/div\u003e\n        \u003c/div\u003e\n        \u003cdiv class=\"row\"\u003e\n            \u003cdiv class=\"col-md-12\"\u003e\n                \u003cform:input path=\"email\" name=\"email\" cssClass=\"form-control\" placeholder=\"email\" /\u003e\n                \u003cform:errors path=\"email\" cssStyle=\"color: #ff0000\"/\u003e\n                \u003cp\u003e\u003c/p\u003e\n            \u003c/div\u003e\n        \u003c/div\u003e\n        \u003cdiv class=\"row\"\u003e\n            \u003cdiv class=\"col-md-12\"\u003e\n                \u003cform:input path=\"createDate\" name=\"createDate\" cssClass=\"form-control\" placeholder=\"createDate\" /\u003e\n                \u003cform:errors path=\"createDate\" cssStyle=\"color: #ff0000\"/\u003e\n                \u003cp\u003e\u003c/p\u003e\n            \u003c/div\u003e\n        \u003c/div\u003e\n        \u003cdiv class=\"row\"\u003e\n            \u003cdiv class=\"col-md-12\"\u003e\n                \u003cform:input path=\"description\" name=\"description\" cssClass=\"form-control\" placeholder=\"description\" /\u003e\n                \u003cform:errors path=\"description\" cssStyle=\"color: #ff0000\"/\u003e\n                \u003cp\u003e\u003c/p\u003e\n            \u003c/div\u003e\n        \u003c/div\u003e\n        \u003cdiv class=\"row\"\u003e\n            \u003cdiv class=\"col-md-2\"\u003e\u003c/div\u003e\n            \u003cdiv class=\"col-md-10 text-right\"\u003e\n                \u003cbutton id=\"POST\"    class=\"btn btn-default btn-primary\"\u003e등록\u003c/button\u003e\n                \u003cbutton id=\"PUT\"     class=\"btn btn-default btn-primary\"\u003e수정\u003c/button\u003e\n                \u003cbutton id=\"DELETE\"  class=\"btn btn-default btn-primary\"\u003e삭제\u003c/button\u003e\n            \u003c/div\u003e\n        \u003c/div\u003e\n        \u003c/form:form\u003e\n    \u003c/div\u003e\n\u003c/div\u003e\n\u003cscript type=\"text/javascript\"\u003e\n;require([\n    \"jquery\",\n    \"local\"\n],\nfunction($, LOCAL) { $(document).ready(function() {\n    var $form = $(\"#user\");\n\n    $(\"button\").on(\"click\", function (event) {\n        var $self = $(this);\n        var methodName = $self.prop(\"id\");\n\n        var $methods = $form.find(\"input[type=hidden][name=_method]\");\n        if ( $methods.length \u003e 0 ) {\n            $methods.each(function(index, element) {\n                $self = $(this);\n                $self.val(methodName);\n            });\n        } else {\n            $(\"\u003cinput /\u003e\")\n                    .prop(\"type\", \"hidden\")\n                    .prop(\"name\", \"_method\")\n                    .prop(\"value\", methodName)\n                    .appendTo($form);\n        }\n        $form.submit();\n    });\n});});\n\u003c/script\u003e\n```\n\n`[WEB_HOME]/WEB-INF/view/valid/result.jsp` 파일을 생성한다.\n``` javascript\n\u003c%@ page contentType=\"text/html;charset=UTF-8\" language=\"java\" %\u003e\n\u003c%@ page trimDirectiveWhitespaces=\"true\" %\u003e\n\u003c%@ taglib uri=\"http://java.sun.com/jsp/jstl/core\"              prefix=\"c\"\t    %\u003e\n\u003c%@ taglib uri=\"http://java.sun.com/jsp/jstl/functions\"         prefix=\"fn\"     %\u003e\n\u003c%@ taglib uri=\"http://java.sun.com/jsp/jstl/fmt\"               prefix=\"fmt\"    %\u003e\n\u003c%@ taglib uri=\"http://tiles.apache.org/tags-tiles\"             prefix=\"tiles\"  %\u003e\n\u003c%@ taglib uri=\"http://www.springframework.org/security/tags\"   prefix=\"sec\"    %\u003e\n\u003c%@ taglib uri=\"http://www.springframework.org/tags\"            prefix=\"spring\" %\u003e\n\u003c%@ taglib uri=\"http://www.springframework.org/tags/form\"       prefix=\"form\"   %\u003e\n\n\u003cdiv class=\"panel panel-default\"\u003e\n    \u003cdiv class=\"panel-heading\"\u003eUser Info (\u003cspring:eval expression=\"method\" /\u003e)\u003c/div\u003e\n    \u003cdiv class=\"panel-body\"\u003e\n        \u003cdiv class=\"row\"\u003e\n            \u003cdiv class=\"col-md-12\"\u003e\n                \u003cspring:eval expression=\"user.name\" /\u003e\n                \u003cp\u003e\u003c/p\u003e\n            \u003c/div\u003e\n        \u003c/div\u003e\n        \u003cdiv class=\"row\"\u003e\n            \u003cdiv class=\"col-md-12\"\u003e\n                \u003cspring:eval expression=\"user.age\" /\u003e\n                \u003cp\u003e\u003c/p\u003e\n            \u003c/div\u003e\n        \u003c/div\u003e\n        \u003cdiv class=\"row\"\u003e\n            \u003cdiv class=\"col-md-12\"\u003e\n                \u003cspring:eval expression=\"user.email\" /\u003e\n                \u003cp\u003e\u003c/p\u003e\n            \u003c/div\u003e\n        \u003c/div\u003e\n        \u003cdiv class=\"row\"\u003e\n            \u003cdiv class=\"col-md-12\"\u003e\n                \u003cspring:eval expression=\"user.createDate\" /\u003e\n                \u003cp\u003e\u003c/p\u003e\n            \u003c/div\u003e\n        \u003c/div\u003e\n        \u003cdiv class=\"row\"\u003e\n            \u003cdiv class=\"col-md-12\"\u003e\n                \u003cspring:eval expression=\"user.description\" /\u003e\n                \u003cp\u003e\u003c/p\u003e\n            \u003c/div\u003e\n        \u003c/div\u003e\n        \u003cdiv class=\"row\"\u003e\n            \u003cdiv class=\"col-md-2\"\u003e\u003c/div\u003e\n            \u003cdiv class=\"col-md-10 text-right\"\u003e\n            \u003c/div\u003e\n        \u003c/div\u003e\n    \u003c/div\u003e\n\u003c/div\u003e\n```\n\n`[WEB_HOME]/WEB-INF/web.xml` 파일에 아래의 내용을 추가한다.\n```xml\n\u003c!-- Spring HiddenHttpMethodFilter --\u003e\n\u003cfilter\u003e\n    \u003cfilter-name\u003ehttpMethodFilter\u003c/filter-name\u003e\n    \u003cfilter-class\u003eorg.springframework.web.filter.HiddenHttpMethodFilter\u003c/filter-class\u003e\n\u003c/filter\u003e\n\u003cfilter-mapping\u003e\n    \u003cfilter-name\u003ehttpMethodFilter\u003c/filter-name\u003e\n    \u003curl-pattern\u003e/*\u003c/url-pattern\u003e\n\u003c/filter-mapping\u003e\n```\n\nHiddenHttpMethodFilter를 설정해 줌으로 해서 GET, POST만 지원하는 환경에서도 PUT, DELETE를 사용할수\n\n있게 해준다.\n\n\n`[JAVA_SRC_HOME]/dd2/local/busi/valid/web/ValidController.java` 파일을 생성한다.\n``` java\npackage dd2.local.busi.valid.web;\n\nimport dd2.local.busi.com.web.CommonController;\nimport dd2.local.entity.User;\nimport org.apache.commons.logging.Log;\nimport org.apache.commons.logging.LogFactory;\nimport org.springframework.stereotype.Controller;\nimport org.springframework.ui.ModelMap;\nimport org.springframework.validation.BindingResult;\nimport org.springframework.web.bind.annotation.ModelAttribute;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RequestMethod;\nimport org.springframework.web.bind.annotation.SessionAttributes;\nimport org.springframework.web.bind.support.SessionStatus;\n\nimport javax.validation.Valid;\nimport java.util.Date;\n\n@RequestMapping(value = \"/valid/user\")\n@SessionAttributes(value = \"user\")\n@Controller\npublic class ValidController extends CommonController {\n    private static final Log logger = LogFactory.getLog(ValidController.class);\n    private static final String BASE_URL = \"valid/\";\n\n    @Override\n    protected String getBaseUrl() {\n        return BASE_URL;\n    }\n\n\n    /**\n     * @ModelAttribute(\"user\")\n     *  어노테이션을 메소드에 선언하면 해당 클래스이 메소드들이 리턴을 할때 usr() 메소드 결과를 호출한다.\n     *  만약 파라미터로 받는다면 파라리터로 받는게 우선한다.\n     *\n     * @return\n     */\n    @ModelAttribute(\"user\")\n    public User user() {\n        User user = new User();\n        user.setName(\"Sample User\");\n        user.setAge(\"\");\n        user.setEmail(\"sample@sample.co.kr\");\n        user.setCreateDate(new Date());\n        user.setDescription(\"Description\");\n\n        return user;\n    }\n\n\n    @RequestMapping(method = RequestMethod.GET)\n    public String getUser(ModelMap model) {\n        if ( logger.isInfoEnabled() ) {\n            logger.info(\"getUser\");\n        }\n\n        return getBaseUrl() + \"user.defaultTpl\";\n    }\n\n    @RequestMapping(method = RequestMethod.POST)\n    public String postUser(@Valid @ModelAttribute User user,\n                           BindingResult bindingResult,\n                           ModelMap model,\n                           SessionStatus status) {\n        if ( logger.isInfoEnabled() ) {\n            logger.info(\"postUser\");\n        }\n\n        model.put(\"method\", \"POST\");\n\n        if ( bindingResult.hasErrors() ) {\n            if ( logger.isDebugEnabled() ) {\n                logger.debug(\"검증 에러 발생\");\n            }\n            return getBaseUrl() + \"user.defaultTpl\";\n        }\n\n        // 저장성공 했을때\n        status.setComplete();\n        return getBaseUrl() + \"result.defaultTpl\";\n    }\n\n    @RequestMapping(method = RequestMethod.PUT)\n    public String putUser(@Valid @ModelAttribute User user,\n                          BindingResult bindingResult,\n                          ModelMap model,\n                          SessionStatus status) {\n        if ( logger.isInfoEnabled() ) {\n            logger.info(\"putUser\");\n        }\n\n        model.put(\"method\", \"PUT\");\n\n        if ( bindingResult.hasErrors() ) {\n            if ( logger.isDebugEnabled() ) {\n                logger.debug(\"검증 에러 발생\");\n            }\n            return getBaseUrl() + \"user.defaultTpl\";\n        }\n\n        // 수정 성공했을때\n        status.setComplete();\n        return getBaseUrl() + \"result.defaultTpl\";\n    }\n\n    @RequestMapping(method = RequestMethod.DELETE)\n    public String deleteUser(@Valid @ModelAttribute User user,\n                             BindingResult bindingResult,\n                             ModelMap model,\n                             SessionStatus status) {\n        if ( logger.isInfoEnabled() ) {\n            logger.info(\"deleteUser\");\n        }\n\n        model.put(\"method\", \"DELETE\");\n\n        if ( bindingResult.hasErrors() ) {\n            if ( logger.isDebugEnabled() ) {\n                logger.debug(\"검증 에러 발생\");\n            }\n            return getBaseUrl() + \"user.defaultTpl\";\n        }\n\n        // 삭제 성공했을때\n        status.setComplete();\n        return getBaseUrl() + \"result.defaultTpl\";\n    }\n}\n```\n\n\n\n\n\n## 마치며\n\n### 마치며\n\n읽어 주셔서 감사합니다.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdaejoon%2Flets-ko","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdaejoon%2Flets-ko","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdaejoon%2Flets-ko/lists"}