MySQL의 특징으로 어느 정도의 트레픽까지는 문제없이 활용 가능하다라고 설명해주셨는데 어느 정도를 수치로 말씀해주실 수 있을까요? 그리고 Redis가 MySQL보다 절대적으로 성능이 뛰어난지, 아니면 특정 트래픽 이상부터 성능이 뛰어난게 체감이 되는건지도 궁금합니다. 그리고 성능이라고 표현하신게 비용 대비 퍼포먼스인지, 속도만 놓고봤을 때 더 빠르다인지 모호한 감이 있어서 구체적으로 말씀해주시면 정말 감사하겠습니다. 좋은 강의 촬영해주셔서 감사합니다. 다음 강의도 기대하고 있겠습니다!
회원 도메인 실행과 테스트 부분에서 MemberApp 클래스에 public static void main(String args[]) 를 만들고 여기서 실행하는 것은 이해했습니다. 그런데 기존의 CoreApplication 에도 메인 메서드가 있어서 프로젝트에 메인 메서드가 2개이고 이렇게 프로젝트가 구성이 되어도 실행하는데 문제가 없는건가요??
안녕하세요, 강의에는 나오지 않았지만 변경감지를 병합보다 추천하셔서 DTO를 사용해서 한 번 짜보았는데 봐주실 수 있나요? ```java package jpabook.jpashop.service; import lombok.Getter; import lombok.Setter; @Getter @Setter public class UpdateItemDto { private Long id; private String name; private int price; private int stockQuantity; private String author; private String isbn; } ``` 일단 UpdateItemDto는 간단히 Getter, Setter만으로 구성했습니다. @PostMapping("items/{itemId}/edit") public String updateItem(@PathVariable("itemId") Long itemId, @ModelAttribute("form") BookForm form) { UpdateItemDto updateItemDto = new UpdateItemDto(); updateItemDto.setId(form.getId()); updateItemDto.setName(form.getName()); updateItemDto.setPrice(form.getPrice()); updateItemDto.setStockQuantity(form.getStockQuantity()); updateItemDto.setAuthor(form.getAuthor()); updateItemDto.setIsbn(form.getIsbn()); itemService.updateItem(itemId, updateItemDto); return "redirect:/items"; } 위 코드는 ItemController 안의 updateItem 메소드입니다. @Transactional public void updateItem(Long itemId, UpdateItemDto updateItemDto) { Item findItem = itemRepository.findOne(itemId); //findItem으로 찾아온 값은 영속상태 findItem.change(updateItemDto); } 위 코드는 ItemService 클래스의 updateItem 메소드입니다. public void change(UpdateItemDto updateItemDto) { // if (updateItemDto.getId() != null) { // this.id = updateItemDto.getId(); // } if (updateItemDto.getName().length() != 0) { this.name = updateItemDto.getName(); } // if (updateItemDto.getPrice() != null) { // this.price = updateItemDto.getPrice(); // } // if (updateItemDto.getStockQuantity() != null) { // this.stockQuantity = updateItemDto.getStockQuantity(); // } this.price = updateItemDto.getPrice(); this.stockQuantity = updateItemDto.getStockQuantity(); // if (updateItemDto.getAuthor() != null) { // this.author = updateItemDto.getAuthor(); // } // if (updateItemDto.getIsbn() != null) { // this.isbn = updateItemDto.getIsbn(); // } } 마지막으로 Item 클래스의 change 메소드입니다. Book의 상위 클래스이기 때문에 author과 isbn 필드가 없어서 이 change 메소드를 Book 클래스 내로 옮겨야 하는지 고민을 했습니다. 그러나 그렇게 진행하면 부수적인 문제가 너무 많이 생겨 그냥 Item 클래스 내에 남기고 author와 isbn을 수정하는 기능은 없앴습니다. id는 바꿀 필요가 없다고 생각하여 기능을 뺐습니다. Form에도 id를 수정하는 란은 없는데 기존에 영한 님이 강의하실 때 ItemController 내에서 book.setId(form.getId)); 하셨는데 잘못된 부분인 것 같습니다. name같은 경우 String이라 웹 상에서 수정을 할 때 빈 칸으로 두면 null보다 empty string으로 받는 것 같아서 updateItemDto.getName() != null 조건에 안 걸리는 것 같더라구요. 그래서 빈 칸을 empty string을 찾는 식으로 짰습니다. 마지막으로 price와 stockQuantity는 int라서 != null 조건을 걸수가없는데 웹 상에서 빈 칸일 때 이전 price와 stockQuantity를 유지하는 방법을 여쭤보고 싶습니다. 결론적으로 제 질문은: 1. UpdateItemDto의 경우 controller 패키지의 BookForm과 이름 외에 똑같은데 그래도 DTO 클래스를 따로 만드는데 의의가 있는건가요? 2. ItemController 내에서 어설프게 Entity를 생성하지 말라고 하셨는데 DTO 객체를 생성하는 것은 괜찮은가요? 3. 전반적으로 이런 방식으로 변경감지를 하는건지가 궁금합니다. 4. change 메소드를 Item 밑에 두는게 맞는지 Book 밑에 두는게 맞는지? 답변 주시면 정말 큰 도움이 될 것 같습니다. 감사합니다!
service, repository interface에 직접 Bean 으로 DI 를 위하여 scan 범위를 controller 만 잡는다는 의미를 정확하게 이해하고 싶어서요. scan 범위를 제거한다면 @service, @repository 에 의해서 모든 서비스와 레포지토리가 대상이 되고 Spring container에 등록은 되지만 수동으로 등록하는 Bean(MemoryConfig)에 의해 ItemService와 ItemRepository에 주입되어 타입이 정해지는걸로 이해하는데요. scan 범위를 지정한것은 불필요한 service, repository가 bean으로 등록되는것을 막기위한 효율의 측면인가요?
에러 코드 전문 java.lang.IllegalStateException: Failed to load ApplicationContext at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:132) at org.springframework.test.context.support.DefaultTestContext.getApplicationContext(DefaultTestContext.java:124) at org.springframework.test.context.web.ServletTestExecutionListener.setUpRequestContextIfNecessary(ServletTestExecutionListener.java:190) at org.springframework.test.context.web.ServletTestExecutionListener.prepareTestInstance(ServletTestExecutionListener.java:132) at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:248) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.createTest(SpringJUnit4ClassRunner.java:227) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner$1.runReflectiveCall(SpringJUnit4ClassRunner.java:289) at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.methodBlock(SpringJUnit4ClassRunner.java:291) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:246) 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:190) 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: mapping values are not allowed here in 'reader', line 10, column 15: properties: ^ at org.yaml.snakeyaml.scanner.ScannerImpl.fetchValue(ScannerImpl.java:910) at org.yaml.snakeyaml.scanner.ScannerImpl.fetchMoreTokens(ScannerImpl.java:400) at org.yaml.snakeyaml.scanner.ScannerImpl.checkToken(ScannerImpl.java:251) at org.yaml.snakeyaml.parser.ParserImpl$ParseBlockMappingKey.produce(ParserImpl.java:628) at org.yaml.snakeyaml.parser.ParserImpl.peekEvent(ParserImpl.java:166) at org.yaml.snakeyaml.comments.CommentEventsCollector$1.peek(CommentEventsCollector.java:59) at org.yaml.snakeyaml.comments.CommentEventsCollector$1.peek(CommentEventsCollector.java:45) at org.yaml.snakeyaml.comments.CommentEventsCollector.collectEvents(CommentEventsCollector.java:140) at org.yaml.snakeyaml.comments.CommentEventsCollector.collectEvents(CommentEventsCollector.java:119) at org.yaml.snakeyaml.composer.Composer.composeScalarNode(Composer.java:214) at org.yaml.snakeyaml.composer.Composer.composeNode(Composer.java:184) at org.yaml.snakeyaml.composer.Composer.composeValueNode(Composer.java:314) at org.yaml.snakeyaml.composer.Composer.composeMappingChildren(Composer.java:305) at org.yaml.snakeyaml.composer.Composer.composeMappingNode(Composer.java:286) at org.yaml.snakeyaml.composer.Composer.composeNode(Composer.java:188) at org.yaml.snakeyaml.composer.Composer.composeValueNode(Composer.java:314) at org.yaml.snakeyaml.composer.Composer.composeMappingChildren(Composer.java:305) at org.yaml.snakeyaml.composer.Composer.composeMappingNode(Composer.java:286) at org.yaml.snakeyaml.composer.Composer.composeNode(Composer.java:188) at org.yaml.snakeyaml.composer.Composer.getNode(Composer.java:115) at org.yaml.snakeyaml.constructor.BaseConstructor.getData(BaseConstructor.java:135) at org.springframework.boot.env.OriginTrackedYamlLoader$OriginTrackingConstructor.getData(OriginTrackedYamlLoader.java:99) at org.yaml.snakeyaml.Yaml$1.next(Yaml.java:514) at org.springframework.beans.factory.config.YamlProcessor.process(YamlProcessor.java:198) at org.springframework.beans.factory.config.YamlProcessor.process(YamlProcessor.java:166) at org.springframework.boot.env.OriginTrackedYamlLoader.load(OriginTrackedYamlLoader.java:84) at org.springframework.boot.env.YamlPropertySourceLoader.load(YamlPropertySourceLoader.java:50) at org.springframework.boot.context.config.StandardConfigDataLoader.load(StandardConfigDataLoader.java:54) at org.springframework.boot.context.config.StandardConfigDataLoader.load(StandardConfigDataLoader.java:36) at org.springframework.boot.context.config.ConfigDataLoaders.load(ConfigDataLoaders.java:107) at org.springframework.boot.context.config.ConfigDataImporter.load(ConfigDataImporter.java:128) at org.springframework.boot.context.config.ConfigDataImporter.resolveAndLoad(ConfigDataImporter.java:86) at org.springframework.boot.context.config.ConfigDataEnvironmentContributors.withProcessedImports(ConfigDataEnvironmentContributors.java:116) at org.springframework.boot.context.config.ConfigDataEnvironment.processInitial(ConfigDataEnvironment.java:240) at org.springframework.boot.context.config.ConfigDataEnvironment.processAndApply(ConfigDataEnvironment.java:227) at org.springframework.boot.context.config.ConfigDataEnvironmentPostProcessor.postProcessEnvironment(ConfigDataEnvironmentPostProcessor.java:102) at org.springframework.boot.context.config.ConfigDataEnvironmentPostProcessor.postProcessEnvironment(ConfigDataEnvironmentPostProcessor.java:94) at org.springframework.boot.env.EnvironmentPostProcessorApplicationListener.onApplicationEnvironmentPreparedEvent(EnvironmentPostProcessorApplicationListener.java:102) at org.springframework.boot.env.EnvironmentPostProcessorApplicationListener.onApplicationEvent(EnvironmentPostProcessorApplicationListener.java:87) at org.springframework.context.event.SimpleApplicationEventMulticaster.doInvokeListener(SimpleApplicationEventMulticaster.java:176) at org.springframework.context.event.SimpleApplicationEventMulticaster.invokeListener(SimpleApplicationEventMulticaster.java:169) at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:143) at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:131) at org.springframework.boot.context.event.EventPublishingRunListener.environmentPrepared(EventPublishingRunListener.java:85) at org.springframework.boot.SpringApplicationRunListeners.lambda$environmentPrepared$2(SpringApplicationRunListeners.java:66) at java.base/java.util.ArrayList.forEach(ArrayList.java:1541) at org.springframework.boot.SpringApplicationRunListeners.doWithListeners(SpringApplicationRunListeners.java:120) at org.springframework.boot.SpringApplicationRunListeners.doWithListeners(SpringApplicationRunListeners.java:114) at org.springframework.boot.SpringApplicationRunListeners.environmentPrepared(SpringApplicationRunListeners.java:65) at org.springframework.boot.SpringApplication.prepareEnvironment(SpringApplication.java:344) at org.springframework.boot.SpringApplication.run(SpringApplication.java:302) at org.springframework.boot.test.context.SpringBootContextLoader.loadContext(SpringBootContextLoader.java:132) at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContextInternal(DefaultCacheAwareContextLoaderDelegate.java:99) at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:124) ... 27 more 환경설정 1.yml spring: datasource: url: jdbc:h2:tcp://localhost/~/jpashop username: sa password: driver-class-name: org.h2.Driver jpa: hibernate: ddl-auto: create properties: hibernate: # show_sql: true format_sql: true logging.level: org.hibernate.SQL: debug 2.gradle plugins { id 'org.springframework.boot' version '2.7.3' id 'io.spring.dependency-management' version '1.0.13.RELEASE' id 'java' } group = 'jpabook' version = '0.0.1-SNAPSHOT' sourceCompatibility = '11' configurations { compileOnly { extendsFrom annotationProcessor } } repositories { mavenCentral() } dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'junit:junit:4.13.2' compileOnly 'org.projectlombok:lombok' runtimeOnly 'com.h2database:h2' annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' } tasks.named('test') { useJUnitPlatform() } 해결을 위해 시도했던 것들 1. yml에 ;MVCC=TRUE를 삭제해보았다. (X) 2. h2 DB가 실행되지 않아서 발생할 수 있다는 답변을 보고 켜보았다. (X) 3. yml을 복붙해서 다시 실행해보았다. (X) 4. Test에 @WebAppConfiguration 어노테이션을 추가해보았다. (X)
안녕하세요. 영한님 강의 중에 http 도 들었어서 궁금함이 생겨 질문합니다. 데이터 수정을 하시는 부분에서 @PostMapping을 쓰셨는데 이건 그냥 회사 규율이나 개발자 취향에 따라 쓰는 건가요? 리소스의 일부분을 수정하는 것으로 Patch가 있는 걸로 알고 있고 이때 @PatchMapping 을 사용해도 되는 것인지 잘 모르겠어서요. 감사합니다.
@RestController에서 서버는 데이터를 주고 받을 때 데이터타입이 json이어야한다고 하셨습니다. 그래서 클라이언트에서 ajax통신으로 서버에 요청을 보낼때, json.stringify을 통해서 Json타입으로 변환 후 전달하셨습니다. 그렇게 하지 않으면 서버에서 제대로 값을 전달 받지 못하는 것도 확인했습니다. 그런데 궁금한게 있습니다. json이란게 아래와 같이 객체를 문자열로 표기한 거잖아요. "{"key":value."key2":value2}" 그럼 애초에 client(jsp) 에서 ajax 통신시 "{"key":value."key2":value2}" 이런식으로 data를 보내면 되야하는 거 아닌가요? json이란 게 약속된 규칙을 통해 객체를 문자열로 표기한 건데 굳이 Json.stringfy를 통해 문자열을 한번더 가공해야지 통신된다는게 이해가 잘 안됩니다. 그렇다면 "{"key":value."key2":value2}" 이건 json타입이 아니라 stringfy()로 감싸야지 json타입이 된다는 말 인것 같거든요. 아니면 @RestController 어노테이션이 붙은 상태에서 서버로 데이터를 보낼때는 Json.stringfy를 꼭 붙여야한다는 규칙이 있다 라고 생각하면 되는건가요? 답변 부탁드립니다.
1. 엔티티에서 setter는 지양하라고 말씀하셨는데 예를 들면 Order의 연관관계 메서드를 작성 시 OrderItem의 setter가 사용됩니다. 이 때 setter를 사용하지 않고 양방향 연관관계 세팅을 할 수 있는 방법이 무엇이 있을까요? 2. 양방향값 세팅은 양방향연관관계일 때 필수적인 건가요?
싱글톤의 경우, 요청이 올 때 마다 객체를 생성하는 것이 아니라, 이미 만들어진 객체를 공유해서 사용할 수 있다라고 이해했습니다. 그러면 프로토타입의 경우, 스프링 컨테이너에 빈의 이름과 빈이 등록이 되고 나서 빈 객체는 생성이 되지 않고 그냥 빈 자체만 등록이 되는 것인가요? 그리고 요청이 들어오면 그때서야 빈 객체를 새로 만들어주는 것이 맞나요?
@Component public class CustomBCryptPasswordEncoder extends BCryptPasswordEncoder { } 위 처럼 따로 BcryptPasswordEncoder를 만들어주고 컨테이너에 등록합니다! //해당 메서드의 리턴되는 오브젝트를 IoC로 등록해준다. /* @Bean() public BCryptPasswordEncoder encoder(){ return new BCryptPasswordEncoder(); } */ 그리고 위의 코드(SecurityConfig)를 저처럼 주석으로 처리해주세요! 순환 참조가 나는 이유: 1. SpringContainer에서 처음 빈으로 등록하기 위해 객체를 생성 하여 줍니다(싱클톤) 2. 그래서 SecurityConfig 객체를 생성하던 중 PrincipalOauth2UserService객체를 의존하고있네요? 그래서 PrincipalOauth2UserService를 만들어 주는데.. 3. 어라? PrincipalOauth2UserService에서도 SecurityConfig에서 빈으로 등록한 BCryptPasswordEncoder를 참조하고있네?? 4. 오잉? 스프링 :참조가 순환되넹?아아아아아악! => 오류 즉, SecurityConfig -> PrincipalOauth2UserService, 다시 PrincipalOauth2UserService->SecurityConfig 그래서 저는 SecuritiConfig -> PrincipalOauth2UserService-> CustomBCryptPasswordEncoder 로 구조를 변경하였습니다.