반응형

이제서야 OSGi 에 관심이 생겨서 뭔가를 만들어보기 위해서 OSGi + Tomcat 6 + SpringDM[각주:1] 개발 환경을 구성해 봅니다.


Eclipse Indigo + Maven Integration for Eclipse





Create Project


File - New - Other... → Maven - Maven Project




[maven-archetype-quickstart] 선택합니다.



[Group Id], [Artifact Id] 아무거나 입력 합니다. -_-;;


[Package] 는 필요가 없습니다.





Create Target Definition


File - New - Other... → Plug-in Development - Target Definition



[Parent Folder]는 전에 만든 "spring-dm-platform" 프로젝트를 선택합니다.


File name: spring-dm-tomcat.target

Initalize the target definition with: Nothing



lib 폴더를 하나 생성합니다.


target 폴더는 메이븐 라이브러리가 복사되는 곳이고, lib 폴더는 메이븐으로 구하지 못한 라이브러를 넣어둘 것입니다.



spring-dm-tomcat.target 파일을 열어 편집합니다.


Target: SpringDM with Tomcat


오른쪽 Add... 버튼을 클릭해서 Locations 을 추가합니다.



Directory 선택



Variables.. 를 이용해서 현재 프로젝트 경로(project_loc)를 얻어내고 하위 폴더 target 을 입력합니다. (그냥 입력해도 됨..)



같은 방법으로 lib 폴더도 선택합니다.



파일 저장 후 Windows - Preferences - Plug-in Development - Target Platform


SpringDM with Tomcat 을 선택합니다.






Running OSGi Framework


이제 한번 실행시켜 봅시다.


Run - Run Configurations...



왼쪽 메뉴 중에 OSGi Framework 에서 오른쪽 버튼 클릭 - New



이름 적당히 지어 주시고 Run 을 클릭해 봅시다.



org.eclipse.osgi 플러그인이 없다고 에러가 납니다.





Setting Osgi Framework on Maven


이제 메이븐으로 OSGi Framework 라이브러리를 세팅합시다.


pom.xml 파일을 열어 아래와 같이 수정 합니다.



메이븐에 대해서 설명하지는 않겠습니다. -_-;;


위의 라이브러리 저장소(repository)는 SpringSource Repository(FAQ)에서 확인할 수 있습니다.


이제 SpringSource Repository에서 번들(라이브러리)를 하나씩 찾아서 넣어야 합니다. ㅠ_ㅠ;;


검색하는 방법은 [Advanced Search]나 오른쪽 상단 [Quick Search] 을 이용해서 검색하면 됩니다.



일단 OSGi Framework 실행 했을 때 없다고한 플러그인 라이브러리를 검색해봅시다.


검색해서 나온 버전 중 가장 최신 버전을 선택합니다.



중간에 보면 Maven 부분이 있습니다. 이걸 복사해서 <dependencies> </dependencies> 사이에 붙여넣기 하면 됩니다. ㅎㅎ



<dependencies>

<!-- 라이브러리 설정 -->

<dependency>

<groupId>org.eclipse.osgi</groupId>

<artifactId>org.eclipse.osgi</artifactId>

<version>3.7.1.R37x_v20110808-1106</version>

</dependency>

</dependencies>


저장 후 pom.xml 파일에서 오른쪽버튼 클릭 후 Run As - Maven install 클릭 합니다.



그러면 target 폴더에 jar 파일이 복사가 됩니다.



이제 spring-dm-tomcat.target 파일을 열어봅시다.


아래와 같이 번들이 추가 되어있습니다.


※ 메이븐을 이용해서 빠르게 라이브러리를 바꿔치기 하다보면 번들(라이브러리)이 갱신이 안될 때가 있는데 그냥 파일 닫았다가 다시 열어주면 갱신이 되더군요....



맨위의 타이틀 오른쪽에 "Set as Target Platform"을 클릭해야 실제 적용이 됩니다. 주의!



이제 다시 Run Configurations 로 가봅시다. 추가한 번들이 올라와 있습니다. 선택 후 Run!



콘솔 창을 보면 OSGi Framework 가 실행되었습니다. ㅠ_ㅠ



이제 OSGi 할 준비가 된 것입니다. ㅋㅋ


톰켓이나 웹로직 같이 끄려면 빨간 버튼(Terminate)을 이용해서 끄면 됩니다.


나중에 톰켓를 띄우게 될텐데 OSGi Framework 한번 실행한 상태에서 한번 실행하면 톰켓 포트 충돌로 인한 에러를 볼 수 있습니다.




Setting SpringDM on Maven


바로 SpringDM을 적용해 봅시다!!


SpringSource Repository 에서 "spring", "spring-osgi", "slf4j" 를 검색해서 최신 버전을 적용 합니다.



이제 pom.xml 에서 마우스 오른쪽 버튼 - Run As - Maven install 해서 jar 파일을 target 에 생기게 합니다.


그리고 spring-dm-tomcat.target 파일을 열어서 ${project_loc}/target 폴더에 플러그인 갯수가 늘어났는지 확인합니다.


만약 파일은 존재하는데 변경이 되지 안았다면 spring-dm-tomcat.target 파일 닫고 바로 다시 열어주면 카운트가 변경 되어 있을 겁니다.



상단 오른쪽의 "Set as Target Platform" 링크를 클릭하여 라이브러리를 적용시켜 줍니다.



Run Configurations 창으로 가서 추가된 번들(또는 라이브러리)를 선택합니다.


그 후 오른쪽 아래 "Validate Bundles" 버튼을 클릭합니다. 버튼명 그대로 번들들의 유효성을 검사하게 됩니다.



아래와 같이 어떤 라이브러리에서 필요한 라이브러리가 빠져있는지 나옵니다.



웹에 관련된 부분은 바로 이어서 할 것이므로 일단 이 번들은 빼고 구동(Run)합니다.



아래와 같이 나오면 성공! 웹쪽을 뺀 SpringDM 까지 올라갔습니다.


osgi> log4j:WARN No appenders could be found for logger (org.springframework.osgi.extender.internal.activator.ContextLoaderListener).

log4j:WARN Please initialize the log4j system properly.

log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.


osgi> ss


Framework is launched.


id State       Bundle

0 ACTIVE      org.eclipse.osgi_3.7.1.R37x_v20110808-1106

1 ACTIVE      org.springframework.transaction_3.2.1.RELEASE

2 ACTIVE      org.springframework.osgi.core_1.2.1

3 ACTIVE      org.springframework.osgi.extensions.annotations_1.2.1

4 ACTIVE      com.springsource.org.apache.log4j_1.2.16

5 ACTIVE      org.springframework.jdbc_3.2.1.RELEASE

6 ACTIVE      org.springframework.oxm_3.2.1.RELEASE

7 ACTIVE      org.springframework.aspects_3.2.1.RELEASE

8 ACTIVE      com.springsource.org.apache.commons.logging_1.1.1

9 ACTIVE      org.springframework.context.support_3.2.1.RELEASE

10 ACTIVE      org.springframework.core_3.2.1.RELEASE

11 ACTIVE      org.springframework.beans_3.2.1.RELEASE

12 ACTIVE      org.springframework.context_3.2.1.RELEASE

13 ACTIVE      org.springframework.aop_3.2.1.RELEASE

14 RESOLVED    com.springsource.slf4j.log4j_1.6.1

           Master=17

15 ACTIVE      org.springframework.expression_3.2.1.RELEASE

16 ACTIVE      org.springframework.orm_3.2.1.RELEASE

17 ACTIVE      com.springsource.slf4j.api_1.6.1

           Fragments=14

18 ACTIVE      com.springsource.org.aopalliance_1.0.0

19 ACTIVE      org.springframework.osgi.extender_1.2.1

20 ACTIVE      org.springframework.osgi.io_1.2.1


osgi> 




Configuration Log4j


OSGi Framework 를 실행 하면 아래와 같은 경고가 나오게 되는데 흔하디 흔한 log4j.properties / log4j.xml 파일 못 찾는 경고입니다.


log4j:WARN No appenders could be found for logger (org.springframework.osgi.extender.internal.activator.ContextLoaderListener).

log4j:WARN Please initialize the log4j system properly.

log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.


log4j.xml


첨부한 log4j.xml 파일을 프로젝트 폴더에 복사합니다.



Run Configurations 창의 Arguments 탭으로 이동합니다.


VM arguments 부분에 -Dlog4j.debug=true -Dlog4j.configuration=file:log4j.xml 를 추가합니다.


Working directory 부분은 현재 자기의 프로젝트 경로로 바꿔줍니다.



Workspace... 버튼 클릭 후 spring-dm-platform 프로젝트 폴더를 선택하면 됩니다.



이제 OSGi Framework 를 실행하면 로그가 출력 됩니다.


osgi> log4j: Using URL [file:log4j.xml] for automatic log4j configuration.

log4j: Preferred configurator class: org.apache.log4j.xml.DOMConfigurator

log4j: System property is :null

log4j: Standard DocumentBuilderFactory search succeded.

log4j: DocumentBuilderFactory is: com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl

log4j: debug attribute= "null".

log4j: Ignoring debug attribute.

log4j: reset attribute= "false".

log4j: Threshold ="null".

log4j: Retreiving an instance of org.apache.log4j.Logger.

