스프링을 공부하고 정리하고자 남겼습니다.
기억이 애매하면 ctrl+f 하기 위해 남깁니다.
Mybatis 는 따로 분리되어 있습니다.
인프런 김영한님 완전 정복 로드맵
스파르타 웹개발의 봄, Spring
-
A
-
B
-
C
cascadeType.Persist : 부모가 영속되면 자식도 적용
실제로는 반드시 부모 자식의 관계라고 생각하시기보단
cascadeType.Persist 가 들어간 클래스와 그 연관된 클래스 관계입니다.@OneToMany, @ManyToOne 관계를 만든다고 가정하겠습니다.
CascadeType.PERSIST 를 적용하고 싶다고 생각하겠습니다.
여기에서 잠시 질문을 하나 드리면@OneToMany(cascade = CascadeType.PERSIST) 가 맞을까요?
아니면
@ManyToOne(cascade = CascadeType.PERSIST) 가 맞을까요?여기에서 정답은?
"전혀 상관 없다." 입니다.@OneToMany, @ManyToOne 관계는 상관이 없습니다.
CascadeType.PERSIST 가 의미하는 바는
내가 쓰인 곳이 PERSIST 된다면, 연관관계의 entity 도 PERSIST 해줘 입니다.예를 들어
Parent parent = new Parent(); parent.addChild(new Child()); em.persist(parent);
를 한다면, persist는 parent 하나만 적었지만
parent 와 child 둘다 persist 되어 저장됩니다.entityManager 가 죽어가서 JPARepository 로 바꿔보면
Parent parent = new Parent(); parent.addChild(new Child()); parentRepository.add(parent);
가 되겠습니다.
@OneToOne, @OneToMany, @ManyToOne, 어떤 관계이든
persist 가 되는 객체에서 지정하여 사용할 수 있습니다.cascadeType.Remove 만큼 조심하게 사용하실 필요는 없지만
단일하고 연속적으로 생성되는 entity 에 사용하면 좋을 것 같습니다.
생각좀 하고 살자 도형아.
cascadeType.Remove : 부모가 삭제되면 자식도 적용
실제로는 반드시 부모 자식의 관계라고 생각하시기보단
cascadeType.Remove 가 들어간 클래스와 그 연관된 클래스 관계입니다.cascadeType.Remove 는 .Persist 와 같은 맥락입니다.
cascadeType 가 이해가 안되신다면, 먼저 .Persist 를 보고 와주세요.그런데 여기에서 궁굼증이 하나 생겼습니다.
다음과 같이 저장되어 있다고 할 때
parentClass 에서는
@OneToMany(mappedBy = "parent", cascade = CascadeType.REMOVE) List<Child> children = new ArrayList<>();
다음과 같이 정의되어 있다면...
A
Parent parent = em.find(Parent.class, 1L); parent.getChildren().remove(0); em.persist(parent); em.flush(); em.clear();
B
Parent parent = em.find(Parent.class, 1L); // parent.getChildren().remove(0); em.remove(parent); em.flush(); em.clear();
A 와 B 중에서 어떤 방법이 children 을 삭제할까요?
직접 확인해본 결과 정답은 B 였습니다.A 에서 삭제가 적용되길 원하신다면 orphanRemoval 을 사용하셔야 합니다.
cascadeType.All : 부모의 영속성 컨텍스트를 자식도 적용
위 2개 외에도 모든 cascadeType 을 자식에게 적용합니다.
-
D
ddl-auto : 데이터베이스 스키마 생성 방침
application properties 에
spring.jpa.hibernate.ddl-auto=
형태로 존재한다.create : 기존 태이블 삭제 후 다시 생성
create-drop : create 와 같지만 종료 시점에 삭제
update : 변경분만 반영
validate : 엔티티와 테이블이 매핑되었는지 확인
none : 사용안함개발 초기 단계 : create, update
테스트 서버 : update, validate
운영 서버 : validate, none
DTO
data-transfer-object
데이터가 변경되며 전송될 경우 사용하는 클래스입니다.
여기에서 전송이 된다는 것은 controller -> service 같이 layer 를 통과하여 전송되는 의미입니다.데이터의 값들이 코드가 진행되면서 변경되며 조작되어야 할 때 사용합니다.
굳이 변경이 되지 않는다면 VO 를 사용합시다.@Getter
,@Setter
를 포함시켜 코드가 진행되는 중 setter 를 통해 변경시킵니다.@Getter @Setter public class TravelerDTO { private String name; private Integer age; } public class Travel { public void travelForYear(TravelerDTO travelerDTO){ travelerDTO.setAge(travelerDTO.getAge() + 1); } }
@Setter 의 추가는 persistance 안에 들어가서 ORM 된 클래스에만 없어야 하는 것입니다.
ORM 된 이후에 변경이 되면 db 에도 변경되기에 혼선이 생기는 것을 방지하기 위해 orm 클래스의 @Setter 를 방지하는 것입니다.
DAO
data access object
데이터의 접근을 위한 클래스를 뜻합니다.
CRUD 같이 데이터의 접근을 위한 function 들을 정의하고 있습니다.예를 들어 jpa 의 repository / mybatis 의 wrapper 는 DAO 입니다.
@Repository public interface UserDAO { // DAO 라는 명칭이나 repository 라는 명칭이나 중요하지 않지만 public void createUser(); // 회사의 규졍과 규칙을 따르는 것은 중요합니다. public void deleteUser(); // 엔트로피를 생성하지 맙시다 ㅋㅋ public User getUserById(Integer id); }
-
E
@Enheritance 로 부모 클래스를 구현하자
@SupperMapping 의 java 내부 구조는 같습니다.
Extends 를 사용해서 부모를 지정해주는 형식입니다.
다만 데이터베이스는 3가지로 다르게 구성할 수 있습니다.
이 방식들에 따라 @SuperMapping 은 3가지 전략이 있습니다.JOIN 전략
@Entity @Enheritance(strategy = InheritanceType.JOINED) @DiscriminatorColumn public class Item{ ... }
자식 클래스 Album/Movie/Book
@Entity @DiscriminatorValue("A") public class Album extends Item{ ... }
ALBUM, MOVIE, BOOK 은 각각 ITEM 의 ID를 받아오는 것을 알 수 있습니다.
여기에서 DTYPE 이라는 column 이 하나 존재하는데
이 column 은 @DiscriminatorColumn 을 추가하면 생기는
column 으로 자식이 어떤 class 에 속해있는지 String 으로 나타냅니다.
@DiscriminatorColumn 은 생략이 가능합니다.만약 테이블명 그대로 DTYPE 에 지정되는 것을 바꾸고 싶다면
@DiscriminatorValue("A")으로 직접 자식 클래스에서 구분지을 수 있습니다.저장은 java 에서 자식 클래스를 사용하면 부모의 parameter 에 접근할 수 있음으로
Album album = new Album(); album.setName("Son lux - Bones"); em.persist(album);
같이 사용하시면 됩니다.
SINGLE_TABLE 전략
jpa default 전략입니다.
Child 와 Parent 를 단일한 테이블에 저장하는 단순한 방법입니다.
Join 을 하지 않아 SQL query 의 성능적 이점을 갖습니다.
다만 정규화 법칙을 따르지 않아 생기는 문제들도 고려해야 합니다.부모 클래스의 item
@Entity @Enheritance(strategy = InheritanceType.SINGLE_TABLE) //@DiscriminatorColumn public class Item{ ... }
자식 클래스의 Album/Movie/Book
@Entity @DiscriminatorValue("A") public class Album extends Item{ ... }
모든 내용은 위와 비슷비슷합니다.
그런데 여기에서 특이한 점은@DiscriminatorColumn
이 없어도
무조건 DTYPE 의 column 이 만들어진다는 점입니다.
DTYPE 가 만들어지지 않는다면 어떤 자식인지를 구분하는 것이 불가능합니다.
물론 null 값이 어디에 들어갔는지 확인하는 방법이 있겠지만...
효율적인 방법이라고 보지 않습니다.
마지막으로 null 값들이 들어가서 DB 분들이 좋아하지 않습니다.TABLE_PER_CLASS 전략
이 전략은 부모 테이블을 생략하고 자식 테이블에 부모 param을 모두 넣는 전략입니다.
부모 클래스의 item@Entity @Enheritance(strategy = InheritanceType.SINGLE_TABLE) public class Item{ ... }
자식 클래스의 Album/Movie/Book
@Entity public class Album extends Item{ ... }
여기에서 주의해서 보셔야 할 점은
@DiscriminatorColumn
,@DiscriminatorValue
가 없어진다는 점입니다.
위의 그림을 자세히 보시면 각 child 가 테이블로 분리되어서
DTYPE 이 필요가 없어진다는 것을 알 수 있습니다.이 전략은 치명적인 단점이 있습니다.
한개의 id를 통해 찾으려고 한다면 3개의 테이블을 조회해야 합니다. 그래서 현업에서는 사용되지 않습니다.
@Entity 에서는 setter 를 열지 말자
@Setter 가 열려 있어 너무 많은 변경 포인트가 있다면 유지보수가 어렵다
@Setter 는 그 의도를 파악하기 어렵다.
또한 객체의 일관성을 보장하기 어렵다.
Constructor, builder 를 최대한 활용하자.
가령 사용하더라도 정해진 비즈니스 로직을 짜서 규정하고 사용하자.
@Embeddable, @Embedded 를 활용하자
Entity 내부 Class 를 관계형 연결(일 대 다, 일 대 일...etc) 없이 나타낼 수 있다.
@Embedded 안에는 @NoArgsConstructor 가 필요하다.Java 내에서만 적용된다
@Embeddable
@Embeddable @Getter public class Address { private String city; private String street; private String zipcode; }
@Embedded
@NoArgsConstructor(access = AccessLevel.PROTECTED) @Getter @Entity public class Member { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "member_id") private Long id; @Column private String name; @Embedded private Address address; }
Java 내에서만 적용되며 database 에 관계형으로 정의되지 않는다.
이제 조금 더 심화된 내용으로 들어가면...
임베디드의 연관관계
임베디드 클레스에 임베디드 클래스를 넣으면 조금 더 정리 정돈이 될 수 있습니다.
@Entity 안에 @Embedded 안에 @Embedded 를 넣어봅시다.
@Entity public class Parent { @Id @GeneratedValue private Long id; @Embedded private Embedable embedable; }
entity
@NoArgsConstructor @Embeddable public class embeddable { private String jack; @Embedded private Embedable2 embedable2; }
entity -> embedded
@NoArgsConstructor @Embeddable public class embeddable2 { private String shit; }
entity -> embeddable -> embeddable2
모든 embeddable 은 반드시 @NoArgsConstructor 를 갖고 있어야 합니다.
이렇게 만들면
이제 entity -> embeddable -> embeddable2 관계는 가능하니...
entity -> embeddable -> entity2 을 구현해보겠습니다.위의 클래스는 다 그대로 가져가는데 Embeddable2 만 좀 수정하면 됩니다.
@NoArgsConstructor @AllArgsConstructor @Getter @Embeddable public class Embeddable2 { private String jack; // 위어서 추가되는 부분 @OneToOne @JoinColumn(name = "ENTITY_CHILD_ID") private EntityChild entityChild; }
그리고 추가되는 EntityChild Class 를 만듭니다.
@Getter @NoArgsConstructor @Entity @Table(name = "ENTITY_CHILD") public class EntityChild { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; }
하면서 이상하게 query 가 테이블 명을 잘못 적어서 @Table 을 지정해줘야 했지만 이정도 에러들은 다 잡아내실 수 있는 분들이라 믿습니다.
@Enumerated(EnumType.STRING) 로 enum column 을 만들자
반드시 EnumType.STRING 을 사용하자
Integer 는 enum 이 삭제/변경되었을 때 같은 숫자가 중복되어 사용된다enum OrderStatus { ORDER, CANCEL; }
@Getter @Entity public class Order { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Enumerated(EnumType.STRING) private OrderStatus status; }
-
F
Fetchtype 은 반드시 Lazy 로 지정한다
모든 연관된 테이블들이 딸려 나와서 n+1 폭탄을 맞고 싶지 않다면 lazy 로 사용하자
쿼리는 본인이 customize 하여 최적화 할 수 있게끔 만들어야 한다fetchtype 을 지정하지 않은 경우
@ManyToOne 의 경우 FetchType 은 eager 이며
@OneToMany 의 경우 FetchType 은 lazy 이다.@ManyToOne 에 주의하자.
lazy 가 설정된 이후 getter 를 사용하면 query 가 나간다.
n+1 문제는 여기에도 정리되어 있으니 확인하자.
Foreign key 를 사용한다면 반드시 name 을 지정한다
연관 관계 중 foreign key 가 생성된다면 spring 에서 임의로 이름을 만든다
임의로 지정된 이름은 JJ9J21D82 같은 gibberish 이기 때문에 user_account_fk 처럼 정의하자@JoinColumn(name = "account_id", foreignKey = @ForeignKey(name = "user_account_fk"))
반드시 fk를 사용하지 않아도 된다.
조금 더 유연한 시스템을 운영하고 싶다면 fk를 사용하지 않고 연결관계를 정의만 하는 것 또한 하나의 방법이다. -
G
-
H
H2 : 로컬에서 간편하게 연결하는 임시 데이터베이스
-
설치
우선 spring initializr 에서 h2 가 추가됬는지 확인한다.
h2 설치 링크
설치된 경로에서 \H2\bin 을 들어간다.
h2.sh 또는 bat 을 실행한다.
localhost:8082 를 들어간다.
JDBC url 을 jdbc:h2:file:~/testDB 로 바꾼다.
:mem 의 경우 메모리 :file 의 경우 파일 저장이다.
~/의 경로는 user 에서 시작된다.
저장된 파일 경로 예시 : C:\Users\doe\testDB.mv.db
jdbc:h2:file:D:\Program Files (x86)\H2\saved\testDB 로 특정 경로를 지정해도 된다.
-
연결
스프링과 h2의 연결은 application.properties 에서spring.datasource.url=jdbc:h2:tcp://localhost/~/testDB spring.datasource.driverClassName=org.h2.Driver spring.datasource.username=sa spring.datasource.password= spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
testDB는 사용자가 지정한 명칭으로 바꿔도 된다.
그 이외에 추가하면 좋은 properties 도 잊지 말자# 테이블이 없다면 자동으로 생성한다. spring.jpa.hibernate.ddl-auto=create # sql 을 로깅으로 표시한다. # spring.jpa.show-sql=true logging.level.org.hibernate.sql = debug
-
-
I
-
J
-
K
-
L
-
M
@ManyToMany 관계는 절대 사용하지 말자
many to many 사이의 테이블은 entity 로 정의되지 않는다.
Relation 으로 자동 생성되어서 변경에 용의하지 않다.중간 테이블 엔티티를 직접 만들고 사용하도록 하자.
@ManyToOne 단방향 매핑
Team 과 Member class 가 있다고 가정하면
Member class 에서@ManyToOne @JoinColumn(name = "team_id") private Team team;
으로 team 을 매핑한다.
그리고 단방향임으로 Team class 에서는 members 을 참조하지 않는다.
그럼으로 Team class 에서List<Member> members
는 존재하지 않아도 된다.
@ManyToOne 양방향 매핑
Team 과 Member class 가 있다고 가정하면
Member class 에서@ManyToOne @JoinColumn(name = "team_id") private Team team;
Team class 에서
@OneToMany(mappedBy = "team") private List<Member> members = new ArrayList<>();
로 매핑한다.
여기에서
@OneToMany
의mappedBy
는 member class 의 Team 객체 명 team 을 지정한다.또한 Many 쪽이 외래키를 가져야 하고 One 쪽은 mapping 을 당하는 쪽이라서 수동태의 mappedBy 가 지정되는 것이다.
@MappedSuperclass 로 공통된 column 들을 묶자
두가지 객체에서 공통된 column 이 많이 사용된다 싶으면
DB 에 적용되지 않으면서 다른 class 에 옮겨 통일시킬 수 있습니다.BaseEntity
@MappedSuperclass public abstract class BaseEntity{ @Column private String name; }
Member
@Entity public class Member extends BaseEntity{ ... }
@Inheritance 와 다른 점이라고 한다면
Inheritance 는 부모관계에 따라 SQL 에도 적용되는 strategy 들이 있다는 점이다.
그래서em.find(BaseEntity.class, id)
같은 조회는 불가능하다.
또한 직접 사용할 일이 없다면 abstract 를 붙여 추상 클래스로 바꾸자.
-
N
n+1 문제 : SELECT * query 이후 나온 n 만큼 SELECT 하는 쿼리 폭탄
이런 쿼리문이 보인다면 n + 1 문제입니다.
Spring 에서 n + 1 이 생긴다면 fetchtype.eager 를 하였는지 확인합시다.
For each 문에서도 n + 1 이 발생할 수 있습니다.해결법은 크게 2가지 있습니다.
Join fetch
@Query("select a from books a join fetch a.users") List<books> findAllJoinFetchUsers();
2중 join fetch
@Query("select a from books a join fetch a.users u join fetch u.account") List<books> findAllJoinFetchUsersWithAccount();
join fetch 는 inner join 입니다.
Entity graph
@EntityGraph(attributePaths = "users") @Query("select a from book a") List<Academy> findAllEntityGraphUsers();
2중 entity graph
@EntityGraph(attributePaths = {"users", "users.account"}) @Query("select a from book a") List<Academy> findAllEntityGraphUsersWithAccount();
entity graph 는 left outer join 입니다.
-
O
@OneToMany 단방향 매핑
@OneToMany @JoinColumn(name = "team_id") private List<Member> members = new ArrayList<>();
를 하면 되고 단방향이기에 member class 에서 지정하지 않는다.
동작은 한다. 다만!
다음과 같은 구조에는 성능 최적화에 큰 문제가 있다.예를 들어
Member member = new Member("김도형"); em.persist(member); Team team = new Team("1조"); team.getMembers().add(member); em.persist(team);
에서
Team team = new Team("1조"); team.getMembers().add(member); em.persist(team);
이 부분이 실행 될 때
Team 객체 내에서는 members 가 존재하지만
TEAM 테이블에는 members 를 지정할 수 있는 방법이 없다.
그래서 hibernate 는 MEMBER table 의 TEAM_ID 를 조작하여
UPDATE query 를 추가하게 되어 낭비가 일어난다.@ManyToOne 단방향이나 양방향을 활용하자.
Java 와 SQL 의 연결 구성이 같은 곳에서 일어나는 것을 알 수 있어 더 단순하고, 더 최적화에 용이하다.
위의 문제도 member 는 team_id를 바로 지정하며 insert 되게 바뀐다.
@OneToOne 단방향 매핑
@OneToOne @JoinColumn(name = "LOCKER_ID") private Locker locker;
로 지정하면 된다.
반대로 locker 를 주체로 만들고 싶다면
locker 에private Member member
를 지정하면 된다.단방향임으로 반대쪽에는 참조를 넣지 않는다.
@OneToOne 양방향 매핑
양방향의 경우 java 내에서는 양쪽이 그 반대쪽의 참조 변수를 갖고 있다.
다만 SQL 은 member 한쪽에서만 foreign key 를 들고 있는 것을 볼 수 있다.그럼으로 fk 가 들어있는 Member class 에서는
@OneToOne @JoinColumn(name = "LOCKER_ID") private Locker locker;
참조만 되는 Locker class 에서는
@OneToOne(mappedBy = "locker") private Member member;
같이 작성하면 된다.
여기에서의 중점은
@JoinColumn
이 지정된 쪽이 외부키 column 을 만드는 쪽이고
mappedBy
를 사용하는 곳이 위의 외부키로 mapping 당하는 쪽이다.
OrphanRemoval = true 로 연관관계 내의 자식을 자동 삭제하자
CascadeType.REMOVE 는 부모가 삭제되면 자식이 삭제된다는 성질을 뜁니다.
하지만 .REMOVE 는 arrayList 같이 지정된 parameter 에서 remove 된 자식은 적용하지 않았습니다.
이러한 기능을 OrphanRemoval 이 수행해줍니다.@OneToMany(orphanRemoval=true) List<Child> children = new ArrayList<>();
로 parent 에서 지정된다면
자식이 1개 있을 때Parent parent = em.find(Parent.class, 0); parent.getChildren().remove(0); em.persist(parent);
하면 자식이 자동으로 삭제된다는 의미입니다.
물론 이는 @OneToMany 의 관계 뿐만 아니라 @OneToOne 관계에도 적용됩니다.
List 가 아니라 단일 parameter 도 setter 로 null 을 만들어 삭제할 수 있다는 의미입니다.Parent parent = em.find(Parent.class, 1L); parent.setChild(null); em.persist(parent);
그리고 CascadeType.REMOVE 의 특징을 그대로 갖고 있습니다.
부모 entity 가 삭제되면 자식 entity 도 삭제됩니다.어! 그러면 persist 된 객체에 그대로 적용하면 데이터베이스에도 바로 적용되니까 좋잖아?!
개꿀! 하면서 남용하시면 큰일납니다.우선 관계가 완전히 단일하며 종속적이여야 합니다.
즉 child 는 parent 외 다른 곳에 fk 가 있으면 안됩니다.
fk 에러가 뜨면서 삭제가 안될 수 있습니다.
당연한 예기일지도 모르겠지만 그래도 2번 체크합시다.
OrphanRemoval = true 의 자식은
부모에게 완전 종속적인가요?
다른 곳의 fk 로 사용되고 있지 않나요?
-
P
Persistance.xml : 설정용 파일
application.properties 의 구 버전이다. resources/META-INF/persistence.xml 에 들어간다.<?xml version="1.0" encoding="UTF-8"?> <persistence version="2.2" xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_2.xsd"> <persistence-unit name="hello"> <properties> <!-- 필수 속성 --> <property name="javax.persistence.jdbc.driver" value="org.h2.Driver"/> <property name="javax.persistence.jdbc.user" value="sa"/> <property name="javax.persistence.jdbc.password" value=""/> <property name="javax.persistence.jdbc.url" value="jdbc:h2:tcp://localhost/~/testDB"/> <property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect"/> <!-- 옵션 --> <property name="hibernate.show_sql" value="true"/> <property name="hibernate.format_sql" value="true"/> <property name="hibernate.use_sql_comments" value="true"/> <!--<property name="hibernate.hbm2ddl.auto" value="create" />--> </properties> </persistence-unit> </persistence>
<persistence-unit name="hello">
에서 name 을 지정해 주면Persistence.createEntityManagerFactory("hello"); EntityManager em = emf.createEntityManager();
으로 연결된다.
<property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect"/>
의 dialect 는 방언인데 다양한 데이터베이스를 사투리로서 이해하고 있다.H2Dialect
로 주어진 사투리를 표준말로 변경하겠다는 의미이다.필요에 따라 추가적으로 옵션을 넣을 수 있다.
Proxy : entityManager.getReference 로 받아진 lazy query 타입
프록시는 처음 em.getReference 로 받아집니다.
받아진 프록시를 .getClass 를 할 경우$HibernateProxy$ 하고 타입명이 다르게 나온다는 것을 볼 수 있습니다.이제 get을 한다면 어떻게 될까요?
프록시 안에는 target 이라고 정보를 가져올 객체의 포인터가 있습니다.
해당 포인터가 null 을 가리킨다면 db에 쿼리를 날리고 target 을 체우는 형식으로 동작합니다.그렇다면...
Member member = em.getReference(Member.class, 1L); member.getName(); soutv(member.getClass());
는 어떻게 출력될까요?
프록시로 출력될까요? 아니면 Member 로 출력될까요?정답은 프록시입니다.
위의 정답이 프록시로 나온다면 저희는 또 한가지 주의할 점이 있습니다.
바로 타입 체크에서 주의해야 한다는 점입니다.
member.getClass() == Member.class
를 사용할 것이 아니라...
member1 instanceof Member
를 사용하셔야 일치하게 나옵니다.
해당 프록시는 Member 를 상속받기 때문에 instanceof 가 true 로 나옵니다.
-
Q
-
R
(Relational methods) 연관관계 편의 메소드를 사용하자
관계가 있는 entity 를 저장할 때 양측의 객체를 변경해야 한다.
이를 method 로 묶자.// many to one 관계 (Child to Parent) public void setParent(Parent parent){ this.parent = parent; parent.getChildren().add(this); } // one to many 관계 (Parent to Child) public void addChild(Child child){ children.add(child); child.setParent(this); } // one to one 관계 (Husband to Wife) public void setWife(Wife wife){ this.wife = wife; wife.setHusband(this); } // many to many 는 사용하지 말자
주의할 점이 있는데
연관 관계 메소드가 정의된 곳을 규정해야 한다.
Child 에 정의될지 Parent 에 정의될지 일관성이 필요하다.
예를 들어 foreign key 가 단일 방향으로 설계되었다면
Foreign key 가 규정된 entity 에 정의한다던지 원칙을 정해야 한다.또한 양측에 편의 메소드를 정의하여 중복해서 넣는 실수가 생길 수 있다.
단일한 방향에 setter를 만들어 사용을 강제하는 것이 좋다.
이제부터 ResponseEntity 로 모든 RestController 를 return 하자
// 상품 상세 페이지 (단일 상품 정보 가져오기) @GetMapping(value = "product/{id}") public ResponseEntity<ResponseDTO> getById(@PathVariable long id) { return ResponseEntity.ok().body(productService.findOne(id)); }
이 코드에서는 builder method 를 사용했습니다.
ResponseEntity
.ok
로 이어지는 것을 보면
ResponseEntity
빌더에 httpStatusCode 를 지정할 수 있습니다.
그래서 이렇게 변경이 가능합니다.ResponseEntity.ok()
200
ResponseEntity.badRequest()
400
ResponseEntity.internalServerError()
500이 외에도 3~4개 정도 더 있지만 다양한 status 들을 모두 포함하고 있지는 않습니다.
그래서 builder 안에는
.status()
구문이 있는데
ResponseEntity.status(HttpStatus.ACCEPTED)
같이 사용하면
HttpStatus
클래스 내의 다양한 status code 들을 사용할 수 있습니다.이 외에도
new ResponseEntity<>(response, HttpStatus.ACCEPTED);
같은 방법도 있습니다.
이는 앞에 선언 후 중간에 response 를 변경해야 할 경우에 사용하면 좋을 것 같습니다..contentType(MediaType.APPLICATION_JSON)
같이
application type header 를 추가할 수 있고.header(HttpHeaders.CONTENT_LANGUAGE,"ko-KR")
처럼
header 에 자주 쓰이는 구문들을HttpHeaders
에서 가져와 사용할 수 있습니다.마지막으로
.body(response)
를 붙여 response 를 돌려주면 됩니다.
-
S
spring-boot-devtools : html 의 캐싱이나 .class 변경을 감지해서 개발을 편리하게 하는 도구
-
설치방법
gradle 의 경우
compileOnly ('org.springframework.boot:spring-boot-devtools')
maven 경우<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <optional>true</optional> </dependency>
compileOnly 의 이유
링크
-
활용
파일이 변경된 경우
ctrl+shift+F9 (build->recompile)
을 눌려 프로젝트에 바로 적용한다.
.html 의 경우 캐쉬 없이 바로 적용되고
.class 의 경우 프로젝트가 rerun 된다.
-
-
T
thymeleaf : ssr과 라우팅을 편리하게 하는 하는 모듈
- 보내고 받는 법
보면
@RequiredArgsConstructor @Controller public class MemberController { @GetMapping("/hello") public String hello(Model model){ model.addAttribute("name","김도형"); return "hello"; } }
@RestController
가 아니라@Controller
를 사용한다는 것을 알 수 있다.
rest 는 rest api, 그냥 controller 는 페이지의 전환을 위해서 주로 사용된다.
return "hello" 는 resources/templates/hello.html 을 반환한다.
model.addAttribute("name","김도형");
로 변수를 전달하면
hello.html 에서
<p th:text="'안녕하세요~' + ${data} + '님'" ></p>
처럼 사용할 수 있다.
- 문법
여기 를 참조
데이터베이스 적용을 원치 않는 변수는 @Transient로 만들자
드랍다운 내용
- 보내고 받는 법
-
U
-
V
VO
value object
값 객체
번역에서도 알 수 있듯이 값을 나타내기 위한 객체로 그 값의 변동이 있어서는 안됩니다.
@Setter 를 설정하지 않고 constructor 를 통해 final 변수들을 지정합니다.db 에서 받은 값을 VO 로 오해하실 수 있지만 DB 에서 받는 값은 DAO data-access-object 입니다.
@Getter public class ResponseVO { private final Integer code; private final Method method; private final String body; // 생성자로 final 값들을 만들고 더이상 변동시키지 않는다. public ResponseVO(Integer code, Method method, String body){ this.code = code; this.method = method; this.body = body; } }
회사에서 이 규칙이 잘 지켜지지 않는다는 것이 함정...
-
W
-
X
-
Y
-
Z