join을 해서 가지고 오면 이렇게 데이터가 중복이 되면서 2개로 뻥튀기가 된다고 설명을 해주셨는데요. 윗줄과 아랫줄이 완전히 동일한 것은 아닌데 이것을 왜 중복으로 보고 뻥튀기라 하는지 이해가 잘 가지 않습니다! 제가 나름 이해하기로는 예를 들어 위에 예시로 든 Order가 OrderA 라고 했을 때, 지금 찾고 싶은건 OrderA 라는 Order 하나인데 그 안에 OrderItem이 각각 서로 다른게 2개 들어있어서 OrderA-Boo1 , OrderA-Book2 이렇게 OrderA가 두 개가 조회돼서 뻥튀기라고 보는걸까요? 그런데 이러면 완전히 중복은 아닌거 같기도 하고.. 어떻게 이해하는 것이 좋을지 궁금합니다!
안녕하세요! 호돌맨님 해당 강의를 보면서 토이프로젝트를 진행 중 질문이 생겨서 글을 씁니다. 간단한 Quiz 맞추는 프로젝트를 만들고 있는데 Quiz 엔티티에 JPA 싱글테이블 전략 상속관계로 주관식 , 객관식 , 서술형으로 자식엔티티를 생성했습니다. 자식엔티티를 생성 하고 보니 기존 Quiz의 Request , Edit , Response 클래스를 어떻게 변경해야하는지 답이 안 생겨서 이렇게 질문 드립니다. 🙇🏻♂️🙇🏻♂️🙇🏻♂️🙇🏻♂️ 자식 엔티티 각각 Request , Edit , Response 클래스 생성 -> 컨트롤러 파라미터에서 어떻게 받지? 컨트롤러 메서드로 분리시켜야하나? -> 그럼 프론트엔드에서는 더더욱 복잡해진다. 기존 Quiz Request, Edit , Response 의 각각 자식 엔티티의 필드 추가 -> 컨트롤러 파라미터에거 받을 수 있고, 컨트롤러 메서드가 분리 필요 X -> 컨트롤러에서 데이터 검증은 어떻게 진행되는거지? 객관식인 경우 보기 필드가 필수값일텐데 보기 멤버 변수에 @NotBlank를 추가한다면 주관식이나 서술형 문제유형일 경우엔 오류가 나타나는 상황 코드가 추가된 노션 링크 전달 드리겠습니다. https://devysk.notion.site/Entity-request-edit-response-c7433eccd6e24ee486a60bd59013a2a8
@Entity @Getter @Setter public class Member { @Id @GeneratedValue @Column(name = "member_id") private Long id; private String name; @Embedded private Address address; @OneToMany(mappedBy = "member") private List<Order> orders = new ArrayList<>(); // Best Practice } @Getter @Setter public class MemberForm { @NotEmpty(message = "회원 이름은 필수 입니다.") private String name; private String city; private String street; private String zipcode; } @GetMapping("/members/new") public String createForm(Model model) { model.addAttribute("memberForm", new MemberForm()); return "members/createMemberForm"; } @PostMapping("/members/new") public String create(@Valid MemberForm form, BindingResult result) { if (result.hasErrors()) { return "members/createMemberForm"; } Address address = new Address(form.getCity(), form.getStreet(), form.getZipcode()); Member member = new Member(); member.setName(form.getName()); member.setAddress(address); memberService.join(member); return "redirect:/home"; } [1번째 코드블럭] Address를 따로 분리하면, [2번째 코드블럭] MemberForm을 만들어야만 하고, [3번째 코드블럭] createMemberForm.html 에 보낼 때 MemberForm객체를 보내어, [4번째 코드블럭]사용자에게 입력받은 정보를 다시 MemberForm에 담아 가져온 후, 값을 하나씩 꺼내어 Member객체에 담아 join 시키는데, 저의 관점에서는 이점이 보이지 않는다고 생각이 듭니다. [질문1] Address를 따로 객체로 만들어서 빼 둔 이유가 궁금합니다. [질문2] 그렇게 따로 분리한데는 그에 따른 이득이 있을텐데, 그 경우도 궁금하구요. [질문3] 주소정보(3개-city,street,zipcode)가 아닌 훨씬 많은 경우엔 더 편해지나요? 그런 예시가 딱 떠오르지가 않아서 혹시 실무에서의 예시가 있으시면 알려주세요. [질문4] 추가적으로 계좌정보 (은행, 계좌번호, 예금주)를 입력받아 MEMBER를 만들 경우에 Address를 만든 것 처럼 동일하게 만들면 되나요?
[질문 내용] 안녕하십니까. 김영한 강사님의 모든 실전 강의를 다 들었습니다. JPA 관련 고퀄리티의 강의 정말 감사합니다. 저희 회사에서 이번 프로젝트에 JPA를 적극 도입하고자 하는데 기존 수업을 들었던 환경과 조금 달라 이렇게 문의를 하고자 합니다. 저희 회사에서 여러 DB (최소 4개 이상)를 한 스프링 백엔드에서 다뤄야하는 상황이 되었습니다. 이 상황에 JPA로 DB를 각각 연결하여 사용하는 것이 가능할까요? 또한 성능적으로도 문제가 없을 지 궁금합니다.
[질문 템플릿] 1. 강의 내용과 관련된 질문인가요? (예) 2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (아니오) 3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예) [질문 내용] 안녕하세요. 좋은 강의 덕분에 양질의 지식을 비교적 수월하게 습득하고 있습니다. 양방향 연관관계에서 편의 메서드는 어디에 위치하는게 좋은지 여쭙고자 합니다. (비슷한 질문들을 봤지만 이러한 경우에 어떤게 가장 좋은 선택일지 궁금하여 질문드립니다.) 비슷한 질문1 : https://www.inflearn.com/questions/16308 비슷한 질문2 : https://www.inflearn.com/questions/99330 현재상태 위와 같이 일대다(1:N) 관계가 있고 Team 을 애그리거트 루트로 잡았습니다. 도메인 룰은 다음과 같습니다. Team 은 반드시 이름이 있어야한다. Player 는 반드시 이름과 나이가 있어야한다. Player 는 반드시 Team 에 소속되어야하며, 하나의 Team 에만 소속될 수 있다. player 의 Team 은 변경될 수 없다. 엔티티 코드 입니다. (연관관계 편의 메서드를 애그리거트 루트쪽에 두었습니다.) (두 엔티티는 동일한 패키지에 있습니다. package com.example.jpa.module.team.domain;) @Entity public class Team { @Id @GeneratedValue private Long teamNo; private String name; @OneToMany(mappedBy = "team", cascade = CascadeType.ALL) private List<Player> players = new ArrayList<>(); //생성자 public Team(String name) { this.name = name; } //연관관계 편의 메서드 public void addPlayer(Player player) { player.setTeam(this); this.players.add(player); } } @Entity public class Player { @Id @GeneratedValue private Long playerNo; private String name; private Integer age; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "team_no") private Team team; //생성자 public Player(String name, int age) { this.name = name; this.age = age; } void setTeam(Team team) { this.team = team; } } service 계층 코드입니다. Team team = new Team("team1"); Player player = new Player("kim", 24); team.addPlayer(player); teamRepository.save(team); Team 이 Player 를 완전히 소유하는 관계이기 때문에 애그리거트 루트를 Team 으로 잡았고 연관관계 메서드 또한 Team 에 있는게 자연스러워 보입니다. (예시가 Team-Player라 결합도가 좀 떨어지는것 처럼 느껴지는데 주문-주문항목 이나 게시글-첨부파일 같은 관계로 봐주시면 감사하겠습니다.) 문제점 연관관계 편의 메서드 addPlayer(..) 가 호출되기 전까지 Player의 도메인 룰이 깨진 상태가 됩니다. 도메인 룰 3번 "Player 는 반드시 Team에 소속되어야 한다" 를 위반하게 됩니다. Player의 setTeam(..) 메서드를 클래스 외부에 공개해야 합니다. 같은 패키지라 public 으로 공개하진 않았지만 해당 메서드로 인해 도메인 룰 4번 "player 의 Team 은 변경될 수 없다." 를 위반할 여지가 생겼습니다. setTeam(..) 메서드에 this.team != null 인 경우 예외를 발생시켜 도메인 룰을 지킬 수 있지만 좋은 방법인지 잘 모르겠습니다.. 시도한 방법 Player의 생성자에 Team을 받도록 합니다. public Player(Team team, String name, int age) { this.team = team; this.name = name; this.age = age; } 근데 이렇게 하니 그냥 연관관계 편의 메서드를 Player에 두는게 나은것 같아 생성될때 연관관계를 맺도록 정적 팩토리 메서드를 작성했습니다. public static Player createAndLink(Team team, String name, int age) { Player player = new Player(team, name, age); //생성자 private 으로 변경 team.getPlayers().add(player); //반대편 연관관계 설정 return player; } 일단 도메인 룰은 모두 만족하는 듯 보이나 service 계층 코드가 뭔가 부자연스럽습니다. Team team = new Team("team1"); Player player = Player.createAndLink(team, "kim", 24); teamRepository.save(team); 위 코드를 보면 player를 생성하고 사용하지 않는것 처럼 보여 불필요한 코드로 인식됩니다. (인텔리제이에서도 player가 미사용 중이라고 나옵니다.) 혹시 위와 같은 상황에서 어떻게 해결하는게 가장 좋은 방법일까요? (긴 글 읽어주셔서 감사합니다.)
테스트 진행 중 오류가 납니다. 구글링해두 해결을 못하겠네요 ㅠㅠ 어쩌다가 테스트 진행이 됐었는데 시퀀스를 계속 삭제해줘야 됐었습니다. H2 1.4.200 버전맞고 다른 소스들 교재랑 같습니다. 스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술 강의 듣고 기존에 쓰던 H2에서 database만 추가해서 사용중인데 이게 문제가 될까요? java.lang.IllegalStateException: Failed to load ApplicationContext for [WebMergedContextConfiguration@597a7afa testClass = jpabook.jpashop.MemberRepositoryTest, locations = [], classes = [jpabook.jpashop.JpashopApplication], contextInitializerClasses = [], activeProfiles = [], propertySourceLocations = [], propertySourceProperties = ["org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true"], contextCustomizers = [org.springframework.boot.test.autoconfigure.actuate.observability.ObservabilityContextCustomizerFactory$DisableObservabilityContextCustomizer@9da1, org.springframework.boot.test.autoconfigure.properties .PropertyMappingContextCustomizer@0, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@49d904ec, org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@3c419631, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@3e2e18f2, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.web.client.TestRestTemplateContextCustomizer@63355449, org.springframework.boot.test.context.SpringBootTestAnnotation@8c81e9cd], resourceBasePath = "src/main/webapp", contextLoader = org.springframework.boot.test.context.SpringBootContextLoader, parent = null] at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext( DefaultCacheAwareContextLoaderDelegate.java:141 ) at org.springframework.test.context.support .DefaultTestContext.getApplicationContext( DefaultTestContext.java:127 ) at org.springframework.test.context.web.ServletTestExecutionListener.setUpRequestContextIfNecessary( ServletTestExecutionListener.java:192 ) at org.springframework.test.context.web.ServletTestExecutionListener.prepareTestInstance( ServletTestExecutionListener.java:131 ) at org.springframework.test.context.TestContextManager.prepareTestInstance( TestContextManager.java:249 ) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.createTest( SpringJUnit4ClassRunner.java:228 ) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner$1.runReflectiveCall( SpringJUnit4ClassRunner.java:290 ) at org.junit.internal.runners.model.ReflectiveCallable.run ( ReflectiveCallable.java:12 ) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.methodBlock( SpringJUnit4ClassRunner.java:292 ) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild( SpringJUnit4ClassRunner.java:247 ) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild( SpringJUnit4ClassRunner.java:97 ) at org.junit.runners.ParentRunner$4.run( ParentRunner.java:331 ) at org.junit.runners.ParentRunner$1.schedule( ParentRunner.java:79 ) at org.junit.runners.ParentRunner.runChildren( ParentRunner.java:329 ) at org.junit.runners.ParentRunner.access$100( ParentRunner.java:66 ) at org.junit.runners.ParentRunner$2.evaluate( ParentRunner.java:293 ) at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate( RunBeforeTestClassCallbacks.java:61 ) at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate( RunAfterTestClassCallbacks.java:70 ) at org.junit.runners.ParentRunner$3.evaluate( ParentRunner.java:306 ) at org.junit.runners.ParentRunner.run ( ParentRunner.java:413 ) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run ( SpringJUnit4ClassRunner.java:191 ) at org.junit.runner.JUnitCore.run ( JUnitCore.java:137 ) at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs( JUnit4IdeaTestRunner.java:69 ) at com.intellij.rt.junit.IdeaTestRunner$Repeater$1.execute( IdeaTestRunner.java:38 ) at com.intellij.rt.execution.junit.TestsRepeater.repeat( TestsRepeater.java:11 ) at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs( IdeaTestRunner.java:35 ) at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart( JUnitStarter.java:235 ) at com.intellij.rt.junit.JUnitStarter.main( JUnitStarter.java:54 ) Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in class path resource [org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.class]: [PersistenceUnit: default] Unable to build Hibernate SessionFactory; nested exception is org.hibernate.MappingException: Could not instantiate id generator [entity-name=jpabook.jpashop.Member] at org.springframework.beans.factory.support .AbstractAutowireCapableBeanFactory.initializeBean( AbstractAutowireCapableBeanFactory.java:1751 ) at org.springframework.beans.factory.support .AbstractAutowireCapableBeanFactory.doCreateBean( AbstractAutowireCapableBeanFactory.java:599 ) at org.springframework.beans.factory.support .AbstractAutowireCapableBeanFactory.createBean( AbstractAutowireCapableBeanFactory.java:521 ) at org.springframework.beans.factory.support .AbstractBeanFactory.lambda$doGetBean$0( AbstractBeanFactory.java:326 ) at org.springframework.beans.factory.support .DefaultSingletonBeanRegistry.getSingleton( DefaultSingletonBeanRegistry.java:234 ) at org.springframework.beans.factory.support .AbstractBeanFactory.doGetBean( AbstractBeanFactory.java:324 ) at org.springframework.beans.factory.support .AbstractBeanFactory.getBean( AbstractBeanFactory.java:200 ) at org.springframework.context.support .AbstractApplicationContext.getBean( AbstractApplicationContext.java:1130 ) at org.springframework.context.support .AbstractApplicationContext.finishBeanFactoryInitialization( AbstractApplicationContext.java:905 ) at org.springframework.context.support .AbstractApplicationContext.refresh( AbstractApplicationContext.java:584 ) at org.springframework.boot.SpringApplication.refresh( SpringApplication.java:730 ) at org.springframework.boot.SpringApplication.refreshContext( SpringApplication.java:432 ) at org.springframework.boot.SpringApplication.run ( SpringApplication.java:308 ) at org.springframework.boot.test.context.SpringBootContextLoader.lambda$loadContext$3( SpringBootContextLoader.java:137 ) at org.springframework.util.function.ThrowingSupplier.get( ThrowingSupplier.java:59 ) at org.springframework.util.function.ThrowingSupplier.get( ThrowingSupplier.java:47 ) at org.springframework.boot.SpringApplication.withHook( SpringApplication.java:1386 ) at org.springframework.boot.test.context.SpringBootContextLoader$ContextLoaderHook.run( SpringBootContextLoader.java:543 ) at org.springframework.boot.test.context.SpringBootContextLoader.loadContext( SpringBootContextLoader.java:137 ) at org.springframework.boot.test.context.SpringBootContextLoader.loadContext( SpringBootContextLoader.java:108 ) at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContextInternal( DefaultCacheAwareContextLoaderDelegate.java:183 ) at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext( DefaultCacheAwareContextLoaderDelegate.java:117 ) ... 27 more Caused by: jakarta.persistence.PersistenceException: [PersistenceUnit: default] Unable to build Hibernate SessionFactory; nested exception is org.hibernate.MappingException: Could not instantiate id generator [entity-name=jpabook.jpashop.Member] at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.buildNativeEntityManagerFactory( AbstractEntityManagerFactoryBean.java:421 ) at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.afterPropertiesSet( AbstractEntityManagerFactoryBean.java:396 ) at org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.afterPropertiesSet( LocalContainerEntityManagerFactoryBean.java:352 ) at org.springframework.beans.factory.support .AbstractAutowireCapableBeanFactory.invokeInitMethods( AbstractAutowireCapableBeanFactory.java:1797 ) at org.springframework.beans.factory.support .AbstractAutowireCapableBeanFactory.initializeBean( AbstractAutowireCapableBeanFactory.java:1747 ) ... 48 more Caused by: org.hibernate.MappingException: Could not instantiate id generator [entity-name=jpabook.jpashop.Member] at org.hibernate.id .factory.internal.StandardIdentifierGeneratorFactory.createIdentifierGenerator( StandardIdentifierGeneratorFactory.java:222 ) at org.hibernate.mapping.SimpleValue.createIdentifierGenerator( SimpleValue.java:515 ) at org.hibernate.mapping.SimpleValue.createIdentifierGenerator( SimpleValue.java:372 ) at org.hibernate.internal.SessionFactoryImpl.lambda$new$1( SessionFactoryImpl.java:291 ) 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:179 ) at java.base/java.util.HashMap$ValueSpliterator.forEachRemaining( HashMap.java:1779 ) at java.base/java.util.stream.AbstractPipeline.copyInto( AbstractPipeline.java:509 ) at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto( AbstractPipeline.java:499 ) 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:596 ) at org.hibernate.internal.SessionFactoryImpl.<init>( SessionFactoryImpl.java:290 ) at org.hibernate.boot.internal.SessionFactoryBuilderImpl.build ( SessionFactoryBuilderImpl.java:415 ) at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.build ( EntityManagerFactoryBuilderImpl.java:1425 ) at org.springframework.orm.jpa.vendor.SpringHibernateJpaPersistenceProvider.createContainerEntityManagerFactory( SpringHibernateJpaPersistenceProvider.java:66 ) at org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.createNativeEntityManagerFactory( LocalContainerEntityManagerFactoryBean.java:376 ) at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.buildNativeEntityManagerFactory( AbstractEntityManagerFactoryBean.java:409 ) ... 52 more Caused by: org.hibernate.HibernateException: Could not fetch the SequenceInformation from the database at org.hibernate.engine.jdbc.env.internal.ExtractedDatabaseMetaDataImpl.sequenceInformationList( ExtractedDatabaseMetaDataImpl.java:302 ) at org.hibernate.engine.jdbc.env.internal.ExtractedDatabaseMetaDataImpl.getSequenceInformationList( ExtractedDatabaseMetaDataImpl.java:151 ) at org.hibernate.id .enhanced.SequenceStyleGenerator.getSequenceIncrementValue( SequenceStyleGenerator.java:561 ) at org.hibernate.id .enhanced.SequenceStyleGenerator.configure( SequenceStyleGenerator.java:210 ) at org.hibernate.id .factory.internal.StandardIdentifierGeneratorFactory.createIdentifierGenerator( StandardIdentifierGeneratorFactory.java:217 ) ... 70 more Caused by: org.h2.jdbc.JdbcSQLSyntaxErrorException: Column "start_value" not found [42122-214] at org.h2.message.DbException.getJdbcSQLException( DbException.java:502 ) at org.h2.message.DbException.getJdbcSQLException( DbException.java:477 ) at org.h2.message.DbException.get( DbException.java:223 ) at org.h2.message.DbException.get( DbException.java:199 ) at org.h2.jdbc.JdbcResultSet.getColumnIndex( JdbcResultSet.java:3492 ) at org.h2.jdbc.JdbcResultSet.getLong( JdbcResultSet.java:745 ) at com.zaxxer.hikari.pool.HikariProxyResultSet.getLong( HikariProxyResultSet.java ) at org.hibernate.tool.schema.extract.internal.SequenceInformationExtractorLegacyImpl.resultSetStartValueSize( SequenceInformationExtractorLegacyImpl.java:110 ) at org.hibernate.tool.schema.extract.internal.SequenceInformationExtractorLegacyImpl.lambda$extractMetadata$0( SequenceInformationExtractorLegacyImpl.java:54 ) at org.hibernate.tool.schema.extract.spi.ExtractionContext.getQueryResults( ExtractionContext.java:50 ) at org.hibernate.tool.schema.extract.internal.SequenceInformationExtractorLegacyImpl.extractMetadata( SequenceInformationExtractorLegacyImpl.java:39 ) at org.hibernate.engine.jdbc.env.internal.ExtractedDatabaseMetaDataImpl.sequenceInformationList( ExtractedDatabaseMetaDataImpl.java:286 ) ... 74 more
[질문 내용] 안녕하세요. 예전 부터 궁금했던 내용인데, 혹시 나중에 나오지 않을까 해서 미루다가 결국 질문합니다..! public void changeTeam(Team team){ this.team = team; team.getMembers().add(this); } 예전 강의에서는 양방향 연관 관계에서 주인이 관계를 변경할 때 위처럼 Member Entity는 자신의 필드에 새로운 Team을 세팅하고 Team은 Member를 list에 추가하는 식으로만 마무리했던 것으로 기억합니다. 지나가는 말로 영한님께서 기존 Member와 연관 관계에 있던 Team list에서 Member를 remove하는 작업도 해야 하는데, 연습하는 거니까 빼셨다고 하셨던 것 같습니다. 이 부분이 조금 궁금했습니다. 관계가 바뀌기 이전의 기존 Member의 Team list에서 Member를 지워 주는 부분을 어떤식으로 작성해야 할 지 조금 감이 안 잡힙니다..ㅠㅠ 고려할 사항이 너무나도 많다고 해야 할까요.. public void changeTeam(Team team){ this.team.getMembers().remove(this); this.team = team; team.getMembers().add(this); } 위처럼 단순히 remove()를 사용하기에는 몇 가지 고려할 사항들이 있었습니다. 첫 번째 문제는 remove()의 경우 add와 달리 list의 Member들을 필요로 하는 로직이 들어 있기 때문에 DB로부터 Team의 Member list 정보를 불러온 다음에 동작한다는 점입니다. 특정 로직은 Team의 Members를 사용할 일이 없어 굳이 DB와 Entity그래프를 일치시킬 필요가 없는데, remove()로직이 들어가면서 DB를 조회하는 일이 생긴다는 점입니다. 이로 인한 성능상 문제는 정말 미세하겠지만, 뭔가 조금 걸리는 느낌입니다.. 두 번째는 remove()를 할 때에 eqauls()를 사용한다는 점입니다. 단순히 equals()를 오버라이딩하여 구현하면 될 줄 알았지만, 이 부분도 고려할 부분이 생각 보다 많았습니다. 크게 세 가지로 나뉘는 것 같았습니다. pk를 이용한 equals()오버라이딩 pk와 연관관계 필드를 제외한 필드로 eqauls()를 오버라이딩 Business-Id를 이용한 eqauls()오버라이딩 이렇게 세 가지 사항 정도가 고려되는 것 같았습니다. 각각의 장단점이 있어 보였는데, 3번이 제일 괜찮은 방식으로 보였습니다. 1번은 pk가 GenerateValue방식일 경우 Entity가 persist되기 이전에는 pk를 초기화하지 못 해, set과 같은 Collection을 사용할 때 제한이 생긴다는 점이나 NPE발생 가능성 내재, pk값이 null인 객체가 같은 객체로 인식이 되는 위험, 비영속 상태 객체와 영속 상태 객체의 eqauls연산 시 일치 불가 등이 있는 것 같았습니다. 사실 위의 문제점들이 발생할 만한 로직을 실제 작성하게 될 일이 많지는 않을 것 같지만 뭔가 내재된 위험이 많아 보여 패스했습니다. 2번은 pk와 연관 관계 필드를 제외한 모든 필드들이 합쳐서 Unique한 값을 갖지 못할 경우 중복이 발생하는 문제점이 있어 보였습니다. 3번이 제일 적절해 보였지만 Business-Id로 사용할 만한 데이터가 없을 경우 문제가 있을 것 같았습니다. 특정 글에서는 UUID와 같이 Business-Id를 일부로 두기도 한다는 것 같은데 괜찮은 방식인 지는 모르겠습니다. 내용이 길어졌는데 결론은 양방향 연관 관계에서 관계를 바꿔 줄 때 Entity 그래프를 일치시키기 위해서 어떤 형태로 로직을 작성하는 지 궁금하다는 것입니다. 이렇게 보니 제가 너무 이상한 방식으로 접근한 게 아닌가 생각이 듭니다ㅠㅠ 현업에서는 어떤 식으로 구현하는 지 궁금한데 혼자 공부하다 보니 마땅히 예시를 볼만한 곳이 없어 질문드립니다. 긴 글 읽어 주셔서 감사합니다..! (_ _)
6분 45초쯤에 말씀을 하시는게 지금 이 강의는 예제로 보여주기 위해 Member와 Order가 일대다, 다대일로 양방향 연관관계를 이루고 있지만 실무에서는 이렇게 양방향으로 짜기보단 Order가 Member를 참조하게 되는 다대일만 써주는 것이 좋다. 이 말이 혹시 맞을까요...? 그리고 추가로 Order와 OrderItem의 관계에서는 양방향 연관관계를 안쓰려고 하면 OrderItem이 Order를 참조하는 것으로만 설계를 하면 되는건가요...?
"주 테이블만 조회해도 대상 테이블에 데이터가 있는지 확인 가능" 이 부분을 저는 주 테이블만 봐도 일단 외래키가 있다는 것은 대상 테이블에 뭔가가 있다는 것이니까 대상 테이블의 데이터 존재 유무 확인이 가능하다 라고 이해를 하였고 "값이 없으면 외래 키에 null 허용" 이 부분을 저는 OneToOne 관계이므로 한 사람은 라커 하나를 사용할 수 있고 라커도 한 사람에게 할당될 수 있으므로 주 테이블의 외래키인 LOCKER_ID가 null 이라는 것은 이 사람에게 할당될 라커가 없고 고로 대상 테이블에도 데이터가 없다 라고 이해를 했습니다. 이런 상황에서 두 가지 질문이 있습니다. 제가 이해한 것이 맞을까요? 테이블 칼럼의 값이 null인 것이 왜 단점일까요?
안녕하세요! 영한님^^ 개발자들을 위해여 양질의 강의를 제공해주셔서 정말로 감사의 말씀을 드립니다.🙏 질문드리겠습니다. 먼저 제가 이해한 내용을 말씀드리겠습니다. PageableExecutionUtils 를 사용하면 count 쿼리 최적화하 가능 하다고 말씀하셨습니다. 그 이유는 람다를 사용하여 게으른 호출로 인하여 실제 사용되기 전까지는 함수가 실행되지 않는다고 이해했습니다. 다음 2가지 경우에 count 쿼리가 실행되지 않다고 말씀하셨습니다. 페이지 시작이면서 컨텐츠 사이즈가 페이지 사이즈 보다 작을때 페이지 시작(0) 이면서 컨텐츠 사이즈(3) 가 페이지 사이즈(10) 보다 작다면 컨텐츠 사이즈(3)가 카운트 갯수 마지막 페이지 일때( offset + 컨텐츠 사이즈 = 카운트 ) 예를 들어 전체 데이터는 103개이고, 페이지 사이즈는 10이라고 한다면 마지막 페이지 일때는 offset(100) , 컨텐츠 사이즈(3) 이라서 카운트는 103 으로 이해 하고 있는데 맞는 내용 인가요? 감사합니다.^^
안녕하세요. 백엔드는 스프링, 프론트엔드는 리액트를 이용해서 리프레쉬 토큰과 엑세스 토큰으로 로그인을 구현할 때 한번 로그인을 하면 같은 브라우저 안에 있는 탭끼리는 로그인 상태를 유지시키고 싶은데 리프레쉬 토큰과 엑세스 토큰을 어디에 저장하고 어떻게 관리해야 되는지 궁급합니다.
안녕하세요. 강의 마지막에 컬렉션 페치 조인을 둘 이상 사용하지 말라고 하셨는데, 관련하여 질문드립니다. 말씀하신 상황이 제가 생각한 항목 중 어떤것이 맞을까요? 1 : N : N Team(1) : Member(N) : Order(N) 1 : N / 1 : N Post(1) : Comment(N) Post(1) : Attachment(N) 감사합니다.
[질문 템플릿] 1. 강의 내용과 관련된 질문인가요? (예 2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예 3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예 [질문 내용] public static OrderItem createOrderItem(Item item,int orderPrice, int count) 강의 중 생성메서드를 구현하는 부분 중 OrderItem 생성메서드를 생성할때 파라미터로 Order order를 추가하지 않은 것에 대한 이유가 궁금하여 질문드립니다. OrderItem 엔티티에서 order필드가 연관관계의 주인이기에 null이 되면 안된다 생각하였고 그렇기에 생성메서드 파라미터에도 Order order를 꼭 넣어줘야 하는게 아닌지 궁금점이 들어 질문드립니다!! 감사드립니다!
[질문 내용] 안녕하세요 강의 너무 잘 듣고 있습니다. @BatchSize에 관해서 의문점이 생겨서 질문을 드리게 되었습니다. @BatchSize를 사용하면 특정 Entity에서 조회 쿼리가 필요한 필드를 초기화 할 때, 1차 캐시로부터 같은 종류의 Entity id값을 @BatchSize에 지정한 size값만큼 가져와 IN에 넣어서 필요한 데이터를 함께 조회하고 초기화하는 것으로 이해했습니다. 근데, 이 때 단순히 1차 캐시에서 랜덤으로 id값을 가져와서 IN에 넣으면 원하지 않는 Entity에 초기화가 잘못 이뤄질 수 있지 않을까 하는 의문이 있었지만, 당연하게도 이런 부분에 대해서는 알아서 최적화를 해 둔 것 같았습니다. 예를 들면 JPQL의 결과로 조회된 Entity컬렉션 내부에서 하나를 집어 Entity 필드를 지연 로딩 초기화할 경우 같은 컬렉션 내부의 Entity들이 우선순위를 갖고 함께 초기화되는 것 같았습니다. 하지만 이것이 정확히 어떤 원리로 동작하는 지는 알 수가 없었습니다..ㅠㅠ JPQL로 조회한 컬렉션에 대해서 따로 영속성 컨텍스트가 참조하고 있다거나 하는 식으로 동작하는 것일까요? 이런 원리를 모르다 보니 @BatchSize가 IN에 넣을 Entity를 결정하는 우선순위를 알 수가 없었습니다. @BatchSize의 size값이 JPQL로 조회한 컬렉션의 size보다 클 때에는 1차 캐시에서 부족한 만큼의 Entity id를 더 끌어와서 IN에 추가하는데, 이 때 함께 초기화될 Entity가 어떤 Entity일 지 예측이 안 됩니다. 이런 것들도 우선순위가 따로 존재하나요? 크게 중요한 내용은 아닌 것 같은데, 그냥 같은 컬렉션에 있는 Entity들이 IN의 우선순위가 된다고 이해하고 사용하는 정도면 문제가 없을 지 궁금합니다..!
[질문 템플릿] 1. 강의 내용과 관련된 질문인가요? (예) 2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예) 3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예) [질문 내용] 안녕하세요?? 김영한님 JPA강의 덕에 포폴 순항하고 있습니다. 항상 좋은 강의 감사한 마음을 가지고 있습니다. 파이팅하세요 !! 궁금한점을.... 간단하게 설명하려고 노력하겠습니다 !! 예를들어서 블로그 포스팅 domain을 아래와 같이 만들었습니다... id: ~~~~ content: ~~~~ tags: ~~~ (일대다 관계) comments: ~~~(일대다 관계) 그런데 !!! 여기서 궁금한점 !! 제 생각으로는... 아 그냥 편하게 포스팅한번에 끌고와서(tags, comments) 응답해주고 싶은뎅... 그런데 영한님께서는 이 강의에서 일대다 관계를 두번이상하면 안된다고 하셔서...ㄷㄷ;;;; 그래서, 오랜 고민끝에 포스팅 (id,content,tags)를 응답해주고 comments는 다대일 방법으로 응답을 해줬거든요??.... (이게 옳은방법일까...ㄷㄷ;;) 그러니까 요청을 두번 보내는거에요.. 포스팅 한번... 포스팅에 연관관계있는 comments 한번.. 저의 이러한 판단과 설계과 괜찮은 걸까요?? 질문드리고 싶습니다 !! (잘 이해하셨을라나...ㄷㄷ;ㅠㅠ)
안녕하세요! 먼저 좋은강의 만들어주셔서 감사합니다. 다름이 아니라, 강의보면서 궁금한점이 생겨 문의드립니다. 지금 에러(exception)가 발생하면 공통적으로 ErrorResponse 객체를 반환하게 만들어서 에러처리를 일관성있게 할 수있게 만드신것 같습니다. 그런데 혹시 성공적인 응답일 때는 이런 객체를 만들지 않아도 되나요? 예를 들면 SuccessResponse 등을 만들어서 응답으로 주는 데이터를 다시 한번 감싸서 준다던가 하는게 더 좋을지, 아니면 그냥 응답dto 객체만 넘겨주면 되는지 궁금합니다!
학습하는 분들께 도움이 되고, 더 좋은 답변을 드릴 수 있도록 질문전에 다음을 꼭 확인해주세요. 1. 강의 내용과 관련된 질문을 남겨주세요. 2. 인프런의 질문 게시판과 자주 하는 질문(링크)을 먼저 확인해주세요. (자주 하는 질문 링크: https://bit.ly/3fX6ygx) 3. 질문 잘하기 메뉴얼(링크)을 먼저 읽어주세요. (질문 잘하기 메뉴얼 링크: https://bit.ly/2UfeqCG) 질문 시에는 위 내용은 삭제하고 다음 내용을 남겨주세요. ========================================= [질문 템플릿] 1. 강의 내용과 관련된 질문인가요? (예) 2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예) 3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예) [질문 내용] V4 에서는 fetch join을 사용하지 않으셨는데 조회 쿼리는 한번만 나가는 것을 확인했습니다. dto로 조회를 하면 Lazy로딩으로 세팅해도 즉시 로딩으로 나간다고 이해하면 될까요?
[질문 템플릿] 1. 강의 내용과 관련된 질문인가요? (예) 2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예) 3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예) [질문 내용] 안녕하세요. 프록시 강의를 보고 궁금한게 생겨 질문드립니다. 먼저 member객체를 생성하고 member.setUserName("hello");로 이름을 설정하고 persist(member); 하시고 바로 flush를 하셨습니다. 이후에 em.getReference(Member.class, member.getId());를 하시고 getId와 getUserName을 하실때 getId는 이미 getReference를 할때 getId(이미 있는 값이라 하셨습니다)를 사용해서 db에서 조회할 일이 없었다 라고 하셨고 getUserName은 db에서 조회해야한다 라고 하셨었는데 처음에 member객체에서 setUserName으로 값을 넣어주었고 id도 generatedValue로 값이 자동으로 설정되어서 2개의 값이 flush하기 전에 세팅이 되었는데 왜 id는 조회할때 이미 있는값이라 select문이 안나가고 userName은 값이 없어 select문이 나간건지 궁금합니다(설명을 잘 못해서 죄송합니다 ㅜ... 이해가 잘 안간탓에... 답변 해주시면 감사하겠습니다)