Basis Service

Basis Service는 Java 5부터 지원하는 Generics 개념을 기반으로 개발되었으며, DAO 인터페이스/구현 클래스, Service 인터페이스/구현 클래스, 단위테스트케이스 등 기본 CRUD 기능이 모두 구현된 클래스를 상속받아서 쉽게 사용할 수 있는 기능을 제공하고 있다.
Basis Service는 AppFuse (http://appfuse.org/display/APF/Home)의 개념과 소스 코드(템플릿 포함)를 참고하여 Anyframe에 맞게 수정되었다. 아래의 Basis Service 구성 클래스들을 활용한 샘플 코드들은 직접 작성하여 사용할 수도 있고, Anyframe Gen 툴을 통해 자동 생성되는 코드를 사용할 수도 있다. Anyframe Gen에 대한 자세한 설명은 Tool 매뉴얼 을 참고하도록 한다.
Basis Service를 이용할 때 다음과 같은 장점을 얻을 수 있다.
  • 도메인 모델를 생성하고, 이 객체를 중심으로 DB접근 영역까지의 기본 CRUD 코드들을 쉽게 작성 할 수 있다.
  • Hibernate/JPA, Query Service 와 같은 DAO Framework을 지원한다.
다음은 Basis Service 사용 방법이다.

Domain Model 클래스 생성

BaseObject

BaseObject는 DB 테이블을 도메인 모델로 생성하며, 엔티티 클래스로 정의하도록 한다. BaseObject는 다음 세가지 메소드로 구현되어 있으며, BaseObject 를 상속받아 사용할 경우 세가지 메소드에 대해 오버라이드 해야한다.
package anyframe.core.basis.model;
				
public abstract class BaseObject implements Serializable {
    //key=value 형태의 String을 return
    public abstract String toString();
    //객체 비교. 단, Hibernate을 사용하는 경우 primary key 는 이 메소드에서 비교하지 말아야 한다.
    public abstract boolean equals(Object o);
    //equals() 메소드를 오버라이드 할 경우에는 hashcode()를 반드시 오버라이드 할것.
    public abstract int hashCode();	
}

Samples

다음은 BaseObject 를 이용한 Users Domain 에 대한 샘플코드 Users.java 의 일부분이다. BaseObject에 선언되어 있는 메소드를 오버라이드 하였다.
@Entity
@Table(name = "USERS", schema = "PUBLIC")
public class Users extends BaseObject implements Serializable {

    private String userId;
    private String userName;
    ...중략
    public String toString() {
        StringBuffer sb = new StringBuffer(getClass().getSimpleName());
        sb.append(" [");
        sb.append("userId").append("='").append(getUserId()).append("', ");
        sb.append("userName").append("='").append(getUserName()).append("', ");
        ...중략
        sb.append("]");
        return sb.toString();
    }

    public boolean equals(Object o) {
        ...중략
        Users pojo = (Users) o;
        if ((userName != null)
          ? (!userName.equals(pojo.userName))
          : (pojo.userName != null)) {
            return false;
        }
        ...중략
    }

    public int hashCode() {
        int result = 0;
        result = ((userName != null) ? userName.hashCode() : 0);
        ...중략
    }
}

DAO 클래스 생성

GenericDao

DAO 인터페이스 생성을 위해 GenericDao<T, PK extends Serializable> Class를 이용한다. 여기서 T는 도메인 객체 타입으로 도메인 모델 클래스를 의미하고, PK는 도메인 객체의 Primary Key 타입을 의미한다. 다음은 GenericDao 에 정의되어 있는 단건조회, 데이터 존재여부 확인, 저장, 삭제, 리스트 조회와 관련한 메소드이다.
package anyframe.core.basis.dao;
				
public interface GenericDao<T, PK extends Serializable> {
    // 단건조회
    T get(PK id) throws Exception;
    // 데이터 존재여부 확인
    boolean exists(PK id) throws Exception;
    // 저장
    T save(T object) throws Exception;
    // 삭제
    void remove(PK id) throws Exception;
    // 리스트 조회(페이징처리)
    public Page getList(SearchVO searchVO) throws Exception;				
}

GenericDaoHibernate

DAO 구현 클래스를 생성할 때 Hibernate/JPA를 활용하기 위한 GenericDaoHibernate 의 구조이다. DAO 인터페이스에 선언된 메소드가 구현되어 있다.
package anyframe.core.basis.dao.hibernate;
				
public class GenericDaoHibernate<T, PK extends Serializable> implements GenericDao<T, PK> {
    ...중략
    public T get(PK id) throws Exception {...중략}
    public boolean exists(PK id) throws Exception {...중략}
    public T save(T object) throws Exception {...중략}
    public void remove(PK id) throws Exception {...중략}
    public Page getList(SearchVO searchVO) throws Exception {...중략}
}

GenericDaoQuery

DAO 구현 클래스를 생성할 때 Query Service 를 이용하기 위해 GenericDaoQuery , QueryDaoUtils 를 사용한다. 다음은 GenericDaoQuery 의 구조이며, QueryDaoUtils 는 primary key 와 query name을 위한 Utility 파일로 링크를 참조한다.
package anyframe.core.basis.dao.query;
				
public class GenericDaoQuery<T, PK extends Serializable> 
		extends AbstractDAO implements GenericDao<T, PK> {
    ...중략
    public T get(PK id) throws Exception {...중략}
    public boolean exists(PK id) throws Exception {...중략}
    public T save(T object) throws Exception {...중략}
    public void remove(PK id) throws Exception {...중략}
    public Page getList(SearchVO searchVO) throws Exception {...중략}

Samples

앞서 소개된 Class 를 기반으로 하여 개발한 UsersDao.javaUsersDaoHibernateImpl.java , UserDaoQueryImpl.java 코드의 일부이다. Hibernate 또는 Query Service 를 사용하기 위한 Configuration 은 각각의 Tech.Service 매뉴얼을 참조한다.
  • UserDao로 GenericDao를 상속받았으며, Page getList 메소드 를 오버라이드 하였다.
    public interface UsersDao<Users, String> extends GenericDao {
        // GenericDao 선언된 메소드 중 리스트 조회만 오버라이드 한 경우
        // 나머지 단건조회, 데이터 존재여부 확인, 저장, 삭제 기능은 GenericDao 에 정의된 그대로 사용
        Page getList(SearchVO searchVO) throws Exception;
    }
  • Hibernate 을 이용한 DAO 구현으로 오버라이드 하려고 했던 메소드에 대해서만 코딩을 하였다. 오버라이드 하지 않은 메소드(단건조회, 데이터 존재여부 확인, 저장, 삭제)의 기능은 GenericDaoHibernate에 구현된 형태 그대로 사용한다.
    @Repository("usersDao")
    public class UsersDaoHibernateImpl 
    	extends GenericDaoHibernate<Users, String> implements UsersDao {
        public UsersDaoHibernateImpl() {
            super(Users.class);
        }
        // GenericDaoHibernate 구현된 메소드 중 리스트 조회만 오버라이드 한 경우
        // 나머지 단건조회, 데이터 존재여부 확인, 저장, 삭제 기능은 GenericDaoHibernate 에 정의된 그대로 사용
        public Page getList(SearchVO searchVO) throws Exception {
            ...중략
        }        
    }
  • Query Service를 이용한 DAO 구현소스의 일부분이다. 오버라이드 하지 않은 메소드(단건조회, 데이터 존재여부 확인, 저장, 삭제)의 기능은 GenericDaoQuery에 구현된 형태 그대로 사용한다.
    @Repository("usersDao")
    public class UsersDaoQueryImpl extends GenericDaoQuery<Users, String> implements UsersDao {
        public UsersDaoQueryImpl() {
            super(Users.class);
        }
        // GenericDaoQuery에 구현된 메소드 중 리스트 조회만 오버라이드 한 경우
        // 나머지 단건조회, 데이터 존재여부 확인, 저장, 삭제 기능은 GenericDaoQuery 에 정의된 그대로 사용
        public Page getList(SearchVO searchVO) throws Exception {
        ...중략
        }        
    }
    						

Service 클래스 생성

GenericManager

서비스 인터페이스는 GenericManager 를 사용한다. GenericMager에는 단건조회, 데이터 존재여부 확인, 저장, 삭제, 리스트 조회에 관한 메소드가 선언되어 있다.
package anyframe.core.basis.service;
									
public interface GenericManager<T, PK extends Serializable> {
    // 단건조회
    T get(PK id) throws Exception;
    // 데이터 존재여부 확인
    boolean exists(PK id) throws Exception;
    // 저장
    T save(T object) throws Exception;
    // 삭제
    void remove(PK id) throws Exception;
    // 리스트 조회(페이징 처리)
    public Page getList(SearchVO searchVO) throws Exception;
}

GenericManagerImpl

서비스 구현 클래스는 GenericManagerImpl 을 사용하며 다음과 같은 구조로 되어 있다.
package anyframe.core.basis.service.impl;
				
public class GenericManagerImpl<T, PK extends Serializable> implements GenericManager<T, PK> {
    protected final Log log = LogFactory.getLog(getClass());
    protected GenericDao<T, PK> dao;
    public GenericManagerImpl() {
    }
    public GenericManagerImpl(GenericDao<T, PK> genericDao) {
        this.dao = genericDao;
    }
    public T get(PK id) throws Exception {
        return dao.get(id);
    }
    ...중략
}

Samples

다음은 GenericMager, GenericManagerImpl을 상속받은 UsersService.java , UsersServiceImpl.java 파일이며, 필요할 경우 오버라이드 하여 사용한다.
public interface UsersService extends GenericManager<Users, String> {
    // GenericManager에 선언된 메소드 중 리스트 조회만 오버라이드 한 경우
    // 나머지 단건조회, 데이터 존재여부 확인, 저장, 삭제 기능은 GenericManager 에 정의된 그대로 사용
    Page getList(SearchVO searchVO) throws Exception;            
}
public class UsersServiceImpl extends GenericManagerImpl<Users, String> implements UsersService {
    UsersDao usersDao;
    public UsersServiceImpl(UsersDao usersDao) {
        super(usersDao);
        this.usersDao = usersDao;
    }   
    // GenericManagerImpl에 구현된 메소드 중 리스트 조회만 오버라이드 한 경우
    // 나머지 단건조회, 데이터 존재여부 확인, 저장, 삭제 기능은 GenericManagerImpl 에 정의된 그대로 사용
    public Page getList(SearchVO searchVO) throws Exception {
        return this.usersDao.getList(searchVO);
    }        
}

Test Code 생성

Unit Test Case

Unit Test Case 작성을 위해 jMock을 사용한 BaseManagerMockTestCase 를 이용한다.
package anyframe.core.basis.service.impl;
			
@RunWith(JMock.class)
public abstract class BaseManagerMockTestCase {
    final protected Log log = LogFactory.getLog(getClass());
    protected ResourceBundle rb;
    protected Mockery context = new JUnit4Mockery();

    public BaseManagerMockTestCase() {중략...}
    protected Object populate(Object obj) throws Exception {중략...}
}

Integration Test Case

Integration Test Case는 Basis Service 에서는 제공하지 않으며, Spring Framework의 Test를 이용한다.

Samples

다음은 BaseManagerMockTestCase 를 이용한 Unit Test Case UsersServiceImplTest 파일이다. Integration Test Case는 다음 링크된 소스UsersDaoTest.java 를 참조한다.
public class UsersServiceImplTest extends BaseManagerMockTestCase {
    private UsersServiceImpl service = null;
    private UsersDao dao = null;

    @Before
    public void setUp() {
        dao = context.mock(UsersDao.class);
        service = new UsersServiceImpl(dao);
    }

    @After
    public void tearDown() {
        service = null;
    }

    @Test
    public void testGetUsers() throws Exception {
        log.debug("testing get...");
		
        final String userId = new String("CpVpDcTwTcBwToFzAzGs");
        final Users users = new Users();

        // set expected behavior on dao 
        context.checking(new Expectations() {{
            one(dao).get(with(equal(userId)));
            will(returnValue(users));
        }});

        Users result = service.get(userId);
        assertSame(users, result);
    }
    중략...
}

Resources