Object Relational Mapping(이하 ORM)
기반 설계 및 개발 과정

박재성
www.slipp.net
오늘 할 이야기
요구사항
사용자는 질문을 할 수 있어야 한다.
질문에 대한 답변을 할 수 있어야 한다.
질문을 할 때 태그를 추가할 수 있어야 한다.
태그는 태그 풀에 존재하는 태그만 추가할 수 있다.
태그가 추가될 경우 해당 태그 수는 +1 증가, 삭제될 경우 해당 태그
수는 -1 증가해야 한다.
User

Tag

Question
1

0..n

0..n
1

1

0..n

Answer
0..n

0..n
어디서부터 시작할 것인가?
객체 or 테이블
테이블 주도 개발
테이블 설계로부터 시작 한다면…

요구사항
질문 할 때 태그를 추가할 수 있다. (예 java eclipse)
질문을 수정할 때 태그를 수정할 수 있다. (예. eclipse ant)
질문 추가시 java eclipse 태그 추가할 경우
insert into question values( ?, ?, ?, ?, ?); => question_id = 1
select id, name from tag where name=’java’; => tag_id = 11
select id, name from tag where name=’eclipse’; => tag_id = 12
insert into question_tag values( 1, 11 );
insert into question_tag values( 1, 12 );
update tag set tagged_count = tagged_count + 1 where name=’java’;
update tag set tagged_count = tagged_count + 1 where name=’eclipse’;
질문 수정시 eclipse ant 태그 추가할 경우
update question set title=?, contents=? where question_id = 1;

delete from question where question_id = 1;
select id, name from tag where name=’eclipse’; => tag_id = 12
select id, name from tag where name=’ant’; => tag_id = 13
insert into question_tag values(1, 13);
delete from question_tag where question_id=1 and tag_id=11;
update tag set tagged_count = tagged_count + 1 where name=’ant’;
update tag set tagged_count = tagged_count - 1 where name=’java’;
객체 간의 관계는 사라지고 데이터베이스에 대한 처리에 집중하게 된다.
즉, 비즈니스 로직 구현 보다 데이터베이스 접근 로직 구현에 집중한다.
도메인(객체) 주도 개발
객체 설계로부터 시작 한다면…

요구사항
질문 할 때 태그를 추가할 수 있다. (예 java eclipse)
질문을 수정할 때 태그를 수정할 수 있다. (예. eclipse ant)
일단 테이블 구조는 의식하지 않고 비즈니스 로직 구현한다.
데모
질문 추가 1단계 – 태그 목록을 추출한다.

• 쉼표(,)로 구분되어 있는 태그를 파싱한다.(예. java eclipse)
• 태그가 태그 풀에 존재하는지 확인한다.
• 태그 풀에 존재하지 않으면 태그를 신규 태그로 등록한다.
데모
질문 추가 2단계 – Question에 태그 추가

• 태그 목록을 Question에 전달한다.
• Question에 추가한 모든 Tag의 taggedCount를 +1 증가시킨다.
데모
질문을 수정

• 질문 추가할 때와 같이 태그 목록을 구한다.(예. eclipse ant)
• 추가된 태그는 Tag의 taggedCount를 +1 증가한다.
• 삭제된 태그는 Tag의 taggedCount를 -1 감소한다.
객체와 테이블 매핑
데모

@Entity
public class Question {
@ManyToOne
@org.hibernate.annotations.ForeignKey(name = "fk_question_writer")
private User writer;
@Column(name = "title", length = 100, nullable = false)
private String title;
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "created_date", nullable = false, updatable = false)
private Date createdDate;
}

[...]
테이블의 primary key와 이와 매핑되는 객체의 필드를 설계한다.

@Entity
public class Question {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long questionId;
}

[...]
데이터베이스 성능에 대한 고려해 설계한다.
@Entity
public class Question {
@ElementCollection(fetch = FetchType.LAZY)
@CollectionTable(name = "question_content_holder", joinColumns = @JoinColumn(name = "question_id", unique
= true))
@org.hibernate.annotations.ForeignKey(name = "fk_question_content_holder_question_id")
@Lob
@Column(name = "contents", nullable = false)
private Collection<String> contentsHolder;
public Question(User writer, String title, String contents, Set<Tag> tags) {
this.writer = writer;
this.title = title;
this.contentsHolder = Lists.newArrayList(contents);
processTags(tags);
this.tags = tags;
this.createdDate = new Date();
}
[...]
public String getContents() {
if (isEmptyContentsHolder()) {
return "";
}

}

}

