Whiteship's Note


[회사일] Member 추가. MemberDAO 구현. GenericDAO 구현

프로젝트/SLT : 2010.06.09 14:56


이제 Code 도메인 가지고 아주 기본적인 CRUD를 만들었으니 새로운 도메인을 하나 더 추가해서 중복코드를 잡아가면서 프레임워크를 뽑아내면 됩니다.

첫번쨰 도메인 CRUD를 만들때는 아키텍처 정하고 화면 구상하고 이것저것 플러그인 찾아보고 네비게이션 정하고 버튼 모양 덩덩 정하느라 시간이 많이 걸렸다면 이제부터는 중복코드와 싸움입니다.

먼저 Member 도메인을 추가합니다.

@Entity
public class Member {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    int id;

    @Column(length = 50)
    @NotNull(message = "입력해주세요.")
    @Size(min = 1, message = "입력해주세요.")
    String loginId;
    @Column(length = 50)
    @NotNull(message = "입력해주세요.")
    @Size(min = 1, message = "입력해주세요.")
    String password;

    @Column(length = 50)
    @NotNull(message = "입력해주세요.")
    @Size(min = 1, message = "입력해주세요.")
    String name;
    @Column(length = 50)
    @NotNull(message = "입력해주세요.")
    @Size(min = 1, message = "입력해주세요.")
    String email;
    @Column(length = 50)
    String phoneNumber;
    @Column(length = 50)
    String jobTitle;
    @Temporal(TemporalType.DATE)
    Date birthDay;
...
}

여기서도 애노테이션 뭉탱이를 중복제거하고 싶은데.. 일단 조금 뒤로 미루겠습니다.

다음은 DAO를 만듭니다. 인터페이스를 만들고 구현체를 만듭니다.

public interface MemberDao {

    void add(Member member);

    List<Member> list(PageParam pageParam, MemberSearchParam sp);

    int totalSize(MemberSearchParam sp);

    Member getById(int id);

    void deleteBy(int id);

    void update(Member member);

}

구현체는..

@Repository
public class MemberDaoImpl implements MemberDao {

    @Autowired SessionFactory sessionFactory;

    public void add(Member member) {
        getSession().save(member);
    }

    public Member getById(int id) {
        return (Member) getSession().get(Member.class, id);
    }

    public void deleteBy(int id) {
        int result = getSession().createQuery("delete from Member where id = ?").setInteger(0, id).executeUpdate();
        if(result != 1)
            throw new RuntimeException();
    }

    public void update(Member member) {
        getSession().update(member);
    }

    public List<Member> list(PageParam pageParam, MemberSearchParam searchParam) {
        Criteria c = getCriteriaOf(Member.class);
        //searching
        applySearchParam(c, searchParam);
        //paging
        c.setFirstResult(pageParam.getFirstRowNumber());
        c.setMaxResults(pageParam.getRows());
        //ordering
        if(pageParam.getSord().equals("asc"))
            c.addOrder(Order.asc(pageParam.getSidx()));
        else
            c.addOrder(Order.desc(pageParam.getSidx()));

        return c.list();
    }


    public int totalSize(MemberSearchParam searchParam) {
        Criteria c = getCriteriaOf(Member.class);
        applySearchParam(c, searchParam);
        return (Integer)c.setProjection(Projections.count("id"))
            .uniqueResult();
    }

    private void applySearchParam(Criteria c, MemberSearchParam searchParam) {
        
    }

    private Session getSession() {
        return sessionFactory.getCurrentSession();
    }

    private Criteria getCriteriaOf(Class clazz){
        return getSession().createCriteria(clazz);
    }
        
}

CodeDaoImple 코드와 거의 똑같습니다.

상속을 사용해서 중복을 제거하겠습니다. 먼저 인터페이스 부터..

public interface GenericDao<E, S> {

    void add(E e);

    List<E> list(PageParam pageParam, S s);

    int totalSize(S s);

    E getById(int id);

    void deleteBy(int id);

    void update(E e);

}

그리고 이걸 CodeDao와 MemberDao에 사용합니다.

public interface CodeDao extends GenericDao<Code, CodeSearchParam>{
    
}

public interface MemberDao extends GenericDao<Member, MemberSearchParam>{
   
}

오퀘 인터페이스가 텅 비었습니다. 아주 일반적인 CRUD 이외 추가기능이 생기면 눈에 확 띄겠죠.

다음은 GenericDao 구현체를 만듭니다.

public abstract class GenericDaoImpl<E, S> implements GenericDao<E, S>{

    @Autowired protected SessionFactory sessionFactory;
    
    private Class<E> entityClass;

