Stay Hungry. Stay Foolish. Don't Be Satisfied.

[Spring 레퍼런스] 24장 JCA CCI

이 문서는 개인적인 목적이나 배포하기 위해서 복사할 수 있다. 출력물이든 디지털 문서든 각 복사본에 어떤 비용도 청구할 수 없고 모든 복사본에는 이 카피라이트 문구가 있어야 한다.



24. JCA CCI


24.1 소개

Java EE는 엔터프라이즈 인포메이션 시스템(EIS, Enterprise Information Systems)에 대한 표준화된 접근의 명세 JCA(J2EE Connector Architecture)를 제공한다. 이 명세는 여러 부분으로 나누어져 있다.

  • 커넥터 프로바이더인 SPI (Service provider interfaces)는 반드시 구현해야 한다. 이러한 인터페이스는 Java EE 애플리케이션 서버에 배포할 수 있는 리소스 어댑터다. 이런 시나리오에서 서버는 커넥션 풀, 트랜잭션, 보안(관리 모드)를 관리한다. 애플리케이션 서버는 클라이언트 애플리케이션 외부에 있는 구성도 관리한다. 애플리케이션 서버 없이도 커넥터를 사용할 수 있는데 이 경우에는 애플리케이션이 커넥터를 직접 구성해야 한다.(비관리 모드)
  • 애플리케이션이 사용할 수 있는 CCI (Common Client Interface) 는 커넥터와 상호작용하므로 EIS로 통신한다. 로컬 트랜잭션 경계에 대한 API도 제공된다.

스프링 CCI 지원은 스프링 프레임워크의 일반적인 리소스 관리와 트랜잭션 관리 기능을 사용하면서 일반적인 스프링 방식으로 CCI 커넥터에 접근하는 클래스를 제공한다.

Note
커넥터의 클라이언트 측이 항상 CCI를 사용하는 것은 아니다. 어떤 커넥터는 자신만의 API를 가지고 Java EE 컨테이너의 시스템 규약(커넥션 풀링, 전역 트랜잭션, 보안)을 사용하는 JCA 리소스 어댑터만 제공하기도 한다. 이렇게 커넥터에 특화된 API를 스프링이 따로 지원하진 않는다.


24.2 CCI 구성

24.2.1 커넥터 구성

JCA CCI를 사용하는 기반은 ConnectionFactory 인터페이스다. 사용하는 커넥터는 반드시 이 인터페이스의 구현체를 제공해야 한다.

커넥터를 사용하려면 애플리케이션 서버에 커넥터를 배포하고 서버의 JNDI 환경에서 ConnectionFactory를 가져올 수 있어야 한다.(관리 모드) 커넥터를 RAR 파일(resource adapter archive)로 패키징해야 하고 배포와 관련된 정보를 담고 있는 ra.xml이 포함되어 있어야 한다. 리소스의 실제 이름은 배포할 때 지정한다. 스프링 내에서 커넥터에 접근하려면 JNDI 이름으로 팩토리를 가져오는 스프링의 JndiObjectFactoryBean / 를 사용하면 된다.

어플리케이션서버를 사용해서 배포하고 구성하는 대신 애플리케이션에 커넥터를 내장시키는 것도 커넥터를 사용하는 또 하나의 방법이다.(비관리 모드) 스프링에서는 제공된 FactoryBean으로 커넥터를 빈으로 구성할 수 있다. 이 방법을 사용할 때는 클래스패스에 커넥터 라이브러리만 넣으면 된다.(RAR 파일이나 ra.xml 디스크립터가 필요 없다.) 필요하다만 커넥터의 RAR 파일에서 라이브러리만 추출해야 한다.

일단 ConnectionFactory 인스턴스에 접근하고 나면 컴포넌트에 주입할 수 있다. 이러한 컴포넌트는 평범한 CCI API를 기반으로 작성하거나 스프링의 CCI 접근 지원 클래스(CciTemplate 같은)를 기반으로 작성할 수 있다.

Note
비관리 모드에서 커넥터를 사용할 때는 현재 스레드의 현재 전역 트랜잭션에 리소스가 절대 추가되거나 빠지지 않으므로 전역 트랜잭션을 사용할 수 없다. 리소스는 어떤 전역 Java EE 트랜잭션도 인지하지 못한다.


24.2.2 스프링에서 ConnectionFactory의 구성

EIS에 연결하려고 할 때 관리 모드인 경우에는 애플리케이션 서버에서 ConnectionFactory를 얻어야 하고 비관리 모드인 경우에는 스프링에서 직접 ConnectionFactory를 가져와야 한다.

관리 모드에서는 JNDI에서 ConnectionFactory에 접근한다. ConnectionFactory의 프로퍼티는 애플리케이션 서버에서 설정할 것이다.

<jee:jndi-lookup id="eciConnectionFactory" jndi-name="eis/cicseci"/>

비관리 모드에서는 스프링 설정에서 사용할 ConnectionFactory를 JavaBean으로 설정해야 한다. LocalConnectionFactoryBean 클래스가 커넥터의 ManagedConnectionFactory 구현체를 전달하고 애플리케이션 수준의 CCI ConnectionFactory를 노출시켜서 이러한 설정 방법을 제공한다.

<bean id="eciManagedConnectionFactory" class="com.ibm.connector2.cics.ECIManagedConnectionFactory">
  <property name="serverName" value="TXSERIES"/>
  <property name="connectionURL" value="tcp://localhost/"/>
  <property name="portNumber" value="2006"/>
</bean>

<bean id="eciConnectionFactory" class="org.springframework.jca.support.LocalConnectionFactoryBean">
  <property name="managedConnectionFactory" ref="eciManagedConnectionFactory"/>
