네트빌, iMBC·한솔교육 등에 커뮤니티솔루션 공급

[아이뉴스24 2005-11-18 15:05]  
<아이뉴스24>

커뮤니티 솔루션 개발업체 네트빌(대표 문기헌 www.netville.co.kr)은 자사의 솔루션 ‘이클럽메이트(e-ClubMate)’의 엔터프라이즈 버전을 iMBC의 좋은친구(http://friends.imbc.com)와 한솔교육 어린이 커뮤니티 퍼니또(http://www.funitto.com)에 공급했다고 18일 밝혔다.

문기헌 네트빌 사장은 “옥션, 엠게임 등에서 도입할 예정이며 앞으로 회사가 진행하는 다수의 프로젝트에도 적용될 것”이라고 말했다.



e-ClubMate 엔터프라이즈 버전은 기존 ‘e-ClubMate 4.0’에 비해 회원관리, 메뉴관리, 디자인 관리 기능이 대폭 개선된 버전으로 개별 클럽의 경우에도 중대형 사이트 수준으로 클럽 개설이 가능하다.



문기헌 사장은 “e-ClubMate 4.0 의 상위 버전인 엔터프라이즈 버전의 출시로 인해 다양한 고객에 대한 제품라인업이 가능해졌으며 이를통해 커뮤니티 솔루션 시장에서 선두업체의 자리를 좀 더 확고히 할 수 있게 됐다”고 밝혔다.



/김상범기자 ssanba@inews24.com

Hibernate: Mapping a “one-to-zero-or-one” relationship

I’ve been searching a way to do a lazy a “one-to-zero-or-one”
relationship in Hibernate and I finally found a way to do it using
many-to-one with unique=”true”. See example below:

Association: Person [1 <--> 0..1] Note

<class name=”Person” table=”person”>
<id name=”id” column=”id” type=”int” unsaved-value=”-1″>
<generator class=”sequence”>
<param name=”sequence”father_id_seq</param>
</generator>
</id>
<property name=”name” type=”string”/>
<many-to-one name=”note” class=”Note” column=”id”
unique=”true” insert=”false” update=”false”
cascade=”all”/>
</class>

<class name=”Note” table=”note”>
<id name=”id” column=”id” type=”int” unsaved-value=”-1″ >
<generator class=”foreign”>
<param name=”property”>owner</param>
</generator>
</id>
<property name=”note” type=”string”/>
<one-to-one name=”owner” class=”Person” constrained=”true”/>
</class>

Observe that column “id” is used twice in Person mapping.I’ve been
searching a way to do a lazy a “one-to-zero-or-one” relationship in
Hibernate and I finally found a way to do it using many-to-one with
unique=”true”. See example below:

Association: Person [1 <--> 0..1] Note

<class name=”Person” table=”person”>
<id name=”id” column=”id” type=”int” unsaved-value=”-1″>
<generator class=”sequence”>
<param name=”sequence”father_id_seq</param>
</generator>
</id>
<property name=”name” type=”string”/>
<many-to-one name=”note” class=”Note” column=”id”
unique=”true” insert=”false” update=”false”
cascade=”all”/>
</class>

<class name=”Note” table=”note”>
<id name=”id” column=”id” type=”int” unsaved-value=”-1″ >
<generator class=”foreign”>
<param name=”property”>owner</param>
</generator>
</id>
<property name=”note” type=”string”/>
<one-to-one name=”owner” class=”Person” constrained=”true”/>
</class>

Observe that column “id” is used twice in Person mapping.

OR 매핑의 Lazy Loading에 대해 (Hibernate Framework)

ORM 프래임워크의 대표격인 자바 기반의 하이버네이트 (Hibernate)의 레이지 로딩 (Lazy loading)에 대한 기사.
Lazy loading은 ORM의 성능에 있어서 아주 중요한 개념입니다.


————————————–


The Good, The Bad, and The Ugly


One of the potential problems with object/relational mapping is that a lot of work has to be done to convert relational results into object results. Because of the nature of an object-level querying and criteria system, the results returned by a query in an O/R mapping tool will potentially be much more coarse than what you might be able to achieve via standard JDBC calls. Tree structures are a great example of this. Say you had a class like this:


public class Node {
private List<Node> children;
private Node parent;
private String name;
public List<Node> getChildren() {
return children;
}
public Node getParent() {
return parent;
}
public void setChildren(List<Node> children) {
this.children = children;
}
public void setParent(Node parent) {
this.parent = parent;
}
// …
}


This Node class has a fairly complex set of associations; for a naive O/R mapper to provide an a fully populated Node object, it potentially would have to load the entire ‘Node’ table. For instance, given this tree:


-A
|
*—B
| |
| *—C
| |
| *—D
| |
| *—E
| |
| *—F
| |
| *—G
*—H


If you were to ask a naive O/R mapper to load C, it would have to load B (due to the parent reference). To load B, the O/R mapper then has to load D (due to the children reference). D then has to load E, F, and G. Then, to continue loading B, the O/R mapper has to load A, which then loads H. It’s possible that all we wanted was the name of C, however, and simply by loading C, we have loaded the entire tree. In the mean time, each association population would require a unique SQL select. By my count, that is around 8 gazillion SQL statements for just the example above, and the number of SQL statements grows *linearly* with the number of nodes in the data. The infamous n+1 problem; not a good performance statistic.


Let Sleeping O/R Mappers Lie (Get it? Hibernating? Nevermind…)


Hibernate is not a naive O/R mapper however. Instead, Hibernate does an excellent job of providing many APIs to minimize the impact of a query, and to make sure that you’re not doing too much work for the result you want. For one thing, Hibernate supports scalar queries, so you could simply get the name, and be done with it (that’s a discussion for another day however). In addition, if you really did need the whole tree, Hibernate supports join-style fetching so that the linear select situation can be minimized down to a couple (or even 1!) select statements (that is *also* a discussion for another day, however).


Hibernate is so Lazy!


Hibernate also supports the concept of ‘lazy’ fetching. Lazy interaction is discussed in more detail in Chapter 20 of the Hibernate 3.0 Reference Documentation. When working with Hibernate, it is *critical* to understand lazy associations, as they can be your best friend, but if you don’t treat them right, they can be your worst enemy. Besides, most lazy fetching strategies have been *enabled* by default in Hibernate 3.0!


Hibernate provides three forms of lazy fetching – lazy (single-point) associations, lazy collections, and lazy properties. Lazy collections have been around for a while in Hibernate, and they are the most commonly important form of lazy fetching. The children property of the Node class above is an example of a collection. Lazy (single-point) associations differ from collections by being single references to other objects persisted by Hibernate. So, the parent property of the Node class above is an example of an association. They are called single-point associations because if they were multi-point associations they would be a collection. From now on I’ll refer to single-point associations as just associations, because I’m lazy too. Finally, lazy properties could be values like name , that are typically mapped to a single column. This is rarely an important feature to enable (this, unlike other lazy fetching strategies in Hibernate 3, is disabled by default). It even says in the Hibernate documentation that ” Hibernate3 supports the lazy fetching of individual properties. This optimization technique is also known as fetch groups. Please note that this is mostly a marketing feature, as in practice, optimizing row reads is much more important than optimization of column reads. However, only loading some properties of a class might be useful in extreme cases, when legacy tables have hundreds of columns and the data model can not be improved.” . For that reason I intend to primarily discuss lazy associations and collections, as they are the more complicated to understand anyway. The same points I’ll make here apply for lazy properties as well as associations and collections.


Disclaimer on Lazy Associations


For lazy associations to work, the class containing the associations (in this case, the Node class) must either be a.) represented and retrieved via an interface or b.) not declared as final. This is because for the association relationships to be initialized after-the-fact, Hibernate has to instrument the class so that when ‘getParent()’ is actually called, it can go do the leg work at that time. It either has to do this through CGLIB, or through dynamic proxies. Dynamic proxies require an interface; CGLIB requires a non-final class. Note: at the time of this writing, CGLIB is *significantly* faster than dynamic proxies, but also requires more security access (doesn’t like sandboxes that restrict bytecode modification). So it is a tradeoff. For more information on these restrictions, see Section 5.1.4 .