    public GenericDaoImpl() {
ParameterizedType genericSuperclass = (ParameterizedType) getClass().getGenericSuperclass();
        Type type = genericSuperclass.getActualTypeArguments()[0];
        if (type instanceof ParameterizedType) {
            this.entityClass = (Class) ((ParameterizedType) type).getRawType();
        } else {
            this.entityClass = (Class) type;
        }
}
    
    public void add(E e) {
        getSession().save(e);
    }

    public E getById(int id) {
         return (E) getSession().get(entityClass, id);
    }

    public void deleteBy(int id) {
        int result = getSession().createQuery("delete from" + entityClass.getSimpleName()+ " where id = ?").setInteger(0, id).executeUpdate();
        if(result != 1)
            throw new RuntimeException();
    }

    public void update(E e) {
        getSession().update(e);
    }

    public List<E> list(PageParam pageParam, S s) {
        Criteria c = getCriteriaOf(entityClass);
        //searching
        applySearchParam(c, s);
        //paging
        c.setFirstResult(pageParam.getFirstRowNumber());
        c.setMaxResults(pageParam.getRows());
        //ordering
        if(pageParam.getSord() != null){
            if (pageParam.getSord().equals("asc"))
                c.addOrder(Order.asc(pageParam.getSidx()));
            else
                c.addOrder(Order.desc(pageParam.getSidx()));
        }
        //noinspection unchecked
        return c.list();

    }

    protected abstract void applySearchParam(Criteria c, S s);

    public int totalSize(S s) {
        Criteria c = getCriteriaOf(entityClass);
        applySearchParam(c, s);
        return (Integer) c.setProjection(Projections.count("id"))
                .uniqueResult();
    }

    protected Session getSession() {
        return sessionFactory.getCurrentSession();
    }

    protected Criteria getCriteriaOf(Class clazz){
        return getSession().createCriteria(clazz);
    }
}

그리고 이녀석을 사용하도록 CodeDaoImpl과 MemberDaoImpl을 수정합니다.

@Repository
public class CodeDaoImpl extends GenericDaoImpl<Code, CodeSearchParam> implements CodeDao {
    
    @Override
    protected void applySearchParam(Criteria c, CodeSearchParam searchParam) {
        CriteriaUtils.addOptionalLike(c, "code", searchParam.getCode());
        CriteriaUtils.addOptionalLike(c, "name", searchParam.getName());
        CriteriaUtils.addOptionalEqual(c, "cate", searchParam.getCateValue());
    }
    
}

@Repository
public class MemberDaoImpl extends GenericDaoImpl<Member, MemberSearchParam> implements MemberDao {

    @Override
    protected void applySearchParam(Criteria c, MemberSearchParam searchParam) {

    }
}

마지막으로 이전에 만들어 둔 CodeDaoImplTest를 한번 돌려주면 됩니다. 굳이 뭐또 GenericDaoImpl 테스트를 만들 필요는 없는것 같네요.

이렇게 과감한 코드 수정을 할 수 있었던 게 다 CodeDaoImplTest 덕분입니다.

자 그럼 잠깐 밥먹고 MemberService도 만들어야지 





top


DAO vs Repository

JEDI/DDD : 2007.12.04 23:21


http://aeternum.egloos.com/1160846 이 글을 참조하세요. 아래 내용은 권해드리지 않는 내용입니다.

나도 DDD 하고싶어 모델링 적용기
Repository Pattern vs. Transparent Persistence

위 두 개의 글의 공통점은 DAO와 Repository의 차이에 대한 고민입니다. 저도 잘 모릅니다. 저는 사실 찬욱군이 쓴 글에 있는 링크를 따라가보지도 않았으며, 얇은 DDD책(DDD Quickly)도 다 읽지 못 했고, 그나마 읽었던 부분들도 잊혀져가고 있습니다.(다시 읽으려고 마음먹었습니다.)

그래도.. 나름대로 생각해 봤습니다. 어차피 작명의 문제이고 누군가 어떤 의도를 가지고 작명을 했을테니 그 뜻은 분명 단어 속에 숨어있겠죠.

DAO는 Data Access Object의 약자로 데이터(베이스)에 접근하는 객체를 나타냅니다. DB 접근하는 일을 분리해 냈습니다. SRP 객체지향 원칙의 사례로 등장했었던 적이 있었는데 어떤 글인지는 모르겠습니다.

Repository는 저장소입니다. 어떤 객체들을 저장하는 장소를 뜻할 것이며, 아마도 DB 자체를 또는 Table 자체 또는 '영속성이 보장되는 저장소'를 객체화한 표현이 아닐까 생각해봅니다. 이것이 필요한 객체는 바로 도메인 객체겠죠.

이정도 정보를 가지고 이 둘의 차이를 살펴보기엔 정말 막연합니다. 그래서 Finder라는 녀석까지 같이 보겠습니다. Finder는 findByXXX 류의 메소드들을 가진 클레스로 이 녀석 역시 DB나 영속성을 보장하는 어떠한 저장소에 접근을 합니다.

