JPQL : 객체지향쿼리

- SQL을 추상화한 JPA의 객체지향 쿼리

- 테이블이 아닌 엔티티 객체를 대상으로 쿼리

- 엔티티와 속성은 대소문자 구분

- 테이블 이름이 아닌 엔티티 이름 사용

- 별칭(alias) 사용 필수

 

 

1. EntityManager 사용시

select alias 시 전체 조회 (sql 에서 a.* 와 같음)

변수바인딩은 :이름 혹은 ?시퀀스 로 할 수 있다

1
2
3
4
5
6
7
8
9
10
11
12
@Autowired
EntityManager em;
 
User user = new User();
user.setName("pyo");
userRepository.save(user);
      
TypedQuery<User> tq = em.createQuery("select u from User u where u.name = :name", User.class);
tq.setParameter("name""pyo");
      
List<User> rs = tq.getResultList();
LOGGER.info("id : " + rs.get(0).getId());
cs

 

2. repository interface 사용시

@Query를 사용해서 직접 JPQL 지정

이름을 사용한 바인딩시 @Param("이름") 을 사용하지 않을 경우 에러가 발생하니 주의

1
2
3
4
5
6
7
8
9
10
11
12
13
public interface UserRepository extends JpaRepository<User, Long>{
    
   List<User> findByName(String name);
   User findById(long id);
   
   @Query("select u from User u where u.name = ?1")
   User customFind1(String nm);
   
   
   @Query("select u from User u where u.name = :name")
   User customFind2(@Param("name"String nm);
   
}
cs

 

JPQL , SQL 의 문제점

- JPQL 은 문자(String)이며, Type-Check가 불가능.

- 해당 로직 실행 전까지 작동여부 확인이 불가

- 실행시점에 오류가 발견됨

 

 

QueryDSL

- 문자가 아닌 코드로 작성

- 컴파일 시점 문법 오류 발견이 가능

- 코드 자동 완성

- 동적쿼리 가능

- JPQL 단점 보완

 

[gradle 설정]

gradle 설정은 gradle 버전마다 상이하다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
buildscript {
    ext {
        springBootVersion = '2.1.4.RELEASE'
        querydslPluginVersion = '1.0.10'
    }
    repositories {
        mavenCentral()
        maven { url "https://plugins.gradle.org/m2/" } // plugin 저장소
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
        classpath "io.spring.gradle:dependency-management-plugin:1.0.7.RELEASE"
        classpath("gradle.plugin.com.ewerk.gradle.plugins:querydsl-plugin:${querydslPluginVersion}")
    }
}
 
 
apply plugin: 'java'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'
apply plugin: "com.ewerk.gradle.plugins.querydsl"
apply plugin: 'eclipse'
 
group = 'com.jpp'
version = '0.0.1'
sourceCompatibility = '1.8'
targetCompatibility = '1.8'
 
configurations {
   compileOnly {
      extendsFrom annotationProcessor
   }
}
 
repositories {
   mavenCentral()
}
 
dependencies {
   compile 'org.springframework.boot:spring-boot-starter-actuator'
   compile 'org.springframework.boot:spring-boot-starter-data-jpa'
   compile 'org.springframework.boot:spring-boot-starter-web'
   compileOnly 'org.projectlombok:lombok'
   runtimeOnly 'com.h2database:h2'
   annotationProcessor 'org.projectlombok:lombok'
   
   compile 'org.springframework.boot:spring-boot-starter-tomcat'
   
   testCompile('org.springframework.boot:spring-boot-starter-test') {
      exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
   }
   
   compile 'org.apache.tomcat.embed:tomcat-embed-jasper'
   compile 'javax.servlet:jstl'
   
   compile 'org.json:json:20180813'
   compile 'com.google.code.gson:gson:2.8.5'
   
   compile group: 'org.aspectj'name'aspectjweaver', version: '1.9.2'
   compile group: 'org.aspectj'name'aspectjrt', version: '1.9.2'
   
   compile 'com.querydsl:querydsl-jpa' // querydsl
   compile 'com.querydsl:querydsl-apt' // querydsl
}
 
test {
   useJUnitPlatform()
   ignoreFailures = true
}
 
def querydslSrcDir = 'src/main/generated'
 
querydsl {
    library = "com.querydsl:querydsl-apt"
    jpa = true
    querydslSourcesDir = querydslSrcDir
}
 
sourceSets {
    main {
        java {
            srcDirs = ['src/main/java', querydslSrcDir]
        }
    }
}
 
compileQuerydsl{
    options.annotationProcessorPath = configurations.querydsl
}
 
configurations {
    querydsl.extendsFrom compileClasspath
}
 
cs

 

gradle build 후 지정한 경로(src/main/generated)에 Query Class가 생성되었음을 확인 할 수 있다.

 

[QueryDSL 사용]

1
2
3
4
5
6
7
8
9
@PersistenceContext
EntityManager em;
 
public List<User> selectUserByNm(String nm){
  JPAQueryFactory jqf = new JPAQueryFactory(em);
  
  return jqf.selectFrom(user).where(user.name.eq(nm)).fetch();
}
 
cs

 

 

참고 :

Tacademy 김영한 개발자님의 강의

김영한 개발자님의 JPA 저서

https://www.baeldung.com/spring-data-jpa-query

https://www.baeldung.com/querydsl-with-jpa-tutorial

 

 

 

 

반응형

'back > JPA' 카테고리의 다른 글

[JPA] cascade  (0) 2022.11.10
[JPA] equals , hashcode  (0) 2022.11.08
[JPA] JPA 영속성 컨텍스트  (0) 2020.03.08
[JPA] 연관관계 매핑 : 단방향, 양방향 매핑  (0) 2020.03.01
[JPA] JPA 기초 설정 및 entity 필드 매핑  (0) 2020.02.27

1. Servlet 생명주기

Init > Service(get/post) > Destory

https://minwan1.github.io/2017/10/08/2017-10-08-Spring-Container,Servlet-Container/

https://javannspring.tistory.com/231

 

 

2. BeanFactory 와 ApplicationContext 구조

https://whiteship.tistory.com/518

BeanFactory 를 ListenableBeanFactory, HierachialBeanFactory가 구현

ListenableBeanFactory, HierachialBeanFactory를 ApplicationContext가 구현

XmlWebApplicationContext, FileSystemXmlApplicationContext, ClassPathXmlApplicationContext 등이ApplicationContext 구현

 

 

3. 객체 생성의 차이

https://otep.tistory.com/19

ApplicationContext 는 Bean을 preload.

BeanFactory getBean 은 lazy 

 

 

4. Init/Destroy Method 지정 방법

Init 지정 방법

https://www.baeldung.com/running-setup-logic-on-startup-in-spring

1. @PostConstruct 사용

2. InitializingBean Interface 구현, afterPropertiesSet() 사용

3. ApplicationListener<ContextEvt> 구현

4. @Bean InitMethod 사용

순서 : @PostConstruct > InitializingBean > init-method > ContextStartedEvent 

https://programmer.ink/think/5ddc351b29481.html

 

Destory 지정 방법

https://www.baeldung.com/spring-shutdown-callbacks

1. @PreDestroy 사용

2. DisposableBean Interface 구현, Destroy() 사용

3. ApplicationListener<ContextEvt> 구현

4. ServletContetListener 구현 (web.xml 등록)

5. @Bean destroyMethod 사용

순서 : ContextClosedEvent > @PreDestroy > DisposableBean > destroy-method 

https://programmer.ink/think/5ddc351b29481.html

 

※ Init 은 Order @DependOn 등을 사용하여 주입 순서를 지정 할 수 있음

※ Bean Destroy 는 Bean Init 의 역순으로 이루어짐

 

 

반응형

영속성 컨텍스트

- 엔티티를 영구 저장하는 환경

- 영속성 컨텍스트는 논리적인 개념으로 눈에 보이지 않음

- 엔티티 매니저를 통해 영속성 컨텍스트에 접근

 

 

생명주기

비영속(new)

영속성 컨텍스트와 관계 없는 상태

Member member = new Member();
member.setId("m1");
member.setUsername("");

영속(managed)

영속성 컨텍스트에 저장된 상태

Member member = new Member();
member.setId("m1");
member.setUsername("");

EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
em.persist(member);

준영속(detached)

영속성 컨텍스트에 저장되었다가 분리된 상태

em.detach(member);

삭제(delete)

영속성 컨텍스트와 DB에서 삭제된 상태

em.remove(member);

 

영속성 컨텍스트가 제공하는 이점

1. JPA 의 조회시 캐시 기능

em.find(Member.class, "m1");

영속 컨텍스트(entityManager)(1차 캐시) 에서 먼저 조회

(영속 컨텍스트에 존재하지 않을시 DB에서 조회 후 영속 컨텍스트에 저장, 그리고 반환)

 

2. 영속 엔티티의 동일성 보장

Member a = em.find(Member.class, "m1");

Member b = em.find(Member.class, "m1");

System.out.println(a==b); //true

1차 캐시로 반복 가능한 읽기(REPEATABLE READ)등급의 트랜잭션 격리 수준을 데이터베이스가 아닌 애플리케이션 차원에서 제공

 

 

3. 트랜잭션을 지원하는 쓰기 지원

em.persist(member); //쓰기지연 SQL 저장소, 1차 캐시에 저장

flush / commit 시 DB에 insert 쿼리를 보낸다.

 

4. 엔티티 수정시 변경 감지

Member memberA = em.find(Member.class, "m1");
memberA.setUserName("pyo");
memberA.setAge(10);

em.update(memberA); 와 같은 코드가 필요없다.

transaction.commit;

flush/commit 시점에 스냅샷을 확인하여 바뀐 값이 존재시 update 쿼리를 DB에 보낸다.

collection framework list 에서 값을 변경 후 list 에 값을 다시 넣어주지 않아도 list 값이 바뀌는 것과 마찬가지로

update 를 직접 해줄 필요가 없다.

 

JPQL 쿼리 실행시 flush가 자동으로 호출된다.

아래와 같이 flush 및 commit을 직접적으로 해주지 않을 경우 query 로 조회된 결과가 나오지 않으므로

이같은 실수를 방지하기 위해  JPQL 쿼리 실행시 flush 자동으로 호출.

em.persist(memberA);
em.persist(memberB);

query = em.createQuery("select m from Member m", member.class);
List<Member> members = query.getResultList();

flush 는 영속성 컨텍스트를 비우는게 아닌, 영속성 컨텍스트의 변경내용을 DB에 동기화

트랜잭션 작업단위가 중요. 커밋 직전에만 동기화 하면 된다.

 

준영속 상태로 만드는 방법

em.detach(memberA) 특정 엔티티만 준영속 상태로 전환

em.clear() 영속성 컨텍스트 초기화

em.close() 영속성 컨텍스트 종료

 

 

반응형

JPA에서의 연관관계 매핑

 

1. 단방향 매핑

Team 1 : User N 인 경우

User entity가 참조할 Team entity 매핑키(tid)를 넣어준다.

* 1:N 관계에서 N 테이블에 1테이블의 fk 를 가지고 있다.

