👉 기본 환경

- Language: Java

- DB: H2 Database

- IDE: IntelliJ

 

 

⌨️ 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
@Entity
@SequenceGenerator(
       name = "MEMBER_SEQ_GENERATOR",
       sequenceName = "MEMBER_SEQ",
       allocationSize = 3
)
public class Member {
 
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "MEMBER_SEQ_GENERATOR")
    private Long id;
}
 
 
 

 

 

🖨️오류

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
Exception in thread "main" javax.persistence.PersistenceException: [PersistenceUnit: hello] Unable to build Hibernate SessionFactory
    at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.persistenceException(EntityManagerFactoryBuilderImpl.java:1326)
    at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.build(EntityManagerFactoryBuilderImpl.java:1252)
    at org.hibernate.jpa.HibernatePersistenceProvider.createEntityManagerFactory(HibernatePersistenceProvider.java:56)
    at javax.persistence.Persistence.createEntityManagerFactory(Persistence.java:79)
    at javax.persistence.Persistence.createEntityManagerFactory(Persistence.java:54)
    at jpa_basic.Main.main(Main.java:13)
Caused by:      [entity-name=jpa_basic.Member]
    at org.hibernate.id.factory.internal.DefaultIdentifierGeneratorFactory.createIdentifierGenerator(DefaultIdentifierGeneratorFactory.java:124)
    at org.hibernate.mapping.SimpleValue.createIdentifierGenerator(SimpleValue.java:344)
    at org.hibernate.internal.SessionFactoryImpl.lambda$new$1(SessionFactoryImpl.java:286)
    at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:183)
    at java.base/java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:177)
    at java.base/java.util.HashMap$ValueSpliterator.forEachRemaining(HashMap.java:1692)
    at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:484)
    at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:474)
    at java.base/java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:150)
    at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:173)
    at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
    at java.base/java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:497)
    at org.hibernate.internal.SessionFactoryImpl.<init>(SessionFactoryImpl.java:285)
    at org.hibernate.boot.internal.SessionFactoryBuilderImpl.build(SessionFactoryBuilderImpl.java:468)
    at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.build(EntityManagerFactoryBuilderImpl.java:1249)
    ... 4 more
Caused by: org.hibernate.MappingException: The increment size of the [MEMBER_SEQ] sequence is set to [3] in the entity mapping while the associated database sequence increment size is [5].
    at org.hibernate.id.enhanced.SequenceStyleGenerator.configure(SequenceStyleGenerator.java:261)
    at org.hibernate.id.factory.internal.DefaultIdentifierGeneratorFactory.createIdentifierGenerator(DefaultIdentifierGeneratorFactory.java:118)
 
 
 

 

 

📡 원인

SequenceGenerator에서 allocationSize를 5로 설정한 후, 3으로 수정

 

엔티티 매핑에서 MEMBER_SEQ 시퀀스의 증가 크기를 3으로 설정했지만,

실제 데이터베이스의 MEMBER_SEQ 시퀀스는 증가 크기가 5로 설정되어 있음

 

JPA DDL 생성 기능에서 value를 create로 설정하더라도 table처럼 Sequence 객체도 drop 후 새롭게 생성되지 않음

 

 

📰 해결 방법

1
2
DROP SEQUENCE IF EXISTS sequence_name;
 
 
 

DB에서 직접 drop 후 애플리케이션을 새롭게 실행해야 함

 

이 글은 김영한의 [자바 ORM 표준 JPA 프로그래밍 - 기본편] 수강하며 정리한 글입니다.

 

👉 기본 환경

- Language: Java

- DB: H2 Database

- IDE: IntelliJ

 

 

1. 기본키 직접 할당

1
2
3
4
5
6
7
@Entity
public class Member {
 
    @Id // PK 지정 필수, PK 직접 할당
    private Long id;
}
 
 

 

2. 기본키 자동 생성: @GeneratedValue

    2.1. AUTO

    - Default

    - DB 방언에 맞춰 Value 자동 생성

 

    2.2. IDENTITY

1
2
3
4
5
6
7
8
@Entity
public class Member {
 
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
}
 
 
 

    - PK 생성을 DB에 위임(예: MySQL: AUTO_INCREMENT)

    - PK에 null값을 넘기면 DB에서 자동으로 생성해줌

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
public class Main {
    public static void main(String[] args) {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
 
        EntityManager em = emf.createEntityManager();
 
        EntityTransaction tx = em.getTransaction();
        tx.begin();
 
        try {
            Member member = new Member();
            System.out.println("==========");
            em.persist(member);
            System.out.println("==========");
 
            tx.commit();
        } catch (Exception e) {
            tx.rollback();
        } finally {
            em.close();
        }
 
        emf.close();
    }    
}
 
 
 

    - JPA는 tx.commit() 시점에 INSERT SQL 실행

