ChoiSing BlogChoiSing's tech blog
/
Fri, 10 Dec 2021 13:34:37 +0000Fri, 10 Dec 2021 13:34:37 +0000Jekyll v3.9.0도서 - 스프링부트 실전 활용 마스터 1,2장<h1 id="스프링부트-실전-활용-마스터--2장">스프링부트 실전 활용 마스터 (~ 2장)</h1>
<h2 id="1장-스프링-부트-웹-애플리케이션-만들기">1장. 스프링 부트 웹 애플리케이션 만들기</h2>
<h3 id="스프링-부트란-무엇인가">스프링 부트란 무엇인가</h3>
<ul>
<li>스프링 포트폴리오를 신속하게 미리 정의된 방식으로, 이식성 있게, 실제 서비스 환경에 사용할 수 있도록 조립해놓은 것
<ul>
<li>신속성 : 의존관계를 포함한 여러 요소에 의사결정을 신속히</li>
<li>미리 정의된 방식 : 기본적인 설정값이 자동 지정.</li>
<li>이식성</li>
</ul>
</li>
<li>스프링 프레임워크 5.0에 포함된 새로운 패러다임 <code class="language-plaintext highlighter-rouge">리액트 프로그래밍</code>
<ul>
<li>대규모 사용자가 지속적으로 증가하는 시스템을 만들다 보면,
<ul>
<li>비동기</li>
<li>논블로킹</li>
<li>방식의 처리가 필요할 때가 있다.</li>
</ul>
</li>
</ul>
</li>
</ul>
<h3 id="리액티브-프로그래밍-소개">리액티브 프로그래밍 소개</h3>
<ul>
<li>리액티브 스트림
<ul>
<li>기존 자원을 더 효율적이고 일관성 있게 사용하는 해법</li>
<li>발행자와 구독자 사이의 간단한 계약
<ul>
<li>트래픽을 가능한한 빨리 발행하는 대신에</li>
<li>구독자가 “난 10개만 더 받을 수 있어” 발행자에게 알림</li>
<li>발행자는 10개만 더보낸다.</li>
<li><code class="language-plaintext highlighter-rouge">수요조절 (demand control)</code>, <code class="language-plaintext highlighter-rouge">배압(backpressure)</code></li>
</ul>
</li>
</ul>
</li>
<li>프로젝트 리액터
<ul>
<li>VM 웨어에서 만든 리액티브 스트림 구현체.</li>
<li>논블로킹, 비동기 프로그래밍 모델</li>
<li>함수형 프로그래밍</li>
<li>스레드를 신경 쓸 필요 없는 동시성</li>
</ul>
</li>
</ul>
<h3 id="리액터-타입">리액터 타입</h3>
<ul>
<li>리액티브 스트림은 수요조절에 기반하고 있다.</li>
<li>프로젝트 리액터는 핵심 타입인 <code class="language-plaintext highlighter-rouge">Flux<T></code> 를 사용하여 수요 조절을 구현한다.</li>
</ul>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>class KitchenService {
Flux<Dish> getDishes() {
return Flux.just(
new Dish("Sesame chicken"),
new Dish("Lo mein noodles, plain"),
new Dish("Sweet & sour beef"))
}
}
</code></pre></div></div>
<ul>
<li><code class="language-plaintext highlighter-rouge">Flux<T></code> 는 실제 물건을 전달해주는 역할을 하는 플레이스홀더
<ul>
<li>비유, 레스토링 서빙 점원과 비슷하다.</li>
<li>주방에서 요리가 완성되면, 주방에서 요리를 받아 손님에게 가져다 주고, 다시 제자리로 돌아와 다음 요리를 기다린다.</li>
<li>서빙 점원은 요리가 언제 주방에서 완성될 지 알 수 없다.</li>
<li>언제가 됐든 요리가 완성되고, 서빙 점원이 그 요리를 받아서 전달할 수 있는 상태라면, 서빙점원은 다음 요리를 손님에게 가져다준다.</li>
</ul>
</li>
<li>서빙점원은 손님에게 가져다줄 <code class="language-plaintext highlighter-rouge">Dish</code> 객체를 달라고 <code class="language-plaintext highlighter-rouge">KitchenService</code> 에 요청할 수 있다. 코드에 나온 세 가지 요리가 모두 완성된 후에 받을수도 있지만, <code class="language-plaintext highlighter-rouge">Flux<Dish></code> 객체로 바로 받을 수도 있다.
<ul>
<li><code class="language-plaintext highlighter-rouge">Flux<Dish></code> 안에 포함된 요리는 아직은 완성되지 않았지만, 머지않아 완성될 것이고, 언제 완성될 지는 알 수 없다.</li>
</ul>
</li>
<li>요리가 완성되면 서빙 점원은 행동(act)에 나설 수 있다. 즉 요리 완성에 대한 <code class="language-plaintext highlighter-rouge">react</code>
<ul>
<li>리액터는 <code class="language-plaintext highlighter-rouge">non-blocking</code> 방식으로 동작하기 때문에, 주방에서 요리가 완성될 때까지 서빙 점원(서버 스레드)이 다른 일을 못 한 채 계속 기다리게 하지 않는다.</li>
</ul>
</li>
<li>결과가 아직 정해지지 않았고 미래 어느 시점이 되어야 알 수 있다는 점에서 <code class="language-plaintext highlighter-rouge">Flux</code> 는 <code class="language-plaintext highlighter-rouge">Future</code>와 비슷하다.
<ul>
<li><code class="language-plaintext highlighter-rouge">Futrue</code> 는 이미 시작되었음을, <code class="language-plaintext highlighter-rouge">Flux</code>는 시작할 수 있음을 나타낸다.</li>
</ul>
</li>
<li><code class="language-plaintext highlighter-rouge">Flux</code> 의 특징
<ul>
<li>하나 이상의 Dish 포함 가능</li>
<li>각 Dish가 제공될 때 어떤 일이 발생하는지 지정 가능</li>
<li>성공과 실패의 두 가지 경로 모두에 대한 처리 방향 정의 가능</li>
<li>결과 폴링 불필요</li>
<li>함수형 프로그래밍 지원</li>
</ul>
</li>
</ul>
<h2 id="2장-스프링-부트를-활용한-데이터-액세스">2장. 스프링 부트를 활용한 데이터 액세스</h2>
<h3 id="리액티브-데이터-스토어-요건">리액티브 데이터 스토어 요건</h3>
<ul>
<li>리액티브 프로그래밍은 여러 가지 오버헤드를 수반하므로 성능저하가 발생한다.
<ul>
<li>사용자 수가 작고 데이터도 많지 않다면 불필요한 오버헤드를 감수하면서 리액티브를 사용하는 것은 낭비다.</li>
<li>하지만 대규모 트래픽, 대용량 데이터 처리 환경에서는 장점이 빛을 발하게 된다.
<ul>
<li>스레드는 어떤 작업이 끝날 때까지 블로킹 되어 기다리지 않고 다른 작업을 수행할 수 있다.</li>
</ul>
</li>
</ul>
</li>
<li>리액티브가 제대로 동작하려면 DB도 리액티브 하게 동작해야 한다.
<ul>
<li>지원하는 DB
<ul>
<li>몽고DB</li>
<li>Redis (Lettuce)</li>
<li>ES</li>
<li>등등</li>
</ul>
</li>
</ul>
</li>
<li>관계형 데이터베이스는 ㄴㄴ</li>
</ul>
<h3 id="테스트-데이터-로딩">테스트 데이터 로딩</h3>
<ul>
<li>애플리케이션 시작 시점에 subcribe() 를 하면 이벤트 루프를 deadlock 상태에 빠뜨릴 수 있는 위험이 존재한다.
<ul>
<li>이유를 모르겠음</li>
</ul>
</li>
</ul>
Tue, 27 Jul 2021 00:00:00 +0000
/book/2021/07/27/%EC%8A%A4%ED%94%84%EB%A7%81%EB%B6%80%ED%8A%B8-%EC%8B%A4%EC%A0%84-%ED%99%9C%EC%9A%A9-%EB%A7%88%EC%8A%A4%ED%84%B0.html
/book/2021/07/27/%EC%8A%A4%ED%94%84%EB%A7%81%EB%B6%80%ED%8A%B8-%EC%8B%A4%EC%A0%84-%ED%99%9C%EC%9A%A9-%EB%A7%88%EC%8A%A4%ED%84%B0.htmljava,book,springBook강의 - Kotlin으로 개발하는 Spring boot (작성중)<h1 id="kotlin으로-개발하는-spring-boot">Kotlin으로 개발하는 Spring boot</h1>
<h2 id="uri-설계원칙-rfc-3986">URI 설계원칙 (RFC-3986)</h2>
<ul>
<li>
<p>URI 마지막 문자로 (/) 는 포함하지 않는다.</p>
</li>
<li>
<p>하이픈(-)은 URI 가독성을 높이는데 사용한다.</p>
</li>
<li>
<p>밑줄(_)은 사용하지 않는다.</p>
</li>
<li>
<p>URI 경로에는 소문자가 적합하다.</p>
</li>
<li>
<p>파일 확장자를 포함하지 않는다.</p>
</li>
<li>
<p>세션 ID를 포함하지 않는다.</p>
</li>
<li>
<p>프로그래밍 언어의 Method 명을 이용하지 않는다.</p>
</li>
<li>
<p>명사에 단수형 보다는 복수형을 사용한다.</p>
</li>
<li>
<p>CRUD 기능을 나타내는 것은 URI 에 사용하지 않는다.</p>
</li>
</ul>
<h2 id="http">HTTP</h2>
<ul>
<li>HTTP status code
<ul>
<li>1xx
<ul>
<li>의미 : 처리중</li>
<li>처리가 계속되고 있는 상태, 클라이언트는 요청을 계속하거나 서버의 지시에 따라 재요청</li>
</ul>
</li>
</ul>
</li>
</ul>
Tue, 29 Jun 2021 00:00:00 +0000
/book/2021/06/29/TIL.html
/book/2021/06/29/TIL.htmljava,book,springBook도서 - 토비의 스프링 3.1 Vol 1, 5장~8장<h1 id="토비의-스프링-31-vol-1-317p--751p">토비의 스프링 3.1 Vol 1 (317p ~ 751p)</h1>
<h2 id="5장-서비스-추상화">5장 서비스 추상화</h2>
<h3 id="51-사용자-레벨-관리-기능-추가">5.1 사용자 레벨 관리 기능 추가</h3>
<ul>
<li>요구사항
<ul>
<li>사용자 레벨은 BASIC, SILVER, GOLD 세 가지 중 하나다.</li>
<li>사용자가 처음 가입하면 BASIC, 이후 활동에 따라 한 단계씩 업그레이드 가능</li>
<li>가입 후 50회 이상 로그인을 하면 BASIC -> SILVER</li>
<li>SILVER 레벨이면서 30번 이상 추천을 받으면 GOLD</li>
<li>사용자 레벨 변경 작업은 일정한 주기를 가지고 일괄 진행.
<ul>
<li>변경 작업 전에는 조건을 충족하더라도 레벨 변경이 일어나지 않는다.</li>
</ul>
</li>
</ul>
</li>
<li>Level Enum 추가
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>public enum Level {
BASIC(1), SILVER(2), GOLD(3);
private final int value;
Level(int value) {
this.value = value;
}
public int intValue() {
return this.value;
}
public static Level valueOf(int value) {
switch (value) {
case 1 : return BASIC;
case 2 : return SILVER;
case 3 : return GOLD;
default: throw new AssertionError("Unknown value: " + value);
}
}
}
</code></pre></div> </div>
</li>
<li>User Class field 추가
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>public class User {
...
@Column(name = "level", nullable = false)
private Level level;
@Column(name = "login", nullable = false)
private int login;
@Column(name = "recommend", nullable = false)
private int recommend;
...
}
</code></pre></div> </div>
</li>
<li>UserJdbcDao 수정
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>public class UserJdbcDao implements UserDao {
...
private RowMapper<User> userMapper = new RowMapper<User>() {
@Override
public User mapRow(ResultSet rs, int rowNum) throws SQLException {
User user = new User();
user.setId(rs.getString("id"));
user.setName(rs.getString("name"));
user.setPassword(rs.getString("password"));
user.setLevel(Level.valueOf(rs.getInt("level")));
user.setLogin(rs.getInt("login"));
user.setRecommend(rs.getInt("recommend"));
return user;
}
};
...
@Override
public void add(final User user) {
this.jdbcTemplate.update("insert into user (id, name, password, level, login, recommend) values (?,?,?,?,?,?)",
user.getId(), user.getName(), user.getPassword(), user.getLevel().intValue(), user.getLogin(), user.getRecommend());
}
@Override
public User get(String id) {
return this.jdbcTemplate.queryForObject("select * from user where id = ?", new Object[]{id}, this.userMapper);
}
...
}
</code></pre></div> </div>
</li>
</ul>
<h4 id="512-사용자-수정-기능-추가">5.1.2 사용자 수정 기능 추가</h4>
<ul>
<li>Test Code 먼저 추가
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>public class UserDaoTest {
...
@Test
public void update() {
dao.deleteAll();
dao.add(user1);
user1.setName("name");
user1.setPassword("5678");
user1.setLevel(Level.GOLD);
user1.setLogin(1000);
user1.setRecommend(999);
dao.update(user1);
User updateUser1 = dao.get(user1.getId());
checkSameUser(user1, updateUser1);
}
}
</code></pre></div> </div>
</li>
<li>UserJdbcDao update method 추가 (interface 에도 추가)
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>public class UserJdbcDao implements UserDao {
...
@Override
public void update(User user) {
this.jdbcTemplate.update(
"update user " +
"set name = ?, password = ?, level = ?, login = ?, recommend = ?" +
"where id = ?",
user.getName(), user.getPassword(), user.getLevel().intValue(), user.getLogin(), user.getRecommend(), user.getId());
}
}
</code></pre></div> </div>
</li>
</ul>
<h4 id="513-userserviceupgradelevels">5.1.3 UserService.upgradeLevels()</h4>
<ul>
<li>드디어 service layer 등장
<ul>
<li>dao 는 데이터 액세스가 목적이므로, 비지니스 로직은 해당 클래스에 어울리지 않는다.</li>
<li>비지니스 로직을 담을 service 클래스를 추가한다.</li>
</ul>
</li>
<li>UserService class
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>public class UserService {
private UserDao userDao;
public UserService(UserDao userDao) {
this.userDao = userDao;
}
public void upgradeLevels() {
List<User> users = userDao.getAll();
for (User user : users) {
boolean changed = false;
if (user.getLevel() == Level.BASIC && user.getLogin() >= 50) {
user.setLevel(Level.SILVER);
changed = true;
} else if (user.getLevel() == Level.SILVER && user.getRecommend() >= 30) {
user.setLevel(Level.GOLD);
changed = true;
}
if (changed) {
userDao.update(user);
}
}
}
}
</code></pre></div> </div>
</li>
<li>Test
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>class UserServiceTest {
@Autowired
private UserService userService;
@Autowired
private UserDao userDao;
private List<User> users;
@BeforeEach
public void setUp() {
users = Arrays.asList(
new User("id1", "name1", "p1", Level.BASIC, 49, 0),
new User("id2", "name2", "p2", Level.BASIC, 50, 10),
new User("id3", "name3", "p3", Level.SILVER, 60 ,29),
new User("id4", "name4", "p4", Level.SILVER, 60 ,30),
new User("id5", "name5", "p5", Level.GOLD, 100 ,100)
);
}
@Test
public void upgradeLevels() {
userDao.deleteAll();
for (User user : users) {
userDao.add(user);
}
userService.upgradeLevels();
checkLevel(users.get(0), Level.BASIC);
checkLevel(users.get(1), Level.SILVER);
checkLevel(users.get(2), Level.SILVER);
checkLevel(users.get(3), Level.GOLD);
checkLevel(users.get(4), Level.GOLD);
}
private void checkLevel(User user, Level expectedLevel) {
User userUpdate = userDao.get(user.getId());
Assertions.assertEquals(userUpdate.getLevel(), expectedLevel);
}
}
</code></pre></div> </div>
</li>
</ul>
<h4 id="514-userserviceadd">5.1.4 UserService.add()</h4>
<ul>
<li>처음 가입하는 사용자의 레벨을 BASIC 으로 설정하고 싶다.
<ul>
<li>DAO 에 담기에는 비지니스 로직이 개입되는 행위라 적절하지 않다.</li>
<li>엔티티인 User 클래스에서 필드를 BASIC 으로 초기화 하는 것은 어떨까?
<ul>
<li>난 좋은 것 같은데 필자는 처음 외에 무의미한 정보인데 굳이? 라는 입장</li>
</ul>
</li>
<li>비지니스 로직을 담고있는 service 에서 해보자
<ul>
<li>level 이 없는 경우 basic, 있는 경우 유지.</li>
</ul>
</li>
</ul>
</li>
<li>테스트 클래스
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>class UserServiceTest {
...
@Test
public void add() {
userDao.deleteAll();
User userWithLevel = users.get(4); // gold
User userWithoutLevel = users.get(0);
userWithoutLevel.setLevel(null);
userService.add(userWithLevel);
userService.add(userWithoutLevel);
User userWithLevelRead = userDao.get(userWithLevel.getId());
User userWithoutLevelRead = userDao.get(userWithoutLevel.getId());
assertEquals(userWithLevelRead.getLevel(), userWithLevel.getLevel());
assertEquals(userWithoutLevelRead.getLevel(), Level.BASIC);
}
...
}
</code></pre></div> </div>
</li>
<li>UserService 클래스
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>public class UserService {
...
public void add(User user) {
if (user.getLevel() == null) {
user.setLevel(Level.BASIC);
}
userDao.add(user);
}
...
}
</code></pre></div> </div>
</li>
</ul>
<h4 id="515-코드-개선">5.1.5 코드 개선</h4>
<ul>
<li>작성된 코드를 살펴볼 때 아래와 같은 질문
<ul>
<li>중복은 없는가</li>
<li>코드의 행위가 이해하기 편한가</li>
<li>코드가 자신이 있어야할 자리에 있는가</li>
<li>변경에 유연한 구조인가</li>
</ul>
</li>
<li>upgradeLevels() 리팩토링</li>
<li>
<p><img src="https://github.com/Oraindrop/oraindrop.github.io/blob/master/assets/_img/upgradeLevel.PNG?raw=true" alt="upgradeLevel" /></p>
</li>
<li>1 : 현재 레벨 파악</li>
<li>2 : 업그레이드 조건 판단</li>
<li>3 : 레벨 업그레이드</li>
<li>4 : DB 저장</li>
<li>
<p>성격이 다른 로직이 한데 섞여 있다.</p>
</li>
<li>추상적으로, upgradeLevels() 에는 loop 을 돌며 업그레이드가 가능하면 업그레이드 한다. 라는 작업 흐름만 명시</li>
</ul>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>List<User> users = userDao.getAll();
for (User user : users) {
if (canUpgradeLevel(user)) {
upgradeLevel(user);
}
}
</code></pre></div></div>
<ul>
<li>canUpgradeLevel() 메소드</li>
</ul>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>private boolean canUpgradeLevel(User user) {
Level currentLevel = user.getLevel();
switch (currentLevel) {
case BASIC: return (user.getLogin() >= 50);
case SILVER: return (user.getRecommend() >= 30);
case GOLD: return false;
default: throw new IllegalArgumentException("Unknow Level: " + currentLevel);
}
}
</code></pre></div></div>
<ul>
<li>upgradeLevel() 메소드도 추상적인 작업흐름(업데이트, DB저장)만 명시</li>
</ul>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>private void upgradeLevel(User user) {
user.upgradeLevel();
userDao.update(user);
}
</code></pre></div></div>
<ul>
<li>User Class</li>
</ul>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>public class User {
...
public void upgradeLevel() {
Level nextLevel = this.level.nextLevel();
if (nextLevel == null) {
throw new IllegalStateException(this.level + " is cannot upgrade.");
}
this.level = nextLevel;
}
...
}
</code></pre></div></div>
<ul>
<li>Level Enum</li>
</ul>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>public enum Level {
GOLD(3, null), SILVER(2, GOLD), BASIC(1, SILVER);
private final int value;
private final Level next;
...
}
</code></pre></div></div>
<ul>
<li>오브젝트와 메소드가 각각 자기 몫의 책임을 맡아 일을 하는 구조로 설계하는 것이 바람직하다.
<ul>
<li>책임 분리, 변경/확장 유연성 확보</li>
<li>데이터를 가져와서 작업하지 않고, 다른 오브젝트에 작업을 요청</li>
</ul>
</li>
<li>추가적으로, 업그레이드 정책을 유연하게 변경할 수 있도록 개선할 수 있다.
<ul>
<li>연말이벤트, 홍보기간 레벨 업그레이드 정책을 다르게 적용할 수도 있기 때문</li>
</ul>
</li>
<li>업그레이드 정책 인터페이스화</li>
</ul>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>public interface UserLevelUpgradePolicy {
boolean canUpgradeLevel(User user);
void upgradeLevel(User user);
}
</code></pre></div></div>
<ul>
<li>구현체 class</li>
</ul>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>public class UserLevelUpgradePolicyDefault implements UserLevelUpgradePolicy {
public static final int MIN_LOGIN_COUNT_FOR_SILVER = 50;
public static final int MIN_RECOMMEND_FOR_GOLD = 30;
private UserDao userDao;
public UserLevelUpgradePolicyDefault(UserDao userDao) {
this.userDao = userDao;
}
@Override
public boolean canUpgradeLevel(User user) {
Level currentLevel = user.getLevel();
switch (currentLevel) {
case BASIC: return (user.getLogin() >= MIN_LOGIN_COUNT_FOR_SILVER);
case SILVER: return (user.getRecommend() >= MIN_RECOMMEND_FOR_GOLD);
case GOLD: return false;
default: throw new IllegalArgumentException("Unknow Level: " + currentLevel);
}
}
@Override
public void upgradeLevel(User user) {
user.upgradeLevel();
userDao.update(user);
}
}
</code></pre></div></div>
<ul>
<li>서비스 class 에 DI 하여 적용</li>
</ul>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>public class UserService {
...
private UserLevelUpgradePolicy userLevelUpgradePolicy;
...
public void upgradeLevels() {
List<User> users = userDao.getAll();
for (User user : users) {
if (userLevelUpgradePolicy.canUpgradeLevel(user)) {
userLevelUpgradePolicy.upgradeLevel(user);
}
}
}
}
</code></pre></div></div>
<h3 id="52-트랜잭션-서비스-추상화">5.2 트랜잭션 서비스 추상화</h3>
<ul>
<li>
<p>사용자 레벨 조정 작업 중간에 문제가 발생하면, 현재 코드는 롤백이 될까 커밋이 될까.</p>
</li>
<li>
<p>장애상황을 연출하려 할 때, 애플리케이션 코드를 수정하는 것은 가장 쉽지만 바람직하지 않다.</p>
</li>
<li>테스트용 클래스를 만들고, DI 하여 테스트
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>class UserServiceTest {
...
@Test
public void upgradeAllOrNothing() {
userService.setUserLevelUpgradePolicy(new UserLevelUpgradePolicyTest(userDao, users.get(3).getId()));
userDao.deleteAll();
for (User user : users) {
userDao.add(user);
}
Assertions.assertThrows(TestUserServiceException.class, () -> {
userService.upgradeLevels();
});
checkLevel(users.get(1), false); // 앞에 업그레이드 한 것이 롤백이 되면 테스트가 성공.
}
static class UserLevelUpgradePolicyTest extends UserLevelUpgradePolicyDefault {
private String id;
public UserLevelUpgradePolicyTest(UserDao userDao, String id) {
super(userDao);
this.id = id;
}
@Override
public void upgradeLevel(User user) {
if (user.getId().equals(this.id)) {
throw new TestUserServiceException();
}
super.upgradeLevel(user);
}
}
static class TestUserServiceException extends RuntimeException {
}
}
</code></pre></div> </div>
<p>테스트는 실패한다. 중간에 실패하더라도, 이미 업그레이드가 되었다.</p>
</li>
<li>테스트 실패의 원인은 upgradLevels() 메소드가 하나의 트랜잭션 안에서 동작하기 않았기 때문</li>
</ul>
<h4 id="522-트랜잭션-경계-설정">5.2.2 트랜잭션 경계 설정</h4>
<ul>
<li>DB는 완벽한 트랜잭션을 제공한다.
<ul>
<li>SQL을 이용해 다중 로우 수정, 삭제 시 부분성공/실패 하는 경우는 없다.</li>
<li>즉, 하나의 SQL 명령을 처리하는 경우 DB가 트랜잭션을 보장한다.</li>
</ul>
</li>
<li>하지만, 여러 SQL 을 하나의 트랜잭션으로 처리해야 하는 경우
<ul>
<li>첫번째 SQL 이 성공하고 두번째 SQL 이 실패하면 Rollback 이 필요.</li>
<li>모든 SQL이 성공하였을 때 Commit.</li>
</ul>
</li>
<li>JDBC 를 이용해 트랜잭션을 적용하는 간단한 예제</li>
</ul>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>...
Connection c = datasource.getConnection();
c.setAutoCommit(false); // Transaction Start
try {
PreparedStatement st1 = c.prepareStatement("update users ...");
st1.executeUpdate();
PreparedStatement st2 = c.prepareStatement("update users ...");
st2.executeUpdate();
c.commit(); // Transaction commit
} catch(Exception e) {
c.rollback(); // Transaction rollback
}
c.close();
</code></pre></div></div>
<ul>
<li>애플리케이션에서 트랜잭션이 시작되고 종료되는 위치를 <code class="language-plaintext highlighter-rouge">트랜잭션의 경계</code> 라 한다.
<ul>
<li>setAutoCommit(false) 로 트랜잭션 시작을 선언하고 commit() 또는 rollback() 으로 트랜잭션을 종료하는 작업을 <code class="language-plaintext highlighter-rouge">트랜잭션의 경계설정(transaction demarcation)</code> 이라 한다.</li>
<li>트랜잭션의 경계는 하나의 Connection이 만들어지고 닫히는 범위 안에 존재한다.
<ul>
<li>이렇게 하나의 DB 커넥션 안에서 만들어지는 트랜잭션을 <code class="language-plaintext highlighter-rouge">로컬 트랜잭션</code> 이라고도 한다.</li>
</ul>
</li>
</ul>
</li>
<li>현재는 DAO 메소드를 호출할 때마다 하나의 새로운 트랜잭션이 만들어지는 구조
<ul>
<li>DAO 메소드 안으로 upgradeLevels() 메소드 내용을 옮긴다면 비지니스 로직과 데이터 로직이 한데 묶여 섞이는 결과를 초래한다.</li>
<li>위와 같은 방식이 아닌, 트랜잭션의 경계설정 작업을 UserService 쪽으로 가져와야 한다.</li>
<li>Connection 을 Service 에서 생성하여 파라미터로 전달한다면 문제는 해결할 수 있다.
<ul>
<li>하지만 DB 커넥션을 포함한 리소스 처리를 Service 레이어에서 해야하고,</li>
<li>Connection 파라미터가 세상 끝까지 추가되어야 하고,</li>
<li>Service 레이어가 Data Access 기술에 종속적이 된다.</li>
<li>스프링은 이 딜레마를 해결할 수 있는 멋진 방법을 제공한다.</li>
</ul>
</li>
</ul>
</li>
<li>트랜잭션 동기화 방식
<ol>
<li>UserService 는 Connection 생성</li>
<li>이를 트랜잭션 동기화 저장소에 저장해두고 Connection의 setAutoCommit(false)를 호출해 트랜잭션을 시작시키고 DAO 의 기능을 이용하기 시작.</li>
<li>DAO 의 첫번째 update() 메소드가 호출되고, update() 메소드 내부에서 이용하는 JdbcTemplate 메소드에서는 가장먼저</li>
<li>트랜잭션 동기화 저장소에 현재 시작된 트랜잭션을 가진 Connection 오브젝트가 존재하는지 확인한다. (2)에서 저장해둔 Connection 을 발견하고 이를 가져온다.</li>
<li>가져온 Connection 을 이용하여 PreparedStatement를 만들어 수정 SQL 을 실행, 트랜잭션 동기화 저장소에서 DB 커넥션을 가져왔을 때는 JdbcTemplate은 Connection 을 닫지 않은 채로 작업을 마친다. (첫 번째 DB 작업 완료, Connection 은 열려있고, 트랜잭션은 진행 중, 트랜잭션 동기화 저장소에 저장)</li>
<li>N번째 update()가 호출되면 이 때도 마찬가지로 작업을 한다.</li>
<li>트랜잭션 내 모든 작업이 정상적으로 끝났다면 UserService 는 Connection commit() 을 호출하여 트랜잭션을 완료시킨다.</li>
<li>마지막으로 트랜잭션 저장소가 더 이상 Connection 오브젝트를 저장해두지 않도록 이를 제거한다.
<ul>
<li>어느 작업 중에라도 예외 발생 시 UserService는 즉시 Connection rollback()을 호출하고 트랜잭션을 종료할 수 있다. (이 때도 트랜잭션 저장소의 Connection 오브젝트 제거)</li>
</ul>
</li>
</ol>
</li>
<li>트랜잭션 동기화 저장소는 작업 스레드마다 독립적으로 Connection 오브젝트를 저장/관리한다.
<ul>
<li>멀티스레드 가능</li>
</ul>
</li>
<li>트랜잭션 동기화 방식을 적용한 upgradeLevels()</li>
</ul>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>...
public void upgradeLevels() throws Exception {
TransactionSynchronizationManager.initSynchronization(); // 트랜잭션 동기화 관리자를 이용해 동기화 작업 초기화
Connection c = DataSourceUtils.getConnection(dataSource); // 커넥션 생성, 동기화
c.setAutoCommit(false); // 트랜잭션 시작
try {
...
c.commit(); // 정상적으로 작업을 마치면 커밋
} catch (Exception e) {
c.rollback(); // 예외 시 롤백
throw e;
} finally {
DataSourceUtils.releaseConnection(c, dataSource); // DB 커넥션 세이프 종료
TransactionSynchronizationManager.unbindResource(dataSource); // 동기화 작업 종료
TransactionSynchronizationManager.clearSynchronization(); // 정리
}
}
</code></pre></div></div>
<ul>
<li>한 개 이상의 DB로 작업을 하나의 트랜잭션으로 만들기 위해서는 <code class="language-plaintext highlighter-rouge">로컬 트랜잭션</code> 으로는 불가능하다.
<ul>
<li>별도의 트랜잭션 관리자를 통해 트랜잭션을 관리하는 <code class="language-plaintext highlighter-rouge">글로벌 트랜잭션</code> 방식을 사용해야 한다.</li>
<li>자바는 글로벌 트랜잭션을 지원하는 트랜잭션 매니저를 지원하기 위한 API 인 <code class="language-plaintext highlighter-rouge">JTA(Java Transaction API)</code> 를 제공한다.</li>
<li>11장에서 자세히</li>
</ul>
</li>
<li>JTA를 이용한 트랜잭션 코드 구조</li>
</ul>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>InitialContext ctx = new InitialContext();
UserTransaction tx = (UserTransaction)ctx.lookup(USER_TX_JNDI_NAME);
tx.begin();
Connection c = dataSource.getConnection();
try {
...
tx.commit();
} catch (Exception e) {
tx.rollback();
throw e;
} finally {
c.close();
}
</code></pre></div></div>
<ul>
<li>또 문제는 하이버네이트를 이용한 트랜잭션 관리는 JDBC나 JTA의 코드와는 또 다르다.
<ul>
<li>Connection 을 직접 사용하지 않고 Session 을 사용하며 독자적인 트랜잭션 관리 API를 사용한다.</li>
</ul>
</li>
</ul>
<h4 id="트랜잭션-api의-의존관계-문제와-해결책">트랜잭션 API의 의존관계 문제와 해결책</h4>
<ul>
<li>트랜잭션의 경계설정 필요가 생기면서 특정 데이터 액세스 기술에 종속되는 구조가 된다.</li>
<li>트랜잭션의 경계설정을 담당하는 코드는 일정한 패턴을 갖는 유사한 구조다.
<ul>
<li>이렇게 여러 기술의 사용방법에 공통점이 있다면 <code class="language-plaintext highlighter-rouge">추상화</code> 를 생각해볼 수 있다.</li>
<li><code class="language-plaintext highlighter-rouge">추상화</code> 란 하위 시스템의 공통점을 뽑아내서 분리시키는 것을 말한다.
<ul>
<li>DB에서 제공하는 DB 클라이언트, 라이브러리, API는 서로 전혀 호환이 되지 않는 독자적인 방식으로 만들어져9 있다. 하지만 모두 SQL 을 이용하는 방식이라는 공통점이 있다.</li>
<li>이 공통점을 추출하여 추상화 한 것이 <code class="language-plaintext highlighter-rouge">JDBC</code></li>
<li><code class="language-plaintext highlighter-rouge">JDBC</code> 라는 추상화 기술이 있기 때문에 자바는 DB의 종류와 상관없이 일관된 방법으로 데이터 액세스 코드를 작성할 수 있다.</li>
</ul>
</li>
</ul>
</li>
<li>스프링의 트랜잭션 추상화 API 를 적용</li>
</ul>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>public void upgradeLevels() {
PlatformTransactionManager transactionManager = new DataSourceTransactionManager(dataSource);
TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());
try {
List<User> users = userDao.getAll();
for (User user : users) {
if (userLevelUpgradePolicy.canUpgradeLevel(user)) {
userLevelUpgradePolicy.upgradeLevel(user);
}
}
transactionManager.commit(status);
} catch (RuntimeException e) {
transactionManager.rollback(status);
throw e;
}
}
</code></pre></div></div>
<ul>
<li>트랜잭션 경계설정을 위한 추상 인터페이스 <code class="language-plaintext highlighter-rouge">PlatformTransactionManager</code>
<ul>
<li>JDBC의 로컬 트랜잭션을 이용한다면 구현체 <code class="language-plaintext highlighter-rouge">DataSourceTransactionManager</code> 사용</li>
<li>필요에 따라 트랜잭션 매니저가 커넥션을 가져오는 작업도 함께 수행한다.</li>
<li>트랜잭션을 가져올 때 <code class="language-plaintext highlighter-rouge">transactionManager.getTransaction(new DefaultTransactionDefinition());</code> 일단은 트랜잭션을 시작한다는 의미라고 이해하면 된다고 한다.</li>
<li>이렇게 시작된 트랜잭션은 <code class="language-plaintext highlighter-rouge">TransactionStatus status</code> 변수에 저장된다.</li>
</ul>
</li>
<li>JTA를 이용한 글로벌 트랜잭션으로 변경하려면 구현체를 <code class="language-plaintext highlighter-rouge">JTATransactionManager</code> 로 변경하기만 하면 된다.
<ul>
<li>하이버네이트를 사용했다면 <code class="language-plaintext highlighter-rouge">HibernateTransactionManager</code>, JPA를 사용했다면 <code class="language-plaintext highlighter-rouge">JPATransactionManager</code></li>
<li>구현체가 여러가지이고, 그걸 service 에서 직접 구현하는 걸 보니 DI 하고싶다!
<ul>
<li>스프링의 빈으로 등록할 때 먼저 검토해야 할 것은 싱글톤으로 멀티스레드에 사용해도 괜찮은가 이다.</li>
<li>스프링에서 제공하는 모든 PlatformTransactionManager 의 구현체는 싱글톤으로 사용이 가능하다.</li>
</ul>
</li>
</ul>
</li>
</ul>
<h3 id="53-서비스-추상화와-단일-책임-원칙">5.3 서비스 추상화와 단일 책임 원칙</h3>
<ul>
<li>
<p>기술과 서비스에 추상화 기법을 이용하면 특정 기술환경에 종속되지 않는 포터블한 코드를 만들 수 있다.</p>
</li>
<li>UserDao와 UserService는 각각 담당하는 코드의 기능적인 관심에 따라 분리되고, 서로 불필요한 영향을 주지 않으면서 독자적으로 확장이 가능하도록 만든 것이다.
<ul>
<li>같은 애플리케이션 로직을 담은 코드지만, 내용에 따라 분리했다.</li>
<li>같은 계층에서 수평적인 분리라고 볼 수 있다.</li>
</ul>
</li>
<li>트랜잭션의 추상화는 이와 조금 다르다.
<ul>
<li>애플리케이션의 비지니스 로직과 그 하위에서 동작하는 로우레벨의 트랜잭션 기술이라는 아예 다른 계층의 특성을 갖는 코드를 분리한 것이다.</li>
</ul>
</li>
<li>애플리케이션 로직의 종류에 따른 수평적인 구분이든, 로직과 기술이라는 수직적인 구분이든 모두 결합도가 낮으며, 서로 영향을 주지 않고 자유롭게 확장할 수 있는 구조를 만들 수 있는데는 스프링의 DI가 중요한 역할을 하고 있다.
<ul>
<li>DI의 가치는 이렇게 관심, 책임, 성격이 다른 코드를 깔끔하게 분리하는 데 있다.</li>
</ul>
</li>
</ul>
<h4 id="단일-책임-원칙">단일 책임 원칙</h4>
<ul>
<li>이러한 적절한 분리가 가져오는 특징은 객체지향 설계의 원칙 중의 하나인 단일 책임 원칙(SRP) 으로 설명할 수 있다.
<ul>
<li>모듈은 한 가지 책임을 가져야 한다.</li>
<li>모듈이 변경되는 이유는 한 가지다.</li>
</ul>
</li>
<li>UserService 에 JDBC Connection의 메소드를 직접 사용하는 트랜잭션 코드가 들어있었을 때
<ul>
<li>사용자 레벨을 어떻게 관리할 것인가</li>
<li>트랜잭션을 어떻게 관리할 것인가</li>
<li>이렇게 두 가지 책임을 가지고 있었다.
<ul>
<li>사용자 레벨 관리 방식의 변경이 없어도, 트랜잭션 기술을 변경해야 한다면 해당 클래스가 변경되어야 한다. 즉, 변경의 이유가 두 가지</li>
</ul>
</li>
</ul>
</li>
<li>단일 책임 원칙의 장점
<ul>
<li>어떤 변경이 필요할 때 수정 대상이 명확해진다.</li>
<li>기술이 바뀌면, 기술계층과의 연동을 담당하는 기술 추상화 계층의 설정만 바꿔주면 된다.</li>
</ul>
</li>
<li>객체지향 설계와 프로그래밍의 원칙은 서로 긴밀하게 연결되어 있다.
<ul>
<li>단일 책임 원칙을 잘 지키는 코드를 만들려면 interface를 도입하고 이를 DI로 연결해야 하며, 그 결과로 단일 책임 원칙 뿐 아니라 개방폐쇄 원칙도 잘 지키고, 모듈 간 결합도가 낮아 서로의 변경이 영향을 주지 않고, 같은 이유로 변경이 단일 책임에 집중되는 응집도 높은 코드가 나온다.</li>
</ul>
</li>
</ul>
<h3 id="54-메일-서비스-추상화">5.4. 메일 서비스 추상화</h3>
<ul>
<li>Spring 이 직접 제공하는 MailSender 를 구현한 추상화 클래스는 JavaMailServiceImple 하나뿐이다.
<ul>
<li>그럼에도 불구하고 이 추상화된 메일 전송 기능을 사용해 애플리케이션을 작성함으로써 얻을 수 있는 장점은 크다.
<ul>
<li>테스트 시 실제 메일이 발송되지 않게</li>
<li>JavaMail 이 아닌 다른 메시징 서버의 API 를 이용</li>
<li>메일을 바로 전송하지 않고 큐에 담아뒀다가 정해진 시간에 발송</li>
<li>다양한 추가/변경 작업 시 MailSender로 추상화된 메일 전송 추상화 계층이 도움이된다.</li>
</ul>
</li>
</ul>
</li>
<li>기술이나 환경이 바뀔 가능성이 있음에도, JavaMail 처럼 확장이 불가능하게 설계해놓은 API를 사용해야 하는 경우라면 추상화 계층의 도입을 적극 고려해볼 필요가 있다.
<ul>
<li>외부 리소스와 연동하는 대부분 작업은 추상화 대상이 될 수 있다.</li>
</ul>
</li>
<li>테스트용으로 사용되는 특별한 오브젝트들이 있다.
<ul>
<li>대부분 테스트 대상인 오브젝트의 의존 오브젝트가 되는 것들</li>
<li>테스트환경을 만들어주기 위해, 테스트 대상이 되는 오브젝트의 기능에만 충실하게 수행하면서 빠르게, 자주 테스트를 실행할 수 있도록 사용하는 이런 오브젝트들을 통틀어서 <code class="language-plaintext highlighter-rouge">테스트 대역(test double)</code> 이라고 부른다.</li>
<li>대표적인 테스트 대역은 <code class="language-plaintext highlighter-rouge">테스트 스텁(test stub)</code> 이다.
<ul>
<li>테스트 스텁은 테스트 대상 오브젝트의 의존객체로서 존재하면서 테스트 동안에 코드가 정상적으로 수행할 수 있도록 돕는 것을 말한다.</li>
<li>간접적인 입력값을 제공</li>
</ul>
</li>
<li><code class="language-plaintext highlighter-rouge">Mock Object</code>
<ul>
<li>테스트 대상 오브젝트와 의존 오브젝트 사이에서 일어나는 일을 검증할 수 있도록 특별히 설계</li>
<li>테스트 오브젝트와 자신의 사이에서 일어나는 커뮤니케이션을 저장해뒀다가 테스트 결과를 검증하는 데 활용할 수 있게 해준다.</li>
<li>간접적인 출력 값까지 확인이 가능</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2 id="6장-aop">6장 AOP</h2>
<h3 id="61-트랜잭션-코드의-분리">6.1 트랜잭션 코드의 분리</h3>
<h4 id="611-메소드-분리">6.1.1 메소드 분리</h4>
<ul>
<li>
<p>UserService 의 트랜잭션 경계설정 코드와 비즈니스 로직 코드가 복잡하게 얽혀 있는 듯 보이지만, 뚜렷하게 두 가지 종류의 코드가 구분되어 있다.</p>
</li>
<li>
<p>비즈니스 로직 코드를 사이에 두고 트랜잭션의 시작과 종료를 담당</p>
</li>
<li>
<p>트랜잭션 경계설정 코드와 비즈니스 로직 코드 간에 서로 주고받는 정보가 없이 독립적이다.</p>
</li>
<li>
<p>비즈니스 로직만 추출한 예</p>
</li>
</ul>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>public void upgradeLevels() {
TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());
try {
upgradeLevelsInternal(); // here!!
transactionManager.commit(status);
} catch (RuntimeException e) {
transactionManager.rollback(status);
throw e;
}
}
private void upgradeLevelsInternal() {
List<User> users = userDao.getAll();
for (User user : users) {
if (userLevelUpgradePolicy.canUpgradeLevel(user)) {
userLevelUpgradePolicy.upgradeLevel(user);
}
}
}
</code></pre></div></div>
<h4 id="612-di를-이용한-클래스-분리">6.1.2 DI를 이용한 클래스 분리</h4>
<ul>
<li>
<p>여전히 트랜잭션을 담당하는 기술적 코드가 <code class="language-plaintext highlighter-rouge">UserService</code> 클래스에 있다.</p>
</li>
<li>여태 DI를 적용했던 이유는 일반적으로 구현 클래스를 바꿔가면서 사용하기 위해서
<ul>
<li>테스트에 따라 테스트 구현클래스 등</li>
<li>하지만 꼭 이것 때문에 DI를 쓰는건 아님</li>
</ul>
</li>
<li>UserService 의 책임을 나눈 구현 클래스를 두 개 만든다.
<ul>
<li><img src="https://github.com/Oraindrop/oraindrop.github.io/blob/master/assets/_img/UserServiceTx.png?raw=true" alt="UserServiceTx" /></li>
</ul>
</li>
<li>UserService interface</li>
</ul>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>public interface UserService {
void add(User user);
void upgradeLevels();
}
</code></pre></div></div>
<ul>
<li>비즈니스 로직만 들고 있는 UserServiceImpl</li>
</ul>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>public class UserServiceImpl implements UserService {
...
@Override
public void upgradeLevels() {
List<User> users = userDao.getAll();
for (User user : users) {
if (userLevelUpgradePolicy.canUpgradeLevel(user)) {
userLevelUpgradePolicy.upgradeLevel(user);
}
}
}
@Override
public void add(User user) {
if (user.getLevel() == null) {
user.setLevel(Level.BASIC);
}
userDao.add(user);
}
...
}
</code></pre></div></div>
<ul>
<li>트랜잭션 경계설정 + 작업 위임하는 UserServiceTx</li>
</ul>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>public class UserServiceTx implements UserService {
private UserService userService;
...
@Override
public void upgradeLevels() {
TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());
try {
userService.upgradeLevels();
transactionManager.commit(status);
} catch (RuntimeException e) {
transactionManager.rollback(status);
throw e;
}
}
...
}
</code></pre></div></div>
<ul>
<li>장점
<ul>
<li>비즈니스 로직을 담당하고 있는 UserServiceImpl 의 코드를 작성할 때는 트랜잭션과 같은 기술적인 내용에는 전혀 신경쓰지 않아도 된다.</li>
<li>비즈니스 로직에 대한 테스트를 손쉽게 만들어낼 수 있다.</li>
</ul>
</li>
</ul>
<h3 id="62-고립된-단위-테스트">6.2. 고립된 단위 테스트</h3>
<ul>
<li>가장 편하고 좋은 테스트 방법은 가능한 한 작은 단위로 쪼개서 테스트 하는 것.
<ul>
<li>좋은 이유는, 테스트가 실패했을 때 그 원인을 찾기 쉽기 때문</li>
<li>의도나 내용이 분명, 만들기도 쉬워짐</li>
</ul>
</li>
<li>asis upgradeLevels() 테스트
<ul>
<li><img src="https://github.com/Oraindrop/oraindrop.github.io/blob/master/assets/_img/upgradeLevelsTest.PNG?raw=true" alt="upgradeLevelsTest" />
<ol>
<li>테스트용 정보를 DB에 넣는다.</li>
<li>메일 발송 여부를 확인하기 위해 MailSender Mock Object 를 DI</li>
<li>실제 테스트 대상인 userService 메소드 실행</li>
<li>DB에서 데이터를 가져와 결과 확인</li>
<li>Mock 오브젝트에서 데이터를 가져와 UserService 에 의한 메일 발송이 있었는지 확인</li>
</ol>
</li>
</ul>
</li>
<li>mock userDao</li>
</ul>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>static class MockUserDao implements UserDao {
private List<User> users;
private List<User> updated = new ArrayList<>();
private MockUserDao(List<User> users) {
this.users = users;
}
public List<User> getUpdated() {
return this.updated;
}
public List<User> getAll() {
return this.users;
}
public void update(User user) {
updated.add(user);
}
public void add(User user) { throw new UnsupportedOperationException(); };
public void deleteAll() { throw new UnsupportedOperationException(); };
public User get(String id) { throw new UnsupportedOperationException(); };
public int getCount() { throw new UnsupportedOperationException(); };
}
</code></pre></div></div>
<ul>
<li>upgradeLevels test use mock userDao</li>
</ul>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>public void upgradeLevels() {
UserServiceImpl userServiceImpl = new UserServiceImpl(new MockUserDao(this.users));
...
userServiceImpl.upgradeLevels();
List<User> updated = mockUserDao.getUpdated();
assertEquals(updated.size(), 2);
assertEquals(updated.get(0).getLevel(), Level.SILVER);
assertEquals(updated.get(1).getLevel(), Level.GOLD);
...
}
</code></pre></div></div>
<ul>
<li>테스트 대역 오브젝트를 이용해 완전히 고립된 테스트를 만듬
<ul>
<li>스프링 컨테이너에서 빈을 가져올 필요가 없다.</li>
<li>테스트 수행 성능이 향상된다.
<ul>
<li>테스트가 빨리 돌아가면 부담 없이 자주 테스트를 돌려볼 수 있다.</li>
</ul>
</li>
<li>의존 대상 영향을 고려하지 않아도 된다.</li>
</ul>
</li>
<li>고립된 테스트를 만들려면 목 오브젝트 작성과 같은 약간의 수고가 더 필요할지 모르나, 그 보상은 충분히 기대할 만하다.</li>
</ul>
<h4 id="mockito-프레임워크">Mockito 프레임워크</h4>
<ul>
<li>mock 생성</li>
</ul>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>UserDao mockUserDao = mock(UserDao.class);
</code></pre></div></div>
<ul>
<li>메소드 리턴값 정의</li>
</ul>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>when(mockUserDao.getAll()).thenReturn(this.users);
</code></pre></div></div>
<ul>
<li>메소드 호출 횟수 확인</li>
</ul>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>verify(mockUserDao, times(2)).update(any(User.class));
</code></pre></div></div>
<ul>
<li>메소드 파라미터 캡쳐</li>
</ul>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ArgumentCaptor<SimpleMailMessage> mailMessageArg = ArgumentCaptor.forClass(SimpleMailMessage.class);
verify(mockMailSender, times(2)).send(mailMessageArg.capture())
List<SimpleMailMessage> mailMessages = mailMessageArg.getAllValues();
// assert
</code></pre></div></div>
<h4 id="63-다이내믹-프록시와-팩토리-빈">6.3 다이내믹 프록시와 팩토리 빈</h4>
<ul>
<li>트랜잭션이라는 부가기능을 UserServiceTx, 핵심기능을 UserServiceImpl 로 분리했다.</li>
<li>중요한 특징이 있다.
<ul>
<li>부가기능 외에 나머지 모든 기능은 원래 핵심 기능을 가진 클래스로 위임.</li>
<li>즉, 부가기능이 핵심기능을 사용하는 구조가 된다.</li>
<li>클라이언트가 핵심기능을 가진 클래스를 직접 사용해버리면 부가기능 적용 기회가 없다.</li>
<li>그래서 부가기능은 마치 자신이 핵심기능을 가진 클래스인 것처럼 꾸며서, 클라이언트가 자신을 사용하도록 만들어야 한다.</li>
</ul>
</li>
<li>이렇게 마치 자신이 클라이언트가 사용하려고 하는 실제 대상인 것 처럼 위장해서 클라이언트의 요청을 받아주는 것을 <code class="language-plaintext highlighter-rouge">프록시(proxy)</code> 라 부른다.
<ul>
<li>그리고 프록시를 통해 최종적으로 요청을 위임받아 처리하는 실제 오브젝트를 <code class="language-plaintext highlighter-rouge">타깃(target)</code>, <code class="language-plaintext highlighter-rouge">실체(real object)</code> 라 부른다.</li>
</ul>
</li>
<li>
<p>프록시의 특징은 타깃과 같은 인터페이스를 구현했다는 것과, 프록시가 타깃을 제어할 수 있는 위치에 있다는 것.</p>
</li>
<li>프록시의 사용 목적
<ul>
<li>클라이언트가 타깃에 접근하는 방법을 제어</li>
<li>타깃에 부가적인 기능을 부여하기 위해</li>
</ul>
</li>
</ul>
<h5 id="데코레이터-패턴">데코레이터 패턴</h5>
<ul>
<li>타깃에 부가적인 기능을 런타임 시 다이내믹하게 부여해주기 위해 프록시를 사용하는 패턴
<ul>
<li>다이내믹하게 <- 의 의미는 컴파일 시점(코드 상) 에는 어떤 방법과 순서로 프록시와 타깃이 연결되어 사용되는지 정해져 있지 않다는 뜻</li>
</ul>
</li>
<li>
<p>실제 내용물은 동일하지만 부가적인 효과를 부여</p>
</li>
<li>
<p>프록시가 꼭 한개로 제한되지는 않는다.</p>
</li>
<li>예를들어 소스코드를 출력하는 기능을 가진 핵심 기능
<ul>
<li>데코레이터 개념을 부여, 타깃과 같은 인터페이스를 구현, 런타임 시 이를 적절한 순서로 조합해서 사용
<ul>
<li>소스코드에 라인넘버를 붙여주는 데코레이터 -> 신텍스 하이리팅 하는 데코레이터 -> 페이징 데코레이터 -> 핵심 기능 (타깃)</li>
</ul>
</li>
</ul>
</li>
<li>데코레이터의 다음 위임 대상은 인터페이스로 주입 받아야 한다.
<ul>
<li>다음 대상이 최종 타깃인지, 다음단계 데코레이터 프록시인지 알지 못하기 때문</li>
</ul>
</li>
<li>데코레이터 패턴은 타깃의 코드를 손대지 않고, 클라이언트가 호출하는 방법도 변경하지 않은 채로 새로운 기능을 추가할 때 유용한 방법이다.</li>
</ul>
<h5 id="프록시-패턴">프록시 패턴</h5>
<ul>
<li>
<p>프록시라는 용어와 디자인 패턴의 프록시 패턴은 다르다.</p>
</li>
<li>프록시 패턴은 타깃에 대한 접근방법을 제어하려는 목적을 가진 경우를 가리킨다.
<ul>
<li>타깃의 기능을 확장하거나 추가하지 않는다.</li>
<li>대신 클라이언트가 타깃에 접근하는 방식을 변경해준다.</li>
</ul>
</li>
<li>타깃 오브젝트를 생성하기가 복잡하거나 당장 필요하지 않으나 오브젝트의 레퍼런스가 미리 필요한 경우, 실제 타깃 오브젝트 대신 프록시를 넘겨주는 것.
<ul>
<li>프록시의 메소드를 통해 타깃을 사용하려고 시도하면, 그때 프록시가 타깃 오브젝트를 생성하고 요청을 위임해주는 식</li>
</ul>
</li>
<li>
<p>레퍼런스를 갖고 있지만 끝까지 사용하지 않거나, 많은 작업이 진행된 후에 사용되는 경우라면 생성을 최대한 늦춤으로써 얻는 장점이 많다.</p>
</li>
<li>원격 오브젝트를 이용하는 경우에도 프록시를 사용하면 편리하다.
<ul>
<li>다른 서버에 존재하는 원격 오브젝트에 대한 프록시를 만들어두고, 클라이언트는 마치 로컬에 존재하는 오브젝트를 쓰는 것처럼</li>
</ul>
</li>
<li>타깃에 대한 접근권한을 제어하기 위해 프록시 패턴 사용
<ul>
<li>특정 레이어에서 읽기전용으로 동작하는 경우</li>
</ul>
</li>
<li>
<p>이렇게 프록시 패턴은 타깃의 기능 자체에는 관여하지 않으면서 접근하는 방법을 제어해주는 프록시를 이용하는 것이다.</p>
</li>
<li>타깃과 동일한 인터페이스를 구현하고 클라이언트와 타깃 사이에 존재하면서 부가 기능 또는 접근 제어를 담당하는 오브젝트는 모두 프록시</li>
</ul>
<h4 id="632-다이내믹-프록시">6.3.2 다이내믹 프록시</h4>
<ul>
<li>프록시는 다음 두 가지 기능으로 구성된다.
<ul>
<li>타깃과 같은 메소드를 구현하고 있다가 메소드가 호출되면 타깃 오브젝트로 위임.</li>
<li>지정된 요청에 대해서는 부가기능 수행.</li>
</ul>
</li>
<li>프록시가 만들기 번거로운 이유
<ol>
<li>타깃의 인터페이스를 구현하고 위임하는 코드 작성이 번거롭다.
<ul>
<li>부가기능이 필요없는 메소드도 구현, 위임 필요</li>
</ul>
</li>
<li>부가기능 코드의 중복 가능성</li>
</ol>
</li>
<li>목 오브젝트와 비슷하게 프록시를 손쉽게 만드는 것을 도와주는 API 가 존재한다
<ul>
<li>JDK 의 <strong>다이내믹 프록시</strong></li>
</ul>
</li>
<li>다이내믹 프록시는 리플렉션 기능을 이용해서 프록시를 만들어준다.
<ul>
<li>리플렉션은 자바의 코드 자체를 추상화해서 접근하도록 만든 것.</li>
<li>학습 테스트</li>
</ul>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> @Test
public void invokeMethod() throws Exception {
String name = "Spring";
assertEquals(name.length(), 6);
Method lengthMethod = String.class.getMethod("length");
assertEquals((Integer)lengthMethod.invoke(name), 6);
assertEquals(name.charAt(0), 'S');
Method charAtMethod = String.class.getMethod("charAt", int.class);
assertEquals((Character)charAtMethod.invoke(name, 0), 'S');
}
</code></pre></div> </div>
<p>invoke() 메소드는 메소드를 실행시킬 대상 오브젝트와 파라미터 목록을 받아 메소드를 호출한 뒤 그 결과를 Object 타입으로 돌려준다.</p>
</li>
<li>
<p>다이내믹 프록시 학습테스트 예제</p>
<ul>
<li>Hello Interface</li>
</ul>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> public interface Hello {
String sayHello(String name);
String sayHi(String name);
String sayThankYou(String name);
}
</code></pre></div> </div>
<ul>
<li>타깃 클래스</li>
</ul>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> public class HelloTarget implements Hello {
@Override
public String sayHello(String name) {
return "Hello " + name;
}
@Override
public String sayHi(String name) {
return "Hi " + name;
}
@Override
public String sayThankYou(String name) {
return "Thank You " + name;
}
}
</code></pre></div> </div>
<ul>
<li>프록시</li>
</ul>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> public class UppercaseHandler implements InvocationHandler {
Object target;
public UppercaseHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object ret = method.invoke(target, args);
if (ret instanceof String && method.getName().startsWith("say")) {
return ((String)ret).toUpperCase();
} else {
return ret;
}
}
}
</code></pre></div> </div>
<ul>
<li>클라이언트 (학습테스트)</li>
</ul>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> @Test
public void simpleProxy() {
Hello hello = new HelloTarget();
assertEquals(hello.sayHello("Toby"), "Hello Toby");
assertEquals(hello.sayHi("Toby"), "Hi Toby");
assertEquals(hello.sayThankYou("Toby"), "Thank You Toby");
Hello proxiedHello = (Hello) Proxy.newProxyInstance(getClass().getClassLoader(), new Class[] {
Hello.class
}, new UppercaseHandler(new HelloTarget()));
assertEquals(proxiedHello.sayHello("Toby"), "HELLO TOBY");
assertEquals(proxiedHello.sayHi("Toby"), "HI TOBY");
assertEquals(proxiedHello.sayThankYou("Toby"), "THANK YOU TOBY");
}
</code></pre></div> </div>
</li>
</ul>
<h4 id="633-다이내믹-프록시를-이용한-트랜잭션-부가기능">6.3.3 다이내믹 프록시를 이용한 트랜잭션 부가기능</h4>
<ul>
<li>UserServiceTx 를 다이내믹 프록시 방식으로 변경해보자.</li>
</ul>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@Test
@DirtiesContext
public void upgradeAllOrNothing() {
UserService target = new UserServiceImpl(userDao, new UserLevelUpgradePolicyTest(userDao, users.get(3).getId()));
TransactionHandler txHandler = new TransactionHandler(target, new DataSourceTransactionManager(dataSource), "upgradeLevels");
UserService txUserService = (UserService) Proxy.newProxyInstance(getClass().getClassLoader()
, new Class[] {UserService.class}, txHandler);
userDao.deleteAll();
for (User user : users) {
userDao.add(user);
}
Assertions.assertThrows(TestUserServiceException.class, txUserService::upgradeLevels);
checkLevelUpgraded(users.get(1), false);
}
</code></pre></div></div>
<h4 id="634-다이내믹-프록시를-위한-팩토리-빈">6.3.4 다이내믹 프록시를 위한 팩토리 빈</h4>
<ul>
<li>
<p>사전에 프록시 오브젝트의 클래스 정보를 미리 알아내서 스프링 빈에 정의할 방법이 없다.</p>
</li>
<li>
<p>다이내믹 프록시는 Proxy 클래스의 newProxyInstance() 라는 스태틱 팩토리 메소드를 통해서만 만들 수 있다.</p>
</li>
<li>팩토리 빈
<ul>
<li>스프링을 대신해서 오브젝트의 생성로직을 담당하도록 만들어진 특별한 빈</li>
<li>팩토리 빈을 만드는 여러 방법 중 스프링의 FactoryBean 인터페이스를 구현할 수도 있다.</li>
<li>FactoryBaen 인터페이스
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> public interface FactoryBean<T> {
String OBJECT_TYPE_ATTRIBUTE = "factoryBeanObjectType";
@Nullable
T getObject() throws Exception; // 빈 오브젝트 생성, return
@Nullable
Class<?> getObjectType(); // 생성되는 오브젝트 타입
default boolean isSingleton() { // getObject()가 항상 같은 오브젝트를 리턴하는가
return true;
}
}
</code></pre></div> </div>
</li>
</ul>
</li>
<li>학습 테스트
<ul>
<li>private 생성자 클래스</li>
</ul>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> public class Message {
private String text;
private Message(String text) {
this.text = text;
}
public String getText() {
return text;
}
public static Message newMessage(String text) {
return new Message(text);
}
}
</code></pre></div> </div>
<ul>
<li>사실 스프링은 private 생성자를 가진 클래스도 빈으로 등록하면 리플렉션을 사용해 오브젝트를 만들 수 있다.</li>
<li>하지만 private 로 선언되었다는 것은 스테틱 메소드를 통해 오브젝트가 만들어져야 하는 중요한 이유가 있을 수 있으므로 이를 무시하고 강제로 오브젝트 생성하는 것은 위험하다.</li>
<li>Message 클래스의 오브젝트를 생성해주는 팩토리 빈 클래스</li>
</ul>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> public class MessageFactoryBean implements FactoryBean<Message> {
private String text;
public MessageFactoryBean(String text) {
this.text = text;
}
@Override
public Message getObject() throws Exception {
return Message.newMessage(this.text);
}
@Override
public Class<?> getObjectType() {
return Message.class;
}
@Override
public boolean isSingleton() {
return false;
}
}
</code></pre></div> </div>
</li>
<li>다이내믹 프록시를 만들어주는 팩토리 빈</li>
</ul>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>public class TxProxyFactoryBean implements FactoryBean<Object> {
private Object target;
private PlatformTransactionManager transactionManager;
private String pattern;
Class<?> serviceInterface;
@Override
public boolean isSingleton() {
return false;
}
public TxProxyFactoryBean(Object target, PlatformTransactionManager transactionManager, String pattern, Class<?> serviceInterface) {
this.target = target;
this.transactionManager = transactionManager;
this.pattern = pattern;
this.serviceInterface = serviceInterface;
}
@Override
public Object getObject() throws Exception {
TransactionHandler txHandler = new TransactionHandler(this.target, this.transactionManager, this.pattern);
return Proxy.newProxyInstance(
getClass().getClassLoader(), new Class[] { serviceInterface }, txHandler
);
}
@Override
public Class<?> getObjectType() {
return serviceInterface;
}
}
</code></pre></div></div>
<ul>
<li>예외 발생 시 트랜잭션 롤백됨을 확인하려면 테스트 코드에서 FactoryBean 을 DI 받아 TestUserService 오브젝트를 타깃 오브젝트로 Object를 생성하면 된다.</li>
</ul>
<h4 id="635-프록시-팩토리-빈-방식의-장점과-한계">6.3.5 프록시 팩토리 빈 방식의 장점과 한계</h4>
<ul>
<li>장점
<ul>
<li>한 번 부가기능을 가진 프록시를 생성하는 팩토리 빈을 만들어두면 타깃의 타입에 상관없이 재사용할 수 있다.
<ul>
<li>TransactionHandler 를 이용하는 다이내믹 프록시를 생성해주는 TxProxytFactoryBean 은 코드의 수정 없이도 다양한 클래스에 적용할 수 있다. 타깃 오브젝트만 맞도록 변경하여 빈으로 등록해주면 된다.</li>
</ul>
</li>
<li>다이내믹 프록시를 사용하면 타깃 인터페이스를 구현하는 클래스를 일일이 만드는 번거로움을 제거할 수 있다.</li>
<li>하나의 핸들러 메소드를 구현하는 것만으로도 수많은 메소드에 부가기능을 부여해줄 수 있으니 부가기능 코드 중복 문제도 해결된다.</li>
<li>DI 덕분이다. DI 짱.</li>
</ul>
</li>
<li>한계
<ul>
<li>한 번에 여러 개의 클래스에 공통적인 부가기능을 제공할 수 없다.
<ul>
<li>타깃 오브젝트는 하나.</li>
</ul>
</li>
<li>하나의 타깃에 여러 개의 부가기능을 적용하려고 할 때 문제다.
<ul>
<li>프록시 팩토리 빈 설정(xml, 코드 등)이 부가기능의 개수만큼 늘어난다.</li>
<li>설정파일이 복잡해진다.</li>
<li>TransactionHandler 가 프록시 팩토리 빈 개수만큼 만들어진다.</li>
</ul>
</li>
</ul>
</li>
</ul>
<h3 id="64-스프링의-프록시-팩토리-빈">6.4 스프링의 프록시 팩토리 빈</h3>
<ul>
<li>스프링은 프록시 오브젝트를 생성해주는 기술을 추상화한 팩토리 빈을 제공한다.</li>
<li>스프링의 ProxyFactoryBean 은 프록시를 생성해서 빈 오브젝트로 등록하게 해주는 팩토리 빈이다. 기존에 만들었던 TxProxyFactoryBean과 달리, ProxyFactoryBean은 순수하게 프록시를 생성하는 작업만 담당하고, 프록시를 통해 제공해줄 부가기능은 별도의 빈에 둘 수 있다.
<ul>
<li>부가기능은 MethodInterceptor 인터페이스를 구현
<ul>
<li>InvocationHandler 와 비슷하지만, MethodInterceptor 의 invoke() 메소드는 ProxyFactoryBean 으로부터 타깃 오브젝트에 대한 정보까지 함께 제공받는다.
<ul>
<li>덕분에 타깃 오브젝트에 상관없이 독립적으로 만들어질 수 있다.</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@Test
public void proxyFactoryBean() {
ProxyFactoryBean pfBean = new ProxyFactoryBean();
pfBean.setTarget(new HelloTarget());
pfBean.addAdvice(new UppercaseAdvice());
Hello proxiedHello = (Hello) pfBean.getObject();
assertEquals(proxiedHello.sayHello("Toby"), "HELLO TOBY");
assertEquals(proxiedHello.sayHi("Toby"), "HI TOBY");
assertEquals(proxiedHello.sayThankYou("Toby"), "THANK YOU TOBY");
}
static class UppercaseAdvice implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
String ret = (String)invocation.proceed();
return ret.toUpperCase();
}
}
</code></pre></div></div>
<ul>
<li>어드바이스: 타깃이 필요없는 순수한 부가기능
<ul>
<li>MethodInvocation 은 타깃오브젝트가 등장하지 않는다. 타깃오브젝트의 메소드를 실행할 수 있는 기능이 있기 때문에 부가기능 제공하는 데만 집중할 수 있다.
<ul>
<li>MethodInvocation 의 구현체는 일종의 공유 가능한 템플릿 처럼 동작한다. (템플릿/콜백 패턴)</li>
</ul>
</li>
<li>어드바이스는 타깃 오브젝트에 적용되지만 종속되지 않는 순수한 부가기능을 담은 오브젝트</li>
</ul>
</li>
<li>포인트컷: 부가기능 적용 대상 메소드 선정 방법
<ul>
<li>TxProxyFactoryBean 에서는 pattern 이라는 String 으로 메소드를 선정했었다.</li>
<li>MethodInterceptor 는 재사용 가능한 순수한 부가기능 제공 코드만 남기기 위해, 다이내믹프록시가 MethodInterceptor 를 호출하기 전 메소드 선정 알고리즘을 담은 오브젝트를 호출하는 구조를 제공한다. 이를 포인트컷이라고 한다.</li>
<li>프록시는 클라이언트로부터 요청을 받으면 먼저 포인트컷에게 부가기능을 부여할 메소드인지 확인 요청을 한다. 적용 대상 메소드라면 MethodInterceptor 타입의 어드바이스를 호출한다.</li>
</ul>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> @Test
public void proxyFactoryBean() {
ProxyFactoryBean pfBean = new ProxyFactoryBean();
pfBean.setTarget(new HelloTarget());
NameMatchMethodPointcut pointcut = new NameMatchMethodPointcut();
pointcut.setMappedName("sayH*");
pfBean.addAdvisor(new DefaultPointcutAdvisor(pointcut, new UppercaseAdvice()));
Hello proxiedHello = (Hello) pfBean.getObject();
assertEquals(proxiedHello.sayHello("Toby"), "HELLO TOBY");
assertEquals(proxiedHello.sayHi("Toby"), "HI TOBY");
assertEquals(proxiedHello.sayThankYou("Toby"), "Thank You Toby");
}
</code></pre></div> </div>
<ul>
<li>addAdvice() 메소드를 호출하지않고 Advisor 타입으로 addAdvisor() 메소드를 호출했다.</li>
<li>여러 개의 어드바이스와 포인트 컷이 추가 될 때, 어떤 어드바이스에 어떤 포인트컷을 적용할지 명확하게 하기 위해 이런 방식을 사용.</li>
<li>이렇게 어드바이스와 포인트컷을 묶은 오브젝트를 <code class="language-plaintext highlighter-rouge">어드바이저</code> 라 한다.</li>
<li><strong>어드바이저 = 포인트컷(메소드 선정 알고리즘) + 어드바이스(부가기능)</strong></li>
</ul>
</li>
<li>TransactionAdvice</li>
</ul>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>public class TransactionAdvice implements MethodInterceptor {
private PlatformTransactionManager transactionManager;
public TransactionAdvice(PlatformTransactionManager transactionManager) {
this.transactionManager = transactionManager;
}
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
TransactionStatus status = this.transactionManager.getTransaction(new DefaultTransactionDefinition());
try {
Object ret = invocation.proceed();
this.transactionManager.commit(status);
return ret;
} catch (RuntimeException e) {
this.transactionManager.rollback(status);
throw e;
}
}
}
</code></pre></div></div>
<ul>
<li>Config</li>
</ul>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@Configuration
public class WebConfiguration {
...
@Bean
public ProxyFactoryBean proxyFactoryBean() {
ProxyFactoryBean pfBean = new ProxyFactoryBean();
pfBean.setTarget(this.userServiceImpl());
NameMatchMethodPointcut pointcut = new NameMatchMethodPointcut();
pointcut.setMappedName("upgrade*");
pfBean.addAdvisor(new DefaultPointcutAdvisor(pointcut, new TransactionAdvice(new DataSourceTransactionManager(this.dataSource()))));
return pfBean;
}
@Bean
public UserService userService() {
return (UserService) this.proxyFactoryBean().getObject();
}
...
}
</code></pre></div></div>
<ul>
<li>UserService 외에도 이미 만들어둔 TransactionAdvice 를 재사용할 수 있다.</li>
</ul>
<h3 id="65-스프링-aop">6.5 스프링 AOP</h3>
<ul>
<li>
<p>중복도 제거하고 적절하게 잘 만들어 왔지만, 일정한 타깃 목록이 있을 때 ProxyFactoryBean 타입 빈 설정을 매번 추가해서 프록시를 만들어야 하는게 마음에 안든다.</p>
</li>
<li>빈 후처리기를 사용하면 이를 해결가능하다.
<ul>
<li>DefaultAdvisorAutoProxyCreator 빈 후처리기를 등록하면, 스프링은 빈 오브젝트를 만들 때마다 후처리기에게 빈을 보낸다.</li>
<li>DefaultAdvisorAutoProxyCreator 는 빈으로 등록된 모든 어드바이저 내의 포인트컷을 이용해 전달받은 빈이 프록시 적용대상인지 확인한다.
<ul>
<li>포인트컷은 두 가지 기능이 있다.
<ul>
<li>MethodMatcher : 어드바이스를 적용할 메소드인지 확인</li>
<li>ClassFilter : 프록시를 적용할 클래스인지 확인</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>포인트컷 테스트</li>
</ul>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>public class DynamicProxyTest {
@Test
public void classNamePointcutAdvisor() {
NameMatchMethodPointcut classMethodPointcut = new NameMatchMethodPointcut() {
public ClassFilter getClassFilter() {
return new ClassFilter() {
@Override
public boolean matches(Class<?> clazz) {
return clazz.getSimpleName().startsWith("HelloT");
}
};
}
};
classMethodPointcut.setMappedName("sayH*");
checkAdviced(new HelloTarget(), classMethodPointcut, true);
class HelloWorld extends HelloTarget {};
checkAdviced(new HelloWorld(), classMethodPointcut, false);
class HelloToby extends HelloTarget {};
checkAdviced(new HelloToby(), classMethodPointcut, true);
}
private void checkAdviced(Object target, Pointcut pointcut, boolean adviced) {
ProxyFactoryBean pfBean = new ProxyFactoryBean();
pfBean.setTarget(target);
pfBean.addAdvisor(new DefaultPointcutAdvisor(pointcut, new UppercaseAdvice()));
Hello proxiedHello = (Hello) pfBean.getObject();
if (adviced) {
assertEquals(proxiedHello.sayHello("Toby"), "HELLO TOBY");
assertEquals(proxiedHello.sayHi("Toby"), "HI TOBY");
assertEquals(proxiedHello.sayThankYou("Toby"), "Thank You Toby");
} else {
assertEquals(proxiedHello.sayHello("Toby"), "Hello Toby");
assertEquals(proxiedHello.sayHi("Toby"), "Hi Toby");
assertEquals(proxiedHello.sayThankYou("Toby"), "Thank You Toby");
}
}
}
</code></pre></div></div>
<ul>
<li>DefaultAdvisorAutoProxyCreator 적용</li>
</ul>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>public class NameMatchClassMethodPointcut extends NameMatchMethodPointcut {
public void setMappedClassName(String mappedClassName) {
this.setClassFilter(new SimpleClassFilter(mappedClassName));
}
static class SimpleClassFilter implements ClassFilter {
private String mappedName;
public SimpleClassFilter(String mappedName) {
this.mappedName = mappedName;
}
@Override
public boolean matches(Class<?> clazz) {
return PatternMatchUtils.simpleMatch(mappedName, clazz.getSimpleName());
}
}
}
</code></pre></div></div>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@Configuration
public class WebConfiguration {
...
@Bean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
return new DefaultAdvisorAutoProxyCreator();
}
@Bean
public Pointcut nameMatchClassMethodPointcut() {
NameMatchClassMethodPointcut pointcut = new NameMatchClassMethodPointcut();
pointcut.setMappedName("upgrade*");
pointcut.setMappedClassName("*ServiceImpl");
return pointcut;
}
@Bean
public DefaultPointcutAdvisor testTransactionAdvisor() {
return new DefaultPointcutAdvisor(this.nameMatchClassMethodPointcut(), new TransactionAdvice(new DataSourceTransactionManager(this.dataSource())));
}
...
}
</code></pre></div></div>
<ul>
<li>
<p>이렇게 하면 <code class="language-plaintext highlighter-rouge">DefaultAdvisorAutoProxyCreator</code> 친구가 Advisor 인터페이스를 구현한 것을 모두 찾아 생성되는 빈에 어드바이저의 포인트컷을 적용해보면서 프록시 적용 대상을 선정한다.</p>
</li>
<li>
<p>하지만 특정 상황에 exception 을 발생하는 테스트를 만들기가 어려워져서, TestUserService 를 구현했다.</p>
</li>
</ul>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>public class TestUserLevelUpgradePolicy extends UserLevelUpgradePolicyDefault {
private String id;
public TestUserLevelUpgradePolicy(UserDao userDao) {
super(userDao);
this.id = "id4";
}
@Override
public void upgradeLevel(User user) {
if (user.getId().equals(this.id)) {
throw new TestUserServiceException();
}
super.upgradeLevel(user);
}
}
</code></pre></div></div>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@Configuration
public class WebConfiguration {
@Bean
public UserService testUserService() {
return new UserServiceImpl(this.userDao(), new TestUserLevelUpgradePolicy(this.userDao()));
}
}
</code></pre></div></div>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@SpringBootTest(classes = Application.class)
class UserServiceTest {
...
@Autowired
private UserService testUserService;
...
@Test
public void upgradeAllOrNothing() {
for (User user : users) {
userDao.add(user);
}
Assertions.assertThrows(TestUserServiceException.class, testUserService::upgradeLevels);
checkLevelUpgraded(users.get(1), false);
}
}
</code></pre></div></div>
<h4 id="653-포인트컷-표현식을-이용한-포인트컷">6.5.3 포인트컷 표현식을 이용한 포인트컷</h4>
<ul>
<li>포인트컷 지시자 중 가장 대표적으로 사용되는 것은 execution()
<ul>
<li><code class="language-plaintext highlighter-rouge">excution([접근제한자 패턴] 리턴값타입패턴 [클래스타입패턴.]메소드이름패턴 (파라미터타입패턴 | "..", ...)) [throws 예외 패턴])</code></li>
</ul>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> public class PointcutTest {
@Test
public void methodSignaturePointcut() throws SecurityException, NoSuchMethodException {
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
System.out.println(Target.class.getMethod("minus", int.class, int.class));
pointcut.setExpression("execution(public int "
+ "learningtest.pointcut.Target.minus(int,int) "
+ "throws java.lang.RuntimeException)");
assertTrue(pointcut.getClassFilter().matches(Target.class)
&& pointcut.getMethodMatcher().matches(Target.class.getMethod("minus", int.class, int.class), null));
assertFalse(pointcut.getMethodMatcher().matches(Target.class.getMethod("plus", int.class, int.class), null));
assertFalse(pointcut.getClassFilter().matches(TestBean.class));
}
}
</code></pre></div> </div>
<ul>
<li>적용된 포인트컷 표현식은
<ul>
<li>execution(public int learningtest.pointcut.Target.minus(int,int) throws java.lang.RuntimeException)</li>
</ul>
</li>
<li>필수가 아닌 항목인 접근제어자, 클래스타입, 예외 패턴을 생략하면
<ul>
<li>execution(int minus(int, int))</li>
<li>대신 생략한 부분은 모든 경우를 허용하도록 되어있기에 느슨한 포인트컷이 된다.</li>
<li>더 생략하면
<ul>
<li>execution(* minus(int, int))
<ul>
<li>minus 메소드 이름에 파라미터가 int int 인 애들</li>
</ul>
</li>
<li>execution(* minus(..))
<ul>
<li>minus 메소드 이름인 애들</li>
</ul>
</li>
<li>execution(* *(..))
<ul>
<li>모든 메소드</li>
<li>가장 느슨</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>execution() 외에도 몇 가지 표현식 스타일이 있다
<ul>
<li>대표적으로
<ul>
<li>bean()</li>
<li>@annotaion()</li>
</ul>
</li>
</ul>
</li>
<li>적용해보기</li>
</ul>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@Configuration
public class WebConfiguration {
...
@Bean
public Pointcut pointcut() {
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression("execution(* *..*ServiceImpl.upgrade*(..))");
return pointcut;
}
...
}
</code></pre></div></div>
<ul>
<li>포인트컷 표현식을 사용하면 코드와 설정이 모두 단순화되지만, 문자열로 표현되기에 런타임 시점까지 문법의 검증이나 기능 확인이 어렵다.
<ul>
<li>다양한 테스트를 활용하여 검증이 필요하다.</li>
<li>Vol 2 에서 스프링 지원툴로 포인트컷이 선정한 빈을 확인할 수 있는 도구를 알려줄듯</li>
</ul>
</li>
<li>포인트컷 클래스 타입 패턴은 상속 클래스/구현 인터페이스 모두 바라보고 선정한다.</li>
</ul>
<h4 id="654-aop란-무엇인가">6.5.4 AOP란 무엇인가?</h4>
<ul>
<li>애스펙트 지향 프로그래밍
<ul>
<li>관점 지향 프로그래밍</li>
<li>aspect(애스펙트) 란 부가기능 모듈
<ul>
<li>그 자체로 애플리케이션의 핵심기능을 담고 있지는 않지만, 중요한 한 가지 요소이며 핵심기능에 부가되어 의미를 갖는 특별한 모듈</li>
</ul>
</li>
<li>부가기능을 정의한 어드바이스와 어드바이스를 어디에 적용할 지 결정하는 포인트컷을 함께 갖고 있다.
<ul>
<li>어드바이저는 단순한 형태의 애스펙트</li>
</ul>
</li>
</ul>
</li>
</ul>
<h4 id="655-aop-적용기술">6.5.5. AOP 적용기술</h4>
<ul>
<li>프록시를 이용한 AOP
<ul>
<li>스프링 AOP 는 프록시 AOP 방식을 사용한다고 볼 수 있다.</li>
</ul>
</li>
<li>바이트코드 생성과 조작을 통한 AOP
<ul>
<li>타겟 오브젝트를 뜯어고쳐서 부가기능을 직접 넣어준다.</li>
<li>컴파일된 타깃의 클래스 파일 자체를 수정하거나 JVM 로딩 시점을 가로채서 바이트코드를 조작한다.</li>
</ul>
</li>
</ul>
<h4 id="656-aop-용어">6.5.6 AOP 용어</h4>
<ul>
<li>타깃
<ul>
<li>부가기능을 부여할 대상</li>
</ul>
</li>
<li>어드바이스
<ul>
<li>부가기능을 담은 모듈</li>
</ul>
</li>
<li>조인 포인트
<ul>
<li>어드바이스가 적용될 수 있는 위치</li>
<li>스프링 프록시 AOP 에서는 메소드 실행단계</li>
</ul>
</li>
<li>포인트컷
<ul>
<li>어드바이스를 적용할 조인 포인트를 선별하는 작업 또는 그 기능을 정의한 모듈</li>
</ul>
</li>
<li>프록시
<ul>
<li>클라이언트와 타깃 사이에 투명하게 존재하면서 부가기능을 제공하는 오브젝트</li>
</ul>
</li>
<li>어드바이저
<ul>
<li>포인트컷과 어드바이스를 갖고 있는 오브젝트</li>
<li>스프링 AOP 에서만 사용되는 용어</li>
</ul>
</li>
<li>애스펙트
<ul>
<li>AOP의 기본 모듈</li>
<li>한 개 또는 그 이상의 포인트컷과 어드바이스의 조합</li>
</ul>
</li>
</ul>
<h3 id="66-트랜잭션-속성">6.6 트랜잭션 속성</h3>
<ul>
<li>propagation, isolation level, timeout, readonly ..</li>
</ul>
<h4 id="트랜잭션-전파-transaction-propagation">트랜잭션 전파 (transaction propagation)</h4>
<ul>
<li>
<p>이미 진행 중인 트랜잭션이 있을 때 또는 없을 때 동작 결정 방식</p>
</li>
<li>PROPAGATION_REQUIRED
<ul>
<li>진행 중인 트랜잭션이 없으면 새로 시작하고, 이미 시작된 트랜잭션이 있으면 이에 참여한다.</li>
<li>default</li>
</ul>
</li>
<li>PROPAGATION_REQUIRES_NEW
<ul>
<li>항상 새로운 트랜잭션을 시작한다.</li>
</ul>
</li>
<li>PROPAGATION_NOT_SUPPORTED
<ul>
<li>트랜잭션 없이 동작, 진행 중인 트랜잭션이 있어도 무시</li>
</ul>
</li>
</ul>
<h4 id="격리-수준-isolation-level">격리 수준 (isolation level)</h4>
<ul>
<li>기본적으로 DB에 설정되어 있지만, JDBC 드라이버나 DataSource 등에서 재설정 할 수 있다.
<ul>
<li>필요하다면 트랜잭션 단위로도 가능</li>
<li>Default 는 ISOLATION_DEFAULT
<ul>
<li>이는 DataSource에 설정되어있는 디폴트 격리수준을 따른다.</li>
</ul>
</li>
</ul>
</li>
</ul>
<h4 id="663-포인트컷과-트랜잭션-속성의-적용-전략">6.6.3 포인트컷과 트랜잭션 속성의 적용 전략</h4>
<ul>
<li>트랜잭션 포인트컷 표현식은 타입 패턴이나 빈 이름을 이용한다.</li>
<li>공통된 메소드 이름 규칙을 통해 최소한의 트랜잭션 어드바이스와 속성을 정의한다.</li>
<li>프록시 방식 AOP 는 같은 타깃 오브젝트 내의 메소드 호출할 때는 적용되지 않는다.</li>
</ul>
<h3 id="67-애노테이션-트랜잭션-속성과-포인트컷">6.7 애노테이션 트랜잭션 속성과 포인트컷</h3>
<ul>
<li>스프링은 @Transactional을 적용할 때 4단계의 대체(fallback) 정책을 이용한다.
<ul>
<li>메소드 속성을 확인할 때 타깃 메소드, 타깃 클래스, 선언 메소드, 선언 타입의 순서에 따라서 @Transactional이 적용됐는지 차례로 확인하고, 가장 먼저 발견되는 속성정보를 사용하게 하는 방법.</li>
<li>예를들어 가장 먼저 타깃의 메소드에 @Transactional이 있는지 확인하고, 있으면 이를 사용하고 없으면 다음 대체 후보인 타깃 클래스에 부여된 @Transaction 애노테이션을 찾는다.
<ul>
<li>끝까지 발견되지 않으면 트랜잭션 적용대상이 아닌 것.</li>
</ul>
</li>
</ul>
</li>
</ul>
<h3 id="68-트랜잭션-지원-테스트">6.8 트랜잭션 지원 테스트</h3>
<ul>
<li>
<p>AOP를 이용해 코드 외부에서 트랜잭션의 기능을 부여해주고 속성을 지정할 수 있게 하는 방법을 선언적 트랜잭션 declarative transaction 이라고 한다.</p>
</li>
<li>
<p>반대로, TransactionTemplate 나 개별 데이터 기술의 트랜잭션 API를 사용해 직접 코드안에서 사용하는 방법은 프로그램에 의한 트랜잭션 programmatic transaction 이라고 한다.</p>
</li>
<li>
<p>특별한 경우가 아니라면 선언적 트랜잭션</p>
</li>
</ul>
<h4 id="682-트랜잭션-동기화와-테스트">6.8.2 트랜잭션 동기화와 테스트</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@Test
public void transactionSync() {
userService.deleteAll();
userService.add(users.get(0));
userService.add(users.get(1));
}
</code></pre></div></div>
<ul>
<li>위와 같다면 3개의 트랜잭션이 만들어 졌을 것이다.
<ul>
<li>전파속성이 REQUIRED 이므로</li>
</ul>
</li>
</ul>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@Test
public void transactionSync() {
DefaultTransactionDefinition defaultTransactionDefinition = new DefaultTransactionDefinition();
TransactionStatus txStatus = transactionManager.getTransaction(defaultTransactionDefinition);
userService.deleteAll();
userService.add(users.get(0));
userService.add(users.get(1));
transactionManager.commit(txStatus);
}
</code></pre></div></div>
<ul>
<li>
<p>세 개의 메소드 속성이 REQUIRED 므로 미리 시작된 트랜잭션이 있다면 하나의 트랜잭션에서 수행될 것이다.</p>
</li>
<li>
<p>트랜잭션 동기화/롤백 검증 테스트</p>
</li>
</ul>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@Test
public void transactionSync() {
userDao.deleteAll();
assertEquals(userDao.getCount(), 0);
DefaultTransactionDefinition defaultTransactionDefinition = new DefaultTransactionDefinition();
TransactionStatus txStatus = transactionManager.getTransaction(defaultTransactionDefinition);
userService.deleteAll();
userService.add(users.get(0));
userService.add(users.get(1));
assertEquals(userDao.getCount(), 2);
transactionManager.rollback(txStatus);
assertEquals(userDao.getCount(), 0);
}
</code></pre></div></div>
<ul>
<li>
<p>롤백테스트는 DB 작업이 포함된 테스트가 수행돼도 DB에 영향을 주지 않기 때문에 장점이 많다.</p>
</li>
<li>테스트코드의 @Transactional 은 디폴트 속성이 동일하나, 테스트가 끝나면 자동으로 롤백된다.
<ul>
<li>강제롤백을 원하지 않을 경우 @Rollback 이라는 어노테이션을 사용해야 한다.
<ul>
<li>롤백 여부를 지정 가능. <code class="language-plaintext highlighter-rouge">@Rollback(false)</code></li>
</ul>
</li>
</ul>
</li>
<li>@TransactionalConfiguration 을 사용하면 롤백에 대한 공통 속성을 지정할 수 있다.
<ul>
<li>클래스레벨에서 디폴트 롤백 속성을 false 로 해두고, 테스트 메소드 중 일부 롤백을 적용하고 싶을 때 <code class="language-plaintext highlighter-rouge">@Rollback</code> 을 부여해줘도 된다.</li>
</ul>
</li>
<li>@NotTransactional 을 테스트 메소드에 부여하면 클래스 레벨의 @Transactional 설정이 있어도 트랜잭션을 시작하지 않는다.
<ul>
<li>deprecated 되어서 이제는 전파속성을 사용한다.</li>
<li>@Transaction(propagation=Propagation.NEVER)</li>
</ul>
</li>
<li>테스트 내에서 트랜잭션을 제어할 수 있는 애노테이션을 잘 활용하면 DB가 사용되는 통합테스트를 만들 때 매우 편리하다.</li>
</ul>
<h2 id="7장-스프링-핵심-기술의-응용">7장 스프링 핵심 기술의 응용</h2>
<ul>
<li>
<p>현재는 SQL 변경이 필요할 경우 DAO 코드가 수정되어야 한다.</p>
</li>
<li>
<p>SQL을 적절히 분리해 DAO 코드와 다른 파일이나 위치에 두고 관리할 수 있다면 더 좋을 것 같다.</p>
</li>
<li>
<p>스프링 설정파일을 이용한 분리도 가능하나, SQL과 DI가 섞이게 된다.</p>
</li>
<li>
<p>SQL 서비스를 만들어보자.</p>
</li>
<li><code class="language-plaintext highlighter-rouge">SqlService</code> 의 책임은 2가지
<ul>
<li>SQL 정보를 외부의 리소스로부터 읽어오는 것</li>
<li>읽어온 SQL 을 보관하고 있다가 필요할 때 제공해 주는 것</li>
</ul>
</li>
<li>
<p>부가적인 기능으로 SQL을 필요에 따라 수정할 수 있는 기능</p>
</li>
<li><img src="https://github.com/Oraindrop/oraindrop.github.io/blob/master/assets/_img/SqlService.png?raw=true" alt="SqlService" /></li>
<li>
<p><img src="https://github.com/Oraindrop/oraindrop.github.io/blob/master/assets/_img/SqlRegistry,SqlReader.png?raw=true" alt="SqlRegistry,SqlReader" /></p>
</li>
<li>SqlService interface</li>
</ul>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>public interface SqlService {
String getSql(String key) throws SqlRetrievalFailureException;
}
</code></pre></div></div>
<ul>
<li>SqlReader interface</li>
</ul>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>public interface SqlReader {
void read(SqlRegistry sqlRegistry);
}
</code></pre></div></div>
<ul>
<li>SqlRegistry interface</li>
</ul>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>public interface SqlRegistry {
void registerSql(String key, String sql);
String findSql(String key) throws SqlRetrievalFailureException;
}
</code></pre></div></div>
<ul>
<li>OXM : ObjectXMLMapping
<ul>
<li>XML과 자바오브젝트를 맵핑하여 상호변환해주는 기술</li>
</ul>
</li>
</ul>
<h3 id="74-인터페이스-상속을-통한-안전한-기능확장">7.4 인터페이스 상속을 통한 안전한 기능확장</h3>
<ul>
<li>DI란 미래를 프로그래밍 하는 것</li>
</ul>
<h4 id="742-인터페이스-상속">7.4.2 인터페이스 상속</h4>
<ul>
<li>SqlRegistry interface</li>
</ul>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>public interface SqlRegistry {
void registerSql(String key, String sql);
String findSql(String key) throws SqlRetrievalFailureException;
}
</code></pre></div></div>
<ul>
<li>UpdatableSqlRegistry interface</li>
</ul>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>public interface UpdatableSqlRegistry extends SqlRegistry {
void updateSql(String key, String sql) throws SqlUpdateFailureException;
void updateSql(Map<String ,String> sqlMap) throws SqlUpdateFailureException;
}
</code></pre></div></div>
<ul>
<li>SQL 업데이트 기능을 가진 새로운 인터페이스를 만들었을 때 기존에 SqlRegistry 를 사용하던 BaseSqlService 도 새로운 UpdatableSqlRegistry 인터페이스를 이용해야 할까?
<ul>
<li>그렇지 않다.</li>
</ul>
</li>
<li>
<p>새로운 SQL 업데이트 기능까지 구현한 SQL 레지스트리 클래스를 <code class="language-plaintext highlighter-rouge">MyUpdatableSqlRegistry</code> 라고 할 때, 동일한 오브젝트를 DI 받지만 설계와 코드에서는 각각 필요한 인터페이스에 의존하고 있으면 된다.</p>
</li>
<li><img src="https://github.com/Oraindrop/oraindrop.github.io/blob/master/assets/_img/InterfaceInheritance.png?raw=true" alt="InterfaceInheritance" /></li>
</ul>
<h3 id="75-di를-이용해-다양한-구현-방법-적용하기">7.5 DI를 이용해 다양한 구현 방법 적용하기</h3>
<ul>
<li>ConcurrentHashMap
<ul>
<li>HashMap 은 스레드 세이프 하지 않다.</li>
<li>ConcurrentHashMap 은 안전하면서도 성능이 보장되는 동기화된 HashMap</li>
</ul>
</li>
<li>Test Class</li>
</ul>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>class ConcurrentHashMapSqlRegistryTest {
UpdatableSqlRegistry sqlRegistry;
@BeforeEach
public void setUp() {
sqlRegistry = new ConcurrentHashMapSqlRegistry();
sqlRegistry.registerSql("KEY1", "SQL1");
sqlRegistry.registerSql("KEY2", "SQL2");
sqlRegistry.registerSql("KEY3", "SQL3");
}
@Test
public void find() {
checkFindResult("SQL1", "SQL2", "SQL3");
}
private void checkFindResult(String expected1, String expected2, String expected3) {
assertEquals(sqlRegistry.findSql("KEY1"), expected1);
assertEquals(sqlRegistry.findSql("KEY2"), expected2);
assertEquals(sqlRegistry.findSql("KEY3"), expected3);
}
@Test
public void unknownKey() {
Assertions.assertThrows(SqlRetrievalFailureException.class,
() -> sqlRegistry.findSql("SQL9999!@#$"));
}
@Test
public void updateSingle() {
sqlRegistry.updateSql("KEY2", "Modified2");
checkFindResult("SQL1", "Modified2", "SQL3");
}
@Test
public void updateMulti() {
Map<String, String> sqlmap = new HashMap<>();
sqlmap.put("KEY1", "Modified1");
sqlmap.put("KEY3", "Modified3");
sqlRegistry.updateSql(sqlmap);
checkFindResult("Modified1", "SQL2", "Modified3");
}
@Test
public void updateWithNotExistingKey() {
Assertions.assertThrows(SqlUpdateFailureException.class,
() -> sqlRegistry.updateSql("SQL9999!@#$", "Modified"));
}
}
</code></pre></div></div>
<ul>
<li>interface 구현 클래스</li>
</ul>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>public class ConcurrentHashMapSqlRegistry implements UpdatableSqlRegistry {
private Map<String, String> sqlMap = new ConcurrentHashMap<>();
@Override
public void updateSql(String key, String sql) throws SqlUpdateFailureException {
if (sqlMap.containsKey(key)) {
sqlMap.put(key, sql);
return;
}
throw new SqlUpdateFailureException(key + "is empty");
}
@Override
public void updateSql(Map<String, String> sqlMap) throws SqlUpdateFailureException {
for (Map.Entry<String, String> entry : sqlMap.entrySet()) {
updateSql(entry.getKey(), entry.getValue());
}
}
@Override
public void registerSql(String key, String sql) {
sqlMap.put(key, sql);
}
@Override
public String findSql(String key) throws SqlRetrievalFailureException {
if (sqlMap.containsKey(key)) {
return sqlMap.get(key);
}
throw new SqlRetrievalFailureException(key + "is empty");
}
}
</code></pre></div></div>
<h4 id="컨테이너의-빈-등록-정보-확인">컨테이너의 빈 등록 정보 확인</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@Autowired DefaultListableBeanFactory bf;
@Test
public void beans() {
for (String n : bf.getBeanDefinitionNames()) {
System.out.println(n + " \t " + bf.getBean(n).getClass().getName());
}
}
</code></pre></div></div>
<h2 id="8장-스프링이란-무엇인가">8장 스프링이란 무엇인가?</h2>
<h3 id="81-스프링의-정의">8.1 스프링의 정의</h3>
<ul>
<li>
<p>자바 엔터프라이즈 개발을 편하게 해주는 오픈소스 경량급 애플리케이션 프레임워크</p>
</li>
<li>애플리케이션 프레임워크
<ul>
<li>일반적으로 라이브러리나 프레임워크는 특정 업무 분야나 한 가지 기술에 특화된 목표를 가지고 만들어진다.</li>
<li>스프링은 <code class="language-plaintext highlighter-rouge">애플리케이션 프레임워크</code> 라는 특징을 갖고 있고, 이는 특정 계층/기술/업무분야에 국한되지 않고 애플리케이션의 전 영역을 포괄하는 범용적인 프레임워크를 말한다.</li>
</ul>
</li>
<li>경량급
<ul>
<li>스프링은 20여 개의 모듈로 세분화되고 수십만 라인에 달하는 코드를 가진 매우 복잡하고 방대한 규모의 프레임워크이다.</li>
<li>그럼에도 스프링이 가볍다고 하는 이유는, 불필요하게 무겁지 않기 때문 (<-> EJB)</li>
</ul>
</li>
<li>자바 엔터프라이즈 개발을 편하게
<ul>
<li>단순히 편리한 몇가지 도구나 기능을 제공하는 차원이 아니라, 근본적인 문제점에 도전해서 해결책을 제시한다는 것이 스프링의 특장점.</li>
<li>편리한 애플리케이션 개발이란 개발자가 복잡하고 실수하기 쉬운 로우레벨 기술에 많은 신경을 쓰지 않으면서도 애플리케이션의 핵심인 사용자의 요구사항, 즉 비즈니스 로직을 빠르고 효과적으로 구현하는 것을 말한다.</li>
</ul>
</li>
<li>오픈소스
<ul>
<li>소스가 모두에게 공개되고, 특별한 라이선스를 취득할 필요가 없이 얼마든지 가져다 자유롭게 이용해도 된다는 뜻</li>
<li>아파치 라이선스 버전 2.0
<ul>
<li>상업적인 목적의 제품에 포함시키거나 비공개 프로젝트에 자유롭게 이용가능</li>
<li>스프링을 사용한다는 점과 원 저작자를 밝히고, 제품을 패키징 할 때 라이선스 정보를 포함시키는 등 기본적인 의무사항을 따르면 된다.</li>
</ul>
</li>
<li>오픈소스의 장점
<ul>
<li>개방된 커뮤니티 공간 안에서 투명한 방식으로 다양한 참여를 통해 개발되기 때문에 매우 빠르고 유연한 개발이 가능하다는 것.</li>
<li>잠재적인 버그와 문제점이 빠르게 발견되고 해결될 수 있다.</li>
<li>기업/사용자 입장에서 라이선스 비용에 대한 부담이 없다.</li>
</ul>
</li>
<li>오픈소스의 단점
<ul>
<li>지속적이고 안정적인 개발이 계속될지 불확실하다.</li>
<li>스프링은 이런 오픈소스의 문제점과 한계를 잘 알기에, 개발을 책임지고 진행할 수 있는 전문기업을 만들었다.</li>
</ul>
</li>
</ul>
</li>
</ul>
<h3 id="82-스프링의-목적">8.2 스프링의 목적</h3>
<ul>
<li>엔터프라이즈 애플리케이션 개발을 편하게!</li>
</ul>
<h4 id="821-엔터프라이즈-개발의-복잡함">8.2.1 엔터프라이즈 개발의 복잡함</h4>
<ul>
<li>
<p>엔터프라이즈 시스템이란 서버에서 동작하며 기업과 조직의 업무를 처리해주는 시스템</p>
</li>
<li>첫번째는 기술적인 제약조건과 요구사항이 늘어가기 때문
<ul>
<li>엔터프라이즈 시스템을 개발하는 데는 순수한 비즈니스 로직을 구현하는 것 외에도 기술적으로 고려할 사항이 많다. (성능, 보안, 안정성, 확장성)</li>
<li>타 시스템 연계, 분산 트랜잭션 등 기술적인 요구는 점차 심화되고 그에 따른 복잡도는 증가하여 개발자 개개인이 져야 할 기술적인 부담은 점점 더 커진다.</li>
</ul>
</li>
<li>
<p>두번째는 엔터프라이즈 애플리케이션이 구현해야 할 핵심기능인 비즈니스 로직의 복잡함이 증가하기 때문</p>
</li>
<li>또, 업무 프로세스를 변경하고 조종하는 것을 상시화할 만큼 변화의 속도가 빨라졌다.
<ul>
<li>기능 요구사항, 업무 정책 등이 바뀌기 때문에 애플리케이션을 자주 수정해야 한다.</li>
</ul>
</li>
<li>복잡함을 가증시키는 원인
<ul>
<li>단지 양이 많고 어렵다는 뜻이 아님.</li>
<li>세부 요소가 이해하기 힘든 방식으로 얽혀있고, 그 때문에 쉽게 다루기 어렵다.</li>
<li>근본적인 비즈니스 로직과 엔터프라이즈 기술이라는 두 가지 복잡함이 한데 얽혀 있기 때문</li>
</ul>
</li>
</ul>
<h4 id="822-복잡함을-해결하려는-도전">8.2.2 복잡함을 해결하려는 도전</h4>
<ul>
<li>제거될 수 없는 근본적인 복잡함
<ul>
<li>그 복잡함을 효과적으로 상대할 수 있는 전략과 기법이 필요.</li>
</ul>
</li>
<li>실패한 해결책 : EJB
<ul>
<li>애플리케이션 로직을 담은 핵심코드에서 일부 기술적인 코드가 제거된 것은 사실이지만, 오히려 EJB 환경과 스펙에 종속되는 코드로 만들어져야 하는 부담감이 가증</li>
<li>복잡함을 덜으려다 오히려 더 큰 복잡함을 추가</li>
</ul>
</li>
<li>비침투적인 방식을 통한 효과적인 해결책 : 스프링
<ul>
<li>EJB의 실패를 교훈삼아 출발.</li>
<li>기술적인 복잡함을 애플리케이션 핵심 로직의 복잡함에서 제거하는 것을 목표로 둔 것은 동일하나</li>
<li>EJB 처럼 개발자의 코드에 난입해서 지저분하고 복잡한 코드를 만들어버리는 실수를 하지 않았다.</li>
<li>어떤 기술을 적용했을 때 그 기술과 관련된 코드나 규약 등이 코드에 등장하는 경우를 침투적인(invasive) 기술이라고 한다.</li>
<li>비침투적인(non-invasive) 기술은 기술의 적용 사실이 코드에 직접 반영되지 않는다. 어딘가에는 기술의 적용에 따라 필요한 작업을 해줘야 하겠지만, 애플리케이션 코드 여기저기에 불쑥 등장하거나, 코드의 설계와 구현 방식을 제한하지 않는다.</li>
</ul>
</li>
</ul>
<h4 id="823-복잡함을-상대하는-스프링의-전략">8.2.3 복잡함을 상대하는 스프링의 전략</h4>
<ul>
<li>
<p>기본 전략은 비즈니스 로직을 담은 애플리케이션 코드와 엔터프라이즈 기술을 처리하는 코드를 분리시키는 것.</p>
</li>
<li>기술적 복잡함을 상대하는 전략
<ul>
<li>첫번째 문제 : 기술에 대한 접근방식이 일관성 없고, 특정환경에 종속적이다.
<ul>
<li>공략방법은 추상화
<ul>
<li>로우레벨 기술 구현 부분과 기술을 사용하는 인터페이스를 분리하고, 환경과 세부 기술에 독립적인 접근 인터페이스를 제공하는 것이 가장 좋은 해결책이다.</li>
</ul>
</li>
</ul>
</li>
<li>두번째 문제 : 기술적인 처리를 담당하는 코드가 성격이 다른 코드에 섞여 등장한다.
<ul>
<li>공략방법은 AOP</li>
</ul>
</li>
</ul>
</li>
<li>비즈니스와 애플리케이션 로직의 복잡함을 상대하는 전략
<ul>
<li>객체지향 기술</li>
</ul>
</li>
<li>핵심도구 : 객체지향과 DI</li>
</ul>
<h3 id="83-pojo-프로그래밍">8.3 POJO 프로그래밍</h3>
<h4 id="831-스프링의-핵심-pojo">8.3.1 스프링의 핵심: POJO</h4>
<h4 id="832-pojo란-무엇인가">8.3.2 POJO란 무엇인가?</h4>
<ul>
<li>단순한 Java 오브젝트</li>
</ul>
<h4 id="833-pojo의-조건">8.3.3 POJO의 조건</h4>
<ul>
<li>특정 규약에 종속되지 않는다.
<ul>
<li>특정 규약을 따라 비즈니스 컴포넌트를 만들어야 하는 경우 POJO 가 아니다.</li>
</ul>
</li>
<li>특정 환경에 종속되지 않는다.
<ul>
<li>특정 벤더나 서버의 특정 기업의 프레임워크 안에서만 동작 가능한 경우 POJO 가 아니다.</li>
<li>비즈니스 로직을 담은 코드에 <code class="language-plaintext highlighter-rouge">HttpServletRequest</code> 나 <code class="language-plaintext highlighter-rouge">HttpSession</code>, 캐시와 같은 API 가 등장하거나 웹 프레임워크의 클래스를 직접 이용하는 부분이 있다면 그것은 진정한 POJO 라고 볼 수 없다.</li>
</ul>
</li>
<li>진정한 POJO란 객체지향적인 원리에 충실하면서, 환경과 기술에 종속되지 않고 필요에 따라 재활용될 수 있는 방식으로 설계된 오브젝트</li>
</ul>
<h4 id="834-pojo의-장점">8.3.4 POJO의 장점</h4>
<ul>
<li>기술과 환경에 종속되지 않는다.
<ul>
<li>검증 및 테스트 작성이 수월하다.</li>
<li>자동화된 테스트에 유리하다.</li>
</ul>
</li>
<li>객체지향 설계가 자유롭다.</li>
</ul>
<h4 id="835-pojo-프레임워크">8.3.5 POJO 프레임워크</h4>
<ul>
<li>
<p>스프링은 POJO를 이용한 엔터프라이즈 애플리케이션 개발을 목적으로 하는 프레임워크</p>
</li>
<li>
<p>스프링은 개발자들이 복잡한 엔터프라이즈 기술보다는 객체지향적인 설계와 개발의 원리에 좀 더 집중할 수 있도록 돕는다.</p>
</li>
</ul>
<h3 id="84-스프링의-기술">8.4 스프링의 기술</h3>
<ul>
<li>스프링에는 POJO 프로그래밍을 손쉽게 할 수 있도록 지원하는 세 가지 가능기술을 제공한다.
<ul>
<li>IoC/DI, AOP, PSA</li>
</ul>
</li>
</ul>
<h4 id="841-iocdi">8.4.1 IoC/DI</h4>
<ul>
<li>직접 자신이 사용할 오브젝트를 <code class="language-plaintext highlighter-rouge">new</code> 키워드로 생성해서 사용하는 강한 결합을 쓰는 방법보다 나은 점은 무엇일까?
<ul>
<li><strong>유연한 확장이 가능하게 하기 위해서</strong></li>
</ul>
</li>
<li>DI의 활용 방법
<ul>
<li>핵심기능의 변경</li>
<li>핵심기능의 동적인 변경</li>
<li>부가기능의 추가</li>
<li>인터페이스의 변경</li>
<li>프록시</li>
<li>템플릿/콜백</li>
<li>싱클톤과 오브젝트 스코프</li>
<li>테스트</li>
</ul>
</li>
</ul>
<h4 id="842-aop">8.4.2 AOP</h4>
<ul>
<li>
<p>AOP와 OOP는 서로 배타적이 아니다.</p>
</li>
<li>AOP의 적용 기법
<ul>
<li>스프링과 같이 다이내믹 프록시 사용</li>
<li>자바 언어의 한계를 넘어서는 언어의 확장을 이용하는 방법
<ul>
<li>e.g.) AspectJ 사용</li>
</ul>
</li>
</ul>
</li>
<li>AOP 적용 단계
<ul>
<li>미리 준비된 AOP 이용
<ul>
<li>e.g.) <code class="language-plaintext highlighter-rouge">@Transactional</code></li>
</ul>
</li>
<li>전담팀을 통한 정책 AOP 적용
<ul>
<li>보안, 로깅, 트레이싱, 성능 모니터링 등</li>
</ul>
</li>
<li>AOP의 자유로운 이용</li>
</ul>
</li>
</ul>
<h4 id="843-psa-포터블-서비스-추상화">8.4.3 PSA (포터블 서비스 추상화)</h4>
<ul>
<li>환경과 세부 기술의 변화에 관계없이 일관된 방식으로 기술에 접근할 수 있게 해주는 PSA</li>
</ul>
Thu, 01 Apr 2021 00:00:00 +0000
/book/2021/04/01/%ED%86%A0%EB%B9%84%EC%9D%98-%EC%8A%A4%ED%94%84%EB%A7%81-5-8.html
/book/2021/04/01/%ED%86%A0%EB%B9%84%EC%9D%98-%EC%8A%A4%ED%94%84%EB%A7%81-5-8.htmljava,book,springBook도서 - 토비의 스프링 3.1 Vol 1, 1장~4장<h1 id="토비의-스프링-31-vol-1--315p">토비의 스프링 3.1 Vol 1 (~ 315p)</h1>
<h2 id="1장-오브젝트와-의존관계">1장 오브젝트와 의존관계</h2>
<h3 id="단어장">단어장</h3>
<ul>
<li>리팩토링
<ul>
<li>기존의 코드를 외부의 동작방식에는 변화 없이 내부 구조를 변경해서 재구성하는 작업 또는 기술</li>
</ul>
</li>
<li>디자인 패턴
<ul>
<li>소프트웨어 설계 시 특정 상황에서 자주 만나는 문제를 해결하기 위해 사용할 수 있는 재사용 가능한 솔루션</li>
</ul>
</li>
<li>템플릿 메소드 패턴
<ul>
<li>상속을 통해 슈퍼클래스의 기능을 확장할 때 사용하는 가장 대표적인 방법</li>
<li>자주 변경되며 확장할 기능은 서브클래스에서 만들도록 하는 것</li>
<li>서브클래스에서 <em>선택적으로</em> 오버라이드 할 수 있도록 만들어둔 메소드를 <strong>훅(hook) 메소드</strong> 라고 한다.</li>
</ul>
</li>
<li>팩토리 메소드 패턴
<ul>
<li>서브클래스에서 구체적인 오브젝트 생성 방법을 결정</li>
<li>서브클래스에서 오브젝트 생성 방법과 클래스를 결정할 수 있도록 미리 정의해둔 메소드를 <strong>팩토리 메소드</strong> 라고 한다.</li>
</ul>
</li>
<li>객체지향 설계 원칙
<ul>
<li>SOLID</li>
<li>SRP(단일 책임 원칙), OCP(개방 폐쇄 원칙), LSP(리스코프 치환 원칙), ISP(인터페이스 분리 원칙), DIP(의존관계 역전 원칙)</li>
</ul>
</li>
<li>개방 폐쇄 원칙 (OCP)
<ul>
<li>Open-Closed Principle</li>
<li>객체지향 설계 원칙 중 하나</li>
<li>클래스나 모듈은 확장에 열려있어야 하고, 변경에는 닫혀있어야 한다.</li>
</ul>
</li>
<li>전략 패턴
<ul>
<li>자신의 기능 맥락에서 필요에 따라 변경이 필요한 알고리즘을 인터페이스를 통해 외부로 분리시키고,</li>
<li>이를 구현한 구체적인 알고리즘 클래스를 필요에 따라 바꿔서 사용할 수 있게 하는 디자인 패턴</li>
</ul>
</li>
<li>빈(Bean)
<ul>
<li>스프링에 제어권을 가지고 직접 만들고 관계를 부여하는 오브젝트</li>
</ul>
</li>
<li>빈 팩토리
<ul>
<li>스프링 IoC 를 담당하는 핵심 컨테이너</li>
<li>빈 등록, 생성, 조회 등 관리</li>
</ul>
</li>
<li>애플리케이션 컨텍스트
<ul>
<li>빈 팩토리를 확장한 IoC 컨테이너</li>
<li>빈팩토리 기능 + 부가서비스</li>
</ul>
</li>
<li>싱글톤 패턴
<ul>
<li>애플리케이션 내에서 클래스를 제한된 인스턴스 개수(주로 하나)만 존재하도록 강제하는 패턴</li>
</ul>
</li>
</ul>
<h3 id="12-dao의-분리">1.2 DAO의 분리</h3>
<ul>
<li>관심사의 분리
<ul>
<li>Separation of Concerns</li>
<li>관심사가 같은 것끼리 하나의 객체 or 친한 객체</li>
<li>관심사가 다른 것은 가능한 한 서로 영향을 주지 않도록 분리</li>
</ul>
</li>
<li>
<p>UserDao add() 메소드의 관심사항</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> public void add(User user) throws SQLException, ClassNotFoundException {
Class.forName ("org.h2.Driver");
Connection c = DriverManager.getConnection("jdbc:h2:mem:toby-spring", "sa", "");
PreparedStatement ps = c.prepareStatement(
"insert into user (id, name, password) values (?,?,?)");
ps.setString(1, user.getId());
ps.setString(2, user.getName());
ps.setString(3, user.getPassword());
ps.executeUpdate();
ps.close();
c.close();
}
</code></pre></div> </div>
<ol>
<li>DB와 연결(커넥션)
<ul>
<li>어떤 DB, 드라이버, 로그인 정보, 커넥션 생성방법</li>
</ul>
</li>
<li>DB에 요청할 SQL 문장을 담고 실행
<ul>
<li>파라미터 바인딩, SQL 실행</li>
</ul>
</li>
<li>리소스 해제</li>
</ol>
</li>
<li>
<p>중복된 DB 연결코드 메소드 추출</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> public void add(User user) throws SQLException, ClassNotFoundException {
Connection c = getConnection();
...
(생략)
}
private Connection getConnection() throws ClassNotFoundException, SQLException {
Class.forName("org.h2.Driver");
return DriverManager.getConnection("jdbc:h2:mem:toby-spring", "sa", "");
}
</code></pre></div> </div>
</li>
<li>DB 커넥션 생성 방식의 추상화
<ul>
<li>Connection 을 생성하는 구체적인 코드를 상속받은 쪽에서.</li>
</ul>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> public abstract class UserDao {
...
public abstract Connection getConnection() throws ClassNotFoundException, SQLException;
}
public class CustomUserDao extends UserDao {
@Override
public Connection getConnection() throws ClassNotFoundException, SQLException {
Class.forName("org.h2.Driver");
return DriverManager.getConnection("jdbc:h2:mem:toby-spring", "sa", "");
}
}
</code></pre></div> </div>
<p>이렇게 슈퍼클래스에 기본적인 로직의 흐름을 만들고, 그 기능의 일부를 추상메소드나 오버라이딩이 가능한 메소드로 만든 뒤
서브 클래스에서 이런 메소드를 필요에 맞게 구현해서 사용하도록 하는 방법을 <strong>템플릿 메소드 패턴</strong> 이라고 한다.</p>
<p>또한 UserDao의 getConnection() 메소드는 Connection 타입 오브젝트를 생성한다는 기능을 정의해놓은 추상 메소드.
UserDao의 서브클래스의 getConnection() 구현이 Connection 오브젝트를 어떻게 생성할 것인지 결정하는 방법.
이렇게 서브클래스에서 구체적인 오브젝트 생성 방법을 결정하게 하는 것을 <strong>팩토리 메소드 패턴</strong></p>
<ul>
<li>단점
<ul>
<li>상속을 사용했다.</li>
<li>다중상속을 허용하지 않기에, UserDao가 다른 목적을 위해 상속을 사용하고 있었다면?</li>
<li>상속관계는 두 가지 다른 관심사에 대해 긴밀한 결합을 허용함</li>
</ul>
</li>
</ul>
</li>
</ul>
<h3 id="13-dao의-확장">1.3 DAO의 확장</h3>
<ul>
<li>클래스의 분리
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> public class UserDao {
private SimpleConnectionMaker simpleConnectionMaker;
public UserDao() {
simpleConnectionMaker = new SimpleConnectionMaker();
}
public void add(User user) throws SQLException, ClassNotFoundException {
Connection c = simpleConnectionMaker.makeNewConnection();
...
}
}
public class SimpleConnectionMaker {
public Connection makeNewConnection() throws ClassNotFoundException, SQLException {
Class.forName("org.h2.Driver");
return DriverManager.getConnection("jdbc:h2:mem:toby-spring", "sa", "");
}
}
</code></pre></div> </div>
<p>DB 커넥션 생성기능을 새로운 클래스로 분리, 독립 시킨다.</p>
<ul>
<li>단점
<ul>
<li>UserDao가 SimpleConnectionMaker 라는 특정 클래스에 종속되어 있다.
<ul>
<li>UserDao 코드 수정 없이 DB 커넥션 생성 기능을 변경할 방법이 없다.</li>
</ul>
</li>
<li>DB 커넥션 제공 클래스의 메소드 명이 다를 수 있다.</li>
</ul>
</li>
</ul>
</li>
<li>인터페이스의 도입
<ul>
<li>긴밀한 연결 사이 추상적인 느슨한 연결고리를 만들어준다.</li>
</ul>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> public interface ConnectionMaker {
Connection makeConnection() throws ClassNotFoundException, SQLException;
}
public class SimpleConnectionMaker implements ConnectionMaker {
@Override
public Connection makeConnection() throws ClassNotFoundException, SQLException {
Class.forName("org.h2.Driver");
return DriverManager.getConnection("jdbc:h2:mem:toby-spring", "sa", "");
}
}
public class UserDao {
private ConnectionMaker connectionMaker;
public UserDao(ConnectionMaker connectionMaker) {
this.connectionMaker = connectionMaker;
}
...
}
public class Application {
public static void main(String[] args) throws SQLException, ClassNotFoundException {
UserDao dao = new UserDao(new SimpleConnectionMaker());
...
}
}
</code></pre></div> </div>
<p>UserDao 클래스를 높은 응집도와 낮은 결합도 (high coherence and low coupling) 를 갖도록 리팩토링
<strong>높은 응집도</strong> 란, 하나의 책임 또는 관심사에만 집중되어있는 형태
<strong>낮은 결합도</strong> 란, 다른 모듈과 느슨하게 연결된 형태</p>
</li>
</ul>
<h4 id="전략-패턴">전략 패턴</h4>
<ul>
<li>MainClass - UserDao - ConnectionMaker 구조는 전략(Strategy) 패턴에 해당한다.</li>
<li>UserDao 는 전략 패턴의 <strong>컨텍스트</strong>
<ul>
<li>컨텍스트는 자신의 기능을 수행하는데 필요한 기능 중 변경 가능한 DB 연결 방식이라는 알고리즘을 ConnectionMaker 라는 인터페이스로 정의하고</li>
<li>이를 구현한 클래스(즉 전략)를 변경하며 사용할 수 있게 분리했다.</li>
</ul>
</li>
<li>컨텍스트(UserDao) 를 사용하는 클라이언트(MainClass) 는 컨텍스트가 사용할 전략(ConnectionMaker를 구현한 클래스)을 컨텍스트의 생성자 등을 통해 선택, 제공</li>
</ul>
<h3 id="14-제어의-역전ioc">1.4 제어의 역전(IoC)</h3>
<ul>
<li>오브젝트 팩토리
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> public class DaoFactory {
public UserDao userDao() {
return new UserDao(new SimpleConnectionMaker());
}
}
public class Application {
public static void main(String[] args) throws SQLException, ClassNotFoundException {
UserDao dao = new DaoFactory().userDao();
...
}
}
</code></pre></div> </div>
<p>책임의 분리, 어떤 ConnectionMaker 구현 클래스를 사용할 지 결정하는 기능을 팩토리 클래스에게 완벽히 위임한다.
오브젝트를 생허나는 쪽과 생성된 오브젝트를 사용하는 쪽의 역할과 책임을 깔끔하게 분리
어떻게 만들지와 어떻게 사용할지는 분명 다른 관심이다.</p>
</li>
</ul>
<h4 id="143-제어권-이전을-통한-제어관계-역전">1.4.3 제어권 이전을 통한 제어관계 역전</h4>
<ul>
<li>일반적인 프로그램 흐름 상
<ul>
<li>main() 메소드와 같은 프로그램 시작부에서 다음 사용할 오브젝트를 결정하고, 결정한 오브젝트를 생성하고, 만들어진 오브젝트에 있는 메소드를 호출하고, 그 오브젝트 메소드 안에서 다음에 사용할 것을 결정하고 호출하는 식의 작업이 반복된다.</li>
<li>모든 오브젝트가 능동적으로 자신이 사용할 클래스를 결정하고, 언제 어떻게 그 오브젝트를 만들지를 스스로 관장한다.</li>
<li><em>모든 종류의 작업을 사용하는 쪽에서 제어하는 구조</em></li>
</ul>
</li>
<li>제어의 역전이란 이러한 제어 흐름의 개념을 거꾸로 뒤집는 것.
<ul>
<li>오브젝트가 자신이 사용할 오브젝트를 스스로 선택하지 않는다. 생성하지도 않는다. 자신도 어떻게 만들어지고 어디서 사용되는지 알 수 없다.</li>
<li><em>제어 권한을 갖는 특별한 오브젝트에 의해 결정되고 만들어진다.</em></li>
</ul>
</li>
<li>제어의 역전의 개념을 쉽게 보면
<ul>
<li>UserDao 개선 작업 시 서브클래스를 만들었을 때.</li>
<li>서브클래스가 getConnection() 을 구현하지만, 메소드가 언제 어떻게 사용될지는 자신은 모른다.</li>
<li>슈퍼클래스의 템플릿 메소드에서 필요시 호출해서 사용하는 것.</li>
<li>이처럼 제어권을 상위에 넘기고 자신은 필요할 때 호출되어 사용되도록 한다는 개념</li>
</ul>
</li>
<li>라이브러리와 프레임워크의 차이
<ul>
<li>라이브러리를 사용하는 애플리케이션 코드는 애플리케이션 흐름을 직접 제어한다.</li>
<li>동작 중 필요한 기능이 있을 때 능동적으로 라이브러리 사용.</li>
<li>프레임워크는 거꾸로 애플리케이션 코드가 프레임워크에 의해 사용된다.</li>
<li>프레임워크 위에 개발한 클래스를 등록해두고, 프레임워크가 흐름을 주도하는 중에 개발자가 만든 애플리케이션 코드를 사용하도록 만든 방식.</li>
<li>즉 필자는 분명한 제어의 역전개념이 적용되어있어야 프레임워크라고 말하고 있다.</li>
</ul>
</li>
</ul>
<h3 id="15-제어의-역전ioc">1.5 제어의 역전(IoC)</h3>
<ul>
<li>스프링 IoC
<ul>
<li>빈 팩토리(빈을 생성하고 관계를 설정) 이자 애플리케이션 컨텍스트(구성요소의 제어) 이다.
<ul>
<li>사실 똑같은 말</li>
</ul>
</li>
<li>적용
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> @Configuration
public class DaoFactory {
@Bean
public UserDao userDao() {
return new UserDao(this.connectionMaker());
}
@Bean
public ConnectionMaker connectionMaker() {
return new SimpleConnectionMaker();
}
}
</code></pre></div> </div>
</li>
</ul>
<p>이 클래스는 이제 자바 코드의 탈을 쓴 스프링 전용 설정정보라고 볼 수 있다.</p>
<ul>
<li>이렇게 스프링을 적용 했을 때 얻을 수 있는 이득을 앞으로 알아보자.</li>
</ul>
</li>
<li>애플리케이션 컨텍스트의 동작방식
<ul>
<li><img src="https://github.com/Oraindrop/oraindrop.github.io/blob/master/assets/_img/ApplicationContextProcess.jpg?raw=true" alt="ApplicationContextProcess" />
<ul>
<li><code class="language-plaintext highlighter-rouge">애플리케이션 컨텍스트</code> DaoFactory 클래스를 설정정보로 등록</li>
<li><code class="language-plaintext highlighter-rouge">애플리케이션 컨텍스트</code> @Bean이 붙은 메소드의 이름을 가져와 빈 목록 생성</li>
<li><code class="language-plaintext highlighter-rouge">클라이언트</code> 가 <code class="language-plaintext highlighter-rouge">애플리케이션 컨텍스트</code> 의 getBean() 메소드를 호출</li>
<li><code class="language-plaintext highlighter-rouge">애플리케이션 컨텍스트</code> 는 빈 목록에서 요청한 이름을 찾아 return.</li>
</ul>
</li>
<li>장점
<ol>
<li>클라이언트는 구체적인 팩토리 클래스를 알 필요가 없다.
<ul>
<li>프로그램이 커져서 팩토리 클래스가 늘어나도 애플리케이션 컨텍스트를 이용하여 일관된 방식으로 원하는 오브젝트를 가져올 수 있다.</li>
</ul>
</li>
<li>애플리케이션 컨텍스트는 종합 IoC 서비스를 제공해준다.
<ul>
<li>오브젝트 생성 및 관계설정 뿐 아니라 오브젝트 생성 방식, 시점, 전략, 자동생성, 후처리, 설정 방식 다변화 등 오브젝트를 효과적으로 활용할 수 있는 다양한 기능을 제공한다.</li>
</ul>
</li>
<li>애플리케이션 컨텍스트는 빈을 검색하는 다양한 방법을 제공한다.
<ul>
<li>빈의 이름 뿐 아니라 타입, 어노테이션 등으로 빈을 찾을 수도 있다.</li>
</ul>
</li>
</ol>
</li>
</ul>
</li>
</ul>
<h3 id="16-싱글톤-레지스트리와-오브젝트-스코프">1.6 싱글톤 레지스트리와 오브젝트 스코프</h3>
<ul>
<li>오브젝트가 동일하다
<ul>
<li>하나의 오브젝트만 존재하는 것이고 두 개의 오브젝트 레퍼런스 변수를 갖고 있다.</li>
</ul>
</li>
<li>오브젝트가 동등하다
<ul>
<li>기준에 따라 두 오브젝트의 정보가 동등하다고 판단하는 것.</li>
</ul>
</li>
<li>
<p>스프링은 여러 번에 걸쳐 빈을 요청하더라도 매번 동일한 오브젝트를 돌려준다.</p>
</li>
<li>
<p>애플리케이션 컨텍스트는 싱글톤을 저장하고 관리하는 <code class="language-plaintext highlighter-rouge">싱글톤 레지스트리</code> 이다.</p>
</li>
<li>최초 제한된 리소스에서 성능을 확보하기 위해 싱글톤으로 고안
<ul>
<li>스펙에서 강제하지 않지만, 서블릿도 대부분 멀티스레드 환경에서 싱글톤으로 동작한다.</li>
</ul>
</li>
<li>싱글톤 패턴은 사용하기 까다롭고 여러가지 문제점이 있다.
<ul>
<li>안티패턴이라고 생각하는 사람도 존재</li>
</ul>
</li>
<li>싱글톤 패턴의 한계
<ul>
<li>자바의 싱글톤 구현
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> public class UserDao {
private static UserDao INSTANCE;
...
private UserDao(ConnectionMaker connectionMaker) {
this.connectionMaker = connectionMaker;
}
public static synchronized UserDao getInstance() {
if (INSTANCE == null) INSTANCE = new UserDao(...);
return INSTANCE;
}
}
</code></pre></div> </div>
<p>클래스 밖에서 Instance 를 생성하지 못하도록 private 생성자
자신과 같은 타입의 static field 정의
스테틱 팩토리 메소드를 만들고, 최초 호출되는 시점 한 번만 Instance 생성
이후 이미 만들어져 스태틱 필드에 저장해둔 오브젝트만 넘겨준다</p>
</li>
<li>문제점
<ol>
<li>private 생성자를 갖고 있기에 상속할 수 없다.</li>
<li>테스트하기 힘들다
<ul>
<li>Mocking 이 힘들고, 오브젝트를 다이내믹하게 주입하기 힘들다.</li>
</ul>
</li>
<li>서버환경에서 싱글톤이 하나만 만들어지는 것을 보장할 수 없다.
<ul>
<li>서버 클래스 로더 구성에 따라, 싱글톤 클래스임에도 하나 이상의 오브젝트가 만들어질 수 있다.</li>
<li>여러 JVM 에 분산되어 설치되면 각각 독립적으로 오브젝트가 생성되기에 싱글톤 가치가 떨어진다.</li>
</ul>
</li>
<li>싱글톤의 사용은 전역 상태를 만들 수 있기에 바람직하지 못하다.</li>
</ol>
</li>
</ul>
</li>
<li>싱글톤 레지스트리
<ul>
<li>스프링이 직접 싱글톤 형태의 오브젝트를 만들고 관리하는 기능</li>
<li>스태틱 메소드와 private 생성자를 사용하지 않고, 평범한 자바 클래스를 싱글톤으로 활용할 수 있다.</li>
<li>싱글톤 환경이 아니면 자유롭게 오브젝트를 만들 수 있고, 테스트가 용이해진다.</li>
<li>고전적인 싱글톤 패턴을 대신한다.</li>
</ul>
</li>
<li>싱글톤과 오브젝트의 상태
<ul>
<li>싱글톤은 멀티스레드 환경이라면 여러 스레드가 동시에 접근해서 사용할 수 있다.</li>
<li>때문에 무상태(stateless) 방식으로 만들어져야 한다.</li>
<li>읽기전용 정보 외에 인스턴스 필드를 사용하면 위험하다.</li>
</ul>
</li>
<li>스프링 빈의 스코프
<ul>
<li>빈이 생성되고, 존재하고, 적용되는 범위</li>
<li>기본은 싱글톤</li>
<li>prototype 스코프(매번 새로운 오브젝트 생성), request 스코프(HTTP 요청 시 오브젝트 생성), session 스코프 등이 존재</li>
</ul>
</li>
</ul>
<h3 id="17-의존관계-주입di">1.7 의존관계 주입(DI)</h3>
<ul>
<li>
<p>IoC 컨테이너 = DI 컨테이너</p>
</li>
<li>
<p>의존하고 있다는 것은 <strong>의존대상이 변하면 영향을 미친다는 뜻</strong></p>
</li>
<li>A에서 B에 정의된 메소드를 호출해서 사용하는 경우, <code class="language-plaintext highlighter-rouge">사용에 대한 의존관계</code> 가 있다.
<ul>
<li>B에 새로운 메소드가 추가되거나, 기존 메소드 형식이 바뀌면 A도 그에따라 수정되어야 한다.</li>
<li>이 경우 B는 A에 의존하지 않는다.</li>
</ul>
</li>
<li>UserDao 의 의존관계
<ul>
<li>작업해왔던 UserDao 는 ConnectionMaker 인터페이스에 의존하고 있다.
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> public class UserDao {
private ConnectionMaker connectionMaker;
...
}
</code></pre></div> </div>
</li>
<li>ConnectionMaker 인터페이스가 변한다면 UserDao 가 직접적인 영향을 받게 된다.</li>
<li>하지만 ConnectionMaker 의 구현체인 SimpleConnectionMaker 가 다른 것으로 바뀌거나 내부에서 사용하는 메소드가 변화해도 <strong>UserDao에 영향을 주지 않는다.</strong></li>
<li>인터페이스에만 의존관계를 만들어두면 구현 클래스와 관계가 느슨해지면서 변화에 영향을 덜 받는 상태가 된다.
<ul>
<li>즉, 결합도가 낮다.</li>
</ul>
</li>
<li>설계 관점의 의존관계는 이렇지만, 런타임 시 설계 시점의 의존관계가 실체화 되는데
<ul>
<li>런타임 의존관계 또는 오브젝트 의존관계</li>
</ul>
</li>
<li>런타임 의존관계는 모델링 시점의 의존관계와는 성격이 분명히 다르다.</li>
<li>프로그램이 시작되고 UserDao 오브젝트가 만들어지고 나서 런타임 시에 의존관계를 맺는 대상, 즉 실제 사용대상인 오브젝트를 <code class="language-plaintext highlighter-rouge">의존 오브젝트</code> 라고 말한다.</li>
<li><strong>DI는 이렇게 구체적인 의존 오브젝트와 그것을 사용할 주체, 보통 클라이언트라고 부르는 오브젝트를 런타임 시에 연결해주는 작업을 말한다.</strong></li>
<li>정리하면 의존관계의 주입이란 아래 세 가지 조건을 충족한다.
<ol>
<li>클래스 모델이나 코드에는 런타임 시점의 의존관계가 드러나지 않는다. 그러기 위해서는 인터페이스에만 의존하고 있어야 한다.</li>
<li>런타임 시점의 의존관계는 컨테이너나 팩토리 같은 제3의 존재가 결정한다.</li>
<li>의존관계는 사용할 오브젝트에 대한 레퍼런스를 외부에서 제공(주입)해줌으로써 만들어진다.</li>
</ol>
</li>
<li>핵심은, 설계시점에 알지 못했던 두 오브젝트의 관계를 맺도록 도와주는 제3의 존재가 있다는 것.</li>
</ul>
</li>
<li>의존관계 검색 (DL)
<ul>
<li>코드에서는 구체적인 클래스에 의존하지 않고, 런타임 시에 의존관계를 결정한다는 점은 의존관계 주입과 비슷</li>
<li>외부로부터 주입이 아니라 스스로 검색을 이용</li>
<li>자신이 필요로 하는 의존 오브젝트를 능동적으로 찾는다. 물론 자신이 어떤 클래스의 오브젝트를 이용할지 결정하지는 않는다.</li>
<li>의존관계를 맺을 오브젝트를 결정하는 것과 오브젝트 생성작업은 외부 컨테이너에게 IoC를 맡기지만, 이를 가져올 때는 메소드나 생성자를 통한 주입 대신 스스로 컨테이너에 요청하는 방법을 사용한다.
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> public UserDao() {
DaoFactory daoFactory = new DaoFactory();
this.connectionMaker = daoFactory.connectionMaker();
}
</code></pre></div> </div>
</li>
<li>이렇게 해도 UserDao는 여전히 자신이 어떤 ConnectionMaker 오브젝트를 사용할지 미리 알지 못한다.</li>
<li>여전히 코드의 의존대상은 ConnectionMaker 인터페이스뿐이다.</li>
<li>이 경우 단순히 요청이지만, 이런 작업을 일반화한 스프링 애플리케이션 컨텍스트라면 미리 정해놓은 이름을 전달해서 그 이름에 해당하는 오브젝트를 찾게 된다.</li>
</ul>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> public UserDao() {
ApplicationContext context = new AnnotationConfigApplicationContext(DaoFactory.class);
this.connectionMaker = context.getBean("connectionMaker", ConnectionMaker.class);
}
</code></pre></div> </div>
<ul>
<li>단점은 UserDao 에서 스프링이나 오브젝트 팩토리를 만들고 API를 이용하는 코드가 섞여있게 되는 것.</li>
<li>때문에 <strong>대개는 의존관계 주입 방식을 사용하는 편이 낫다.</strong></li>
<li>다만 애플리케이션의 기동시점에서 적어도 한 번은 의존관계 검색 방식을 사용해 오브젝트를 가져와야 한다.
<ul>
<li>스테틱 메소드인 main()에서는 DI를 이용해 오브젝트를 주입받을 방법이 없기 때문이다.</li>
</ul>
</li>
<li>서버에서도 마찬가지다.
<ul>
<li>서버에는 main()과 같은 기동 메소드는 없지만, 사용자의 요청을 받을 때마다 한 번은 의존관계 검색 방식을 사용해 오브젝트를 가져와야 한다.</li>
</ul>
</li>
<li>의존관계 검색과 의존관계 주입의 중요한 차이점
<ul>
<li><strong>의존관계 검색 방식에서 검색하는 오브젝트는 자신이 스프링의 빈일 필요가 없다.</strong></li>
</ul>
</li>
<li>DI를 원하는 오브젝트는 먼저 자기 자신이 컨테이너가 관리하는 빈이 돼야 한다는 사실을 잊지 말자.</li>
</ul>
</li>
<li>DI 응용
<ul>
<li>기능 구현의 교환
<ul>
<li>개발용 ConnectionMaker 생성 코드
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> @Bean
public ConnectionMaker connectionMaker() {
return new LocalDBConnectionMaker();
}
</code></pre></div> </div>
</li>
<li>운영용 ConnectionMaker 생성 코드
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> @Bean
public ConnectionMaker connectionMaker() {
return new ProductionDBConnectionMaker();
}
</code></pre></div> </div>
</li>
<li>DAO가 100개든 1000개이든 각각 커넥션 메이커를 생성하는게 아니기에, 이렇게 딱 한 줄만 수정하면 된다.</li>
</ul>
</li>
<li>부가기능 추가
<ul>
<li>DAO가 DB를 얼마나 많이 연결해서 사용하는지 파악하고 싶을 경우</li>
<li>부가기능을 추가한 ConnectionMaker 인터페이스를 구현한 클래스를 만들면 된다.
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> public class CountingConnectionMaker implements ConnectionMaker {
private int count = 0;
private ConnectionMaker realConnectionMaker;
public CountingConnectionMaker(ConnectionMaker realConnectionMaker) {
this.realConnectionMaker = realConnectionMaker;
}
@Override
public Connection makeConnection() throws ClassNotFoundException, SQLException {
this.count++;
return this.realConnectionMaker.makeConnection();
}
public int getCount() {
return this.count;
}
}
</code></pre></div> </div>
</li>
<li><img src="https://github.com/Oraindrop/oraindrop.github.io/blob/master/assets/_img/RuntimeDependency.jpg?raw=true" alt="RuntimeDependency" /></li>
</ul>
</li>
</ul>
</li>
<li>메소드를 이용한 DI
<ul>
<li>setter 메소드를 이용한 주입
<ul>
<li>세터메소드는 외부에서 오브젝트 내부의 애트리뷰트 값을 변경하려는 용도로 주로 사용</li>
<li>파라미터로 전달된 값을 내부의 인스턴스 변수에 저장</li>
<li>부가적으로 입력값에 대한 검증 등의 작업을 수행할 수 있다.</li>
</ul>
</li>
<li>일반 메소드를 이용한 주입
<ul>
<li>파라미터를 하나로 제한하는 세터의 제약이 싫다면 일반 메소드를 사용할 수도 있다.</li>
<li>파라미터 개수가 많아지고 타입이 여러개라면 실수의 가능성이 있음.</li>
<li>여러 개의 초기화 메소드로 나눠서 한 번에 모든 필요한 파라미터를(생성자의 한계) 다 받지 않도록 할 수도 있다.</li>
</ul>
</li>
<li><strong>도서에는 setter 메소드를 통한 DI 를 추천하지만, 현재는 constructor DI 를 권장하는 흐름</strong></li>
</ul>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> public class UserDao {
private ConnectionMaker connectionMaker;
public void setConnectionMaker(ConnectionMaker connectionMaker) {
this.connectionMaker = connectionMaker;
}
...
}
@Configuration
public class CountingDaoFactory {
@Bean
public UserDao userDao() {
UserDao userDao = new UserDao();
userDao.setConnectionMaker(this.connectionMaker());
return userDao;
}
}
</code></pre></div> </div>
</li>
</ul>
<h3 id="18-xml을-이용한-설정">1.8 XML을 이용한 설정</h3>
<ul>
<li>DataSource 인터페이스로 변환
<ul>
<li>직접 작성한 ConnectionMaker는 DB 커넥션을 생성해주는 기능 하나만 정의한 매우 단순한 인터페이스</li>
<li>Java 에서는 DB 커넥션을 포함한 다양한 기능을 추상화 한 <code class="language-plaintext highlighter-rouge">DataSource</code> 라는 인터페이스가 존재한다.</li>
<li><code class="language-plaintext highlighter-rouge">DataSource</code> 의 구현 Class 에서 Connection 을 만들어 준 후 사용하면 된다.
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> @Configuration
public class DaoFactory {
@Bean
public UserDao userDao() throws ClassNotFoundException {
return new UserDao(this.dataSource());
}
@Bean
public DataSource dataSource() throws ClassNotFoundException {
SimpleDriverDataSource dataSource = new SimpleDriverDataSource();
Class driverClass = Class.forName("org.h2.Driver");
dataSource.setDriverClass(driverClass);
dataSource.setUrl("jdbc:h2:mem:toby-spring");
dataSource.setUsername("sa");
dataSource.setPassword("");
return dataSource;
}
}
</code></pre></div> </div>
</li>
</ul>
</li>
</ul>
<h2 id="2장-테스트">2장 테스트</h2>
<h3 id="21-userdaotest-다시-보기">2.1 UserDaoTest 다시 보기</h3>
<ul>
<li>작은 단위의 테스트
<ul>
<li>테스트하고자 하는 대상이 명확하다면 그 대상에만 집중해서 테스트하는 것이 바람직하다.</li>
<li>관심사의 분리</li>
<li>= Unit Test</li>
<li>통제할 수 없는 외부의 리소스에 의존하는 테스트는 단위 테스트가 아니라고 보기도 한다.
<ul>
<li>예) DB가 매번 달라지고, 테스트를 위해 DB를 특정상태로 만들 수 없다면</li>
</ul>
</li>
</ul>
</li>
<li>자동수행 테스트 코드
<ul>
<li>테스트를 자주 수행해도 부담이 없도록 자동으로 수행될 수 있어야 한다.</li>
</ul>
</li>
<li>지속적인 개선과 점진적인 개발을 위한 테스트
<ul>
<li>지속적인 개선의 작은 단계를 거치는 동안 테스트를 수행해 확신을 가지고 코드를 변경하면, 전체적으로 코드를 개선하는 작업에 속도가 붙고 더 쉬워진다.</li>
<li>새로운 기능이 기대대로 동작하는지 확인할 수 있을 뿐 아니라, 기존 기능들이 수정한 코드에 영향받지 않고 여전히 잘 동작하는지를 확인할 수 있다.</li>
</ul>
</li>
<li>UserDaoTest의 문제점
<ul>
<li>수동 확인 작업의 번거로움</li>
<li>실행 작업의 번거로움</li>
</ul>
</li>
</ul>
<h3 id="22-userdaotest-개선">2.2 UserDaoTest 개선</h3>
<ul>
<li>테스트 검증의 자동화
<ul>
<li>모든 테스트는 성공과 실패의 두 가지 결과를 가질 수 있다.
<ul>
<li>또 실패는 테스트가 진행되는 동안에 에러가 발생해서 실패하는 경우(테스트 에러)와</li>
<li>테스트 작업 중에 에러가 발생하진 않았지만 그 결과가 기대한 것과 다르게 나오는 경우(테스트 실패)로 구분해볼 수 있다.</li>
</ul>
</li>
</ul>
</li>
<li>테스트에 일부 자동화 코드 추가
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> if (!user.getName().equals(user2.getName())) {
System.out.println("테스트 실패 (name)");
} else if (!user.getPassword().equals(user2.getPassword())) {
System.out.println("테스트 실패 (password)");
} else {
System.out.println("테스트 성공");
}
</code></pre></div> </div>
<p>테스트를 돌리고 마지막 출력메시지가 “테스트 성공” 이라고 나오는지 확인하는 것 뿐이다.</p>
</li>
<li>
<blockquote>
<p>테스트란 개발자가 마음 편하게 잠자리에 들 수 있게 해주는 것 - 켄트 벡</p>
</blockquote>
</li>
<li>개발 및 유지보수 과정에서 코드 수정 시 마음의 평안을 얻고
<ul>
<li>자신이 만지는 코드에 대해 항상 자신감을 가질 수 있으며</li>
<li>새로 도입한 기술 적용에 문제가 없는 지 확인할 수 있는 가장 좋은 방법은</li>
<li>빠르게 실행 가능한 자동화된 테스트를 만들어두는 것이다.</li>
</ul>
</li>
<li>JUnit 테스트로 전환
<ul>
<li>JUnit 은 프레임워크
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> @Test
public void addAndGet() throws SQLException, ClassNotFoundException {
// given
ApplicationContext context = new AnnotationConfigApplicationContext(CountingDaoFactory.class);
UserDao dao = context.getBean("userDao", UserDao.class);
User user = new User();
user.setId("choising");
user.setName("seungmin");
user.setPassword("hello");
dao.add(user);
User user2 = dao.get(user.getId());
// expect
assertEquals(user2.getName(), user.getName());
assertEquals(user2.getPassword(), user.getPassword());
}
</code></pre></div> </div>
</li>
</ul>
</li>
</ul>
<h3 id="23-junit">2.3 JUnit</h3>
<ul>
<li>UserDao 클래스 deleteAll(), getCount() 메소드 추가
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> public void deleteAll() throws SQLException, ClassNotFoundException {
Connection c = connectionMaker.makeConnection();
PreparedStatement ps = c.prepareStatement("delete from user");
ps.executeUpdate();
ps.close();
c.close();
}
public int getCount() throws SQLException, ClassNotFoundException {
Connection c = connectionMaker.makeConnection();
PreparedStatement ps = c.prepareStatement("select count(*) from user");
ResultSet rs = ps.executeQuery();
rs.next();
int count = rs.getInt(1);
rs.clearWarnings();
ps.close();
c.close();
return count;
}
</code></pre></div> </div>
</li>
<li>기존 테스트코드에 deleteAll(), getCount() 검증 코드 추가
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> @Test
public void addAndGet() throws SQLException, ClassNotFoundException {
ApplicationContext context = new AnnotationConfigApplicationContext(CountingDaoFactory.class);
UserDao dao = context.getBean("userDao", UserDao.class);
dao.deleteAll();
assertEquals(dao.getCount(), 0); // here
User user = new User();
user.setId("choising");
user.setName("seungmin");
user.setPassword("hello");
dao.add(user);
assertEquals(dao.getCount(), 1); // here
User user2 = dao.get(user.getId());
assertEquals(user2.getName(), user.getName());
assertEquals(user2.getPassword(), user.getPassword());
}
</code></pre></div> </div>
</li>
<li>getCount() 테스트 케이스 추가
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> @Test
public void count() throws SQLException, ClassNotFoundException {
ApplicationContext context = new AnnotationConfigApplicationContext(CountingDaoFactory.class);
UserDao dao = context.getBean("userDao", UserDao.class);
User user1 = new User("zingo", "노징고", "1234");
User user2 = new User("choising", "최싱", "12345");
User user3 = new User("forever", "포에버", "123456");
dao.deleteAll();
assertEquals(dao.getCount(), 0);
dao.add(user1);
assertEquals(dao.getCount(), 1);
dao.add(user2);
assertEquals(dao.getCount(), 2);
dao.add(user3);
assertEquals(dao.getCount(), 3);
}
</code></pre></div> </div>
</li>
<li>addAndGet() 테스트 케이스 보완 및 추가
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> @Test
public void addAndGet() throws SQLException, ClassNotFoundException {
ApplicationContext context = new AnnotationConfigApplicationContext(CountingDaoFactory.class);
UserDao dao = context.getBean("userDao", UserDao.class);
User user1 = new User("zingo", "노징고", "1234");
User user2 = new User("choising", "최싱", "12345");
dao.deleteAll();
assertEquals(dao.getCount(), 0);
dao.add(user1);
dao.add(user2);
assertEquals(dao.getCount(), 2);
User userGet1 = dao.get(user1.getId());
assertEquals(userGet1.getName(), user1.getName());
assertEquals(userGet1.getPassword(), user1.getPassword());
User userGet2 = dao.get(user2.getId());
assertEquals(userGet2.getName(), user2.getName());
assertEquals(userGet2.getPassword(), user2.getPassword());
}
</code></pre></div> </div>
<p>파라미터로 주어진 id에 해당하는 사용자를 가져오는 지 검증</p>
</li>
<li>get() 메소드의 예외상황 테스트
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> @Test
public void getUserFailure() throws SQLException, ClassNotFoundException {
ApplicationContext context = new AnnotationConfigApplicationContext(CountingDaoFactory.class);
UserDao dao = context.getBean("userDao", UserDao.class);
dao.deleteAll();
assertEquals(dao.getCount(), 0);
assertThrows(EmptyResultDataAccessException.class, () -> {
dao.get("unkown_id");
});
}
</code></pre></div> </div>
<p>테스트는 실패한다. get() 메소드에서 쿼리 결과의 첫 번째 row를 가져오게하는 rs.next()를 실행할 때 가져올 row 가 없기 때문.
해당 테스트가 성공하도록 get() 메소드를 수정한다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> public User get(String id) throws SQLException, ClassNotFoundException {
// ... (생략)
User user = null;
if (rs.next()) {
user = new User();
user.setId(rs.getString("id"));
user.setName(rs.getString("name"));
user.setPassword(rs.getString("password"));
}
// ... (생략)
if (user == null) {
throw new EmptyResultDataAccessException(1);
}
return user;
}
</code></pre></div> </div>
</li>
<li>부정적인 케이스를 먼저 만드는 습관을 들이는 게 좋다.
<ul>
<li>예외적인 상황을 빠뜨리지 않는 꼼꼼한 개발이 가능하다.</li>
</ul>
</li>
<li>테스트 주도 개발(TDD)
<ul>
<li>이처럼 테스트 코드를 먼저 만들고, 테스트를 성공하게 해주는 코드를 작성하는 방식의 개발</li>
<li>= 테스트 우선 개발(TFD)</li>
<li>“실패한 테스트를 성공시키기 위한 목적이 아닌 코드는 만들지 않는다” 는 기본 원칙</li>
</ul>
</li>
<li>테스트 코드 개선
<ul>
<li>세 개의 테스트 메소드에 반복적으로 등장하는 앞의 코드 중복 제거
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> ...
private UserDao dao;
@BeforeEach
public void setUp() {
ApplicationContext context = new AnnotationConfigApplicationContext(CountingDaoFactory.class);
this.dao = context.getBean("userDao", UserDao.class);
}
...
</code></pre></div> </div>
</li>
<li>JUnit 프레임워크의 테스트 메소드 실행 과정
<ol>
<li>테스트 클래스에서 @Test 가 붙은 public, void, 파라미터가 없는 테스트 메소드를 모두 찾는다.</li>
<li>테스트 클래스의 오브젝트를 하나 만든다.</li>
<li><code class="language-plaintext highlighter-rouge">@Before</code>가 붙은 메소드가 있으면 실행한다.</li>
<li><code class="language-plaintext highlighter-rouge">@Test</code>가 붙은 메소드를 하나 호출하고 테스트 결과를 저장해둔다.</li>
<li><code class="language-plaintext highlighter-rouge">@After</code>가 붙은 메소드가 있으면 실행한다.</li>
<li>나머지 테스트 메소드에 대해 2~5번을 반복한다.</li>
<li>모든 테스트의 결과를 종합하여 돌려준다.</li>
</ol>
<ul>
<li>JUnit5 에서는 <code class="language-plaintext highlighter-rouge">@BeforeEach</code> 와 <code class="language-plaintext highlighter-rouge">@AfterEach</code> 를 사용하면 동일한 기대결과를 볼 수 있다.</li>
<li>테스트 메소드 실행시 마다 새로운 오브젝트를 만들기에, 각 테스트가 서로 영향을 주지 않고 독립적으로 실행됨이 확실히 보장된다.</li>
<li>또한 인스턴스 변수도 부담없이 사용할 수 있다.</li>
<li>테스트 메소드 일부에서만 공통적으로 사용되는 코드가 있다면 <code class="language-plaintext highlighter-rouge">@Before</code> 보다 일반적인 메소드 - 추출방법으로 메소드를 분리하는 것이 낫다.</li>
</ul>
</li>
</ul>
</li>
<li>픽스처 (Fixture)
<ul>
<li>테스트를 수행하는 데 필요한 정보나 오브젝트</li>
<li>UserDaoTest 의 dao 가 픽스처</li>
</ul>
</li>
</ul>
<h3 id="24-스프링-테스트-적용">2.4 스프링 테스트 적용</h3>
<ul>
<li>테스트 메소드의 컨텍스트 공유
<ul>
<li>JUnit 확장기능은 테스트가 실행하기 전 딱 한 번 애플리케이션 컨텍스트를 만들어 두고, 테스트 오브젝트가 만들어질 때마다 특별한 방법을 이용해 애플리케이션 컨텍스트 자신을 오브젝트의 특정 필드에 주입한다.</li>
</ul>
</li>
<li>테스트 클래스의 컨텍스트 공유
<ul>
<li>여러 개의 테스트 클래스가 있는데 모두 같은 설정파일을 가진 애플리케이션 컨텍스트를 사용한다면, 테스트 클래스 사이에서도 애플리케이션 컨텍스트를 공유한다.</li>
</ul>
</li>
<li>
<p>컨텍스트를 공유함으로 성능 대폭 향상</p>
</li>
<li><code class="language-plaintext highlighter-rouge">@DirtiesContext</code>
<ul>
<li>애플리케이션 컨텍스트 상태를 변경한다는 것을 알려준다.</li>
<li>이 annotation 이 붙은 테스트 클래스(메소드) 에는 애플리케이션 컨텍스트 공유를 하지 않는다.</li>
<li><code class="language-plaintext highlighter-rouge">@DirtiesContext</code> 을 사용하면 테스트 성능이 나빠짐으로 테스트를 위한 별도의 DI 설정으로 해결할 수도 있다.</li>
</ul>
</li>
<li>
<p>일반적으로 테스트하기 좋은 코드가 좋은 코드일 가능성이 높다.</p>
</li>
<li>스프링 컨테이너 없이 테스트할 수 있는 방법을 최우선 고려
<ul>
<li>가장 수행속도가 빠르고 테스트 자체가 간결하다.</li>
</ul>
</li>
</ul>
<h3 id="25-학습-테스트로-배우는-스프링">2.5 학습 테스트로 배우는 스프링</h3>
<ul>
<li>학습 테스트
<ul>
<li>자신이 만들지 않은 프레임워크/라이브러리 등을 테스트하는 행위</li>
<li>목적은 API나 프레임워크의 기능을 테스트로 보면서 사용방법을 익히려는 것</li>
<li>기능검증 목적이 아닌 사용방법 검증 목적</li>
</ul>
</li>
<li>학습 테스트의 장점
<ul>
<li>다양한 조건에 따른 기능을 손쉽게 확인해볼 수 있다.</li>
<li>학습 테스트 코드를 개발 중에 참고할 수 있다.</li>
<li>프레임워크나 제품을 업그레이드할 때 호환성 검증을 도와준다.</li>
<li>테스트 작성에 대한 좋은 훈련이 된다.</li>
<li>새로운 기술을 공부하는 과정이 즐거워진다.
<ul>
<li>이건 너무 주관적인 것 아닐까?</li>
</ul>
</li>
</ul>
</li>
<li>버그 테스트
<ul>
<li>코드에 오류가 있을 때 그 오류를 가장 잘 드러내줄 수 있는 테스트</li>
</ul>
</li>
<li>버그 테스트의 장점
<ul>
<li>테스트의 완성도를 높여준다.</li>
<li>버그의 내용을 명확하게 분석해준다.</li>
<li>기술적인 문제를 해결하는 데 도움이 된다.</li>
</ul>
</li>
</ul>
<h2 id="3장-템플릿">3장 템플릿</h2>
<ul>
<li>OCP
<ul>
<li>코드에서 어떤 부분은 변경을 통해 그 기능이 다양해지고 확장하려는 성질</li>
<li>어떤 부분은 고정되어있고 변하지 않으려는 성질</li>
<li>변화의 특성이 다른 부분을 구분하고, 각각 다른 목적과 다른 이유에 의해 다른 시점에 독립적으로 변경될 수 있는 효율적인 구조를 만들어주는 것이 바로 OCP(개방폐쇄원칙)</li>
</ul>
</li>
<li>템플릿이란, 이렇게 바뀌는 성질이 다른 코드 중에서 변경이 거의 일어나지 않으며 일정한 패턴으로 유지되는 특성을 가진 부분을 독립시켜서 효과적으로 활용할 수 있도록 하는 방법</li>
</ul>
<h3 id="31-다시-보는-초난감-dao">3.1 다시 보는 초난감 DAO</h3>
<ul>
<li>deleteAll() 메소드에서 예외처리가 되어있지 않은 예.
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> public void deleteAll() throws SQLException, ClassNotFoundException {
Connection c = connectionMaker.makeConnection();
PreparedStatement ps = c.prepareStatement("delete from user");
ps.executeUpdate(); // 이런데서 작업을 하다가 죽으면 리소스가 반환되지 않는다.
ps.close();
c.close();
}
</code></pre></div> </div>
</li>
<li>예외처리
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> public void deleteAll() throws SQLException, ClassNotFoundException {
Connection c = null;
PreparedStatement ps = null;
try {
c = connectionMaker.makeConnection();
ps = c.prepareStatement("delete from user");
ps.executeUpdate();
} catch (SQLException e) {
throw e;
} finally {
if (ps != null) {
try {
ps.close();
} catch (SQLException e) {
}
}
if (c != null) {
try {
c.close();
} catch (SQLException e) {
}
}
}
}
</code></pre></div> </div>
</li>
<li>
<p>수많은 부분이 변하지 않는 부분이 된다. 위 코드에서 변하는 부분은 <code class="language-plaintext highlighter-rouge">ps = c.prepareStatement("delete from user")</code> 이 Line 뿐이다.</p>
</li>
<li>변하는 부분을 메소드로 추출
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> public void deleteAll() throws SQLException, ClassNotFoundException {
...
ps = this.makeStatement(c);
...
}
private PreparedStatement makeStatement(Connection c) throws SQLException {
PreparedStatement ps;
ps = c.prepareStatement("delete from user");
return ps;
}
</code></pre></div> </div>
</li>
<li>
<p>해당 부분을 추상메소드로 만들고, 해당 클래스를 추상클래스로 변경 후 구체적인 <code class="language-plaintext highlighter-rouge">makeStatement()</code> 정의를 서브클래스에게 위임한다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> public abstract class UserDao {
...
abstract protected PreparedStatement makeStatement(Connection c) throws SQLException;
...
}
</code></pre></div> </div>
<p>makeStatement() 를 구현한 UserDao 서브클래스</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> public class UserDaoDeleteAll extends UserDao {
@Override
protected PreparedStatement makeStatement(Connection c) throws SQLException {
PreparedStatement ps;
ps = c.prepareStatement("delete from user");
return ps;
}
}
</code></pre></div> </div>
</li>
<li>이처럼 변하지 않는 부분은 슈퍼클래스에 두고 변하는 부분은 추상 메소드로 정의해둬서 서브클래스에서 오버라이드하여 새롭게 정의해 쓰도록 하는 것이 <code class="language-plaintext highlighter-rouge">템플릿 메소드 패턴</code>
<ul>
<li>상속을 통해 기능을 확장해서 사용하는 것</li>
</ul>
</li>
<li>클래스 기능을 확장하고 싶을 때 마다 상속을 통해 확장이 가능한 구조가 되었다.
<ul>
<li>상위 DAO 클래스에 불필요한 변화는 생기지 않는 구조</li>
<li>하지만 이렇게되면 DAO 로직마다 상속을 통해 새로운 클래스를 만들어야 한다.</li>
<li>UserDao 처럼 JDBC 메소드가 4개인 경우
<ul>
<li><img src="https://github.com/Oraindrop/oraindrop.github.io/blob/master/assets/_img/TemplateMethod.jpg?raw=true" alt="TemplateMethod" /></li>
</ul>
</li>
<li>클래스를 설계하는 시점에서 구조가 고정된다.
<ul>
<li>관계에 대한 유연성이 떨어진다.</li>
</ul>
</li>
</ul>
</li>
<li>전략패턴을 적용해보자
<ul>
<li>OCP를 잘 지키면서, 템플릿 메소드 패턴보다 유연하고 확장성이 뛰어남</li>
<li>오브젝트를 컨텍스트(context) 와 전략(Strategy) 로 분리하고, 클래스 레벨에서는 인터페이스를 통해서만 의존하도록 만드는 패턴</li>
<li>확장에 해당하는 변하는 부분을 별도의 클래스로 만들어 추상화된 인터페이스를 통해 위임하는 방식</li>
<li>Context 의 contextMethod() 에서 일정한 구조를 가지고 동작,
<ul>
<li>특정 확장 기능은 Strategy 인터페이스를 통해 외부의 독립된 전략 클래스에 위임</li>
</ul>
</li>
<li>deleteAll() 메소드에서 변하지 않는 부분이 contextMethod() 가 될 것이다.
<ul>
<li>JDBC를 이용한 DB 업데이트 작업 이라는 변하지 않는 <code class="language-plaintext highlighter-rouge">맥락(context)</code> 를 갖는다.</li>
<li>deleteAll() 의 Context
<ul>
<li>DB 커넥션 가져오기</li>
<li><strong>PreparedStatement 를 만들어줄 외부 기능 호출하기</strong></li>
<li>전달받은 PreparedStatement 실행하기</li>
<li>예외 throw</li>
<li>리소스 close</li>
</ul>
</li>
<li>볼드처리한 해당 부분이 전략에 해당한다.</li>
</ul>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> public interface StatementStrategy {
PreparedStatement makePreparedStatement(Connection c) throws SQLException;
}
</code></pre></div> </div>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> public class DeleteAllStatement implements StatementStrategy {
@Override
public PreparedStatement makePreparedStatement(Connection c) throws SQLException {
return c.prepareStatement("delete from user");
}
}
</code></pre></div> </div>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> public class UserDao {
...
public void deleteAll() throws SQLException, ClassNotFoundException {
Connection c = null;
PreparedStatement ps = null;
try {
StatementStrategy strategy = new DeleteAllStatement();
ps = strategy.makePreparedStatement(c);
ps.executeUpdate();
...
}
...
}
</code></pre></div> </div>
<p>컨텍스트 안에서 구체적인 전략 클래스인 <code class="language-plaintext highlighter-rouge">DeleteAllStatement</code> 를 사용하도록 고정되어 있는 것이 문제</p>
</li>
</ul>
</li>
<li>DI 적용을 위한 클라이언트/컨텍스트 분리
<ul>
<li>전략패턴에 따르면 Context가 어떤 전략을 사용하게 할 것인가는 앞단의 Client 가 결정하는 것이 일반적</li>
<li><img src="https://github.com/Oraindrop/oraindrop.github.io/blob/master/assets/_img/StrategyPattern.jpg?raw=true" alt="StrategyPattern" /></li>
</ul>
<p>UserDao.class</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> public void deleteAll() throws SQLException, ClassNotFoundException {
jdbcContextWithStatementStrategy(new DeleteAllStatement());
}
private void jdbcContextWithStatementStrategy(StatementStrategy strategy) throws ClassNotFoundException, SQLException {
Connection c = null;
PreparedStatement ps = null;
try {
c = connectionMaker.makeConnection();
ps = strategy.makePreparedStatement(c);
ps.executeUpdate();
} catch (SQLException e) {
throw e;
} finally {
if (ps != null) {
try {
ps.close();
} catch (SQLException e) {
}
}
if (c != null) {
try {
c.close();
} catch (SQLException e) {
}
}
}
}
</code></pre></div> </div>
<p>deleteAll() 메소드가 Client
클라이언트와 컨텍스트 클래스를 분리하지는 않았음</p>
</li>
<li>결국 이 구조에서 오브젝트 생성과 컨텍스트로의 전달을 담당하는 책임을 분리시킨 것이 바로 ObjectFactory 이며, 이를 일반화 한 것이 앞에서 살펴봤던 의존관계 주입(DI)
<ul>
<li>DI란 이러한 전략패턴의 장점을 일반적으로 활용할 수 있도록 만든 구조</li>
<li>일반적으로 DI는 의존관계에 있는 두 개의 오브젝트와 이 관계를 다이내믹하게 설정해주는 오브젝트 팩토리(DI 컨테이너), 그리고 이를 사용하는 클라이언트라는 4개의 오브젝트 사이에서 일어난다.</li>
<li>때로는 구조에 따라 하나의 클래스가 클라이언트와 오브젝트 팩토리의 책임을 함께 지고 있을 수도 있고, 클라이언트와 전략, 클라이언트와 DI 관계에 있는 두 개의 오브젝트가 모두 하나의 클래스에 담길 수도 있다.</li>
<li>이런 경우에는 DI가 매우 작은 단위의 코드와 메소드 사이에서 일어나기도 한다.
<ul>
<li>이런 케이스를 <code class="language-plaintext highlighter-rouge">마이크로 DI</code> 또는 코드에 의한 DI라는 의미로 <code class="language-plaintext highlighter-rouge">수동 DI</code> 라 한다.</li>
</ul>
</li>
</ul>
</li>
<li>add() 메소드 개선하기
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> public class AddStatement implements StatementStrategy {
private User user;
public AddStatement(User user) {
this.user = user;
}
@Override
public PreparedStatement makePreparedStatement(Connection c) throws SQLException {
PreparedStatement ps = c.prepareStatement(
"insert into user (id, name, password) values (?,?,?)");
ps.setString(1, user.getId());
ps.setString(2, user.getName());
ps.setString(3, user.getPassword());
return ps;
}
}
</code></pre></div> </div>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> public class UserDao {
...
public void add(User user) throws SQLException, ClassNotFoundException {
jdbcContextWithStatementStrategy(new AddStatement(user));
}
...
}
</code></pre></div> </div>
</li>
<li>현 구조의 단점은
<ul>
<li>DAO 메소드마다 새로운 StatementStrategy 구현 클래스를 만들어야 한다는 점</li>
<li>User 와 같은 부가정보가 필요할 경우 인스턴스 변수를 만들어야 한다는 점</li>
</ul>
</li>
<li>로컬 클래스
<ul>
<li>클래스 파일이 많아지는 문제를 해결</li>
<li>UserDao 클래스 안에 내부 클래스로 정의하는것
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> public class UserDao {
...
public void add(User user) throws SQLException, ClassNotFoundException {
class AddStatement implements StatementStrategy {
@Override
public PreparedStatement makePreparedStatement(Connection c) throws SQLException {
PreparedStatement ps = c.prepareStatement(
"insert into user (id, name, password) values (?,?,?)");
ps.setString(1, user.getId());
ps.setString(2, user.getName());
ps.setString(3, user.getPassword());
return ps;
}
}
jdbcContextWithStatementStrategy(new AddStatement());
}
...
}
</code></pre></div> </div>
</li>
</ul>
<p>메소드 안에 클래스를 넣는 건 처음 봄. 신기하당.
클래스 파일이 줄어드는 것과, 내부 클래스의 특징을 이용해 로컬 변수를 바로 가져다 사용할 수 있는 장점</p>
</li>
<li>익명 클래스
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> public class UserDao {
...
public void add(User user) throws SQLException, ClassNotFoundException {
jdbcContextWithStatementStrategy(new StatementStrategy() {
@Override
public PreparedStatement makePreparedStatement(Connection c) throws SQLException {
PreparedStatement ps = c.prepareStatement(
"insert into user (id, name, password) values (?,?,?)");
ps.setString(1, user.getId());
ps.setString(2, user.getName());
ps.setString(3, user.getPassword());
return ps;
}
});
...
}
</code></pre></div> </div>
</li>
<li>중첩 클래스
<ul>
<li>독립적인 오브젝트로 만들어질 수 있는 static class</li>
<li>정의된 클래스의 오브젝트 안에서만 만들어질 수 있는 내부 클래스(inner class)
<ul>
<li>scope 에 따라 세 가지로 구분된다.</li>
<li>멤버 필드 처럼 오브젝트 레벨에 정의되는 member inner class</li>
<li>메소드 레벨에 정의되는 local class</li>
<li>익명 내부 클래스</li>
</ul>
</li>
</ul>
</li>
<li>전략 패턴의 구조로 보자면 UserDao의 메소드가 Client, 익명 내부 클래스가 개별 전략, jdbcContextWithStatementStrategy() 메소드는 컨텍스트.
<ul>
<li>컨텍스트 메소드는 UserDao 내의 PreparedStatemnet 를 실행하는 기능</li>
<li>이 메소드는 Specific 하게 UserDao 용이 아니라, JDBC의 일반적인 작업 흐름을 담고 있으므로, UserDao 클래스 밖으로 독립시켜 모든 DAO가 사용할 수 있게 할 수 있다.
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> public class UserDao {
private ConnectionMaker connectionMaker;
private JdbcContext jdbcContext;
...
}
</code></pre></div> </div>
</li>
<li>JdbcContext 를 Bean 으로 등록, UserDao 가 JdbcContext Class 를 DI 받는 방식
<ul>
<li>인터페이스가 아닌 클래스를 직접 DI</li>
<li>강한 응집도를 갖는 클래스, 다른 구현체 변경 니즈가 없는 경우.</li>
<li>귀찮으니까 클래스를 사용하자는 건 잘못된 생각이지만, 이같은 경우 클래스를 직접 DI 해도 ㄱㅊ</li>
<li>찝찝하면 이또한 인터페이스로 해도 ㄱㅊ</li>
</ul>
</li>
<li>UserDao 의 수동 DI
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> public class UserDao {
private ConnectionMaker connectionMaker;
private JdbcContext jdbcContext;
public UserDao(ConnectionMaker connectionMaker) {
this.connectionMaker = connectionMaker;
this.jdbcContext = new JdbcContext(this.connectionMaker);
}
}
</code></pre></div> </div>
<ul>
<li>굳이 인터페이스를 둘 필요 없는 긴밀한 관계는 어색하게 따로 빈으로 분리하지 않고, 내부에서 직접 만들어 사용할 수도 있음.</li>
<li>분명하게 설명할 자신이 없다면 인터페이스를 사이에 둔 평범한 DI 가 좋을지도..?</li>
</ul>
</li>
</ul>
</li>
</ul>
<h3 id="35-템플릿과-콜백">3.5 템플릿과 콜백</h3>
<ul>
<li>전략 패턴의 기본 구조
<ul>
<li>복잡하지만 바뀌지 않는 일정한 패턴을 갖는 작업흐름(context)</li>
<li>일부분만 자주 바꿔서 사용해야 하는 경우(strategy)</li>
</ul>
</li>
<li>위에서 적용한 방식은 전략 패턴의 구조에 익명 내부클래스를 활용한 방식
<ul>
<li>이런 방식을 스프링에서 <code class="language-plaintext highlighter-rouge">템플릿/콜백 패턴</code> 이라고 부른다.</li>
<li>context = template</li>
<li>익명 내부 클래스 오브젝트 = callback</li>
</ul>
</li>
<li>템플릿
<ul>
<li>고정된 작업 흐름을 가진 코드를 재사용한다는 의미</li>
</ul>
</li>
<li>콜백
<ul>
<li>실행되는 것을 목적으로 다른 오브젝트의 메소드에 전달되는 오브젝트</li>
<li>파라미터로 전달되지만 값을 참조하기 위한 것이 아니라 특정 로직을 담은 메소드를 실행시키기 위함</li>
<li>functional object</li>
</ul>
</li>
<li>템플릿/콜백의 특징
<ul>
<li>콜백은 보통 단일 메소드 인터페이스를 구현한 익명 내부 클래스 (전략 패턴의 전략은 여러 메소드 가능)</li>
<li>템플릿의 작업 흐름 중 특정 기능을 위해 한 번 호출되는 경우가 일반적이기 때문</li>
<li>여러 가지 종류의 전략을 사용해야 한다면 하나 이상의 콜백 오브젝트를 사용할 수도?</li>
<li>콜백 인터페이스의 메소드에는 보통 파라미터가 있다.
<ul>
<li>템플릿 작업 흐름 중에 만들어지는 컨텍스트 정보를 전달</li>
</ul>
</li>
</ul>
</li>
<li><img src="https://github.com/Oraindrop/oraindrop.github.io/blob/master/assets/_img/TemplateCallback.jpg?raw=true" alt="TemplateCallback" />
<ul>
<li>클라이언트는 템플릿 안에서 실행될 로직을 담은 <code class="language-plaintext highlighter-rouge">콜백 오브젝트 생성</code> , 템플릿 메소드 호출 시 파라미터로 전달</li>
<li>템플릿은 정해진 작업 흐름을 따라 진행, 내부의 참조정보를 가지고 <code class="language-plaintext highlighter-rouge">콜백 오브젝트 메소드 호출</code>
<ul>
<li>콜백은 클라이언트 메소드 정보와 템플릿이 제공한 참조정보를 이용해서 작업을 수행하고 그 결과를 템플릿에 return</li>
</ul>
</li>
<li>템플릿은 콜백 return 정보를 사용하여 작업을 마무리, 경우에 따라 클라이언트에 return</li>
</ul>
</li>
<li>클라이언트가 템플릿 메소드를 호출하면서 콜백 오브젝트를 전달하는 것은 메소드레벨의 DI</li>
<li>전략패턴과 DI의 장점을 익명 내부 클래스 사용 전략과 결합한 독특한 활용법</li>
</ul>
<h4 id="콜백의-분리와-재활용">콜백의 분리와 재활용</h4>
<ul>
<li>익명 내부 클래스 사용을 최소화 해보자
<ul>
<li>익명 내부 클래스를 사용하는 부분을 메소드로 추출하고, 변경되는 부분 (문자열) 만 전달하도록 변경
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> public class UserDao {
...
public void deleteAll() throws SQLException, ClassNotFoundException {
executeSql("delete from user");
}
private void executeSql(final String query) throws ClassNotFoundException, SQLException {
this.jdbcContext.workWithStatementStrategy(new StatementStrategy() {
@Override
public PreparedStatement makePreparedStatement(Connection c) throws SQLException {
return c.prepareStatement(query);
}
});
}
...
}
</code></pre></div> </div>
<p>모든 고정된 sql 을 실행하는 dao 메소드는 deleteAll() 메소드처럼 executeSql() 을 호출하는 한 줄이면 끝.</p>
</li>
</ul>
</li>
<li>이건 UserDao 에만 종속되는 코드가 아니므로 공통으로 다시 추출
<ul>
<li>클라이언트는 익명 내부 클래스 사용도 없고 깔끔, 단순</li>
<li><code class="language-plaintext highlighter-rouge">JdbcContext</code> 안에 클라이언트와 템플릿, 콜백이 모두 함께 공존하면서 동작하는 구조</li>
</ul>
</li>
<li>
<blockquote>
<p>고정된 작업 흐름을 갖고 있으면서 여기저기서 자주 반복되는 코드가 있다면, 중복되는 코드를 분리할 방법을 생각해보는 습관을 기르자.</p>
</blockquote>
<ul>
<li>먼저 메소드로 분리하는 간단한 시도를 해보고</li>
<li>일부 작업을 필요에 따라 바꾸어 사용해야 한다면 인터페이스를 사이에 두고 분리해서 전략 패턴을 적용하고 DI로 의존관계를 관리하도록</li>
<li>바뀌는 부분이 동시에 여러 종류가 있다면 템플릿/콜백 패턴 적용을 고려</li>
</ul>
</li>
</ul>
<h4 id="간단한-템플릿콜백-예제">간단한 템플릿/콜백 예제</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>public interface LineCallback {
int doSomethingWithLine(String line, int value);
}
</code></pre></div></div>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>public class Calculator {
public int calcSum(String filepath) throws IOException {
LineCallback sumCallback = (line, value) -> value + Integer.parseInt(line);
return this.lineReadTemplate(filepath, sumCallback, 0);
}
public int calcMultiply(String filepath) throws IOException {
LineCallback multiplyCallback = (line, value) -> value * Integer.parseInt(line);
return this.lineReadTemplate(filepath, multiplyCallback, 1);
}
public int lineReadTemplate(String filepath, LineCallback callback, int initVal) throws IOException {
BufferedReader br = null;
try {
br = new BufferedReader(new FileReader(filepath));
int res = initVal;
String line = null;
while ((line = br.readLine()) != null) {
res = callback.doSomethingWithLine(line, res);
}
return res;
} catch (IOException e) {
System.out.println(e.getMessage());
throw e;
} finally {
if (br != null) {
try {
br.close();
} catch (IOException e) {
System.out.println(e.getMessage());
}
}
}
}
}
</code></pre></div></div>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>public class CalcTest {
Calculator calculator;
String numFilePath;
@BeforeEach
public void setUp() {
this.calculator = new Calculator();
this.numFilePath = getClass().getResource("/numbers.txt").getPath();
}
@Test
public void sumOfNumbers() throws IOException {
assertEquals(calculator.calcSum(this.numFilePath), 10);
}
@Test
public void multiplyOfNumbers() throws IOException {
assertEquals(calculator.calcMultiply(this.numFilePath), 24);
}
}
</code></pre></div></div>
<ul>
<li>제너릭 추가하기
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>public interface LineCallback<T> {
T doSomethingWithLine(String line, T value);
}
</code></pre></div> </div>
</li>
</ul>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>public class Calculator {
public int calcSum(String filepath) throws IOException {
LineCallback<Integer> sumCallback = (line, value) -> value + Integer.parseInt(line);
return this.lineReadTemplate(filepath, sumCallback, 0);
}
public int calcMultiply(String filepath) throws IOException {
LineCallback<Integer> multiplyCallback = (line, value) -> value * Integer.parseInt(line);
return this.lineReadTemplate(filepath, multiplyCallback, 1);
}
public String concatenate(String filepate) throws IOException {
LineCallback<String> concatenateCallback = (line, value) -> value + line;
return this.lineReadTemplate(filepate, concatenateCallback, "");
}
public <T> T lineReadTemplate(String filepath, LineCallback<T> callback, T initVal) throws IOException {
BufferedReader br = null;
try {
br = new BufferedReader(new FileReader(filepath));
T res = initVal;
String line = null;
while ((line = br.readLine()) != null) {
res = callback.doSomethingWithLine(line, res);
}
return res;
} catch (IOException e) {
System.out.println(e.getMessage());
throw e;
} finally {
if (br != null) {
try {
br.close();
} catch (IOException e) {
System.out.println(e.getMessage());
}
}
}
}
}
</code></pre></div></div>
<h3 id="36-스프링의-jdbctemplate">3.6 스프링의 JdbcTemplate</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>public class UserDao {
private JdbcTemplate jdbcTemplate;
public UserDao(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
public void add(final User user) {
this.jdbcTemplate.update("insert into user (id, name, password) values (?,?,?)",
user.getId(), user.getName(), user.getPassword());
}
public User get(String id) {
return this.jdbcTemplate.queryForObject("select * from user where id = ?", new Object[]{id},
new RowMapper<User>() {
public User mapRow(ResultSet rs, int rowNum) throws SQLException {
User user = new User();
user.setId(rs.getString("id"));
user.setName(rs.getString("name"));
user.setPassword(rs.getString("password"));
return user;
}
});
}
public void deleteAll() {
this.jdbcTemplate.update("delete from user");
}
public int getCount() {
return this.jdbcTemplate.queryForObject("select count(*) from user", Integer.class);
}
public List<User> getAll() {
return this.jdbcTemplate.query("select * from user order by id",
new RowMapper<User>() {
@Override
public User mapRow(ResultSet rs, int rowNum) throws SQLException {
return new User(rs.getString("id"), rs.getString("name"), rs.getString("password"));
}
});
}
}
</code></pre></div></div>
<ul>
<li>Negative Test
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> @Test
public void getAllNegative() {
dao.deleteAll();
List<User> users = dao.getAll();
assertEquals(users.size(), 0);
}
</code></pre></div> </div>
<ul>
<li>자신이 만든 메소드가 아니더라도, 크기가 0인 리스트 오브젝트를 return 하는지, null 을 return 하는지 확인해 볼 필요가 있다.</li>
</ul>
</li>
</ul>
<h2 id="4장-예외">4장 예외</h2>
<h3 id="41">4.1</h3>
<ul>
<li>예외가 발생하면 그것을 catch 로 잡고 아무것도 하지않고 넘어가는 것은 정말 위험한 일이다.
<ul>
<li>원치않는 예외가 발생하는 것보다도 훨씬 더 나쁜 일이다.</li>
<li>예외가 발생했는데 그것을 무시하고 계속 진행해버리기 때문</li>
<li><strong>어떠한 경우에도 이런 코드를 만들면 안된다.</strong>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> } catch (SQLException e) {
System.out.println(e); // 이것도
e.printStackTrace(); // 이것도
}
</code></pre></div> </div>
</li>
<li>예외 처리의 핵심 원칙은 모든 예외는 적절하게 복구되든지 아니면 작업을 중단시키고 통보되든지.</li>
<li>예외를 무시하거나 잡아먹어 버리는 코드를 만들지 말라.</li>
</ul>
</li>
<li>무의미하고 무책임한 throws
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> public void method1() throws Exception {
method2();
}
public void method2() throws Exception {
method3();
}
public void method3() throws Exception {
...
}
</code></pre></div> </div>
<ul>
<li>예외를 일일이 catch 하기도 귀찮고, 매번 정확하게 예외 이름을 적어서 선언하기도 귀찮아서.</li>
<li>이 경우, 사용 시 <code class="language-plaintext highlighter-rouge">throws Exception</code> 으로 얻을 수 있는 정보가 없다.
<ul>
<li>예외가 진짜 발생하는건지, 어떤 예외가 발생할 수 있는 건지, 습관적으로 복붙한건지…</li>
</ul>
</li>
<li>이것도 당연히 안티패턴</li>
</ul>
</li>
</ul>
<h4 id="412-예외의-종류와-특징">4.1.2 예외의 종류와 특징</h4>
<p>자바에서 throw 를 통해 발생시킬 수 있는 예외는 크게 세 가지가 있다.</p>
<ol>
<li>Error
<ul>
<li>java.lang.Error 클래스의 서브클래스들</li>
<li>주로 JVM 에서 발생 (OOM, ThreadDeath)</li>
<li>Appliaction 레벨에서 대응 방법이 없어 처리를 신경쓰지 않아도 된다.</li>
</ul>
</li>
<li>Exception / Checked Exception
<ul>
<li>java.lang.Exception 클래스와 서브클래스</li>
<li>Checked Excpetion 은 Exception 클래스의 서브클래스이면서 RuntimeException 클래스를 상속하지 않은 것</li>
<li>반드시 예외처리를 해야한다
<ul>
<li>try/catch, throws</li>
<li>그렇지 않으면 compile error</li>
</ul>
</li>
<li>자바 초기 설계자들은 전부 checked exception 을 사용하고자 했던 듯
<ul>
<li>대표적으로 IOException, SQLException</li>
</ul>
</li>
</ul>
</li>
<li>RuntimeException / Unchecked Exception
<ul>
<li>RuntimeException 클래스를 상속한 것</li>
<li>예외처리를 강제하지 않는다.</li>
<li>주로 프로그램의 오류
<ul>
<li>NPE, IllegalArgumentException</li>
</ul>
</li>
</ul>
</li>
</ol>
<h4 id="413-예외처리-방법">4.1.3 예외처리 방법</h4>
<ol>
<li>예외 복구
<ul>
<li>예외상황을 파악하고 문제를 해결해서 정상 상태로 돌려놓는 것</li>
<li>사용자에게 예외상황으로 비쳐도 애플리케이션에서는 정상적으로 설계된 흐름을 따라 진행되도록</li>
<li>이를테면 네트워크가 불안하여 원격 DB 서버에 접속하다 실패해서 SQLException 이 발생할 경우 재시도 횟수를 정하고 재시도를 해볼 수 있다.
<ul>
<li>복구가 될 수도 있지만 정해진 횟수를 초과하면 복구를 포기해야 함</li>
</ul>
</li>
<li>예외처리를 강제하는 checked exception 은 어떤식으로든 복구할 가능성이 있는 경우</li>
</ul>
</li>
<li>예외처리 회피
<ul>
<li>throws, catch -> rethrow</li>
<li>템플릿/콜백 패턴에서 콜백 오브젝트 메소드는 대부분 예외를 회피한다.
<ul>
<li>콜백 오브젝트의 역할이 아니라고 보기 때문, 템플릿 레벨에서 처리</li>
</ul>
</li>
<li>무책임한 회피는 ㄴㄴ
<ul>
<li>다른 오브젝트에게 예외처리 책임을 분명히 지게 하거나</li>
<li>사용하는 쪽에서 예외를 다루는 게 최선의 방법이라는 확신이 있어야 한다.</li>
</ul>
</li>
</ul>
</li>
<li>예외 전환
<ul>
<li>발생한 예외를 적절한 예외로 전환하여 메소드 밖으로 던지는 것
<ul>
<li>첫째 목적
<ul>
<li>예외상황에 대한 의미를 분명하게 해줄 수 있는 예외로 변경하기 위해</li>
<li>예를 들어 회원가입 시 아이디가 같은 사용자가 있어 DB Save 에서 <code class="language-plaintext highlighter-rouge">SQLException</code> 이 발생하였다면
<ul>
<li>서비스 계층에서는 왜 <code class="language-plaintext highlighter-rouge">SQLException</code> 이 발생했는지 알 수 없다.</li>
<li>이 때 <code class="language-plaintext highlighter-rouge">DupliacateUserIdException</code> 같은 예외로 변경해서 던진다면?</li>
</ul>
</li>
</ul>
</li>
<li>둘째 목적
<ul>
<li>예외를 처리하기 쉽고 단순하게 만들기 위해 포장(wrap) 하는 것</li>
<li>예를 들어 비즈니스 로직에서 의미 있는 예외거나 복구 가능한 예외가 아닌 checked Exception 을 runtime Exception 으로 wraping 하여 던지는 것</li>
</ul>
</li>
</ul>
</li>
<li>전환하는 예외는 보통 발생한 예외를 담아 중첩 예외로 만드는 것이 좋다.
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> catch(SQLException e) {
...
throw Duplication(e);
}
</code></pre></div> </div>
</li>
</ul>
</li>
</ol>
<h4 id="414-예외처리-전략">4.1.4 예외처리 전략</h4>
<ul>
<li>자바가 처음 만들어 질 때 많이 사용되던 애플릿이나 AWT, 스윙을 사용한 독립형 애플리케이션에서는 통제 불가능한 시스템 예외여도 애플리케이션 작업이 중단되지 않게 해주고 상황을 복구해야 했다.
<ul>
<li>예를 들어 워드 파일 열기 기능에서 입력한 이름의 파일을 찾을 수 없다고 애플리케이션이 종료될 수는 없기에</li>
</ul>
</li>
<li>하지만 자바 엔터프라이즈 서버환경은 다르다.
<ul>
<li>수많은 사용자 각각의 독립적인 요청(작업)의 예외발생 시 작업을 일시 중지하고 사용자와 바로 커뮤니케이션 하여 예외상황을 복구할 수 있는 방법이 없다.</li>
<li>예외가 발생하지 않도록 차단하는 것과, 예외 발생 시 해당 요청의 작업을 취소하고 서버관리자나 개발자에게 통보해주는 편이 낫다.</li>
</ul>
</li>
<li>때문에 checked exception 의 활용도와 가치는 점점 떨어지고 있다.</li>
<li>
<p>예전에는 복구할 가능성이 조금만 보이면 checked exception 으로 만들 생각을 했으나, 이제는 항상 복구 가능한 상황이 아니면 unchecked exception 으로 만드는 경향</p>
</li>
<li>런타임 예외를 사용하는 경우 컴파일러가 예외처리를 강제하지 않으므로 충분히 신경써야 한다.
<ul>
<li>API 문서나 레퍼런스 문서 등을 통해, 메소드를 사용할 때 발생할 수 있는 예외의 종류와 원인, 활용방법을 자세히 설명해두자.</li>
</ul>
</li>
<li>애플리케이션 예외
<ul>
<li>비지니스 로직에서 처리되어야 하는 예외
<ul>
<li>이를테면 잔고 이상의 돈을 출금하려 할 때,</li>
</ul>
</li>
<li>이 때는 반드시 catch 하여 에러처리를 하라는 의미로 checked exception 으로 만드는 것을 권장</li>
</ul>
</li>
</ul>
<h3 id="42-예외-전환">4.2 예외 전환</h3>
<ul>
<li>JdbcTemplate 이 던지는 DataAccessException은 런타임 예외로 SQLException 을 포장해주는 역할을 한다.
<ul>
<li>또한 상세한 예외정보를 의미있고 일관성 있는 예외로 전환해서 추상화해주려는 용도로 쓰이기도.</li>
</ul>
</li>
<li>Jdbc 의 한계
<ul>
<li>비표준 SQL
<ul>
<li>각 벤더사 마다 조금씩 다른 SQL</li>
<li>이건 7장에서 다시 보자.</li>
</ul>
</li>
<li>SQLException의 비표준 에러코드
<ul>
<li>각 벤더사의 전용 코드이다.</li>
<li>getSQLState() 로 예외상황에 대한 상태정보를 가져올 수는 있고, 이는 어느정도 표준이 있다.</li>
<li>스프링 JdbcTemplate 은 DataAccessException 으로 SQLException 을 대체하고
<ul>
<li>DB별 에러코드를 분류해서 맵핑해놓음.</li>
<li>때문에 DB의 종류와 상관 없이 중복 키 에러는 <code class="language-plaintext highlighter-rouge">DuplicationKeyException</code> 이 발생.</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>DataAccessException
<ul>
<li>DataAccessException 은 JDBC 의 SQLException 을 전환하는 용도로만 만들어 진 것이 아니다.</li>
<li>의미가 같은 예외라면 JDBC 이외 자바 데이터 액세스 기술의 종류와 상관없이 일관된 예외가 발생하도록 만들어준다.</li>
<li>데이터 액세스 기술에 독립적인 추상화된 예외를 제공하는 것.
<ul>
<li>스프링은 진짜 천잰가..?</li>
</ul>
</li>
</ul>
</li>
<li>DAO 인터페이스와 구현과 분리
<ul>
<li>DAO를 따로 사용하는 가장 중요한 이유는 데이터 엑세스 로직을 담은 코드를 성격이 다른 코드에서 분리하기 위함.</li>
<li>분리된 DAO는 전략패턴을 적용해 구현방법을 변경해서 사용할 수도 있고.</li>
<li>DAO를 사용하는 쪽에서 DAO 내부에서 어떤 데이터 액세스 기술을 사용하는지 신경쓰지 않아도 된다.
<ul>
<li>구체적인 클래스 정보와 구현 방법을 감추고, DI를 통해 제공되도록 만드는 것이 바람직하다.</li>
</ul>
</li>
<li>DataAccessException 예외 추상화를 잘 적용하면 이상적</li>
</ul>
</li>
</ul>
<h4 id="424-기술에-독립적인-userdao-만들기">4.2.4 기술에 독립적인 UserDao 만들기</h4>
<ul>
<li>인터페이스 네이밍
<ul>
<li>I라는 접두어를 붙이는 방법</li>
<li>인터페이스 이름을 단순화 하고 구현 클래스는 각각의 특징을 명시하는 방법</li>
</ul>
</li>
<li>UserDao Interface
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> public interface UserDao {
void add(User user);
User get(String id);
List<User> getAll();
void deleteAll();
int getCount();
}
</code></pre></div> </div>
</li>
<li>UserDao 구현체, UserJdbcDao 클래스
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> public class UserJdbcDao implements UserDao {
private JdbcTemplate jdbcTemplate;
public UserJdbcDao(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
@Override
public void add(User user) {
...
}
@Override
public User get(String id) {
...
}
@Override
public List<User> getAll() {
...
}
@Override
public void deleteAll() {
...
}
@Override
public int getCount() {
...
}
}
</code></pre></div> </div>
</li>
<li>JdbcTemplate 을 사용했을 때 중복 키 테스트
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> @Test
public void duplicateKey() {
dao.deleteAll();
dao.add(user1);
assertThrows(DuplicateKeyException.class, () -> {
dao.add(user1);
});
}
</code></pre></div> </div>
<ul>
<li>DuplicateKeyException 은 JDBC 를 이용하는 경우에만 발생
<ul>
<li>하이버네이트는 ConstraintViolationException 등</li>
</ul>
</li>
<li>때문에 DAO에서 사용하는 데이터 액세스 기술과 상관없이 동일한 예외를 얻고 싶다면 직접 예외를 정의해두고 <strong>예외전환</strong> 이 필요하다</li>
</ul>
</li>
<li>예외전환 학습테스트
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> @Test
public void sqlExceptionTranslate() {
dao.deleteAll();
try {
dao.add(user1);
dao.add(user1);
} catch (DuplicateKeyException e) {
SQLException sqlException = (SQLException)e.getRootCause();
SQLExceptionTranslator sqlExceptionTranslator = new SQLErrorCodeSQLExceptionTranslator(this.dataSource);
Assertions.assertTrue(sqlExceptionTranslator.translate(null, null, sqlException) instanceof DuplicateKeyException);
}
}
</code></pre></div> </div>
</li>
<li>핵심은 DAO 를 데이터 액세스 기술에서 독립시키려면 인터페이스 도입과 런테임 예외 전환, 기술에 독립적인 추상화된 예외로 전환이 필요하다.</li>
</ul>
Thu, 01 Apr 2021 00:00:00 +0000
/book/2021/04/01/%ED%86%A0%EB%B9%84%EC%9D%98-%EC%8A%A4%ED%94%84%EB%A7%81-1-4.html
/book/2021/04/01/%ED%86%A0%EB%B9%84%EC%9D%98-%EC%8A%A4%ED%94%84%EB%A7%81-1-4.htmljava,book,springBook인터뷰를 준비해보자 from. 더개발자 인터뷰 가이드 강의<h2 id="면접에서-검증당하는-스킬">면접에서 검증당하는 스킬</h2>
<h3 id="소프트-스킬">소프트 스킬</h3>
<ul>
<li>like 커뮤니케이션 스킬</li>
<li>6가지 주요 소프트 스킬
<ul>
<li>고객 중심
<ul>
<li>질문 예
<ul>
<li>고객 요구를 만족시켰던 서비스 제공 경험
<ul>
<li>성공적일 수 있던 핵심적 이유</li>
</ul>
</li>
<li>고객 기대에 만족하는 서비스를 제공하기 힘들었던 경험
<ul>
<li>다시 그런 업무를 하게된다면 어떻게?</li>
</ul>
</li>
<li>고객이 가지고 있는 문제를 해결해 본 경험</li>
</ul>
</li>
<li>좋은 답변 특징
<ul>
<li>책임감을 갖고 주기적으로 고객의 요구/이슈를 파악하려는 시도</li>
<li>고객의 요구와 기대를 조사하기 위한 피드백 시스템 구축/운영</li>
<li>고객의 요구와 기대를 조사해 서비스 적용경험</li>
</ul>
</li>
</ul>
</li>
<li>결과 도출
<ul>
<li>비즈니스에 필수적으로 필요한 업무에 집중하고 적절한 품질과 기간 내에 결과를 만들어 내는 역량
<ul>
<li>문제를 효율적으로 해결, 책임감, 비즈니스에 필요한 도전적인 업무, 동료의 신뢰</li>
</ul>
</li>
<li>질문 예
<ul>
<li>프로젝트 목표 달성하기 어려웠던 경험과 문제</li>
<li>장기적인 프로젝트에서 오너쉽을 가지고 일한 경험? 각각의 마일스톤을 제때 맞추기위해 어떤 노력을 했는지?</li>
<li>도전적인 프로젝트였지만 동료들과 함께 해낸 경험</li>
</ul>
</li>
<li>후속 질문
<ul>
<li>해당 프로젝트는 어떻게 참여?</li>
<li>어떤 문제? 어떻게 해결?</li>
<li>다시 돌아봤을 때 성공 요인 or 다르게 시도해보고 싶은 것</li>
</ul>
</li>
<li>좋지 못한 답변
<ul>
<li>지시에 따르기만 한 경우(수동적인 태도)</li>
<li>충분한 회의/분석 없이 문제해결 시도에 급급</li>
<li>중요한 의사소통 결여</li>
</ul>
</li>
<li>좋은 답변
<ul>
<li>예정된 일정 내 긍정적인 결과</li>
<li>문제 해결에 필요한 정량적인 데이터를 수집해본 경험(구체적이고 목적성을 띈)</li>
<li>목표를 달성하는데 필요한 업무를 주관적으로 제안하고 실행에 옮긴 경험</li>
<li>지속적으로 목표 달성에 필요한 데이터를 모니터해 본 경험(적절하게 분석하고 주도적으로 해결한)</li>
</ul>
</li>
</ul>
</li>
<li>영향력
<ul>
<li>다른 동료 또는 팀에 좋은 영향/설득
<ul>
<li>효율적/논리적 커뮤니케이션 능력</li>
<li>긍정적인 관계 형성</li>
</ul>
</li>
<li>질문 예
<ul>
<li>업무를 위해 설득/이해 시킨 경험</li>
<li>동료와 좋은 관계 형성으로 프로젝트에 긍정적 영향을 미친 경험</li>
<li>다양한 견해를 가지고 있는 관계자들의 요구를 성공적으로 만족시킨 경험</li>
<li>동료나 다른 팀을 설득해 프로세스 개선 경험</li>
</ul>
</li>
<li>좋지 못한 답변
<ul>
<li>설득에 필요한 정량적인 데이터를 활용 X</li>
<li>관계자들과 신뢰</li>
<li>개인 관심사 중심 업무(A,B 중에 A가 해보고싶어서 했다)</li>
<li>비효율/비논리적 방법으로 관계 형성</li>
</ul>
</li>
<li>좋은 답변 특징
<ul>
<li>데이터를 근거삼아 설득한 경험</li>
<li>강한 신뢰</li>
<li>관계자들의 견해와 입장을 이해하고, 이해를 바탕으로 업무에 도움</li>
<li>설득을 통해 팀 / 타팀까지 좋은 영향을 주었던 경험</li>
</ul>
</li>
</ul>
</li>
<li>적응력
<ul>
<li>변화하는 환경에 효율적으로 반응하는 능력
<ul>
<li>끊임없는 탐구/학습</li>
<li>스트레스가 심한 상황에서도 건설적 자세유지</li>
</ul>
</li>
<li>질문 예
<ul>
<li>조직 개편으로인한 스트레스 경험</li>
<li>업무 처리 방법에 변화를 시도했던 경험</li>
<li>가이드나 정보가 거의 없이 시작했던 프로젝트를 완료했던 경험</li>
</ul>
</li>
<li>좋지 못한 답변
<ul>
<li>스트레스를 잘 처리하지 못한 경험</li>
<li>정보가 부족한 경우, 비효율적으로 일을 처리한 경험</li>
<li>변하는 상황에 맞추려는 노력이 부족했던 경험</li>
</ul>
</li>
<li>좋은 답변
<ul>
<li>변하는 상황에 효율적/즉각적 대응 경험</li>
<li>정보가 부족한 경우에도 효율적으로 일을 하거나, 그 자체를 새로운 기회로 삼았던 경험</li>
<li>스트레스가 심한 상황에서 건설적 자세를 유지한 경험 (개인에겐 힘들지만, 조직에서 왜 필요한지, 왜 스트레스인지, 어떻게 해결할 수 있는지 고민)</li>
</ul>
</li>
</ul>
</li>
<li>판단력
<ul>
<li>여러 대안과 관점을 고려하여 결정하는 능력
<ul>
<li>해결하려는 문제의 범위 결정</li>
<li>가정 X, 근거를 통한 증명</li>
</ul>
</li>
<li>질문 예
<ul>
<li>업무에서 치명적인 실수를 했던 경험
<ul>
<li>실수를 통해 배운점이나 개선점을 말하면 좋을듯</li>
</ul>
</li>
<li>여러 대안이 있는 상황에서 어떤 과정을 통해 가장 적절한 방법을 선택했는지(납기가 불충분 할때 어떻게 선택하는지)</li>
<li>복잡했던 문제를 해결하기 전에 판단에 필요한 지식을 습득해야 했던 경험</li>
</ul>
</li>
<li>좋지 못한 답변
<ul>
<li>여러 대안을 충분히 검토하지 X</li>
<li>서두르거나 충분한 정보 없이 섣부른 판단</li>
<li>동료 또는 타 팀 도움이 필요한 상황에서 도움을 요청하지 않은 경험</li>
</ul>
</li>
<li>좋은 답변
<ul>
<li>판단에 필요한 정보를 다양한 방법으로 수집하고 검토하여 결정한 경험</li>
<li>필요한 정보를 기반으로 적절한 시간 내에 좋은 판단을 한 경험</li>
<li>필요한 상황에서 적절한 도움을 요청했던 경험</li>
</ul>
</li>
</ul>
</li>
<li>협업
<ul>
<li>동료 또는 다른 팀간의 팀웍 또는 공조를 통해 업무를 해내는 능력
<ul>
<li>여러 동료 또는 팀의 리소스와 노력을 조합하여 일을 한다</li>
<li>개인의 목표가 아닌 공동의 목표를 달성하려 노력</li>
</ul>
</li>
<li>질문 예
<ul>
<li>치명적인 이슈를 해결하는데 필요한 다른 팀과 일한 경험</li>
<li>여러 팀과 함께 일해야 했던 프로젝트가 잘 되지 않았던 경험</li>
<li>동료와 마찰이 생겼던 경험</li>
</ul>
</li>
<li>좋지 못한 답변
<ul>
<li>협업이 필요한 상황에서 최소한의 기여만 한 경험</li>
<li>요청 받은 경우에만 협업했던 경험</li>
<li>협업을 증진시켰던 좋은 사례를 들지 못하는 경우</li>
</ul>
</li>
<li>좋은 답변
<ul>
<li>협업에서 의미있고 중요한 업무를 했던 경험</li>
<li>필요한 경우 협업을 제시하거나 주도한 경험</li>
<li>협업을 증진 시켰던 좋은 사례</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<h3 id="하드-스킬">하드 스킬</h3>
<ul>
<li>like 코딩 능력</li>
</ul>
<h2 id="준비">준비</h2>
<ul>
<li>예상 질문 답변 준비
<ul>
<li>용어 정리</li>
</ul>
</li>
<li>
<p>프로그래밍 관련 서적 읽기</p>
</li>
<li>자료구조 / 알고리즘 복습
<ul>
<li>Big O</li>
<li>리스트, 배열, 스택, 큐, 해시 테이블, 힙, 이진 트리 등 자료구조</li>
<li>정렬</li>
<li>BFS, DFS</li>
</ul>
</li>
</ul>
<h2 id="이력서">이력서</h2>
<ul>
<li>기술과 핵심 역량을 구체적으로 명시
<ul>
<li>기술set + 경험 + effect</li>
</ul>
</li>
<li>편견을 제공할 수 있는 정보 최소화
<ul>
<li>정말 시민의식 높은 세상이라면 이게 맞겠지만</li>
<li>유리한 편견을 제공할 수 있는 정보를 제공하는 쪽도 고려해봄직할 듯</li>
</ul>
</li>
</ul>
<h2 id="면접">면접</h2>
<ul>
<li>가장 어려웠던 프로젝트?</li>
</ul>
<h3 id="질문에-대한-답변-프레임워크">질문에 대한 답변 프레임워크</h3>
<ul>
<li>STAR 프레임워크
<ul>
<li>Situation : 어떠한 상황</li>
<li>Task : 어떤 업무(Task) 나 또는 목표(Target) 를 달성해야 했는지</li>
<li>Action : 그래서 본인이 왜, 무엇을, 어떻게 했으며</li>
<li>Result : 결과는 어땠는지, 성과는? or 실패했다면 배운 점은?</li>
</ul>
</li>
</ul>
<h2 id="오퍼-수락-전-확인-할-것">오퍼 수락 전 확인 할 것</h2>
<ul>
<li>구체적으로 어떤 일을 하게 될 지
<ul>
<li>이를테면 B2C 인지, 플랫폼 성격인지</li>
</ul>
</li>
<li>같이 일을 하게 될 동료 구성원
<ul>
<li>신생 팀인지, 안정화된 팀에 들어가는 케이스인지, 목적/기능조직인지</li>
</ul>
</li>
<li>운영 업무와 개발 업무의 비중</li>
<li>하루 일과(워라밸?)</li>
</ul>
<h2 id="신규-조직-업무-적응">신규 조직 업무 적응</h2>
<ul>
<li>생소한 키워드를 문맥별로 모아서 질문
<ul>
<li>간단한 질문이 아니라면 30분~1시간 가량 미팅을 잡는게 좋음</li>
</ul>
</li>
<li>처음부터 너무 많은 정보를 파악하려하지말고, 나눠서 정복</li>
<li>장기 or 단기적으로 개선하고 싶은 점들을 기록하고 정리해보자.
<ul>
<li>건설적인 태도로 공유까지 고려</li>
</ul>
</li>
</ul>
<h2 id="코딩-인터뷰">코딩 인터뷰</h2>
<ul>
<li>주요 목적
<ul>
<li>프로그래밍 언어 능력</li>
<li>학습 능력 / 적응력</li>
<li>문제 분석 및 해결 능력</li>
<li>논리적 사고 / 코드로 풀어내는 능력</li>
<li>협업에 필요한 의사소통 능력</li>
</ul>
</li>
<li>문제 풀이 프레임워크
<ul>
<li>문제를 명확히 이해했는지 확인</li>
<li>가정하고 있거나 가정할 수 있는 것을 확인</li>
<li>실제 예를 들어가며 다시 확인 (추후 TC)</li>
<li>문제 해결책 고민</li>
<li>해결책을 설명하고 시간/공간 복잡도</li>
<li>코딩</li>
<li>테스트</li>
</ul>
</li>
</ul>
Mon, 08 Feb 2021 00:00:00 +0000
/back-end/2021/02/08/preparing-for-an-interview.html
/back-end/2021/02/08/preparing-for-an-interview.htmlspring,javaBack-endTIL(20201217)<h1 id="20201217">20201217</h1>
<h2 id="querydsl-우아콘-이동욱님-세션">QueryDSL 우아콘 이동욱님 세션</h2>
<h3 id="querydsl-의-exist-메소드-쓰지-않기">QueryDSL 의 exist 메소드 쓰지 않기</h3>
<ul>
<li>queryDsl exists 메소드는 sql exsist 를 쓰지 않는다.
<ul>
<li>sql exist 는 하나를 찾자마자 멈추기 때문에 효율적.</li>
</ul>
</li>
<li>그래서 query dsl exsist 메소드나, fetchCount > 1 보다</li>
<li><strong>fetchFirst 가 더 효율적이다!</strong>
<ul>
<li>왜냐면 하나를 찾자마자 멈추는 효과때문에</li>
</ul>
</li>
</ul>
<h3 id="이미-아는-값을-조회하지-말자-as-표현식으로-대체할-수-있다-asstring-asnumber">이미 아는 값을 조회하지 말자. as 표현식으로 대체할 수 있다. asString, asNumber</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Projcetions.fields(BookPageDto.class,
book.name,
Expressions.asNumber(bookNo).as("bookNo"),
book.id)
</code></pre></div></div>
<h3 id="select-절에-entity-조회를-굳이-안해도-되는지-생각해보기">Select 절에 Entity 조회를 굳이 안해도 되는지 생각해보기</h3>
<ul>
<li>projections 로 dto 를 가져올 때 id 만 조회해와서
<ul>
<li>adItem.customer.id.as(“customerId”)</li>
</ul>
</li>
<li>toEntity() 메소드 시 .customer(new Customer(customerId))</li>
<li>이렇게 ID 만 같은 객체를 생성해도 저장할 때 문제가 없다. 는데 customer 가 다 null 로 저장되지는 않으려나?</li>
</ul>
<h3 id="group-by-최적화">group by 최적화</h3>
<ul>
<li>file sort 제거를 위하여</li>
<li>order by null 직접 구현하여 넣어주기</li>
<li>(Order.ASC, NullExpression.DEFAULT, Default);</li>
<li>페이징일 경우 order by null 을 사용하지 못한다.</li>
</ul>
<h3 id="정렬">정렬</h3>
<ul>
<li>조회 결과가 100건 이하라면, 애플리케이션 정렬이 유리하다.</li>
</ul>
<h3 id="커버링-인덱스">커버링 인덱스</h3>
<ul>
<li>JPQL 은 from 절의 서브쿼리를 사용하지 못하므로</li>
<li>DB 를 두번치는 방법을 사용한다.
<ul>
<li>id 만 조회하는 쿼리</li>
<li>두번째 진짜 조회
<ul>
<li>where 절에 in 구문에 위 ID 리스트</li>
</ul>
</li>
</ul>
</li>
</ul>
<h3 id="jpa-bulk-inset-는-자제한다">JPA Bulk inset 는 자제한다.</h3>
Thu, 17 Dec 2020 00:00:00 +0000
/til/2020/12/17/TIL.html
/til/2020/12/17/TIL.htmlTILjavajpaTIL도서 - Elasticsearch in action (4, 9)<h1 id="elasticsearch-in-action-139p">Elasticsearch in action (139p~)</h1>
<h2 id="4장-데이터-검색">4장 데이터 검색</h2>
<h3 id="41-검색-요청의-구조">4.1 검색 요청의 구조</h3>
<h4 id="411-검색-범위-지정하기">4.1.1 검색 범위 지정하기</h4>
<ul>
<li><code class="language-plaintext highlighter-rouge">_search</code> …
<ul>
<li>전체 클러스터 검색</li>
</ul>
</li>
<li><code class="language-plaintext highlighter-rouge">{index}/_search</code> …
<ul>
<li>index 에서 검색</li>
</ul>
</li>
<li><code class="language-plaintext highlighter-rouge">{index}/event/_search</code> …
<ul>
<li>index 에서 event type 검색</li>
</ul>
</li>
<li><code class="language-plaintext highlighter-rouge">_all/event/_search</code>, <code class="language-plaintext highlighter-rouge">*/event/_search</code> …
<ul>
<li>전체 index 에서 event type 검색</li>
</ul>
</li>
<li><code class="language-plaintext highlighter-rouge">{index},other/event,group/_search</code> …
<ul>
<li>index 와 다른 인덱스에서 이벤트 및 그룹 타입 검색</li>
</ul>
</li>
<li><code class="language-plaintext highlighter-rouge">+get-toge*,-{index}/_search</code> …
<ul>
<li>명시된 index 제외, get-toge에 해당하는 모든 인덱스에서 검색</li>
</ul>
</li>
<li>다중 인덱스 검색하는 alias 도 사용할 수 있다.
<ul>
<li>당연히 최고 성능을 내려면 최소한의 인덱스를 타도록 하는게 좋다.</li>
</ul>
</li>
</ul>
<h4 id="412-검색-요청의-기본-구성-요소">4.1.2 검색 요청의 기본 구성 요소</h4>
<ul>
<li><code class="language-plaintext highlighter-rouge">query</code>
<ul>
<li>검색 요청에 있어 가장 중요한 구성 요소</li>
<li>점수 기반으로 최적의 도큐먼트를 반환하거나 원치 않는 도큐먼트를 걸러내도록 설정</li>
<li>DSL 쿼리와 DSL 필터를 사용해서 구성</li>
<li>e.g.) <code class="language-plaintext highlighter-rouge">_search?q=title:elasticsearch</code>
<ul>
<li>title이 elasticsearch 단어와 일치하는 도큐먼트를 찾겠다.</li>
</ul>
</li>
</ul>
</li>
<li><code class="language-plaintext highlighter-rouge">size</code>
<ul>
<li>반환할 도큐먼트 개수</li>
</ul>
</li>
<li><code class="language-plaintext highlighter-rouge">from</code>
<ul>
<li>size 와 함께 페이징</li>
<li>e.g.) <code class="language-plaintext highlighter-rouge">_search?from=7&size=5</code>
<ul>
<li>from 7, size 5 라면 8,9,10,11,12 번째 결과를 반환한다.</li>
</ul>
</li>
</ul>
</li>
<li><code class="language-plaintext highlighter-rouge">_source</code>
<ul>
<li>_source 필드를 어떻게 반환할 것인가</li>
<li>_source 하면 완전한 _source를 반환</li>
<li>설정으로 반환되는 필드를 걸러낼 수 있다.</li>
<li>전체 내용이 필요하지 않을 때 적절히 사용</li>
<li>
<p>e.g.) <code class="language-plaintext highlighter-rouge">_search?sort=date:asc&_source=title,date</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> ...
"_source": {
"date" :
"title" :
},
...
</code></pre></div> </div>
<p>날짜 오름차순 순으로 정렬한 결과에서 최초 10건을 response에 title 과 date 필드만 포함</p>
</li>
</ul>
</li>
<li><code class="language-plaintext highlighter-rouge">sort</code>
<ul>
<li>e.g.) <code class="language-plaintext highlighter-rouge">_search?sort=date:asc</code>
<ul>
<li>날짜 오름차순으로 정렬한 결과 중 최초 10개를 반환한다.</li>
</ul>
</li>
</ul>
</li>
</ul>
<h4 id="413-본문-기반-검색-요청-사용하기">4.1.3 본문 기반 검색 요청 사용하기</h4>
<ul>
<li><code class="language-plaintext highlighter-rouge">from</code>, <code class="language-plaintext highlighter-rouge">size</code> 예제
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> curl '..../{index}/_search' -d '{
"query": {
"match_all": {}
},
"from": 10,
"size": 10
}'
</code></pre></div> </div>
</li>
<li><code class="language-plaintext highlighter-rouge">_source</code> 필터링 예제
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> curl '..../{index}/_search' -d '{
"query": {
"match_all": {}
},
"_source": ["name", "date"]
}'
</code></pre></div> </div>
<p>와일드카드도 사용 가능 (name, nation 필드 둘 다 반환할 때) <code class="language-plaintext highlighter-rouge">_source: "na*"</code> 이렇게 할 수 도 있음</p>
</li>
<li>response 에 포함할 필드(include), 제외할 필드(exclude) 예제
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> curl '..../{index}/_search' -d '{
"query": {
"match_all": {}
},
"_source": {
"include": ["location.*", "date"],
"exclude": ["location.geolocation"]
}
}'
</code></pre></div> </div>
</li>
<li>순서 정렬
<ul>
<li>지정하지 않으면, 일치한 도큐먼트를 <code class="language-plaintext highlighter-rouge">_score</code> 값의 내림차순으로 정렬</li>
<li>
<p>생성일 기준 가장 오래된 것으로 시작해서, 그룹 이름 기준으로, 마지막으로 <code class="language-plaintext highlighter-rouge">_score</code> 결과로 정렬 결과를 반환하는 예제</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> curl '..../{index}/_search' -d '{
"query": {
"match_all": {}
},
"sort": [
{"created_on": "asc"},
{"name": "desc"},
"_score"
]
}'
</code></pre></div> </div>
</li>
</ul>
</li>
</ul>
<h3 id="42-쿼리와-필터-dsl">4.2 쿼리와 필터 DSL</h3>
<ul>
<li>match query 예제
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> curl '..../{index}/_search' -d '{
"query": {
"match": {
"title": "hadoop"
}
},
}'
</code></pre></div> </div>
</li>
<li>쿼리가 특정 텀으로 점수 계산하는 것과 달리, 검색에서 필터는 <strong>이 도큐먼트가 이 쿼리와 일치 하는가</strong> 하는 단순 이진법의 예/아니요 답변만을 반환한다.
<ul>
<li>필터는 일반 쿼리를 사용하는 것보다 더 빠를 수 있고, 캐시할 수 있다.</li>
<li>필터 쿼리 예제
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> curl '..../{index}/_search' -d '{
"query": {
"filtered": {
"query": {
"match": {
"title": "hadoop"
}
},
"filter": {
"term": {
"host": "andy"
}
}
}
},
}'
</code></pre></div> </div>
<p>일반 쿼리 필터 title hadoop, 텀 필터 host andy</p>
</li>
<li>ES 는 필터를 캐시 해야 할 지에 관해 수동으로 지정하는 기능을 제공한다.
<ul>
<li>음 살짝 db index 비슷한 기분?</li>
</ul>
</li>
</ul>
</li>
<li>쿼리 대신 필터 사용이 필요할 때 <code class="language-plaintext highlighter-rouge">match_all</code> 쿼리가 유용하다.
<ul>
<li>도큐먼트 점수에 신경쓰지 않을 때 아래와 같이 사용
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> curl '..../{index}/_search' -d '{
"query": {
"filtered": {
"query": {
"match_all": {}
},
"filter": {
// 필터 세부 내용
}
}
},
}'
</code></pre></div> </div>
<p>쿼리 요소는 이 경우 완벽하게 생략할 수 있다.</p>
</li>
</ul>
</li>
<li>query_string 쿼리
<ul>
<li><code class="language-plaintext highlighter-rouge">AND</code>, <code class="language-plaintext highlighter-rouge">OR</code> 처럼 불린 연산자로 서로 다른 텀을 검색하고, <code class="language-plaintext highlighter-rouge">-</code>(마이너스) 연산을 사용해서 결과로부터 도큐먼트 제외하는걸 합칠 수 있다.</li>
<li>가독성이 떨어지고 확장하기 어려운 단점</li>
<li>웹 사이트 사용자에게 노출되면 위험하다는 점</li>
</ul>
</li>
<li>텀 쿼리와 텀 필터
<ul>
<li>텀 쿼리
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> curl '..../{index}/_search' -d '{
"query": {
"term": {
"tags": "elasticsearch" // 내용
}
},
}'
</code></pre></div> </div>
</li>
<li>텀 필터
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> curl '..../{index}/_search' -d '{
"query": {
"filtered": {
"query": {
"match_all": {}
},
"filter": {
"term": {
"tags": "elasticsearch" // 내용
}
}
}
},
}'
</code></pre></div> </div>
</li>
<li>텀 필터는 점수에 영향을 미치지 않고 텀을 포함하는 도큐먼트</li>
</ul>
</li>
<li>텀즈 쿼리
<ul>
<li>jym 과 hadoop 중 어느 하나와 일치하는 도큐먼트를 찾는 예제
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> curl '..../{index}/_search' -d '{
"query": {
"terms": {
"tags": ["jym", "hadoop"]
}
},
}'
</code></pre></div> </div>
</li>
<li>쿼리 일치 전에 도큐먼트에서 최소 개수의 텀 일치를 강제하려면, <code class="language-plaintext highlighter-rouge">minimum_should_match</code> 파라미터를 사용
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> curl '..../{index}/_search' -d '{
"query": {
"terms": {
"tags": ["jym", "hadoop", "lucene"],
"minimum_should_match": 2
}
},
}'
</code></pre></div> </div>
</li>
</ul>
</li>
</ul>
<h4 id="423-매치-쿼리와-텀-필터">4.2.3 매치 쿼리와 텀 필터</h4>
<ul>
<li>매치 쿼리는 몇몇 서로 다른 방식으로 동작할 수 있다.</li>
<li>
<p>주요한 두 가지 기능은 <code class="language-plaintext highlighter-rouge">boolean</code>, <code class="language-plaintext highlighter-rouge">phrase</code></p>
</li>
<li>boolean 기능
<ul>
<li>기본적으로 쿼리는 boolean 과 or 연산</li>
<li>“Elasticsearch Denver” 텍스트를 검색한다면 “Elasticsearch <code class="language-plaintext highlighter-rouge">OR</code> Denver” 로 검색</li>
<li>아래처럼 연산자를 and 로 변경할 수 있다.
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> curl '..../{index}/_search' -d '{
"query": {
"match": {
"name": {
"query": "Elasticsearch Denver",
"operator": "and"
}
}
},
}'
</code></pre></div> </div>
</li>
</ul>
</li>
<li>phrase 기능
<ul>
<li>각 단어의 위치에 따른 검색</li>
<li>leeway 는 토큰사이의 거리
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> curl '..../{index}/_search' -d '{
"query": {
"match": {
"name": {
"type": "phrase", // phrase
"query": "enterprise london",
"slop": 1 // 텀간 leeway 거리를 가지도록
}
}
},
}'
</code></pre></div> </div>
</li>
</ul>
</li>
<li>phrase_prefix 쿼리
<ul>
<li>phrase 로 검색하는 거에 더해서 마지막 텀에 프리픽스 매칭을 허용</li>
</ul>
</li>
<li>multi_match 로 다중 일치
<ul>
<li>하나의 쿼리를 다중 필드에 검색</li>
</ul>
</li>
</ul>
<h4 id="431-bool-쿼리">4.3.1 bool 쿼리</h4>
<ul>
<li>몇 개의 쿼리라도 특정 부분이 must, should, must_not 로 데이터가 일치하는지 명시하게 하는 쿼리 구문을 이용해서 결합할 수 있다.</li>
</ul>
<table>
<thead>
<tr>
<th>bool 쿼리 절</th>
<th style="text-align: center">이진 연산 대응</th>
<th style="text-align: center">의미</th>
</tr>
</thead>
<tbody>
<tr>
<td>must</td>
<td style="text-align: center">and</td>
<td style="text-align: center">일치</td>
</tr>
<tr>
<td>must_not</td>
<td style="text-align: center">not</td>
<td style="text-align: center">일치하지 않아야 함</td>
</tr>
<tr>
<td>should</td>
<td style="text-align: center">or</td>
<td style="text-align: center">유사하게 일치하거나 그렇지 않을 수 있다, minimum_should_match 로 일치 개수 설정 가능</td>
</tr>
</tbody>
</table>
<ul>
<li>bool filter 도 거의 동일
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> curl '..../{index}/_search' -d '{
"query": {
"filtered": {
"query": {
"match_all": {}
},
"filter": {
"bool" {
"must" : [], // 내용
"should": [], // 내용
"must_not": [] // 내용
}
}
}
},
}'
</code></pre></div> </div>
</li>
</ul>
<h3 id="44-매치와-필터-쿼리를-넘어서서">4.4 매치와 필터 쿼리를 넘어서서</h3>
<h4 id="441-범위-쿼리와-필터">4.4.1 범위 쿼리와 필터</h4>
<ul>
<li><strong>2012년 6월 1일 ~ 8월 1일</strong> 범위 쿼리 예제
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> curl '..../{index}/_search' -d '{
"query": {
"range": {
"created_on": {
"gt": "2012-06-01",
"lt": "2012-09-01"
}
}
},
}'
</code></pre></div> </div>
</li>
<li>필터도 비슷하다.</li>
<li>범위쿼리는 문자열도 지원한다.</li>
<li>범위 쿼리를 만드는 데 99% 경우 필터가 옳은 선택이다.</li>
</ul>
<h4 id="442-프리픽스-쿼리와-필터">4.4.2 프리픽스 쿼리와 필터</h4>
<ul>
<li>“query” 에 “prefix”</li>
</ul>
<h4 id="443-와일드카드-쿼리">4.4.3 와일드카드 쿼리</h4>
<ul>
<li>e.g.)
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> "query": {
"wildcard": {
"title": {
"wildcard": "ba*n"
}
}
},
</code></pre></div> </div>
</li>
<li>결코 가볍지 않다. 더 많은 작업이 필요하다.</li>
</ul>
<h3 id="45-존재하는-필드에-필터로-쿼리">4.5 존재하는 필드에 필터로 쿼리</h3>
<h4 id="451-exists-필터">4.5.1 Exists 필터</h4>
<ul>
<li>특정 필드에 값을 가지는 도큐먼트</li>
<li><code class="language-plaintext highlighter-rouge">location.geolocation</code> 필드를 가진 도큐먼트만 반환하는 예제
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> curl '..../{index}/_search' -d '{
"query": {
"filtered": {
"query": {
"match_all": {}
},
"filter": {
"exists" {
"filed": "location.geolocation"
}
}
}
},
}'
</code></pre></div> </div>
</li>
</ul>
<h4 id="452-missing-필터">4.5.2 Missing 필터</h4>
<ul>
<li>특정 필드에 값이 없는 도큐먼트</li>
<li><code class="language-plaintext highlighter-rouge">reviews</code> 필드가 없는 도큐먼트 검색하는 예제
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> curl '..../{index}/_search' -d '{
"query": {
"filtered": {
"query": {
"match_all": {}
},
"filter": {
"missing" {
"filed": "reviews",
"existence": false,
"null_value": true
}
}
}
},
}'
</code></pre></div> </div>
</li>
<li><code class="language-plaintext highlighter-rouge">null_value</code> 는 해당 필드가 null 이라도 일치한다는 것인데, <code class="language-plaintext highlighter-rouge">existence</code> 는 확실하지가 않다.</li>
</ul>
<h4 id="453-쿼리를-필터로-변환">4.5.3 쿼리를 필터로 변환</h4>
<ul>
<li>
<p>ES 는 어떤 쿼리도 필터로 전환하여 사용 할 수 있다.</p>
</li>
<li>캐시 쿼리
<ul>
<li>“query” 부분은 “fquery” 내부로 이동하고, “query”와 같은 레벨로 “_cache” 옵션</li>
<li>“_cache”: true</li>
</ul>
</li>
<li>표 4.3
<ul>
<li><img src="https://github.com/Oraindrop/oraindrop.github.io/blob/master/assets/_img/EsInActionTable4-3.PNG?raw=true" alt="일반 사용 사례에서 어느 쿼리 타입을 사용해야 하는가" /></li>
</ul>
</li>
</ul>
<h3 id="47-요약">4.7 요약</h3>
<ul>
<li>필터는 점수 계산을 하지 않고 캐시로 쿼리 성능을 올릴 수 있다.</li>
</ul>
<h2 id="9장-스케일아웃">9장 스케일아웃</h2>
<p>현재 필요한 지식은 score 와 관련없는 equal 검색 뿐이므로, 5,6,7,8 장을 skip.
(사실 6장은 읽긴했는데 필요한 지식 X)</p>
<h3 id="91-일래스틱서치-클러스터에-노드-추가하기">9.1 일래스틱서치 클러스터에 노드 추가하기</h3>
<ul>
<li>더 많은 처리량을 갖게 되기를 원할 때 가장 쉽게 향상시킬 수 있는 방법</li>
<li>클러스트에 노드를 추가하여 수평적으로 확장
<ul>
<li><code class="language-plaintext highlighter-rouge">bin/elasticsearch</code></li>
</ul>
</li>
<li>health check
<ul>
<li><code class="language-plaintext highlighter-rouge">GET /_cluster/health?pretty</code></li>
</ul>
</li>
<li>status
<ul>
<li>green : 모든 샤드가 정상, 읽기/쓰기 정상</li>
<li>yellow : 일부 or 모두의 replica 샤드가 비정상, 읽기/쓰기 정상이나 primary 샤드에 문제가 생기면 데이터 유실 가능성이 있다.</li>
<li>red : 일부 or 모두의 primary 샤드 비정상, 읽기/쓰기 불가
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> curl -XGET localhost:9200/_cat/indices?v // primary 당 replica 몇 개 존재하는 지 확인 가능
curl -XGET localhost:9200/_cat/shards?v // 샤드의 상태 체크
</code></pre></div> </div>
</li>
</ul>
</li>
<li>노드가 추가되면, ES는 자동으로 모든 노드에 걸쳐서 샤드들을 균등하게 배분하고자 한다.
<ul>
<li>같은 샤드의 primary 와 replica 는 한 노드에 함께 있을 수 없다.</li>
</ul>
</li>
</ul>
<h3 id="92-다른-일래스틱서치-노드-발견하기">9.2 다른 일래스틱서치 노드 발견하기</h3>
<ul>
<li>추가한 두 번째 노드가 어떻게 첫 번째 노드를 발견하고 자동으로 클러스터에 조인했을까?</li>
</ul>
<h4 id="921-멀티캐스트-디스커버리">9.2.1 멀티캐스트 디스커버리</h4>
<ul>
<li>
<p>default</p>
</li>
<li>
<p>멀티캐스트로 핑을 보내고 같은 클러스터 이름을 가진 다른 ES 노드로부터 응답을 받게 된다.</p>
<ul>
<li>elasticsearch.yml 파일의 cluster.name 기본값을 특정한 이름으로 바꿔야 할 듯.</li>
</ul>
</li>
</ul>
<h4 id="922-유니캐스트-디스커버리">9.2.2 유니캐스트 디스커버리</h4>
<ul>
<li>
<p>네트워크 전체와 통신하기보다 지정된 노드 목록에 연결</p>
</li>
<li>
<p>의도하지 않은 다른 노드들이 클러스터에 연결되는 상황을 방지</p>
</li>
<li>
<p>다른 노드의 IP 주소와 포트를 지정한다</p>
<ul>
<li>elasticsearch.yml 파일에 discovery.zen.ping.unicast.hosts: [””, “”]</li>
</ul>
</li>
</ul>
<h4 id="923-마스터-선출-및-장애-감지">9.2.3 마스터 선출 및 장애 감지</h4>
<ul>
<li>
<p>노드들이 직접 마스터를 선출함</p>
</li>
<li>
<p>node.master 값이 false 가 아닌 모든 노드들이 후보</p>
</li>
<li>minimum_master_nodes 는 클러스터에 있는 노드 중 얼마나 많은 노드가 마스터가 될 자격이 있는지
<ul>
<li>마스터의 개수가 아닌 마스터 후보 개수</li>
<li>(전체클러스터 노드 수 / 2) + 1 개로 하는걸 권장함 (1 보다 큰 값이어야 스플릿 브레인을 방지할 수 있다고 하는데 잘 모르겠음)</li>
</ul>
</li>
<li><code class="language-plaintext highlighter-rouge">GET /_cluster/state/master_node,nodes?pretty</code></li>
</ul>
<h4 id="924-장애-감지">9.2.4 장애 감지</h4>
<ul>
<li>discovery.zen.fd.ping_interval(defualt 1s) 마다 핑을 보내고, discovery.zen.fd.ping_timeout(default 30s) 만큼 대기. 그리고 최대 discovery.zen.fd.ping_retries(default 3회) 만큼 재시도 후 노드가 연결이 끊어졌다고 판단
<ul>
<li>필요에 따라 샤드 이동 or 마스터 선출 진행</li>
</ul>
</li>
</ul>
<h3 id="93-클러스터에서-노드-제거하기">9.3 클러스터에서 노드 제거하기</h3>
<ul>
<li>노드 해체, 해체된 노드에 있는 모든 샤드를 클러스터 내의 다른 노드로 이동시킨다.
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> PUT /_cluster/settings -d ' {
"transient": {
"cluster.routing.allocation.exclude._ip": "{ip}"
}
}
</code></pre></div> </div>
</li>
<li>노드 조회
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> GET /_nodes?pretty // 노드 목록 조회
GET /_cluster/state/routing_table,routing_nodes?pretty // 필터링된 클러스터 상태 조회
</code></pre></div> </div>
</li>
</ul>
<h3 id="94-일래스틱서치-노드-업그레이드">9.4 일래스틱서치 노드 업그레이드</h3>
<h4 id="941-롤링-리스타트">9.4.1 롤링 리스타트</h4>
<ul>
<li>
<p>무중단 업그레이드</p>
</li>
<li>
<p>한 번에 하나씩 노드를 정지</p>
</li>
<li>
<p>레플리카가 있어야 한다.</p>
</li>
</ul>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>1. 클러스터의 allocation을 비활성화
PUT '/_cluster/settings' -d '{
"transient": {
"cluster.routing.allocation.enable": "none"
}
}
2. 업그레이드할 노드를 정지
3. 노드 업그레이드
4. 업그레이드된 노드 구동
5. 업그레이드된 노드가 클러스터에 추가되기를 기다림
6. 클러스터의 allocation 활성화
PUT '/_cluster/settings' -d '{
"transient": {
"cluster.routing.allocation.enable": "all"
}
}
7. 클러스터 그린상태
</code></pre></div></div>
<h3 id="95-_cat-api">9.5 _cat API</h3>
<ul>
<li>_cat API 목록 확인
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> GET /_cat
</code></pre></div> </div>
</li>
</ul>
<h3 id="96-스케일링-전략">9.6 스케일링 전략</h3>
<h4 id="961-오버-샤딩">9.6.1 오버 샤딩</h4>
<ul>
<li>노드의 수는 주 샤드의 숫자를 넘지 않아야 효과가 있음
<ul>
<li>주 샤드가 1개씩 다 분배되고 나면 남은 노드에는 샤드가 분배될 수 없으므로.</li>
</ul>
</li>
<li>그래서 미리 샤드의 수를 5개, 11개 이런식으로 노드보다 많이 가져가는 걸 오버샤딩이라고 함</li>
</ul>
<h3 id="97-앨리어스">9.7 앨리어스</h3>
<ul>
<li>
<p>하나 이상의 실제 인덱스를 지칭하기 위해 사용할 수 있는 포인터</p>
</li>
<li>
<p>뷰와같은 개념이나 파티셔닝 같은 개념으로 활용 가능</p>
</li>
<li>앨리어스에 인덱스 추가 / 삭제
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> POST /_aliases -d ' {
"actions": [
{
"add": { // 앨리어스에 인덱스 추가
"index": "get-together",
"alias": "gt-alias"
}
},
{
"remove": { // 앨리어스에 인덱스 삭제
"index": "old-get-together",
"alias": "gt-alias"
}
}
]
}
</code></pre></div> </div>
</li>
<li>앨리어스 생성
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> PUT /{index}/_alias/{alias}
</code></pre></div> </div>
<p>{index} 에는 쉼표로 구분된 인덱스 목록이나 패턴 _all 등이 가능</p>
</li>
<li>앨리어스 삭제는 DELETE 메소드로 변경만 하면됨</li>
</ul>
<h3 id="98-라우팅">9.8 라우팅</h3>
<ul>
<li>커스텀하게 라우팅을 한다면, 쿼리에 라우팅 값을 지정하여 요청이 인덱스의 특정 샤드들에 대해서만 수행되도록 할 수 있음</li>
</ul>
Wed, 11 Nov 2020 00:00:00 +0000
/book/2020/11/11/Elasticsearch_in_action_2.html
/book/2020/11/11/Elasticsearch_in_action_2.htmljava,book,esBookTop 40 Spring Interview Questions and Answers.<ul>
<li>
<p>출처 : https://www.greycampus.com/blog/programming/top-spring-interview-questions-and-answers</p>
</li>
<li>
<p>1,2 번 문제는 임의로 변경하였음.</p>
</li>
</ul>
<h2 id="1-bean-vs-component">1. @Bean vs @Component</h2>
<ul>
<li>@Bean 은 Method level, @Component 는 Class level (ElementType.Type)
<ul>
<li>이게 핵심인 것 같음 이게 중요한게, 그래서 3rd-party lib 같은 경우 클래스에 직접적으로 @Component 를 달 수가 없으니 팩토리클래스 같은걸 만들어서 메소드에 @Bean 어노테이션을 달고 객체를 return 하도록한다.</li>
<li>이 이야기에서 알 수 있듯 @Bean 은 인스턴스 생성로직을 제어할 수 있다. 메소드를 구현할 수 있으니까.</li>
</ul>
</li>
<li>@Bean 은 @Configuration 어노테이션이 필요하다. @Component 는 노필요.</li>
<li>@Component 는 Specializtion 이 있음, @Controller, @Service 등 @Bean 에는 없음</li>
<li>@Component does not decouple the declaration of the bean from the class definition where as @Bean decouples the declaration of the bean from the class definition.
<ul>
<li>이해가 잘 안되는데, @Component 는 클래스에 하나고, @Bean 은 메소드당 있으니 클래스와 Bean 의 선언이 분리되었다고 하는거려나.</li>
</ul>
</li>
</ul>
<h2 id="2-tight-coupling-vs-loose-coupling">2. Tight Coupling vs Loose Coupling</h2>
<ul>
<li>A class 내부에서 다른 B class 또는 C Interface 를 이용할 때 A 가 B/C 에 의존한다, 디펜던시가 있다 라고 말한다.
<ul>
<li>이 때 A-B/C 간 커플링이 형성</li>
</ul>
</li>
<li>Tight Coupling = 단단한 결합, 긴밀한 결합, 밀접한 결합
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> class Car {
private HyundaiEngine engine;
public Car(HyundaiEngine hyundaiEngine) {
this.engine = hyundaiEngine;
}
}
</code></pre></div> </div>
<ul>
<li>Car 인스턴스 생성시 반드시 HyunDaiEngine 클래스의 인스턴스가 필요</li>
</ul>
</li>
<li>Loose Coupling = 느슨한 결합
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> class Car {
private Engine engine;
public Car(Engine engine) {
this.engine = engine;
}
}
</code></pre></div> </div>
<ul>
<li>Car 인스턴스 생성시 Engine 인터페이스의 구현체가 필요
<ul>
<li>현대, 기아 .. 등 다양한 엔진 가능</li>
</ul>
</li>
<li>유연성, 재사용성. 변경가능성 증가</li>
</ul>
</li>
<li>즉 최대한 구체적인 것에 의존하지 않으면, 커플링은 느슨해진다.</li>
</ul>
<h2 id="3-what-are-beans-in-spring">3. What are Beans in Spring?</h2>
<ul>
<li>스프링 컨테이너에 의해 만들어지고 관리되는 객체
<ul>
<li>컴포넌트 스캐닝이나, 직접 Bean 으로 등록된 객체</li>
<li>ApplicationContext 가 만들어서 그 안에 담고 있는 객체.</li>
<li>Singleton, 한 번 생성된다.</li>
</ul>
</li>
</ul>
<h2 id="4-bean-creation-process">4. Bean Creation Process</h2>
<ul>
<li>크게 두 가지 방법으로 빈을 만든다.
<ol>
<li>Component Scanning
<ul>
<li>@ComponentScan(@SpringBootApplication 은 내부적으로 컴포넌트 스캔을 사용함) 이 붙어있는 클래스가 있는 패키지에서부터 모든 하위 패키지의 클래스를 훑어보며 @Component 가 붙은 클래스를 찾는다.</li>
<li>의존성이 있으면 의존성이 있는 클래스의 Bean 도 생성</li>
<li>두 클래스 간에 연결</li>
</ul>
</li>
<li>직접 Bean 등록
<ul>
<li>@Configurtation 아래에 @Bean 등록</li>
<li>@Configuration 도 어차피 @Component 를 내부적으로 사용하기에 Component 스캐닝됨.</li>
</ul>
</li>
</ol>
</li>
</ul>
<h2 id="5-what-is-the-importance-of-the-annotation-primary">5. What is the importance of the annotation @Primary</h2>
<ul>
<li>
<p>@Component, @Bean 과 같이 쓰일 수 있다.</p>
</li>
<li>
<p>Component 스캔 시 Dependency Injection 과정에서 주입할 대상이 여러개여서 자동적으로 객체를 찾지 못하는 경우가 있다.</p>
</li>
<li>예를들어 <code class="language-plaintext highlighter-rouge">Cleanable</code> 이라는 인터페이스가 있고,
<ul>
<li><code class="language-plaintext highlighter-rouge">Broom</code> 클래스가 <code class="language-plaintext highlighter-rouge">implements Cleanable</code></li>
<li><code class="language-plaintext highlighter-rouge">Tissue</code> 클래스도 <code class="language-plaintext highlighter-rouge">implements Cleanable</code></li>
<li>위 상황에서
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> @Autowired
Cleanable cleanable;
</code></pre></div> </div>
<ul>
<li>이라는 코드가 있다면, <code class="language-plaintext highlighter-rouge">Broom</code> 클래스를 주입할 지, <code class="language-plaintext highlighter-rouge">Tissue</code> 클래스 주입할 지 정할 수 없다.</li>
</ul>
</li>
</ul>
</li>
<li>가장 높은 우선순위를 가질 아이에게 <code class="language-plaintext highlighter-rouge">@Primary</code> 어노테이션을 달면 해결 가능하다.</li>
<li>다시 말해 DI 시 여러개에 종속된 주입 대상이 있을 때 우선순위를 명시하기 위해 사용한다.</li>
</ul>
<h2 id="6-what-is-dependency-injection">6. What is Dependency Injection?</h2>
<ul>
<li>
<p>객체의 의존성을 외부에서 주입하는 패턴</p>
</li>
<li>스프링에서는 객체의 의존성을 스프링 프레임워크에서 주입한다.
<ul>
<li>IoC(Inversion Of Control), 제어의 역전
<ul>
<li>프로그램 제어권을 프레임워크가 가져가는 것</li>
<li>IoC 는 DI를 통해 달성된다</li>
</ul>
</li>
</ul>
</li>
<li>Spring 이(스프링컨테이너가) Bean 을 찾고 종속성을 식별하고 인스턴스를 생성하여 자동으로 연결하는 프로세스</li>
</ul>
<h2 id="7-explain-inversion-of-control-ioc">7. Explain Inversion of Control (IOC)</h2>
<ul>
<li>
<p>DI 랑 세트메뉴</p>
</li>
<li>개발자가 프로그램의 흐름을 주도하던 과거
<ul>
<li>객체의 생성부터 생명주기를 개발자가 관리</li>
</ul>
</li>
<li>스프링은 객체생명주기를 프레임워크가 주도하게 된다.
<ul>
<li>객체의 생성부터 생명주기를 프레임워크가 관리</li>
</ul>
</li>
<li>
<p>제어권이 넘어가서 제어의 역전</p>
</li>
<li>객체간의 결합도를 줄이고 유연한 코드를 작성할 수 있게 돕는다.
<ul>
<li>내가 코드레벨에서 <code class="language-plaintext highlighter-rouge">A a = new A()</code> 해버리면 아주 강결합인데,</li>
<li>외부에서 <code class="language-plaintext highlighter-rouge">A a</code> 를 주입받으면 한 단계 추상화가 된 것이니깐.</li>
</ul>
</li>
</ul>
<h2 id="8-ioc-inversion-of-control-container-역할">8. IOC (Inversion of Control) Container 역할</h2>
<ul>
<li>Bean Factory
<ul>
<li>Bean 생성과 관계설정, 사용, 제거 담당</li>
<li>Bean 을 찾아 의존관계를 설정하고, Bean 의 생명주기를 관리</li>
<li>즉 Bean(오브젝트) 의 제어권 담당</li>
</ul>
</li>
<li>코드레벨로 말하자면 IoC 컨테이너라고 말하는건 <code class="language-plaintext highlighter-rouge">ApplicationConetxt</code> Interface 를 구현한 클래스의 오브젝트</li>
</ul>
<h2 id="9-application-context-는-무엇">9. Application Context 는 무엇?</h2>
<ul>
<li>BeanFactory 를 상속받고, 더 확장된 인터페이스</li>
<li>IoC 컨테이너에 이미 다 말해서 별로 할말이없음</li>
<li>스프링의 IoC 컨테이너</li>
</ul>
<h2 id="10-spring-에서-application-context-생성하는-과정">10. Spring 에서 Application Context 생성하는 과정</h2>
<ul>
<li>xml 이나 @Configuration 두 방법 중 하나로 구성</li>
<li>ClassPathXmlApplicationContext 를 사용하여 생성</li>
<li>ClassPathXmlApplicationContex 는 xml 파일을 찾거나, AnnotationConfigApplicationContext 를 사용하는 방법 둘 중 하나를 사용한다.</li>
</ul>
<h2 id="11-component-scan-설명">11. Component Scan 설명</h2>
<ul>
<li>annotation 방법이면 @ComponentScan 위치(base 패키지) 아래에 있는 모든 @Component 어노테이션을 검색</li>
<li>xml 방법이면 <context : component-scan base-package =”com.demo.compscanex”/> 이런걸 쓴다고 함.</li>
</ul>
<h2 id="12-spring-boot-에서-component-scan-은-어떻게">12. Spring Boot 에서 Component Scan 은 어떻게?</h2>
<ul>
<li>보통 @SpringBootApplication 이라는 어노테이션을 사용한다
<ul>
<li>@ComponentScan과 @EnableAutoConfiguration 이 포함되어 있음</li>
<li>이 클래스 위치가 base package 가 되어 쭈욱 스캔</li>
</ul>
</li>
</ul>
<h2 id="13-component-repository-service-controller-비교">13. @Component, @Repository @Service @Controller 비교</h2>
<ul>
<li>
<p>@Controller, @Service, @Repository 어노테이션은 @Component 어노테이션을 더 읽기 쉽도록 특정 상황에 Specific 하게 정의해 놓은 것</p>
</li>
<li>
<p>예를들어 MVC 관점에서 클라이언트 통신의 최초시점은 @Component를 사용하는 대신 @Controller를 사용
비지니스 로직을 담당하는 곳은 @Service, 일반적으로 DAO 라고 부르는 데이터베이스와 상호작용이 일어나는 부분은 @Repository를 사용하여 가독성을 높인다.</p>
</li>
</ul>
<h2 id="14-spring-bean-scopes">14. Spring Bean Scopes</h2>
<ul>
<li>
<p>스프링에서 Bean으로 지정된 객체는 기본적으로 싱글톤 객체로 관리된다.</p>
</li>
<li>
<p>하지만 요구사항 과 구현기능 등의 필요에 따라서 비싱글톤이 필요한 경우도 있고, 이를 명시적으로 구분하기 위하여 scope 이라는 키워드를 제공한다.</p>
</li>
<li>
<p>singleton, prototype, request, session 정도가 있다.</p>
</li>
<li>singleton
<ul>
<li>디폴트</li>
<li>bean 은 spring 컨테이너에서 한 번 생성된다.</li>
<li>생성된 인스턴스를 single beans cache 에 저장하고, 요청과 참조가 있으면 캐시된 객체를 반환한다.</li>
</ul>
</li>
<li>prototype
<ul>
<li>bean 의 모든 요청에 새로운 객체를 생성한다.</li>
<li>즉, 의존성 관계의 bean에 주입될 때 새로운 객체가 생성되어 주입된다.</li>
<li>정상적인 방식으로 gc에 의해 bean이 제거된다.</li>
</ul>
</li>
<li>request
<ul>
<li>HTTP Request 별로 새로운 객체를 생성하고, 요청이 끝나면 소멸한다.</li>
</ul>
</li>
<li>session
<ul>
<li>HTTP 세션 별로 새로운 객체를 생성하고, 세션이 끝나면 소멸한다.</li>
</ul>
</li>
</ul>
<h2 id="15-di-종류">15. DI 종류</h2>
<ul>
<li>
<p>Setter Injection, Constructor Injection, Field Injection</p>
</li>
<li>
<p>피부에 와닿는 큰 장점을 느끼지 못해서 Field Injection 을 계속 사용하고 있으나 <code class="language-plaintext highlighter-rouge">Constructor Injection</code> 을 가장 클린코드에 가깝다고 생각하는 분위기인 것 같음</p>
</li>
<li>
<p>여러 이유가 있겠지만, 객체 생성시점에 순환참조가 일어나는 지 확인할 수 있다는 장점은 수긍이 되었고, 테스트코드 생성 시에도 조금 이점이 있다는 것도 이해는 되었다!</p>
</li>
<li>
<p>Field Injection 은 매번 편하다는 것 빼고 장점이 없다는 글들이 많이 보이는데, 편리한것도 엄청 큰 장점 아닌가..?</p>
</li>
</ul>
<h2 id="16-configuration-xml-vs-annotation">16. Configuration XML vs Annotation</h2>
<ul>
<li>Configuration 설정하는 두 가지 방법</li>
<li>xml 은 길다. 매번 많은 설정 파일을 작성해야함.
<ul>
<li><code class="language-plaintext highlighter-rouge">xml</code> 을 사용한다는 것 자체가 장점이 될 수 없을 것 같음</li>
<li>외부에 존재하고, 필요한 경우 이미 빌드한 결과에 재 컴파일 없이 xml 만 수정하여 변경사항을 적용할 수 있다는 장점이 있음</li>
</ul>
</li>
<li>annotation 은 코드레벨로 확인할 수 있다는 점이 쉽게 판단할 수 있어 좋고, 코드의 양도 적은 편</li>
</ul>
<h2 id="17-spring-의-autowiring-방식들">17. Spring 의 Autowiring 방식들</h2>
<ul>
<li>
<p>byName, byType, constructor, no</p>
</li>
<li>
<p>byName : Spring 컨테이너가 이름과 동일한 빈을 찾는다.</p>
</li>
<li>
<p>byType : Spring 컨테이너가 클래스가 동일한 빈을 찾는다.</p>
</li>
<li>constructor : Spring 컨테이너가 생성자 기반으로 autowiring 한다.
<ul>
<li>방식은 byType 과 유사, 생성자의 arguments 클래스 유형을 찾은 다음 주입.</li>
</ul>
</li>
<li>
<p>no : autowiring 을 사용하지 않는다.</p>
</li>
<li>
<p>SpringBoot 를 사용하고 있는 내가 @Autowired 해서 쓰는 건 byType으로 사용하고 있는 것이려나</p>
</li>
<li>@Qualifier 를 사용해서 name 을 지정해주면 byName 이 되는느낌</li>
</ul>
<h2 id="18-dirty-read-가-무엇">18. Dirty Read 가 무엇?</h2>
<ul>
<li>
<p>커밋되지 않은 데이터를 읽는 것이지 뭐</p>
</li>
<li>
<p>하나의 트랜잭션 T1이 다른 트랜잭션 T2에 의해 변경된, 하지만 아직 커밋되지 않은 변경사항을 read 하는 것</p>
</li>
<li>
<p>dirty read 의 위험성은 이게 롤백될 수도 있는 데이터이기때문</p>
</li>
</ul>
<h2 id="19-spring-40-과-50-의-새로운-기능">19. Spring 4.0 과 5.0 의 새로운 기능</h2>
<ul>
<li>4.0 이 Java 를 최초로 지원했고, 5.0 이 Reactive Programming 및 Kotlin 을 지원했다는데.. 그냥 대충 넘어가겠음</li>
</ul>
<h2 id="20-front-controller-란">20. Front Controller 란?</h2>
<ul>
<li>
<p>Front Controller = DispatchServlet</p>
</li>
<li>
<p>HTTP Request 를 최초로 받는 친구</p>
</li>
<li>
<p>매핑된 다른 컨트롤러로 위임</p>
</li>
</ul>
<h2 id="21-viewresolver-란">21. ViewResolver 란?</h2>
<ul>
<li>
<p>컨트롤러가 최종적으로 결과를 출력할 View를 리턴할 때, View 를 직접 리턴할 수 도 있지만, 매번 View를 생성하는 것은 비효율 적이므로 View 의 논리적인 이름인 String 만 return</p>
</li>
<li>
<p>DispatchServlet 의 View Resolver가 이를 이용해 View Object 를 만들어 return</p>
</li>
<li>
<p>정리하면 ViewResolver 란 논리적인 이름과 맵핑되는 View 를 생성해주는 애. 라고 보면 될 듯</p>
</li>
<li>
<p>SpringBoot 에서도 일반적인 API 같은 json return 에서 그냥 @Controller를 사용할 경우 (@ResponseBody 도 사용하지 않을 경우) 역시 ViewResolver(BeanNameViewResolver) 에서 ViewName 에 해당하는 뷰를 찾으려고 시도</p>
</li>
<li>
<p>@RequestBody, @ResponseBody 가 있을 경우 <code class="language-plaintext highlighter-rouge">HttpMessageConverter</code> 를 사용한다. @RestController 에서는 @ResponseBody 가 자동으로 적용된다고 보면 됨.</p>
</li>
<li>즉, @Controller
<ul>
<li>Client -> DispatcherServlet -> Handler Mapping -> <strong>Controller</strong> -> <strong>ViewResolver</strong> -> <strong>View</strong></li>
</ul>
</li>
<li>@RestController
<ul>
<li>Client -> DispatcherServlet -> Handler Mapping -> <strong>MessageConverter</strong> -> <strong>HTTP Response</strong> (application/json, text/plain 등)</li>
<li>대표적인 MessageConverter
<ul>
<li>application/json : MappingJackson2HttpMessageConverter</li>
<li>application/xml 또는 text/xml : MarshallingHttpMessageConverter</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2 id="22-spring-mvc-처리과정">22. Spring MVC 처리과정</h2>
<ol>
<li>
<p>Client 의 Request 를 DispatcherServlet 이 받는다.</p>
</li>
<li>
<p>DispatcherServlet 은 HandlerMapping 을 통해 적절한 Controller 를 찾아 위임한다.</p>
</li>
<li>
<p>Controller 는 필요에 따라 의존성 주입된 Service 와 DB 데이터를 활용하여 결과를 만들어 Model 에 넣고 View Name 을 반환한다.</p>
</li>
<li>
<p>DispatcherServlet 은 ViewResolver 를 활용하여 View 를 만들어 Response.</p>
</li>
</ol>
<h2 id="23-modelattribute-란">23. @ModelAttribute 란?</h2>
<ul>
<li>
<p>클라이언트의 Request Parameter 들을 객체로 바인딩 시키고, View 로 넘겨 사용 및 출력하기 위해 사용.</p>
</li>
<li>
<p>@RequestBody 의 경우 Http Request 의 Body 부분을 MessageConverter 를 활용하여 Java Object로 변환한다.</p>
</li>
<li>
<p>@ModelAttribute 는 완전 자바빈규약 즉 setter 만 활용하여 바인딩. 즉 얘는 세터가 없으면 매핑이 아예되지가 않음.</p>
</li>
</ul>
<h2 id="24-sessionattributes-란">24. @SessionAttributes 란?</h2>
<ul>
<li>
<p>Model 정보를 HttpSession 에 저장</p>
</li>
<li>
<p>클래스에 다는거라 Controller 에 주로 다는 것으로 보임</p>
</li>
<li>
<p>컨트롤러에서 메서드가 생성하는 모델정보 중에서 @SessionAttirbutes(“key”) 라고 명시한 key 값과 동일한 것을 세션에 저장</p>
</li>
</ul>
<h2 id="25-initbinder-란">25. @InitBinder 란?</h2>
<ul>
<li>특정 컨트롤러에서 바인딩 또는 검증 설정을 변경하고 싶을 때 설정값을 지정하려 사용</li>
</ul>
<h2 id="26-controlleradvice-란">26. @ControllerAdvice 란?</h2>
<ul>
<li>
<p>여러 Controller 전반에 걸쳐 어떤 로직을 적용하고 싶을 때 사용</p>
</li>
<li>보통은 @ExceptionHandler 와 함께 에러처리를 묶어서 하려 사용
<ul>
<li>@ExceptionHandler 말고 @ModelAttribute, @InitBinder 도 사용할 수 있음</li>
</ul>
</li>
<li>별도 설정하지 않으면 모든 Controller 대상</li>
</ul>
<h2 id="27-why-spring-boot">27. Why Spring Boot?</h2>
<ul>
<li>
<p>스프링 어플리케이션을 구동하기 위해 많은 설정들이 자동으로 되어있어, 신속하게 설정할 수 있다.</p>
</li>
<li>
<p>스프링부트의 AutoConfiguration 은 클래스 패스(경로)에 특정 클래스가 있는지 감지한 후 자동으로 모든 설정을 구성함.</p>
</li>
<li>
<p>또 SpringBoot 의 특성으로는 내장 Tomcat</p>
</li>
</ul>
<h2 id="28-spring-vs-spring-mvc-vs-spring-boot">28. Spring vs Spring MVC vs Spring Boot</h2>
<ul>
<li>Spring
<ul>
<li>Spring 프레임워크의 가장 중요한 특징은 의존성 주입(DI, Dependency Injection)</li>
<li>모든 스프링 모듈들의 핵심에는 의존성 주입이나 IOC(Inversion of Control)가 있다.</li>
</ul>
</li>
<li>Spring MVC
<ul>
<li>Spring MVC 프레임워크는 <strong>웹 애플리케이션 개발 방법</strong> 을 제공한다.</li>
<li>Dispatcher Servlet, ModelAndView, View Resolver 과 같은 개념을 이용해서 웹 애플리케이션 개발을 쉽게 할 수 있도록 해준다.</li>
</ul>
</li>
<li>SpringBoot
<ul>
<li>SpringBoot 가 해결한 핵심은 쉽게 애플리케이션을 구동할 수 있는 것.</li>
<li><strong>Auto Configuration</strong>
<ul>
<li>클래스패스상에 사용가능한 프레임워크와 환경설정을 바라본다.</li>
<li>이것들을 기반으로 스프링 부트는 애플리케이션을 이 프레임워크들과 함께 구성하는데 필요한 기본 환경설정을 제공한다.</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2 id="29-springbootapplication">29. @SpringBootApplication?</h2>
<ul>
<li><code class="language-plaintext highlighter-rouge">@SpringBootApplication</code> 은 세 가지 어노테이션을 포함한다.
<ul>
<li>@SpringBootConfiguration, @EnableAutoConfiguration, @ComponentScan</li>
</ul>
</li>
<li>참고
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> @Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
excludeFilters = {@Filter(
type = FilterType.CUSTOM,
classes = {TypeExcludeFilter.class}
), @Filter(
type = FilterType.CUSTOM,
classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication { ... }
</code></pre></div> </div>
</li>
</ul>
<ol>
<li>@ComponentScan
<ul>
<li>해당 어노테이션이 선언된 하위 패키지에서 @Componet 관련 어노테이션을 찾아 Bean 으로 등록한다.</li>
</ul>
</li>
<li>@EnableAutoConfiguration
<ul>
<li>org.springframework.boot:spring-boot-autoconfigure 의 META-INF 에 spring-factories 파일이 있고, Key 값들과 많은 Class 들을 가지고 있다.</li>
<li>그들은 @Configuration이라는 Annotation 을 가지고 있고, 이런 명시된 class 들이 AutoConfiguration 의 대상이 된다.</li>
</ul>
</li>
<li>@SpringBootConfiguration
<ul>
<li>그냥 Configuration 과 비슷한 듯.</li>
<li>SpringBoot 의 Configuration 이다. 정도?
<ul>
<li>테스트 어노테이션을 사용할 때 계속 이 어노테이션을 찾기 때문에 스프링 부트에서는 필수 어노테이션이다. 라고 함.</li>
</ul>
</li>
</ul>
</li>
</ol>
<h2 id="30-springboot-의-embedded-server-의-의미">30. SpringBoot 의 Embedded Server 의 의미</h2>
<ul>
<li>
<p>별도의 WAS 없이, 독립적으로 실행 가능한 Jar.</p>
</li>
<li>
<p>SpringBoot 의 AutoConfiguration 에 Embedded Server(Tomcat) 설정과 구동이 포함되어 있기 때문.</p>
<ul>
<li>org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration.java</li>
</ul>
</li>
</ul>
<h2 id="31-applicationproperties">31. application.properties</h2>
<ul>
<li>
<p>이 파일은 스프링부트가 애플리케이션을 구동할 때 자동으로 로딩하는 파일.</p>
</li>
<li>
<p>key - value 형식으로 값을 정의하면 애플리케이션에서 참조하여 사용할 수 있다.</p>
</li>
<li>
<p>스프링 부트 사용 시 필요한 옵션들을 작성/추가 할 수 있다.</p>
<ul>
<li>이를테면 DB 정보, 로깅 정보, 톰캣 스레드 설정 등등</li>
</ul>
</li>
</ul>
<h2 id="32-what-is-spring-jdbc">32. What is Spring JDBC</h2>
<ul>
<li>JDBC
<ul>
<li>자바에서 데이터베이스에 상호작용을 할 수 있도록 하는 일종의 표준</li>
</ul>
</li>
<li>Spring JDBC
<ul>
<li>JDBC 를 쉽게 쓸 수 있도록 도와주는 라이브러리</li>
<li>반복적인 작업 (Connection, Statement, ResultSet ..) 을 대신해줘서 개발자는 필요한 부분, 핵심 로직만 개발하면 된다.</li>
</ul>
</li>
</ul>
<h2 id="33-jdbc-vs-spring-jdbc">33. JDBC vs Spring JDBC</h2>
<ul>
<li>
<p>위에서 해버렸다.</p>
</li>
<li>
<p>Spring JDBC 는 기존 JDBC 의 반복적인 작업을 상당수 대신한다.</p>
<ul>
<li>Connection 연결 객체 생성/종료</li>
<li>Statement 준비/실행/종료</li>
<li>ResultSet 처리/종료/예외처리 등등</li>
</ul>
</li>
</ul>
<h2 id="34-jpa-란">34. JPA 란?</h2>
<ul>
<li>
<p>JPA 를 알려면 먼저 ORM 을 알아야함.</p>
</li>
<li>ORM 이란
<ul>
<li>객체를 DB에 맵핑해주는 거</li>
<li>Object 와 RDB 패러다임의 불일치를 개발자 대신 해결해준다.</li>
<li>쉽게말해, SQL Query 를 사용하지 않고 다분히 직관적인 코드로 데이터를 조작할 수 있다.</li>
</ul>
</li>
<li>JPA 는 Java 진영의 ORM 기술표준으로, 인터페이스의 모음이라고 생각하면 됨.</li>
</ul>
<h2 id="35-hibernate-란">35. Hibernate 란?</h2>
<ul>
<li>JPA 인터페이스의 구현체</li>
<li>ORM 프레임워크 중 하나</li>
</ul>
<h2 id="36-constructor-injection-과-setter-injection-case-설명">36. Constructor Injection 과 Setter Injection Case 설명</h2>
<ul>
<li>Setter Injection
<ul>
<li>상황에 따라 의존성 주입이 가능하다.</li>
<li>즉 선택적인 의존성 사용 시 유용하다.</li>
<li>과거의 스프링(3.x) 에서는 Setter Injection 을 추천했다.</li>
</ul>
</li>
<li>Constructor Injection
<ul>
<li>필수적인 의존성 주입에 유용하다.</li>
<li>순환 의존성도 알 수 있다.</li>
<li>현재 스프링에서는 Constructor Injection 을 추천한다.</li>
</ul>
</li>
</ul>
<h2 id="37-pomxml-이란">37. pom.xml 이란?</h2>
<ul>
<li>Maven 프로젝트의 구성이 정의된 문서</li>
</ul>
<h2 id="38-requestparam">38. @RequestParam?</h2>
<ul>
<li>http request 의 parameter 를 서버에서 데이터를 읽고 바인딩 할 때 사용하는 annotaion</li>
</ul>
<h2 id="39-spring-security-란">39. Spring Security 란?</h2>
<ul>
<li>
<p>Spring Security는 Spring 기반의 애플리케이션의 보안(인증과 권한, 인가 등)을 담당하는 스프링 하위 프레임워크이다.</p>
</li>
<li>
<p>인증과 권한에 대한 부분을 <code class="language-plaintext highlighter-rouge">filter</code> 흐름에 따라 처리하고 있다. (not Interceptor)</p>
</li>
<li>
<p>보안과 관련된 많은 옵션들을 제공해주어 개발자 입장에서는 일일히 보안 관련 로직을 작성하지 않아도 된다는 장점</p>
</li>
</ul>
<h2 id="40-csrf">40. CSRF</h2>
<ul>
<li>CSRF (Cross Site Request Forgery)</li>
<li>사이트간 요청 위조
<ul>
<li>웹 애플리케이션 취약점 중 하나로 사용자가 자신의 의지와 무관하게 공격자가 의도한 행동을 하여 특정 웹페이지를 보안에 취약하게 한다거나, 수정, 삭제, 등록 등의 작업을 하게 만드는 공격방법을 의미합니다.</li>
</ul>
</li>
<li>
<p>뭐 딱히 정형화된 수법이 있는건 아니고, 의도하지않은 request 는 대강 이거에 해당함.</p>
</li>
<li>대표적인 방어법
<ul>
<li>Referrer 검증</li>
<li>CSRF Token
<ul>
<li>Session</li>
</ul>
</li>
</ul>
</li>
</ul>
Sun, 25 Oct 2020 00:00:00 +0000
/back-end/2020/10/25/Spring-interview-questions.html
/back-end/2020/10/25/Spring-interview-questions.htmlspring,javaBack-end도서 - Elasticsearch in action (1~3)<h1 id="elasticsearch-in-action--138p">Elasticsearch in action (~ 138p)</h1>
<h2 id="1장-일래스틱서치-소개">1장 일래스틱서치 소개</h2>
<ul>
<li>일래스틱서치는 오픈소스 검색 라이브러리 <strong>아파치 루씬</strong>을 기반으로 만들었다.
<ul>
<li>루씬은 자바 애플리케이션에 검색 기능을 구현할 수 있게 해준다.</li>
<li>루씬은 역 색인(inverted indexing)을 사용한다.</li>
</ul>
</li>
<li>
<p>일래스틱서치 클러스터를 색인, 검색, 관리하기 위해 데이터를 JSON 형태로 HTTP를 통해 전달할 수 있다.</p>
</li>
<li>
<p>rawdata</p>
<table>
<thead>
<tr>
<th>ID</th>
<th style="text-align: center">태그</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td style="text-align: center">투표</td>
</tr>
<tr>
<td>2</td>
<td style="text-align: center">평화</td>
</tr>
<tr>
<td>3</td>
<td style="text-align: center">투표, 평화</td>
</tr>
<tr>
<td>4</td>
<td style="text-align: center">평화</td>
</tr>
</tbody>
</table>
</li>
<li>
<p>색인 데이터</p>
<table>
<thead>
<tr>
<th>태그</th>
<th style="text-align: center">ID</th>
</tr>
</thead>
<tbody>
<tr>
<td>투표</td>
<td style="text-align: center">1, 3</td>
</tr>
<tr>
<td>평화</td>
<td style="text-align: center">2, 3, 4</td>
</tr>
</tbody>
</table>
</li>
</ul>
<h3 id="관련-결과-보장">관련 결과 보장</h3>
<ul>
<li>관련성 점수(relevancy score)
<ul>
<li>검색조건과 일치하는 각각의 문서에 할당된 점수</li>
<li>문서가 얼마나 조건과 관련성이 있는지</li>
<li>검색어가 좀 더 많이 존재하면 대개 그 문서에 더 높은 순위를 매긴다.</li>
<li>관련성 점수를 평가하는 데 사용하는 알고리즘은 tf-idf
<ul>
<li>단어빈도 - 역문서빈도 를 나타낸다.
<ul>
<li>관련성 점수에 영향을 주는 두 요소</li>
<li>단어빈도 (term frequency) : 문서에서 찾고자 하는 단어가 많이 나올수록 높은 점수</li>
<li>역 문서 빈도(inverse document frequency) : 단어가 다른 문서들 간에 흔치 않으면 각 단어의 가중치가 높다</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<h3 id="완전-일치를-뛰어넘어-검색하기">완전 일치를 뛰어넘어 검색하기</h3>
<ul>
<li>오타 처리</li>
<li>파생어 지원</li>
<li>통계 사용</li>
<li>제안 제공</li>
</ul>
<h3 id="일래스틱서치-사용사례">일래스틱서치 사용사례</h3>
<ul>
<li>기본값으로 클러스터링 할 수 있어서 하나의 서버에서 실행할 때도 클러스터라고 부른다.</li>
</ul>
<h3 id="일래스틱서치에서-데이터-구조화하기">일래스틱서치에서 데이터 구조화하기</h3>
<ul>
<li>일래스틱서치에서는 문서가 계층적일 수 있다.</li>
</ul>
<h2 id="2장-기능-들여다보기">2장 기능 들여다보기</h2>
<h3 id="논리적인-배치-이해하기-문서-타입-색인">논리적인 배치 이해하기: 문서, 타입, 색인</h3>
<ul>
<li>문서의 색인을 만들 때 색인 안의 타입에 문서를 넣는다.
<ul>
<li>색인 엄청 어색하다 인덱스가 번역되어 버려서..</li>
</ul>
</li>
<li>색인-타입-ID 조합은 ES 에서 하나의 문서를 유일하게 식별한다.
<ul>
<li>검색할 때 특정 색인의 특정 타입에서 문서를 찾거나, 여러 타입 혹은 여러 색인에 걸쳐 검색할 수 있다.</li>
</ul>
</li>
<li>ES가 문서 기반이라고 했는데, 색인과 검색하는 데이터의 가장 작은 단위가 문서라는 것을 의미한다.</li>
</ul>
<h4 id="문서">문서</h4>
<ul>
<li>독립적이다. 문서는 필드(name)와 값(ES Denver)을 가지고 있다.</li>
<li>계층을 가질 수 있다. (문서 안의 문서)</li>
<li>유연한 구조로 되어 있다. 즉 미리 정의한 스키마에 의존하지 않는다.</li>
<li>문서는 보통 JSON 표현</li>
<li>필드를 추가하거나 생략할 수 있지만, 필드의 타입은 중요하다.</li>
</ul>
<h4 id="타입">타입</h4>
<ul>
<li>문서에 대한 논리적인 컨테이너</li>
<li>각 타입에서 필드의 정의는 매핑이라고 부른다.</li>
<li>새로운 문서가 매핑에 존재하지 않는 필드와 함께 색인하면, ES는 자동으로 새로운 필드를 매핑에 추가한다.
<ul>
<li>필드를 추가하기 위해 타입을 추측한다.</li>
<li>원하는 타입으로 동작하지 않을 수 있다.</li>
<li>데이터의 색인을 만들기 전에 매핑을 정의하는 것이 안전하다.</li>
</ul>
</li>
</ul>
<h4 id="색인">색인</h4>
<ul>
<li>색인은 매핑 타입의 컨테이너다.</li>
<li>색인은 독립적인 문서 덩어리다.</li>
<li>각각의 색인은 디스크에 같은 파일 집합으로 저장한다.</li>
<li>모든 매핑 타입의 모든 필드를 저장하고 고유의 설정을 한다.</li>
<li>각 색인은 refresh_interval 이라는 설정으로 새로 색인한 문서를 검색할 수 있도록 하는 간격을 정의한다. (기본값은 1초에 한번)
<ul>
<li>준 실시간 (near-real-time)</li>
</ul>
</li>
</ul>
<h3 id="물리적-배치-이해하기-노드와-샤드">물리적 배치 이해하기: 노드와 샤드</h3>
<ul>
<li>예) 각 색인은 다섯 개의 주 샤드로 구성되고, 각각은 하나의 복제를 가지고 있어서 총 10개의 샤드 가지게 됨</li>
<li>기술적으로 샤드는 루씬이 여러분의 색인에 대한 데이터를 저장하는 파일들의 디렉토리</li>
<li>샤드는 또한 ES가 노드에서 노드로 옮기는 가장 작은 단위이다.</li>
<li>노드가 충분히 빨라 서로 간에 통신을 할 수 있도록 보장해야한다.
<ul>
<li>스플릿 브레인(split brain) 을 방지하기 위해.
<ul>
<li>클러스터의 두 파트가 통신할 수 없어 다른 파트가 떨어져 나갔다고 생각하는 것.</li>
</ul>
</li>
</ul>
</li>
</ul>
<h4 id="문서의-색인을-만들-때-무슨-일이-일어나는가">문서의 색인을 만들 때 무슨 일이 일어나는가?</h4>
<ul>
<li>우선 문서 ID의 해시값에 기반을 둬서 선택한 주 샤드 중 하나에 보낸다.</li>
<li>그 다음 문서는 주 샤드의 모든 복제에 색인하도록 보내진다.
<ul>
<li>복제가 주 샤드로부터 데이터 동기화 유지</li>
<li>동기된 복제는 검색을 제공하고, 주 샤드가 이용 불가한 경우 주 샤드로 자동 승격될 수 있다.</li>
</ul>
</li>
</ul>
<h4 id="색인을-검색할-때-무슨일이-일어나는가">색인을 검색할 때 무슨일이 일어나는가?</h4>
<ul>
<li>색인 검색 시, ES는 해당 색인의 전체 샤드를 찾아야 한다.</li>
<li>검색하는 색인의 주 샤드와 레플리카 샤드 간에 검색 로드를 분배해서, 검색 성능과 고장 감내(fault tolerance)에 유용하게 레플리카를 사용</li>
</ul>
<h4 id="주-샤드와-레플리카-샤드-이해하기">주 샤드와 레플리카 샤드 이해하기</h4>
<ul>
<li>하나의 샤드는 하나의 루씬 색인이다.</li>
<li>루씬 색인은 역 색인을 포함하는 파일들의 모음이다.
<ul>
<li>역 색인은 ES가 전체 문서를 찾지 않고도, 하나의 텀(단어)을 포함하는 문서를 찾도록 해주는 구조</li>
<li>기본 값으로 원문서의 내용과 검색하는 데 도움을 주는 단어사전과 단어 빈도 같은 추가 정보를 저장한다.</li>
</ul>
</li>
<li>ES 색인은 하나 이상의 주 샤드와 0개 이상의 레플리카 샤드로 구성된다.</li>
<li>레플리카는 실행 시간에 추가나 삭제할 수 있지만 주 샤드는 그렇지 않다.
<ul>
<li>색인을 생성하기 전에 샤드의 수를 결정해야 한다.</li>
<li>너무 적은 샤드는 확장에 제한하고, 너무 많은 샤드는 성능에 영향을 준다.</li>
</ul>
</li>
<li>검색요청은 전체 데이터를 포함한 주/레플리카 샤드에 전달된다. 그 후에 결과를 집계하여 클라이언트에게 보낸다.</li>
</ul>
<h3 id="실습">실습</h3>
<ul>
<li>Windows 에서 안되던게 Mac 으로 뚝딱 된다. Mac 짱짱
<ul>
<li>install
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> brew install elasticsearch
</code></pre></div> </div>
</li>
<li>run
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> elasticsearch
</code></pre></div> </div>
</li>
<li>get-together index 의 group type 문서를 올려보기
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> curl -X PUT 'localhost:9200/get-together/group/1?pretty' \
-H 'Content-Type: application/json;charset=UTF-8' \
-d ' {
"name": "Elasticsearch Denver",
"organizer": "Lee"
}'
</code></pre></div> </div>
</li>
<li>new-index 인덱스 생성해보기
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> curl -H 'Content-Type: application/json;charset=UTF-8' \
-X PUT 'localhost:9200/new-index'
</code></pre></div> </div>
</li>
<li>https://github.com/dakrone/elasticsearch-in-action -> clone -> ./populate.sh
<ul>
<li>문제가 있네, es 버전이 달라서 신 버전부터는 String type 이 없어지고 text/keyword 타입으로 나누어졌고</li>
<li>curl 요청 시 header 를 더 명확히 전달해야해서 sh 파일이 정상적으로 동작하지 않음</li>
</ul>
</li>
<li>데이터 검색 시 쿼리는 q=name:elasticsearch 처럼 특정 필드에 수행하지만, 모든 필드 검색을 원하는 경우 명시하지 않아도 된다.
<ul>
<li>이럴 경우 _all이라는 특정필드를 사용한다</li>
</ul>
</li>
<li>타입을 지정할 수도 있다. 아래에선 group과 event type 만 검색하겠다는 것
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> curl "localhost:9200/get-together/group,event/_search?q=elasticsearch&pretty"
</code></pre></div> </div>
</li>
<li>모든 인덱스에서 검색하려면 색인명을 생략하는 것도 가능하다.</li>
<li>검색 응답은 검색 기준 일치하는 문서 뿐 아니라, 검색 성능이나 결과의 유사도를 확인하는 데 유용한 정보를 포함한다.
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> {
"took" : 2
"timed_out": false
// 요청이 얼마나 걸리고 타임아웃 발생여부
"_shards" : {
"total" : 2,
"successful" : 2,
"failed" 0
}
// 몇 개의 샤드에 질의했는지
"hit" : {
"total" : 2 // 일치하는 문서 수
"max_score" : 0.9066504, // 일치하는 문서의 최대 점수
// 일치하는 모든 문서에 대한 통계
"hits" : { ... // 결과배열
}
}
</code></pre></div> </div>
</li>
<li>전체 문서 수는 응답에서 보는 문서 수와 일치하지 않을 수도 있다. (결과 수가 10으로 limit 걸려있어서)</li>
<li>hits 는 응답에서 보통 가장 관심있는 정보
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> "hits" : [ {
"_index" : "get-together",
"_type" : "group",
"_id" : "3",
"_score" : 0.9066504,
"fields" : {
"location" : ["San Francisco, California, "USA"],
"name" : ["Elasticsearch San Francisco"]
}
}]
</code></pre></div> </div>
<ul>
<li>어떤 필드를 원하는지 명시하지 않으면 _source 필드가 보인다</li>
</ul>
</li>
<li>적절한 쿼리타입 선택
<ul>
<li>다양한 다른 형태의 쿼리가 존재한다.</li>
<li>쿼리는 결과를 돌려주고 각 결과는 점수가 있다.</li>
<li>점수에 관심이 없다면, <code class="language-plaintext highlighter-rouge">필터 쿼리</code>를 대신 실행할 수 있다.
<ul>
<li>오직 결과가 검색과 일치하는지만 관심</li>
</ul>
</li>
<li>텀 쿼리
<ul>
<li>명시한 필드에 나타난 텀별 수치를 보여준다.
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> "aggregations" : {
"organizers" : {
"terms" : { "field" : "organizer" }
}
}
</code></pre></div> </div>
</li>
<li>terms 타입 요청, organizer 필드를 찾는 organizers 라고 명명된 집계를 주세요.</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<h3 id="es-설정하기">es 설정하기</h3>
<ul>
<li>elasticsearch.yml
<ul>
<li>일래스틱 특유의 옵션이 들어가는 주 설정 파일
<ul>
<li>클러스터 이름 명시 등..</li>
</ul>
</li>
</ul>
</li>
<li>logging.yml
<ul>
<li>로깅 옵션 수정</li>
</ul>
</li>
<li>elasticsearch.in.sh
<ul>
<li>es 를 작동시키는 jvm 설정
<ul>
<li>메모리 설정 조정 등</li>
</ul>
</li>
</ul>
</li>
<li>es 로그 엔트리는 세 가지 파일 형태로 구성된다.
<ul>
<li>메인 로그(cluster-name.log)</li>
<li>느린 검색 로그(cluster-name_index_search_slowlog.log)
<ul>
<li>기본 설정은 0.5초 이상</li>
</ul>
</li>
<li>느린 색인 로그(cluster-name_index_indexing_slowlog.log)
<ul>
<li>색인 작업이 0.5초 이상</li>
</ul>
</li>
</ul>
</li>
</ul>
<h4 id="클래스터에-노드-추가하기">클래스터에 노드 추가하기</h4>
<ul>
<li>/_cat/shards?v
<ul>
<li>like inux top</li>
</ul>
</li>
</ul>
<h2 id="3장-데이터-색인-변경-삭제">3장 데이터 색인, 변경, 삭제</h2>
<ul>
<li>필드
<ul>
<li>_timestamp 처럼 새 데이터를 자동으로 도큐먼트에 추가하도록 설정하거나, _ttl 필드를 사용하여 지정된 시간 이후 자동삭제하게끔 할 수 있음</li>
</ul>
</li>
<li>도큐면트 변경 시 동시성 문제 관련된 것을 살펴볼 것이다.</li>
</ul>
<h3 id="도큐먼트-종류를-정의하는-매핑-사용하기">도큐먼트 종류를 정의하는 매핑 사용하기</h3>
<ul>
<li>색인은 타입을 포함하고, 타입은 개별 다큐먼트를 포함한다.</li>
<li>다른 다큐먼트라도 같은 필드를 갖는다면 타입이 같은게 좋다.</li>
<li>GET <code class="language-plaintext highlighter-rouge">/_mapping</code> 을 활용하여 타입의 매핑을 확인할 수 있다.</li>
<li>PUT <code class="language-plaintext highlighter-rouge">/_mapping</code> 을 활용하여 타입의 매핑을 설정할 수 있다.</li>
<li>이미 존재하는 필드 데이터 타입은 변경할 수 없다.
<ul>
<li>타입의 데이터를 모두 삭제하고, 새 매핑을 입력한 후 데이터 전체를 다시 색인하게 할 수도 있다.</li>
</ul>
</li>
</ul>
<h3 id="기본-타입">기본 타입</h3>
<ul>
<li>문자열, 숫자, 날짜, 불린
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> PUT /get-together/new-events/1 -d {
"name": "Late Night with Elasticsearch"
"date": "2013-10-25T19:00"
}
</code></pre></div> </div>
<ul>
<li>“Late Night with Elasticsearch” 색인 시, 기본 분석기는 모든 문자를 소문자로 만들고 문자열을 단어 단위로 분할한다.</li>
<li>late, night, with, elasticsearch 4가지 텀을 만들어낸다.
<ul>
<li>텀은 text로 부터 나온 단어이고, 검색을 위한 기본 단위</li>
</ul>
</li>
</ul>
</li>
<li>매핑에서 분석을 위한 다수의 옵션을 지정할 수 있다.
<ul>
<li>예를 들어, 원본 텀의 동의어 텀을 만들어내는 분석도 설정할 수 있다.
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> PUT /get-together/_mapping/new-events -d {
...
"name": {
"type: "string",
"index": "not_analyzed"
}
}
</code></pre></div> </div>
</li>
<li>index 기본 값은 analyzed 인데 이렇게하면 전체 문자를 단일 텀으로 인식하는 것.</li>
<li>index 를 no 로 설정하면 검색이 아예 안됨
<ul>
<li>검색이 필요없는 필드에 이렇게 설정하면 되겠지?</li>
</ul>
</li>
</ul>
</li>
<li>날짜 타입은 long 숫자로 루씬 색인에 저장함
<ul>
<li>UTC 1970 1 1 00:00:00 부터 지정시간까지 ms 숫자로 저장함</li>
</ul>
</li>
<li>문자열을 위한 다중 필드
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> PUT /blog/_mapping/posts -d {
"properties": {
"tags": {
"type": "string",
"index": "analyzed",
"fields": { // 두번째 필드 정의
"verbatim": {
"type": "string",
"index": "not_analyzed"
}
}
}
}
}
</code></pre></div> </div>
</li>
</ul>
<h3 id="사전-정의-필드">사전 정의 필드</h3>
<ul>
<li>특수 기능을 갖고있다.
<ul>
<li>e.g. _ttl 필드는 특정 시간 이후에 도큐먼트 자동 삭제 기능</li>
</ul>
</li>
<li>필드 이름이 모두 언더스코어(_)로 시작한다.</li>
<li>_source 는 모든것을 저장하고 _all 는 모든것을 색인한다.</li>
<li>_uid 를 내부적으로 사용하고 다른구조로 사용하기 쉽도록 _type과 _id라는 추상적인 개념으로 분리하여 제공한다.</li>
</ul>
<h3 id="기존-도큐먼트-변경하기">기존 도큐먼트 변경하기</h3>
<ul>
<li>조회 - 처리 - 재색인을 수반한다.</li>
<li>존재하지 않는 도큐먼트를 생성할 때는 <code class="language-plaintext highlighter-rouge">upsert</code></li>
<li>동시에 다수 변경을 실행한다면 동시성 문제가 발생할 수 있다.</li>
</ul>
<h3 id="데이터-삭제하기">데이터 삭제하기</h3>
<ul>
<li>삭제 방법
<ul>
<li>개별 도큐먼트 또는 도큐먼트 그룹 삭제</li>
<li>완성된 색인 삭제</li>
<li>색인 닫기
<ul>
<li>실제 삭제는 아닌 것 같고</li>
<li>읽기 쓰기 작업 불허, 메모리 Load X</li>
<li>삭제한 것과 다름이 없지만 디스크에는 남아있어 닫은 색인을 열기만 하면 복구가 된다는 장점</li>
</ul>
</li>
</ul>
</li>
</ul>
Wed, 05 Aug 2020 00:00:00 +0000
/book/2020/08/05/Elasticsearch_in_action.html
/book/2020/08/05/Elasticsearch_in_action.htmljava,book,esBook도서 - 클린코드 2 (작성중)<h1 id="클린코드--245p">클린코드 (~ 245p)</h1>
<h2 id="동시성">동시성</h2>
<blockquote>
<p>객체는 처리의 추상화다. 스레드는 일정의 추상화다.</p>
</blockquote>
<ul>
<li>동시성은 결합을 없애는 전략
<ul>
<li>무엇(What)과 언제(When)를 분리하는 전략</li>
</ul>
</li>
<li>동시성에 대한 일반적인 <strong>오해</strong>
<ol>
<li>동시성은 항상 성능을 높여준다.
<ul>
<li>동시성은 <strong>때로</strong> 성능을 높여준다.</li>
<li>대기 시간이 아주 길어 여러 스레드가 프로세서를 공유할 수 있거나</li>
<li>여러 프로세서가 동시에 처리할 독립적인 계산이 충분히 많은 경우</li>
</ul>
</li>
<li>동시성을 구현해도 설계는 변하지 않는다.
<ul>
<li>단일 스레드 시스템과 다중 스레드 시스템 설계는 <strong>판이하게</strong> 다르다.</li>
</ul>
</li>
</ol>
</li>
<li>동시성에 대한 타당한 생각
<ol>
<li>동시성은 다소 부하를 유발한다.</li>
<li>동시성은 복잡하다.</li>
<li>일반적으로 동시성 버그는 재현하기 어렵다.</li>
<li>동시성을 구현하려면 흔히 근본적인 설계 전략을 재고해야 한다.</li>
</ol>
</li>
<li>동시성 방어 원칙
<ol>
<li>SRP
<ul>
<li>동시성 관련 코드는 다른 코드와 분리해야 한다.</li>
<li>1-1. 자료 범위를 제한하라
<ul>
<li>임계영역을 활용</li>
<li>공유 자원을 최대한 줄여라</li>
<li>자료를 캡슐화 하라</li>
</ul>
</li>
<li>1-2. 자료 사본을 이용하라
<ul>
<li>공유 자원을 줄이려면 처음부터 공유하지 않는 방법이 제일 좋다.</li>
<li>복사하여 읽기 전용으로 사용 가능</li>
</ul>
</li>
<li>1-3. 스레드는 가능한 독립적으로 구현하라
<ul>
<li>다른 스레드와 자료를 공유하지 않는다.</li>
<li>각 스레드는 클라이언트 요청 하나를 처리할 뿐, 비공유 출처에서 가져오며 로컬 변수를 활용.</li>
<li>독립적으로 돌려도 괜찮도록 자료를 독립적인 단위로 분할하라.</li>
</ul>
</li>
</ul>
</li>
<li>라이브러리를 이해하라
<ul>
<li>스레드 세이프 한 컬렉션을 사용하라.</li>
<li>일부 클래스 라이브러리는 스레드 세이프 하지 않다.</li>
<li>java.util.concurrent 패키지가 제공하는 클래스는 다중 스레드 환경에서 사용해도 안전하며, 성능도 좋다.</li>
<li>실제로 ConcurrentHashMap 은 거의 모든 상황에서 HashMap 보다 빠르다고 한다. (정말?)</li>
</ul>
</li>
<li>실행 모델을 이해하라
<ul>
<li>Bound Resource(한정된 자원) : 다중 스레드 환경에서 사용하는 자원으로, 크기나 숫자가 제한적이다. DB 연결, 길이가 일정한 read/write buffere…</li>
<li>Mutual Exclusion(상호 배제) : 한 번에 한 스레드만 공유 자료/자원 을 사용할 수 있는 경우</li>
<li>Stravation(기아) : 오랫동안 혹은 영원히 자원을 기다린다.</li>
<li>Deadlock(데드락) : 여러 스레드가 서로가 끝나기를 기다린다.</li>
<li>Livelock(라이브락) : 락을 거는 단계에서 각 스레드가 서로를 방해한다. 스레드는 계속해서 진행하려 하지만, 굉장히 오랫동안 혹은 영원히 진행하지 못한다.</li>
<li>생산자-소비자
<ul>
<li>생산자는 대기열에 빈 공간이 있을 때 정보를 채우고, 소비자는 대기열에 정보가 있어야 가져온다.</li>
<li>생산자와 소비자는 서로에게 시그널을 보낸다.
<ul>
<li>생산자는 대기열에 정보를 채운 다음 “대기열에 정보가 있다.”</li>
<li>소비자는 정보를 읽어들인 후 “대기열에 빈 공간이 있다.”</li>
<li>잘못하면 생산자 스레드와 소비자 스레드가 둘 다 진행 가능함에도 불구하고 동시에 서로에게서 시그널을 기다릴 가능성이 존재한다.</li>
</ul>
</li>
</ul>
</li>
<li>읽기-쓰기
<ul>
<li>읽기 스레드를 위한 주된 정보원 공유 자원을 쓰기 스레드가 이따금 갱신한다고 하자.</li>
<li>간단한 방법, 읽기 스레드가 요청이 없을 때 까지 쓰기 스레드가 기다린다.
<ul>
<li>읽기 스레드가 계속 이어진다면 쓰기 스레드가 기아 상태에 빠진다.</li>
</ul>
</li>
<li>반면, 쓰기 스레드에게 우선권을 준 상태에서 쓰기 스레드가 계속 이어진다면 처리율이 떨어진다.</li>
<li>양 쪽 균형을 잡으면서 동시 갱신 문제를 피하는 해법이 필요하다.</li>
</ul>
</li>
<li>식사하는 철학자들
<ul>
<li>둥근 식탁에 철학자 한 무리가 둘러앉아 있고, 각 철학자 왼쪽에는 포크가 놓여있다. 식탁 가운데는 커다란 스파게티 한 접시가 놓여있다.</li>
<li>철학자들은 배가 고프지 않으면 생각하며 시간을 보낸다.</li>
<li>배가 고프면 양손에 포크를 집어들고 스파게티를 먹는다. (양손에 포크를 쥐지 않으면 먹지 못한다.) 먹고 나면 포크를 내려놓고 배가 고플 때까지 생각에 잠긴다.</li>
<li>왼쪽 철학자나 오른쪽 철학자가 포크를 사용중이라면 포크를 내려놓을 때 까지 기다려야 먹을 수 있다.</li>
<li>철학자를 스레드로, 포크를 자원으로 바꿔 생각하면 멀티스레드 문제이다.</li>
</ul>
</li>
</ul>
</li>
<li>동기화하는 메서드 사이에 존재하는 의존성을 이해하라
<ul>
<li>공유 객체 하나에는 메서드 하나만 사용하라.</li>
<li>공유 객체 하나에 여러 메서드가 필요한 상황일 경우 아래 세 가지 방법을 고려한다,
<ol>
<li>클라이언트에서 잠금
<ul>
<li>클라이언트에서 첫 번째 메서드를 호출하기 전에 서버를 잠근다. 마지막 메서드를 호출할 때까지 잠금을 유지한다.</li>
</ul>
</li>
<li>서버에서 잠금
<ul>
<li>서버에다 “서버를 잠그고 모든 메서드를 호출한 후 잠금을 해제하는” 메서드를 구현한다. 클라이언트는 이 메서드를 호출한다.</li>
</ul>
</li>
<li>연결(Adapted) 서버
<ul>
<li>잠금을 수행하는 중간 단계를 생성한다. 서버에서 잠금 방식과 유사하지만 원래 서버는 변경하지 않는다.</li>
</ul>
</li>
</ol>
</li>
</ul>
</li>
<li>동기화하는 부분을 작게 만들어라
<ul>
<li>자바에서 <code class="language-plaintext highlighter-rouge">synchronized</code> 키워드를 사용하면 락을 설정한다. 같은 락으로 감싼 모든 코드 영역은 한 번에 한 스레드만 실행이 가능하다.</li>
<li>락은 스레드를 지연시키고 부하를 가중시킨다.</li>
<li>따라서 <code class="language-plaintext highlighter-rouge">synchronized</code> 문을 남발하는 코드는 바람직하지 않다.</li>
<li>하지만 임계영역은 반드시 보호해야 한다.</li>
<li>결국 임계영역 수를 최대한 줄여야 한다.</li>
</ul>
</li>
<li>올바른 종료 코드는 구현하기 어렵다
<ul>
<li>영구적으로 돌아가는 시스템을 구현하는 방법과 잠시 돌다 깔끔하게 종료하는 시스템을 구현하는 방법은 다르다.</li>
<li>깔끔하게 종료하는 코드는 올바로 구현하기 어렵다. 가장 흔히 발생하는 문제가 <code class="language-plaintext highlighter-rouge">데드락</code> 이다. 스레드가 절대 오지 않을 시그널을 기다린다.</li>
<li>종료 코드를 개발 초기부터 고민하고 동작하게 초기부터 구현하라.</li>
<li>생각보다 어려우므로 이미 나온 알고리즘을 검토하라.</li>
</ul>
</li>
<li>스레드 코드 테스트하기
<ul>
<li>테스트가 정확성을 완전히 보장하지는 않는다.</li>
<li>그럼에도 충분한 테스트는 위험을 낮춘다.
<ol>
<li>말이 안되는 실패는 잠정적인 스레드 문제로 취급하라
<ul>
<li>하지만 시스템 실패를 일회성이라 치부하지는 마라.</li>
</ul>
</li>
<li>다중 스레드를 고려하지 않은 순차 코드부터 제대로 돌게 만들자
<ul>
<li>스레드 환경 밖에서 생기는 버그와 스레드 환경에서 생기는 버그를 동시에 디버깅하지 마라.</li>
<li>먼저 스레드 환경 밖에서 코드를 올바로 돌려라.</li>
</ul>
</li>
<li>다중 스레드를 쓰는 코드 부분을 다양한 환경에 쉽게 끼워 넣을 수 있게 쓰레드 코드를 구현하라
<ul>
<li>다중 스레드를 쓰는 코드를 다양한 설정으로 실행하기 쉽게 구현하라.</li>
</ul>
</li>
<li>다중 스레드를 쓰는 코드 부분을 상황에 맞게 조율할 수 있게 작성하라
<ul>
<li>적절한 스레드 개수를 파악하려면 상당한 시행착오가 필요하다.</li>
<li>다양한 설정으로 프로그램의 성능 측정 방법을 강구한다.</li>
</ul>
</li>
<li>프로세서 수보다 많은 스레드를 돌려보라</li>
<li>다른 플랫폼에서 돌려보라</li>
<li>코드에 보조 코드를 넣어 돌려라. 강제로 실패를 일으키게 해보라</li>
</ol>
</li>
</ul>
</li>
</ol>
</li>
<li>다중 스레드 코드는 올바로 구현하기 어렵다. 간단했던 코드가 여러 스레드와 공유 자료를 추가하면서 악몽으로 변한다. 각별히 깨끗하게 코드를 짜야 한다.</li>
</ul>
Sun, 19 Jul 2020 00:00:00 +0000
/book/2020/07/19/%ED%81%B4%EB%A6%B0%EC%BD%94%EB%93%9C2.html
/book/2020/07/19/%ED%81%B4%EB%A6%B0%EC%BD%94%EB%93%9C2.htmljava,bookBook