Lazy collections don’t suffer from this problem, simply because Hibernate harbors custom implementations of the java.util.List , java.util.Map and java.util.Set interfaces (among others) that it sends to your class in place of default implementations (such as ArrayList ).


Atrophied Associations


In general practice, lazy collections and associations are managed by Hibernate transparently, to the point that you as a developer never have to worry about the fact that objects are only being loaded ‘as you need them’. Unfortunately, one of the most common patterns for Hibernate usage – inside an MVC web application – trips this transparent wire, setting off an anti-personnel mine of LazyInitializationException s. Too much ridiculous metaphor? Ok, let’s get into the real example.


In a general-case use, you might have code that looks like this:


Session s = getHibernateSession(); // get session from factory
try {
Query q = s.createQuery(“from Node n where n.name=:nodeName”).setString(“nodeName”, “C”);
Node cNode = (Node)q.uniqueResult(); // assuming Node with name C is unique…
Node cNodeParent = cNode.getParent();
System.out.println(“Node: “ + cNode.getName() + ” Parent: “ + cNodeParent.getName());
}
finally {
s.close();
}


This works perfectly, and you get a print-out of “Node: C Parent: B”, and Hibernate did it quite efficiently. Unfortunately, the modern pattern of a layered MVC web application causes a conflict with this. In an MVC web application you may have a servlet like this:


// Called via http://[server-name]/ListNodeAndParentServlet?nodeName=C
public class ListNodeAndParentServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) {
String nodeName = request.getParameter(“nodeName”);
Node node = nodeDao.getNodeForName(nodeName);
request.setAttribute(“node”, node);
// forward to the JSP
request.getRequestDispatcher(“/nodeName.jsp”).forward(request, response);
}
}


Here is the DAO implementation:


public class NodeDao {
// …
public Node getNodeForName(String name) {
Session s = getHibernateSession();
try {
Query q = s.createQuery(“from Node n where n.name=:nodeName”).setString(“nodeName”, name);
Node node = (Node)q.uniqueResult(); // assuming Node with name ‘name’ is unique…
return node;
}
finally {
s.close();
}
}
}


… and here is a general sketch of the JSP:


<%
Node node = (Node)request.getAttribute(“node”);
%>

Name: <%=node.getName()%><br/>
Parent’s Name: <%=node.getParent().getName()%><br/> <!– This line would cause a LazyInitializationException! –>



As commented in the JSP, calling ‘getParent()’ on the node will attempt to load the parent node from the database, but because the NodeDao closed the session as soon as it was done with it, no further SQL statements can be executed, and Hibernate’s proxy around our Node throws a LazyInitializationException, complaining that we tried to get data that wasn’t loaded yet.




  1. Servlet asks DAO for object.
  2. DAO opens session, loads object, closes session. NO FURTHER SQL after this point
  3. Servlet puts node into request for view
  4. View requests necessary rendering information from node, such as parent. Exception is thrown due to 2.)


So, what is the solution?


Choose Your Poison


There are really two common answers to this problem as seen by the community right now. The first is to explicitly initialize all associations and collections required before returning it. The second is to keep the session open until the view is done rendering (commonly referred to as ‘Open Session In View’).


The latter solution (open session in view) typically requires that you have a robust ServletFilter that *always* closes the Session properly after that view has rendered. From the Hibernate documentation:



In a web-based application, a servlet filter can be used to close the Session only at the very end of a user request, once the rendering of the view is complete (the Open Session in View pattern). Of course, this places heavy demands on the correctness of the exception handling of your application infrastructure. It is vitally important that the Session is closed and the transaction ended before returning to the user, even when an exception occurs during rendering of the view. The servlet filter has to be able to access the Session for this approach. We recommend that a ThreadLocal variable be used to hold the current Session (see chapter 1, Section 1.4, ” Playing with cats “, for an example implementation).


The example described above is thorough enough that there is no reason to reproduce it here. Here is the general sketch of a servlet filter using the HibernateUtil class described in the previous link however:


public class SessionFilter implements Filter
{
private FilterConfig filterConfig;
 
public void doFilter (ServletRequest request,
ServletResponse response,
FilterChain chain)
{
try {
chain.doFilter (request, response);
}
catch(Exception e) {
// handle!
}
finally {
HibernateUtil.closeSession();
}
}
 
public FilterConfig getFilterConfig()
{
return this.filterConfig;
}
 
public void setFilterConfig (FilterConfig filterConfig)
{
this.filterConfig = filterConfig;
}
}


Then, you just need to modify the DAO to support this class:


public class NodeDao {
// implementation previously omitted.
public Session getHibernateSession() {
return HibernateUtil.currentSession();
}

public Node getNodeForName(String name) {
Session s = getHibernateSession();
Query q = s.createQuery(“from Node n where n.name=:nodeName”).setString(“nodeName”, name);
Node node = (Node)q.uniqueResult(); // assuming Node with name ‘name’ is unique…
return node;
}
}



