이제 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도 만들어야지