  관계의 주인은 N 테이블이 된다.

 

[Sample Code]

[Team entity]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package com.jpp.webservice.web.domain.team;
 
import java.util.ArrayList;
import java.util.List;
 
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToMany;
 
import com.jpp.webservice.web.domain.user.User;
 
@Entity
public class Team {
    
   @Id
   @GeneratedValue(strategy=GenerationType.AUTO)
   private Long tid;
    
   private String teamNm;
 
   public Long getTid() {
      return tid;
   }
 
   public void setTid(Long tid) {
      this.tid = tid;
   }
 
   public String getTeamNm() {
      return teamNm;
   }
 
   public void setTeamNm(String teamNm) {
      this.teamNm = teamNm;
   }
}
cs

 

[User entity]

User 엔티티가 참조할 Team 의 tid 칼럼을 필드로 갖게한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package com.jpp.webservice.web.domain.user;
 
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
 
@Entity
public class User {
    
   @Id
   @GeneratedValue
   private Long id;
    
   private String name;
    
   private String mobileNum;
 
   private Long tid;
    
   public Long getId() {
      return id;
   }
   public void setId(Long id) {
      this.id = id;
   }
   public String getName() {
      return name;
   }
   public void setName(String name) {
      this.name = name;
   }
   public String getMobileNum() {
      return mobileNum;
   }
   public void setMobileNum(String mobileNum) {
      this.mobileNum = mobileNum;
   }
   public Long getTid() {
      return tid;
   }
   public void setTid(Long tid) {
      this.tid = tid;
   }
    
}
cs

 

[테스트]

User 를 저장할때 tid를 넣어주어 Team과 User를 매핑해준다.

실제로 참조관계를 설정해준건 아니기 때문에 연관관계가 있다고 하긴 모호하다.

일단 tid를 기준으로 User를 조회해보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
package com.jpp.webservice.web.domain.team;
 
import java.util.List;
 
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit4.SpringRunner;
 
import com.jpp.webservice.web.domain.user.User;
import com.jpp.webservice.web.domain.user.UserRepository;
 
@RunWith(SpringRunner.class)
@SpringBootTest
@ActiveProfiles("test")
public class TeamRepositoryTest {
   
   protected static final Logger LOGGER = LoggerFactory.getLogger(TeamRepositoryTest.class);
   
   @Autowired
   TeamRepository teamRepository;
   
   @Autowired
   UserRepository userRepository;
   
   @Test
   public void test() {
      Team team = new Team();
      team.setTeamNm("vip");
      LOGGER.info("tid before save >>> " + team.getTid());    //save 를 하기 전엔 entity의 id(pk)가 생성되지 않는다
      teamRepository.save(team);
      
      Long tid = team.getTid();
      LOGGER.info("tid after save >>> " + tid); //save 를 하고 난 후 entity의 id(pk)가 생성되어 있다
      
      List<Team> teams = teamRepository.findAll();
      for(Team t : teams) {
         LOGGER.info("t nm:"+t.getTeamNm());
         LOGGER.info("id:"+t.getTid());
      }
      
      User user = new User();
      user.setMobileNum("01012345678");
      user.setName("pyo");
      user.setTid(tid);    //team entity를 save 할 때 사용한 tid를 user entity 의 fk칼럼인 tid에 넣어준다
      
      userRepository.save(user);
      List<User> users = userRepository.findByName("pyo");
      for(User u : users) {
         LOGGER.info("u nm:"+u.getName());
         LOGGER.info("u tid:"+u.getTid());
         List<User> users2 = userRepository.findByTid(u.getTid());
         for(User us : users2) {
            LOGGER.info("us : " + us.getName());
         }
      }
   }
}
cs

 

[결과]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
Hibernate: 
    drop table posts if exists
Hibernate: 
    drop table team if exists
Hibernate: 
    drop table user if exists
Hibernate: 
    create table posts (
        id bigint generated by default as identity,
        author varchar(255),
        content TEXT not null,
        title varchar(500not null,
        primary key (id)
    )
Hibernate: 
    create table team (
        tid bigint generated by default as identity,
        team_nm varchar(255),
        primary key (tid)
    )
Hibernate: 
    create table user (
        id bigint generated by default as identity,
        mobile_num varchar(255),
        name varchar(255),
        tid bigint,
        primary key (id)
    )
09:53:57.773 [main] INFO  o.h.tool.hbm2ddl.SchemaExport - HHH000230: Schema export complete
 
 
 
09:54:00.266 [main] INFO  c.j.w.w.d.team.TeamRepositoryTest - tid before save >>> null
Hibernate: 
    insert 
    into
        team
        (tid, team_nm) 
    values
        (null, ?)
09:54:00.324 [main] INFO  c.j.w.w.d.team.TeamRepositoryTest - tid after save >>> 1
09:54:00.344 [main] INFO  o.h.h.i.QueryTranslatorFactoryInitiator - HHH000397: Using ASTQueryTranslatorFactory
Hibernate: 
    select
        team0_.tid as tid1_1_,
        team0_.team_nm as team_nm2_1_ 
    from
        team team0_
09:54:00.485 [main] INFO  c.j.w.w.d.team.TeamRepositoryTest - t nm:vip
09:54:00.485 [main] INFO  c.j.w.w.d.team.TeamRepositoryTest - id:1
Hibernate: 
    insert 
    into
        user
        (id, mobile_num, name, tid) 
    values
        (null, ?, ?, ?)
Hibernate: 
    select
        user0_.id as id1_2_,
        user0_.mobile_num as mobile_n2_2_,
        user0_.name as name3_2_,
        user0_.tid as tid4_2_ 
    from
        user user0_ 
    where
        user0_.name=?
09:54:00.525 [main] INFO  c.j.w.w.d.team.TeamRepositoryTest - u nm:pyo
09:54:00.525 [main] INFO  c.j.w.w.d.team.TeamRepositoryTest - u tid:1
Hibernate: 
    select
        user0_.id as id1_2_,
        user0_.mobile_num as mobile_n2_2_,
        user0_.name as name3_2_,
        user0_.tid as tid4_2_ 
    from
        user user0_ 
    where
        user0_.tid=?
09:54:00.530 [main] INFO  c.j.w.w.d.team.TeamRepositoryTest - us : pyo
 
cs

위와같이 Team 에 넣은 tid 값을 기준으로 User를 조회할 수 있다.

중요한건 DDL 부분인데, pk만 지정되었을 뿐 참조관계, 즉 fk가 설정되지 않았다. (~28 Line 까지가 DDL 이며, 참조관계가 설정되는 구문이 보이지 않는다)

참조관계를 설정해줬다기 보단 User 테이블에 Team 테이블의 pk 칼럼을 포함시켰을 뿐이다.

(코드는 마치 fk 가 잡혀있듯이 개발하고, 실제 테이블엔 fk를 설정하지 않는 것 처럼)

 

그렇다면 진짜 참조관계(fk)를 설정해보자.

 

2. 양방향 매핑

1) 테이블에서의 양방향 매핑

- 외래 키 하나로 두 테이블 연관관계 관리

- MEMBER.TEAM_ID FK키 하나로 양방향 연관관계를 가지며 양쪽으로 조인이 가능하다 

SELECT *
FROM MEMBER M
JOIN TEAM T ON M.TEAM_ID = T.TEAM_ID

SELECT * 
FROM TEAM T
JOIN MEMBER M ON T.TEAM_ID M.TEAM_ID

2) 객체에서의 양방향 매핑

