@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);
}
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
* 데이터베이스의 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() {
}
}
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;
}
}