CRUD 실습

다음과 같은 스키마 구성을 가지는 CRUD 및 몇가지 검색 질의를 실습해본다.

본 절에서 사용되는 property 란 도메인 클래스의 속성을 의미한다.

  1. 요구사항
  2. 도메인 구성
  3. User 도메인 만들기

1.요구사항

  • 조직은 상위조직을 가지는 트리형태로 구성된다.
  • 사용자는 1개의 조직에 포함된다.
  • 사용자는 여러개의 권한을 가진다.
  • 사용자는 다른 사용자들과 가족관계를 구성할 수 있다.

2.도메인 구성

User 엔티티(Table Name:USER)

User 는 사용자를 의미하며, Authority 와 1:n 의 관계를 가진다. User 는 Department 와 n:1 의 관계를 가진다. 즉, 사용자는 여러개의 권한을 가지며, 1개에 부서에 속할 수 있다.

Property Table Column Type Name Key
uniqueNumber UNIQ_NO VARCHAR(36) 고유번호 PK
name NM VARCHAR(36) 이름
regDate REG_DATE TIMESTAMP 등록일시
department DEPT_ID BIGINT(19) 조직 ID FK:DEPT

Department 엔티티(Table Name:DEPT)

조직은 조직 유형을 가지며, 상위 조직을 가지는 트리형태로 구성된다.

Property Table Column Type Name Key
id DEPT_ID BIGINT(19) 조직ID PK, AUTO_INCREMENT
name NM VARCHAR(36) 조직명
parent_id PARENT_DEPT_ID BIGINT(19) 상위 조직 ID FK:DEPT
type DEPT_TYPE VARCHAR(10) 조직유형

Authority 엔티티(Table Name:USER_AUTH)

사용자는 문자열로 된 다수개의 권한을 가진다.

Property Table Column Type Name Key
uniqueNumber UNIQ_NO VARCHAR(36) 사용자 고유번호 CK(FK:USER)
code AUTH_CODE VARCHAR(36) 권한코드 CK
regDate REG_DATE TIMESTAMP 권한등록일시

Family 관계 (Table Name:USER_FAMILY)

사용자는 등록된 사용자들과 가족간계를 형성할 수 있으며 n:n 의 관계를 가진다. 관계는 도메인이 아니며, 관계 테이블만 가진다.

Table Column Type Name Key
SRC_USER_NO VARCHAR(36) 원 사용자 고유번호 CK(FK:USER)
DEST_USER_NO VARCHAR(36) 대상 사용자 고유번호 CK(FK:USER)

2.패키지 구성

  • 비즈니스 계층(core)과 프리젠테이션 계층(web)은 엄격히 분리된다.
  • 도메인 모델은 core 패키지 하위에 위치한다.
  • 각 도메인별로 패키지를 구성한다.
  • 도메인이 특정 도메인에 종속될 경우에는 종속되는 도메인 하위에 위치한다.

패키지

  • org.scjegov.stone : 모듈 패키지
    • org.scjegov.stone.core : 비즈니스 계층
    • org.scjegot.stone.core.user : User 도메인
      • org.scjegot.stone.core.user.auth : User Authority 도메인
    • org.scjegot.stone.core.dept : Department 도메인
    • org.scjegov.stone.web : 프리젠테이션 및 api 서빙 계층
    • org.scjegov.stone.util : 모듈 전반에 사용되는 util

패키지 내 구성

각 도메인별 패키지에는 다음과 같은 구성을 가진다.

  • Domain 모델 클래스
  • Repository : DAO 역할 수행
  • Service 인터페이스 : 비즈니스로직을 포함하는 서비스 인터페이스
  • ServiceImpl : Service 인터페이스를 구현하는 구현체

3. 도메인 만들기