- 객체의 양방향 관계는 Table의 양방향 관계와 달리 서로 다른 단방향 관계 2개(순환참조)로 만든다.

  (위에서 살펴본 단방향 매핑 두개가 양쪽에 있는 개념)

Class A {
  B b;
}
Class B {
  A a;
}

 

[Sample Code]

[Team entity]

List<User> users 를 넣어주었다.

mappedBy는 말 그대로 매핑이 ?에 의해 이루어 진다는 의미로 참조를 당한다는 의미다.

@OneToMany는 1:N을 의미(앞쪽의 One 은 현재 엔티티 뒷쪽의 Many는 매핑된 다른 엔티티)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
package com.jpp.webservice.web.domain.team;
 
import java.util.ArrayList;
import java.util.List;
 
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToMany;
 
import com.jpp.webservice.web.domain.user.User;
 
@Entity
public class Team {
    
   @Id
   @GeneratedValue(strategy=GenerationType.AUTO)
   private Long tid;
    
   private String teamNm;
 
   @OneToMany(mappedBy = "team")
   private List<User> users = new ArrayList<User>();
    
   public Long getTid() {
      return tid;
   }
 
   public void setTid(Long tid) {
      this.tid = tid;
   }
 
   public String getTeamNm() {
      return teamNm;
   }
 
   public void setTeamNm(String teamNm) {
      this.teamNm = teamNm;
   }
 
   public List<User> getUsers() {
      return users;
   }
 
   public void setUsers(List<User> users) {
      this.users = users;
   }
}
 
cs

 

[User entity]

Team team을 넣어주었다.

@JoinColumn 어노테이션을 달아 주었으며 이는 외부 엔티티의 필드를 참조함을 의미한다.

@ManyToOne은 N:1을 의미 (앞쪽의 Many가 현재 entity)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
package com.jpp.webservice.web.domain.user;
 
 
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
 
import com.jpp.webservice.web.domain.team.Team;
 
@Entity
public class User {
    
  @Id
  @GeneratedValue
  private Long id;
    
  private String name;
    
  private String mobileNum;
    
  @ManyToOne
  @JoinColumn
  private Team team;
    
   public Long getId() {
      return id;
   }
   public void setId(Long id) {
      this.id = id;
   }
   public String getName() {
      return name;
   }
   public void setName(String name) {
      this.name = name;
   }
   public String getMobileNum() {
      return mobileNum;
   }
   public void setMobileNum(String mobileNum) {
      this.mobileNum = mobileNum;
   }
   public Team getTeam() {
      return team;
   }
   public void setTeam(Team team) {
      this.team = team;
   }
    
}
 
cs

JPA Entity 매핑에선

1 Entity엔 @OneToMany(mappedBy= 1의 Table명)

N Entity엔 @ManyToOne @JoinColumn(id = 1의 Key)

ex) Member 1 : Order N 관계에서의 Entity 설정은 아래와 같다

@Entity

class Member {

  @OneToMany(mappedBy = "member")

   private List<Order> orders = new ArrayList<>();

}

@Entity

class Order {

  @ManyToOne @JoinColumn(name = "member_id")

  private Member member;

}

 

 

[테스트]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
package com.jpp.webservice.web.domain.team;
 
import java.util.List;
 
import org.junit.After;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.transaction.annotation.Transactional;
 
import com.jpp.webservice.web.domain.user.User;
import com.jpp.webservice.web.domain.user.UserRepository;
 
@RunWith(SpringRunner.class)
@SpringBootTest
@ActiveProfiles("test")
public class TeamRepositoryTest {
   
   protected static final Logger LOGGER = LoggerFactory.getLogger(TeamRepositoryTest.class);
   
   @Autowired
   TeamRepository teamRepository;
   
   @Autowired
   UserRepository userRepository;
   
   @Test
   public void test1() {
      Team team = new Team();
      team.setTeamNm("vip");
      teamRepository.save(team);
      
      User user = new User();
      user.setMobileNum("01012345678");
      user.setName("pyo");
      
      team.getUsers().add(user);
      user.setTeam(team);
      
      userRepository.save(user);
   }
   
   @Test
   public void test2() {
      List<Team> teamList = teamRepository.findAll();
      for(Team t : teamList) {
         for(User u : t.getUsers()) {
            LOGGER.info("?!!");
            LOGGER.info("user name : "+u.getName());
            LOGGER.info("user team : "+u.getTeam());
         }
      }
   }
   
}
 
cs

[결과]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
16:15:34.405 [main] INFO  o.h.tool.hbm2ddl.SchemaExport - HHH000227: Running hbm2ddl schema export
Hibernate: 
    drop table posts if exists
Hibernate: 
    drop table team if exists
Hibernate: 
    drop table user if exists