return Iterables.getFirst(contentsHolder, "");
객체의 구조(특히 상속)와 테이블의 구조가 일치하지 않는 부분을 고려
해 설계한다.
안정화 단계까지 ORM의 스키마 자동 생성 기반으로 개
발
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence" [...]>
<persistence-unit name="slipp.qna" transaction-type="RESOURCE_LOCAL">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<properties>
<property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect" />
<property name="hibernate.hbm2ddl.auto" value="create" />
<property name="hibernate.ejb.naming_strategy"
value="org.hibernate.cfg.ImprovedNamingStrategy" />
<property name="hibernate.format_sql" value="true" />
<property name="hibernate.show_sql" value="false"/>
</properties>
</persistence-unit>
</persistence>
ORM 사용시 테이블 스키마
관리
데모

public class JPASchemaExport {
public static void main(String[] args) {
Ejb3Configuration cfg = new Ejb3Configuration();
HashMap<String, String> props = new HashMap<String, String>();
props.put("hibernate.format_sql", "true");
Ejb3Configuration configured = cfg.configure("slipp.qna", props);
SchemaExport se = new SchemaExport(configured.getHibernateConfiguration());
se.setDelimiter(";");
se.create(true, false);
}
}

객체와 테이블을 매핑하면 테이블 스키마를 자동으로 Export할 수 있다.
데모

