해당 오류의 문제

: H2 Database 연결에 대한 문제입니다.

저 같은 경우

jdbc:h2:tcp://localhost/~/random

이와 같이 random 이라는 데이터베이스를 새로 만들었는데

정작 데이터베이스를 만들지 않았기 때문에 발생한 문제입니다.

 

최근 H2 Database 가 보안적에 관한 규칙이 생겨 자동으로 데이터베이스를 생성하지 않아 생기는 문제입니다.

 

 

[ 해결방법 ]

 

ㅁㅁ

위의 드래그된 곳에 자신이 만들 데이터베이스을 작성하면 됩니다.

 

접속은

 jdbc:h2:tcp://localhost/./random

 

이와 같이 접속하면 됩니다.

 

[ 해결 완료 ]

application.yml 파일의 들여쓰기 문제입니다.

 

spring:
	datasource:
    	url:~~
        
    jpa:
    	hibernate:
        	ddl-auto:
    
 logging.level:
 	org.hibernate.SQL:~~

 

이와 같이 띄어쓰기가 잘 되어있는지 확인해보시고 돌려보시길 바랍니다.

임베디드 타입

  • 새로운 값 타입을 직접 정의할 수 있음
  • JPA 는 임베디드 타입이라 함
  • 기본 값 타입을 모아 만들어서 복합 값 타입이라 부름
  • int,String 과 같은 값 타입

 

임베디드 타입 장점

  • 재사용성
  • 높은 응집도
  • 해당 값 타입만 사용하는 의미 있는 메소드 생성 가능
  • 임베디드 타입을 포함한 모든 값 타입은 값 타입을 소유한 엔티티에 생명주기를 의존

 

임베디드 타입 사용 방법

  • @Embeddable : 값 타입 정의하는 곳에 표시
  • @Embedded : 값 타입 사용하는 곳에 표시
  • 기본 생성자 필수

 

임베디드 타입과 테이블 매핑

: 임베디드 타입을 쓴것과 안 쓴것의 회원 테이블은 이전과 똑같음을 알 수 있습니다.

 

 - 테이블은 데이터를 다루는 개념이기에 변경되지 않도록 설계하는 것이 좋습니다.

 - 엔티티의 경우 메소드까지 신경을 써야하므로 공통 기능은 묶어서 사용하는게 사용성이 더 좋습니다.

 

 

임베디드 타입과 테이블 매핑

  • 임베디드 타입은 엔티티의 값일 뿐입니다.
  • 임베디드 타입을 사용하기 전과 후에 매핑하는 테이블은 같습니다.
  • 객체와 테이블을 아주 세밀하게 매핑하는 것이 가능합니다. ( 프로젝트가 커질 수록 세밀한게 좋다 )
  • 잘 설계한 ORM 애플리케이션은 매핑한 테이블의 수보다 클래스의 수가 더 많습니다.

 

임베디드 타입과 엔티티

  • 임베디드 타입 멤버 안에 엔티티 객체도 들어 올 수 있습니다

- PhoneNumber 라는 임베디드 객체는 PhoneEntity 라는 엔티티를 가질 수 있음

 

 

사용 예시

  • 회원 테이블의 [ Address ( city, street, zipcode ) ] , [ WorkPeriod ( Order, Send, ArriveDateTIme ) ] 임베디드 타입을 정의

 

발생오류

1. EntityManagerFactory 빈을 등록 안한 오류 <해결완료>

 

[ 기존 코드 ]

    @Bean
    public EntityManager getEm() {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory();
        return emf.createEntityManager();
    }

[ 수정 코드 ]

    @Bean
    public EntityManagerFactory getEmf() {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("5xik");
        return emf;
    }
    
    @Bean
    public EntityManager getEm() {
        EntityManagerFactory emf = getEmf();
        EntityManager em = emf.createEntityManager();
        return em;
    }

 

 

2. 테이블이 업데이트 되지 않는 오류 < 해결완료 >

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

 

 