Hibernate: 
    create table posts (
        id bigint generated by default as identity,
        author varchar(255),
        content TEXT not null,
        title varchar(500not null,
        primary key (id)
    )
Hibernate: 
    create table team (
        team_id bigint generated by default as identity,
        team_nm varchar(255),
        primary key (team_id)
    )
Hibernate: 
    create table user (
        id bigint generated by default as identity,
        mobile_num varchar(255),
        name varchar(255),
        team_id bigint,
        primary key (id)
    )
Hibernate: 
    alter table user 
        add constraint FKbmqm8c8m2aw1vgrij7h0od0ok 
        foreign key (team_id) 
        references team
16:15:34.431 [main] INFO  o.h.tool.hbm2ddl.SchemaExport - HHH000230: Schema export complete
 
Hibernate: 
    insert 
    into
        team
        (team_id, team_nm) 
    values
        (null, ?)
Hibernate: 
    insert 
    into
        user
        (id, mobile_num, name, team_id) 
    values
        (null, ?, ?, ?)
16:15:37.128 [main] INFO  o.h.h.i.QueryTranslatorFactoryInitiator - HHH000397: Using ASTQueryTranslatorFactory
Hibernate: 
    select
        team0_.team_id as team_id1_1_,
        team0_.team_nm as team_nm2_1_ 
    from
        team team0_
Hibernate: 
    select
        users0_.team_id as team_id4_2_0_,
        users0_.id as id1_2_0_,
        users0_.id as id1_2_1_,
        users0_.mobile_num as mobile_n2_2_1_,
        users0_.name as name3_2_1_,
        users0_.team_id as team_id4_2_1_ 
    from
        user users0_ 
    where
        users0_.team_id=?
16:15:37.264 [main] INFO  c.j.w.w.d.team.TeamRepositoryTest - ?!!
16:15:37.264 [main] INFO  c.j.w.w.d.team.TeamRepositoryTest - user name : pyo
cs

참조 조건이 설정됨을 확인할 수 있다. (31~34 line)

Team 에서 User를 조회할 수 있다.  (반대의 경우도 가능하다)

 

 

Owner : 연관관계의 주인

객체의 두 관계 중 하나를 연관관계의 주인으로 지정

연관관계의 주인만이 외래키를 관리(등록, 수정)

주인이 아닌 쪽은 읽기만 가능

주인은 mappeedBy 속성 사용하지 않음

주인이 아니면 mappedBy 속성으로 주인 지정

 

※ 일반적인 설계

위 예에서 User가 연관관계의 주인, 반대편인 Team은 가짜매핑(조회를 편하게 하기위한)

설계시 User의 Team을 주인으로 정한다. fk 키를 가지고 있는 칼럼을 주인으로 정해야 혼란을 방지할 수 있다.

 

※ 기타

양방향 매핑은 사실 필요가 없다.

조회를 편하게 하기 위함일 뿐, 일반적으론 단방향 매핑 관계만 적용하여 설계한다.

반대방향으로 조회가 필요한 경우, 그 때 양방향 관계(mappedBy)를 추가한다.

그리고 설계시 연관관계의 주인(JoinColumn)은 fk 키를 가지고 있는 entity의 필드를 주인으로 삼는다.

 

참고 :

Tacademy 김영한 개발자님의 강의

김영한 개발자님의 JPA 저서

https://www.baeldung.com/jpa-join-column

반응형

'back > JPA' 카테고리의 다른 글

[JPA] equals , hashcode  (0) 2022.11.08
[JPA] JPQL, QueryDSL  (0) 2020.03.21
[JPA] JPA 영속성 컨텍스트  (0) 2020.03.08
[JPA] JPA 기초 설정 및 entity 필드 매핑  (0) 2020.02.27
[JPA] JPA란? : 등장배경, ORM, 하이버네이트  (0) 2020.02.23

@Autowired

Type --> 이름 --> Qualifier --> fail

@Resource

이름 --> Type --> Qualifier --> fail

@Inject

Type --> Qualifier --> 이름 --> fail

 

https://engkimbs.tistory.com/682

반응형

- JPA 는 특정 데이터베이스에 종속적이지 않음

- dialect interface 를 구현하는 MySQLDialect, OracleDialect H2Dialect 등이 존재

(dialect : SQL 표준을 지키지 않거나 특정 데이터베이스만의 고유한 기능(ROWNUM(ORACLE) vs LIMIT(Mysql)))

- h2 : 크기가 작은 메모리 DB, 테스트용으로 사용

 

[pom.xml (dependency)]

org.hibernate.hibernate-entitymanager dependency필요(해당 dependency가 jpa.persistence-api, hibernate-core 땡겨옴)

 

[persistence.xml]

jpa 설정을 위한 persistence.xml가 필요(spring fw기준)(boot는 yml 혹은 property에 작성)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://xmlns.jcp.org/xml/ns/persistence" version=“2.2">
     <persistence-unit name="hello">
         <properties>
             <!-- 필수 속성 -->
             <property name="javax.persistence.jdbc.driver" value="org.h2.Driver"/>
             <property name="javax.persistence.jdbc.user" value="sa"/>
             <property name="javax.persistence.jdbc.password" value=""/>
             <property name="javax.persistence.jdbc.url" value="jdbc:h2:tcp://localhost/~/test"/>
             <property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect" />
             <!-- 옵션 -->
             <property name="hibernate.show_sql" value="true" />
             <property name="hibernate.format_sql" value="true" />
             <property name="hibernate.use_sql_comments" value="true" />
             <property name="hibernate.id.new_generator_mappings" value="true" />
             <!--<property name="hibernate.hbm2ddl.auto" value="create" />-->
         </properties>
     </persistence-unit>
</persistence>
cs

* hibernate.hbm2ddl.auto (hibernate.ddl-auto) 옵션

create : 기존테이블 삭제 후 다시 생성

create-drop : create와 같으나 종료 시점에 테이블 drop

update : 변경분만 반영

validate : 엔티티와 테이블 정상 매핑인지만 확인

none : 사용하지않음

* 환경별 옵션 사용

개발 초기(create, update)

테스트서버 (update, validate)

운영서버 (validate, none)

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Entity
public class Member {
    @Id
    private Long id;
    
    @Column(name = "USERNAME")
    private String name;
    
    private int age;
    
    @Temporal(TemporalType.TIMESTAMP)
    private Date regDate;
    
    @Enumerated(EnumType.STRING)
    private MemberType memberType;
}
cs

@Entity (javax.persistence)

: JPA로 관리할 객체

@Id

: DB PK와 매핑 할 필드 ( * 보통 auto_increment 로 설계)

@Column

- name : 필드와 실제로 매핑할 테이블의 칼럼명(테이블 칼럼명이 필드와 다를 경우 사용)

- nullable : null허용

- unique : 유니크제약

- length 등..

@Temporal(TemporalType.DATE)

: 날짜타입매핑

@Enumerated

: 열거형 매핑

- EnumType.ORDINAL : 순서 저장(default)

- EnumType.STRING : 열거형 이름 그대로 저장 (ordinal 대신 string 사용할 것)

@Lob

: CLOB, BLOB 알아서 매핑

CLOB : char[]

BLOB : byte[]

@Transient

: DB에 저장하지 않는 Column(앱단에서만 사용)

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public static void main(String args[]) {
    EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");    //factory는 하나만 생성하여 애플리케이션 전체에서 공유
    EntityManager em = emf.createEntityManager();    //요청마다 manager 생성하여 사용
    EntityTransaction tx = em.getTransaction();    //transcation
    tx.begin();
    try {
         Member member = new Member();
         member.setId(1L);
         member.setName("hello");
         em.persist(member);
         tx.commit();
    } catch (Exception e) {
        tx.rollback();
    } finally {
        em.close();
    }
    emf.close();
}
cs

 

 

반응형

'back > JPA' 카테고리의 다른 글

[JPA] equals , hashcode  (0) 2022.11.08
[JPA] JPQL, QueryDSL  (0) 2020.03.21
[JPA] JPA 영속성 컨텍스트  (0) 2020.03.08
[JPA] 연관관계 매핑 : 단방향, 양방향 매핑  (0) 2020.03.01
[JPA] JPA란? : 등장배경, ORM, 하이버네이트  (0) 2020.02.23

기존 개발방식(mybatis)의 문제

- 물리적으로 코드가 분리되어있지만 논리적인 분리는 이루어 지지 않음

- sql 에 의존적인 개발

- 유지보수의 문제(칼럼 추가 수정 삭제시 sql, 코드 수정 양이 큼)

- 패러다임 불일치 문제

   : a 테이블을 참조하고 있는 b 테이블,

    b 테이블에 데이터를 넣을 때 a 테이블에도 데이터를 넣어줘야 참조관계가 깨지지 않음

 

 

JPA 

: Java Persistence API

- 자바 진영의 ORM 기술 표준

 

ORM

: Object-relational mapping(객체 관계 매핑)

- 객체는 객체대로 설계, 관계형 데이터베이스는 관계형 데이터베이스대로 설계

- ORM 프레임워크가 이 둘을 중간에서 매핑

 

JPA는 애플리케이션과 DB 사이에 있다

JAVA APP <> JPA <> JDBC API <> DB

 

JPA는 인터페이스, 하이버네이트는 JPA의 구현체

 

동작순서(저장)

1. MemberDAO

2. PERSIST(entity)

3. JPA : Entity 분석, Insert ql 생성, JDBC API 사용, 패러다임 불일치 문제 해결

4. DB insert

* 패러다임 불일치 문제 : a 테이블을 참조하고 있는 b 테이블, b 테이블에 데이터 넣을 때 a 테이블에도 데이터를 넣어줘야 참조관계가 깨지지 않음

 

동작순서(조회)

MemberDAO

find(id)

JPA : SELECT SQL 생성, JDBC API 사용, ResultSet매핑, 패러다임 불일치 문제 해결

4. DB select

5. Entity Object 반환

 

JPA의 CRUD

저장 : jpa.persist(member)

조회 : Member member = jpa.find(memberId)

수정: member.setName("~")

삭제: jpa.remove(member)

 

 

반응형

'back > JPA' 카테고리의 다른 글

[JPA] equals , hashcode  (0) 2022.11.08
[JPA] JPQL, QueryDSL  (0) 2020.03.21
[JPA] JPA 영속성 컨텍스트  (0) 2020.03.08
[JPA] 연관관계 매핑 : 단방향, 양방향 매핑  (0) 2020.03.01
[JPA] JPA 기초 설정 및 entity 필드 매핑  (0) 2020.02.27

아래는 1년전 쯤 개발했던 httpUrlConnection 모듈이다

https://developyo.tistory.com/10?category=688588

 

시간이 지날 수록 소스는 누더기가 되어가고..

디자인패턴을 공부하고, 객체지향스러운 개발을 위해 이책 저책(클린코드 도서 등) 읽고나니

위 코드가 리펙토링이 필요하다는 것을 느껴 리펙토링을 해보았다.

 

상황]