DB Migration 도구를 활용해 스키마 관리
Maven Carbon Five 플러그인 활용(https://code.google.com/p/c5-db-migration/)
기능 추가 및 변경시 스키마
관리
기능 안정화 단계
DB Migration 도구를 활용해 테이블 스키마 변경 관리한다.

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<persistence [...]>
<persistence-unit name="slipp.qna" transaction-type="RESOURCE_LOCAL">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<properties>
<property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect" />
<property name="hibernate.hbm2ddl.auto" value="update" />
<property name="hibernate.ejb.naming_strategy"
value="org.hibernate.cfg.ImprovedNamingStrategy" />
<property name="hibernate.format_sql" value="true" />
<property name="hibernate.show_sql" value="false"/>
</properties>
</persistence-unit>
</persistence>
데모

요구사항

질문을 상세보기 할 때마다 조회수를 1 증가시킨다.
schema_version 테이블
자바 진영에서 사용할 수 있는 DB Migration Tools
•

Flyway

•

Liquibase

•

c5-db-migration

•

dbdeploy

•

mybatis

•

MIGRATEdb

•

migrate4j

•

dbmaintain

•

AutoPatch
DB Migration Tools 시작
•

http://flywaydb.org/에 각 도구별 비교 자료 확인

•

자신의 프로젝트에 적합한 도구 선정

•

개발 단계부터 DB Migration Tool 기반으로 개발해야 성공 가능성이 높다.
데모

QnA 게시판 완료 - SLiPP
마치며…
도메인 주도 개발

객체 속성 추가 및 테이블 칼럼 추가

데이터베이스 접근 로직 구현

매핑 정보를 활용해 테이블 스키마 생
성

객체(도메인) 설계

비즈니스 로직 구현

객체와 테이블 매핑
테이블 주도 개발

객체 속성 추가 및 테이블 칼럼 추가

비즈니스 로직 구현

데이터베이스 접근 로직 구현

테이블 설계

테이블로부터 객체 생성

객체와 테이블 매핑
도메인 주도 개발

객체 속성 추가 및 테이블 칼럼 추가

데이터베이스 접근 로직 구현

매핑 정보를 활용해 테이블 스키마 생
성

데이터베이스 서버 필요함

객체(도메인) 설계

비즈니스 로직 구현

객체와 테이블 매핑

데이터베이스 서버가 없는 상태에서 개발
가능
테이블 주도 개발
객체 속성 추가 및 테이블 칼럼 추가

비즈니스 로직 구현

데이터베이스 접근 로직 구현

테이블 설계

테이블로부터 객체 생성

객체와 테이블 매핑

데이터베이스 서버에 항상 의존관계를 가진다.
도메인 주도 개발

데이터베이스에 의존하지 않은 상태에서 개발 가능한 시간이 있기 때문에 구현 – 피드백 사이클이

빠르다.
빠른 피드백 사이클은 삽질할 수 있는 시간을 확보함으로써 빠른 지식 축적이 가능하다.
지식 축적은 도메인에 최적화된 설계를 할 수 있도록 한다.
좋은 설계는 사용자의 요구사항 변화에 빠르게 대응할 수 있다.
개발자는 소스 코드에 대한 자부심과 여유 시간을 확보할 수 있다.
질문 : 지금 내가 일하는 곳은 DBA의 영향력이 너무 커서 변화를 만들 수 없다.
어떻게??

틈틈이 객체 설계, ORM에 대한 공부한다. 장난감 프로젝트를 하면 더 좋다.

몇 년이 지나 본인이 프로젝트를 주도할 때 도메인 주도 개발로 진행한다.
질문 : ORM 적용하고 싶은데 팀장님이나 선배 개발자가 못하게 해요.
어떻게??

틈틈이 객체 설계, ORM에 대한 공부한다. 장난감 프로젝트를 하면 더 좋다.

몇 년이 지나 본인이 프로젝트를 주도할 때 도메인 주도 개발로 진행한다.
현재 내 영향력 하에서 변화를 만들 수 있는 부분에 집중하자.
점차 영향력을 확대해 나간다.
질문 및 후기
www.slipp.net
데모 소스 코드
• https://github.com/javajigi/slipping
• 이 프로젝트의 slipp-qna 프로젝트
• orm_start부터 orm_step5 브랜치

ORM을 활용할 경우의 설계, 개발 과정

  • 1.
    Object Relational Mapping(이하ORM) 기반 설계 및 개발 과정 박재성 www.slipp.net
  • 2.
  • 4.
    요구사항 사용자는 질문을 할수 있어야 한다. 질문에 대한 답변을 할 수 있어야 한다. 질문을 할 때 태그를 추가할 수 있어야 한다. 태그는 태그 풀에 존재하는 태그만 추가할 수 있다. 태그가 추가될 경우 해당 태그 수는 +1 증가, 삭제될 경우 해당 태그 수는 -1 증가해야 한다.
  • 5.
  • 6.
  • 7.
  • 8.
    테이블 설계로부터 시작한다면… 요구사항 질문 할 때 태그를 추가할 수 있다. (예 java eclipse) 질문을 수정할 때 태그를 수정할 수 있다. (예. eclipse ant)
  • 10.
    질문 추가시 javaeclipse 태그 추가할 경우 insert into question values( ?, ?, ?, ?, ?); => question_id = 1 select id, name from tag where name=’java’; => tag_id = 11 select id, name from tag where name=’eclipse’; => tag_id = 12 insert into question_tag values( 1, 11 ); insert into question_tag values( 1, 12 ); update tag set tagged_count = tagged_count + 1 where name=’java’; update tag set tagged_count = tagged_count + 1 where name=’eclipse’;
  • 11.
    질문 수정시 eclipseant 태그 추가할 경우 update question set title=?, contents=? where question_id = 1; delete from question where question_id = 1; select id, name from tag where name=’eclipse’; => tag_id = 12 select id, name from tag where name=’ant’; => tag_id = 13 insert into question_tag values(1, 13); delete from question_tag where question_id=1 and tag_id=11; update tag set tagged_count = tagged_count + 1 where name=’ant’; update tag set tagged_count = tagged_count - 1 where name=’java’;
  • 12.
    객체 간의 관계는사라지고 데이터베이스에 대한 처리에 집중하게 된다. 즉, 비즈니스 로직 구현 보다 데이터베이스 접근 로직 구현에 집중한다.
  • 13.
  • 14.
    객체 설계로부터 시작한다면… 요구사항 질문 할 때 태그를 추가할 수 있다. (예 java eclipse) 질문을 수정할 때 태그를 수정할 수 있다. (예. eclipse ant)
  • 15.
    일단 테이블 구조는의식하지 않고 비즈니스 로직 구현한다.
  • 16.
    데모 질문 추가 1단계– 태그 목록을 추출한다. • 쉼표(,)로 구분되어 있는 태그를 파싱한다.(예. java eclipse) • 태그가 태그 풀에 존재하는지 확인한다. • 태그 풀에 존재하지 않으면 태그를 신규 태그로 등록한다.
  • 17.
    데모 질문 추가 2단계– Question에 태그 추가 • 태그 목록을 Question에 전달한다. • Question에 추가한 모든 Tag의 taggedCount를 +1 증가시킨다.
  • 18.
    데모 질문을 수정 • 질문추가할 때와 같이 태그 목록을 구한다.(예. eclipse ant) • 추가된 태그는 Tag의 taggedCount를 +1 증가한다. • 삭제된 태그는 Tag의 taggedCount를 -1 감소한다.
  • 19.
  • 20.
    데모 @Entity public class Question{ @ManyToOne @org.hibernate.annotations.ForeignKey(name = "fk_question_writer") private User writer; @Column(name = "title", length = 100, nullable = false) private String title; @Temporal(TemporalType.TIMESTAMP) @Column(name = "created_date", nullable = false, updatable = false) private Date createdDate; } [...]
  • 21.
    테이블의 primary key와이와 매핑되는 객체의 필드를 설계한다. @Entity public class Question { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long questionId; } [...]
  • 22.
    데이터베이스 성능에 대한고려해 설계한다. @Entity public class Question { @ElementCollection(fetch = FetchType.LAZY) @CollectionTable(name = "question_content_holder", joinColumns = @JoinColumn(name = "question_id", unique = true)) @org.hibernate.annotations.ForeignKey(name = "fk_question_content_holder_question_id") @Lob @Column(name = "contents", nullable = false) private Collection<String> contentsHolder; public Question(User writer, String title, String contents, Set<Tag> tags) { this.writer = writer; this.title = title; this.contentsHolder = Lists.newArrayList(contents); processTags(tags); this.tags = tags; this.createdDate = new Date(); } [...] public String getContents() { if (isEmptyContentsHolder()) { return ""; } } } return Iterables.getFirst(contentsHolder, "");
  • 23.
    객체의 구조(특히 상속)와테이블의 구조가 일치하지 않는 부분을 고려 해 설계한다.
  • 24.
    안정화 단계까지 ORM의스키마 자동 생성 기반으로 개 발 <?xml version="1.0" encoding="UTF-8" standalone="no"?> <persistence xmlns="http://java.sun.com/xml/ns/persistence" [...]> <persistence-unit name="slipp.qna" transaction-type="RESOURCE_LOCAL"> <provider>org.hibernate.ejb.HibernatePersistence</provider> <properties> <property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect" /> <property name="hibernate.hbm2ddl.auto" value="create" /> <property name="hibernate.ejb.naming_strategy" value="org.hibernate.cfg.ImprovedNamingStrategy" /> <property name="hibernate.format_sql" value="true" /> <property name="hibernate.show_sql" value="false"/> </properties> </persistence-unit> </persistence>
  • 25.
    ORM 사용시 테이블스키마 관리
  • 26.
    데모 public class JPASchemaExport{ public static void main(String[] args) { Ejb3Configuration cfg = new Ejb3Configuration(); HashMap<String, String> props = new HashMap<String, String>(); props.put("hibernate.format_sql", "true"); Ejb3Configuration configured = cfg.configure("slipp.qna", props); SchemaExport se = new SchemaExport(configured.getHibernateConfiguration()); se.setDelimiter(";"); se.create(true, false); } } 객체와 테이블을 매핑하면 테이블 스키마를 자동으로 Export할 수 있다.
  • 27.
    데모 DB Migration 도구를활용해 스키마 관리 Maven Carbon Five 플러그인 활용(https://code.google.com/p/c5-db-migration/)
  • 28.
    기능 추가 및변경시 스키마 관리
  • 29.
    기능 안정화 단계 DBMigration 도구를 활용해 테이블 스키마 변경 관리한다. <?xml version="1.0" encoding="UTF-8" standalone="no"?> <persistence [...]> <persistence-unit name="slipp.qna" transaction-type="RESOURCE_LOCAL"> <provider>org.hibernate.ejb.HibernatePersistence</provider> <properties> <property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect" /> <property name="hibernate.hbm2ddl.auto" value="update" /> <property name="hibernate.ejb.naming_strategy" value="org.hibernate.cfg.ImprovedNamingStrategy" /> <property name="hibernate.format_sql" value="true" /> <property name="hibernate.show_sql" value="false"/> </properties> </persistence-unit> </persistence>
  • 30.
    데모 요구사항 질문을 상세보기 할때마다 조회수를 1 증가시킨다.
  • 31.
  • 33.
    자바 진영에서 사용할수 있는 DB Migration Tools • Flyway • Liquibase • c5-db-migration • dbdeploy • mybatis • MIGRATEdb • migrate4j • dbmaintain • AutoPatch
  • 34.
    DB Migration Tools시작 • http://flywaydb.org/에 각 도구별 비교 자료 확인 • 자신의 프로젝트에 적합한 도구 선정 • 개발 단계부터 DB Migration Tool 기반으로 개발해야 성공 가능성이 높다.
  • 35.
  • 36.
  • 37.
    도메인 주도 개발 객체속성 추가 및 테이블 칼럼 추가 데이터베이스 접근 로직 구현 매핑 정보를 활용해 테이블 스키마 생 성 객체(도메인) 설계 비즈니스 로직 구현 객체와 테이블 매핑
  • 38.
    테이블 주도 개발 객체속성 추가 및 테이블 칼럼 추가 비즈니스 로직 구현 데이터베이스 접근 로직 구현 테이블 설계 테이블로부터 객체 생성 객체와 테이블 매핑
  • 39.
    도메인 주도 개발 객체속성 추가 및 테이블 칼럼 추가 데이터베이스 접근 로직 구현 매핑 정보를 활용해 테이블 스키마 생 성 데이터베이스 서버 필요함 객체(도메인) 설계 비즈니스 로직 구현 객체와 테이블 매핑 데이터베이스 서버가 없는 상태에서 개발 가능
  • 40.
    테이블 주도 개발 객체속성 추가 및 테이블 칼럼 추가 비즈니스 로직 구현 데이터베이스 접근 로직 구현 테이블 설계 테이블로부터 객체 생성 객체와 테이블 매핑 데이터베이스 서버에 항상 의존관계를 가진다.
  • 41.
    도메인 주도 개발 데이터베이스에의존하지 않은 상태에서 개발 가능한 시간이 있기 때문에 구현 – 피드백 사이클이 빠르다. 빠른 피드백 사이클은 삽질할 수 있는 시간을 확보함으로써 빠른 지식 축적이 가능하다. 지식 축적은 도메인에 최적화된 설계를 할 수 있도록 한다. 좋은 설계는 사용자의 요구사항 변화에 빠르게 대응할 수 있다. 개발자는 소스 코드에 대한 자부심과 여유 시간을 확보할 수 있다.
  • 42.
    질문 : 지금내가 일하는 곳은 DBA의 영향력이 너무 커서 변화를 만들 수 없다. 어떻게?? 틈틈이 객체 설계, ORM에 대한 공부한다. 장난감 프로젝트를 하면 더 좋다. 몇 년이 지나 본인이 프로젝트를 주도할 때 도메인 주도 개발로 진행한다.
  • 43.
    질문 : ORM적용하고 싶은데 팀장님이나 선배 개발자가 못하게 해요. 어떻게?? 틈틈이 객체 설계, ORM에 대한 공부한다. 장난감 프로젝트를 하면 더 좋다. 몇 년이 지나 본인이 프로젝트를 주도할 때 도메인 주도 개발로 진행한다.
  • 44.
    현재 내 영향력하에서 변화를 만들 수 있는 부분에 집중하자. 점차 영향력을 확대해 나간다.
  • 45.
  • 46.
  • 47.
    데모 소스 코드 •https://github.com/javajigi/slipping • 이 프로젝트의 slipp-qna 프로젝트 • orm_start부터 orm_step5 브랜치