log4j: Setting [org.springframework.osgi.web.tomcat.internal] additivity to [false].

log4j: Level value for org.springframework.osgi.web.tomcat.internal is  [debug].

log4j: org.springframework.osgi.web.tomcat.internal level set to DEBUG

log4j: Class name: [org.apache.log4j.ConsoleAppender]

log4j: Parsing layout of class: "org.apache.log4j.PatternLayout"

log4j: Setting property [conversionPattern] to [%d _ %t _ %p _ %c _ %m%n].

log4j: Adding appender named [console] to category [org.springframework.osgi.web.tomcat.internal].

log4j: Retreiving an instance of org.apache.log4j.Logger.

log4j: Setting [org.springframework] additivity to [false].

log4j: Level value for org.springframework is  [info].

log4j: org.springframework level set to INFO

log4j: Adding appender named [console] to category [org.springframework].

log4j: Level value for root is  [warn].

log4j: root level set to WARN

log4j: Adding appender named [console] to category [root].

2013-03-27 00:54:43,505 _ Start Level Event Dispatcher _ INFO

_ org.springframework.osgi.extender.internal.activator.ContextLoaderListener

_ Starting [org.springframework.osgi.extender] bundle v.[1.2.1]

2013-03-27 00:54:43,545 _ Start Level Event Dispatcher _ INFO

_ org.springframework.osgi.extender.internal.support.ExtenderConfiguration

_ No custom extender configuration detected; using defaults...

2013-03-27 00:54:43,550 _ Start Level Event Dispatcher _ INFO

_ org.springframework.scheduling.timer.TimerTaskExecutor _ Initializing Timer


osgi> 


여기까지 했으면 이제 웹을 제외한 SpringDM을 사용할 준비가 되었습니다.




Setting Web Platform


이제 거의 다 왔습니다.... 후우~.. 이제 웹도 되게 합시다!



Run Configurations 창에서 전 단계에서 선택하지 않았던 4개의 웹 관련 번들을 선택 후 검사합니다. (Validate Bundles 버튼)



아래에 나온 없는 패키지가 포함된 번들을 찾아줘야 합니다.



일단 서블릿 2.4 버전을 찾아서 넣어줍시다.



이렇게 추가된 라이브러리를 적용한 후 다시 Run Configurations 창을 엽니다.


Add Required Bundles 버튼을 클릭하면 필요한 라이브러리를 자동으로 선택 해줍니다. (만약 있다면..)



이제 번들 유효성 검사를 해보면 아무 이상이 없다고 합니다.



실행! 잘 되는듯 하다가 에러가 나는군요...


2013-03-28 23:20:49,878 _ WebExtender-Init _ INFO

_ org.springframework.osgi.web.extender.internal.activator.WarListenerConfiguration

_ No custom extender configuration detected; using defaults...

Exception in thread "WebExtender-Init" java.lang.NoClassDefFoundError: org/apache/catalina/Loader

at o.s.osgi.web.extender.internal.activator.WarListenerConfiguration.createDefaultWarDeployer(WarListenerConfiguration.java:194)

at org.springframework.osgi.web.extender.internal.activator.WarListenerConfiguration.<init>(WarListenerConfiguration.java:105)

at org.springframework.osgi.web.extender.internal.activator.WarLoaderListener$1.run(WarLoaderListener.java:361)

at java.lang.Thread.run(Unknown Source)

Caused by: java.lang.ClassNotFoundException: org.apache.catalina.Loader

at org.eclipse.osgi.internal.loader.BundleLoader.findClassInternal(BundleLoader.java:513)

at org.eclipse.osgi.internal.loader.BundleLoader.findClass(BundleLoader.java:429)

at org.eclipse.osgi.internal.loader.BundleLoader.findClass(BundleLoader.java:417)

at org.eclipse.osgi.internal.baseadaptor.DefaultClassLoader.loadClass(DefaultClassLoader.java:107)

at java.lang.ClassLoader.loadClass(Unknown Source)

... 4 more


톰켓 번들을 설치합시다.


내용이 너무 길어저 pom.xml 만 남깁니다.



번들(라이브러리) 적용 후 우효성 검사를 하면 이상이 없다고 합니다.


보면 서블릿 번들이이 2.4/2.5 두개가 있는데 이중에 한가지만 선택하면 되겠습니다.



이제 실행해 봅시다.


WebExtender-Init _ ERROR _ o.s.osgi.web.deployer.tomcat.TomcatWarDeployer _ No Catalina Service found, bailing out

o.s.osgi.service.ServiceUnavailableException: service matching filter=[(objectClass=org.apache.catalina.Service)] unavailable


WebExtender-Init _ ERROR _ o.s.osgi.web.extender.internal.activator.WarLoaderListener _ Cannot property start Spring DM WebExtender; stopping bundle...

o.s.osgi.OsgiException: Cannot create Tomcat deployer

Caused by: o.s.osgi.service.ServiceUnavailableException: service matching filter=[(objectClass=org.apache.catalina.Service)] unavailable


WebExtender-Init _ INFO _ org.springframework.scheduling.timer.TimerTaskExecutor _ Cancelling Timer


org.apache.catalina.Service 서비스를 찾을 수 없다. 즉 톰켓이 안 떠있다는 겁니다.


catalina.start.osgi-6.0.16-20080327.121306-4.jar


첨부한 번들을 lib 폴더에 복사해주고 적용 시킵니다. (이건 메이븐에 없더군요...)



마지막으로 실행!


Tomcat Catalina Start Thread _ INFO _ o.s.osgi.web.tomcat.internal.Activator _ Starting Apache Tomcat/6.0.32 ...

Tomcat Catalina Start Thread _ DEBUG _ o.s.osgi.web.tomcat.internal.Activator _ Reading default server configuration from  bundleresource://45.fwk1110027070/conf/embedded-server-defaults.properties

2013. 3. 28 오후 11:40:53 org.apache.catalina.startup.Embedded start

정보: Starting tomcat server

2013. 3. 28 오후 11:40:53 org.apache.catalina.core.StandardEngine start

정보: Starting Servlet Engine: Apache Tomcat/6.0.32

2013. 3. 28 오후 11:40:53 org.apache.coyote.http11.Http11Protocol init

정보: Initializing Coyote HTTP/1.1 on http-127.0.0.1-8080

2013. 3. 28 오후 11:40:53 org.apache.coyote.http11.Http11Protocol start

정보: Starting Coyote HTTP/1.1 on http-127.0.0.1-8080

Tomcat Catalina Start Thread _ INFO _ o.s.osgi.web.tomcat.internal.Activator _ Succesfully started Apache Tomcat/6.0.32

WebExtender-Init _ INFO _ o.s.osgi.service.importer.support.OsgiServiceProxyFactoryBean _ Found mandatory OSGi service for bean []

Tomcat Catalina Start Thread _ INFO _ o.s.osgi.web.tomcat.internal.Activator _ Published Apache Tomcat/6.0.32 as an OSGi service

WebExtender-Init _ INFO _ o.s.osgi.web.deployer.tomcat.TomcatWarDeployer _ Found service Catalina


후우... 성공!


web.xml


마지막으로 첨부한 web.xml 파일을 conf/web.xml 에 복사합니다. (그냥 톰켓에 있는 web.xml 입니다)



이 파일이 있어야 나중에 웹어플 배치했을 때 정상적으로 됩니다.


안그러면 배치했을 때 아래 로그와 함께 영원히 404를 볼 수 있습니다.


[ INFO - WebExtender-Init] org.springframework.osgi.web.extender.internal.activator.WarLoaderListener - hello.web (hello.web) is a WAR, scheduling war deployment on context path [/hello.web] (web.xml found at [bundleentry://11.fwk854535264/WEB-INF/web.xml])

2013. 3. 30 오전 2:03:35 org.apache.catalina.startup.ContextConfig defaultWebConfig

정보: No default web.xml

[ INFO - Timer-0] org.springframework.osgi.web.deployer.tomcat.TomcatWarDeployer - Successfully deployed bundle [hello.web (hello.web)] at [/hello.web] on server org.apache.catalina.startup.Embedded/1.0




Trouble Shooting


설정을 바꿨는데도 안바뀐다면?


Run Configurations 의 Settings 탭에서 "Clear the configuration area before launching"를 체크해주세요.



중간 중간 아래와 같은 에러가 난다면? 하라는 대로 하면 됩니다. -_-;;





마지막으로 최종 pom.xml 파일 첨부합니다.


pom.xml



  1. 구) Spring OSGi / 신) Spring Dynamic Modules [본문으로]
반응형

'Framework > Spring OSGi' 카테고리의 다른 글

SpringDM Test Project - hello.osgi  (0) 2013.04.07
//
반응형
출처 : http://onjava.com/lpt/a/6443



스프링 XML Configuration을 위한 12가지 최선의 실천사항들

by Jason Zhicheng Li
01/25/2006

Spring is a powerful Java application framework, used in a wide range of Java applications. It provides enterprise services to Plain Old Java Objects (POJOs). Spring uses dependency injection to achieve simplification and increase testability.property name="shippedBy" value="lizjason Spring beans, dependencies, and the services needed by beans are specified in configuration files, which are typically in an XML format. The XML configuration files, however, are verbose and unwieldy. They can become hard to read and manage when you are working on a large project where many Spring beans are defined.

