조건맞는 요소 추가 + 가변배열->정적배열
import java.util.*;
class Solution {
public int[] solution(int[] arr, int divisor) {
List<Integer> list = new ArrayList<>();
for(int a : arr){
if(a%divisor == 0) list.add(a);
}
if(list.size() == 0) return new int[] {-1};
int[] answer = new int[list.size()];
for (int i = 0; i < list.size(); i++) {
answer[i] = list.get(i);
}
Arrays.sort(answer);
return answer;
}
}
List 방식 대신 stream을 사용할 수 있다.
Arrays.stream(arr)
.filter(n -> n % divisor == 0)
.sorted()
.toArray();
class Solution {
public int solution(int[] absolutes, boolean[] signs) {
int answer = 0;
for(int i = 0; i< signs.length; i++){
answer += signs[i] ? absolutes[i] : -absolutes[i];
}
return answer;
}
}
배열[i] 앞에 부호를 붙여도 되나 싶어 해봤는데 이게 되네? 했던 코드😅
확인 결과 양수에 +를 붙여도 동작한다.
자바에서 단항 연산자 + / - 가 가능한 타입:
byte
short
int
long
float
double
char (정수로 승격됨)
제대로 이해했는지 확인하기 위해 강의에서 나오는 외래키의 주인을 Food에서 User로 바꿔 작성했다.
@OneToOne 애너테이션은 1:1 관계를 맺어주는 역할을 한다.
@JoinColum 애너테이션은 해당 테이블에 저장될 외래키 이름을 지정한다.
고객 Entity가 외래 키의 주인인 경우:
👩💼: 내가 시킨 음식은...🍔 이다.
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
// Food를 필드로 가진다.
@OneToOne
@JoinColumn(name = "food_id")
private Food food;
}
🍔: ????
@Entity
@Table(name = "foods")
public class Food {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private double price;
}
양방향 관계에서는 각 엔티티가 서로를 필드로 가지고있다.
👩💼: 내가 시킨 음식은...🍔 이다.
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@OneToOne
@JoinColumn(name = "food_id") // 외래키 이름 지정
private Food food;
}
🍔: 나는... 👩💼에게 시켜졌다.
@Entity
@Table(name = "foods")
public class Food {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private double price;
@OneToOne(mappedBy = "food") // 상대 Entity의 "필드"명, users테이블은 외래키를 가지게 된다.
private User user;
}
이 때, DB에서 외래 키는 연관관계를 가진 두 테이블 중 한쪽에만 저장된다. 그래서 JPA에서는 외래키의 주인이 어떤 엔티티인지 정보를 저장해야한다.
이 때, @OneToOne애너테이션의 mappedBy 옵션으로 해당 필드를 어떤 Entity가 가지고 있는지 상대 Entity의 필드명을 적어준다.
@OneToOne(mappedBy = "food")
private User user;
❓ 이미 외래키를 가진 엔티티에 작성이되어있는데 왜 반대편에서 mappedBy로 또 명시해야 하지? JPA가 자동으로 추론해서 연결해주면 안 되나?”
@OneToOne @JoinColumn(name = "food_id") // 외래키 이름 지정 private Food food;
- FK 컬럼이 어디 있는지
- 어떤 엔티티를 참조하는지
- 어떤 필드가 DB를 수정하는지
JPA는 DB의 FK 정보를 기반으로 동작하지만, 매핑은 객체 필드 단위로 정의된다.
JPA는 각 필드를 독립 관계로 보기 때문에, 두 필드가 같은관계로 이어져있다 추론하지 않는다. mappedBy는 기존관계를 재사용한다고 명시해 중복 매핑과 의미 혼동을 막기 위한 장치다.
@Test
@Rollback(false)
@DisplayName("1대1 양방향 테스트 : 외래 키 저장 실패")
/**
* 외래키 주인인 User 엔티티만 외래키 필드를 직접 조작할 수 있다
*/
void testUserAsOwner() {
Food food = new Food();
food.setName("고구마 피자");
food.setPrice(30000);
// 외래키 주인인 User에서 Food를 설정
User user = new User();
user.setName("Robbie");
user.setFood(food); // O
// 외래키 주인이 아닌 Food에서 User를 설정하면 DB food_id 값이 null로 비어있다.
// food.setUser(user); // BUG: 단순 설정만으로는 FK 저장 불가
// 외래 키(연관 관계) 설정 Food.user.setFood(this); 를 추가한 food.addUser(user); 등의 메서드를 만들어 해결한다.
food.addUser(user);
userRepository.save(user);
foodRepository.save(food);
}
@Test
@DisplayName("1대1 조회 : User 기준 food 정보 조회")
void test6() {
User user = userRepository.findById(1L).orElseThrow(NullPointerException::new);
// 고객 정보 조회
System.out.println("user.getName() = " + user.getName());
// 해당 고객이 주문한 음식 정보 조회
Food food = user.getFood();
System.out.println("food.getName() = " + food.getName());
System.out.println("food.getPrice() = " + food.getPrice());
}
@Test
@DisplayName("1대1 조회 : Food 기준 user 정보 조회")
void test5() {
Food food = foodRepository.findById(1L).orElseThrow(NullPointerException::new);
// 음식 정보 조회
System.out.println("food.getName() = " + food.getName());
// 음식을 주문한 고객 정보 조회
System.out.println("food.getUser().getName() = " + food.getUser().getName());
}
JPA에 외래키를 어디에 저장할 지 지정되지 않았을 때 중간 테이블이 생성된다.
단, 1:1 양방향 관계에서는 외래 키의 주인 Entity에서는 mappedBy로 상태 엔티티의 필드명을 표시해 @JoinColumn() 애너테이션을 사용하지 않아도 default 옵션으로 외래키를 생성이 적용되기 때문에 생략이 가능하다.
@OneToMany 애너테이션은 1:N 관계에서 1쪽 엔티티에서 사용하며, 여러 개의 자식(N)을 관리한다.
@ManyToOne 애너테이션은 N:1 관계에서 N쪽 엔티티에서 사용하며, 하나의 부모(1)를 참조한다.
OneToMany 단방향에서는 외래키가 N쪽 테이블에 있음에도
JPA가 1쪽에서 외래키를 관리하려 하므로 INSERT 이후 UPDATE가 추가로 발생하는 성능상 비효율이 생긴다.
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
// 현실적 구현에서는 @ManyToOne이 있는 N쪽에서 외래 키를 관리하는 것이 안전
// INSERT/UPDATE 시 성능 문제가 발생할 수 있다.
// 실제로는 Food 테이블에 외래 키가 생기지만, JPA는 user_id 컬럼을 직접 관리
@OneToMany
@JoinColumn(name = "user_id") // foods 테이블에 user_id 컬럼
private List<Food> foodList = new ArrayList<>();
}
@Entity
@Table(name = "food")
public class Food {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private double price;
}
OneToMany 단방향에서 기존 User 수정 시 JPA 동작
OneToMany 단방향에서 @JoinColumn을 사용하면
연관관계의 주인이User(1쪽) 가 된다.
하지만 실제 외래키는 foods 테이블(N쪽) 에 존재한다.
따라서flush()시점에 JPA는 다음과 같이 동작한다:
- User 엔티티의 변경 사항을 감지하여 users 테이블을 UPDATE 한다.
- @OneToMany 컬렉션의 변경 여부를 스냅샷 기반으로 비교한다.
- foodList에 포함된 Food 엔티티들에 대해,
외래키(user_id) 값을 반영하기 위해 컬렉션에 포함된 Food 개수만큼 UPDATE를 실행할 수 있다.UPDATE foods SET user_id = ? WHERE id = ?💡 그래서
@ManyToOne이 있는 N쪽에서 외래 키를 관리하는 것이 좋다.
N쪽(Food)에@ManyToOne으로 외래키를 직접 설정하면,
Food INSERT 시점에 user_id를 함께 저장할 수 있으므로INSERT INTO foods (name, price, user_id) VALUES (?, ?, ?)추가 UPDATE가 발생하지 않는다.
1:1 관계에서는 mappedBy옵션을 통해 양방향 관계를 설정해줄 수 있었다.
그러나 1대 N관계에서는 일반적으로 양방향 관계가 존재하지 않는다.
@ManyToOne 애너테이션은 mappedBy 속성을 제공하지 않는다.
양방향을 구현할 수는 있지만 외래키를 읽기만 하고 절대 수정하지 않는 설정이다.
N쪽이 FK를 관리하는 단방향 관계만으로도 충분하다.
N 관계의 Food Entity에서 @JoinColumn의 insertable과 updatable옵션을 false로 설정하여 양쪽으로 JOIN 설정을 하면 양방향 처럼 설정할 수는 있다.
@Entity
@Table(name = "foods")
public class Food {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private double price;
@ManyToOne
@JoinColumn(name = "user_id", insertable = false, updatable = false)
private User user;
}
insertable = false
JPA가 INSERT SQL에 해당 컬럼을 포함하지 않음
즉, 엔티티를 새로 저장할 때 이 컬럼 값은 DB에 반영되지 않음
updatable = false
JPA가 UPDATE SQL에 해당 컬럼을 포함하지 않음
즉, 엔티티를 수정해도 이 컬럼 값은 변경되지 않음
보통 외래 키를 다른 엔티티나 DB 스키마에서 관리되고 있어
JPA가 중복해서 UPDATE/INSERT 하지 않도록 막고 싶을 때
해당 엔티티를 읽기 전용으로 만들기 위해 사용한다.
1쪽 엔티티에서 @OneToMany(mappedBy = "user"),
N쪽 엔티티에서 @ManyToOne으로 연결하면
insertable=false, updatable=false 옵션 없이도
양방향 관계 처럼 동작한다.
@Entity
@Table(name = "users")
public class User {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@OneToMany(mappedBy = "user") // Food.user가 관리하는 관계
private List<Food> foodList = new ArrayList<>();
}
@Entity
@Table(name = "food")
public class Food {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private double price;
@ManyToOne
@JoinColumn(name = "user_id") // 외래 키 관리
private User user;
}
성능/설계 측면에서 @ManyToOne으로 외래 키는 항상 N쪽이 관리하는 게 정석이다.
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
}
@Entity
@Table(name = "food")
public class Food {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private double price;
// N쪽에서 외래 키 관리
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id") // food 테이블에 user_id FK 생성
private User user;
}
@Test
@DisplayName("1대N 조회 : Food 기준 user 정보 조회")
void test5() {
Food food = foodRepository.findById(1L).orElseThrow(NullPointerException::new);
System.out.println("food.getName() = " + food.getName());
System.out.println("food.getUser().getName() = " + food.getUser().getName());
}
@Test
@DisplayName("N대1 조회 : User 기준 food 정보 조회")
void test6() {
User user = userRepository.findById(1L).orElseThrow(NullPointerException::new);
System.out.println("user.getName() = " + user.getName());
List<Food> foodList = user.getFoodList();
for (Food food : foodList) {
System.out.println("food.getName() = " + food.getName());
System.out.println("food.getPrice() = " + food.getPrice());
}
}
-- 조회 시 실제로 실행되는 join 쿼리 (User 기준)
select
u1_0.id,
u1_0.name,
f1_0.id,
f1_0.name,
f1_0.price
from
users u1_0
left join
foods f1_0
on u1_0.id=f1_0.user_id
where
u1_0.id=?
Git 사용을 하는 동안 stash를 안하고 커밋 되돌리기를 force로 해서 작업 내용을 몽땅 날리는 등 문제가 있었다.
당장 오늘도 Push를 하는데 GitHub 프로필 설정이 잘못 되어 다른 프로필로 커밋이 올려져 고생했다.
나는 git global config의 이메일이 로컬에 표시하는 용도로만 생각했는데,
gitHub에서는 이 이메일로 GitHub 프로필 정보를 표시한다고 했다.
(근데 검증도 안하고 이래도 되나?!😫)
그런데 오늘 마침 Git 특강을 해주셨다.
질의시간에 이에 대해 여쭤봤는데, --global 옵션을 빼면, git config를 레포지토리 단위로 설정할 수 있다고 하셨다.
addcommit checkoutpush pull현재 내가 작업중인 위치를 가리키는 포인터
브랜치 명과 커밋 메시지의 좋은 예
• hotfix/버전번호_validation
• hotfix-버전번호_validation o fix/기능 이름
브랜치명에
/로 구분하는 것은 여러 브랜치가 생성 될 경우 네임스페이스로 구분하기 위함
release, hotix 와 같은 경우는 팀 컨벤션에 따르는 것이 좋다!
Pull Request(PR)는 코드 변경 사항을 다른 브랜치에 병합하도록 요청하는 기능이다.
병합 전 변경 내용을 검토하고 토론하기도 하며 추가로 병합에 필요한 필수 승인자 등을 넣어 배포 승인 절차로 취급할 수도 있다.
PR은 리뷰어들이 PR 을 쉽고 빠르게 이해하기 위해 작은 단위로 올리는 것이 좋다.
코드 작성을 하는 모두가 리뷰어로 적극적으로 참여하는게 좋다.
팀에서 PR 메세지를 일관화 하기 위해 템플릿을 사용할 수 있다.
프로젝트 하위에 .github 숨김 폴더에
템플릿내용을 포함한 pull_request_template.md 파일을 생성하면 반영된 브랜치로 pull request를 생성할 때 양식이 자동으로 완성된다.
## 변경 타입
- [ ] 신규 기능 추가/수정
- [ ] 버그 수정
- [ ] 리팩토링
- [ ] 설정
- [ ] 비기능 (주석 등 기능에 영향을 주지 않음)
## 변경 내용
- **as-is**
- (변경 전 설명을 여기에 작성)
- **to-be**
- (변경 후 설명을 여기에 작성)
## 코멘트
- (추가적인 설명이나 코멘트가 필요한 경우 여기에 작성)
| 태그 | 의미 | 리뷰어 의도 | 작성자 권장 행동 |
|---|---|---|---|
| P1 | 꼭 반영해주세요 | ⚠️ 중대한 코드 수정이 반드시 필요 (Request changes) | 요청을 반드시 반영하거나, 불가 시 합리적 이유로 설득 |
| P2 | 적극적으로 고려해주세요 | ⚡ 중요하지만 필수는 아님, 토론 권장 (Request changes) | 수용하거나, 불가 시 의견을 들어 토론 권장 |
| P3 | 웬만하면 반영해 주세요 | 💬 사소하지만 반영하면 좋은 의견 (Comment) | 수용하거나, 불가 시 이유 설명 또는 다음 반영 계획(JIRA 티켓 등) 명시 |
| P4 | 반영해도 좋고 넘어가도 좋습니다 | ✅ 중요도가 낮은 의견 (Approve) | 의견 달지 않아도 무방, 반영 여부 자유 |
| P5 | 그냥 사소한 의견입니다 | ℹ️ 아주 사소한 의견 (Approve) | 의견 달지 않아도 무방 |
예) 서로 다른 브랜치에서 같은 파일을 수정하고 서로 main에 병합하려고 할 때
각각의 브랜치에서 수정된 두 파일의 내용을 모두 작성하거나 둘 중 하나만 반영할지 git이 알지 못한다.
이를 선택해서 어떤 커밋의 변경사항을 반영할 지 고르는 것을 충돌을 해결(Resolve Conflict)한다고 한다.
학부1학년때 처음 git을 시작하고 add, commit, push밖에 몰랐던 상태로 팀 프로젝트를 할 때 이러한 충돌이 왜 일어나는지 몰라서 거의 공포로 다가왔던 기억이 있었다.
기억은 잘 안나지만 아마 이 때문에 버전 관리를 안하고 zip파일을 서로 주고받으면서 순서대로 파일을 건드렸던 것 같다.
...버전관리 만세! 우리의 작업시간을 매우 단축시켜줬다.
특강에선 익숙하지 않은 InteliJ GUI에서 충돌을 해결하는 방법 위주로 봤다.