3. 커밋 시기를 save 함수 내에서 작동하도록 변경 < 해결완료 >

 

 [ 기존 코드 ]

테스트 코드에서 의존성 주입받은 EntityManager 를 통해 커밋을 해주는 방식을 사용하기 위해 시도

  -> 트랜잭션호출 생성 함수에서 NPE 발생

 

[ 수정 코드 ]

public Member save(Member member) {
    tx.begin();
    try {
        em.persist(member);
        tx.commit();
    } catch (Exception e) {
        tx.rollback();
    } finally {
        //em.close();
    }
    //mf.close();
    return member;
}
    @Test
    @Commit
    void insertData() {
        Member member = new Member();
        member.setName("test");
        member.setPhonenumber("01011");
        member.setAddress(new Address("city","street","10000"));
        member.setWorkPeriod(new WorkPeriod());
        Member save = repository.save(member);
    }

- member 객체를 테스트 코드에서 만듬

- save 함수 내부에서 begin ~ persist ~ commit 과정을 거침으로써 테스트코드에서는 이에 대한 코드를 작성할 일이 없음

임베디드 타입으로 선언한 값들이 정상적으로 들어오는 것을 알 수 있음

 

 

 

 

 

값 타입 공유 참조

임베디드 타입 같은 값 타입을 여러 엔티티에서 공유하면 위험함

 

[ 예시 ]

    @Test
    @Commit
    void insertData() {
        Address address = new Address("city","str","10000");
        //공유 객체

        Member member = new Member();
        member.setName("test");
        member.setPhonenumber("01011");
//        member.setAddress(new Address("city","street","10000"));
        member.setAddress(address);
        member.setWorkPeriod(new WorkPeriod());

        Member member2 = new Member();
        member2.setName("test");
        member2.setPhonenumber("01011");
        member2.setAddress(address);

        member2.getAddress().setCity("geoje"); // 수정

        Member save = repository.save(member);
        Member save2 = repository.save(member2);
    }

 

- address 라는 객체를 공유해서 사용

- member2 에 address 를 수정

- member1 에 address 도 수정됨을 볼 수 있음

 

[ 해결 방법 ]

 

1) 각각의 객체에 new Address 로 생성자를 통해 할당 해줌

member.setAddress(new Address("city","street","10000"));
member2.setAddress(new Address("city2","street2","20000"));

 

2) 불변 객체로 생성

 - setter 부분을 private 로 선언해서 외부에서 수정할 수 없게 만들어주는 방법

집합 연산

레코드를 조회하고 분류한 뒤, 특정 작업을 하는 연산

 

1. GROUP BY

데이터를 조회할 때 그룹으로 묶어서 조회합니다.

 

ex 1. 고객 테이블의 State 에 따라 그룹화해서 조회해라.

SELECT * FROM customers
GROUP BY State;

 

* 만약 쿼리 결과 데이터의 중간이 비어있다면 ??

데이터베이스에서 데이터를 불러오는 과정에서 State 에 따라 그룹을 지정했지만,

그룹에 대한 작업없이 조회만 했습니다.

 

그래서 쿼리 결과로 나타나는 데이터는 각 그룹의 첫번째 데이터만 표현됩니다.

 

 

HAVING

GROUP BY 로 조회한 결과를 필터링 할 수 있습니다.

 

SELECT CustomerId, AVG(Total)
FROM invoices
GROUP BY CustomerId
HAVING AVG(Total) > 6.00

 

모든 고객의 주문서에서 가격의 평균을 구한 뒤에, 그 평균이 6.00을 넘는 결과만 조회합니다.

 

이렇게 GROUP BY로 그룹을 지은 결과에 필터를 적용할 때 HAVING 을 사용할 수 있습니다.

 

HAVING 은 WHERE 과는 적용방식이 다릅니다.

HAVING 은 그룹화한 결과에 대한 필터이고, WHERE 은 저장된 레코드를 필터링 합니다.