스프링은 Java 애플리케이션의 많은 범위에서 사용하고 있는 강력한 Java 애플리케이션 프레임워크입니다. 이것은 POJO(Plan Old Java Objects)에 엔터프라이즈 서비스의 힘을 제공합니다. 스프링은 간결함과 테스트 용이성을 이루기 위해 의존성 주입(dependency injection)을 이용합니다. 스프링 빈들과 의존성 그리고 빈들이 필요한 서비스들은 configuration 파일들에 설정하는데 일반적으로 XML 형식으로 되어 있습니다. 어쨌거나 이 XML configuration 파일들은 장황하고 다루기 쉽지 않습니다. 큰 프로젝트에서 스프링 빈을 많이 정의할 경우 이 configuration은 읽기도 어렵고 관리하기도 어려워집니다.

In this article, I will show you 12 best practices for Spring XML configurations. Some of them are more necessary practices than best practices. Note that other factors, such as domain model design, can impact the XML configuration, but this article focuses on the XML configuration's readability and manageability.

이 기사에서 스프링 XML configuration에 대한 12가지 최선의 실천사항들을 소개할 것입니다.  그 중 일부는 최선의 실천사항이라기 보다는 필수 실천사항들입니다. 도메인 모델 설계같은 다른 요소의 영향도 고려해야 하지만 이 기사에서는 configuration의 가독성(readability)과 관리편의성(manageablitity)에 초점을 두었습니다.


1. Avoid using autowiring
Spring can autowire dependencies through introspection of the bean classes so that you do not have to explicitly specify the bean properties or constructor arguments. Bean properties can be autowired either by property names or matching types. Constructor arguments can be autowired by matching types. You can even specify the autodetect autowiring mode, which lets Spring choose an appropriate mechanism. As an example, consider the following:

스프링은 빈의 introspection을 통해 의존관계를 자동으로 묶을(autowirie) 수 있어 명시적으로 빈의 프로퍼티나 생성자 아규먼트를 지정하지 않아도 됩니다. 프로퍼티 이름이나 타입 매칭으로 빈의 프로퍼티를 자동 설정할 수 있습니다. 타입 패칭으로 생성자의 아규먼트를 자동 설정할 수 있습니다. 또한 자동탐지를 autowiring 모드로 지정하여 스프링이 적절한 메커니즘을 선택하게 할 수 있습니다. 다음 예제를 보십시오.

<bean id="orderService" class="com.lizjason.spring.OrderService" autowire="byName"/>

The property names of the OrderService class are used to match a bean instance in the container. Autowiring can potentially save some typing and reduce clutter. However, you should not use it in real-world projects because it sacrifices the explicitness and maintainability of the configurations. Many tutorials and presentations tout autowiring as a cool feature in Spring without mentioning this implication. In my opinion, like object-pooling in Spring, it is more a marketing feature. It seems like a good idea to make the XML configuration file smaller, but this will actually increase the complexity down the road, especially when you are working on a large project where many beans are defined. Spring allows you mix autowiring and explicit wiring, but the inconsistency will make the XML configurations even more confusing.

컨테이너에서 OrderService 클래스의 프로퍼티 이름으로 빈 인스턴스를 매치하도록 사용하고 있습니다. Autowiring은 어쩌면 타이핑하는 수고를 줄이고 난잡함을 줄일 수 있습니다.  그러나 이것은 configuration의 명확성과 유지보수성을 희생하기에 실제 프로젝트에서 사용하면 안됩니다. 많은 튜토리얼과 프리젠테이션에서 이러한 숨겨진 영향을 언급하지 않고 스프링의 쿨한 특징으로 적극 추천하고 있습니다. 사견으로 스프링의 객체 풀링 같이 이것은 마케팅을 위한 특징에 가깝습니다. 이것은 XML configuration 파일을 작게 만드는 좋은 아이디어 같지만 실제로 복잡함을 증가시킬 것입니다. 특히 많은 빈들이 정의된 대규모 프로젝트에서 더합니다. 스프링에서 autowiring과 명시적인 wiring을 섞어서 쓸 수도 있지만 이러한 일관성 없는 방식은 XML configuration을 더욱 혼란스럽게 만듭니다.

2. Use naming conventions
This is the same philosophy as for Java code. Using clear, descriptive, and consistent name conventions across the project is very helpful for developers to understand the XML configurations. For bean ID, for example, you can follow the Java class field name convention. The bean ID for an instance of OrderServiceDAO would be orderServiceDAO.For large projects, you can add the package name as the prefix of the bean ID.

이것은 java 코드에서와 같은 사상입니다. 프로젝트 전반에 거쳐 명확함, 선언적 그리고 일관된 명명규칙을 사용하면 개발자들이 XML configuration을 이해하는데 많은 도움을 줍니다. 빈 ID로 예를 들자면 Java 클래스의 필드 이름 규칙을 사용할 수 있을 것입니다. OrderServiceDAO 인스턴스의 빈 아이디는 orderServiceDAO가 될 것입니다. 대규모 프로젝트에서는 빈 ID의 접두어로 패키지 이름을 추가할 수 있을 겁니다.

3. Use shortcut forms
The shortcut form is less verbose, since it moves property values and references from child elements into attributes. For example, the following:

프로퍼티 값이나 레퍼런스를 자식 엘리먼트에서 속성으로 옮기기에 간단한 형식이 덜 장황합니다. 아래 예를 보십시오.

    <bean id="orderService"
        class="com.lizjason.spring.OrderService">
        <property name="companyName">
            <value>lizjason</value>
        </property>
        <constructor-arg>
            <ref bean="orderDAO">
        </constructor-arg>
    </bean>

can be rewritten in the shortcut form as:

이것은 다음과 같이 간단한 형식으로 다시 작성할 수 있습니다. 

    <bean id="orderService"
        class="com.lizjason.spring.OrderService">
        <property name="companyName"
            value="lizjason"/>
        <constructor-arg ref="orderDAO"/>
    </bean>

The shortcut form has been available since version 1.2. Note that there is no shortcut form for <ref local="...">.

간단한 형식은 1.2 버전부터 사용이 가능합니다. <ref local="...">을 위한 간단한 형식은 없다는 것을 염두해 두십시오.

The shortcut form not only saves you some typing, but also makes the XML configuration files less cluttered. It can noticeably improve readability when many beans are defined in a configuration file.

간단한 형식은 타이핑을 줄이는 것뿐만 아니라 XML configuration 파일을 덜 난잡하게 해 줍니다. Configuration 파일에 많은 빈들을 정의할 경우 눈에 띄게 가독성을 향상시킬겁니다.

4. Prefer type over index for constructor argument matching
Spring allows you to use a zero-based index to solve the ambiguity problem when a constructor has more than one arguments of the same type, or value tags are used. For example, instead of:

스프링은 생성자가 두 개이상의 같은 타입을 가질 때 모호한 문제를 풀기 위해 0부터 시작하는 index를 사용하는 것을 허용합니다. 예를 들면 다음과 같습니다.

    <bean id="billingService"
        class="com.lizjason.spring.BillingService">
        <constructor-arg index="0" value="lizjason"/>
        <constructor-arg index="1" value="100"/>
    </bean>

It is better to use the type attribute like this:


이것은 다음과 같이 type 속성을 사용하는게 더 낫습니다.

    <bean id="billingService"
        class="com.lizjason.spring.BillingService">
        <constructor-arg type="java.lang.String"
            value="lizjason"/>
        <constructor-arg type="int" value="100"/>
    </bean>

Using index is somewhat less verbose, but it is more error-prone and hard to read compared to using the type attribute. You should only use index when there is an ambiguity problem in the constructor arguments.

index를 사용하는 것이 어느 정도 더 간결하지만 더 에러가 발생하기 쉽고 type 속성에 비해 읽기도 어렵습니다. 생성자 아규먼트에 모호한 문제가 있을 경우에만 index를 사용하여야 합니다.

5. Reuse bean definitions, if possible
Spring offers an inheritance-like mechanism to reduce the duplication of configuration information and make the XML configuration simpler. A child bean definition can inherit configuration information from its parent bean, which essentially serves as a template for the child beans. This is a must-use feature for large projects. All you need to do is to specify abstract=true for the parent bean, and the parent reference in the child bean. For example:

Configuration 정보의 중복을 줄이고 XML configuration을 간결하게 하기 위해 스프링은 상속과 비슷한 메커니즘을 제공합니다. 자식 빈 정의는 부모 빈으로부터 configuration 정보를 상속받을 수 있습니다. 부모 빈 정의가 자식 빈을 위한 템플릿 역할을 합니다. 대규모 프로젝트에서는 반드시 사용하여할 기능입니다. 해야 할 일은 단지 부모 빈에 abstract=true로 설정하고 자식 빈에서 parent 로 참조하는 것입니다. 예를 들어보면.

    <bean id="abstractService" abstract="true"
        class="com.lizjason.spring.AbstractService">
        <property name="companyName"
            value="lizjason"/>
    </bean>     <bean id="shippingService"
        parent="abstractService"
        class="com.lizjason.spring.ShippingService">
        <property name="shippedBy" value="lizjason"/>
    </bean>

The shippingService bean inherits the value lizjason for the companyName property from the abstractService bean. Note that if you do not specify a class or factory method for a bean definition, the bean is implicitly abstract.

