CRUD 실습
다음과 같은 스키마 구성을 가지는 CRUD 및 몇가지 검색 질의를 실습해본다.
본 절에서 사용되는 property 란 도메인 클래스의 속성을 의미한다.
- 요구사항
- 도메인 구성
- 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()));
}
}