어노테이션
•
@GetMapping(”link”) : ~~/link 로 GET 요청을 매핑해준다.
•
@RequestParam(”param”) : ~~/link?param=xxx 같이 URI로 전달된 파라미터를 사용한다.
◦
value : 단독으로 사용할 땐 생략 가능하며 다른 속성과 같이 사용할 땐 파라미터 이름에 붙여 줘야함
◦
required : 기본값은 true로 해당 파라미터를 필수값으로 지정
•
@BeforeEach, @AfterEach : 테스트에서 메서드에 붙여 각 테스트 전, 후마다 실행한다.
•
@ComponentScan : 패키지 단위로 @Component가 붙은 클래스를 스프링 빈으로 자동 등록한다.
•
@Component : @ComponentScan에 의해 자동으로 스프링 빈으로 등록된다.
◦
@Controller : 컨트롤러임을 명시하며 스프링 빈으로 자동 등록됨
◦
@Service : 서비스임을 명시하며 스프링 빈으로 자동 등록됨
◦
@Respository : 레포지토리임을 명시하며 스프링 빈으로 자동 등록됨
•
@Configuration : 해당 클래스가 설정 정보임을 명시한다.
•
@Bean : @Configuration 안에서 해당 클래스를 실행해 생성된 객체를 반환하는 빈을 수동 등록함
•
@SpringBootTest : JVM만을 사용하는 테스트가 아닌 스프링 부트를 사용하는 테스트라고 명시함
•
@Transactional : 테스트 시작하기 전 트랜잭션을 걸고 테스트가 끝난 후 롤백한다.
thymeleaf
•
Controller에서 리턴 값으로 문자를 반환하면 viewResolver가 화면을 찾아서 처리한다.
◦
스프링 부트 템플릿엔진의 기본 viewName 매핑
◦
“resources:templates/” + ViewName + “.html”
콘솔로 빌드하고 실행하기
1. ./gradlew build
2. cd build/libs
3. java -jar 패키지명-0.0.1-SNAPSHOT.jar
4. 실행확인
Java
복사
정적 컨텐츠
•
/hello-static.html에 대한 요청에 스프링은 Controller에서 먼저 찾고 없을 시 static에서 찾는다.
MVC와 템플릿 엔진
//HelloController.java
@Controller
public class HelloController {
@GetMapping("hello-mvc")
public String helloMvc(@RequestParam(value = "name", required = false) String name, Model model) {
model.addAttribute("name", name);
return "hello-template";
}
}
Java
복사
<!-- hello-template.html -->
<!DOCTYPE html>
<html lang="en">
<html xmlns="http://www.thymeleaf.org"></html>
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hello</title>
</head>
<body>
<p th:text="'hello' + ${name}">Hello empty</p>
</body>
</html>
HTML
복사
1.
웹 브라우저로부터 온 /hello-mvc 요청을 톰캣이 받은 후 스프링에게 전달한다.
2.
스프링은 hello-mvc가 Controller를 뒤져 매핑된 메서드를 찾는다.
3.
Controller 로직 처리 후 model과 html 이름을 스프링에게 반환
4.
스프링은 viewResolver에게 전달
5.
viewResolver는 html 이름에 해당하는 뷰를 찾고 thymeleaf 템플릿 엔진에 전달
6.
thymeleaf 템플릿 엔진은 html을 변환 후 웹 브라우저에 전달한다. (정적일 땐 변환 X)
API
•
@ResponseBody 문자 반환
@GetMapping("hello-string")
@ResponseBody
public String helloString(@RequestParam("name") String name) {
return "hello " + name;
}
Java
복사
◦
viewResolver를 사용하지 않고 HTTP Body에 문자 내용을 직접 반환한다. (HTML TAG X)
◦
viewResolver 대신 HttpMessageConverter가 동작한다.
▪
기본 문자처리 : StringHttpMessageConverter
▪
기본 객체처리 : MappingJackson2HttpMessageConverter
▪
byte 처리 등 기타 여러 HttpMessageConverter가 기본으로 등록되어 있다.
•
@ResponseBody 객체 반환
@GetMapping("hello-api")
@ResponseBody
public Hello helloApi(@RequestParam("name") String name) {
Hello hello = new Hello();
hello.setName(name);
return hello;
}
Java
복사
◦
@ResponseBody로 객체를 반환하면 기본적으로 JSON으로 변환된다.
•
@ResponseBody가 붙어있으면 HttpMessageConverter가 동작한다.
◦
내용이 문자면 StringConverter, 객체면 JsonConverter 등
일반적인 웹 애플리케이션 계층 구조
•
컨트롤러 : 웹 MVC의 컨트롤러 역할
•
서비스 : 핵심 비즈니스 로직 구현
•
리포지토리 : 데이터베이스에 접근, 도메인 객체를 DB에 저장하고 관리
•
도메인 : 비즈니스 도메인 객체 (회원, 주문, 쿠폰 등등 주로 데이터베이스에 저장하고 관리됨)
순수 JDBC
•
DataSource는 데이터베리스 커넥션을 획득할 때 사용하는 객체이다.
•
스프링 부트는 데이터베이스 커넥션 정보를 바탕으로 DataSource를 생성하고 스프링 빈으로 만들어두기 때문에 DI를 받을 수 있다.
//MemberRepository를 구현한 JdbcMemberRepository 구현 객체
public class JdbcMemberRepository implements MemberRepository {
private final DataSource dataSource;
@Autowired
public JdbcMemberRepository(DataSource dataSource) {
this.dataSource = dataSource;
}
@Override
public Member save(Member member) {
String sql = "insert into member(name) values(?)";
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
conn = getConnection();
pstmt = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
pstmt.setString(1, member.getName());
pstmt.executeUpdate();
rs = pstmt.getGeneratedKeys();
if (rs.next()) {
member.setId(rs.getLong(1));
} else {
throw new SQLException("id 조회 실패");
}
return member;
} catch (Exception e) {
throw new IllegalStateException(e);
} finally {
close(conn, pstmt, rs);
}
}
...
}
Java
복사
//Config
@Configuration
public class SpringConfig {
private final DataSource dataSource;
@Autowired
public SpringConfig(DataSource dataSource) {
this.dataSource = dataSource;
}
...
Java
복사
•
sql 에 sql문을 미리 작성해둔다. (변하는 값은 ?로 적어둠)
•
Connection은 dataSource.getConnection()을 통해 데이터베이스 커넥션을 받아온 것이다.
•
PreparedStatement.setString()은 sql문을 작성하기 위한 것이다.
•
PreparedStatement.executeUpdate()는 sql문을 db에 날려주는 것이다.
•
ResultSet은 sql문 실행 결과를 받아오기 위한 것이다.
•
데이터베이스 커넥션은 close를 통해 사용 후엔 릴리즈를 꼭 해줘야한다!
DB 통합 테스트
@SpringBootTest
@Transactional
class MemberServiceIntegrationTest {
@Autowired MemberService memberService;
@Autowired MemberRepository memberRepository;
@Test
void join() {
Member member = new Member();
member.setName("hello");
Long saveId = memberService.join(member);
Member findMember = memberService.findOne(saveId).get();
assertThat(member.getName()).isEqualTo(findMember.getName());
}
@Test
void duplicatedMember() {
Member member1 = new Member();
member1.setName("spring");
Member member2 = new Member();
member2.setName("spring");
memberService.join(member1);
IllegalStateException e = assertThrows(IllegalStateException.class, () -> memberService.join(member2));
assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다.");
}
}
Java
복사
•
@SpringBootTest를 붙여 스프링 컨테이너까지 올려 테스트한다.
•
@Transactional을 테스트에 붙이면 테스트 시작 전 트랜잭션을 걸고 테스트 완료 후 롤백을 한다.
JDBC Templates
public class JdbcTemplateMemberRepository implements MemberRepository {
private final JdbcTemplate jdbcTemplate;
@Autowired
public JdbcTemplateMemberRepository(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
private RowMapper<Member> memberRowMapper() {
return (rs, rowNum) -> {
Member member = new Member();
member.setId(rs.getLong("id"));
member.setName(rs.getString("name"));
return member;
};
}
@Override
public Member save(Member member) {
SimpleJdbcInsert jdbcInsert = new SimpleJdbcInsert(jdbcTemplate);
jdbcInsert.withTableName("member").usingGeneratedKeyColumns("id");
Map<String, Object> parameters = new HashMap<>();
parameters.put("name", member.getName());
Number key = jdbcInsert.executeAndReturnKey(new MapSqlParameterSource(parameters));
member.setId(key.longValue());
return member;
}
...
Java
복사
•
DataSource를 매개변수로 전달해 JdbcTemplate를 DI 받아준다.
•
RowMapper를 통해 데이터베이스로부터 데이터를 받아온다.
•
JdbcTemplate.query를 통해 sql문, mapper, args를 보내 sql를 실행하고 결과를 받는다.
JPA
•
기존의 반복 코드는 물론이고 기본적인 SQL도 JPA가 직접 만들어서 실행해준다.
•
JPA를 사용하면 SQL과 데이터 중심의 설계에서 객체 중심의 설계로 패러다임을 전환할 수 있다.
•
JPA를 사용하면 개발 생산성을 크게 높일 수 있다.
//gradle.build
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
//application.properties
spring.jpa.show-sql=true //JPA가 생성하는 SQL을 출력
spring.jpa.hibernate.ddl-auto=none //테이블을 자동으로 생성하는 기능 (none은 끄기, create는 엔티티를 바탕으로 테이블 생성)
Java
복사
//domain.Member
@Entity
public class Member {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
...
Java
복사
//MemberService
@Transactional
public class MemberService {
Java
복사
//JpaMemberRepository
public class JpaMemberRepository implements MemberRepository {
private final EntityManager entityManager;
@Autowired
public JpaMemberRepository(EntityManager entityManager) {
this.entityManager = entityManager;
}
@Override
public Member save(Member member) {
entityManager.persist(member);
return member;
}
@Override
public Optional<Member> findById(Long id) {
Member member = entityManager.find(Member.class, id);
return Optional.ofNullable(member);
}
@Override
public Optional<Member> findByName(String name) {
List<Member> result = entityManager.createQuery("select m from Member m where m.name = :name", Member.class).setParameter("name", name).getResultList();
return result.stream().findAny();
}
@Override
public List<Member> findAll() {
List<Member> result = entityManager.createQuery("select m from Member m", Member.class).getResultList();
return result;
}
}
Java
복사
//SpringConfig
@Configuration
public class SpringConfig {
private EntityManager entityManager;
@Autowired
public SpringConfig(EntityManager entityManager) {
this.entityManager = entityManager;
}
@Bean
public MemberService memberService() {
return new MemberService(memberRepository());
}
@Bean
public MemberRepository memberRepository() {
return new JpaMemberRepository(entityManager);
}
}
Java
복사
•
domain 객체에 @Entity를 통해 테이블과 매핑할 수 있다.
•
@Id 를 통해 기본키와 매핑할 수 있으며 Entity 하나당 최소 1개가 있어야 한다.
•
@GenratedValue 는 기본키 생성 전략을 지정할 수 있다.
•
@Column(name = “”)을 통해 변수의 이름이 컬럼의 이름과 다르더라도 매핑할 수 있게 한다.
•
JPA를 통한 모든 데이터 변경은 반드시 트랜잭션 안에서 실행 (Service에 꼭 @Transactional을 붙이자)
스프링 데이터 JPA
•
스프링 데이터 JPA는 JPA를 사용하는 프레임워크이다.
•
JPA를 기반으로 기본적인 CRUD나 단순기능들을 기본적으로 제공해준다.
//SpringDataMemberRepository.interface
public interface SpringDataJpaMemberRepository extends JpaRepository<Member, Long>, MemberRepository {
Optional<Member> findByName(String name);
}
Java
복사
//SpringConfig
@Configuration
public class SpringConfig {
private final MemberRepository memberRepository;
@Autowired
public SpringConfig(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
@Bean
public MemberService memberService() {
return new MemberService(memberRepository);
}
}
Java
복사
AOP (Aspect Oriented Programming)
•
공통 관심 사항(cross-cutting concern) , 핵심 관심 사항(core concern) 분리
•
AOP 적용 후 작동원리
@Aspect
@Component
public class TimeTraceAop {
@Around("execution(* com.example.demo2..*(..)))")
public Object execute(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
System.out.println("Start : " + joinPoint.toString());
try {
return joinPoint.proceed();
} finally {
long finish = System.currentTimeMillis();
long timeMs = finish - start;
System.out.println("End : " + joinPoint.toString() + " " + timeMs + "ms");
}
}
}
Java
복사
•
@Aspect 를 붙여야 AOP를 사용할 수 있다
•
@Component를 이용해 빈으로 등록해도 되지만 @Bean으로 수동 등록을 하는 것이 좋다. (명시적)
•
@Around(””)를 통해 표현식을 사용해 적용할 곳을 지정한다.