// SpringBoot Security Starter
    implementation 'org.springframework.boot:spring-boot-starter-security'
    // 웹 페이지
    compile('org.springframework.boot:spring-boot-starter-web')
    // lombok
    compile('org.projectlombok:lombok')
    // Jpa
    compile('org.springframework.boot:spring-boot-starter-data-jpa')
    // H2-Database
    compile('com.h2database:h2')
    // Session store
    compile('org.springframework.session:spring-session-jdbc')
    // Mustache
    compile('org.springframework.boot:spring-boot-starter-mustache')
    // Spring Security
    compile('org.springframework.boot:spring-boot-starter-oauth2-client')
    // Swagger
    compile group: 'io.springfox', name: 'springfox-swagger2', version: '2.9.2'
    compile group: 'io.springfox', name: 'springfox-swagger-ui', version: '2.9.2'
    // Maria DB
    compile('org.mariadb.jdbc:mariadb-java-client')
    // Spring Security Test
    testCompile('org.springframework.security:spring-security-test')
    // Test
    testCompile('org.springframework.boot:spring-boot-starter-test')기존 MyBatis: dao 와 조금 다르다. 그간 xml에 쿼리를 담고 클래스는 오로지 쿼리의 결과만 담던 일들이 모두 도메인 클래스에서 해결된다.
/**
 * 어노테이션 순서 : 주요 어노테이션을 클래스와 가깝게
 *
 * @Getter 롬복은 간편하지만 필수는 아님 - 코틀린은 data class 로 롬복 필요 없음
 * @NoArgsConstructor 파라미터가 없는 기본 생성자 생성 -> public Posts() {}
 * @Entity 실제 DB 테이블과 매칭될 클래스, Entity 클래스라고도 부름름
 */
@Getter
@NoArgsConstructor
@Entity
public class Posts extends BaseTimeEntity {
  /**
   * @Id 해당 테이블의 PK
   * @GeneratedValue 스프링 부트 2.0 에서는 IDENTITY 이 붙어야만 auto increment 실행
   */
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;
  /**
   * @Column 테이블 칼럼 선언, 없어도 되지만 length, columnDefinition 옵션을 조정하기 위해서 사용
   */
  @Column(length = 500, nullable = false)
  private String title;
  @Column(columnDefinition = "TEXT", nullable = false)
  private String content;
  private String author;
  @Builder
  public Posts(String title, String content, String author) {
    this.title = title;
    this.content = content;
    this.author = author;
  }
  public void update(String title, String content) {
    this.title = title;
    this.content = content;
  }
}// 나쁜 예 public class Order{ public void setStatus(boolean status){ this.status = status } public void 주문서비스_취소이벤트(){ order.setStatus(false); } } // 올바른 예 public class Order{ public void cancelOrder(){ this.status = false } public void 주문서비스_취소이벤트(){ order.cancelOrder(); } }
- a,b의 위치를 변경해도 코드를 실행하기 전까지 문제를 찾을 수 없다. -> 시간 낭비, 스트레스 폭발 ....
public Example(String a, String b){ this.a = a; this.b = b; }
- 요로코롬 빌더 패턴으로 해주면 어느 필드에 어떤 값을 채워야할지 명확하게 인지할 수 있다.
Example.builder() .a(a) .b(b) .build();
public interface PostsRepository extends JpaRepository<Posts, Long> { /** * 실제로 SpringDataJpa 에서 제공하는 기본 메소드로 해결 가능 * @Query 가 가독성이 좋긴 하다. * 규모가 있는 프로젝트에서는 Querydsl 을 사용하는게 좋다. * 이유 : * 1. 타입 안전성이 보장된다. * 2. 국내 많은 회사에서 사용 중이다.(쿠팡, 배민 등.. JPA 를 쓰는 회사들) * 3. 레퍼런스가 많다 -> 검색 자료가 많아서 좋음 */ @Query("SELECT p from Posts p ORDER BY p.id DESC ") List<Posts> findAllDesc(); }
@RunWith(SpringRunner.class) @SpringBootTest public class PostsRepositoryTest { ` @Autowired PostsRepository postsRepository; ` @After public void cleanup() { postsRepository.deleteAll(); } ` @Test public void 게시글저장_불러오기() { //given String title = "테스트 게시글"; String content = "테스트 본문"; ` postsRepository.save(Posts.builder() .title(title) .content(content) .author("jojoldu@gmail.com") .build()); ` //when List<Posts> postsList = postsRepository.findAll(); ` //then Posts posts = postsList.get(0); assertThat(posts.getTitle()).isEqualTo(title); assertThat(posts.getContent()).isEqualTo(content); } }


Web Layer
Service Layer
Repository
Dtos
Domain Model
이 5가지 레이어에서 비즈니스 처리를 담당해야할 곳은 Domain 이다.
// BaseTimeEntity.java
/**
 * 모든 Entity 의 상위 클래스가 된다.
 * createdDate, modifiedDate 를 자동으로 관리하는 역할.
 *
 * @MappedSuperclass BaseTimeEntity 를 상속할 경우 필드(createdDate, modifiedDate)들도 칼럼으로 인식하도록 한다.
 * @EntityListeners(AuditingEntityListener.class) BaseTimeEntity 클래스에 Auditing 기능을 포함시킨다.
 * @CreatedDate Entity 가 생성되어 저장될 때 시간이 자동 저장된다.
 * @LastModifiedDate 조회한 Entity 의 값을 변경할 때 시간이 자동 저장된다.
 */
@Getter
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class BaseTimeEntity {
  @CreatedDate
  private LocalDateTime createdDate;
  @LastModifiedDate
  private LocalDateTime modifiedDate;
}// Posts.java
...
public class Posts extends BaseTimeEntity {
...  @Test
  public void BaseTimeEntity_등록() {
    //given
    LocalDateTime now = LocalDateTime.of(2019, 6, 4, 0, 0, 0);
    postsRepository.save(Posts.builder()
            .title("title")
            .content("content")
            .author("author")
            .build());
    //when
    List<Posts> postsList = postsRepository.findAll();
    //then
    Posts posts = postsList.get(0);
    System.out.println(">>>>>>>>> createDate=" + posts.getCreatedDate() + ", modifiedDate=" + posts.getModifiedDate());
    assertThat(posts.getCreatedDate()).isAfter(now);
    assertThat(posts.getModifiedDate()).isAfter(now);
  }