따라서 그룹화 전에 필터를 해야한다면, WHERE 를 사용하는것이 맞습니다.

 

 

COUNT , SUM , AVG , MAX , MIN

레코드의 갯수를 헤아릴 때 사용합니다.

 

ex. 모든 레코드에 대한 COUNT 함수 사용

SELECT * COUNT(*) FROM customers
GROUP BY State;

- 각 그룹의 첫번째 레코드와 각 그룹의 레코드 갯수를 집계하여 리턴합니다.

 

ex. 각 State 에 해당하는 레코드 갯수를 확인하는 COUNT 함수 사용

SELECT State, COUNT(*) FROM Customers
GROUP BY State;

- 그룹으로 묶인 결과의 레코드 갯수를 확인할 수 있습니다.

 

 

 

SELECT 실행 순서

- FROM

- WHERE

- GROUP BY

- HAVING

- SELECT

- ORDER BY

 

 

문제

1. 현재 있는 데이터베이스에 존재하는 모든 테이블 정보를 보여주기

SHOW TABLES;

 

2. USER 테이블의 구조를 보기위한 SQL 을 작성

DESCRIBE USER;

 

3. USER 테이블에 존재하는 모든 컬럼에 대한 데이터를 확인

SELECT * FROM USER;

 

4. USER 테이블에 name = 'kim', email='kim@gmail.com' 으로 데이터를 추가

INSERT INTO user(name,email) VALUES ('kim','kim@gmail.com')

 

5. USER 테이블에 name 이 kim 인 데이터를 찾아라

SELECT * FROM user WHERE user.name = 'kim';

 

6. USER 테이블에 name 이 kim 이 아닌 데이터를 찾아라

SELECT * FROM user WHERE user.name <> 'kim';

 

7. Content 테이블에 존재하는 모든 테이블에서 title 컬럼만을 찾기 위한 SQL 작성

SELECT Content.title FROM Content;

 

 

8. 위의 테이블에서 Content의 title 과 그 컨텐츠를 작성한 User 의 name 을 찾아라 ( 저자가 없어도 찾음 )

SELECT Content.title, User.name FROM Content LEFT JOIN User ON User.id = Content.id;

 

9. 위의 테이블에서 Content의 title 과 그 컨텐츠를 작성한 User 의 name 을 찾아라. ( 저자가 있는 컨텐츠의 타이틀만 찾음 )

SELECT Content.title, User.name FROM Content JOIN user ON user.id = content.userId;

 

10. Content 의 데이터를 수정하기 위한 SQL 을 작성해라.

( title이 database sprint인 content 데이터에서 body를 database is very easy로 수정해야합니다. )

UPDATE Content SET Content.body = 'database is very easy' WHERE Content.title = 'database sprint';

JPQL

엔티티 객체를 대상으로 쿼리를 작성하는 객체지향 쿼리언어

 

 

 

 

JPQL은 왜 등장했을까?

JPA를 사용하면 엔티티 객체를 중심으로 개발하게 됩니다.

검색을 할 때도 테이블이 아닌 엔티티 객체를 대상으로 검색하게 되죠.

 

데이터가 수 만, 수십 만개가 있을 때 JPA만으로 모든 DB데이터를 객체로 변환해서 검색하는 것은 사실상 불가능합니다.

그래서 애플리케이션이 필요한 데이터만 DB에 불러오려면 결국 검색 조건이 포함된 SQL이 필요했습니다.

그래서 JPQL이 등장하게 되었습니다.

 

 

JPQL 특징
  1. JPA에서 SQL을 추상화한 JPQL이라는 객체 지향 쿼리 언어를 제공합니다.
  2. SQL 문법과 유사합니다 ( SELECT ,FROM, WHERE, GROUP BY, HAVING, JOIN 지원 )
  3. JPQL은 엔티티 객체를 대상으로 쿼리합니다.
  4. 반면에 SQL은 데이터베이스 테이블을 대상으로 쿼리합니다.

 

