묻고 답해요
161만명의 커뮤니티!! 함께 토론해봐요.
인프런 TOP Writers
-
미해결실전! Querydsl
InitMember.class의 패키지 위치 질문있습니다.
영한님 안녕하세요. 덕분에 점점 JPA가 재밋어지고 최적화에 재미를 들이고 있습니다. 강의 중에 데이터를 초기화 하는 클래스를 main 패키지 하단에 두셧는데요. 보통 저런 데이터 초기화 클래스를 test 패키지에 안두고 main에 두어도 상관 없는지 궁금합니다. 실무에선 보통 저런 초기화 데이터를 실제 어떻게 만들고 관리하는지도 궁금합니다. 감사합니다.
-
미해결실전! 스프링 데이터 JPA
PK값을 이미가지고 있는 Entity를 DB에 저장하는 방법 문의
안녕하세요. Jpa린이 입니다. 좋은 강의 감사합니다. MSA형태로 Application을 구성하려고 합니다. 아래와 같은 경우에 2번DB에 save할때 Jpa가 내부적으로 pk를 가지고 먼저 select하는것 같은데, 2번DB에는 당연히 해당데이터가 없으니 에러가 발생하는 군요. PK 값을 가지고 있지만 select없이 바로 저장하게 할 수 있을까요? --아래-- 1번DB와 2번DB의 위치는 다르지만, 테이블구조와 데이터는 동일해야합니다. Eqp1Tr과 Eqp1TrDet는 1:N 구조입니다. 1번 App - 1번 DB에 저장 -> Kafka Publish (ID포함) 2번 App - Kafka Consume (ID포함) -> 2번 DB에 저장 <2번 App Consumer 와 Service 코드> @KafkaListener(topics = "${app.topic.name}", groupId = "${spring.kafka.consumer.group-id}")public void receiveMessage(@Payload Eqp1Tr eqp1Tr, @Header(KafkaHeaders.RECEIVED_TOPIC) String topic, @Header(KafkaHeaders.RECEIVED_PARTITION_ID) Integer partition, @Header(KafkaHeaders.OFFSET) Long offset) { log.info("Received message: data = {}, topic = {}, partition = {}, offset = {}", eqp1Tr, topic, partition, offset); eqp1TrService.createTr(eqp1Tr);} public Eqp1Tr createTr(Eqp1Tr eqp1Tr) { log.info("eqp1Tr : {} {}", eqp1Tr.getEqp1TrDets(), eqp1Tr.getName()); List<Eqp1TrDet> eqp1TrDets = eqp1Tr.getEqp1TrDets().stream() .collect(Collectors.toList()); // Save at Eqp1Tr, Eqp1TrDet trRepository.save(eqp1Tr); trDetRepository.saveAll(eqp1TrDets); return eqp1Tr;} <Error> ... Hibernate: select eqp1trdet0_.id as id1_1_0_, eqp1trdet0_.createdBy as createdb2_1_0_, eqp1trdet0_.createdDate as createdd3_1_0_, eqp1trdet0_.col1 as col4_1_0_, eqp1trdet0_.col2 as col5_1_0_, eqp1trdet0_.tr_id as tr_id6_1_0_ from Eqp1TrDet eqp1trdet0_ where eqp1trdet0_.id=? 2021-04-24 00:05:45.319 TRACE 19496 --- [ntainer#0-0-C-1] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [BIGINT] - [2] 2021-04-24 00:06:25.719 INFO 19496 --- [ntainer#0-0-C-1] o.a.k.clients.consumer.KafkaConsumer : [Consumer clientId=consumer-tr-consumer-service-1, groupId=tr-consumer-service] Seeking to offset 3 for partition eqp.tr.ic.eqp1-0 2021-04-24 00:06:27.013 ERROR 19496 --- [ntainer#0-0-C-1] essageListenerContainer$ListenerConsumer : Error handler threw an exception org.springframework.kafka.KafkaException: Seek to current after exception; nested exception is org.springframework.kafka.listener.ListenerExecutionFailedException: Listener method 'public void me.kalpha.trconsumerservice.trmart.service.Eqp1TrConsumerService.receiveMessage(me.kalpha.trconsumerservice.trmart.entity.Eqp1Tr,java.lang.String,java.lang.Integer,java.lang.Long)' threw exception; nested exception is org.springframework.orm.jpa.JpaObjectRetrievalFailureException: Unable to find me.kalpha.trconsumerservice.trmart.entity.Eqp1TrDet with id 2; nested exception is javax.persistence.EntityNotFoundException: Unable to find me.kalpha.trconsumerservice.trmart.entity.Eqp1TrDet with id 2; nested exception is org.springframework.orm.jpa.JpaObjectRetrievalFailureException: Unable to find me.kalpha.trconsumerservice.trmart.entity.Eqp1TrDet with id 2; nested exception is javax.persistence.EntityNotFoundException: Unable to find me.kalpha.trconsumerservice.trmart.entity.Eqp1TrDet with id 2 ...
-
미해결실전! 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발
양방향 연관관계에서의 편의 메서드
강의중, 연관관계 편의 메서드는 연관관계의 주인으로 설정된 엔티티에서 형성하는 것이 좋다고 하셨는데 Order 엔티티에 //연관관계 메서드public void setMember(Member member) { this.member = member; member.getOrders().add(this);}public void addOrderItem(OrderItem orderItem) { orderItems.add(orderItem); orderItem.setOrder(this);}public void setDelivery(Delivery delivery) { this.delivery = delivery; delivery.setOrder(this);} 이렇게 메서드를 구성했습니다. 그러나 Order - OrderItem 두 엔티티가 갖는 연관관계에서 주인은 OrderItem입니다. 그렇다면 Order에 .addOrderItem()을 없애고 OrderItem 엔티티에 public void setOrder(Order order){ this.order = order; order.getOrderItems().add(this);} 위와 같은 편의 메소드를 추가하는것이 맞지 않을까 질문합니다!
-
해결됨실전! 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발
개인 프로젝트를 위해 도움 요청합니다!
안녕하세요. 비전공자로써 국비지원 학원을 수료한 취준생입니다. 학원에서 만들었던 프로젝트가 포트폴리오로 내세우기에는 너무나도 부족해서 선생님의 강의를 들으며 프로젝트를 새로 만들 계획입니다. 프로젝트를 만드는 과정에서 무척 궁금한 부분이 생겼는데 바로 '이미지 첨부'에 관한 것입니다. 학원에서 프로젝트를 만들 때는 모르는 부분이 있으면 구글링하고 이것저것 헤딩해보면서 하다보니 그때 사용했던 방법은 Multipart를 통해 이미지를 프로젝트 파일 내에 있는 폴더에 저장하는 방식을 사용했습니다. 하지만 돌이켜 생각해보니 이미지를 많이 사용하는 사이트를 만든다고 했을 때, '프로젝트 파일의 용량이 너무 불어나면 안 좋지 않을까?' 하는 생각이 들었고, 또한, 기왕에 프로젝트 제대로 만들어 볼 거 AWS Free tier를 활용해서 배포까지 해야 취업에 유리하지 않을까 싶어서 배포단계까지 가는 것을 목표로 하고 있는데요. 그렇다고 했을 때, 배포 중인 애플리케이션의 이미지 첨부 기능에 에러사항이 있지는 않을까, 하는 걱정이 앞섭니다. (아직 애플리케션을 배포해 본 경험은 없습니다...) 사실, 구글 클라우드 스토리지라는 것이 있다기에 연동은 해봤지만 제대로 활용해보지는 못했습니다. 필요하다고 하면 다시 제대로 배워 볼 생각입니다. 이하 각설하고 제 질문은, 1. 실무에서 이미지 첨부 기능을 주로 어떤 스토리지 방식에 의존하는지 2. 작은 규모의 프로젝트이지만 이미지가 약 100장(+이미지 첨부 게시판) 정도일 때 어떤 스토리지 방식을 채택하는 것이 좋을지 입니다. 이게 참... 강의 주제에 벗어나는 질문이지만 너무 궁금한 부분이고, 비전공자이다보니 주변에 실무 종사자 분들이 없어서 실례를 무릅쓰고 이런 질문을 올려보게 되었습니다. 혹시라도 다른 실무 종사자 분들의 조언이 또 있다면 감사히 받겠습니다.
-
해결됨자바 ORM 표준 JPA 프로그래밍 - 기본편
@SequenceGenerator 를 통한 시퀀스 생성시 에러
안녕하세요 선생님, 복습겸 다시 보고 있는데 뭔가 이해가 안되는 현상이 생겨서 질문드립니다. 제가 처음에는 선생님이 하는 방식대로 아래처럼 @SequenceGenerator를 사용했습니다. @SequenceGenerator( name = "MEMBER_SEQ_GENERATOR", sequenceName = "MEMBER_SEQ", initialValue = 1, allocationSize = 1 ) 그러고 나서 JpaMain으로 테스트를 해서 시퀀스 생성 + insert 되는 거까지 확은을 했습니다. 그런데 initialValue와 allocationSize는 default 값이 각각 1, 50 이라길래, 아래처럼 코드를 수정하고 바로 JpaMain 을 다시 실행해봤습니다. @SequenceGenerator( name = "MEMBER_SEQ_GENERATOR", sequenceName = "MEMBER_SEQ" ) 그랬더니... "The increment size of the [MEMBER_SEQ] sequence is set to [50] in the entity mapping while the associated database sequence increment size is [1]" 이라는 에러가 터집니다. 심지어 DDL 조차도 로그에 찍히지 않습니다. 원래라면 persistence.xml 에서 hibernate.hbm2ddl.auto 를 create 로 주었으니, 기존 시퀀스는 다 drop 하고 다시 생성하는 DDL 이 보이면서 동작할 텐데, 그러지도 않네요. 어디서부터 잘못된건지 감이 안 잡힙니다. 대체 왜 이러는 걸까요?
-
미해결실전! 스프링 데이터 JPA
Unit Test 관련 질문입니다.
안녕하세요 영한님 ! 어제 h2 관련해서 질문을 드렸었는데요 좋은 답변 감사했습니다! 다름이 아니라 service unit test 를 하는 과정에서 repository 의 Pageable 을 mock 으로 주입해야 하는 상황이 왔습니다. 현재 junit 5 의 bdd 를 이용해서 테스트를 하고 있는데요, junit5의 bdd 역시 영한님은 잘 아시겠지만 given()을 이용해서 JpaRepository.findAll(Pageable pageable) 이 호출되면 willReturn() 으로 반환되는 반환 값을 가짜로 주입해야 하는 상황입니다. 하지만 여기서 어떻게 Page<Entity> pagedEntitied 를 주입해야 하는지 몰라 진행이 힘들더라구요 ㅠㅠ 수업의 방향과 상관 없는 내용이라 실례임을 인지하고 있지만 어떻게 해결해야 할지 도통 갈피를 못 잡아서 질문드려 봅니다.. import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; class MemberServiceTest { private MemberService memberService; private final MemberRepository memberRepository = mock(MemberRepository.class); @BeforeEach void setUp() { ModelMapper modelMapper = new ModelMapper(); modelMapper.getConfiguration() .setFieldAccessLevel(Configuration.AccessLevel.PRIVATE) .setFieldMatchingEnabled(true); memberService = new MemberService(memberRepository, modelMapper); // 문제의 willReturn 반환 타입.. 현재는 null을 넣어놨습니다. given(memberRepository.findAll(any(PageRequest.class))).willReturn(null); } @Test @DisplayName("모든 사용자 조회") void getMembers() { // when PageRequest pageRequest = PageRequest.of(0, 5); // then Page<MemberResponseData> pagedData = memberService.getMembers(pageRequest); assertEquals(pagedData.getSize(), 5); } } @Entity @Getter @Builder @AllArgsConstructor @NoArgsConstructor public class Member extends BaseEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String email; private String password; private String nickname; public static MemberResponseData fromEntity(Member member) { return MemberResponseData.builder() .email(member.getEmail()) .nickname(member.getNickname()) .build(); } } // Page<MemberResponseData>에 해당하는 MemberResponseData(DTO) @Getter @Builder @AllArgsConstructor @NoArgsConstructor public class MemberResponseData { private String email; private String nickname; } // MemberService @Service public class MemberService { private final MemberRepository memberRepository; private final ModelMapper modelMapper; public MemberService(MemberRepository memberRepository, ModelMapper modelMapper) { this.memberRepository = memberRepository; this.modelMapper = modelMapper; } /** * 모든 회원을 조회한다. * * @param pageable : 페이징 정보 * @return 모든 회원 */ public Page<MemberResponseData> getMembers(Pageable pageable) { return memberRepository.findAll(pageable).map(Member::fromEntity); } }
-
미해결자바 ORM 표준 JPA 프로그래밍 - 기본편
값 타입 컬렉션에서 일대다 매핑으로 바꾼 뒤!
수정을 하고 싶을 때, 값 타입 컬렉션 경우에는 .remove()와 .add()를 사용하여 아래와 같이 했습니다. findMember.getAddressHistory().remove(new AddressEntity("oldCity1", "street1", "10000"));findMember.getAddressHistory().add(new AddressEntity("newCity1", "street1", "10000")); 그렇다면, AddressEntity를 사용하여 엔티티의 경우에는 아래와 같이 사용하면 되나요? AddressEntity addressEntity = em.find(AddressEntity.class, 2L);Address address = new Address("newCity1", "street1", "10000");addressEntity.setAddress(address); setAddress와 같이 커밋시점에 변경 감지로 수정을 할 수 있다고 생각했습니다.
-
미해결실전! 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발
회원가입 제출 이후 whiteLabel
안녕하세요 강사님. 강의 보며 많이 배우고 있습니다. 강의 내용대로 쭉 따라가며 진행하고 있는데 회원가입 부분에서 회원가입 제출 누르면 홈으로 리다이렉트가 안되고 403에러와 화이트라벨 페이지로 이동하게 됩니다. create 메서드에 로그를 찍어도 안찍히는걸 보니 해당 post 매핑이 잘못되었나 하여 준비해주신 자료에 있는 소스를 복사해 똑같이 붙여넣기해도 마찬가지네요... 혹시 원인이 무엇일까요..ㅠㅠ
-
미해결자바 ORM 표준 JPA 프로그래밍 - 기본편
NoSQL과 JPA 관련 질문입니다.
안녕하세요 영한님 매일매일 강의 정말 너무너무 잘 듣고 있습니다. 수업과 살짝 관계가 없을 수도 있는 질문일수도 있지만 궁금해서 여쭤봅니다 ... ^^ 수업에서 사용하시는 케이스는 전부 관계형 데이터 베이스를 사용하는 경우, JPA를 어떻게 잘 사용할지에 대한 내용인 것 같은데 혹시 NoSQL 계열의 데이터베이스를 사용할 경우에는 JPA를 사용하는 것이 비효율적인지 또는 사용을 못하는건지 같은 관점에서 궁금합니다 (찾아보니까 몽고DB랑 같이 사용하는 케이스가 있는 것 같긴하던데..잘 감이 안오더라구요) 답변 주시면 감사하겠습니다!!
-
미해결스프링 시큐리티
세션, 쿠키 생성되는 조건
안녕하세요, 강의 잘 보고 있습니다. 세션 고정 보호에 대해서 질문이 있습니다. 테스트를 하실때 세션 고정 공격을 위해서 sessionFixation()을 none으로 변경 한 후에 공격자가 웹서버 접속만으로 쿠키에 JSESSIONID가 할당되는 것을 확인했습니다. 제가 기존에 알고 있던 지식은 로그인을 통해 인증 절차를 받아야 사용자 세션이 생기고 세션ID를 클라이언트 쿠키에 담아주는 것으로 알고 있었는데 테스트에서는 인증을 하지 않았는데 어떻게 세션ID가 있는건가요? 아니면 제가 잘 못 알고 있는건지.. 감사합니다!
-
해결됨실전! 스프링 부트와 JPA 활용2 - API 개발과 성능 최적화
회원가입 시 비밀번호 입력에 대한 질문
안녕하세요 영한님 질문이 있습니다. 회원가입을 할 때 비밀번호를 입력하는 예제를 만들어 보려고 합니다. 회원가입 시에 비밀번호를 입력하고 비밀번호 확인을 입력하여 둘이 같으면 회원가입을 요청하도록 로직을 짜려고 하는데, 이 로직을 어느 계층에 넣어야 할지 고민이 됩니다. 비밀번호 확인이라는 칼럼을 멤버 클래스에 추가하지 않고 클라이언트에서 넘겨준 데이터를 확인만 하고 버리려고 하는데 그러면 MemberApiController에서 직접 처리를 해야하는 걸까요? 감사합니다.
-
해결됨스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술
Optional.ofNullable
"회원 도메인과 리포지토리 만들기" 중 findById를 구현하실때 Null값 때문에 Optional.ofNullable 을 쓰셨다고 하셨는데 그냥 if문을 써서 Null이 아닐때 id를 return하고 아닐때 Null을 return하는 방식의 구현을 하면 안되는 건가요? 코드가 좀 더 지저분해져서 그런건가요? 제가 java 쪽은 아직 study를 하지 않아서 질문 설명에 조금 부족한 부분이 있네요.
-
미해결실전! Querydsl
queryDsl에서는 DATE 함수 지원 하지 않나요?
SELECT * FROM member AS m INNER JOIN history AS h ON (m.id = h.id) WHERE DATE(h.created_at) = DATE_ADD(CURDATE(), INTERVAL -1 DAY) 해당 쿼리문을 만들어야 하는데요... 지금 까지 제가 해본 것 입니다!!!! 함수 () { LocalDate minusDays = LocalDate.now().minusDays(1); DateTemplate<LocalDate> localDateDateTemplate = Expressions.dateTemplate(LocalDate.class, "DATE({0},'%Y-%m-%d')", subQEmrConsultationHistory.createdAt, ConstantImpl.create("%Y-%m-%d")); BooleanBuilder booleanBuilder = new BooleanBuilder(); booleanBuilder.and(localDateDateTemplate.eq(minusDays)); return queryFactory .from(qhistory) .join(qhistory.member) .where(booleanBuilder) } 이렇게 한번 해보았는데 당연히 안되었네요... 이방법이 아닌거 같은데요... 이 경우 어쩔수 없이 Mybatis로 가야하나요....
-
해결됨자바 ORM 표준 JPA 프로그래밍 - 기본편
고급매핑 중 MappedSuperclass 를 질문드립니다.
- 배경 1. @MappedSuperclass 예시 코드에는 create, update 정보만 있었지만, 저는 deleted (소프트 딜리트 플래그) 도 넣고 싶습니다. 2. deleted 는 검색이 필요하기에 객체 관계가 필요하고, 그렇기에 @MappedSuperclass 로 묶을 수 없다는 것을 인지했습니다. 3. 위 조건을 만족하기 위해서 @Entity 를 주면 해결이 되지만, 이 경우 매핑 정보 전달만을 위한 클래스가 테이블로 생성되어 버럽니다. - 질문 1. 현재는 뾰족한 방법을 찾지 못해 모든 테이블에 deleted 를 따로 만들어주고 있습니다.공통적으로 관리할 수 있는 방법이 있을까요?
-
미해결실전! Querydsl
영속성 컨텍스트 쿼리 질문 드립니다.
안녕하세요, 강의 잘 듣고 있습니다. 질문을 드리고 싶어서요. 영속성 컨텍스트에 엔티티A가 있다고 가정해 볼게요. EntityManager의 em.find()를 통해 엔티티A를 조회하면, EntityManager는 영속성 컨텍스트에 있는 엔티티A를 반환하고, 실제 DB에 select 쿼리는 날리지 않는걸로 알고 있어요. 그런데 queryFactory는 생성자 인자로 EntityManager를 받고 있음에도, 왜 해당 강의 7분 21초에서는 select 쿼리가 나가나요?? queryDSL은 EntityManager 차원의 find 실행이 아니고, JPQL 차원의 쿼리 실행이라, 무조건 쿼리가 나가는 건가요??
-
미해결실전! 스프링 데이터 JPA
스냅샷이란..?
선생님 강의 정말 잘 듣고있습니다!! 선생님이 QueryHint에서 readOnly를 켜주면 " 스냅샷을 안찍죠 . . . " 이러셨는데 이떄 스냅샷이 JPA 영속성컨텍스트(1차캐시) 를 의미하는 건가요?
-
해결됨실전! Querydsl
DTO .as 질문
안녕하세요 영한님! 강의 영상 5:36 경에 new QMemberTeamDto 인자의 member.id처럼 필드명이 맞지 않는거를 as로 바꿔주셨는데 @QueryProjection 방법 특성상 생성자로 Dto를 만들어 select하는거라 타입만 맞으면 되지 않나요?? 만약 일부로 as를 사용하신거면 dto 안에 이러한 필드가 있다고 좀 더 명시적으로 나타내기 위한 장치인가요?
-
미해결자바 ORM 표준 JPA 프로그래밍 - 기본편
JPA Count 쿼리 Return 값
안녕하세요 JPA 사용하다 궁금한게 생겨 질문드립니다.현재 강의를 듣고 Spring Data JPA를 사용중인데요공식 문서를 보니 count query 시 return 값이 Long(Wrapper Type)으로 반환된다 합니다.혹시나 싶어서 jpa에서 count 후 return class type을 보니 java.lang.Long으로 반환되네요....궁금한건 count는 sql도 그렇고 jpa 문서상에도 null인 경우가 없는것으로 보이는데요JPA에서 Count 쿼리 호출시 primitive가 아닌 Wrapper로 반환하는 이유가 있을까요??엔티티 ID처럼 nullable로 인한 이득이 있는것도 아닌데, 결과값을 박싱해서 내려주는게 어떤 이득이 있는지 모르겠습니다.마지막으로 Long으로 반환되는 결과값을 아래처럼 long으로 unboxing 해서 사용해도 성능이나 여러가지면에서 손해보는면이 있을까요?@Query("select count(m) from ....... wherer ....")long countMember();
-
미해결자바 ORM 표준 JPA 프로그래밍 - 기본편
의존성에 관한 질문
영한님의 JPA 책으로 공부를 하다가 인프런 강의도 3개나 구매를 하며 현업에서도 JPA를 잘 사용하고 있습니다. 퀄리티 좋은 강의 감사합니다! Mabatis 로만 작업하다 JPA를 사용하게 되었으니 이제 나도 객체지향적인 개발을 제대로 해서 우아한 형제들 개발자로 이직해야지!!! 하며 참고 자료를 찾아서 스터디 하던 중 youtube에서 [우아한 테크세미나 조용호님의 우아한객체지향] 발표 내용을 보게 되었습니다. 지금까지 JPA를 사용하며 연관관계를 객체로 맺었고 당연히 이것이 JPA의 최대 강점이라고 생각하고 있었는데 조용호님의 설명에서는 연관관계를 객체가 아닌 객체의 id 값으로만 작성하는걸 추천해주시더라구요. 우아한테크세미나 조용호 유투브 강의와 조용호님의 깃헙 소스를 보면서 객체 연관관계에 대해 추천해주신거에 대해 생각을 해보았습니다. 예를 들어 Order 엔티티와 OrderLineItems 엔티티는 Order 라는 도메인안에서 언제나 함께 하는 관계이기에 객체로 연관 관계를 맺고 Delivery, Order, Shop은 서로 연관은 있지만 Order와 OrderLineItems 만큼 함께 하는 애들(?)이 아니기때문에 객체가 아닌 id 값으로 연관관계를 맺는거 같은데. 위의 내용은 다른분의 의견이고 나름 저의 JPA 스승님(^^;)이신 김영한님께서는 이런 연관관계에 대해서 어떻게 생각하시고 어떤걸 더 추천해주시는지 조언을 구하고 싶습니다. 질문이 너무 길어 죄송합니다^^;
-
해결됨실전! 스프링 데이터 JPA
@Transaction에 대한 궁금중
안녕하세요 영한님. 강의를 들으면서 문득 @Transaction의 위치와 중첩에 대해서 궁금중이 들어서 제 나름대로 몇가지 테스트를 해봤습니다. 첫 번째로, 리포지토리 레이어에 트랜잭션이 적용되어 있고 해당 리포지토리의 메서드를 사용하는 서비스 레이어에도 트랜잭션이 적용되어 있다면 flush는 두 레이어 중 어느 시점에 발생할까 궁금했습니다. 그래서 테스트를 해본 결과 최종적으로 서비스 레이어의 트랜잭션이 끝나는 순간에 모든 쿼리가 적용되는 것을 볼 수 있었습니다. 두 번째로, 위와 동일한 상황에 서비스 레이어의 트랜잭션 옵션만 readOnly라면 리포지토리의 save 메서드는 실제 db에 저장을 할까 궁금해서 테스트해봤습니다. 결과는 물론 서비스 레이어 트랜잭션이 우선이기에 insert 쿼리는 발생하지 않았습니다. 두 테스트를 통해서 트랜잭션 우선권은 좀 더 넓은 범위(?), 즉 요청과 응답에 가까운 레이어의 트랜잭션을 따라간다고 이해가 됩니다. 그렇다면 제 생각에는 컨트롤러에 트랜잭션을 적용한다면, OSIV를 사용하지 않아도 영속성 컨텍스트가 화면을 띄울 때까지 유지될 것 같은데 막상 해보면 no Session 에러를 뿜내요.. 컨트롤러에서 html 파일을 렌더링해서 반환하는게 아니라 컨트롤러에서 정적 파일로 일단 반환하고 트랜잭션이 종료된 후에 따로 렌더링하는 작업을 거쳐서 그런걸까요? 쓰다보니 말이 길어져서.. 죄송합니다 ㅎㅎ..