Some developers consider this to be bad practice however (understandably so), because the transparent lazy fetching of the parent in the above example is not done until you are in your view rendering implementation. That means that if the SQL produced and executed by Hibernate throws any exceptions, or anything else goes wrong, your view is now responsible for handling it (e.g. your JSP); and as many of us have learned, one of the key benefits of this 2-step servlet-first approach is that any errors can be handled by the servlet, which a.) is more succeptible to pluggable error-handling approaches and b.) is prior to the response being committed, so error pages can still be loaded, etc.


Alternatively, the former solution (don’t remember the former solution anymore?) is to pre-load any resources you know you’re going to need in a view. This solution, while being more verbose, is perhaps more accurate, and provides more control if something were to go wrong. To do this, I typically still use the HibernateUtil solution. However, closing it as part of the servlet process itself (rather than through a filter) ensures that the session is out of the picture by the time the view is rendered. While this doesn’t allow the reusability of a filter, most developers now-a-days use an MVC framework such as Webwork (com.opensymphony.webwork.interceptor.*), Spring (org.springframework.web.servlet.HandlerInterceptor), or Struts (org.apache.struts.action.RequestProcessor), all of which support pluggable pre and post execution behaviors (similar to servlet filters, except only for the controller/action itself), and this behavior can certainly be done in those. For this simple example, however, I will simply put it in the code. Then, the trick is to simply initialize any associations required by the view. Simply accessing the association will initialize it:


node.getParent(); // this will cause the parent association to initialize.


// Called via http://[server-name]/ListNodeAndParentServlet?nodeName=C
public class ListNodeAndParentServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) {
try {
String nodeName = request.getParameter(“nodeName”);
Node node = nodeDao.getNodeForName(nodeName);
 
// initialize associations
node.getParent();

request.setAttribute(“node”, node);
// forward to the JSP
request.getRequestDispatcher(“/nodeName.jsp”).forward(request, response);
}
catch(RuntimeException e) {
// handle runtime exception, forwarding to error page, or whatever
}
finally {
HibernateUtil.closeSession();
}
}
}



Note that if we had lazy property association on, we would need to call ‘getName()’ for the node and its parent node as well.


Collections require a little more work to initialize – you could of course, do it manually by traversing the entire collection, but it is perhaps better to call on the Hibernate utility class to do that for you. The Hibernate.initialize method takes an object as an argument, and initializes any lazy associations (this includes collections). So, if we wanted to fully initialize our node, and then fully initialize the collection of children:


Node n = // .. get the node
Hibernate.initialize(n); // initializes ‘parent’ similar to getParent.
Hibernate.initialize(n.getChildren()); // pass the lazy collection into the session to be initialized.


So, if our view was more like this:


<%
Node node = (Node)request.getAttribute(“node”);
%>

Name: <%=node.getName()%><br/>
Parent’s Name: <%=node.getParent().getName()%><br/> <!– This line would cause a LazyInitializationException! –>
Childrens’ Names: <br/>
<%
for(int i=0; i<node.getChildren().size(); i++) {
Node child = node.getChildren().get(i); // generics
if(i > 0) {
%>, <%
}
%><%=child.getName()%><%
}
%>



You would need the servlet (or wherever your object initialization code is) to do a little more legwork.


// Called via http://[server-name]/ListNodeAndParentServlet?nodeName=C
public class ListNodeAndParentServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) {
try {
String nodeName = request.getParameter(“nodeName”);
Node node = nodeDao.getNodeForName(nodeName);
 
// initialize associations
Hibernate.initialize(node);
Hibernate.initialize(node.getChildren());

request.setAttribute(“node”, node);
// forward to the JSP
request.getRequestDispatcher(“/nodeName.jsp”).forward(request, response);
}
catch(RuntimeException e) {
// handle runtime exception, forwarding to error page, or whatever
}
finally {
HibernateUtil.closeSession();
}
}
}



I hope this gives you a starting point for understanding fitting lazy Hibernate collections and associations into a modern web application.

NHibernate 가이드

본 블로그 글의 출처는
http://network.hanbitbook.co.kr/view.php?bi_id=1118

이며 저자는 한동훈 입니다.
This blog entry is from http://network.hanbitbook.co.kr/view.php?bi_id=1118





ASP.NET 뿐만 아니라 다양한 웹 프로그래밍 환경에서 가장 반복적인 부분은 화면상의 컨트롤에 값을 설정하고, 값을 가져오는 일이다. 이와 같은 작업을 보다 편리하게 할 수 있는 방법은 없을까? 웹 페이지상의 각 요소에 값을 자유자재로 넣으려면 전체 HTML 문서 구조를 해석해서 어느 요소에 어떤 값을 넣을지를 결정해주는 작업을 해야한다. 이렇게 복잡하게 할 바엔 직접 코딩할래!라는 개발자가 대부분이다.

또한, DB에 값을 가져오거나 저장하는 일을 단순하게 할 수 있는 방법은 없을까? 라는 물음이다. 물론, Data Access Layer를 담당하는 클래스를 작성해서 DB작업을 단순하게 해주지만 쿼리 작성, 값의 유효성과 같은 것들을 자동화해주지는 못한다.

MS가 Data Access Bulilding Block을 제공한다해도 여전히 반복적인 작업을 해야한다. 이에 대한 대안은 과연 없을까?

컨트롤 처리

ASP.NET에서는 웹 페이지의 각 요소를 컨트롤로 다루기 때문에 각 요소에 접근해서 값을 설정하거나 변경하는 작업을 비교적 쉽게 할 수 있다. 게다가, 앞에서 다룬 것처럼 ASP.NET의 전체 컨트롤 트리를 해석하는 코드를 손쉽게 작성할 수 있기 때문에 페이지 내의 모든 컨트롤 값을 알아내는 작업도 쉽게 할 수 있다.

페이지에 있는 모든 컨트롤의 값을 출력하기 위해 BasePage 클래스에 GetFormVariables() 함수를 추가하였다. 이 함수는 모든 컨트롤값을 해시로 저장한다. 값을 이용하기 위해서는 hash["ControlName"]의 형식으로 이용할 수 있고, 전체 컨트롤 값을 얻기 위해서는 다음과 같이 Iterator 패턴을 사용하면 된다.(닷넷에서는 Enumerator라는 용어를 사용하고 있지만 C++/Java 등에서는 이와 같은 형태에 대해 Iterator라는 용어를 사용하고 있다)