JPQL 문법

기존 SQL 문법과 똑같습니다.

예제
List<Member> result =
 em.createQuery(
	SELECT m FROM Member m WHERE m.username like "%kim%",
	Member.class
 ).getResultList();

FROM Member 에서 Member은 테이블이 아닌 엔티티 객체를 의미합니다.

 

 

테스트

가장 먼저 main 함수로 테스트를 해보겠습니다.

데이터가 잘 들어옴을 알 수 있습니다.

 

하지만 인프런의 "김영한" 님이 말씀하시기를 테스트 코드를 필수로 작성할 줄 알아야 한다고 합니다.

그래서 테스트 코드를 통해 해당 쿼리가 정상작동하는지 보겠습니다.

 

 

 

테스트 코드

[ 구성도 ]

1. DIP를 지키기 위해 MemberRepository Interface 상속하여 JPQLMemberRepository 라는 클래스를 만들어서 테스트

2. persistence.xml 에 table = create로 테스트 진행 ( 테스트할 때만 create 해야됨 )

3. save(), findByName() 메소드 테스트

4. 생성자 주입을 이용

 

 

[ 의문점1]

- 기존에 배웠던 빈 등록과, 의존성 주입은 아래의 과정을 거칩니다.

  1) SpringConfig 라는 IoC컨테이너에 빈을 주입한다.

  2) 해당 빈을 사용하는 부분에서 @Autowired 를 통해 의존관계 주입을 한다.

 

 - 그런데 JPQLMemberRepository에서의 EntityManger(em)과 Test Code에서의 em은 같은 객체인가?

    에 대한 개념을 이해하기 위해 따로 정리하였습니다.

https://tjdwns4537.tistory.com/66

 

EntityManager

Entity Manger Entity를 관리하는 역할을 수행하는 클래스 Entity Jpa가 관리하는 객체 EntityMangaerFactory (emf) 고객의 요청이 올때마다 엔티티 매니저를 하나씩 만들어 줍니다. 이 엔티티 매니저는 내부적

tjdwns4537.tistory.com

 

[ 의문점1 해결완료 ]

즉, em은 쓰레드간에 공유를 하면 안되는다는 의미입니다.

그래서 각각의 em으로 영속해주는 방식을 사용하면 됩니다.

 

 

[ 의문점2 ]

EntitnyManger 은 의존관계를 어떻게 하는가?

 

1) JPQLMemberRepository 에서는 EntityManagerFactory를 직접 생성하지 않습니다.

     애플리케이션 로딩 시점에 생성되기 때문에 EntityManager를 생성자에서 피라미터로 받게 하였습니다.

     

2) IoC컨테이너에서 Repository에 대한 빈을 등록해야하는데, EntityManager를 어떻게 넣어줘야 하는가?

 

 

[ 의문점2 의 설명 ]

EntityManager 는 트랜잭션 단위로 생성하고, EntityManagerFactory는 DB당 하나를 만들어야 하는데,

클라이언트 코드에서 enf를 만들 것이니, IoC컨테이너에서는 enf를 만들 수 없다.

그렇다면 IoC컨테이너의 빈을 등록할 때, enf를 넣도록 해야하는가 ?

 

[ 의문점2의 해결시도1 ]

  • EntityManager는 @PersistenceContext로 주입을 해줄 수 있습니다. (최신버전은 Autowired로도 가능)

그래서 Autowired를 이용해 테스트 코드를 작성해봤습니다.

 

[ JPQLMemberRepository ]

@Transactional
@Repository
public class JPQLMemberRepository implements MemberRepository{

    private final EntityManager em;

    @Autowired // EntityManger 주입부분
    public JPQLMemberRepository(EntityManager em) {
        this.em = em;
    }

    public EntityTransaction getTx() {
        return em.getTransaction();
    }

    public Member save(Member member) {
        em.persist(member);
        return member;
    }

    public void emClose() {
        em.close();
    }