</bean>
Note
특정 ConnectionFactory를 직접 인스턴스화 할 수는 없다. 커넥터에 대한 ManagedConnectionFactory 인터페이스의 대응되는 구현체를 통해야 한다. 이 인터페이스는 JCA SPI 명세에 포함된 내용이다.


24.2.3 CCI 연결 구성

개발자는 JCA CCI를 사용해서 커넥터의 ConnectionSpec 구현체를 사용해서 EIS에 대한 연결을 구성할 수 있다. ConnectionSpec의 프로퍼티를 설정하려면 전용 어댑터 ConnectionSpecConnectionFactoryAdapter로 대상 커넥션 팩토리를 감싸야 한다. 그래서 전용 ConnectionSpec를 connectionSpec 프로퍼티(내부 빈으로)로 설정할 수 있다.

CCI ConnectionFactory 인터페이스는 CCI 연결을 얻는 두 가지 방법을 정의하고 있으므로 이 프로퍼티는 의무사항은 아니다. ConnectionSpec 프로퍼티 중 일부는 애플리케이션 서버에서 설정하거나(관리모드에서) 대응되는 로컬 ManagedConnectionFactory 구현체에서 설정할 수 있다.

public interface ConnectionFactory implements Serializable, Referenceable {
  ...
  Connection getConnection() throws ResourceException;
  Connection getConnection(ConnectionSpec connectionSpec) throws ResourceException;
  ...
}

스프링은 해당 팩토리의 모든 동작을 사용할 수 있도록 ConnectionSpec 인스턴스를 지정할 수 있는 ConnectionSpecConnectionFactoryAdapter를 제공한다. 이 어댑터의 connectionSpec 프로퍼티를 지정했다면 어댑터는 인자 없이 getConnection 계열을 사용하고 지정하지 않았다면 ConnectionSpec 인자와 함께 사용한다.

<bean id="managedConnectionFactory"
    class="com.sun.connector.cciblackbox.CciLocalTxManagedConnectionFactory">
  <property name="connectionURL" value="jdbc:hsqldb:hsql://localhost:9001"/>
  <property name="driverName" value="org.hsqldb.jdbcDriver"/>
</bean>

<bean id="targetConnectionFactory"
    class="org.springframework.jca.support.LocalConnectionFactoryBean">
  <property name="managedConnectionFactory" ref="managedConnectionFactory"/>
</bean>

<bean id="connectionFactory"
    class="org.springframework.jca.cci.connection.ConnectionSpecConnectionFactoryAdapter">
  <property name="targetConnectionFactory" ref="targetConnectionFactory"/>
  <property name="connectionSpec">
    <bean class="com.sun.connector.cciblackbox.CciConnectionSpec">
      <property name="user" value="sa"/>
      <property name="password" value=""/>
    </bean>
  </property>
</bean>


24.2.4 단일 CCI 연결의 사용

단일 CCI 연결을 사용하고 싶은 경우를 위해서 스프링은 ConnectionFactory 어댑터로 이를 관리할 수 있게 한다. SingleConnectionFactory 어댑터 클래스가 단일 연결을 지연해서 열고 애플리케이션 종료 시 해당 빈이 파괴될 때 연결을 닫는다. 이 클래스는 물리적 연결에 기반을 둬서 같은 연결을 공유하면서 상황에 따라 적절하게 동작하는 전용 Connection 프록시를 노출할 것이다.

<bean id="eciManagedConnectionFactory"
    class="com.ibm.connector2.cics.ECIManagedConnectionFactory">
  <property name="serverName" value="TEST"/>
  <property name="connectionURL" value="tcp://localhost/"/>
  <property name="portNumber" value="2006"/>
</bean>

<bean id="targetEciConnectionFactory"
    class="org.springframework.jca.support.LocalConnectionFactoryBean">
  <property name="managedConnectionFactory" ref="eciManagedConnectionFactory"/>
</bean>

<bean id="eciConnectionFactory"
    class="org.springframework.jca.cci.connection.SingleConnectionFactory">
  <property name="targetConnectionFactory" ref="targetEciConnectionFactory"/>
</bean>
Note
이 ConnectionFactory 어댑터는 ConnectionSpec로 직접 설정할 수 없다. 특정 ConnectionSpec에 대한 단일 커넥션이 필요한 경우에는 SingleConnectionFactory와 통신하는 ConnectionSpecConnectionFactoryAdapter로 중개해서 사용해라.


24.3 스프링의 CCI 접근에 대한 지원 사용하기

24.3.1 레코드(Record) 변환

JCA CCI 지원 중 하나는 CCI 레코드를 편리하게 조작할 수 있도록 하는 것이다. 개발자는 스프링의 CciTemplate를 사용해서 레코드를 생성하고 레코드에서 데이터를 추출하는 전략을 지정할 수 있다. 다음 인터페이스들은 애플리케이션에서 레코드를 직접 다루길 원하지 않는 경우 입력과 출력 레코드를 사용하는 전략을 설정할 것이다.

입력 Record를 생성하기 위해 개발자가 RecordCreator 인터페이스의 전용 구현체를 사용할 수 있다.

public interface RecordCreator {
  Record createRecord(RecordFactory recordFactory) throws ResourceException, DataAccessException;
}

여기서 보듯이 createRecord(..) 메서드는 파라미터로 RecordFactory를 받는다. 이 파라미터는 사용한 ConnectionFactory의 RecordFactory를 가리키는데 IndexedRecord나 MappedRecord를 생성하는 데 사용한다. 다음 예제는 RecordCreator 인터페이스와 색인 되거나(indexed) 매핑된(mapped) 레코드를 사용하는 방법을 보여준다.