기존의 DAO에는 Finder가 하는 일까지 같이 포함하고 있었습니다. 그리고 DDD를 구현한(건지.. 구현 하도록 돕는 건지.. Anyway) ROO 라는 프레임워크에서는 Repository와 Finder로 분리해 두었습니다. 조금 더 세밀하게 SRP를 적용했다고 볼 수 있습니다.

따라서 저는 이렇게 결론을 냈습니다.

사용자 삽입 이미지

그럼 DAO와 Finder를 동시에 두면 어떻게 되는걸까? 라는 생각을 해볼 수 있는데요. DAO는 위에서도 언급했다시피, 데이터에 접근하는 객체입니다. Finder는 무언가를 찾아주는 녀석이죠. 그럼 무언가를 찾아주는 녀석이 매번 데이터에 접근해서 찾아야 하는데 결국 Finder도 DAO라고 볼 수 있지 않을까요? 그럴 때는 가능하다면, 기존의 DAO 이름을 Repository로 변경하는 것이... 좋을 것 같다는 생각을 해봅니다.

'JEDI > DDD' 카테고리의 다른 글

[DDD] DDD 입문에 좋은 글  (4) 2009.06.11
파트 3: Refactoring Toward Deeper Insight  (0) 2008.10.24
Factories  (0) 2007.12.25
Aggregates  (0) 2007.12.18
Modules  (2) 2007.12.14
Services  (2) 2007.12.13
DAO vs Repository  (4) 2007.12.04
DTO(Data Transfer Object)  (2) 2007.11.21
DDD: putting the model to work  (4) 2007.11.20
Building Domain Knowledge  (0) 2007.01.03
What Is Domain-Driven Design  (2) 2007.01.02
top


SqlMapClientDaoSupport




MySQL에 만들어둔 Member 테이블에 iBatis를 사용해서 SQL을 날리는 Member.xml에 있는 sql의 ID를 호출하는 SqlmapMemberDao가 상속받고 있는 SqlMapClientDaoSupport에 대해서 조사해 봅니다.

스프링 레퍼런스 12.5
를 참고하면 다음과 같은 표를 볼 수 있습니다.

저는 iBATIS 2,0을 사용하기에 DAO 클래인 SqlmapMemberDao가 SqlMapClientDaoSupport 클래스를 상위 클래스로 상속받도록 했습니다.

그리고 SqlMapClientDaoSupport에서 사용되는 SqlMapClient 객체를 DI(dependency injection)하기 위해 daocontext-member.xml에 다음과 같이 코딩합니다.
sqlMapClient 객체는 사용할 SqlMap을 다음과 같이 sql-map-config.xml 에 코딩해 둡니다.
여기서 사용하기로 한 Member.xml 파일을 보면 다음과 같습니다.

more..


이렇게 설정파일에 해당하는 iBatis Sql Map 2.0 과 JDBC인 datasource를 SqlMapClientFactoryBean 타입의 객체인 sqlMapClient의 속성으로 DI 시킵니다.

more..


글로 정리하다 보니까 이제 눈에 조금 보이네요.

SqlMapClientDaoSupport라는 클래스에서 SqlMapClient 객체를 사용하기 때문에 이것을 와이어링 해주는데.. SqlMapClieint는 org.springframework.orm.ibatis.SqlMapClientFactoryBean 이 타입이며 SqlMapClientFactoryBean을 정의 할때는 configLocation 이것과 dataSource 를 와이어링 해줘야 한다.

configLocation은 이곳에서 사용할 iBATIS sql map에 대한 정보를 가지고 있는 sql-map-config.xml 파일로 지정해주고 sql-map-config.xml에는 member.xml파일이 지정되어 있고 member.xml은 iBATIS를 사용하고 있다.

datasource는 db connection객체를 생성하기 위한 네 가지 정보에 값을 setting한다.

'Spring > 주소록 만들기' 카테고리의 다른 글

log4j 설정하기(in spring)  (0) 2006.12.02
수정해야 할 것들  (2) 2006.11.28
iBATIS에서 selectKey 사용하기  (2) 2006.11.23
MySQL 설치 시 주의 할 점  (6) 2006.11.23
DB 인코딩 문제  (0) 2006.11.20
SqlMapClientDaoSupport  (0) 2006.11.18
AbstractTransactionalDataSourceSpringContextTests  (4) 2006.11.13
회원 목록 추가  (0) 2006.11.13
XML configuration  (0) 2006.11.10
MySQL Connector Down + testAdd()  (0) 2006.11.10
MySQL 설치 + 사용  (1) 2006.11.09
top