shippingService 빈은 abstractService 빈으로 부터 lizjason 값을 가진 companyName 속성을 상속받습니다. 만일 빈 정의에 클래스나 팩토리 메소드를 지정하지 않는다면 그 빈은 암시적으로 abstract이 된다는 것을 알아두십시오.

6. Prefer assembling bean definitions through ApplicationContext over imports
Like imports in Ant scripts, Spring import elements are useful for assembling modularized bean definitions. For example:

Ant 스크립트의 import같이 스프링의 import 엘리먼트는 빈 정의를 모듈화하여 조립하는데 유용합니다. 예를 들면:

    <beans>
        <import resource="billingServices.xml"/>
        <import resource="shippingServices.xml"/>
        <bean id="orderService"
            class="com.lizjason.spring.OrderService"/>
    <beans>

However, instead of pre-assembling them in the XML configurations using imports, it is more flexible to configure them through the ApplicationContext. Using ApplicationContext also makes the XML configurations easy to manage. You can pass an array of bean definitions to the ApplicationContext constructor as follows:

그런데, import를 이용하여 XML configuration 안에서 미리 조립하는 대신 ApplicationContext 클래스를 이용하여 구성하는 것이 보다 유연합니다. 또한 ApplicationContext를 이용하면 XML configuration을 관리하기 쉬워집니다. 다음과 같이 빈 정의의 배열을 ApplicationContext 생성자에 전달할 수 있습니다.

    String[] serviceResources =
        {"orderServices.xml",
        "billingServices.xml",
        "shippingServices.xml"};
    ApplicationContext orderServiceContext = new
        ClassPathXmlApplicationContext(serviceResources);


7. Use ids as bean identifiers
You can specify either an id or name as the bean identifier. Using ids will not increase readability, but it can leverage the XML parser to validate the bean references. If ids cannot be used due to XML IDREF constraints, you can use names as the bean identifiers. The issue with XML IDREF constraints is that the id must begin with a letter (or one of a few punctuation characters defined in the XML specification) followed by letters, digits, hyphens, underscores, colons, or full stops. In reality, it is very rare to run into the XML IDREF constraint problem.

빈 식별자로 id나 name을 지정할 수 있습니다. id를 사용하면 가독성을 향상시키지는 않지만 XML 파서가 빈 참조를 검사하는데 도움이 됩니다. XML IDREF 제약때문에 id를 사용할 수 없으면 빈 식별자로 name을 사용할 수 있습니다. XML IDREF 제약의 이슈는 id는 반드시 문자(또는 XML 스펙에 정의된 구두문자 중 하나)로 시작하여 뒤에 문자, 숫자, 하이픈, 밑줄, 콜론 또는 마침표가 옵니다. 사실, XML IDREF 제약의 문제에 빠지는 것은 매우 드문 일입니다.

8. Use dependency-check at the development phase
You can set the dependency-check attribute on a bean definition to a value other than the default none, such as simple, objects, or all, so that the container can do the dependency validation for you. It is useful when all of the properties (or certain categories of properties) of a bean must be set explicitly, or via autowiring

빈 정의에 dependency-check 속성을 기본값인 none 이나, simple, object 또는 all로 설정할 수 있습니다. 이는 dependency를 검증해 줍니다. 명시적으로든 자동(autowiring)으로 든  빈의 모든 속성(또는 속성의 어떤 카테고리들)에 반드시 값이 설정되어야 할 때 유용합니다.

    <bean id="orderService"
        class="com.lizjason.spring.OrderService"
        dependency-check="objects">
        <property name="companyName"
            value="lizjason"/>
        <constructor-arg ref="orderDAO"/>
    </bean>

In this example, the container will ensure that properties that are not primitives or collections are set for the orderService bean. It is possible to enable the default dependency check for all of the beans, but this feature is rarely used because there can be beans with properties that don't need to be set.

이 예제에서 컨테이너는 기본 타입(primitive)이나 집합 타입이 아닌 orderService 빈의 속성 값은 설정될 것을 보장합니다. 기본으로 모든 빈에 dependency 검사를 하도록 할 수도 있지만, 반드시 설정할 필요가 없는 빈의 속성도 있을 수 있기 때문에 이 기능은 거의 사용하지 않습니다.

9. Add a header comment to each configuration file
It is preferred to use descriptive ids and names instead of inline comments in the XML configuration files. In addition, it is helpful to add a configuration file header, which summarizes the beans defined in the file. Alternatively, you can add descriptions to the description element. For example:

XML configuration 파일 중간에 주석을 다는 것보다는 딱 봐도 알 수 있는(descriptive) id나 이름을 사용하는 것이 더 좋습니다. 거기에다 파일의 헤더에 빈 정의에 대한 요약을 다는 것도 많은 도움이 됩니다.
대신 다음과 같이 description 엘리먼트로 설명을 추가할 수도 있습니다.

    <beans>
        <description>
            This file defines billing service
            related beans and it depends on
            baseServices.xml,which provides
            service bean templates...
        </description>
        ...
    </beans>

One advantage of using the description element is that it is easy to for tools to pick up the description from this element.

이 description 엘리먼트를 사용하면 각종 도구에서 설명을 뽑아내기 쉬운 장점이 있습니다.

10. Communicate with team members for changes
When you are refactoring Java source code, you need to make sure to update the configuration files accordingly and notify team members. The XML configurations are still code, and they are critical parts of the application, but they are hard to read and maintain. Most of the time, you need to read both the XML configurations and Java source code to figure out what is going on.

Java 소스 코드를 리팩토링할 때 관련된 configuration 파일들을 갱신할 필요가 있으면 팀 동료에게 알려야 합니다. XML configuration 역시 코드이며 애플리케이션에서 중요한 요소 중 하나이지만 읽기도 어렵고 유지하기도 어렵습니다. 대부분의 시간동안 뭐가 어떻게 돌아가는지 파악하기 위해 XML configuration과 Java 코드 둘 다 읽어야 할 필요가 있습니다.

11. Prefer setter injection over constructor injection
Spring provides three types of dependency injection: constructor injection, setter injection, and method injection. Typically we only use the first two types.

스프링은 생성자 주입, setter 주입 그리고 메소드 주입 세 가지 의존성 주입(dependency injection) 방법을 제공합니다. 전형적으로 앞의 두 가지 방식만 사용합니다.

    <bean id="orderService"
        class="com.lizjason.spring.OrderService">
        <constructor-arg ref="orderDAO"/>
    </bean>

    <bean id="billingService"
        class="com.lizjason.spring.BillingService">
        <property name="billingDAO"
            ref="billingDAO">
    </bean>


In this example, the orderService bean uses constructor injection, while the BillingService bean uses setter injection. Constructor injection can ensure that a bean cannot be constructed in an invalid state, but setter injection is more flexible and manageable, especially when the class has multiple properties and some of them are optional.

이 예제에서 orderService 빈은 생성자 주입을 사용한 반면 billingService는 setter 주입을 사용합니다. 생성자 주입의 경우 올바르지 않은 상태로는 생성할 수 없도록 보증하지만 setter 주입이 보다 유연하고 관리하기 쉽습니다. 특히 클래스가 여러 개의 속성을 가지고 그 중 몇 개는 선택적일 경우 더욱 그렇습니다.

12. Do not abuse dependency injection
As the last point, Spring ApplicationContext can create Java objects for you, but not all Java objects should be created through dependency injection. As an example, domain objects should not be created through ApplicationContext. Spring is an excellent framework, but, as far as the readability and manageability are concerned, the XML-based configuration can become an issue when many beans are defined. Overuse of dependency injection will make the XML configuration more complicated and bloated. Remember, with powerful IDEs, such as Eclipse and IntelliJ, Java code is much easier to read, maintain, and manage than XML files!

마지막으로, 스프링 ApplicationContext 는 여러분을 위해 Java 객체를 생성해 줍니다. 그러나 모든 Java 객체가 의존성 주입을 통해 생성해야 하는 것은 아닙니다. 그 예로 도메인 객체는 ApplicationContext를 통해 생성하지 말아야 합니다. 스프링은 뛰어난 프레임워크이나 많은 빈들이 정의되면 XML 기반의 configuration이 이슈가 될 수 있으므로 어느 정도 가독성과 관리성을 고려해야 합니다. 의존성 주입을 과용하면 XML configuration은 보다 복잡해지고 비대해질 것입니다. Eclipse나 InteliJ와 같은 강력한 IDE를 사용하면 Java 코드가 XML 파일보다 훨씬 읽거나 유지하거나 관리하기가 쉽다는 것을 기억하십시오!

Conclusion
XML is the prevailing format for Spring configurations. XML-based configuration can become verbose and unwieldy when many beans are defined. Spring provides a rich set of configuration options. Appropriately using some of the options can make the XML configurations less cluttered, but other options, like autowiring, may reduce readability and maintainability. Following good practices discussed in the article may help you to create clean and readable XML configuration files!

XML은 일반적인 Spring configuration 포맷입니다. 많은 빈들을 정의할 경우 XML 기반 configuration은 장황해지고 다루기 어려워지기 쉽습니다. 스프링은 풍부한 configuration 옵션들을 지원합니다. 적절히 이 옵션들을 사용하면 XML configuration은 덜 복잡해지기도 하지만 autowiring 같은 다른 옵션들은 가독성이나 관리성을 떨어뜨립니다. 이 기사에서 논의한 좋은 실천사항들을 따른다면 깨끗하고 가독성이 좋은 XML configuration 파일들을 만드는데 도움이 될 것입니다!