public class MyRecordCreator implements RecordCreator {
  public Record createRecord(RecordFactory recordFactory) throws ResourceException {
    IndexedRecord input = recordFactory.createIndexedRecord("input");
    input.add(new Integer(id));
    return input;
  }
}

EIS에서 다시 데이터를 받을 때 출력 Record를 사용할 수도 있다. 그러므로 출력 Record의 데이터를 추출하기 위해 RecordExtractor 인터페이스의 특정 구현체를 스프링 CciTemplate에 전달할 수 있다.

public interface RecordExtractor {
  Object extractData(Record record) throws ResourceException, SQLException, DataAccessException;
}

다음 예제는 RecordExtractor 인터페이스를 사용하는 방법을 보여준다.

public class MyRecordExtractor implements RecordExtractor {
  public Object extractData(Record record) throws ResourceException {
    CommAreaRecord commAreaRecord = (CommAreaRecord) record;
    String str = new String(commAreaRecord.toByteArray());
    String field1 = string.substring(0,6);
    String field2 = string.substring(6,1);
    return new OutputObject(Long.parseLong(field1), field2);
  }
}


24.3.2 CciTemplate

CciTemplate은 핵심 CCI 지원 패키지 (org.springframework.jca.cci.core)의 중심이 되는 클래스다. CciTemplate는 리소스의 생성과 해제를 관리해서 CCI를 쉽게 사용할 수 있도록 한다. 그리고 연결을 닫는 것을 잊어버리는 등의 일반적인 오류를 피할 수 있게 해준다. CciTemplate는 연결과 상호작용(interaction) 객체의 생명주기를 관리해서 애플리케이션 코드는 애플리케이션에서 입력 레코드를 생성하고 출력 레코드에서 애플리케이션 데이터를 추출하는 데 집중하도록 한다.

JCA CCI 명세는 EIS에서 작업을 호출하는 별개의 두 메서드를 정의하고 있다. CCI Interaction 인터페이스는 두 가지 execute 메서드 시그니처를 제공한다.

public interface javax.resource.cci.Interaction {
  ...
  boolean execute(InteractionSpec spec, Record input, Record output) throws ResourceException;

  Record execute(InteractionSpec spec, Record input) throws ResourceException;
  ...
}

호출된 템플릿 메서드에 따라 CciTemplate이 상호작용에 어떤 execute 메서드를 호출할지 알 수 있다. 어떤 경우에든 제대로 초기화된 InteractionSpec 인스턴스가 필요하다.

CciTemplate.execute(..)는 두 가지로 사용할 수 있다.

  • 직접 Record 인자로 사용하는 방법. 이 경우 CCI 입력 레코드를 전달해야 하고 반환된 객체는 CCI 출력 레코드에 대응된다.
  • 레코드 매핑을 사용해서 애플리케이션 객체를 사용하는 방법. 이 경우에는 대응되는 RecordCreator와 RecordExtractor 인스턴스를 제공해야 한다.

첫번째 방법을 사용하면 템플릿의 다음 메서드를 사용할 것이다. 이 메서드들은 Interaction 인터페이스의 메서드들과 직접 대응된다.

public class CciTemplate implements CciOperations {
  public Record execute(InteractionSpec spec, Record inputRecord)
      throws DataAccessException { ... }

  public void execute(InteractionSpec spec, Record inputRecord, Record outputRecord)
      throws DataAccessException { ... }
}

두번째 방법을 사용하면 인자로 레코드 생성 전략과 레코드 추출 전략을 지정해야 한다. 레코드 변환에 사용하는 인터페이스는 이전 장에서 설명했다. 대응되는 CciTemplate 메서드는 다음과 같다.

public class CciTemplate implements CciOperations {
  public Record execute(InteractionSpec spec, RecordCreator inputCreator)
      throws DataAccessException { ... }

  public Object execute(InteractionSpec spec, Record inputRecord, RecordExtractor outputExtractor)
      throws DataAccessException { ... }

  public Object execute(InteractionSpec spec, RecordCreator creator, RecordExtractor extractor)
      throws DataAccessException { ... }
}

템플릿에 outputRecordCreator 프로퍼티를 설정하지 않으면(다음 장 참고) 모든 메서드가 적절한 CCI Interaction의 execute 메서드를 두 개의 파라미터 InteractionSpec와 입력 Record로 호출할 것이고 이 메서드는 출력 Record를 반환한다.

CciTemplate은 RecordCreator 구현체 외부에서 IndexRecord와 MappedRecord를 생성하는 createIndexRecord(..)와 createMappedRecord(..) 메서드도 제공한다. 해당 CciTemplate.execute(..) 메서드에 전달할 Record 인스턴스를 생성하기 위해서 DAO 구현체 내에서 이 메서드를 사용할 수 있다.

public class CciTemplate implements CciOperations {
  public IndexedRecord createIndexedRecord(String name) throws DataAccessException { ... }

  public MappedRecord createMappedRecord(String name) throws DataAccessException { ... }
}


24.3.3 DAO 지원

스프링의 CCI 지원은 DAO에 대한 추상 클래스를 제공하고 ConnectionFactory나 CciTemplate 인스턴스의 주입을 지원한다. 클래스 이름은 CciDaoSupport이고 setConnectionFactory와 setCciTemplate 메서드를 제공한다. 내부적으로 이 클래스는 하위클래스에서 데이터 접근 구현체를 구현할 수 있게 CciTemplate 인스턴스를 노출하는 해당 ConnectionFactory를 위해서 CciTemplate 인스턴스를 생성할 것이다.

