// 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);
}