1
2
3
4
5
6
7
8
9
10
11
==========
Hibernate: 
    /* insert jpa_basic.Member
        */ insert 
        into
            Member
            (id) 
        values
            (null)
==========
 
 

    - IDENTITY 전략은 em.persist() 시점에 즉시 INSERT SQL 실행

    - 영속성 컨텍스트에 의해 관리되기 위해서 (Key, Value)로 (PK, 객체) 값이 필요한데, 영속성 컨텍스트에 관리되는 시점인 persist 시, PK값을 알 수 없으므로 IDENTITY 전략만 예외적으로 persist에서 DB insert query 발생

 

    2.3. SEQUENCE

1
2
3
4
5
6
7
8
9
10
11
12
13
@Entity
@SequenceGenerator(
       name = "MEMBER_SEQ_GENERATOR"// Java에서의 SEQ 이름
       sequenceName = "MEMBER_SEQ"// DB에서의 SEQ 이름
       allocationSize = 3
)
public class Member {
 
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "MEMBER_SEQ_GENERATOR")
    private Long id;
}
 
 
 

    - Sequence 객체를 생성해서 생성한 Sequence 객체에서 값을 가져와 PK값에 세팅

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
public class Main {
    public static void main(String[] args) {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
 
        EntityManager em = emf.createEntityManager();
 
        EntityTransaction tx = em.getTransaction();
        tx.begin();
 
        try {
            Member member1 = new Member();
            System.out.println("=====11=====");
            em.persist(member1);
            System.out.println("=====22=====");
 
            Member member2 = new Member();
            Member member3 = new Member();
            Member member4 = new Member();
            Member member5 = new Member();
            Member member6 = new Member();
            Member member7 = new Member();
            Member member8 = new Member();
 
            em.persist(member2);
            em.persist(member3);
            em.persist(member4);
 
            System.out.println("=====33=====");
            em.persist(member5);
            em.persist(member6);
            em.persist(member7);
 
            System.out.println("=====44=====");
            em.persist(member8);
 
            System.out.println("=====55=====");
 
            tx.commit();
        } catch (Exception e) {
            tx.rollback();
        } finally {
            em.close();
        }
 
        emf.close();
    }    
}
 
 
 

    - em.persist() 실행 시, 영속성 컨텍스트에 의해 관리되기 위해서 IDENTITY 전략과 마찬가지로 PK 값을 필요로 함

    - 그러나, SEQUENCE 객체도 DB에 의해 관리되므로 PK를 알 수 없음

    - 그러므로, em.persist(); 시 SEQ 객체로부터 PK값을 얻어옴

  ⭐ Insert 시점은 commit 시점으로 query buffer 기능 사용 가능

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
=====11=====
Hibernate: 
    call next value for MEMBER_SEQ
Hibernate: 
    call next value for MEMBER_SEQ
=====22=====
=====33=====
Hibernate: 
    call next value for MEMBER_SEQ
=====44=====
Hibernate: 
    call next value for MEMBER_SEQ
=====55=====
Hibernate: 
    /* insert jpa_basic.Member
        */ insert 
        into
            Member
            (id) 
        values
            (?)
...
 
 
 

    - 처음에 call next value가 2번 호출되는 이유

        - SEQ 객체 초기 값이 -2로 call next value를 호출하여, 1부터 시작될 수 있도록 setting

        - allocationSize에 따라 메모리에 미리 seq값을 저장해두기 위해 호출

        - 그 이후서부터는 미리 저장해둔 seq를 다 사용하면 call next value

 

    2.4. TABLE

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Entity
@TableGenerator(
        name = "MEMBER_SEQ_GENERATOR",
        table = "MY_SEQUENCES",
        pkColumnValue = "MEMBER_SEQ",
        allocationSize = 1
)
public class Member {
 