화면에 출력되는 값들을 보면 컨트롤 내의 모든 요소들의 값을 잘 가져오는 것을 알 수 있다. GetFormVariables() 함수는 Hashtable 형태로 값을 가져오는 버전과 필드 구분자를 이용해서 값을 가져오는 형태로 나뉘어져있다. GetFormVariables()에서 값을 가져오지 않는 몇가지가 있다. 그 중에는 DataGrid, DropDownList, RadioButtonList, CheckBoxList 컨트롤들이 있다. 이들은 그 안에 포함된 데이터를 다루기 위한 방법들이 별도로 필요하기 때문에 생략하였다.

다음으로 컨트롤에 값을 설정하는 경우를 살펴보자. 요즘 업무용 웹 응용프로그램의 경향을 보면 다양한 탭을 사용하고, 각 탭마다 적게는 30개에서 많게는 300개 이상의 컨트롤을 올려놓고 작업하고 있다. 이런 상황에서 ControlName.Text = “Value”;와 같은 형태의 코드를 각 컨트롤마다 사용한다는 것은 심각한 시간낭비라 할 수 있다.

첫번째 SetFormVaribles() 함수는 컨트롤 이름 목록과 컨트롤 값 목록을 각각 배열로 받아서 순서대로 값을 찾아서 컨트롤의 값을 매핑해주는 예이며, 두번째 SetFormVariables() 함수는 Hashtable에 설정된 값을 컨트롤에 매핑해주는 형태이다. 예제에서 볼 수 있는 것처럼 다양한 컨트롤에 값을 매핑할 수 있다. DataGrid와 List 컨트롤의 값 설정과 매핑은 별도의 주제로 다룰 것이기 때문에 그 주제는 다음 기회로 미룰 것이다. ^^;

일단, 값을 설정하는 컨트롤의 형태에서 간파할 수 있는 것이 몇가지 있을 것이다. 조회성 화면을 작성하는 경우에 DB로부터의 결과를 Hashtable의 형태로 가져온다면 그 값을 바로 페이지상의 컨트롤에 매핑할 수 있을 것이다. DB Field 명을 컨트롤 명으로 사용하는 방법도 생각할 수 있고, 중간에 XML 파일 형식을 만들어서 DB Field와 컨트롤 이름 사이의 매핑을 작성할 수 있을 것이다. 아마, 필자라면 와 같은 형태의 매핑 파일을 작성해서 이를 이용할 것 같다.

VS.NET에서 XML 파일을 추가한 경우에 XML 파일을 컴파일된 DLL 파일에 포함시킬 수 있기 때문에 XML 파일을 관리한다거나 누군가가 XML 파일을 변경하는 문제는 발생하지 않는다. 누군가가 DLL에서 XML을 추출해서 변경하고 다시 DLL에 합치는 경우도 강력한 키(Strong Key) 서명을 이용해서 DLL을 배포했다면 예방할 수 있다. DLL에 XML을 포함시키는 방법은 NHibernate을 설명하면서 같이 살펴보도록 하자.

지금은 GetFormVariables와 SetFormVariables의 내부에 대해 좀 더 자세히 살펴보자. 내부구조에 관심없는 분은 살포시 다른 페이지로 이동하면 된다.

Get/SetFormVariables 함수 분석

ASP.NET 페이지의 컨트롤 트리 해석에 대해서는 ASP.NET 가이드 2. 숫자/문자 입력 텍스트 박스 만들기 에서 살펴보았지만 전체 컨트롤 트리를 해석할 필요는 없었다. 여기에 전체 컨트롤 트리를 해석하게 만들기 위해서는 간단하게 재귀를 사용할 수 있다. 하지만, ASP.NET 페이지에 한 탭에 30-100개의 컨트롤이 5-6개의 탭에 올라가는 응용 프로그램을 생각해보면 재귀는 현명한 선택이 아니다. 따라서 재귀버전을 비재귀버전으로 작성할 필요가 있다.

흥미가 있는 분들은 두번째 글을 참고해서 재귀버전을 작성해보고 그에 대한 비재귀버전을 작성해보기 바란다.


비재귀버전으로 작성한 컨트롤 트리 해석

여기서는 비재귀버전으로 작성하기 위해 스택을 사용했다. 스택을 이용해서 작성했기 때문에 컨트롤 트리의 순서는 반대로 출력되지만 사용하기에는 아무 문제가 없다.(순서를 다시 원위치로 복귀해주는 역정렬을 해주는 구현이라든가 이런 예는 인터넷에서 손쉽게 찾아볼 수 있을 것이다)

GetFormVariables() 함수도 Hashtable을 이용한 버전과 구분자로 되어 있는 문자열을 반환하는 형태의 두 가지 버전이 있다.

GetValue() 함수는 실제 컨트롤의 값을 가져오는 역할을 수행하며, GetValue도 Hashtable인 경우와 문자열인 경우의 두 가지 버전을 제공한다.

SetFormVariables() 함수도 GetFormVariables() 함수와 마찬가지로 같은 코드로 작성되어 있으며 GetValue 대신에 SetValue 함수가 사용되었다는 점만 다르다.

이 두 가지 함수면 대부분의 경우에 컨트롤의 값을 편하게 설정하고 제어할 수 있을 것이다. 원한다면 GetValue, SetValue 함수를 사용해서 일부 컨트롤의 값을 변경할 수도 있을 테지만, 그런 경우에도 SetFormVariables에 변경하고 싶은 컨트롤의 목록과 값을 전달하면 충분히 변경할 수 있을 것이다. 30개 중에 3개의 컨트롤 값을 변경하고 싶은 경우에도 SetFormVariables를 유용하게 사용할 수 있으며, 이런 작업에는 Hashtable을 이용하는 버전보다는 배열 형태를 이용하는 SetFormVariables가 더 유용하게 느껴질 것이다. – 실제로, 두 가지 모두 나름대로의 장단점을 갖고 있기 때문에 상호보완하며 혼용하면 손쉽게 문제를 해결할 수 있을 것이다.

컨트롤과 관련해서 개발자를 괴롭히는 또다른 문제는 DB에서 가져온 값을 손쉽게 컨트롤에 매핑할 수 없을까? 라는 문제다. SetFormVariables 함수와 XML 형태의 매핑 함수를 개발하는 방법은 각자가 얼마든지 개발할 수 있을 것이다. 따라서, 여기서는 그런 간단한 문제 대신에 DB의 요소들을 객체로 편하게 다룰 수 있는 방법에 대해 보여줄 것이다.