1. 연동되는 서버가 계속해서 늘어났다.

2. 각각의 연동 서버에 맞춰야 되는 예외상황들이 늘어났다. (ex: timeout, charset, retry, responsecode..)

3. 예외상황들을 처리 하기 위한 파라미터들을 받기 위해 메소드의 인자값이 늘어났다.

4. 오버로딩되는 메소드가 늘어났다.

 

방향]

1. 객체를 받도록 처리하자. 

: 파라미터는 적을 수록 좋고,  파라미터가 너무 많이지면 객체를 파라미터로 받도록 소스를 리펙토링 해야한다.

2. boolean 값을 받는 메소드 1개는 두개의 메소드 따로 만들자. (도서 클린코드 참고)

: requestApi(boolean isJsonFormat, ...) 와 같이 boolean 값을 파라미터로 받은 후 분기처리 처리하지 말고

requestApiJsonFormat(...), requestApi(...) 와 같이 두개의 메소드로 분리해야 한다. (도서 클린코드 참고)

3. 필수값과 optional 값을 구분하기 위해 builder 패턴을 적용하여 객체를 설계해보자.

4. 전처리 후처리 로깅을 proxy 패턴을 적용하여 처리해보자. 

5. junit 을 사용하여 테스트해보자. (junit 관련 포스팅1, junit 관련 포스팅2)

 

1. RequestForm

httpUrlConnection 호출에 필요한 파라미터 정보 객체

빌더패턴을 적용하여 필수 파라미터(목적지주소, http method)는 생성자로,

그외 optional 파라미터는 setter 로 받도록 했다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
package com.jpp.web.util.HttpUtil;
 
import java.util.Map;
 
public class RequestForm {
   
   private final int tryCount;
   private final int connectionTimeOut;
   private final int readTimeOut;
   private final String httpMethod;
   private final String url;
   private final Map<String, Object> header;
   private final int httpStatus;
   private final String requestCharset;
   private final String responseCharset;
   
   public static class Builder {
      private String httpMethod; //required
      private String url;        //required
      private int tryCount = 1;  //optional
      private int connectionTimeOut = 1000;  //optional
      private int readTimeOut = 1000;     //optional
      private Map<String, Object> header; //optional
      private int httpStatus = 0;    //optional
      private String requestCharset = "UTF-8";
      private String responseCharset = "UTF-8";
      
      public Builder(String httpMethod, String url) {
         this.httpMethod = httpMethod;
         this.url = url;
      }
      
      public Builder setTryCount(int tryCount) {
         this.tryCount = tryCount<1?1:tryCount;
         return this;
      }
      
      public Builder setConnectionTimeOut(int connectionTimeOut) {
         this.connectionTimeOut = connectionTimeOut<100?1000:connectionTimeOut;
         return this;
      }
      
      public Builder setReadTimeOut(int readTimeOut) {
         this.readTimeOut =  readTimeOut<100?1000:readTimeOut;
         return this;
      }
      
      public Builder setHeader(Map<String, Object> header) {
         this.header = header;
         return this;
      }
      
      public Builder setExpectedHttpStatus(int httpStatus) {
         this.httpStatus = httpStatus;
         return this;
      }
      
      public Builder setRequestCharset(String requestCharset) {
         this.requestCharset = requestCharset;
         return this;
      }
      
      public Builder setResponseCharset(String responseCharset) {
         this.responseCharset = responseCharset;
         return this;
      }
      
      public RequestForm build() {
         return new RequestForm(this);
      }
   }
   
   public RequestForm(Builder builder) {
      this.httpMethod = builder.httpMethod;
      this.url = builder.url;
      this.tryCount = builder.tryCount;
      this.connectionTimeOut = builder.connectionTimeOut;
      this.readTimeOut = builder.readTimeOut;
      this.header = builder.header;
      this.httpStatus = builder.httpStatus;
      this.requestCharset = builder.requestCharset;
      this.responseCharset = builder.responseCharset;
   }
   
   public int getTryCount() {
      return tryCount;
   }
 
   public int getConnectionTimeOut() {
      return connectionTimeOut;
   }
 
   public int getReadTimeOut() {
      return readTimeOut;
   }
 
   public String getHttpMethod() {
      return httpMethod;
   }
 
   public String getUrl() {
      return url;
   }
   
   public Map<String, Object> getHeader(){
      return header;
   }
   
   public int getHttpStatus() {
      return httpStatus;
   }
 
   public String getRequestCharset() {
      return requestCharset;
   }
 
   public String getResponseCharset() {
      return responseCharset;
   }
   
}
 
cs

 

 

2. CustomHttpUrlConnection

java.net.HttpUrlConnection 객체를 사용하여 외부 서버 api를 호출하는 역할을 하는 클래스

1) 요청정보를 담고있는 RequestForm객체를 파라미터로 받도록 했다.

2) jsonFormat(body로 호출) 여부인 boolean 값을 파라미터로 받아 내부에서 분기처리하는게 아닌, 별도의 메소드로 각각 분리했다.

메소드를 작은 단위로 쪼개면 코드 중복이 많이 줄어들 것 같은데 나중에..

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
package com.jpp.web.util.HttpUtil;
 
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.SocketTimeoutException;
import java.net.URL;
import java.net.URLEncoder;
import java.util.Map;
 
import org.json.JSONException;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
 
import com.google.gson.Gson;
import com.jpp.web.comm.CustomException;
import com.jpp.web.constants.Constants;
import com.jpp.web.constants.ConstantsEnum;
 
public class CustomHttpUrlConnection {
   
   private static final Logger logger = LoggerFactory.getLogger(CustomHttpUrlConnection.class);
   private static final String UTF_8 = "UTF-8";
 
   private int responseCode = 0;
   private long startTime = 0;
 
   private RequestForm reqForm;
   private Map<String, Object> reqParams;
   
   public CustomHttpUrlConnection(RequestForm reqForm, Map<String, Object> reqParams){
      this.reqForm = reqForm;
      this.reqParams = reqParams;
   }
   
   public int getResponseCode() {
      return responseCode;
   }
   
   public long getStartTime() {
      return startTime;
   }
   
