게시글
질문&답변
2024.04.09
@Component
안녕하세요 정보근입니다:) 질문자님의 자바/스프링 이해도가 어느 정도인지 몰라 간단하고 기본적인 내용 위주로 답변 드려보겠습니다. 크게 아래 두가지 내용을 설명 드릴 수 있을 것 같네요. 스프링 Bean이란 무엇인가 스프링과 스프링 부트의 차이 스프링 Bean이란 무엇인가 자바는 객체지향적 프로그래밍 언어라는 것은 알고 계시죠? 모든 자바 프로그래밍은 new 클래스명()과 같이 특정 클래스의 객체를 만드는 것에서부터 시작합니다. 그리고 Bean이란 개발자가 직접 new 명령어를 써서 객체를 만드는 것이 아닌, 스프링 내부적으로 알아서 객체를 생성하는, 즉 스프링에 의해 생성되고 관리되는 객체 를 의미합니다. 모든 자바 객체가 아니라는 것을 알아주세요. 그럼 개발자가 생성한 많은 클래스들 중에 어떤 클래스가 스프링에 의해서 관리될까요? 스프링의 관점에서 생각해보면, 실행이 되면서 Bean을 만들어서 등록해야 되는데, 어떤 클래스를 Bean으로 등록해야할지 어떻게 알 수 있을까요? 그걸 알려주는 어노테이션이 @Component입니다. 그리고 @Controller, @Service 등도 마찬가지에요. @Controller나 @Service를 선택하고 cmd + B(맥)나 ctrl + B(윈도우)를 누르면 내용을 볼 수 있는데요. @Component 어노테이션이 포함되어 있습니다. 그래서 스프링이 시작될 때 이런 어노테이션이 달린 모든 클래스를 찾아서, new Controller(), new Service() 이런 식으로 객체를 생성합니다. 기본적으로 Singleton 방식이라 객체는 단 하나만 생성됩니다. 그래서 개발자 작성하는 컨트롤러에서는, new Service()처럼 직접 서비스 객체를 만드는 게 아니고, 컨트롤러 클래스의 생성자에서 서비스 객체를 받아서 사용하게 됩니다. 스프링 내부적으로 컨트롤러 클래스가 서비스 클래스에 의존하는 것을 확인한 뒤, Service service = new Service(); Controller controller = new Controller(service); 와 같이 알아서 필요한 Bean들을 만들고 의존성을 주입해주는거죠. 참고로 @ComponentScan이란 어노테이션으로 어떤 컴포넌트(=빈)를 스캔할지 지정해줄 수도 있습니다. 기본적으로 해당 어노테이션이 있는 클래스와 같은 레벨, 그리고 하위의 모든 패키지가 스캔 범위라고 보시면 됩니다. 그리고 @SpringBootApplication 안에 포함되어 있어요. PortfolioApplication에서 해당 어노테이션 내부를 보시면 @ComponentScan 확인이 되실거에요. 그래서 PortfolioApplication과 같은 레벨에 있는 admin, domain, presentation 패키지 내의 모든 @Controller 등이 붙은 클래스는 빈으로 등록이 되고요. 만약 특정 서비스를 com.yongback.api.service와 같은 위치로 옮기면 스프링 실행 중 오류가 날 거에요. PortfolioApplication은 com.yongback.portfolio 안에 있으니, 스캔 범위를 벗어나기 때문입니다. 스프링과 스프링 부트의 차이 여기서는 "스프링"과 "스프링 부트"를 구분해서 사용하도록 하겠습니다. xml 파일을 사용하는 것은 스프링에서 빈을 등록하는 방법입니다. 빈을 등록하는 다양한 방법 중에 저렇게 직접 xml을 설정해서 범위를 지정하고, 빈의 이름을 지정하고 하는 방법도 있어요. 하지만 꽤 번거로운 작업이죠. 스프링 부트는 위에서 설명한 @ComponentScan, @Component, @Controller, @Service 등으로 저 기능을 다 대신할 수 있어요. xml 없이 자바/코틀린 코드만 가지고 설정할 수 있는거죠. 말씀드렸다시피 PortfolioApplication -> @SpringBootApplication 안에 @ComponentScan이 있으니 와 같이 직접 지정할 필요도 없습니다. 물론 좀 더 커스터마이징을 하고 싶다면 @ComponentScan을 새로 추가해줘야겠죠. @Configuration을 붙인 클래스 내부에 @Bean을 붙인 메소드를 이용해서 빈을 직접 등록해주는 방법도 있습니다. AdminSecurityConfiguration이 그런 방식을 사용했어요. 사실 어떤 게 더 편한지는 개인차가 있을 수 있는 내용이긴 합니다. 하지만 스프링 부트 자체가 사람들이 스프링을 쓰면서 불편했던 점을 개선한 버전이라, 개인적으로는 어노테이션을 이용하는 것이 훨씬 편하다고 생각되긴 하네요. 감사합니다.
- 0
- 1
- 81
질문&답변
2024.04.01
[실습]Thymeleaf- 부트스트랩 템플릿 - 자료가 달라요 ...
안녕하세요 정보근입니다:) 질문 내용을 보면 "Thymleaf - 부트스트랩 템플릿" 강의를 듣고 계신 것으로 보이네요. 해당 파일에서 추가할 내용은 아래 이미지와 같습니다. (사진) 올려주신 vendor가 포함된 디렉토리 이미지는 강의 화면에서 캡쳐한 것으로 보이는데, 나중에 어드민 페이지를 만들 때 추가되어야 할 내용입니다. 데모 프로젝트를 만들고 롤백한 뒤 각 강마다 내용을 추가해가는 과정에서 남아있던 항목이 있던 것 같아요. platform.base.d.ts파일이 회색인 것을 보면, 깃 관리 대상이 아니어서 롤백을 해도 사라지지 않고 남아있었던 것 같네요. 결론은 현재 시점에서는 vendor 디렉토리는 무시하시고 진행하셔도 괜찮습니다. 강의 진행 중 헷갈리시는 부분이 있으면 제 깃허브에서 받으신 프로젝트의 깃 커밋 로그를 참고해주세요. 어떤 파일에서 어떤 내용이 바뀌었는지 확인할 수 있고, 그대로만 진행해주시면 됩니다. 감사합니다:)
- 1
- 2
- 141
질문&답변
2024.03.21
실습리포지토리 테스트 코드 작성 강의 오류
안녕하세요 정보근입니다:) 밑에 AI 인턴이 원인은 잘 설명을 해주었네요. JPA 엔티티는 인자를 받지 않는 생성자(NoArgsConstructor)를 필요로 합니다. 따라서 Experience와 ExperienceDetail에 각각 아래와 같은 코드를 추가해주면 정상적으로 수행될거에요. // Experience constructor() : this("","",0,0,0,0,false) // ExperienceDetail constructor() : this("",false) 하지만 위 코드를 보면 저희가 코틀린 기본 생성자에서 모든 필드를 받도록 해놨기 때문에 임의로 빈 문자열과 0 등을 기본 생성자 인자로 넣어준 게 깔끔하지는 않죠. 방법은 다양합니다. 처음부터 필드를 nullable하게 정의하고 인자를 받아 필드를 세팅하는 생성자를 따로 정의해도 됐을 거고요. 기본 생성자 각 인자에 default 값을 넣고 constructor() : this()와 같이 깔끔하게 빈 생성자만 추가해도 됐을거고요. 저는 반드시 필드에 값이 있어야 한다는 강한 제약을 주고 싶어서 기본 생성자가 인자를 받도록 했습니다. 그런데 왜 제 코드는 별도로 빈 생성자를 정의 안 해줘도 됐냐면요. build.gradle.kts 파일을 여시면 제 소스 코드에는 아래 내용이 있고, just kim님 코드에는 없는 것을 확인하실 수 있어요. // build.gradle.kts kotlin("plugin.jpa") version "1.8.22 이 플러그인에서 알아서 NoArgsConstructor를 만들어줍니다. 위의 생성자 코드 추가 없이 build.gradle.kts에 저 플러그인만 추가해줘도 오류가 사라질거에요. 버전은 다른 코틀린 플러그인과 같이 1.9.22로 맞춰주셔야 할 거고요. 감사합니다.
- 1
- 2
- 81
질문&답변
2024.03.17
고민 있어요ㅠ.ㅠ 자바 스프링 vs 코트린 스프링
안녕하세요 정보근입니다:) 강의 내용과 살짝 거리가 있는 주제지만, 개인적인 의견 남겨봅니다. 질문하신 내용을 요약하면, 스프링을 고급 수준으로 활용하려면 자바를 필수로 알아야하는지 궁금하신 것 같아요. 제 생각을 결론부터 말씀드리자면 고급 수준을 원하신다면 필수 입니다. 이유는 스프링의 컴포넌트들이 자바로 쓰여졌기 때문입니다. 스프링으로 서비스를 개발하고 운영하고 트러블 슈팅을 하다보면, 스프링 소스 코드를 열어볼 수 밖에 없는 일이 생깁니다. 그 때 자바를 모르신다면 원인 파악이 쉽지는 않을 것 같아요. 물론 코틀린만으로도 충분히 스프링의 다양한 기능을 모두 활용한 서비스를 만들 수는 있습니다. 하지만 트러블 슈팅 상황에서 스프링 코어 로직을 분석할 수 없다면 고급 수준이라고 말하긴 어려울 것 같네요. 다시 한 번 정리하면, "코틀린만으로도 충분히 스프링 기능을 잘 활용한 스프링 애플리케이션을 만들 수는 있지만, 트러블 슈팅의 상황에서 자바로 쓰여진 스프링 프레임워크 코어를 분석할 수 없다면 고급이라고 보긴 어렵다" 정도가 제 의견입니다. 그런데 just kim님의 상황을 제가 추측하자면, 자바를 써보신 경험이 있고 충분히 읽을 줄 아시는 것으로 보이네요. 또 안드로이드 개발이 메인이고 스프링은 추가로 공부하시는 것 같은데 이런 경우 코틀린만 공부해도 충분하지 않을까 싶어요. 추가로 개인적인 경험 말씀드리자면, 저도 처음 배운 언어가 파이썬이었고 자바는 좀 올드한 느낌과 verbose한 점 때문에 별로 좋아하지 않았었어요. 하지만 회사의 기술이 자바/스프링이 메인이어서 공부하다보니, 오히려 이런 verbose한 특징 때문에 개발자가 직접 컨트롤 할 수 있는 영역이 많고 코드가 견고해져서 매력적이더라고요. 오랫동안 널리 쓰이는 건 그만한 이유가 있다고 생각하게 됐고, 정도 많이 들었습니다. 이전 회사의 안 좋은 기억 때문에 자바도 같이 안 좋아하게 되신 것 같은데, 잘못한 건 회사지 자바 언어는 아니니깐 조금만 더 여유를 갖고 자바 언어를 바라보시면 just kim님에게 더 좋지 않을까 생각되네요:) (아니면 앱 개발이 메인이시라면 다른 언어로 작성된 백엔드 프레임워크를 공부해보시는 것도 좋을 것 같아요) 감사합니다.
- 1
- 1
- 115
질문&답변
2024.03.17
강의노트 ZIP 파일이 비어있습니다.
안녕하세요 정보근입니다:) 강의 자료는 메일로 보내드렸습니다. 감사합니다.
- 1
- 1
- 57
질문&답변
2024.03.17
Kotlin: Unresolved reference: MappedSuperclass
안녕하세요 정보근입니다:) 올려주신 build.gradle.kts 내용을 보면 Spring Data JPA 의존성이 빠져있네요. jarkata.persistence 패키지는 Spring Data JPA에 포함되어 있습니다. 인텔리제이의 Gradle 탭을 열고, 맥 기준 cmd+f를 누른 후 검색창에 jarkata.persistence를 입력하시면, 아래 이미지처럼 org.springframework.boot:spring-boot-starter-data-jpa:3.1.4에 포함되어 있는 것을 확인하실 수 있습니다. (expand all 버튼을 누른 후 검색하셔야 검색이 됩니다) (사진) 아래 현재 진행 중이신 단계에서 필요한 의존성을 포함한 코드를 첨부드립니다. Spring Security와 jasypt는 강의를 진행하면서 추가하게 되어 주석처리 해두었습니다. 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-validation") // implementation("org.springframework.boot:spring-boot-starter-security") implementation("org.springframework.boot:spring-boot-starter-web") implementation("com.fasterxml.jackson.module:jackson-module-kotlin") implementation("org.jetbrains.kotlin:kotlin-reflect") // implementation("com.github.ulisesbocchio:jasypt-spring-boot-starter:3.0.5") runtimeOnly("com.h2database:h2") runtimeOnly("com.mysql:mysql-connector-j") testImplementation("org.springframework.boot:spring-boot-starter-test") } 감사합니다.
- 1
- 2
- 97
질문&답변
2024.03.10
ProjectSkill은 데이터가 안들어가고 있습니다.
안녕하세요 정보근입니다:) JPA 이해도가 높아보이는 좋은 질문이네요. 결론부터 말씀드리면 잘못 알고 계신 것은 없습니다. 다만 강의노트나 깃허브 리포지토리에서 "엔티티 개발 - 연관관계 있음" 부분을 보시면, project의 @OneToMany에 cascade = [CascadeType.PERSIST] 옵션이 지정되어 있는데요. gotjd9773님이 테스트 하신 코드에는 이 옵션이 안 들어가있었을 것으로 생각됩니다. 위와 같이 옵션을 지정하면 해당 엔티티가 "영속" 상태가 될 때 매핑된 엔티티도 같이 영속 상태가 됩니다. 이렇게 지정한 이유는 project_skill이 그 자체로 의미를 가지지 않은 단순한 매핑 테이블이기 때문입니다. 주로 사용하게 되는 엔티티는 project이기 때문에 편의성을 위해 persist에 한해서 영속성을 연결해준거죠. 다시 한 번 정리하면 주인이 아닌, 즉 mappedBy가 있는 "일" 쪽에 "다"를 추가하해도 "다"가 저장되지는 않습니다. 하지만 cascade를 쓰면 지정된 타입에 따라 "다"도 같이 영속성 상태가 변경이 됩니다. 따라서 Project 엔티티에 cascade를 추가해주면 인서트도 정상적으로 실행될 겁니다. 아래 간단한 코드 첨부드리니 팀과 멤버에 각각 cascade를 넣어보기도, 빼보기도 하고 팀만 저장하기도, 멤버만 저장하기도 하면서 어떻게 결과가 나오는지 직접 확인하시면 이해에 더 도움이 될 것으로 생각되네요. @Entity class Team() { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "team_id") var id: Long? = null var name: String? = null @OneToMany(mappedBy = "team", fetch = FetchType.LAZY, cascade = [CascadeType.PERSIST]) var members: MutableList = mutableListOf() } @Entity class Member() { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "member_id") var id: Long? = null var name: String? = null @ManyToOne(targetEntity = Team::class, fetch = FetchType.LAZY) @JoinColumn(name = "team_id", nullable = false) var team:Team?= null } // 샘플 테스트 코드 var team1 = Team() team1.name = "team1" var member1 = Member() member1.name="a" member1.team = team1 // memberRepository.save(member1) team1.members.add(member1) teamRepository.save(team1) 감사합니다.
- 1
- 2
- 117
질문&답변
2024.02.14
도커 질문이 있습니다.,
안녕하세요 정보근입니다:) 도커 컨테이너는 내부에서 실행 중인 애플리케이션이 중단되면 같이 중단됩니다. 스프링 부트가 제대로 실행이 안 된 것으로 보이는데요. 컨테이너를 클릭해서 로그를 봐야 자세한 원인 확인 가능할 것 같습니다. 로그 확인해보시고, 직접 해결이 어려우시면 첨부해서 댓글 남겨주세요. 원인 몇가지를 추측해보면 인텔리제이로 8080 포트에서 애플리케이션을 실행 중인 상태에서, 도커 컨테이너가 같은 8080 포트를 사용하려고 해서 충돌이 나고 중단됐을 가능성이 있습니다. yml에서 데이터베이스 비밀번호 등 설정이 잘못되어 DB 커넥션에 실패했을 가능성이 있습니다. 2번과 유사하게, 스프링 active profile 설정이 docker로 되지 않아 DB 커넥션에 실패했을 가능성이 있습니다. 감사합니다.
- 1
- 1
- 86
질문&답변
2024.01.22
화면 이동 시 css 적용 안 되는 문제
안녕하세요 정보근입니다:) presentation 디렉토리의 index.html 소스 코드를 올려주신 것으로 보이는데요. 만약 위와 같은 상태에서 index 화면에는 css가 잘 적용되는데, resume, projects에서만 안 된다면 resume.html, projects.html 상단에 아래 replace 코드가 있는지 확인해주세요. 위 코드는 현 html 소스 코드로 fragmeht-head.html의 th:fragment="head"가 포함된 블록을 가져오는 코입니다. fragmeht-head.html의 내용을 보시면 아래 코드와 같은 라인이 있을텐데요. 위 코드가 각 index.html, resume.html, projects.html에 포함되어 있어야 제대로 css 파일을 불러올 수 있습니다. resume에 직접 css 경로를 추가해주면 적용이 된다는 걸 봤을 때 replace 코드가 누락되어있을 가능성이 커보입니다. 또 th:href="@{/css/styles.css}"는 절대 경로를 지정해주는 타임리프 문법입니다. 기본값으로 "src/main/resources/static 디렉토리부터 경로 찾기를 시작하게 됩니다. 따라서 실제 경로는 "src/main/resources/static/css/style.css"와 같을거고요. 항상 "src/resources/static" 이하부터 경로를 찾기 때문에 절대 경로라고 합니다. 반면 작성해주신 "../../../css/style.css"와 같은 방식은 상대 경로입니다. 참고로 ".."은 상위 디렉토리를 의미하고, "."은 현재 디렉토리를 의미합니다. 소스 코드 상에 위와 같이 작성해주신 걸로 보아, 다운받은 부트스트랩 파일을 프로젝트 디렉토리 내부로 가져오지 않고, 다운받은 위치 그대로 경로를 지정해주신 것으로 보입니다. 이렇게 할 경우 내 컴퓨터에 있는 경로를 지정해준 것이기 때문에, 나중에 프로젝트를 서버로 올리면 해당 부트스트랩 파일을 찾을 수 없을 거에요. Presenation 개발 섹션의 "Thymeleaf - 부트스트랩 템플릿" 강의를 보시면 템플릿을 다운 받은 뒤 프로젝트 디렉토리 안으로 복사하는 과정이 있으니 참고해주세요. 참고로 작성해주신 경로 지정에 대해 설명드리자면, 현 디렉토리에서 상위 디렉토리로 7번 이동했다가, 다시 Downloads 디렉토리 이하로 경로를 파고 들어가 style.css를 찾게 됩니다. 만약 style.css의 위치가 그대로 있어도, 현 파일의 위치가 한 뎁스 올라가거나 내려간다면, style.css 파일과의 상대적인 위치도 바뀌게 됩니다. 그럼 제대로 파일을 찾을 수 없겠지요. 결론적으로는 템플릿 파일을 프로젝트 디렉토리의 src/resources/static 내부로 옮기고, 절대 경로로 지정해주세요. 그렇게 해야 프로젝트에서 안정적으로 css 경로를 찾을 수 있습니다. 궁금하신 것이 만족스럽게 해결되셨는지 모르겠네요. 수강 중 어려움이 있으시다면 아래 깃허브 리포지토리에서 현재 수강중인 강의 제목과 같은 브랜치를 찾아서 내용을 비교해보시면 도움이 될 거에요. 추가 질문도 언제든지 편하게 해주셔도 됩니다. https://github.com/infomuscle/portfolio-yongback 감사합니다.
- 2
- 1
- 156
질문&답변
2024.01.10
강의노트 내용
안녕하세요 정보근입니다:) 문의 보고 맥이랑 윈도우에서 강의 자료 다운 받아봤는데, 압축 해제하면 정상적으로 파일이 보여집니다. 우선 다운로드 후 압축 해제 재시도하시거나, 가능하시다면 다른 컴퓨터에서 다운로드 해보시면 좋을 것 같고요. 잘 안 된다면 댓글로 이메일 주소 남겨주시거나 infomuscle10@gmail.com 으로 메일 보내주세요. 강의 자료 직접 전달드리도록 하겠습니다. 감사합니다.
- 1
- 1
- 121