public abstract class CciDaoSupport {
  public void setConnectionFactory(ConnectionFactory connectionFactory) { ... }
  public ConnectionFactory getConnectionFactory() { ... }

  public void setCciTemplate(CciTemplate cciTemplate) { ... }
  public CciTemplate getCciTemplate() { ... }
}


24.3.4 출력 레코드 자동 생성

사용한 커넥터가 입력 레코드와 출력 레코드를 파라미터로 받는 Interaction.execute(..) 메서드(즉, 이 메서드는 적절한 출력 레코드를 반환하지 않고 원하는 출력 레코드를 전달해야 한다.)만 지원한다면 응답을 받을 때 JCA 커넥터가 작성하는 출력 레코드를 자동으로 생성할 수 있게 CciTemplate의 outputRecordCreator 프로퍼티를 설정할 수 있다. 그러면 템플릿의 호출자에게 이 레코드를 반환할 것이다.

위 목적에 맞게 이 프로퍼티에는 RecordCreator 인터페이스의 구현체가 담겨 있다. RecordCreator 인터페이스는 Section 24.3.1, “레코드(Record) 변환”에서 이미 설명했다. outputRecordCreator 프로퍼티는 CciTemplate에서 직접 지정해야 한다. 애플리케이션 코드에서는 다음과 같이 작성한다.

cciTemplate.setOutputRecordCreator(new EciOutputRecordCreator());

아니면 스프링 설정에서 전용 빈 인스턴스로 CciTemplate을 설정한다.(추천하는 방법)

<bean id="eciOutputRecordCreator" class="eci.EciOutputRecordCreator"/>

<bean id="cciTemplate" class="org.springframework.jca.cci.core.CciTemplate">
  <property name="connectionFactory" ref="eciConnectionFactory"/>
  <property name="outputRecordCreator" ref="eciOutputRecordCreator"/>
</bean>
Note
CciTemplate 클래스가 스레드 세이프 하므로 공유하는 인스턴스로 설정하는 게 일반적이다.


24.3.5 요약

다음 표는 CciTemplate 클래스의 메커니즘과 CCI Interaction 인터페이스에서 호출되는 해당 메서드를 요약해서 보여주고 있다.

Table 24.1. Interaction execute 메서드의 사용방법

CciTemplate 메서드 시그니처 CciTemplate outputRecordCreator 프로퍼티 CCI Interaction에서 호출되는 execute 메서드
Record execute(InteractionSpec, Record) 설정안함 Record execute(InteractionSpec, Record)
Record execute(InteractionSpec, Record) 설정함 boolean execute(InteractionSpec, Record, Record)
void execute(InteractionSpec, Record, Record) 설정안함 void execute(InteractionSpec, Record, Record)
void execute(InteractionSpec, Record, Record) 설정함 void execute(InteractionSpec, Record, Record)
Record execute(InteractionSpec, RecordCreator) 설정안함 Record execute(InteractionSpec, Record)
Record execute(InteractionSpec, RecordCreator) 설정함 void execute(InteractionSpec, Record, Record)
Record execute(InteractionSpec, Record, RecordExtractor) 설정안함 Record execute(InteractionSpec, Record)
Record execute(InteractionSpec, Record, RecordExtractor) 설정함 void execute(InteractionSpec, Record, Record)
Record execute(InteractionSpec, RecordCreator, RecordExtractor) 설정안함 Record execute(InteractionSpec, Record)
Record execute(InteractionSpec, RecordCreator, RecordExtractor) 설정함 void execute(InteractionSpec, Record, Record)






24.3.6 CCI Connection와 Interaction을 직접 사용하기

CciTemplate도 JdbcTemplate나 JmsTemplate와 같은 방식으로 직접 CCI 연결과 상호작용을 할 수 있게 지원한다. 이는 CCI 연결이나 상호작용에서 여러 작업을 수행하고자 할 때 유용하다.

ConnectionCallback 인터페이스는 CCI Connection에서 임의의 작업을 할 수 있도록 인자로 CCI Connection을 제공한다. 아니면 Connection를 생성한 CCI ConnectionFactory을 제공할 수도 있다. 후자의 경우는 관련 RecordFactory 인스턴스를 가져와서 색인 된/매핑된 레코드를 생성할 때 유용하게 사용할 수 있다.

public interface ConnectionCallback {
  Object doInConnection(Connection connection, ConnectionFactory connectionFactory)
      throws ResourceException, SQLException, DataAccessException;
}

InteractionCallback 인터페이스는 CCI Interaction에서 임의의 작업을 할 수 있도록 CCI Interaction를 제공하고 대응되는 CCI ConnectionFactory를 제공할 수도 있다.

public interface InteractionCallback {
  Object doInInteraction(Interaction interaction, ConnectionFactory connectionFactory)
      throws ResourceException, SQLException, DataAccessException;
}
Note
InteractionSpec 객체는 여러 템플릿 호출 간에 공유할 수도 있고 콜백 메서드마다 내부에서 새로 생성할 수도 있다. 어떻게 되느냐는 오로지 DAO 구현체에 달려있다.


24.3.7 CciTemplate 사용 예제

이번 장의 CciTemplate 사용방법은 IBM CICS ECI 커넥터를 사용해서 ECI 모드로 CICS에 접근하는 방법을 보여준다.

우선 접근할 CICS 프로그램을 지정하고 CICS 프로그램과 상호작용하는 방법을 지정할 수 있도록 CCI InteractionSpec의 초기화가 이뤄져야 한다.

ECIInteractionSpec interactionSpec = new ECIInteractionSpec();
interactionSpec.setFunctionName("MYPROG");
interactionSpec.setInteractionVerb(ECIInteractionSpec.SYNC_SEND_RECEIVE);