   public String requestApi() {
      
      this.startTime = System.currentTimeMillis();
      
      String httpMethod = reqForm.getHttpMethod();  
      String surl = reqForm.getUrl();               
      int tryCnt = reqForm.getTryCount();   
      int readTimeOut = reqForm.getReadTimeOut(); 
      int connectionTimeOut = reqForm.getConnectionTimeOut();
      Map<String, Object> header = reqForm.getHeader();
      int expectedHttpStatus = reqForm.getHttpStatus();
      String requestCharset = reqForm.getRequestCharset();
      String responseCharset = reqForm.getResponseCharset();
      
      String reqCharset = requestCharset==null||requestCharset.isEmpty()?UTF_8:requestCharset;
      String resCharset = responseCharset==null||responseCharset.isEmpty()?UTF_8:responseCharset;
             
      URL url = null;
      HttpURLConnection conn = null;
      BufferedReader br = null;
      JSONObject jobj = null;
      String postParams = "";
      String errMsg = "";
      String returnText = "";
 
      
      for(int i=0; i < tryCnt; i++){
          
         try {
             
            if(httpMethod.equalsIgnoreCase(Constants.POST) || httpMethod.equalsIgnoreCase(Constants.DELETE)){
               url = new URL(surl);
            } else if(httpMethod.equalsIgnoreCase(Constants.GET)){
               url = new URL(surl + ((reqParams!=null)?"?"+makeUrlEncodedParams(reqParams, reqCharset):""));
            }
            
            conn = (HttpURLConnection) url.openConnection();
            
            if(header != null){
                for(String key : header.keySet()) {
                    conn.setRequestProperty(key, header.get(key)!=null?header.get(key).toString():"");
                }
            }
             
            conn.setRequestMethod(httpMethod);
            conn.setConnectTimeout(connectionTimeOut);
            conn.setReadTimeout(readTimeOut);
            conn.setDoOutput(true);
            
            if(httpMethod.equalsIgnoreCase(Constants.POST) || httpMethod.equalsIgnoreCase(Constants.DELETE)){
               if(reqParams != null){
                  postParams = makeUrlEncodedParams(reqParams, reqCharset);
                  conn.getOutputStream().write(postParams.getBytes(UTF_8));
                  conn.getOutputStream().flush();
               }
            }
            
            this.responseCode = conn.getResponseCode();
            
            if(expectedHttpStatus > 0){
               if(expectedHttpStatus!=conn.getResponseCode()){
                  throw new CustomException("successCode : {" + expectedHttpStatus + "}" + " , responseCode : {" + this.responseCode + "}", ConstantsEnum.API_RESULT.E_NETWORK.getCode());
               }
            }
            
            br = new BufferedReader(new InputStreamReader(conn.getInputStream(),resCharset));
            
            StringBuffer sb = null;
            sb = new StringBuffer();
            
            String jsonData = "";
            while((jsonData = br.readLine()) != null){
               sb.append(jsonData);
            }
            returnText = sb.toString();
            
            try{
               jobj = new JSONObject(returnText);
            } catch (JSONException e){
               throw new CustomException();
            }
            
            break;
            
         } catch (SocketTimeoutException se){
            logger.error("connection fail : " + se);
             errMsg = se.getMessage();
         } catch (CustomException e){
            logger.error("response fail : " + e);
             errMsg = e.getMessage();
         } catch (Exception e){
            throw new CustomException(e.getMessage().toString(), ConstantsEnum.API_RESULT.E_NETWORK.getCode());
         } finally {
            try {
               if (br != null) br.close();
            } catch(Exception e){
               logger.warn("finally..br.close()", e);
            }
            br = null;
            try {
               if(conn!=null) {
                  conn.disconnect();
               }
            } catch(Exception e){
               logger.warn("finally..conn.disconnect()", e);
            }
            conn = null;
         }
      }
      
      if(jobj!=null){
         return jobj.toString();
      } else {
         throw new CustomException(errMsg, ConstantsEnum.API_RESULT.E_NETWORK.getCode());
      }
   }
   
   
   public String requestApiWithJsonForm() {
      
      this.startTime = System.currentTimeMillis();
      
      String httpMethod = reqForm.getHttpMethod();
      int tryCnt = reqForm.getTryCount();
      int readTimeOut = reqForm.getReadTimeOut();
      int connectionTimeOut = reqForm.getConnectionTimeOut();
      String surl = reqForm.getUrl();
      Map<String, Object> header = reqForm.getHeader();
      int expectedHttpStatus = reqForm.getHttpStatus();
      String responseCharset = reqForm.getResponseCharset();
      
      String resCharset = responseCharset==null||responseCharset.isEmpty()?UTF_8:responseCharset;
      
      URL url = null;
      HttpURLConnection conn = null;
      BufferedReader br = null;
      JSONObject jobj = null;
      String postParams = "";
      String errMsg = "";
      String returnText = "";
       
      for(int i=0; i < (tryCnt<1?1:tryCnt); i++){
          
         try {
             
            url = new URL(surl);
            
            conn = (HttpURLConnection) url.openConnection();
            
            if(header != null){
                for(String key : header.keySet()) {
                    conn.setRequestProperty(key, header.get(key)!=null?header.get(key).toString():"");
                }
            }
             
            conn.setRequestProperty("Content-Type""application/json");
            conn.setRequestMethod(httpMethod);
            conn.setConnectTimeout(connectionTimeOut);
            conn.setReadTimeout(readTimeOut);
            conn.setDoOutput(true);
            
            if(reqParams != null){
                postParams = makeJsonParams(reqParams);
                conn.getOutputStream().write(postParams.getBytes(UTF_8));
                conn.getOutputStream().flush();
            }
            
            this.responseCode = conn.getResponseCode();
            
            if(expectedHttpStatus != 0){
               if(expectedHttpStatus!=conn.getResponseCode()){
                  throw new CustomException("successCode : {" + expectedHttpStatus + "}" + " , responseCode : {" + this.responseCode + "}", ConstantsEnum.API_RESULT.E_NETWORK.getCode());
               }
            }
            
            br = new BufferedReader(new InputStreamReader(conn.getInputStream(), resCharset));
            
            StringBuffer sb = null;
            sb = new StringBuffer();
            
            String jsonData = "";
            while((jsonData = br.readLine()) != null){
               sb.append(jsonData);
            }
            returnText = sb.toString();
            
            try{
               jobj = new JSONObject(returnText);
            } catch (JSONException e){
               throw new CustomException();
            }
            
            break;
            
         } catch (SocketTimeoutException se){
            logger.error("connection fail : " + se);
             errMsg = se.getMessage();
         } catch (CustomException e){
            logger.error("response fail : " + e);
             errMsg = e.getMessage();
         } catch (Exception e){
            throw new CustomException(e.getMessage().toString(), ConstantsEnum.API_RESULT.E_NETWORK.getCode());
         } finally {
            try {
               if (br != null) br.close();
            } catch(Exception e){
               logger.warn("finally..br.close()", e);
            }
            br = null;
            try {
               if(conn!=null) {
                  conn.disconnect();
               }
            } catch(Exception e){
               logger.warn("finally..conn.disconnect()", e);
            }
            conn = null;
         }
      }
      
      if(jobj!=null){
         return jobj.toString();
      } else {
         throw new CustomException(errMsg, ConstantsEnum.API_RESULT.E_NETWORK.getCode());
      }
   }
    
   
   private String makeUrlEncodedParams(Map<String, Object> params, String charset) throws Exception{
      String param = "";
      StringBuffer sb = new StringBuffer();
      
      if(params != null){
         for ( String key : params.keySet() ){
             try {
                sb.append(key).append("=").append((params.get(key)==null?"":URLEncoder.encode(params.get(key).toString(), charset)).toString().trim()).append("&");
             } catch (UnsupportedEncodingException e) {
                logger.error("ex while encoding : {}", e.getMessage());
                throw e;
             }
         }
         param = sb.toString().substring(0, sb.toString().length()-1);
      }
      return param;
   }
  
   
   private String makeJsonParams(Map<String, Object> params){
      String json = "";
      if(params != null){
          json = new Gson().toJson(params);
      }
      return json;
   }
   
}
 
 
cs

 

 

3. HttpUtil

클라이언트에서 실제로 사용될 클래스

1) 프록시(proxy) 패턴을 적용하여 CustomHttpUrlConnection객체의 requestApi(), requestApiWithJsonForm() 메소드를 호출하기 전과 후에 logging 메소드가 호출되도록 했다

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
package com.jpp.web.util.HttpUtil;
 
import java.util.Map;
 
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
 
public class HttpUtil {
    
   private static final Logger logger = LoggerFactory.getLogger(HttpUtil.class);
   
   private RequestForm reqForm;
   private Map<String, Object> reqParams;
   private CustomHttpUrlConnection httpUrlConnection;
   
   public HttpUtil(RequestForm reqForm, Map<String, Object> reqParams) {
      this.reqForm = reqForm;
      this.reqParams = reqParams;
      httpUrlConnection = new CustomHttpUrlConnection(reqForm, reqParams);
   }
   
   public String requestApi() {
      printBeforeLog();
      String resParams = httpUrlConnection.requestApi();
      printAfterLog(resParams);
      return resParams;
   }
   
   public String requestApiWithJsonForm() {
      printBeforeLog();
      String resParams = httpUrlConnection.requestApiWithJsonForm();
      printAfterLog(resParams);
      return resParams;
   }
   
   private void printBeforeLog() {
      logger.info("HTTPCONNECTION.REQUEST|URL:{}|IN_PARAMS:{}"
            , reqForm.getUrl(), reqParams);
   }
   
   private void printAfterLog(String resParams) {
      logger.info("HTTPCONNECTION.RESPONSE|URL:{}|TIME:{}|STATUS_CODE:{}|IN_PARAMS:{}|OUT_PARAMS:{}"
            , reqForm.getUrl()
            , System.currentTimeMillis() - httpUrlConnection.getStartTime()
            , httpUrlConnection.getResponseCode()<1?"":httpUrlConnection.getResponseCode()
            , reqParams
            , resParams);
   }
   
}
 
cs

 

4. test

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
package com.jpp.web.util.HttpUtil;
 
import java.util.HashMap;
import java.util.Map;
 
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
 
@RunWith(SpringRunner.class)
@SpringBootTest
public class HttpUtilTest {
   
   private static final Logger logger = LoggerFactory.getLogger(HttpUtil.class);
   
   @Test
   public void test() {
      RequestForm reqForm = new RequestForm.Builder("GET""127.0.0.1:8082/mobile/device")
            .setConnectionTimeOut(1000)
            .setReadTimeOut(1000)
            .setTryCount(3)
            .build();
      
      Map<String, Object> reqParams = new HashMap<String, Object>();
      reqParams.put("deviceType""1");
      reqParams.put("osVersion""10");
      
      String apiResult = new HttpUtil(reqForm, reqParams).requestApiWithJsonForm();
      logger.info(apiResult);
   }
 