    @Id
    @GeneratedValue(strategy = GenerationType.TABLE, generator = "MEMBER_SEQ_GENERATOR")
    private Long id;
}
 
 
 

    - 키 생성 전용 테이블을 하나 만들어서 데이터베이스 시퀀스를 사용하는 것처럼 하는 전략

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
public class Main {
    public static void main(String[] args) {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
 
        EntityManager em = emf.createEntityManager();
 
        EntityTransaction tx = em.getTransaction();
        tx.begin();
 
        try {
            Member member = new Member();
            System.out.println("==========");
            em.persist(member);
            System.out.println("==========");
 
            tx.commit();
        } catch (Exception e) {
            tx.rollback();
        } finally {
            em.close();
        }
 
        emf.close();
    }    
}
 
 
 

    - 테이블을 직접 생성하므로 모든 데이터베이스에 적용 가능하나, 최적화 등 성능 문제가 발생할 수 있음

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
Hibernate: 
    
    create table Member (
       id bigint not null,
        primary key (id)
    )
Hibernate: 
    
    create table MY_SEQUENCES (
       sequence_name varchar(255not null,
        next_val bigint,
        primary key (sequence_name)
    )
Hibernate: 
 
    insert into MY_SEQUENCES(sequence_name, next_val) values ('MEMBER_SEQ',0)
 
=====11=====
Hibernate: 
    select
        tbl.next_val 
    from
        MY_SEQUENCES tbl 
    where
        tbl.sequence_name=? for update
            
Hibernate: 
    update
        MY_SEQUENCES 
    set
        next_val=?  
    where
        next_val=
        and sequence_name=?
 
Hibernate: 
    select
        tbl.next_val 
    from
        MY_SEQUENCES tbl 
    where
        tbl.sequence_name=? for update
            
Hibernate: 
    update
        MY_SEQUENCES 
    set
        next_val=?  
    where
        next_val=
        and sequence_name=?
=====22=====
Hibernate: 
    /* insert jpa_basic.Member
        */ insert 
        into
            Member
            (id) 
        values
            (?)
 
 
 

 

 

 

📚 참고 자료

 

엔티티 매핑

- 객체와 테이블 매핑 : @Entity, @Table - 필드와 컬럼 매핑 : @Column - 기본키 매핑 : @Id, @GeneratedValue 객체와 테이블 매핑, 필드와 컬럼 매핑, 기본키 매핑의 방법을 간단하게 알아보고 각 애노테이션의

programmingrecoding.tistory.com

 

 

이 글은 김영한의 [자바 ORM 표준 JPA 프로그래밍 - 기본편] 수강하며 정리한 글입니다.

 

👉 기본 환경

- Language: Java

- IDE: IntelliJ

 

JPA에서는 DDL을 애플리케이션 실행 시점에 자동 생성해주는 기능을 보유

1
2
<property name="hibernate.hbm2ddl.auto" value="" />
 
 
 

 

데이터베이스 방언을 활용해서 데이터베이스에 맞는 적절한 DDL 생성

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect"/>
<property name="hibernate.hbm2ddl.auto" value="create" />
<!--
    create table Member (
       id bigint not null,
        age integer not null,
        name varchar(255),
        primary key (id)
    )
-->
 
<property name="hibernate.dialect" value="org.hibernate.dialect.Oracle12cDialect"/>
<property name="hibernate.hbm2ddl.auto" value="create" />
<!--
    create table Member (
       id number(19,0) not null,
        age number(10,0) not null,
        name varchar2(255 char),
        primary key (id)
    )
-->
 
 
 

 

⭐ 이렇게 생성된 DDL은 개발 장비에서만 사용

생성된 DDL은 운영서버에서는 사용하지 않거나, 적절히 다듬은 후 사용

 

hibernate.hbm2ddl.auto 옵션

- create: 기존테이블 삭제 후 다시 생성 (DROP + CREATE)

1
2
<property name="hibernate.hbm2ddl.auto" value="create" />
 
 
 

 

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

1
2
<property name="hibernate.hbm2ddl.auto" value="create-drop" />
 
 
 

 

- update: 변경분만 반영

1
2
<property name="hibernate.hbm2ddl.auto" value="update" />
 
 
 

Entity에서 필드 추가 시, alter DDL이 자동 생성되어 column이 생성되지만 필드를 삭제한다고 column이 삭제되진 않음

 

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

1
2
<property name="hibernate.hbm2ddl.auto" value="validate" />
 
 
 

* 테이블에 매칭되지 않는 필드 존재 시, 오류 발생

(반대의 경우로, 엔티티에 매칭되지 않는 컬럼 존재할 경우에는 오류 발생 X)

 

- none: 엔티티와 테이블이 정상 매핑되었는지만 확인

1
2
<property name="hibernate.hbm2ddl.auto" value="none" />
 
 
 

* none 대신 위의 4가지 옵션값이 아닌 값을 입력할 경우, hibernate.hbm2ddl.auto 실행 X

 

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

[JPA_Basic] 객체 지향 모델링  (0) 2023.08.08
[JPA_Basic] 기본키 매핑  (0) 2023.08.05
[JPA_Basic] Persistence Context 장점  (0) 2023.07.29
[JPA] @PrePersist  (0) 2023.06.25
[JPA] JOIN Null값 처리  (0) 2023.06.20

이 글은 김영한의 [자바 ORM 표준 JPA 프로그래밍 - 기본편] 수강하며 정리한 글입니다.

 

👉 기본 환경

- Language: Java

- IDE: IntelliJ

 

1. 1차 캐시

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
public class Main {
    public static void main(String[] args) {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
        // EntityManagerFactory: Application Loading 시점에 1개만 생성(DB당 1개)
 
        EntityManager em = emf.createEntityManager();
        // EntityManager: DB에 저장되는 transaction 단위마다 생성
 
        // JPA의 모든 데이터 변경은 transaction 안에서 실행
        EntityTransaction tx = em.getTransaction();
        tx.begin(); // transaction 시작 선언
 
        try {
            Member member = new Member();
            member.setId(2L);
            member.setName("HelloB");
            em.persist(member); // 1차 cache 저장
 
            tx.commit(); // transaction 종료 후, 1차 캐시에 있던 데이터를 DB에 저장
 
        } catch (Exception e) {
            tx.rollback();
        } finally {
            em.close();
        }
 
        emf.close();
        // 트랜잭션 단위로 관리되는 Entity Manager는 Tx가 종료되면 close가 되어야하지만, emf는 Application 종료 시, close되어야 함
 
    }
}
 
 
 

 

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
public class Main {
    public static void main(String[] args) {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
        // EntityManagerFactory: Application Loading 시점에 1개만 생성(DB당 1개)
 
        EntityManager em = emf.createEntityManager();
        // EntityManager: DB에 저장되는 transaction 단위마다 생성
 
        // JPA의 모든 데이터 변경은 transaction 안에서 실행
        EntityTransaction tx = em.getTransaction();
        tx.begin(); // transaction 시작 선언
 
        try {
            Member findMember1 = em.find(Member.class, 101L);
            Member findMember2 = em.find(Member.class, 101L);
            // 1차 cache에 저장된 동일한 PK로 값을 조회하므로 DB에 Select를 사용하지 않고도 findMember가 가능
 
            System.out.println("Result: " + (findMember1==findMember2));
            // 같은 Transaction 안에서 같은 객체를 조회할 경우, == 비교 성립
 
            tx.commit();
            // Commit 후, 영속성 context에 있는 데이터에 대해 DB에 Query 송부
 
        } catch (Exception e) {
            tx.rollback();
        } finally {
            em.close();
        }
 
        emf.close();
        // 트랜잭션 단위로 관리되는 Entity Manager는 Tx가 종료되면 close가 되어야하지만, emf는 Application 종료 시, close되어야 함
 
    }
}
 
 
 

 

3. 쓰기 지연

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
public class Main {
    public static void main(String[] args) {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
        // EntityManagerFactory: Application Loading 시점에 1개만 생성(DB당 1개)
 
        EntityManager em = emf.createEntityManager();
        // EntityManager: DB에 저장되는 transaction 단위마다 생성
 
        // JPA의 모든 데이터 변경은 transaction 안에서 실행
        EntityTransaction tx = em.getTransaction();
        tx.begin(); // transaction 시작 선언
 
        try {
            Member member1 = new Member(150L, "A");
            Member member2 = new Member(160L, "B");
 
            em.persist(member1);
            em.persist(member2);
 
            tx.commit();
            // 쓰기 지연을 통해서 commit 시, batch_size만큼 SQL 구문 모아서 처리
 
        } catch (Exception e) {
            tx.rollback();
        } finally {
            em.close();
        }
 
        emf.close();
        // 트랜잭션 단위로 관리되는 Entity Manager는 Tx가 종료되면 close가 되어야하지만, emf는 Application 종료 시, close되어야 함
 
    }
}
 
 
 

 

4. 변경 감지(Dirty Checking)

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
public class Main {
    public static void main(String[] args) {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
        // EntityManagerFactory: Application Loading 시점에 1개만 생성(DB당 1개)
 
        EntityManager em = emf.createEntityManager();
        // EntityManager: DB에 저장되는 transaction 단위마다 생성
 
        // JPA의 모든 데이터 변경은 transaction 안에서 실행
        EntityTransaction tx = em.getTransaction();
        tx.begin(); // transaction 시작 선언
 
        try {
            Member findMember = em.find(Member.class, 1L);
            findMember.setName("HelloJPA");
 
            tx.commit();
            // Update를 작성하지 않아도 JPA를 통해서 Entity를 가져올 경우, JPA가 관리
            // 내용의 변경이 있을 경우, JPA가 자동 update query 생성
 
        } catch (Exception e) {
            tx.rollback();
        } finally {
            em.close();
        }
 
        emf.close();
        // 트랜잭션 단위로 관리되는 Entity Manager는 Tx가 종료되면 close가 되어야하지만, emf는 Application 종료 시, close되어야 함
 
    }
}
 
 
 

 

5. 지연 로딩

 

[SpringBoot_JPA_1] FetchType.LAZY, EAGER

이 글은 김영한의 [실전! 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발]을 수강하며 정리한 글입니다. 🟦 기본 환경: IDE: IntelliJ, Language: Java FetchType.EAGER(즉시 로딩): 데이터 조회 시 연관 데이

hj0216.tistory.com

 

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

[JPA_Basic] 기본키 매핑  (0) 2023.08.05
[JPA_Basic] 데이터베이스 스키마 자동 생성  (0) 2023.07.31
[JPA] @PrePersist  (0) 2023.06.25
[JPA] JOIN Null값 처리  (0) 2023.06.20
[SpringBoot_JPA_Basic] JPA 1차 cache  (0) 2023.06.16

이 글은 남궁성의 정석코딩 [자바의정석-기초편] 수강하며 정리한 글입니다.

 

 

🟣 기본 환경: IDE: Eclipse, Language: Java

 

 

 

지네릭스(Generics): 컴파일 시 타입을 체크해 주는 기능

: runtimeException을 compileException으로 변환

 

ArrayList<Tv> tvList = new ArrayList<Tv>;

<>를 통해서 ArrayList에 들어올 수 있는 객체 제한

ArrayList<>(참조변수)와 new ArrayList<>(생성자)가 일치해야 함

 

tvList.add(new Tv());

tvList.add(new Audio()); - compile error(ClassCastException 형변환 오류)

객체 타입의 안정성을 높이고 형변환의 번거로움을 줄여줌

 

Box<T> Generic class, T의 Box or T Box라고 읽음

T(타입 문자): 타입변수 또는 타입 매개변수

Box: 원시타입(raw type)

 

지네릭 클래스 선언

class Box<T> = class 원시타입<타입변수>

 

지네릭 클래스에서 객체 생성

Box<String> b = new Box<String>();

참조변수 생성자

 

<대입된 타입(parameterized type)>

다형성 원리와 달리 대입된 타입의 경우, 조상-자손관계일 때도 타입이 일치해야 함

ArrayList<Product> list = new ArrayList<Tv>(); // Error

 

단, 타입이 아닌 arraylist 등에서는 다형성 성립(지네릭 클래스간 다형성 성립)

List<Tv> list = new ArrayList<Tv()>; // ArrayList가 List 구현, OK

 

매개변수의 다형성 성립

ArrayList<Product> list = newArrayList<Product>();

list.add(new Product());

list.add(new Tv()); // OK

list.add(new Audio()); // OK

Product p = list.get(0); // 형변환 불요

Tv tv = (Tv) list.get(1); // 형변환 필요

 

Hash<K, V>

여러개의 타입 변수가 필요한 경우, ‘,’ 사용

 

extends로 대입할 수 있는 타입을 제한

class FruitBox<T extends Fruit> { // Fruit 자손만 타입으로 지정 가능

 

Interface인 경우에도 extends를 사용

interface Eatable{}

class FruitBox<T extends Fruit & Etable>

→ 인터페이스 여러개 상속 & 만 사용 가능

 

타입 변수에 대입은 인스턴스 별로 다르게 지정 가능

Box<Apple> appleBox = new Box<Apple>();

Box<Grape> grapeBox = new Box<Grape>();

 

static member(모든 인스턴스에서 공통이므로)에 타입변수 사용 불가

** static variable에 generic 사용 불가(static T nameX)

** static method에 generic 사용 불가

** generic method에 static 사용 가능

 

타입변수 배열 선언은 가능

T[] itemArr();

배열, 객체 생성 시 타입 변수 사용불가(= new T (X))

T[] toArray() {T[] tmpArr = new T[itemArr.length]} // Error

 

- 와일드 카드<?>

하나의 참조 변수로 대입된 타입이 다른 객체를 참조 가능

→ 하나의 참조변수로 서로 다른 타입이 대입된 여러 지네릭 객체를 다루기 위한 것

cf. 지네릭 메서드: 메서드를 호출할 때마다 다른 지네릭 타입을 대입할 수 있게 한 것

 

<? extends T> 와일드 카드의 상한 제한, T와 그 자손들만 가능 - 주로 사용

<? super T> 와일드 카드의 하한 제한, T와 그 조상들만 가능

<?> 제한 없음, 모든 타입 가능(=<? extends Object>)

 

메서드의 매개변수에 와일드 카드 사용 가능

static Juice makeJuice(FruitBox<? extends Fruit>) {}

→ Fruit, Apple 가능

 

 

Generic Method

지네릭 타입이 선언된 메서드(타입 변수는 메서드 내에서만 유효)

클래스 타입 매개변수<T>와 메서드의 타입 변수<T>는 별개

매서드를 호출할 때마다 타입을 대입해야 함(대부분 생략 가능)

FruitBox<Fruit> fruitBox = new FruitBox<Fruit>();

FruitBox<Apple> appleBox = new FruitBox<Apple>();

println(Jucier.<Fruit>makeJuice(fruitBox));

println(Jucier.<Apple>makeJuice(appleBox));

→ Fruitbox에서 매개변수 타입<T>이 선언되었으므로 매서드 호출에서 사용되는 매개변수<T>는 대부분 생략 가능

 

메서드를 호출할때 타입을 생략하지 않을 때는 클래스 이름 생략 불가(같은 클래스 내 일지라도)

println(<Fruit>makeJuice(fruitBox)); // Error

println(this.<Fruit>makeJuice(fruitBox)); // OK

println(Jucier.<Fruit>makeJuice(fruitBox)); // OK

 

지네릭 타입과 원시 타입 간의 형변환은 바람직하지 않음

Box b = null;

Box<String> bStr = null;

b = (Box)bStr; // B<>→B 가능은 하지만 경고

bStr = (Box<String>)b; // B→B<> 가능은 하지만 경고

 

지네릭 타입의 제거

컴파일러는 지네릭 타입을 제거하고, 필요한 곳에 형변환을 넣는다.

Object→<T>를 다시 Object로 돌림

단, T type이 제한된 경우는 제한된 타입으로 변경

Box<T extends Fruits>→Fruits

→ 지네릭 타입 제거 후에 타입이 불일치하면 형변환을 추가한다.

get(int i) {return list.get(i);}

→ fruit get(int i) {return fruit.list.get(i)}

list.get(i)가 object이므로 fruit으로 형변환 필요

→ 와일드 카드가 포함된 경우, 적절한 타입으로 형변환 추가

 

 

열거형(enum): 관련된 상수들을 같은 묶어놓은 것

enum Kind = {CLOVER, HEART, DIAMOND, SPADE}

enum Value = {TWO, THREE, FOUR}

-> enum으로 정의하면, CLOVER에 0부터 부여

 

타입의 안전한 열거형 제공(값, 타입 모두 확인)

C언어: Kind enum의 CLOVER와 Value enum의 TWO는 모두 0으로 만일 Card.CLOVER==Card.TWO, 하면 같다고 반환(의미는 Kind와 Value가 다르므로 같다고 나오면 안됨)

Java_enum: Card.kind.CLOVER == Card.Value.TWO 를 적으면 타입이 달라서 비교가 불가능하다는 컴파일 에러가 발생

 

열거형 정의

enum Direction {E, S, W, N}

 

열거형 타입의 변수 선언 및 사용법

class Unit {

unit x, y;

Direction dir; // 열거형 iv 선언(enum Direction {E, S, W, N}만 대입 가능)

void init()

dir = Direction.E; 초기화

}

 

열거형 상수의 비교에 ==와 compareTo() 사용 가능

if(dir==Direction.E) {

x++;

} else if(dir>Direction.E) { // Error - 열거형 상수=객체이므로 비교 연산자 사용 불가(equal, compareTo써야하지만, ==도 예외적으로 가능)

} else if(dir.compareTo(Direction.SOUTH)) { // CompareTo는 사용 가능

→ CompareTo: 왼:+1, 동:0, 오 +1(크기도 반환 가능)

}

 

java.lang.Enum

- String name(): 열거형 상수의 이름을 문자열로 반환

- int oridinal(): 열거형 상수가 정의된 순서를 반환(0부터 시작)

(단, 상수들이 실제로는 다른 값을 갖을 수 있기때문에 상수값과 순서값이 다를 수 있음)

- T valueOf(Class<T> enum Type, String name): 지정된 열거형에서 name과 일치하는 열거형 상수를 반환

컴파일러 자동 추가(values(),valueOf())

static E[] values()

Direction[] dArr = Direction.values(); // Direction의 상수를 배열로 반환

for(Direction d: dArr)

System.out.printf("%s=%d%n", d.name(), d.ordinal()); // 이름과 순서 반환

 

static E valueOf(String name)

Direction d = Direction.valueOf("W");

valueOf(열거형 상수 이름) -> 열거형 상수 반환

(Direction.W와 동일)

 

불연속적인 열거형 상수의 경우, 원하는 값을 괄호안에 적는다.

enum Direction {E(1), S(4), W(-1), N(7)}

* 여러개도 가능

enum Direction {east(1, “>”) south(5, “v”) west(-2, “<”) north(-8, “^”)}

 

→ 이러한 괄호를 사용하려면 인스턴스 변수와 생성자를 새로 추가해 줘야 한다.

enum Direction {

east(1, “>”) south(5, “v”) west(-2, “<”) north(-8, “^”)

private final int value; // 정수를 저장할 필드(인스턴스 변수 선언)를 추가

private final String symbol;

// esat(1, ">") -> east 객체의 1, ">" 생성자 호출 이므로

Direction(int value, String symbol) { // 생성자 추가

this.value = value

this symbol = symbol

}

cf. 생성자는 항상 private이므로 생략 가능→따로 표시가 없더라도 외부에서 객체 생성 불가

(Direction d = new Direction(1); // 열거형 생성자 private: 외부 호출 불가 -> Error)

public int getValue() {return value;}

public String getSymbol() {return symbol;}

}

 

 

애너테이션: 주석처럼 프로그래밍 언어에 영향을 미치지 않으며, 유용한 정보를 제공

/** ~ */ java.doc 주석(소스코드에 대한 설명)

 

@annotation

표준 애너테이션: 자바에서 제공하는 애너테이션

 

* @Override: 오버라이딩을 올바르게 했는지 컴파일러가 체크

class Parent {void parentMethod(){}}

class Child extends Parent {

@Override

void parentMethod() {}

}

 

* @Dreprecated: 앞으로 사용하지 않을 것을 권장하는 필드나 메서드

cmd 창에서 javac JavaFileName.java;

deprecated 경고 발생(eclipse는 자동 컴파일되므로 확인할 수 X)

javac -Xlint JavaFileName.java

상세한 경고 내용 확인 가능

 

* @Functionallterface

함수형 인터페이스에 붙이면 컴파일러가 올바르게 작성했는지 확인

함수형 인터페이스는 하나의 추상 메서드({}구현부가 없)는 메서드만 가져야 한다는 제약이 있어 그것을 확인해줌

일반 인터페이스는 여러 추상 메서드 보유 가능

interface testable {

void test();

void check();

}

일반 인터페이스 취급, 여러 추상 메서드 보유 가능

@FunctionnalInterface

interface testable {

void test();

void check();

}

1개 이상의 추상 메서드 구현으로 오류

 

* @SuppressWarnings

컴파일러의 경고메세지가 나타나지 않게 억제(해당 경고의 원인이 된 곳에 기재)

괄호()안에 억제하고자하는 경고의 종류를 문자열로 지정

@SuppressWarnings({”rawtypes”, ”deprecation”, ””})

→ 해당 경고를 내가 확인했다는 의미

→ 경고가 쌓이면서 놓치는 경우가 있을 수 있으므로 확인 한 경고는 SurpressWarnings에 기재해서 경고 삭제

*xlint옵션으로 cmd에서 컴파일하면 경고메시지 확인 가능

javac -Xlint AnnotionTest.java

-> [] 안의 경고가 SurpressWarnings에 들어가는 경고 이름

 

 

메타 애너테이션: 애너테이션을 만들 때 사용

 

- @Target

애너테이션을 정의할 때, 적용대상 지정에 사용

 

@Target({FIELD, TYPE, TYPE_USE}): 적용대상

ANNOTATION_TYPE: 애너테이션

field: 멤버 변수(IV, CV), ENUM 상수

type: class, interface, enum

type_use: 타입이 사용되는 모든 곳

 

애너테이션 만들기

@Target({FIELD, TYPE, TYPE_USE})

public @interface MyAnnotation {} // annottion 앞에 @interface 기재

@MyAnnotation // 적용대상: TYPE_USE

class MyClass {

@MyAnnotation // 적용대상: FIELD

int i;

@MyAnnotation // 적용대상: TYPE_USE

MyClass mc;

}

 

- @Retention: 애너테이션이 유지되는 기간을 지정하는데 사용

* 1. SOURCE: 소스파일에만 존재, 클래스 파일에는 X // @Override

2. RUNTIME: 클래스 파일에 존재, 실행 시 사용 가능 // @FuntionalInterface

3. CLASS: 클래스 파일에 존재, 실행 시 사용 불가 (기본값)

@Retention(RetentionPolicy.SOURCE)

public @interface Override

@Retention(RetentionPolicy.RUNTIME)

public @interface FunctionalInterface {}

 

- @Documented

javadoc으로 작성한 문서에 애너테이션 포함시키려고 할 때

 

- @Inherited

애너테이션을 자손 클래스에 상속하고자 할 때

@Inherited // SuperAnno가 자손까지 영향을 미치게

@interface SuperAnno{}

 

@SuperAnno

class Parent{}

 

class Child extends Parent {} // child에 @SuperAnno 애너테이션이 붙은 것으로 인식

 

- @Repeatable

반복해서 붙일 수 있는 애너테이션을 정의할 때 사용

 

@Repeatable(ToDos.class)

@interface Todo{String value();}

 

@ToDo()

@ToDo()

class MyClass

하나의 대상에 애너테이션을 반복해서 붙일 수 있음

 

@Repeatable인 @ToDo를 하나로 묶을 컨테이너 애너테이션도 정의해야 함

@interface ToDos{ // 여러개의 애너테이션을 담을 컨테이너 애너테이션 생성

ToDo[] value(); // todo 애너테이션 배열타입의 요소를 선언, 이름이 반드시 value여야 함

}

 

애너테이션 타입 정의하기

@interface 애너테이션 이름{

타입 요소이름();

}

 

애너테이션 메서드는 추상 매서드(구현 필요 X)이며, 애너테이션을 적용할 때 모든 요소를 다 적어줘야 하며, 순서는 상관X

(단, null제외 기본값 지정 가능→기본값 지정시 애너테이션 구성요소 작성 생략 가능)

@interface TestInfo{

int count() default 1;

}

@TestInfo(

count =3

)

 

enum 사용 가능

enum TestType {F, S}

TestType testType();

 

자신이 아닌 다른 애너테이션 포함 가능

@interface DateTime{}

DateTime testDate();

 

요소가 하나이고, 이름이 value면 요소이름 생략 가능

@interface TestInfo{

String value();

}

@TestInfo(”Passed”) // @TestInfo(value=”Passed”)와 동일

class NewClass{}

 

요소 타입이 배열인 경우 괄호를 사용해야 함

@interface TestInfo{

String[] testTools();

}

 

@Test(testTools={”JUnit”, “AutoTester”})

@Test(testTools=”JUnit”) - 1개일 때는 생략 가능

@Test(testTools={}) - 값이 없을 때는 괄호{}가 반드시 필요

 

모든 애너테이션의 조상은 interface

→ extends를 사용해서 상속하는 표현 쓰지X, 자동 상속

→ 모든 annotation의 조상인 interface Annotation의 추상매서드들을 모두 구현하지 않고도 사용 가능(컴파일러가 자동 구현)

 

마커 애너테이션(Marker Annotation)

요소가 하나도 정의되지 않은 애너테이션

@Test, @Deprecated, @Override

 

애너테이션 요소 규칙

애너테이션 요소를 선언할 때, 아래의 규칙을 반드시 지켜야 함

1. 요소의 타입은 기본형, String, enum, Annotation, Class만 허용

2. 추상 메서드 괄호()안에 매개변수를 선언 할 수 X

3. 예외를 선언할 수X

4. 요소를 타입 매개변수<T>로 정의할 수 없음

 

@interface AnnoTest {

(static final) int id = 100; - 상수O, default method*는 안됨

String major(int i, int j); - 2

String minor() throws Exception; - 3

ArrayList<T> list(); - 4

}

* default method

인터페이스에 메서드를 추가할 경우, 해당 인터페이스를 구현하고 있던 모든 class에 오류 발생

이 때, 인터페이스 내부에 메서드 추가시 default를 앞에 붙이면 필요적으로 구현하지 않아도 됨

interface A {

default void b() {}; - 추상 메서드와 달리 빈 선언부가 있음

void c() {};

}

 

class D impletmens A {

void c() {}; - 필요적 구현

void b() {}; - 선택적 구현

}

 

 

 

소스 코드

🔗 HJ0216/java-practice

 

참고 자료

 

Java - Default Method (Interface)

인터페이스를 구현할 때 굳이 필요하지 않은 메서드까지 다 override 해야한다. 그렇기 때문에 인터페이스...

blog.naver.com