이 프로그램은 스프링의 템플릿을 사용해서 CCI를 사용할 수 있고 커스텀 객체와 CCI Records간의 매핑을 지정할 수 있다.

public class MyDaoImpl extends CciDaoSupport implements MyDao {
  public OutputObject getData(InputObject input) {
    ECIInteractionSpec interactionSpec = ...;
    OutputObject output = (ObjectOutput) getCciTemplate().execute(interactionSpec,
        new RecordCreator() {
          public Record createRecord(RecordFactory recordFactory) throws ResourceException {
            return new CommAreaRecord(input.toString().getBytes());
          }
        },
        new RecordExtractor() {
          public Object extractData(Record record) throws ResourceException {
            CommAreaRecord commAreaRecord = (CommAreaRecord)record;
            String str = new String(commAreaRecord.toByteArray());
            String field1 = string.substring(0,6);
            String field2 = string.substring(6,1);
            return new OutputObject(Long.parseLong(field1), field2);
          }
        });

    return output;
  }
}

앞에서 얘기했듯이 CCI 연결과 상호작용에서 직접 동작하도록 콜백을 사용할 수 있다.

public class MyDaoImpl extends CciDaoSupport implements MyDao {
  public OutputObject getData(InputObject input) {
    ObjectOutput output = (ObjectOutput) getCciTemplate().execute(
        new ConnectionCallback() {
          public Object doInConnection(Connection connection, ConnectionFactory factory)
              throws ResourceException {

            // 여기서 작업을 수행한다...
          }
        });
    }
    return output;
  }
}

Note
ConnectionCallback에서 사용한 Connection를 관리할 것이고 CciTemplate가 Connection를 닫을 것이지만 연결에서 생성한 모든 상호작용을 콜백 구현체에서 관리해야 한다.

더 상세한 콜백이 필요하다면 InteractionCallback를 구현할 수 있다. 이 경우 전달한 Interaction를 관리할 것이고 CciTemplate가 Interaction를 닫을 것이다.

public class MyDaoImpl extends CciDaoSupport implements MyDao {
  public String getData(String input) {
    ECIInteractionSpec interactionSpec = ...;
    String output = (String) getCciTemplate().execute(interactionSpec,
        new InteractionCallback() {
          public Object doInInteraction(Interaction interaction, ConnectionFactory factory)
              throws ResourceException {
            Record input = new CommAreaRecord(inputString.getBytes());
            Record output = new CommAreaRecord();
            interaction.execute(holder.getInteractionSpec(), input, output);
            return new String(output.toByteArray());
          }
        });

    return output;
  }
}

위 예제에 포함된 스프링 빈의 해당 설정은 비관리 모드에서는 다음과 같을 것이다.

<bean id="managedConnectionFactory" class="com.ibm.connector2.cics.ECIManagedConnectionFactory">
  <property name="serverName" value="TXSERIES"/>
  <property name="connectionURL" value="local:"/>
  <property name="userName" value="CICSUSER"/>
  <property name="password" value="CICS"/>
</bean>

<bean id="connectionFactory" class="org.springframework.jca.support.LocalConnectionFactoryBean">
  <property name="managedConnectionFactory" ref="managedConnectionFactory"/>
</bean>

<bean id="component" class="mypackage.MyDaoImpl">
  <property name="connectionFactory" ref="connectionFactory"/>
</bean>

관리모드(즉, Java EE 환경)에서는 설정이 다음과 같을 것이다.

<jee:jndi-lookup id="connectionFactory" jndi-name="eis/cicseci"/>

<bean id="component" class="MyDaoImpl">
  <property name="connectionFactory" ref="connectionFactory"/>
</bean>


24.4 operation 객체로 CCI 접근 모델링하기

org.springframework.jca.cci.object 패키지에서는 다른 방법으로 EIS에 접근할 수 있는 지원 클래스가 있다. 이는 스프링의 JDBC operation 객체(JDBC 장을 봐라)와 유사한 재사용 가능한 operation 객체를 통해서 이뤄지고 보통 CCI API를 은닉화할 것이다. 이는 애플리케이션 수준의 입력 객체를 operation 객체에 전달할 것이므로 입력 레코드를 구성하고 받은 레코드 데이터를 애플리케이션 수준의 출력 객체로 변환해서 반환할 수 있다.

Note: 이 접근방법은 내부적으로 CciTemplate 클래스와 RecordCreator / RecordExtractor 인터페이스에 기반을 두고 있고 스프링의 핵심 CCI 지원 기능을 재사용한다.

24.4.1 MappingRecordOperation

MappingRecordOperation는 본래 CciTemplate와 같은 작업을 하지만 미리 구성한 특정 작업을 객체로 나타낸다. MappingRecordOperation는 입력 객체를 입력 레코드로 변환하는(레코드 매핑) 방법을 지정하는 두 가지 템플릿 메서드를 제공한다.

  • 입력 객체를 입력 Record로 변환하는 방법을 지정하는 createInputRecord(..)
  • 출력 Record에서 출력 객체를 추출하는 방법을 지정하는 extractOutputData(..)

이 메서드들의 시그니처는 다음에 나와 있다.

public abstract class MappingRecordOperation extends EisOperation {
  ...
  protected abstract Record createInputRecord(RecordFactory recordFactory, Object inputObject)
      throws ResourceException, DataAccessException { ... }

  protected abstract Object extractOutputData(Record outputRecord)
      throws ResourceException, SQLException, DataAccessException { ... }
  ...
}

EIS 작업을 실행하려면 단일 execute 메서드를 사용해야 하고 애플리케이션 수준의 입력 객체를 전달해서 그 결과로 애플리케이션 수준의 출력 객체를 받는다.