    @Override
    public Optional<Member> findByid(Long id) {
        return Optional.empty();
    }

    @Override
    public Optional<Member> findByname(String name) {
        List<Member> result = em.createQuery(
                "SELECT m FROM Member m WHERE m.name = :name",
                Member.class
        ).setParameter("name", name).getResultList();

        System.out.println("--------------------------------");
        for (Member i : result) {
            System.out.println("member:" + i);
        }
        System.out.println("--------------------------------");

        return result.stream().findAny();
    }
    ... // 그외 인터페이스 메소드
}

 

[ SpringConfig ]

@Configuration
public class SpringConfig {

    @Bean //EntityManagerFactory Bean 등록
    public EntityManagerFactory getEnf() {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("5xik");
        return emf;
    }

    @Bean // EntityManager Bean 등록
    public EntityManager getEm() {
        return getEnf().createEntityManager();
    }

    @Bean
    public MemberServiceInterface memberService() {
        return new MemberServiceImp(memberRepository());
    }

    @Bean
    public MemberRepository memberRepository() {
        return new JPQLMemberRepository(getEm());
    }
 }

 

[ TestCode ]

SpringBootTest
@Transactional
class JPQLMemberRepositoryTest {

    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfig.class);

    JPQLMemberRepository repository = ac.getBean("memberRepository",JPQLMemberRepository.class);

    EntityTransaction tx = repository.getTx();

    @Test
    @Commit
    void insertData() {
        tx.begin();
        try{
            Member member = new Member();
            member.setName("sungjun");
            member.setPhonenumber("010");

            repository.save(member);
            Optional<Member> result =  repository.findByname(member.getName());

            Assertions.assertThat(result).isEqualTo(member);
            tx.commit();

        } catch (Exception e){
            tx.rollback();
        } finally {
            //repository.emClose();
        }
        //ac.close();
    }
}

 

 

그런데 아래와 같은 에러가 발생하였습니다.

caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'JPQLMemberRepository' defined in file [/Users/parksungjun/Desktop/창업동아리/ShoppingMall/out/production/classes/xik/ShoppingMall/Repository/JPQLMemberRepository.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'javax.persistence.EntityManager' available: expected single matching bean but found 2: getEm,org.springframework.orm.jpa.SharedEntityManagerCreator#0

글이 길어졌으므로 에러 해결은 다음 블로깅으로 넘어가서 하도록 하겠습니다.

https://tjdwns4537.tistory.com/67

 

Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.NoUnique

 

tjdwns4537.tistory.com

 

'JPA(MySQL,H2Database)' 카테고리의 다른 글

임베디드 값 타입  (0) 2022.08.08
SQL 내장함수  (0) 2022.08.05
EntityManager  (0) 2022.07.29
연관 관계 매핑  (0) 2022.07.15
exception just for purpose of providing stack trace  (0) 2022.07.13
Entity Manger

Entity를 관리하는 역할을 수행하는 클래스

 

 

Entity

Jpa가 관리하는 객체

 

 

EntityMangaerFactory (emf)

고객의 요청이 올때마다 엔티티 매니저를 하나씩 만들어 줍니다.

이 엔티티 매니저는 내부적으로 DB Connection 을 통해 DB를 사용하게 됩니다.

 

그래서 JPA는 EntityManagerFactory가 필요하며,

애플리케이션 로딩시점에 DB당 딱 하나만 생성되야 합니다.

 

그리고 WAS가 종료되는 시점에 emf 를 close 해주면 됩니다.

 

 

EntityManager

실제 트랜잭션 단위를 수행할 때 마다 생성합니다.

즉, 고객의 요청이 올 때마다 사용했다가 닫습니다.

 

 * 트랜잭션 수행 후에는 반드시 엔티티 매니저를 닫아줘야합니다. 그래야 내부적으로 DB Connection을 반환합니다.

 

 

EntityTransaction

데이터를 변경하는 모든 작업은 반드시 트랜잭션 안에서 이뤄져야 합니다.

단순 조회의 경우는 상관 없습니다.

 

 

Persistenct Context (영속성 컨텍스트)

엔티티 매니저 내부에서 엔티티를 관리해주는 역할을 합니다.

이는 엔티티를 영구히 저장하는 환경을 가진다는 특징이 있습니다.

엔티티를 영속성 컨텍스트에 저장하는 과정에 대한 코드를 먼저 보겠습니다.

EntityManagerFactory emf = Persistenct.createEntityMangerFactory("persistenceUnit name")
EntityManager em = emf.createEntityManger();
EntityTransaction tx = em.getTransaction();

tx.begin();

Member member = new Member();
memer.setName("aaa");

em.persist(member);

tx.commit();

[ 각 소스 코드의 역할 ]

1) EntityTransaction : 커밋한 순간에만 Insert 쿼리를 날릴 수 있게 쓰기 지연 기능을 도와준다.