Resources
The weblog post this article is based upon
The Spring framework website for further information about Spring XML configurations
The XML specification for IDREF constraints

Jason Zhicheng Li is a senior software engineer with Object Computing, Inc. in St. Louis, MO. 
반응형

'Framework > Spring' 카테고리의 다른 글

Mockito.thenThrow() 주의사항!  (0) 2021.09.25
Spring + @Lazy  (0) 2019.08.05
//

PK 두개 이상시 매핑

Posted at 2009. 12. 24. 14:04 | Posted in Framework/Hibernate
반응형


위와 같은 PK(Primary Key)가 두개인 테이블이 있습니다.

이 테이블을 매핑시켜봅시다.

PK가 하나일때는 <id> 태그를 써서 쓰면 되지만, 두개일때는 <composite-id> 태그를 사용합니다.

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
	"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
	"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="model">
	<class name="Code" table="common_code">

		<composite-id>
			<!-- PK1 -->
			<key-property name="code" type="string">
				<column name="code" length="10">
					<comment>코드</comment>
				</column>
			</key-property>
			<!-- PK2 -->
			<key-property name="lang" type="string">
				<column name="language" length="3">
					<comment>언어</comment>
				</column>
			</key-property>
		</composite-id>
			
		<property name="codeDesc" type="text">
			<column name="code_desc" not-null="false">
				<comment>코드 설명</comment>
			</column>
		</property>

	</class>
</hibernate-mapping>

그리고 클래스 파일은 Serializable 인터페이스를 구현해야 한답니다....

package model;

import java.io.Serializable;

public class Code implements Serializable {

    private static final long serialVersionUID = -14882784587978705L;
 
    private String code; // PK1
    private String lang; // PK2
    private String codeDesc;

    // getter, setter
}

반응형

'Framework > Hibernate' 카테고리의 다른 글

Reverse Engineering  (0) 2009.12.24
Hibernate 시작하기  (0) 2009.12.23
Criteria 사용하여 질의 하기 #2  (1) 2009.10.03
Criteria 사용하여 질의 하기 #1  (4) 2009.09.10
다대다(n:m) 관계 설정하기  (0) 2009.09.02
일대다(1:n) 관계 설정하기  (1) 2009.08.31
일대일(1:1) 관계 설정하기  (0) 2009.08.27
하이버네이트(Hibernate) 사용하기  (6) 2009.08.24
//

Reverse Engineering

Posted at 2009. 12. 24. 09:57 | Posted in Framework/Hibernate
반응형


이미 만들어져 있는 데이터베이스의 테이블을 이용해 java 파일과 매핑 xml 파일을 생성시켜봅시다~



일단 http://antop.tistory.com/53 에서 아래 3단계까지는 하셔야 합니다 ㅎㅎ

- 하이버네이트 플러그인 설치
- 하이버네이트 라이브러리 세팅
- 하이버네이트 기본 설정파일 작성

설정파일(hibernate.cfg.xml)까지 만들었으면

File - New - Other... - Hibernate - Hibernate Console Configuration - Next




하이버네이트 콘솔 정보는 hibernate.cfg.xml 설정 파일을 사용합니다.




하이버네이트 콘솔이 뭐냐... 하면 하이버네이트 플러그인을 설치하면 볼 수 있는 Hibernate Perspective 에서 사용하는 데이터베이스 접속 정보 정도로 보면 되겠습니다.

난중에 여기서 HQL 쿼리도 날려보고 여러가지를 할 수 있습니다.


이제 이 콘솔 정보를 기반으로 Reverse Engineering(이하 리버스) 파일을 생성 합니다.

File - New - Other... - Hibernate - Hibernate Reverse Engineering File (reveng.xml) - Next




리버스 파일을 저장 할 폴더 지정 - Next




인제 여기서 console configuration 에서 셀렉트박스를 눌러보면 방금전에 만든 콘솔 정보가 있을 겁니다.

콘솔정보 선택 후 Refresh 클릭하면 DB에 있는 테이블 목록을 받아옵니다.

리버스하고싶은 테이블을 선택 후 Finish




그럼 hibernate.reveng.xml 파일이 열리는데 걍 닫기.


이제 Hibernate Perspective 로 이동합니다.

아래 그림 처럼 [그.. 아이콘] 클릭 후 -_-... "Hibernate Code Generation Configurations..." 클릭




아무것도 없습니다.... "새로 만들기" 버튼 클릭




적당히 이름 정하고, 콘솔정보 선택하시고, 만들어지는 파일들을 저장할 폴더 위치를 지정합니다.

※ 아래 그림의 Name 처럼 하면 이미 있는 이름이라고 안됩니다. 딴거 쓰세요 ㅠ_ㅠ




src 폴더(최상위)를 선택합니다.




package 경로를 지정합니다.

※ 패키지를 지정하면 패키지 폴더도 생성 됩니다.

이제 리버스 엔진 파일 아까 만들었던걸 지정해줍시다.




새로 만들기를 원하냐고 물어보는데 전 이미 만들었으니 "Use existing..." 클릭




파일을 선택합니다.




이제 Exporters 탭으로 이동해서 어떤 파일을 생성할지 선택해줍니다.




Run 클릭...



신나게 파일을 생성하고 있습니다.




파일이 생성되었습니다.

뭐 에러나는 것들도 있네요.. 컴퓨터가 만능은 아니자나여!


반응형
//

Hibernate 시작하기

Posted at 2009. 12. 23. 15:17 | Posted in Framework/Hibernate
반응형
하이버네이트를 시작하기 위한 기본설정을 해봅시다.

Eclipse Galileo
Java 1.6.0_16
Tomcat 6.0.14



http://www.hibernate.org 에서 라이브러리와 플러그인을 다운로드 받습니다.

왼쪽 메뉴에서 'Download' 를 클릭 후 zip 파일을 다운로드 합니다.






하이버네이트 플러그인(Hibernate Tools)은 이클립스 플러그인 설치를 이용해서 합니다.

업데이트 주소는 하이버네이트 홈페이지에서 HIBERNATE Tools 부분에 링크 되어있습니다.



업데이트 주소 : http://download.jboss.org/jbosstools/updates/stable



플러그인이 잘 설치 되었습니다... ㄷㄷ;





이제 하이버네이트를 하기위한 최소한의 라이브러리를 세팅합시다.

다운로드 받은 hibernate-distribution-3.3.2.GA-dist.zip 파일을 압축 해제합니다.

hibernate3.jar 와 lib\required 폴더 안의 *.jar 만 있으면 하이버네이트를 사용할 수 있습니다.

log4j 를 사용하기 위해선 slf4j-log4j12-1.5.10.jarlog4j-1.2.15.jar 가 필요합니다. (설정파일 log4j.properties !!!)

마지막으로 데이터베이스에 맞는 jdbc 드라이버가 필요합니다.





하이버네이트 기본 설정 파일인 hibernate.cfg.xml 을 작성해봅시다. 이클립스 플러그인으로 생성하겠습니다.

File - New - Other... - Hibernate - Hibernate Configuration File (cfg.xml) 선택 - Next




설정파일을 저장할 경로 지정




접속 정보를 입력합니다. - Finish

접속 정보는 http://antop.tistory.com/54 에 있는 샘플 DB 입니다.

※ Database dialect 를 DB 에 알맞게 잘 골라주세요.




src 경로 아래 hibernate.cfg.xml 파일이 생성 되었습니다.

※ 플러그인을 설치하면 하이버네이트 관련 xml 파일의 아이콘이 아래 그림과 같이 표시됩니다.



파일을 열어보면 기본적으로 데이터베이스 접속 정보가 설정되어있습니다.

코드 어시스트(Ctrl + Space)를 이용해(플러그인 있어야함) 다른 설정들도 할 수 있습니다.



하이버네이트 세션(JDBC의 Connection이라고 보면 됨)을 관리해주는 클래스를 사용하도록 합시다.

HibernateUtil 클래스입니다. 하이버네이트 공식 메뉴얼에서 구한 국민 세션 관리 유틸이죠 ㅎㅎ

package util;

import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;

public class HibernateUtil {
   public static final SessionFactory sessionFactory;

   static {
      try {
         // Create the SessionFactory from hibernate.cfg.xml
         sessionFactory = new Configuration().configure().buildSessionFactory();
      } catch (Throwable ex) {
         // Make sure you log the exception, as it might be swallowed
         System.err.println("Initial SessionFactory creation failed." + ex);
         throw new ExceptionInInitializerError(ex);
      }
   }

   public static final ThreadLocal<Session> session = new ThreadLocal<Session>();

   public static Session getCurrentSession() throws HibernateException {
      Session s = session.get();
      // Open a new Session, if this thread has none yet
      if (s == null) {
         s = sessionFactory.openSession();
         // Store it in the ThreadLocal variable
         session.set(s);
      }
      return s;
   }

   public static void closeSession() throws HibernateException {
      Session s = (Session) session.get();
      if (s != null)
         s.close();
      session.set(null);
   }
}


※ 여기까지 하셨으면 하이버네이트를 사용할 최소한의 준비는 끝났습니다.



간단한 테스트 파일을 하나 만들어서 접속 테스트 해봅시다.