도메인 클래스는 테이블 매핑을 정의하며, 다른 도메인 클래스간의 관계를 설정한다. 도메인 클래스는 다음의 규칙이 적용된다.

  • 도메인 클래스는 @Entity 어노테이션이 선언되어야한다.
    • name="" 속성을 이용하여 테이블명을 입력할 수 있다.
  • 도메인 클래스는 @Id 어노테이션을 지정하여 PK 를 설정해야한다.
  • n:1 관계를 구성할 경우에는 @ManyToOne 어노테이션을 사용한다.
  • 1:n 관계를 구성할 경우에는 @OneToMany 어노테이션을 사용한다.
  • 1:1 관계를 구성할 경우에는 @OneToOne 어노테이션을 사용한다.
  • 각 속성은 @Column 어노테이션을 이용하여 테이블 필드명을 지정할 수 있다.
  • 날짜 시간 속성에는 @Temporal 어노테이션을 사용한다.
  • 속성명은 약자를 사용하지 않는다.
  • src/main/resources/META-INF/persistence-local.xml 에 만들어진 도메인 클래스를 등록한다.

주의 다른 도메인과 연관 매핑시, 속성명을 xxx_id 로 지정하지 않도록 한다. 반드시 해당 도메인 자체를 사용한다.

@Entity(name="USER")
public class User {
  @Id
  @Column(name="UNIQ_NO")
  private String id;

  @Column(name="NM")
  private String name;

  @Temporal(TemporalType.TIMESTAMP)
  @Column(name="REG_DATE")
  private Date regDate;
}
<?xml version="1.0" encoding="UTF-8" ?>
<persistence ...>
    <persistence-unit name="vine" transaction-type="RESOURCE_LOCAL">
        ...
        <class>org.scjegov.stone.misc.core.user.User</class>
        ...
    </persistence-unit>
</persistence>

4. Repository 만들기

Repository 는 데이타베이스에 쿼리를 질의하는 역할을 수행한다.(기존 DAO 역할) Repository 는 비즈니스 로직을 가지지 않는다.

  • Repository 는 인터페이스만 정의한다.
  • CrudRepository<도메인클래스명, ID 유형> 인터페이스를 상속받는다.
  • CRUD 외에 검색 JPQL 쿼리를 설정한다.

5. Service 및 ServiceImpl 만들기

Service 서비스 인터페이스와 구현체로 구성되며 비즈니스 로직을 구현한다. 서비스는 Repository 를 이용하여 로직을 구현한다.

6. 테스트 만들기

테스트 소스는 메인소스와 동일한 패키지명으로 src/test/java 하위로 생성한다. 테스트 소스는 DefaultTestCase 를 상속받는다.

public class UserRepositoryTest extends DefaultTestCase {

    @Autowired
    private UserRepository userRepository;

    @PersistenceContext
    private EntityManager em;

    @Test
    @Transactional
    public void testCRUD() {

        String id = UUID.randomUUID().toString();

        // 1. Create test
        User user = new User();
        user.setId(id);
        user.setName("test user");
        user.setRegDate(new Date());
        user = userRepository.save(user);

        assertThat(user.getId(), is(not(nullValue())));
        assertThat(user.getId(), is(id));

        // 2. Read test
        User user2 = userRepository.findOne(id);
        assertThat(user2, is(user));
        assertThat(user2.getId(), is(id));
        assertThat(user2.getName(), is("test user"));

        // 3. Update test
        em.flush(); // 테스트를 위해 쿼리 실행전 지연된 쿼리들을 실행한다. 실개발에는 필요 없음
        user2.setName("renamed user");
        User user3 = userRepository.findOne(id);
        assertThat(user3, is(user2));
        assertThat(user3.getId(), is(id));
        assertThat(user3.getName(), is("renamed user"));

        // 4. Delete test
        em.flush(); // 테스트를 위해 쿼리 실행전 지연된 쿼리들을 실행한다. 실개발에는 필요 없음
        userRepository.delete(user3);
        assertThat(userRepository.exists(user3.getId()), is(false));
        assertThat(userRepository.findOne(user3.getId()), is(nullValue()));

    }
}

results matching ""

    No results matching ""