2) persist : 영속성 컨텍스트에 해당 엔티티를 저장한다. ( 영속화 )

3) commit : Insert 쿼리가 날라간다.

 

[ 영속 상태가 되는 경우 ]

1) persist

2) find

find 해서 DB에서 가져왔는데 그게 영속성 컨텍스트에 없는 경우 1차 캐시에 올리게 됩니다.

1차 캐시에 올린다는 의미는 영속 상태가 된다는 의미입니다.

 

 

영속성 컨텍스트의 장점 - 1차 캐시 ( 쓰기 지연 SQL 저장소 )

1차 캐시란,

[ 처음 들어온 데이터 ] : 영속성 컨텍스트에 데이터 없음 -> DB 접근해서 데이터 조회

[ 두번째 들어온 데이터 ] : 영속성 컨텍스트에 데이터가 있으므로 바로 불러온다 ( DB조회 안함 )

 

이렇게 빠르게 조회할 수 있다는 장점이 있습니다.

이 1차 캐시를 보통 [ 쓰기 지연 SQL 저장소 ] 라고 부릅니다.

그렇다면 쓰기 지연 SQL 저장소와 현재 로컬에서의 엔티티가 다르다면 어떻게 적용 할 수 있을까요?

 

 

영속성 컨텍스트의 장점 - 동일성 보장

동일한 DB를 조회한 객체의 경우 동일한 객체임을 보장합니다.

그래서 (==) 로 비교시 true 가 됨을 알 수 있습니다.

Member a = em.find(Member.class,"sun");
Member b = em.find(Member.class,"moon");

Assertions.assertThat(a).isEqualsTo(b);

 

 

영속성 컨텍스트 장점 - Dirty Checking

데이터 수정시 update() -> persist() 로 업데이트를 해야한다고 생각할 수 있지만

그럴 필요는 없습니다.

 

데이터가 변경되더라도 commit하면 알아서 DB에 반영됩니다.

 

 

플러시

영속성 컨텍스트의 변경사항과 DB의 내용을 맞추는 작업

플러시가 발생하면 아래의 동작과정이 발생합니다.

 

  1. 변경 감지
  2. 수정된 엔티티 쓰기 지연 SQL에 등록
  3. 쓰기 지연 SQL 저장소 쿼리를 DB에 전송 ( INSERT / SELECT / DELETE )

단, 플러시가 발생했다고 트랜잭션에 커밋이 되는건 아닙니다.

플러시 시점에는 해당 쿼리를 DB에 동기화 시키고, 커밋시에 DB에 전달하게 됩니다.

 

[ 플러시 방법 ] 

1) em.flush() : 직접 플러시

2) Transaction commit : 자동 플러시

3) JPQL Query execute : 자동 플러시

'JPA(MySQL,H2Database)' 카테고리의 다른 글

임베디드 값 타입  (0) 2022.08.08
SQL 내장함수  (0) 2022.08.05
JPQL 와 JPQL의 테스트 코드 작성  (0) 2022.07.29
연관 관계 매핑  (0) 2022.07.15
exception just for purpose of providing stack trace  (0) 2022.07.13
연관 관계 매핑