NHibernate 간편 가이드

이 부분은 전적으로 NHibernate Quick Guide를 토대로 작성할 것이며, 번역판이라 생각해도 될 것이다.

Hibernate은 본래 Java에서 쓰이는 프레임워크로 ERD(Entity-Relationship Diagram) 로 그려진 관계형 데이터가 표현할 수 있는 영역과 객체지향 언어가 표현할 수 있는 영역이 일치하지 않는 부분을 중간에서 매핑 시켜주는 프레임워크다. 이러한 매핑툴을 ORM(Object-Relational Mapping) 도구라 부른다. 자바 버전의 Hibernate을 닷넷 버전으로 포팅한 것을 NHibernate이라 한다. VS 2005에서는 이러한 툴을 자체적으로 내장할 예정이며, ObjectSpaces라는 이름으로 제공될 것이다. ORM은 종종 ORM(Object-Relationship Modeling) 으로 쓰이기도 한다.

NHibernate은 http://nhibernate.org 에서 자세한 정보를 얻을 수 있고, http://nhibernate.sourceforge.net 에서 다운받을 수 있다.

다운로드를 받고, 설치를 했으면 VS.NET에서 새로운 프로젝트를 시작한다.

SQL Server에서는 다음과 같은 테이블을 작성한다.

CREATE TABLE [dbo].[users] (
[LogonID] [nvarchar] (20) COLLATE Korean_Wansung_CI_AS NOT NULL ,
[Name] [nvarchar] (40) COLLATE Korean_Wansung_CI_AS NULL ,
[Password] [nvarchar] (20) COLLATE Korean_Wansung_CI_AS NULL ,
[EmailAddress] [nvarchar] (40) COLLATE Korean_Wansung_CI_AS NULL ,
[LastLogon] [datetime] NULL
) ON [PRIMARY]

프로젝트 참조에 NHibernate.dll을 추가하고, 클래스에는 using NHibernate.Cfg;와 using NHibernate;을 추가한다.

DB의 테이블을 클래스와 매핑시킬 것이기 때문에 먼저 User 클래스에 대한 정의가 있어야한다.


User 클래스 정의

NHibernate은 클래스와 DB 사이의 매핑을 XML 파일로 구성한다. 이 XML 파일은 “클래스명.hbm.xml”을 사용한다. 여기서 작성한 user.hbm.xml의 정의는 다음과 같다.


user.hbm.xml 정의

여기서 알 수 있는 것처럼 DB의 컬럼명, 데이터 타입, 데이터의 길이까지 지정할 수 있다는 것을 알 수 있다. CLASS 태그는 중요하다. 필자는 Mona.Web.Samples라는 프로젝트를 생성했고, 컴파일된 DLL은 Mona.Web.Samples.DLL이 된다. 네임 스페이스 또한 Mona.Web.Samples이며 User 클래스의 전체 이름은 Mona.Web.Samples.User가 된다. class의 이름은 “클래스명, DLL명”이 되어야 한다. table 속성은 DB에서 사용할 테이블의 이름을 지정한다. 이 XML 파일은 DLL안에 포함되어야만 NHibernate에서 오류없이 XML 파일의 내용을 읽어들일 수 있다.

XML 파일을 DLL에 포함시키기

XML 파일 위에서 마우스 오른쪽 버튼을 클릭하고 [속성]을 선택한다.

빌드 작업을 보면 “포함 리소스”를 선택할 수 있다. [포함 리소스]를 선택하면 컴파일시에 XML 파일에 DLL에 자동으로 포함된다. 이제, NHibernate을 이용할 준비가 되었으니 사용법을 살펴보자.

NHibernate 사용하기

NHibernate에서는 먼저 XML 설정 파일을 읽어들인다. 설정 파일을 읽어들이고, 각 세션을 유지하기 위해 factory를 생성한다. factory는 각 세션을 유지하고, 세션에서는 트랙잰션을 처리할 수 있다. 다음은 새로운 사용자를 추가하는 예제이다.

cfg.AddXmlFile()은 XML 파일을 DLL에 포함시키지 않은 경우에 사용할 수 있는 코드인데 개발중일 때를 제외하면 잘 사용하지 않는다. SQL 쿼리를 사용하지 않고 새로운 사용자를 DB에 성공적으로 집어넣었다. 작업이 끝나면 반드시 해당 세션을 저장해야하고, 변경사항을 DB에 반영하기 위해 Commit을 수행하면 된다.

이 코드는 factory에서 새로운 세션을 열고, “joe_cool”로 검색되는 사용자를 가져와서 로긴 시간만 변경하는 예이다. Flush()를 호출해서 해당 데이터의 변경사항을 반영할 수 있다.

이 예제는 범주를 만들어서 사용자를 검색하는 예제이며, 각 사용자 목록을 어떻게 처리할 수 있는지를 보여주기 위한 예제이다. SQL 쿼리문에서는 WHERE 절에 해당하는 부분을 NHibernate에서는 CreateCriteria()를 사용해서 대신 처리할 수 있는 것이다. Expression.Gt()는 WHERE 절에서 >를 대신하는 것이다. Gt는 “Greater than”을 뜻하며 Ge는 “Greater than or equal”을 뜻한다. 이런 의미로 Lt, Eq와 같은 다양한 메서드들이 존재한다.

NHibernate은 테이블과 클래스의 1:1 매핑 뿐만 아니라 1:N, M:N과 같은 다양한 관계의 매핑도 처리할 수 있으므로 사용법을 참고하면 좋을 것이다.

마치며

이제, 독자에게 남겨진 작업은 Get/SetFormVariables() 같은 메서드를 이용해서 컨트롤 값을 가져오고, NHibernate과 같은 프레임워크를 이용한 객체 사이를 자동으로 처리할 수 있게 해주는 부분이다. Hashtable로 가져온 컨트롤의 이름과 NHibernate 객체의 속성을 매핑해주는 부분을 작성한다면 ASP.NET 페이지에서 DB 까지의 처리를 보다 빠르고 편하게 할 수 있을 것이다. Hibernate 프레임워크를 이용한다고 위의 예제처럼 Hibernate 코드를 직접 조작하는 것은 결국 웹 프로그래밍에서의 무의미한 반복과 별차이가 없지않은가? 그러니, 반복작업을 줄여줄 함수나 도구를 독자 스스로 만들어서 사용해보기 바란다.

본문 소스코드 다운로드

참고자료