다른 이무 작동 없고 접속만 했다가 끊는 소스 입니다.

HibernateUtil 을 이용해서 접속합니다.

package app;

import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.Transaction;

import util.HibernateUtil;

public class Test {
   
   public static void main(String[] args) {

      Session sess = null;
      Transaction tx = null;
  
      try {
         // 세션 열기
         sess = HibernateUtil.getCurrentSession();
         // 트랜잭션 시작
         tx = sess.beginTransaction();
         
         // processing..
   
         tx.commit(); // 커밋
      } catch (HibernateException e) {
         tx.rollback(); // 롤백
         e.printStackTrace();
      } finally {
         // 세션 닫기
         HibernateUtil.closeSession();
      }
    
   }
}

에러 안뿜어내고 로그 나오면 잘 된겁니다. ㄷㄷ;;


반응형
//

Criteria 사용하여 질의 하기 #2

Posted at 2009. 10. 3. 10:03 | Posted in Framework/Hibernate
반응형
JOIN

http://docs.jboss.org/hibernate/stable/core/api/org/hibernate/Criteria.html





부서와 사원의 1:n 관계에 JOIN 쿼리를 날려봅시다.

원래 하이버네이트에서는 매핑을 잘 맺어주면, 단순 getter 로도 관계를 가지는 테이블의 내용을 가져올 수 있습니다.

Criteria crit = sess.createCriteria(Emp.class);

List<Emp> emps = crit.list();
   
int size = emps.size();
for(int i=0 ; i < size ; i++) {
   Emp emp = emps.get(i);
   Dept dept = emp.getDept();

   String empName = emp.getName();
   String deptName = dept != null ? dept.getDeptName() : "";
    
   System.out.println("row: " + (i+1));
   System.out.println(emp);
   System.out.println(dept);
}

이렇게하면 사원에 해당하는 부서의 정보를 가져올 수 있습니다.

로그를 보면 select문이 여러번 출력되는 것을 볼 수 있습니다.

List<Emp> emps = crit.list(); 에서 ' select empno, ename, job, hire_date from tbl_emp; ' 쿼리가 실행되고,

dept.getDeptName(); 에서 사원의 부서번호에 따라 ' select deptno, dname, loc from tbl_dept; ' 쿼리가 실행 됩니다.

부서의 종류가 엄청나게 많다면 두번째 select 쿼리가 많이 실행 되게 될 것입니다.

쿼리를 할때 최소한의 I/O로 실행되게 하고, 뭐.. 서브쿼리보다는 조인이 빠르고 흘러가면서 들은 것들이 있기 땜시... JOIN ㄱㄱ



INNER JOIN

키를 기준으로 일치하는 데이터만 가져옵니다. (다 아시졍? +_+)

// select * from tbl_emp e
Criteria crit = sess.createCriteria(Emp.class, "e");
// join tbl_dept d on this.deptno = d.deptno
crit.createCriteria("dept", "d");
   
// Result 를 Map 형태로 변환
crit.setResultTransformer(Criteria.ALIAS_TO_ENTITY_MAP);

List<Map<String, Object>> joinList = crit.list();

int size = joinList.size();
for(int i=0 ; i < size ; i++) {
   Map<String, Object> map = joinList.get(i);
   Emp emp = (Emp) map.get("e");
   Dept dept = (Dept) map.get("d");

   System.out.println("row: " + (i+1));
   System.out.println(emp);
   System.out.println(dept);
}


Criteria crit = sess.createCriteria(Emp.class);

Session.createCriteria(클리스명) 를 통해서 생성한 Critereria 객체에 다시 createCriteria(String associationPath, String alias) 를 사용해서 JOIN 을 하면 됩니다.

이렇게 JOIN 한 결과는 Emp 객체도 Dept 객체도 아니기 때문에, Criteria의 setResultTransformer 메소드를 사용하여 Criteria를 이용한 조회 결과를 별도 정의한 객체 형태나 Map 형태로로 전달받아야 하겠습니다.

근데 전 객체로 변형하는 법은 잘 안되서... 걍 Map 으로 ㄱㄱㄱ!

여기서 두번째 인자로 넣는 엘리어스명은 쿼리에서 쓰는 엘리어스명도 되지만 나중에 Map 에서 꺼 낼때의 키도 되기 때문에 꼭 지정해줘야 할 것입니다....

실행 되는 쿼리는 아래와 같습니다.

    select
        this_.empno as empno0_1_,
        this_.ename as ename0_1_,
        this_.job as job0_1_,
        this_.hire_date as hire4_0_1_,
        this_.deptno as deptno0_1_,
        d1_.deptno as deptno1_0_,
        d1_.dname as dname1_0_,
        d1_.loc as loc1_0_
    from
        tbl_emp this_
    inner join
        tbl_dept d1_
            on this_.deptno=d1_.deptno


이러면 당연히 부서가 없는 'TOM' 은 목록에 나오지가 않겠죠? 만약 사원 목록을 본다면 부서가 없는 사원이 나오면 안되겠죠?



LEFT JOIN

두개의 테이블을 조인할 때 왼쪽 테이블을 기분으로 오른쪽 테이블을 조인 시키는 LEFT JOIN 을 해봅시다...

createCriteria(String associationPath, String alias, int joinType) 를 사용하면 됩니다.

// select * from tbl_emp e
Criteria crit = sess.createCriteria(Emp.class, "e");
// join tbl_dept d on this.deptno = d.deptno
crit.createCriteria("dept", "d", Criteria.LEFT_JOIN);
   
// Result 를 Map 형태로 변환
crit.setResultTransformer(Criteria.ALIAS_TO_ENTITY_MAP);

List<Map<String, Object>> joinList = crit.list();

int size = joinList.size();
for(int i=0 ; i < size ; i++) {
   Map<String, Object> map = joinList.get(i);
   Emp emp = (Emp) map.get("e");
   Dept dept = (Dept) map.get("d");

   System.out.println("row: " + (i+1));
   System.out.println(emp);
   System.out.println(dept);
}

실행되는 쿼리는 아래와 같습니다. 엘리어스 사용에 주의 하세요~

    select
        this_.empno as empno0_1_,
        this_.ename as ename0_1_,
        this_.job as job0_1_,
        this_.hire_date as hire4_0_1_,
        this_.deptno as deptno0_1_,
        d1_.deptno as deptno1_0_,
        d1_.dname as dname1_0_,
        d1_.loc as loc1_0_
    from
        tbl_emp this_
    left outer join
        tbl_dept d1_
            on this_.deptno=d1_.deptno


※ RIGHT JOIN 은 없네요 테이블의 좌 우 를 바꾸셔요~ 히힛

간단하게 사원명과 부서명으로 조회하는 페이지 만들어 봤어요~








Pagination

많은 자료를 조회할 때 일정한 양의 자료를 한 페이지만 보여주도록 하는 기능입니다.

꼭 필요한 기능이고, 그다지 복잡할 것도 없지만, 막상 쿼리 날리려면 DBMS 마다 쿼리도 틀리고... 쉽지 안쵸 ㅠ_ㅠ

하지만 하이버네이트에서는

setFirstResult(int firstResult) 과 setMaxResults(int maxResults) 메소드 두개면 해결 됩니다! ㅋㅋㅋ

전부터 해오던 우편번호 테이블 가지고 해봅시다...


Criteria crit = sess.createCriteria(Zipcode.class);
crit.setFirstResult(40);   // 시작 로우 라인 번호(?)
crit.setMaxResults(10);    // 시작 로우 라인 번호로 부터 몇개?!

List<Zipcode> list = crit.list();
   
int size = list.size();
for(int i=0 ; i < size ; i++) {
   Zipcode z = list.get(i);
   System.out.println(z.toString());
}

한 페이지에 10개씩 보여준다고 하고 4페이지의 내용을 보여주게 됩니다. 


페이징 처리가 되있는 우편번호 검색 페이지를 만들어봤습니다.




몇개의 DMBS 에서 돌려보니까 아래와 같은 쿼리가 나오네요


MySQL 5.1.38
    select
        this_.seq as seq0_0_,
        this_.zipcode as zipcode0_0_,
        this_.sido as sido0_0_,
        this_.gugun as gugun0_0_,
        this_.dong as dong0_0_,
        this_.ri as ri0_0_,
        this_.bldg as bldg0_0_,
        this_.st_bunji as st8_0_0_,
        this_.ed_bunji as ed9_0_0_
    from
        tbl_zipcode this_ limit ?,
        ?


Oracle 10g Express Edition
    select
        *
    from
        ( select
            row_.*,
            rownum rownum_
        from
            ( select
                this_.seq as seq0_0_,
                this_.zipcode as zipcode0_0_,
                this_.sido as sido0_0_,
                this_.gugun as gugun0_0_,
                this_.dong as dong0_0_,
                this_.ri as ri0_0_,
                this_.bldg as bldg0_0_,
                this_.st_bunji as st8_0_0_,
                this_.ed_bunji as ed9_0_0_
            from
                tbl_zipcode this_ ) row_
        where
            rownum <= ?
        )
    where
        rownum_ > ?