객체의 참조와 테이블의 외래 키를 매핑하는 것

 

 

연관 관계 매핑이 왜 필요할까?

객체를 테이블에 맞춰 데이터 중심으로 모델링하면, 협력 관계를 만들 수 없다.

아래 두가지의 객체와 테이블의 차이를 보면 알 수 있다.

 - 테이블은 외래 키로 조인을 사용해 연관된 테이블 찾음

 - 객체는 참조를 사용해 연관된 객체를 찾음

 

 

연관 관계 정의 규칙

1) 방향 : 단방향,양방향

2) 연관 관계의 부모 : 연관 관계에서 관리의 주체

3) 다중성 : N:1 , 1:N, 1:1, N:M

 

 

 

단방향, 양방향

여기서 방향이란 한 Entity가 다른 Entity를 가질 수 있느냐로 보면 됩니다.

* 데이터베이스의 Forein Key (외래키) : 데이터베이스의 테이블은 FK하나로 두 개의 테이블이 JOIN가능하다.

* 객체의 참조용 필드 : 객체의 경우 참조용 필드가 있는 객체만 다른 객체를 참조하는 것이 가능하다.

 

 1) 단방향 : 두 객체 사이에 하나의 객체만 참조용 필드를 갖고 참조

 

 2) 양방향 : 두 객체 모두가 각각 참조용 필드를 갖고 참조한다. 양방향 관계를 설정하면 많은 테이블과 연관 관계를 맺게 되고

                   클래스가 굉장히 복잡해진다. 그래서 기본적으로 단방향 매핑을 하고 나중에 역방향으로 탐색이 필요할 때 추가하는게 좋다.

 

 

 

모델링의 예시를 보자

회원과 주문의 관계를 보자

1명의 회원이 여러 상품을 주문을 할 수 있다. 즉, 1:n입니다.

회원은 주문할 때 주문 Id를 알 필요가 있고,

주문에서는 어떤 회원이 주문했는지 알아야하기 때문에 회원 Id를 알 필요가 있습니다.

그렇다면 양방향 관계 매핑이 필요합니다.

그러면 각각 @OneToMany, @ManyToOne 을 이용해야합니다.

 

 

[ MEMBER ]

@Entity
public class Member {

    @Getter
    @Setter
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name="MEMBER_ID")
    private Long id; // 시스템에 저장하기 위해 시스템이 정하는 변수

    @OneToMany(mappedBy = "member")
    private List<Order> orders = new ArrayList<>();

    @Getter
    @Setter
    @Column(name = "NAME")
    private String name; // 고객이 입력한 데이터

    public Member(){

    }
}

: MEMBER_ID 컬럼명의 id가 AUTO_INCREMET로 생성된다.

한 명의 회원이 여러 주문을 할 수 있으므로,

Member에서 Order은 @OneToMany를 합니다.

여기서 mappedBy는 양방향 관계 설정 시 관계의 주체가 되는 쪽에서 정의합니다.

그러니까 회원의 입장에서 여러 주문을 하는 것으로 즉, 주체가 회원이므로 mappedBy는 회원에 작성합니다.

 

 

 

[ ORDER ]

@Entity
@Table(name="ORDERS")
public class Order {
    @Getter
    @Setter
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "ORDER_ID")
    private Long id;

    @Getter
    @Setter
    @ManyToOne
    @JoinColumn(name="MEMBER_ID")
    private Member member;
    
    public Order() {

    }
}

: Order에서 Member은 @ManyToOne을 합니다.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

'JPA(MySQL,H2Database)' 카테고리의 다른 글

임베디드 값 타입  (0) 2022.08.08
SQL 내장함수  (0) 2022.08.05
JPQL 와 JPQL의 테스트 코드 작성  (0) 2022.07.29
EntityManager  (0) 2022.07.29
exception just for purpose of providing stack trace  (0) 2022.07.13