public abstract class MappingRecordOperation extends EisOperation {
  ...
  public Object execute(Object inputObject) throws DataAccessException {
  ...
}

여기서 보듯이 CciTemplate 클래스와는 반대로 execute(..) 메서드는 인자로 InteractionSpec를 갖지 않는다. 대신에 InteractionSpec는 작업(operation)에 대해서 전역적이다. 작업(operation) 객체를 특정 InteractionSpec로 인스턴스화 하는데 다음의 생성자를 반드시 사용해야 한다.

InteractionSpec spec = ...;
MyMappingRecordOperation eisOperation = new MyMappingRecordOperation(getConnectionFactory(), spec);
...


24.4.2 MappingCommAreaOperation

일부 커넥터는 EIS에 전송할 파라미터와 EIS에서 반환받은 데이터를 담고 있는 바이트 배열을 나타내는 COMMAREA에 기반을 둬서 레코드를 사용한다. 레코드 대신 COMMAREA와 직접 동작하는 특수한 오퍼레이션 클래스를 스프링이 제공하고 있다. MappingCommAreaOperation 클래스는 COMMAREA 등을 지원하기 위해 MappingRecordOperation 클래스를 상속받는다. MappingCommAreaOperation는 암묵적으로 입력과 출력 레코드로 CommAreaRecord 클래스를 사용하고 입력 객체를 입력 COMMAREA로 변환하고 출력 COMMAREA를 출력 객체로 변환하는 새로운 두 메서드를 제공한다.

public abstract class MappingCommAreaOperation extends MappingRecordOperation {
  ...
  protected abstract byte[] objectToBytes(Object inObject)
      throws IOException, DataAccessException;

  protected abstract Object bytesToObject(byte[] bytes)
      throws IOException, DataAccessException;
  ...
}


24.4.3 출력 레코드 자동 생성

모든 MappingRecordOperation 하위클래스가 내부적으로 CciTemplate에 기반을 두고 있으므로 CciTemplate와 같을 방법으로 출력 레코드를 자동 생성할 수 있다. 각 작업 객체는 이에 대응되는 setOutputRecordCreator(..) 메서드를 제공한다. 더 자세한 내용은 Section 24.3.4, “출력 레코드 자동 생성”를 참고해라.

24.4.4 요약

작업(operation) 객체 접근방법은 CciTemplate 클래스와 같은 방법으로 레코드를 사용한다.

Table 24.2. Interaction execute 메서드의 사용방법

MappingRecordOperation 메서드 시그니처 MappingRecordOperation outputRecordCreator 프로퍼티 CCI Interaction에서 호출된 execute 메서드
Object execute(Object) 설정 안함 Record execute(InteractionSpec, Record)
Object execute(Object) 설정함 boolean execute(InteractionSpec, Record, Record)






24.4.5 MappingRecordOperation 사용방법의 예시

이번 장에서는 블랙박스 CCI 커넥터로 데이터베이스에 접근하는 MappingRecordOperation의 사용방법을 볼 것이다.

Note
이 커넥터의 원래 버전은 (Sun의) Java EE SDK (버전 1.3)에서 제공한 것이다.


우선, 어떤 SQL 요청을 처리할 것인지 정하기 위해 CCI InteractionSpec의 일부 초기화 작업이 완료되어야 한다. 이 예제에서는 요청의 파라미터들을 CCI 레코드로 변환하는 방법과 CCI 결과 레코드를 Person 클래스의 인스턴스로 변환하는 방법을 직접 정의한다.

public class PersonMappingOperation extends MappingRecordOperation {

  public PersonMappingOperation(ConnectionFactory connectionFactory) {
    setConnectionFactory(connectionFactory);
    CciInteractionSpec interactionSpec = new CciConnectionSpec();
    interactionSpec.setSql("select * from person where person_id=?");
    setInteractionSpec(interactionSpec);
  }

  protected Record createInputRecord(RecordFactory recordFactory, Object inputObject)
      throws ResourceException {
    Integer id = (Integer) inputObject;
    IndexedRecord input = recordFactory.createIndexedRecord("input");
    input.add(new Integer(id));
    return input;
  }