   @Test
   public void test2() {
      RequestForm reqForm = new RequestForm.Builder("GET""127.0.0.1:8082/mobile/version")
            .setConnectionTimeOut(1000)
            .setReadTimeOut(1000)
            .setTryCount(3)
            .build();
      
      Map<String, Object> reqParams = new HashMap<String, Object>();
      
      String rs = new HttpUtil(reqForm, reqParams).requestApi();
      logger.info(rs);
   }
}
 
cs

 

소스리뷰를 받고싶다..

 

모든 코드는 제 github(https://github.com/develo-pyo/boot-mybatis)에 올려놓았습니다.

반응형

 

예제를 위한 클래스

1. ExampleSuperClass : 부모클래스

1
2
3
4
5
package Java.reflection;
 
public class ExampleSuperClass {
}
 
cs

 

2. ExampleClass : 예제 클래스

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package Java.reflection;
 
public class ExampleClass extends ExampleSuperClass {
   
   private static final String CONSTANT_FIELD = "constant_field";
   public int i = 1;
   public String publicField = "publicStrField";
   private String privateField = "privateStrField";
   
   public ExampleClass() { 
   }
   
   public ExampleClass(Object a) throws NullPointerException {
   }
   
   private int privateMethod(Object a, int b) throws NullPointerException {
      if(a == null) {
         throw new NullPointerException();
      }
      return b;
   }
   
   public String addStrInt(String a, int b) {
      return a+b;
   }
}
cs

 

3. ExampleChildClass : 자식 클래스

1
2
3
4
package Java.reflection;
 
public class ExampleChildClass extends ExampleClass {       
}
cs

 

 

Java Reflection 의 사용 예

1. isInstance 사용법 

instanceof 와 같은 역할을 수행

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package Java.reflection;
 
//https://gyrfalcon.tistory.com/entry/Java-Reflection
 
//instance of
public class Reflection1_instanceof {
   public static void main(String[] args) {
      
      //classA.isInstance(B);
      //B가 A의 인스턴스인지를 확인
      Class clazz = ExampleClass.class;
      
      boolean rs1 = clazz.isInstance(new ExampleSuperClass());
      System.out.println(rs1);
      boolean rs2 = clazz.isInstance(new ExampleClass());
      System.out.println(rs2);
      boolean rs3 = clazz.isInstance(new ExampleChildClass());
      System.out.println(rs3);
 
   }
}
 
cs

[결과]

false
true
true

 

2. 클래스에서 메소드 정보 가져오기

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package Java.reflection;
 
import java.lang.reflect.Method;
 
//Method 정보 가져오기
public class Reflection2_FindMethod {
 
   public static void main(String[] args) throws Exception {
      
      //1. class 가져오기
      Class clazz = Class.forName("Java.reflection.ExampleClass");
      //1-1. class 가져오기(위와 같다)
      Class clazz1 = ExampleClass.class;
      
      //2. class 에서 method 가져오기
      Method methodArr[] = clazz.getDeclaredMethods();
      
      for(Method method : methodArr) {
         
         //1) 메소드명 가져오기
         System.out.println("name = " + method.getName());
         
         //2) 선언된 클래스명 가져오기
         System.out.println("class = " + method.getDeclaringClass());
         
         //3) Parameter Type 가져오기
         Class paramTypes[] = method.getParameterTypes();
         for(Class p : paramTypes) {
            System.out.println("param type : " + p);
         }
      
         //4) exception Type 가져오기
         Class exceptionTypes[] = method.getExceptionTypes();
         for(Class e : exceptionTypes) {
            System.out.println("exception type : " + e);
         }
         
         //5) return Type 가져오기
         System.out.println("return type : "+method.getReturnType());
         
         System.out.println();
      }
   }
}
 
cs

[결과]

name = privateMethod
class = class Java.reflection.ExampleClass
param type : class java.lang.Object
param type : int
exception type : class java.lang.NullPointerException
return type : int

name = addStrInt
class = class Java.reflection.ExampleClass
param type : class java.lang.String
param type : int
return type : class java.lang.String

 

3. 생성자(Constructor) 정보 찾기

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package Java.reflection;
 
import java.lang.reflect.Constructor;
 
//Constructor 정보 가져오기
public class Reflection3_FindConstructor {
     
   public static void main(String[] args) throws Exception {
      
      //1. class 가져오기
      Class clazz = ExampleClass.class;
      
      //2. class 에서 Constructor 가져오기
      Constructor constructors[] = clazz.getDeclaredConstructors();
      
      for(Constructor c : constructors) {
         
         //1) 생성자명 가져오기
         System.out.println("name : " + c.getName());
         
         //2) 선언된 클래스명 가져오기
         System.out.println("class : " + c.getDeclaringClass());
         
         //3) parameter type 가져오기
         Class paramTypes[] = c.getParameterTypes();
         for(Class p : paramTypes) {
            System.out.println("param type : " + p);
         }
      
         //4) exception Type 가져오기
         Class exceptionTypes[] = c.getExceptionTypes();
         for(Class e : exceptionTypes) {
            System.out.println("exception type : " + e);
         }
         
         //5) 생성자에 return type은 존재하지 않으므로 getReturnType() 메소드는 없음 
         System.out.println();
      }
   }
}
 
cs

[결과]

name : Java.reflection.ExampleClass
class : class Java.reflection.ExampleClass

name : Java.reflection.ExampleClass
class : class Java.reflection.ExampleClass
param type : class java.lang.Object
exception type : class java.lang.NullPointerException

 

4. 필드 정보 찾기

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package Java.reflection;
 
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
 
//Field 정보 가져오기
public class Reflection4_FindFields {
   
   public static void main(String[] args) throws Exception {
      
      //1. class 가져오기
      Class clazz = ExampleClass.class;
      
      //2. class 에서 Field 가져오기
      //Field fields[] = clazz.getFields();        //public modifier(접근제한자) field 만 가져올 수 있다
      Field fields[] = clazz.getDeclaredFields();  //private modifier field 도 가져올 수 있다
      
      for(Field f : fields) {
         
         //1) 필드명 가져오기
         System.out.println("name : " + f.getName());
         
         //2) 필드가 선언된 클래스명 가져오기
         System.out.println("class : " + f.getDeclaringClass());
         
         //3) 필드 타입 가져오기
         System.out.println("type : " + f.getType());
         
         //4) 필드의 접근제한자 가져오기 (int 형이며 Modifier.toString(mod) 로 string으로 바꿔줄 수 있음)
         int modifiers = f.getModifiers();
         System.out.println("modifiers int : " + modifiers);
         System.out.println("modifiers toString : " + Modifier.toString(modifiers));
         System.out.println("isPublic? : " + (modifiers == Modifier.PUBLIC));
         
         System.out.println();
      }
   }
}
 
cs

[결과]

name : CONSTANT_FIELD
class : class Java.reflection.ExampleClass
type : class java.lang.String
modifiers int : 26
modifiers toString : private static final
isPublic? : false

name : i
class : class Java.reflection.ExampleClass
type : int
modifiers int : 1
modifiers toString : public
isPublic? : true

생략..

 

5. 메소드 실행시키기 (invoke)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package Java.reflection;
 
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
 
//https://kaspyx.tistory.com/80
 
//5. 메소드 실행시키기 invoke
public class Reflection5_invoke_1 {
   
   public Reflection5_invoke_1() {}
   
   public Reflection5_invoke_1(String a) {}
   
   public Reflection5_invoke_1(String a, Object b) {}
   
   public String addStrInt(String a, int b) {
      return a+b;
   }
   
   @SuppressWarnings({ "unchecked""rawtypes" })
   public static void main(String[] args) throws Exception {
      
      //1. class 가져오기
      Class clazz = Reflection5_invoke_1.class;
      
      Class paramTypes[] = new Class[2];
      paramTypes[0= String.class;
      paramTypes[1= Integer.TYPE;
      Method method = clazz.getMethod("addStrInt", paramTypes);   //* private modifier method 는 찾지 못한다
 
      Reflection5_invoke_1 methodObj = new Reflection5_invoke_1();
      Object argumentList[] = new Object[2]; 
      argumentList[0= "Str ";
      argumentList[1= new Integer(10);
      
      //java 문법은 보통 아래와 같이 S V O 
      //String rs = addStrInt("a", 10);
      //하지만, reflection 은 V.invoke(S, O)
      Object rs = method.invoke(methodObj, argumentList);
      
      System.out.println(rs);
      
   }
   
}
 
cs

[결과]

Str 10

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package Java.reflection;
 
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
 
//https://kaspyx.tistory.com/80
 
//5. 메소드 실행시키기 invoke
public class Reflection5_invoke_2 {
   
   public Reflection5_invoke_2() {}
   
   public Reflection5_invoke_2(String a) {}
   
   public Reflection5_invoke_2(String a, Object b) {}
   
   public String addStrInt(String a, int b) {
      return a+b;
   }
   
   @SuppressWarnings({ "unchecked""rawtypes" })
   public static void main(String[] args) throws Exception {
      
      Method m = ExampleChildClass.class.getMethod("addStrInt"new Class[] {String.class, Integer.TYPE});
      String result = (String) m.invoke(new ExampleClass(), new Object[]{"Str "new Integer(10)});
      System.out.println(result);
      
      //위와 같다
      Method m3 = ExampleChildClass.class.getMethod("addStrInt"new Class[] {String.class, Integer.TYPE});
      String result3 = (String) m3.invoke(new ExampleClass(), new Object[]{"Str "new Integer(10)});
      System.out.println(result3);
      
      try {
         Method m2 = ExampleClass.class.getMethod("addStrInt"new Class[] {String.class, Integer.TYPE});
         String result2 = (String) m2.invoke(new ExampleSuperClass(), new Object[]{"Str "new Integer(10)});
         System.out.println(result2);
      } catch (IllegalArgumentException ie) {
         System.out.println(ie.getMessage());
      }
   }
}
 
cs

[결과]

Str 10
Str 10
object is not an instance of declaring class

 

6. 인스턴스 생성하기 (newInstance)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
package Java.reflection;
 
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
 
//https://kaspyx.tistory.com/80
 
//5. 메소드 실행시키기 invoke
public class Reflection6_newInstance {
   
   public Reflection6_newInstance() {}
   
   public Reflection6_newInstance(String a) {}
   
   public Reflection6_newInstance(String a, Object b) {}
   
   public String addStrInt(String a, int b) {
      return a+b;
   }
   
   @SuppressWarnings({ "unchecked""rawtypes" })
   public static void main(String[] args) throws Exception {
      
      Class clazz = Reflection6_newInstance.class;
      
      Class arguTypes[] = new Class[2];
      arguTypes[0= String.class;
      arguTypes[1= Integer.TYPE;
 
      Object params[] = new Object[2];
      params[0= "Str ";
      params[1= 10;
      
      Constructor myConstructor1 = clazz.getConstructor();     
      Object myObj = myConstructor1.newInstance();             
      Method m1 = clazz.getMethod("addStrInt", arguTypes); 
      String result1 = (String) m1.invoke(myObj, params);   
      System.out.println(result1);
      
      Constructor myConstructor2 = clazz.getConstructor(String.class);
      Object myObj2 = myConstructor2.newInstance("");
      Method m2 = clazz.getMethod("addStrInt", arguTypes);
      String result2 = (String) m2.invoke(myObj2, params);
      System.out.println(result2);
      
      Constructor myConstructor3 = clazz.getConstructor(String.class, Object.class);
      Object myObj3 = myConstructor3.newInstance(""new Object());
      Method m3 = clazz.getMethod("addStrInt", arguTypes);
      String result3 = (String) m3.invoke(myObj3, params);
      System.out.println(result3);
      
      Constructor myConstructor4 = clazz.getConstructor(new Class[]{String.class, Object.class});
      Object myObj4 = myConstructor4.newInstance(""new Object());
      Method m4 = clazz.getMethod("addStrInt"new Class[] {String.class, Integer.TYPE});
      String result4 = (String) m4.invoke(myObj4, new Object[] {"String " , 11});
      System.out.println(result4);
      
   }
}
 
cs

[결과]

Str 10
Str 10
Str 10
String 11

 

위의 모든 샘플 예제들은 github(https://github.com/develo-pyo)에 올려놓았습니다.

 

참고:

https://www.geeksforgeeks.org/reflection-in-java/

https://gyrfalcon.tistory.com/entry/Java-Reflection

 

반응형

팩토리 패턴

팩토리라는 클래스에 객체 생성을 위임(캡슐화)하여 팩토리 클래스가 객체를 생성하도록 하는 방식.

어떤 클래스의 인스턴스를 생성할지 서브클래스에서 결정하도록 한다는게 팩토리 패턴의 핵심.

분류에 딱히 큰 의미는 없는 듯 하나, 팩토리 메소드 패턴과 추상 팩토리 메소드 패턴으로 나누고 있다.

"구상클래스에 의존하지 않게, 추상화 된 것에 의존하도록 개발" 을 따르는 패턴

 

 

1. 팩토리 메소드 패턴

: 객체 생성을 담당하는 팩토리 메소드 작성하여 객체 생성을 캡슐화

: 객체를 생성하기 위한 인터페이스를 정의하는데, 어떤 클래스의 인스턴스를 만들지는 서브클래스에서 결정한다.

 

[Mouse interface]

1
2
public interface Mouse {
}
cs

[LGMouse.java]

Mouse interface 구현

1
2
3
4
5
public class LGMouse implements Mouse {
    public LGMouse() {
        System.out.println("LG 마우스 생성");
    }
}
cs

[SamsungMouse.java]

Mouse interface 구현

1
2
3
4
5
public class SamsungMouse implements Mouse {
    public SamsungMouse() {
        System.out.println("Samsung 마우스 생성");
    }
}
cs

 

[Factory.java]

객체 생성을 맡아서 처리 하는 팩토리클래스

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Factory {
    public static Mouse createMouse(String type) {
        Mouse mouse = null;
        switch(type) {
            case "LG":
                mouse = new LGMouse();
                break;
            case "SAMSUNG":
                mouse = new SamsungMouse();
                break;
        }
        return mouse;
    }
}
cs

 

[Client]

1
2
3
4
5
public class Client {
    public static void main(String[] args) {
        Factory.createMouse("LG");
    }
}
cs

[결과]

LG 마우스 생성

 

 

2. 추상 팩토리 패턴

: 연관된 서브 클래스를 그룹화할 수 있고 그룹을 자유롭게 교체할 수 있는 패턴

: 인터페이스를 이용하여 서로 연관된, 또는 의존하는 객체를 구상 클래스를 지정하지 않고 생성할 수 있다

: 팩토리 메소드 패턴이 좀 더 캡슐화 되어있는 형태

 

[Mouse interface]

1
2
public interface Mouse {
}
cs

[LGMouse.java]

Mouse interface 구현

1
2
3
4
5
public class LGMouse implements Mouse {
    public LGMouse() {
        System.out.println("LG 마우스 생성");
    }
}
cs

[SamsungMouse.java]

Mouse interface 구현

1
2
3
4
5
public class SamsungMouse implements Mouse {
    public SamsungMouse() {
        System.out.println("SAMSUNG 마우스 생성");
    }
}
cs

 

[Keyboard interface]

1
2
public interface Keyboard {
}
cs

[LGKeyboard.java]

Keyboard interface 구현

1
2
3
4
5
public class LGKeyboard implements Keyboard {
    public LGKeyboard() {
        System.out.println("LG 키보드 생성");
    }
}
cs

[SamsungKeyboard.java]

Keyboard interface 구현

1
2
3
4
5
public class SamsungKeyboard implements Keyboard {
    public SamsungKeyboard() {
        System.out.println("SAMSUNG 키보드 생성");
    }
}
cs

 

[Computer interface]

1
2
3
4
public interface Computer {
    public Keyboard createKeyboard();
    public Mouse createMouse();
}
cs

[ComputerFactory.java]

Computer interface 구현

1
2
3
4
5
6
public class ComputerFactory {
     public void createComputer(Computer computer){
        computer.createKeyboard();
        computer.createMouse();
    }
}
cs

 

[Client.java]

1
2
3
4
5
6
7
public class Client {
    public static void main(String[] args) {
        ComputerFactory cf = new ComputerFactory();
        cf.createComputer(new SamsungComputer());
        cf.createComputer(new LGComputer());
    }
}
cs

[결과]

SAMSUNG 컴퓨터 생성
SAMSUNG 키보드 생성
SAMSUNG 마우스 생성
LG 컴퓨터 생성
LG 키보드 생성
LG 마우스 생성

 

참고 :

아래의 포스팅을 많이 참고했습니다. 훌륭한 예제를 보시려면 아래를 참고해주세요..

https://victorydntmd.tistory.com/300

 

 

 

반응형

+ Recent posts