아래의 테스트 코드에서 롤백되는 에러가 발생하였습니다.

 

public class JpaTest {

    @Test
    void JpaTest() {
        // 엔티티 팩토리 생성
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("5xik");
        // 엔티티 매니저 생성
        EntityManager em = emf.createEntityManager();
        // 트랜잭션 객체 생성
        EntityTransaction tx = em.getTransaction();
        tx.begin();

        try{
            MemberTest member = new MemberTest();
            member.setId(1L);
            member.setName("parksung");
            member.setPhoneNumber("01074724537");

            em.persist(member);

            Member findMember = em.find(Member.class, 1L);

            System.out.println("findMember,Id = " + findMember.getId());
            System.out.println("findMember.Name = " + findMember.getName());
            tx.commit();
        } catch(Exception e){
            tx.rollback();
        } finally{
            em.close();
        }
        em.close();
    }

    @Entity
    class MemberTest{
        @Id
        @Setter
        @Getter
        private Long id;

        @Setter
        @Getter
        private String name;

        @Setter
        @Getter
        private String PhoneNumber;
    }
}

 

 

해결 시도 1.

 원래 이미 정의되어 있는 Member class 에서 불러오고 있었습니다.

 거기서 엔티티 Id에 대해서 아래와 같이 정의되어 있었습니다.

@Id @GeneratedValue(strategy = GenerationType.IDENTITY)

 

 

Id 값을 자동으로 생성해주는 옵션인데,

setId (1L) 를 해주고 있으니 문제가 발생하나? 라는 생각을 했습니다.

그래서 멤버테스트 클래스를 만들어 해당 클래스에 대한 객체를 만들도록 하였습니다.

하지만 이렇게 해도 해결되지 않았습니다.

 

 

 

 

해결 시도 2.

em.persist(member);

영속성 컨텍스트에 저장할 때 문제가 발생한다고 합니다.

그러면 왜 이때 문제가 발생할까요 ??

에러 로그를 따라가보겠습니다. 

 

at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl$TransactionDriverControlImpl.markRollbackOnly(JdbcResourceLocalTransactionCoordinatorImpl.java:324)

@Override
public TransactionStatus getStatus() {
	return rollbackOnly ? TransactionStatus.MARKED_ROLLBACK : jdbcResourceTransaction.getStatus();
}

@Override
public void markRollbackOnly() {
	if ( getStatus() != TransactionStatus.ROLLED_BACK ) {
		if ( log.isDebugEnabled() ) {
			log.debug(
					"JDBC transaction marked for rollback-only (exception provided for stack trace)",
			new Exception( "exception just for purpose of providing stack trace" )
			);
		}
		rollbackOnly = true;
	}
}

 

MARKED_ROLLBACK : Commit이 실패할 때 나는 오류입니다.

 

그러면 테스트 코드가 아닌 메인 메소드에서 실행해서 확인해보겠습니다.

java.lang.IllegalArgumentException: Unknown entity: xik.ShoppingMall.Domain.Member

Entity를 못찾고 있는것으로 보입니다.

 

 

 

[ Entity 위치를 못찾는 해결방법 ]

 

persistence.xml 파일에 엔티티가 위치하는 코드를 추가적으로 입력해줍니다.

이때, 테스트 코드에 임시로 적어놓은 클래스 파일을 지우고 원래 엔티티 클래스에 대한 엔티티의 경로를 설정하였습니다.

<class>xik.ShoppingMall.Domain.Member</class>

에러 없이 정상 출력됨을 확인할 수 있습니다.

 

 

 

'JPA(MySQL,H2Database)' 카테고리의 다른 글

임베디드 값 타입  (0) 2022.08.08
SQL 내장함수  (0) 2022.08.05
JPQL 와 JPQL의 테스트 코드 작성  (0) 2022.07.29
EntityManager  (0) 2022.07.29
연관 관계 매핑  (0) 2022.07.15

+ Recent posts