Microsoft SQL Server 2005
    select
        top 20 this_.seq as seq0_0_,
        this_.zipcode as zipcode0_0_,
        this_.sido as sido0_0_,
        this_.gugun as gugun0_0_,
        this_.dong as dong0_0_,
        this_.ri as ri0_0_,
        this_.bldg as bldg0_0_,
        this_.st_bunji as st8_0_0_,
        this_.ed_bunji as ed9_0_0_
    from
        tbl_zipcode this_


MSSQL 은 일단 페이지번호 * 갯수 만큼 가져와서 내부적으로 처리를 하나봐요.....




http://blog.naver.com/oyukihana?Redirect=Log&logNo=60002506896

반응형

'Framework > Hibernate' 카테고리의 다른 글

PK 두개 이상시 매핑  (2) 2009.12.24
Reverse Engineering  (0) 2009.12.24
Hibernate 시작하기  (0) 2009.12.23
Criteria 사용하여 질의 하기 #1  (4) 2009.09.10
다대다(n:m) 관계 설정하기  (0) 2009.09.02
일대다(1:n) 관계 설정하기  (1) 2009.08.31
일대일(1:1) 관계 설정하기  (0) 2009.08.27
하이버네이트(Hibernate) 사용하기  (6) 2009.08.24
//

Criteria 사용하여 질의 하기 #1

Posted at 2009. 9. 10. 15:52 | Posted in Framework/Hibernate
반응형
Hibernate에서는 HQL에 익숙하지 못하거나 HQL 작성시 발생할 수 있는 오타로 인한 오류를 최소화 하기 위해 org.hibernate.Criteria API를 사용할 수 있도록 합니다.
Creteria API 호출을 통해 특정 객체에 대한 조회가 가능하고 org.hibernate.criterion.Restrictions API 호출을 통해 WHERE문에 해당하는 기본 조회 조건을 정의할 수 있습니다..

쉽게 말해서 select 쿼리입니다.

우편번호 데이터(50353건)로 여러가지 조회를 해봅시다! (Type 4 사용)



package com.tistory.antop;

public class Zipcode {
    // 데이터 순서(5)
    private int seq;
    // 우편번호(7)
    private String zipcode;
    // 특별시,광역시,도(4)
    private String sido;
    // 시,군,구(15)
    private String gugun;
    // 읍,면,동(26)
    private String dong;
    // 리(18)
    private String ri;
    // 건물명(40)
    private String bldg;
    // 시작번지(11)
    private String stBunji;
    // 끝번지(9)
    private String edBunji;

    // constructor, getter and setter (private setter seq)

    @Override
    public String toString() {
        // 길어서 생략...
    }
}



<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
    "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
    "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="com.tistory.antop">
    <class name="Zipcode" table="tbl_zipcode">
        <!-- 데이터 순서(5) -->
        <id name="seq" length="5">
            <generator class="native" />
        </id>
        <!-- 우편번호(7) -->
        <property name="zipcode" length="7" not-null="true" />
        <!-- 특별시,광역시,도(4) -->
        <property name="sido" length="4" not-null="true" />
        <!-- 시,군,구(15) -->
        <property name="gugun" length="15" not-null="true" />
        <!-- 읍,면,동(26) -->
        <property name="dong" length="26" not-null="true" />
        <!-- 리(18)  -->
        <property name="ri" length="18" />
        <!-- 건물명(40) -->
        <property name="bldg" length="40" />
        <!-- 시작번지(11) -->
        <property name="stBunji" column="st_bunji" length="11" />
        <!-- 끝번지(9) -->
        <property name="edBunji" column="ed_bunji" length="9" />
    </class>
</hibernate-mapping>


들어가기 전에 테스트 소스는 아래와 같습니다.
public class App {

    public static void main(String[] args) {
        Session sess = HibernateUtil.getCurrentSession();
        Transaction tx = sess.beginTransaction();

        // processing
  
        tx.commit();
        HibernateUtil.closeSession();
    }
 
    // Zipcode 객체가 들어있는 List를 출력
    private static void display(List<Zipcode> list) {
  
        try {
            int size = list.size();
   
            if(size == 0) {
                throw new Exception("리스트가 없습니다.(0건)");
            }
      
            for(int i=0 ; i < size ; i++) {
                System.out.println(list.get(i).toString());
            }
   
        } catch (NullPointerException e) {
            System.out.println("리스트가 존재하지 않습니다.");
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
    }
}




SELECT

select * from tbl_zipcode
Criteria crit = sess.createCriteria(Zipcode.class);
List<Zipcode> zipcodeList = crit.list();

select seq, zipcode, st_bunji, ed_bunji from tbl_zipcode
Criteria crit = sess.createCriteria(Zipcode.class);
  
ProjectionList projectionList = Projections.projectionList();
projectionList.add(Projections.id().as("seq"));
projectionList.add(Projections.property("zipcode").as("zipcode"));
projectionList.add(Projections.property("stBunji").as("stBunji"));
projectionList.add(Projections.property("edBunji").as("edBunji"));
  
crit.setProjection(projectionList);
crit.setResultTransformer(new AliasToBeanResultTransformer(Zipcode.class));
  
List<Zipcode> zipcodeList = crit.list();
 
원하는 칼럼을 뽑아낼 때 쓰는 ProjectionListProjections을 사용합니다.(테이블 하나 조회하면서는 안쓸듯...)

해보니까 Projections 에서 꼭 as() 를 사용해서 Zipcode 클래스의 필드명과 이름을 맞춰줘야 하더군요... -0-/

Projections.id() 와 Projections.property() 는 뭐가 다른지 아시겠죠? ㅎㅎ;; id() 가 PK가 되는 필드 입니다.


select count(*) from tbl_zipcode
Criteria crit = sess.createCriteria(Zipcode.class);
crit.setProjection( Projections.rowCount() );
  
List<Integer> zipcodeList = crit.list();
System.out.println("count : " + zipcodeList.get(0));

select count(seq) as count from tbl_zipcode
Criteria crit = sess.createCriteria(Zipcode.class);
crit.setProjection(Projections.count("seq").as("count"));
   
List<Integer> zipcodeList = crit.list();
System.out.println("count : " + zipcodeList.get(0));




ORDER BY

select * from tbl_zipcode order by seq desc
Criteria crit = sess.createCriteria(Zipcode.class);
crit.addOrder(Order.desc("seq"));
   
List<Zipcode> zipcodeList = crit.list();

Order.asc(String propertyName) 도 있습니다~ +_+




Criteria crit = sess.createCriteria(Zipcode.class);
List<Zipcode> zipcodeList = crit.list();

생략하겠습니다~


WHERE

where문은 Restrictions을 사용합니다. 내용이 많으니 자세한것은 API를 봐주세요~

select * from tbl_zipcode where seq = 1555
crit.add(Restrictions.eq("seq", 1555));

select * from tbl_zipcode where seq = 45
crit.add(Restrictions.idEq(45));    // idEq() 를 사용하면 PK 칼럼으로 검색합니다.

select * from tbl_zipcode where dong like '방화%'
crit.add(Restrictions.like("dong", "방화", MatchMode.START));    // '방화%'

MatchMode 종류는 ANYWHERE, END, EXACT, START 4가지 종류가 있습니다.

딱 보면 아시겠지만 '%방화%', '방화%', '방화', '%방화' 이렇게 됩니다.


select * from tbl_zipcode where sido = '서울' and zipcode like '157%'
crit.add(Restrictions.eq("sido", "서울"));
crit.add(Restrictions.like("zipcode", "157", MatchMode.START)); 

여러개의 조건을 사용할 때에는 계속 .add() 해주면 됩니다.


select * from tbl_zipcode where where (st_bunji = '514' and sido = '서울' and dong = '방화2동')
Map<String, String> m = new Hashtable<String, String>();
m.put("sido", "서울");
m.put("dong", "방화2동");
m.put("stBunji", "514");
  
crit.add(Restrictions.allEq(m));

여러개의 eq를 사용할 경우 Map 을 사용할 수 있습니다.


select * from tbl_zipcode where seq >= 5300
crit.add(Restrictions.ge("seq", new Integer(50300)));

주의 해야 할 것은 2번째 인자는 Object 형태라는 것입니다. +_+;

ge(>=), gt(>), le(<=), lt(<) 가 있습니다.

    select * from tbl_zipcode where sido in ('서울', '부산')
    String[] in = {"서울", "부산"};
    crit.add(Restrictions.in("sido", in));    // Collection 또는 Object[] 사용 가능

    select * from tbl_zipcode where ( sido = '서울' and zipcode like '157%' )
    crit.add(
        Restrictions.conjunction()    // and 연산으로 그룹
            .add( Restrictions.eq("sido", "서울") )
            .add( Restrictions.like("zipcode", "157", MatchMode.START) )
    );

    and 조건을 여러개 사용할 경우 그냥 계속 .add() 해도 되지만 Restrictions.conjunction() 로 그룹할 수 있습니다.

    Restrictions.conjunction() 은 and 로 그룹하고, Restrictions.disjunction() 은 or 로 그룹합니다.


    select * from tbl_zipcode where ( (dong like '방화%' or dong like '%1동') and (zipcode like '157%' or zipcode like '431') )
    crit.add(
        Restrictions.conjunction()
            .add(
                Restrictions.disjunction()
                    .add(Restrictions.like("dong", "방화", MatchMode.START))
                    .add(Restrictions.like("dong", "1동", MatchMode.END))
            )
            .add(
                Restrictions.disjunction()
                    .add(Restrictions.like("zipcode", "157", MatchMode.START))
                    .add(Restrictions.like("zipcode", "431", MatchMode.START))
            )
    );