  protected Object extractOutputData(Record outputRecord)
      throws ResourceException, SQLException {
    ResultSet rs = (ResultSet) outputRecord;
    Person person = null;
    if (rs.next()) {
      Person person = new Person();
      person.setId(rs.getInt("person_id"));
      person.setLastName(rs.getString("person_last_name"));
      person.setFirstName(rs.getString("person_first_name"));
    }
    return person;
  }
}

그러면 애플리케이션이 인자로 person 식별자를 사용해서 작업 객체를 실행할 수 있다. 이 작업객체는 스레드 세이프 하므로 공유 인스턴스로 설정할 수도 있다.

public class MyDaoImpl extends CciDaoSupport implements MyDao {
  public Person getPerson(int id) {
    PersonMappingOperation query = new PersonMappingOperation(getConnectionFactory());
    Person person = (Person) query.execute(new Integer(id));
    return person;
  }
}

비관리 모드에서 이와 같은 스프링 빈 설정은 다음과 같을 것이다.

<bean id="managedConnectionFactory"
    class="com.sun.connector.cciblackbox.CciLocalTxManagedConnectionFactory">
  <property name="connectionURL" value="jdbc:hsqldb:hsql://localhost:9001"/>
  <property name="driverName" value="org.hsqldb.jdbcDriver"/>
</bean>

<bean id="targetConnectionFactory"
    class="org.springframework.jca.support.LocalConnectionFactoryBean">
  <property name="managedConnectionFactory" ref="managedConnectionFactory"/>
</bean>

<bean id="connectionFactory"
    class="org.springframework.jca.cci.connection.ConnectionSpecConnectionFactoryAdapter">
  <property name="targetConnectionFactory" ref="targetConnectionFactory"/>
  <property name="connectionSpec">
    <bean class="com.sun.connector.cciblackbox.CciConnectionSpec">
      <property name="user" value="sa"/>
      <property name="password" value=""/>
    </bean>
  </property>
</bean>

<bean id="component" class="MyDaoImpl">
  <property name="connectionFactory" ref="connectionFactory"/>
</bean>

관리 모드에서는(즉, Java EE 환경) 설정이 다음과 같을 것이다.

<jee:jndi-lookup id="targetConnectionFactory" jndi-name="eis/blackbox"/>

<bean id="connectionFactory"
    class="org.springframework.jca.cci.connection.ConnectionSpecConnectionFactoryAdapter">
  <property name="targetConnectionFactory" ref="targetConnectionFactory"/>
  <property name="connectionSpec">
    <bean class="com.sun.connector.cciblackbox.CciConnectionSpec">
      <property name="user" value="sa"/>
      <property name="password" value=""/>
    </bean>
  </property>
</bean>

<bean id="component" class="MyDaoImpl">
  <property name="connectionFactory" ref="connectionFactory"/>
</bean>


24.4.6 MappingCommAreaOperation 사용방법의 예시

이번 장에서는 MappingCommAreaOperation을 사용해서 IBM CICS ECI 커넥터로 ECI 모드에서 CICS에 접근하는 방법을 볼 것이다.

먼저 어떤 CICS 프로그램에 접근하고 어떻게 상호작용할 것인지 지정하기 위해 CCI InteractionSpec을 초기화 해야 한다.

public abstract class EciMappingOperation extends MappingCommAreaOperation {
  public EciMappingOperation(ConnectionFactory connectionFactory, String programName) {
    setConnectionFactory(connectionFactory);
    ECIInteractionSpec interactionSpec = new ECIInteractionSpec(),
    interactionSpec.setFunctionName(programName);
    interactionSpec.setInteractionVerb(ECIInteractionSpec.SYNC_SEND_RECEIVE);
    interactionSpec.setCommareaLength(30);
    setInteractionSpec(interactionSpec);
    setOutputRecordCreator(new EciOutputRecordCreator());
  }

  private static class EciOutputRecordCreator implements RecordCreator {
    public Record createRecord(RecordFactory recordFactory) throws ResourceException {
      return new CommAreaRecord();
    }
  }
}

초기화하고 나면 커스텀 객체와 Records의 매핑을 위해 추상 EciMappingOperation 클래스가 하위 클래스가 될 수 있다.

public class MyDaoImpl extends CciDaoSupport implements MyDao {
  public OutputObject getData(Integer id) {
    EciMappingOperation query = new EciMappingOperation(getConnectionFactory(), "MYPROG") {
      protected abstract byte[] objectToBytes(Object inObject) throws IOException {
        Integer id = (Integer) inObject;
        return String.valueOf(id);
      }
      protected abstract Object bytesToObject(byte[] bytes) throws IOException;
        String str = new String(bytes);
        String field1 = str.substring(0,6);
        String field2 = str.substring(6,1);
        String field3 = str.substring(7,1);
        return new OutputObject(field1, field2, field3);
      }
    });

    return (OutputObject) query.execute(new Integer(id));
  }
}

비관리 모드에서 스프링 빈을 사용한 설정은 다음과 같을 것이다.

<bean id="managedConnectionFactory" class="com.ibm.connector2.cics.ECIManagedConnectionFactory">
  <property name="serverName" value="TXSERIES"/>
  <property name="connectionURL" value="local:"/>
  <property name="userName" value="CICSUSER"/>
  <property name="password" value="CICS"/>
</bean>

<bean id="connectionFactory" class="org.springframework.jca.support.LocalConnectionFactoryBean">
  <property name="managedConnectionFactory" ref="managedConnectionFactory"/>
</bean>

<bean id="component" class="MyDaoImpl">
  <property name="connectionFactory" ref="connectionFactory"/>
</bean>

관리모드에서는(즉, Java EE 환경) 설정이 다음과 같을 것이다.

<jee:jndi-lookup id="connectionFactory" jndi-name="eis/cicseci"/>

<bean id="component" class="MyDaoImpl">
  <property name="connectionFactory" ref="connectionFactory"/>
</bean>


24.5 트랜잭션

JCA는 리소스 어댑터의 트랜잭션을 여러 단계로 지원한다. 리소스 어댑터가 지원하는 트랜잭션 종류는 리소스 어댑터의 ra.xml 파일에서 지정한다. 사실상 세 가지 옵션이 있는데 none(예시: CICS EPI 커넥터), 지역(local) 트랜잭션(예시: CICS ECI 커넥터), 전역(global) 트랜잭션(예시: IMS 커넥터)이다.

<connector>
  <resourceadapter>
    <!-- <transaction-support>NoTransaction</transaction-support> -->
    <!-- <transaction-support>LocalTransaction</transaction-support> -->
    <transaction-support>XATransaction</transaction-support>
  <resourceadapter>
<connector>

전역 트랜잭션에서는 트랜잭션의 경계를 정하기 위해 JtaTransactionManager를 백엔드로 사용해서(Java EE 서버의 분산 트랜잭션 코디네이터에 위임해서) 스프링의 제너릭 트랜잭션을 사용할 수 있다.