NHibernate 공식사이트(http://nhibernate.org) : 소스 다운로드에서 관련 설명서, 기사 모음까지 제공하고 있어 NHibernate 출발점으로 굉장히 유용하다.

NHibernate( http://www.theserverside.net/articles/showarticle.tss?id=NHibernate ): 1:N, M:N에서의 Hibernate 사용법등에 대한 설명이 자세히 되어 있다.

자바 개발자 채용 공고

우리 회사의 채용 공고입니다.
아래 채용 공고를 보시고 관심 있으신 분은 joshuasong @ netville.co.kr 로 메일을…
(이력서, 자기소개서 첨부하지 마시고…!!)

회사 홈페이지는 www.netville.co.kr 입니다.

————————————————-

1. 제목 : 정규직 개발부분 경력자 채용

2. 자격요건 :

– 적극적인 품성의 소지자로서

– S/W 의 미래에 희망을 가지고 있는 분

– 경력 2-3년차 정도의 개발자로서 JAVA/JSP 등의 경험자

– 대졸 출신으로 전산관련학과 혹은 전산관련 교육이수자 우대

– 정보처리기사 자격증 소지자 우대

3. 채용인원 : 2 – 3 명

4. 소속 : e-Biz사업팀 배속 예정

5. 채용절차 : 이력서 검토 -> 면접 -> 입사확정

6. 제출서류 : 이력서, 자기소개서 각 1부

유치하지만 이런것도…

유치하지만 이런것도 가끔은 써야하는 상황이 있다.


[CODE]<%<BR>String comment = “12311_#_92_#_I am not gonna be here.”;<BR><BR>String[] result = comment.split(“_#_”);<BR><BR>out.println (“<br/>Agreements : ” + result[0]);<BR>out.println (“<br/>Disagreements : ” + result[1]);<BR>out.println (“<br/>Comment : ” + result[2]);<BR><BR><BR>%>[/CODE]


JSP 소스 코드




Agreements : 12311
Disagreements : 92
Comment : I am not gonna be here.


결과

Aspect Oriented Programming (AOP)

저자: 김대곤(private @ roadtohome.com)
출처 : 한빛 네트워크. (http://network.hanbitbook.co.kr/view.php?bi_id=968)





본 기사는 Aspect Oriented Programming에 대해 간략한 소개글이다. 아직까지는 생소한 분야일 수 있겠지만, 점점 더 많이 듣게 되리라 생각된다. AOP를 설명하는데 있어서 자주 등장하는 네 개의 용어들(Aspect, Cross-cutting concern, Point-cut, Advice)를 설명함으로서 AOP가 왜 등장하게 되었으며, AOP가 제시하는 해결책에 대해 살펴볼 것이다. 먼저 “Aspect”, “Oriented”, “Programming”에서 생소한 단어는 단연 “Aspect”일 것이다. 야후 사전의 정의에 따르면, “Aspect”은 “사물의 면, 국면, 관점”으로 정의되어 있다. 소프트웨어 시스템은 여러가지 관점에서 바라볼 수 있다, 또는 여러 가지 단면을 가지고 있고 있다. 예를 들어, 자금 이체를 하는 프로그램을 작성한다고 생각해 보자. 출금계좌와 입금계좌, 그리고 이체금액을 입력받아 SQL 문장 또는 함수 한 번 돌리는 것으로 끝나는가? 절대 아니다. 먼저, 해킹을 방지하기 위해 사용자가 적절한 보안 프로그램을 설치했는지 점검하는 코드도 있어야 하고, 사용자가 인증되었는지 점검하는 코드도 써야 하고, 상대방 은행에서 적절하게 처리되었는지도 점점해야 하고, 혹시 사용자가 이체버튼을 두 번 누른 것은 아닌가 체크해야 하고, 시스템 로그도 남겨야 한다. 즉, 구현하려고 하는 기능 뿐 아니라 보안, 인증, 로그, 성능와 같은 다른 기능들도 녹아 있어야 한다. 어쩌면 이체를 위한 코드보다 잡다한 다른 측면의 문제들을 다루는 코드가 더 길어질 수 있다. 이런 코드들은 입금이나 출금 같은 다른 곳에서 들어가야 한다. 구현하려고 하는 비즈니스 기능들을 Primary(Core) Concern, 보안, 로그, 인증과 같이 시스템 전반적으로 산재된 기능들을 Cross-cutting concern이라고 부른다. AOP는 Cross-cutting concern를 어떻게 다룰 것인가에 대한 새로운 패러다임이라고 할 수 있다.

AOP는 구조적 방법론에서 객체지향 방법론으로 전환처럼 시스템 개발에 관한 전체적인 변화는 아니다. Object-Oriented Programming이 Aspect-Oriented Programming으로 대체되는 일은 없을 것이다. AOP는 구조적 방법론에도 적용될 수 있고, 다른 방법론에도 다 적용될 수 있지만, 주로 객체지향방법론이 가지는 단점을 보완하는 것으로 묘사되고 있다. 그러면 객체지향 프로그래밍이 또는 다른 이전의 프로그래밍 기법들이 Cross-cutting Concern를 어떻게 다루는지 알아보자. 매우 간단하다. Primary Concern를 구현한 프로그램에 함께 포함시켰다. 그것이 단 한 줄의 메소드 호출이라 하더라도. 많은 프로그래머들은 거의 모든 프로그램에 산재된 로그하는 단 한 줄의 코드를 찾아서 바꾸어 본 경험이 있을 것이다. 또는 간단하게 생각하고 프로그램을 수정하려고 했는데, 도데체 어디를 수정해야 되는지 모르게 코드가 길고, 알 수 없는 코드들이 자리를 차지하고 있을 때의 난감함. Primary concern, Cross-cutting concern이 하나의 프로그램 안에 들어가게 되면, 프로그램을 이해하기가 힘들고, Cross-cutting concern 코드가 여기저기에 산재되어 수정하기 힘들게 된다. 당연히 생산성 떨어지고, 품질 떨어지고, 유지보수 비용 많이 들게 된다.

그럼 AOP는 Cross-cutting concern를 어떻게 처리하는가? 이것도 매우 간단하다. 새로운 아이디어라고 할 수도 없다. Primary Concern 구현하는 코드 따로, Cross-cutting concern 구현하는 코드 따로 쓰고, 나중에 두 개 조합하게 완벽한 어플리케이션 만들겠다는 것이다. 기술 용어로 쓰면, Advice(Cross-cutting concern 구현한 코드)와 Primary concern 구현한 코드를 Point-cut 정보를 이용해서 Weaving(조합)하는 것이 AOP가 이 문제를 다루는 방법이다.



기술적 용어로서의 “Aspect”은 “Advice”와 “Point-cut”을 함께 지칭하는 단어이다. Point-cut은 어떤 Advice를 Code 어느 위치에 둘 것인가 하는 것이다. 예를 들면, 로그 기능을 구현한 Advice는 Code 속에 있는 모든 public 메소드가 수행되고 나면, 그 마지막에 실행되어라 라고 지정한 것이라 할 수 있다.

이전까지의 객체지향 프로그래밍은 Cross-cutting concern을 정적으로 어플리케이션에 결합시킨 반면 AOP는 동적으로 Cross-cutting concern를 다룬다고 표현하기도 합니다. 용어에서도 알 수 있듯이 AOP는 소프트웨어 엔지니어링 원칙 중에 하나인 “Separation of concern”를 구현하려고 하고 있습니다. 이러한 문제들을 다루고 있는 분야 중에 하나는 디자인 패턴할 수 있고, 예를 들어, Visitor 패턴은 정적인 구조를 동적으로 바꾸려고 합니다. AOP가 현재까지 나온 방법들 중에서 Cross-cutting concern를 다루는 가장 좋은 방법인가 하는 질문엔 아직 답하긴 힘들 것 같습니다. 그럼에도 분명 언제가는 책상 위에 관련 서적 한 권 있어야 할 것 같은 분야가 될 것 같습니다.

Use Hibernate and JSF with Less Coding

이 글은 http://blog.exadel.com/?p=8 에서 가져왔습니다.
THIS ARTICLE IS COPIED FROM http://blog.exadel.com/?p=8
The copyright of the article is owned by below person. I swear to delete this when the author request to delete the article from my blog.

Author: Igor Shabalov


As you can see from the heading, I’d like to talk a bit about JSF and Hibernate. Both technologies promise us better results with less code. Perfect, this is exactly what I’m looking for. I strongly believe that less coding is better, even if it comes with a price.
To demonstrate this, I will build a realistic example that interacts with both user and database using JSF and Hibernate. Both technologies promise us a truly declarative way of coding, hiding most of the complexity under the hood. This is good, but there is also a price for this that I’ll talk about later.

Use Case

I took a simple example from a developer’s everyday life: A user views or edits a timesheet. TimeSheet is a collection of zero or more Records, usually one per day, with information about Task, Project and hours. I’d like to see and edit the entire TimeSheet (all records for the current week) in a single page.
Here are several UML Diagrams to sketch this example out.


Figure 1: Use Case Model


Figure 2: Class Diagram


Figure 3: Activity Diagram

In fact, if you get already bored going through all of this, you can download the live example application from here, get it into you favorite IDE (my favorite is Exadel Studio Pro, of course), and see what’s in it. It should work “as is” with no database required, I used HSQL’s in-memory configuration, the perfect solution for such a case. You can download example and use Eclipse menu “Import Existing Project” right from zip file. At least on my computer it works I used JDK 1.5 generics in my example, so, you will need JDK 1.5.


Figure 4: MVC unleashed

But if you bother to read more, I’ve put a little bit more theory and commentary below.

Visual Components

To implement something like this, you need a good presentation framework that supports a component-based approach. You need a rich set of standard components, but you also need a good way to create and use special components. JSF is perfect for standard components and user interface controls, but it is not set up as well for custom components. Tiles is a better technology for this, but not all JSF implementations are compatible with Tiles. In my example, I use MyFaces, a complete JSF implementation from the Apache Foundation that also works with Tiles.

Hibernate Session and Database Connection

I assume that you already know a little bit about this problem. You need to pay special attention to the moment when you obtain or free a Hibernate Session (and effectively a Database Connection) in your application. There are two major reasons for that:

A Hibernate Session has a one-to-one relation (OK, almost) to a Database Connection. When you get a Session, you effectively get a Connection. And, we are always told that a connection to database is a valuable resource that needs to be used very carefully. For example, it is not good to keep a Database Connection taken between requests.
A Hibernate Session has a special relationship with objects that are loaded by it. The Hibernate Runtime keeps track of all modifications and generates queries to synchronize the object state with the database. If you keep a persistent object somewhere, but forget about the Session, you cannot do anything with this object (ok, almost).
As a result, we were told to use the “One Session per Request” pattern. You cannot keep persistent objects between requests, you need to store primary key and load objects in every request or use the “detach/attach” technique, which is basically same thing. And, you cannot keep queries open.


Session per Session

Instead of this, what I do is use the “One Session per Session” approach. A Hibernate Session gets created when a Web Session is created, and then kept till the Web Session expires. You can either keep Database Connection or use the “disconnect/reconnect” technique. Personally, I believe, that in many cases, you can keep a Database Connection between requests. Most real business applications are intended to be used by a limited number of users.

Personally, I hate the situation where a developer loads an entire database into memory in order to save database connections. Memory is cheap, but still limited! Keep the data in the database and let database vendors worry about efficient memory use. If database vendors try to ration allowable connections, use an open source product, like MySQL.
Or, you can implement a more middle-of-the-road approach. For example, keep the Database Connection opened when you have something like an opened scrollable query on hand, but otherwise use the “disconnect/reconnect” technique.
In the end, the result is clear code, less code overhead, more effective memory use, and fewer database round-trips.
But, everything has a price. In our case, we need to pay special attention to the Hibernate Session Cache. In our case, if somebody loads TimeSheet from user A into memory, then for user B and then repeats that for 10,000 more users, we get into trouble. At any given moment, we will see just one TimeSheet, the current one kept in Web Session, but the Hibernate Session will have all 10,000 of them. This is wrong.

Here is where Business Transaction becomes our good friend! All we need to do is remove TimeSheet from Hibernate Session either on entering or on leaving Business Transaction. In my example, I’m doing this on entrance, using a session.evict() call. This is why we need Business Transaction and why Page Flow is so important, because Page Flow naturally defines transaction boundaries.

Business Transactions

If you take a look to Activity Diagram, you will see Business Transaction. The User can navigate between “View” and “Edit” forms working with the same data, TimeSheet, which is essentially the Business Transaction Context. This Context is properly initialized at the beginning of the transaction; however, we need to pays some special consideration to the end.

Page Flow

Turning to the Page Flow, I have to admit JSF Page Flow is still “under construction”. I have a dream to eventually present you with the completed Page Flow the JSF gurus will deliver. They were talking about integration with Spring Web Flow, so perhaps I will finally be able to see the point of using Spring!

However, JSF Page Flow does exist now. The default implementation is good enough, and I’m using it in my example. Here is a picture of how it looks:


Figure 5: JSF Page Flow

No Transport Beans, Please

In one my previous projects, I suffered through a painful experience with Transport Objects. I will never ever do that again. I recommend using your persistent Model objects anywhere in screens, and let Hibernate unleash its full power. Keep your Presentation Tier close to the Service and Data Tiers. By close, I mean within the same JVM. If you need to expose some of your services (and data) to the outside world, then use the special Services Externalization interface, basically a wrapper around existing services. From this point, you can use Transport Beans, XML, or whatever you want to move data.

Front Controller, Back Controller

I used Front Controller in my example. It’s very natural for JSF, because it is intended for use with backing beans to implement view-related functions. You can put it anywhere, but I suggest you to keep it separated from “real” controller (Business Service) functions.


Figure 6: Front Controller, Back Controller

Here is a more complicated illustration to show how this works. You can see all this implemented in my example. The Form uses a JSF DataTable component and needs a special DataTableModel. It just display objects from the Model Tier, TimeSheetRecrd. TimeSheetView (it is actually a Front Controller, despite it’s name!) holds all the view-related functions, like saveTimeSheetButton(). TimeSheetService is a typical Business Service implementation, with functions like saveTimeSheet().


Conclusion

I hope you found my example helpful. I didn’t try to make a full blueprint; I just tried to highlight some important issues. And, this isn’t the end at all. I will continue my series of articles about JSF and Hibernate. This time, I didn’t pay too much attention to Database Connection. This is an area that I will revisit soon, when I plan on talking about “scrollable” queries.
Finally, as you can see, my example continues to use the “Injection of Control” pattern that I discussed last time!

Best regards,
Igor Shabalov

[펌] SW 거장들이 국내 개발자들에게 던지는 충고

이 글은 아래 URL에서 copy-paste하였습니다.
http://www.it-solutions.co.kr/news/news_view.asp?code=03&news_id=696






SW 거장들이 국내 개발자들에게 던지는 충고
“언어·자격증 갖춰 해외 시장으로 눈 돌려라” 2004/11/30

영어 실력, 국제 공인 자격증, 전문성 갖춰 세계 시장으로 무대를 옮겨라. 세계적인 소프트웨어 거장 이바 야콥슨 박사와 오라클 교육담당 존 홀 수석부사장이 국내 소프트웨어 개발자에게 이같은 충고를 전했다. 이 두 거장은 최근 시점을 달리해 방한했지만 전하는 메시지에는 공통점들이 많았다.
우선 야콥슨 박사와 홀 수석부사장이 공통으로 국내 개발자들의 취약점으로 지적한 것은 ‘언어’문제였다. 새로운 기술 서적이 나와도 번역되기를 기다려야 하기 때문에 영어권 나라의 개발자보다 정보습득이 늦고 영어로 의사소통하는 것이 원활하지 못하다는 것이다.

최근 방한한 이 두 소프트웨어 거장들은 국내의 IT개발자 교육에 주력할 계획이다. 특히 야콥슨 박사는 국내에 자신의 이름을 딴 이바야콥슨컨설팅(IJC)코리아를 설립, 개발자 교육을 시작했다. 존 홀 수석부사장은 OCM이라는 기존 OCP보다는 상위 수준을 측정하는 자격증을 소개하면서 한국오라클유니버시티(OEU)의 영업 인력을 2배로 늘이는 등 교육사업을 강화하겠다고 발표했다.
존 홀 오라클 수석부사장은 국내 개발자들에게 “영어 실력을 쌓고 자신의 능력을 입증할만한 자격증을 획득하라”고 충고했다. 홀 수석부사장은 “OCM을 취득한 인도인들이 미국에서 취업한 사례가 많은데 이는 영어로 의사소통이 가능했기 때문”이라고 덧붙였다. 한국과 일본 개발자들의 여건은 비슷한데 이 두 나라의 해외 진출이 저조한 이유 역시 언어문제인 것으로 풀이했다.
개발자들이 자산의 정보를 업그레이드하고 신기술을 계속 습득하는 것은 교육을 통해서 가능해진다. 대부분의 소프트웨어가 외산이기 때문에 외국 기술을 더 빨리, 정확하게 얻고자 한다면 영어 실력을 향상할 수밖에 없을 것이다.

이바 야콥슨 박사가 다른 나라보다 한국에 가장 먼저 IJC 지사를 설립한 이유에 대해 ‘사업 기회가 있기 때문’이라고 밝혔다. 국내 개발자들을 유럽이나 미국 수준으로 향상시키기 위해 교육이 필요한데 교육에 대한 수요가 많을 것으로 판단했던 것이다. IJC는 올 8월부터 한 과정에 1주일씩, 총 4개 과정으로 이뤄지는 교육 프로그램을 가동했다. IJC는 앞으로 100명의 개발자를 교육할 것을 목표로 하고 있다.

한국오라클은 OEU를 통해 최소 3일에서 5일 등 다양한 교육프로그램을 실시하며 교육에 대한 만족도도 핵심기술이 92%, e-비즈니스 스위트가 93%로 높은 것으로 나타났다. OCM을 취득한 사람은 전세계에 161명이 있으며 이 자격증은 상위 1%에게 주는 것으로 2일 동안 모든 상황에 대해 개발자가 빨리 대처하는 능력을 검증하게 된다. 국내에는 한국오라클 직원 7명을 포함해 총 27명이 최근 OCM을 취득했다.

외국 업체들이 국내 개발자 교육에 대한 관심을 가지는 이유는 국내 IT시장에 비해 수준 높은 개발자들이 적다고 판단했기 때문이다. 한국 IT시장이 계속 성장하고 발전하려면 개발자들의 실력이 향상돼야 하며 이를 위해서는 교육이 중요하다는 것이다. IJC코리아와 한국오라클의 경우 매출에서 교육부분이 차지하는 비중은 컨설팅, 소프트웨어 등 다른 분야보다 작지만 사전 영업으로서 중요한 역할을 하기도 한다.
IJC코리아는 우선 내부 인력들부터 양질의 교육을 받게 한 후 이들이 자사의 고객과 협력사들에게 교육을 전달하도록 한다. 한국오라클은 사용자에게 자사의 제품을 검토할 수 있도록 하고 이를 소프트웨어 판매로 이어간다는 전략이다.

박해정 기자 hjpark@it-solutions.co.kr