    - 동이 '방화'로 시작하거나 '1동'으로 끝나고 우편번호가 157로 시작하거나 431로 시작하는 데이터를 조회 합니다.
    - (동이 '방화'로 시작 or 동이 '1동'으로 끝) and (우편번호가 '157'로 시작 or 우편번호가 '431'로 시작)

    정도가 되겠네요... (헷갈리.. ㅠㅠ)


    select * from tbl_zipcode where st_bunji between '157' and '158'
    crit.add(Restrictions.between("stBunji", "157", "158"));

    select * from tbl_zipcode where ( dong like '방%' and dong like '%1동' )
    crit.add( Restrictions.and( Restrictions.eq("sido", "서울"), Restrictions.like("dong", "1동", MatchMode.END) ) );

    Restrictions.and(조건, 조건) 은 두개의 조건을 and 로 그룹합니다.
    Restrictions.or(조건, 조건) 은 두개의 조건을 or 로 그룹합니다.


    select * from tbl_zipcode where not ( sido in ('서울', '부산') )
    crit.add( Restrictions.not( Restrictions.in("sido", new String[] { "서울", "부산" }) ) );





    GROUP BY

    ProjectionListProjections을 사용합니다

    select sido, count(sido) as sidoCount from tbl_zipcode group by sido
    // 각 시도별 우편번호 갯수를 카운트
    ProjectionList projectionList = Projections.projectionList();
    projectionList.add( Projections.alias( Projections.count("sido"), "sido_count") );   // count(sido) as sido_count
    projectionList.add( Projections.groupProperty("sido").as("sido") );    // group by sido
      
    crit.setProjection(projectionList);
    crit.setResultTransformer(Criteria.ALIAS_TO_ENTITY_MAP);
      
    List<Map<String, Object>> list = crit.list();
      
    for(int i=0 ; i < list.size() ; i++) {
        Map<String, Object> m = (Map<String, Object>) list.get(i);
        System.out.println(m);
    }

    Projections.groupProperty(String propertyName) 을 이용해서 GROUP BY 를 하고

    crit.setResultTransformer(Criteria.ALIAS_TO_ENTITY_MAP); 는 조회해서 나온 결과를 Map 형태로 변환해 줍니다.

    이렇게 나온 결과가 Zipcode 클래스 자체 형태가 아니라면 Map 으로 변환해서 처리하면 됩니다.


    ※ groupPropery(String propertyName) 과 groupProperty(String propertyName).as(String alias) 와는 차이가 있습니다.

    select count(sido) as sido_count from tbl_zipcode group by sido
    projectionList.add( Projections.groupProperty("sido") );

    // 결과
    {sido_count=2800}
    {sido_count=8170}
    {sido_count=3614}
    {sido_count=4921}


    select count(sido) as sido_count, sido from tbl_zipcode group by sido
    projectionList.add( Projections.groupProperty("sido").as("sido") );    // sido 가 select 칼럼에 추가
    // 결과
    {sido=강원, sido_count=2800}
    {sido=경기, sido_count=8170}
    {sido=경남, sido_count=3614}



    여러 칼럼으로 GROUP BY 하려면 똑같이 Projections.groupProperty() 를 추가 하면 됩니다.

    select sido, gugun, count(sido) as sidoCount from tbl_zipcode group by sido, gugun
    // 각 시도별 우편번호 갯수를 카운트
    ProjectionList projectionList = Projections.projectionList();
    projectionList.add( Projections.alias( Projections.count("sido"), "sido_count") );   // count(sido) as sido_count
    projectionList.add( Projections.groupProperty("sido").as("sido") );    // group by sido
    projectionList.add( Projections.groupProperty("gugun").as("gugun") );    // group by gugun


    ※ 다른 WHERE(또는 다른 것들..)과 같이 쓸때 Projections에서 alias를 다른 이름으로 사용해야 합니다.

    crit.add(Restrictions.eq("sido", "서울"));
      
    // 각 시도별 우편번호 갯수를 카운트
    ProjectionList projectionList = Projections.projectionList();
    projectionList.add( Projections.alias( Projections.count("sido"), "sido_count") );   // count(sido) as sido_count
    projectionList.add( Projections.groupProperty("sido").as("sido") );    // group by sido

    select count(sido) as sido_count, sido from tbl_zipcode where sido = '서울' group by sido

    정도의 쿼리를 예상 했지만 실제로 해보면 잘못된 쿼리라는 에러가 납니다.

    이유는 하이버네이트에서 쿼리를 만들때 내부적으로 alias 를 사용하는데,

    select count(this_.sido) as y0_, this_.sido as y1_ from tbl_zipcode this_ where y1_=? group by this_.sido

    위와 같은 쿼리가 나오게 됩니다. 이상하죠? ㅎㄷ;;

    crit.add(Restrictions.eq("sido", "서울"));
      
    // 각 시도별 우편번호 갯수를 카운트
    ProjectionList projectionList = Projections.projectionList();
    projectionList.add( Projections.alias( Projections.count("sido"), "sido_count") );   // count(sido) as sido_count
    projectionList.add( Projections.groupProperty("sido").as("g_sido"));    // group by sido

    // 결과
    {g_sido=서울, sido_count=7807}

    위와 같이 Projections 에서 다른 alias 를 사용하면 where 문의 칼럼이 제대로 나옵니다.

    select count(this_.sido) as y0_, this_.sido as y1_ from tbl_zipcode this_ where this_.sido=? group by this_.sido





    HAVING

    having 은 Criteria 에서 지원이 안되는 것 같습니다. HQL 을 사용하면 되겠지만 쿼리문을 쓰기 싫으므로...

    열시미 찾아서 having 이 지원되게 수정(3.1 rc1 버젼) 된 라이브러리를 찾았습니다! (찾았는데 오류나서 개인적으로 수정 -_-)

     - 수정된 java 파일 입니다.

     - 하이버네이트 jar 에 통합시킨 파일입니다. ( 3.2.4.GA + 수정된 java(3.1 rc1) )


    수정 된 부분은 Criteria 클래스에서 public Criteria addHaving(Criterion criterion); 메소드가 추가되었습니다.


    select count(sido) as sido_count, sido from tbl_zipcode group by sido having sido in ('서울', '경기', '부산')
    ProjectionList projectionList = Projections.projectionList();
    projectionList.add( Projections.alias( Projections.count("sido"), "sido_count") );
    projectionList.add( Projections.groupProperty("sido").as("g_sido"));
      
    crit.addHaving( Restrictions.in("sido", new String[] {"서울", "경기", "부산"}) );    // having sido

    // select count(this_.sido) as y0_, this_.sido as y1_ from tbl_zipcode this_ group by this_.sido having this_.sido in (?, ?, ?)
    {g_sido=경기, sido_count=8170}
    {g_sido=부산, sido_count=3287}
    {g_sido=서울, sido_count=7807}



    이 group by 와 having 부분은 이것저것 해보면 아시겠지만 제약(?)이 있군요.. ㅠ_ㅠ

    select count(sido) as sido_count, sido, gugun from tbl_zipcode group by sido, gugun having count(sido) > 500

    이거 할 줄 아시는분... 젭알!!!!!!! ㅠ_ㅠ





    간단히 우편번호 검색하는 걸 만들어 봤습니다. ㄷㄷ;


    반응형

    'Framework > Hibernate' 카테고리의 다른 글

    PK 두개 이상시 매핑  (2) 2009.12.24
    Reverse Engineering  (0) 2009.12.24
    Hibernate 시작하기  (0) 2009.12.23
    Criteria 사용하여 질의 하기 #2  (1) 2009.10.03
    다대다(n:m) 관계 설정하기  (0) 2009.09.02
    일대다(1:n) 관계 설정하기  (1) 2009.08.31
    일대일(1:1) 관계 설정하기  (0) 2009.08.27
    하이버네이트(Hibernate) 사용하기  (6) 2009.08.24
    //

    *.jsp 접근 막기

    Posted at 2009. 9. 3. 09:29 | Posted in Framework/Struts
    반응형
    struts 사용시 *.jsp 접근을 막기 위한 설정입니다.

    web.xml
    <?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns:xsi="......ID" ...... version="2.5">
        <display-name>many-to-many_mysql</display-name>
            <welcome-file-list>
                <welcome-file>index.html</welcome-file>
                ...
        </welcome-file-list>

        <!-- 웹으로 접속한 사용자가 JSP 파일로 직접 접근할 수 없게 한다. -->
        <security-constraint>
            <web-resource-collection>
                <web-resource-name>PreventViewingJSPs</web-resource-name>
                <description>웹으로 접속한 사용자가 JSP파일로 직접 접근할 수 없도록 한다.</description>
                <url-pattern>*.jsp</url-pattern>
                <http-method>GET</http-method>
                <http-method>POST</http-method>
            </web-resource-collection>
            <auth-constraint>
                <role-name></role-name>
            </auth-constraint>
        </security-constraint>

    </web-app>


    반응형

    'Framework > Struts' 카테고리의 다른 글

    스트러츠(Struts) 기본 세팅  (6) 2009.06.22
    Struts 파일 업로드  (2) 2009.05.05
    //