단일 CCI ConnectionFactory상의 지역 트랜잭션에서는 JDBC의 DataSourceTransactionManager와 유사하게 스프링이 CCI에 대한 구체적인 트랜잭션 관리 전략을 제공한다. CCI API는 지역 트랜잭션 객체와 해당 지역 트랜잭션 경계 메서드를 정의한다. 스프링의 CciLocalTransactionManager는 이러한 지역 CCI 트랜잭션을 실행하고 이는 스프링의 제너릭 PlatformTransactionManager 추상화와 완전히 호환된다.

<jee:jndi-lookup id="eciConnectionFactory" jndi-name="eis/cicseci"/>
<bean id="eciTransactionManager"
    class="org.springframework.jca.cci.connection.CciLocalTransactionManager">
  <property name="connectionFactory" ref="eciConnectionFactory"/>
</bean>

두 트랜잭션 전략 모두 스프링의 어떤 트랜잭션 경계 기능(선언적이거나 프로그래밍적인)과도 사용할 수 있다. 이는 실제 실행 전략과 트랜잭션 경계를 디커플링한 스프링의 제너릭 PlatformTransactionManager 추상화 덕이다. 그래서 트랜잭션 경계는 그대로 둔 채 JtaTransactionManager와 CciLocalTransactionManager를 필요한대로 바꿔 쓸 수 있다.

스프링의 트랜잭션 기능에 대한 자세한 내용은 Chapter 11, 트랜잭션 관리장을 참고해라.

2014/04/18 02:37 2014/04/18 02:37

관련 글

Grunt 플러그인: grunt-contrib-connect

프론트앤드 개발을 할 때 웹서버에 붙어서 작업을 하면 핀리하지만 웹서버 설정을 하는 건 꽤 귀찮은 일이다. 파일을 직접 브라우저에 던져서 로드하는 경우에는 로컬파일 경로로 동작하므로 css나 js같은 다른 정적파일을 로드하는 경로가 애매하게 된다. 그래서 나는 웬만하면 로컬에서 정적 웹서버를 띄어서 개발을 하는 편이다. Apache나 nginx를 서버를 쓸 수도 있지만, 개발용으로 쓰기에는 설정이 번거로우므로 개발할 때는 바로 띄울 수 있는 정적 웹서버가 편하다.

이럴 때 사용할 수 있는 정적 웹서버로는 python -m SimpleHTTPServerLocallyharp.js등이 있고 나는 주로 harp.js를 사용한다. 이러한 도구는 현재 디렉터리를 기준으로 웹서버를 띄울 수 있는 편리한 도구이지만 팀 프로젝트를 할 때는 같은 개발환경이 설정되면 편리하다.

grunt-contrib-connect

Grunt를 사용한다면 connect에 기반을 둔 grunt-contrib-connect를 사용해서 간단한 설정만으로 웹서버를 띄울 수 있다. npm install --save-dev grunt-contrib-connect로 설치하고

...
connect: {
  dev: {}
},
...

위처럼 설정하고 grunt connect를 실행하면

$ grunt connect
Running "connect:dev" (connect) task
Started connect web server on 127.0.0.1:8000.

특별한 옵션을 주지 않아도 기본 설정으로 현재 프로젝트에서 웹서버를 실행한다. 다만 이렇게 실행할 경우 서버가 실행되자마자 할 일이 없으므로 바로 종료되게 된다.(Grunt task가 종료된다.) 서버를 계속 실행해 두려면 keepalive 옵션을 주거나(아래 옵션 참고) grunt connect:target-name:keepalive와 같이 실행하면 서버가 죽지 않는다.

아니면 다음과 같이 grunt-contrib-watch를 같이 사용하면 서버를 실행된 채로 둘 수 있다.

...
connect: {
  dev: {}
},
watch: {}
...
grunt connect watch
Running "connect:dev" (connect) task
Started connect web server on 127.0.0.1:8000.

Running "watch" task
Waiting...


옵션

  • port: 웹서버를 실행할 포트 번호. 기본값: 8000
  • protocol: 프로토콜. 기본값은 'http'이고 'https'도 가능
  • hostname: 웹서버의 호스트명. 기본값 0.0.0.0.
  • base: 정적파일을 제공할 루트 경로로 String이나 Array로 지정할 수 있다. 기본값 '.'
  • directory: 웹 브라우저에서 파일 목록이 나오도록 할 디렉터리. 기본값 null
  • keepalive: 서버가 실행된 채로 유지한다.(앞에 얘기한 watch 대신 사용할 수 있다.)
  • debug: 요청에 대한 로그를 출력한다. 기본값 false
  • livereload: connect-livereload를 사용해서 페이지에 스크립트를 추가해서 브라우저에서 자동으로 리로드가 되게 한다. grunt-contrib-watch등과 조합해서 사용해야 한다. 기본값 false
  • open: true로 지정하면 서버 실행 시 기본브라우저로 페이지를 연다. 기본값 false, url이나 객체로도 지정할 수 있다.
  • useAvailablePort: 지정한 port가 사용 중이면 다음 포트를 찾는다. 기본값 false
  • middleware: connect의 미들웨어를 추가할 수 있다.

grunt-contrib-connect는 자체적으로 HTTPS도 지원하기 때문에 개발을 https로 해야 한다면 옵션에서 protocolhttps로 지정하면 된다. 인증서는 node_modules/grunt-contrib-connect/tasks/certs/안에 있으므로 별도의 설치는 안 해도 되지만 CA가 발급한 인증서는 아니므로 브라우저에서 허용을 해주거나 인증서 관련 설정을 해야 한다. 아니면 직접 인증서를 설정해서 사용할 수도 있다.

2014/04/16 23:54 2014/04/16 23:54

관련 글