From e759ec2325a3145cd0e87e1c52c1c23fd2dd6225 Mon Sep 17 00:00:00 2001 From: twoosky Date: Fri, 11 Nov 2022 01:37:27 +0900 Subject: [PATCH] =?UTF-8?q?5.2.1=20=EB=AA=A8=20=EC=95=84=EB=8B=88=EB=A9=B4?= =?UTF-8?q?=20=EB=8F=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/ch5/domain/User.java | 5 +- src/main/java/com/ch6/dao/DaoFactory6.java | 33 ++++ src/main/java/com/ch6/dao/UserDao.java | 21 +++ src/main/java/com/ch6/dao/UserDaoJdbc.java | 83 +++++++++ src/main/java/com/ch6/domain/Level.java | 32 ++++ src/main/java/com/ch6/domain/User.java | 78 ++++++++ .../java/com/ch6/service/UserService.java | 48 +++++ src/test/java/com/ch6/UserDaoTest.java | 170 ++++++++++++++++++ src/test/java/com/ch6/UserServiceTest.java | 126 +++++++++++++ src/test/java/com/ch6/UserTest.java | 48 +++++ 10 files changed, 641 insertions(+), 3 deletions(-) create mode 100644 src/main/java/com/ch6/dao/DaoFactory6.java create mode 100644 src/main/java/com/ch6/dao/UserDao.java create mode 100644 src/main/java/com/ch6/dao/UserDaoJdbc.java create mode 100644 src/main/java/com/ch6/domain/Level.java create mode 100644 src/main/java/com/ch6/domain/User.java create mode 100644 src/main/java/com/ch6/service/UserService.java create mode 100644 src/test/java/com/ch6/UserDaoTest.java create mode 100644 src/test/java/com/ch6/UserServiceTest.java create mode 100644 src/test/java/com/ch6/UserTest.java diff --git a/src/main/java/com/ch5/domain/User.java b/src/main/java/com/ch5/domain/User.java index 0dc0af2..51647ac 100644 --- a/src/main/java/com/ch5/domain/User.java +++ b/src/main/java/com/ch5/domain/User.java @@ -69,10 +69,9 @@ public User() {} public void upgradeLevel() { Level nextLevel = this.level.nextLevel(); - if(nextLevel == null) { + if (nextLevel == null) { throw new IllegalStateException(this.level + "은 업그레이드가 불가능합니다."); - } - else { + } else { this.level = nextLevel; } } diff --git a/src/main/java/com/ch6/dao/DaoFactory6.java b/src/main/java/com/ch6/dao/DaoFactory6.java new file mode 100644 index 0000000..7895b23 --- /dev/null +++ b/src/main/java/com/ch6/dao/DaoFactory6.java @@ -0,0 +1,33 @@ +package com.ch6.dao; + + +import com.ch6.service.UserService; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.jdbc.datasource.SimpleDriverDataSource; + +import javax.sql.DataSource; + +// 애플리케이션 컨텍스트 또는 빈 팩토리가 사용할 설정 클래스임을 명시하는 어노테이션 +@Configuration +public class DaoFactory6 { + // 오브젝트 생성을 담당하는 IoC용 메소드임을 명시하는 어노테이션 + // 해당 메소드 이름이 빈의 이름이 된다. + @Bean + public DataSource connectionMaker() { + SimpleDriverDataSource dataSource = new SimpleDriverDataSource(); + dataSource.setDriverClass(com.mysql.jdbc.Driver.class); + dataSource.setUrl("jdbc:mysql://localhost:53306/springbook"); + dataSource.setUsername("spring"); + dataSource.setPassword("book"); + return dataSource; + } + + // 빈의 이름은 클래스의 구현 인터페이스 이름을 따르는 것이 좋다. + // -> 그래야 나중에 구현 클래스를 바꿔도 혼란이 없기 때문 + @Bean + public UserDao userDao() { return new UserDaoJdbc(connectionMaker()); } + + @Bean + public UserService userService() { return new UserService(); } +} diff --git a/src/main/java/com/ch6/dao/UserDao.java b/src/main/java/com/ch6/dao/UserDao.java new file mode 100644 index 0000000..dd84d28 --- /dev/null +++ b/src/main/java/com/ch6/dao/UserDao.java @@ -0,0 +1,21 @@ +package com.ch6.dao; + + +import com.ch6.domain.User; + +import java.util.List; + +// 데이터 엑세스 기술(JPA, Hibernate, JDO ..)에 따른 독립적인 UerDao를 만들기 위해 인터페이스와 구현 분리 +public interface UserDao { + /* + 데이터 엑세스 기술마다 던지는 예외가 다르므로 예외에 따라 메소드의 선언이 달라진다. + 다행히도 JDO, Hibernate, JPA는 SQLException같은 체크 예외 대신 런타임 예외를 사용하기 때문에 메소드에 throws 선언을 해주지 않아도 된다. + JDBC에서 SQLException을 런타임 예외로 포장해 던진다면 예외에 따라 메소드 선언을 다르게 할 필요없이 아래와 같이 선언할 수 있게 된다. + */ + void add(User user); + User get(String id); + List getAll(); + void deleteAll(); + int getCount(); + void update(User user); +} diff --git a/src/main/java/com/ch6/dao/UserDaoJdbc.java b/src/main/java/com/ch6/dao/UserDaoJdbc.java new file mode 100644 index 0000000..168fae8 --- /dev/null +++ b/src/main/java/com/ch6/dao/UserDaoJdbc.java @@ -0,0 +1,83 @@ +package com.ch6.dao; + +import com.ch6.domain.Level; +import com.ch6.domain.User; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.RowMapper; + +import javax.sql.DataSource; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.List; + +public class UserDaoJdbc implements UserDao { + private JdbcTemplate jdbcTemplate; + + public void setDataSource(DataSource dataSource) { + this.jdbcTemplate = new JdbcTemplate(dataSource); + } + + public UserDaoJdbc(DataSource dataSource) { + setDataSource(dataSource); + } + + private RowMapper userMapper = + new RowMapper() { + @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 users(id, name, password, level, login, recommend) values(?,?,?,?,?,?)", + user.getId(), + user.getName(), + user.getPassword(), + user.getLevel().intValue(), + user.getLogin(), + user.getRecommend() + ); + } + + public User get(String id) { + return this.jdbcTemplate.queryForObject("select * from users where id = ?", + new Object[] {id}, // SQL에 바인딩할 파라미터 값, 가변인자 대신 배열을 사용한다. + this.userMapper + ); + } + + + public void deleteAll() { + this.jdbcTemplate.update("delete from users"); + } + + public int getCount() { + return this.jdbcTemplate.queryForObject("select count(*) from users", Integer.class); + } + + public List getAll() { + return this.jdbcTemplate.query("select * from users order by id", + this.userMapper); + } + + public void update(User user) { + this.jdbcTemplate.update("update users set name = ?, password = ?, level = ?, login = ?, " + + "recommend = ? where id = ? ", + user.getName(), + user.getPassword(), + user.getLevel().intValue(), + user.getLogin(), + user.getRecommend(), + user.getId() + ); + } +} diff --git a/src/main/java/com/ch6/domain/Level.java b/src/main/java/com/ch6/domain/Level.java new file mode 100644 index 0000000..117bda4 --- /dev/null +++ b/src/main/java/com/ch6/domain/Level.java @@ -0,0 +1,32 @@ +package com.ch6.domain; + +public enum Level { + GOLD(3, null), + SILVER(2, GOLD), + BASIC(1, SILVER); + + private final int value; + private final Level next; + + Level(int value, Level next) { + this.value = value; + this.next = next; + } + + public int intValue() { + return value; + } + + public Level nextLevel() { + return this.next; + } + + 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); + } + } +} diff --git a/src/main/java/com/ch6/domain/User.java b/src/main/java/com/ch6/domain/User.java new file mode 100644 index 0000000..803eeaf --- /dev/null +++ b/src/main/java/com/ch6/domain/User.java @@ -0,0 +1,78 @@ +package com.ch6.domain; + +public class User { + String id; + String name; + String password; + Level level; + int login; // 로그인 수 + int recommend; // 추천 수 + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public Level getLevel() { + return level; + } + + public void setLevel(Level level) { + this.level = level; + } + + public int getLogin() { + return login; + } + + public void setLogin(int login) { + this.login = login; + } + + public int getRecommend() { + return recommend; + } + + public void setRecommend(int recommend) { + this.recommend = recommend; + } + + public User(String id, String name, String password, Level level, int login, int recommend) { + this.id = id; + this.name = name; + this.password = password; + this.level = level; + this.login = login; + this.recommend = recommend; + } + + public User() {} + + public void upgradeLevel() { + Level nextLevel = this.level.nextLevel(); + if (nextLevel == null) { + throw new IllegalStateException(this.level + "은 업그레이드가 불가능합니다."); + } else { + this.level = nextLevel; + } + } +} diff --git a/src/main/java/com/ch6/service/UserService.java b/src/main/java/com/ch6/service/UserService.java new file mode 100644 index 0000000..71c8e63 --- /dev/null +++ b/src/main/java/com/ch6/service/UserService.java @@ -0,0 +1,48 @@ +package com.ch6.service; + +import com.ch6.dao.UserDao; +import com.ch6.domain.Level; +import com.ch6.domain.User; +import org.springframework.beans.factory.annotation.Autowired; + +import java.util.List; + +public class UserService { + public static final int MIN_LOGCOUNT_FOR_SILVER = 50; + public static final int MIN_RECCOMEND_FOR_GOLD = 30; + @Autowired + UserDao userDao; + + public void setUserDao(UserDao userDao) { + this.userDao = userDao; + } + + public void upgradeLevels() { + List users = userDao.getAll(); + for(User user : users) { + if (canUpgradeLevel(user)) { + upgradeLevel(user); + } + } + } + + protected void upgradeLevel(User user) { + user.upgradeLevel(); + userDao.update(user); + } + + public boolean canUpgradeLevel(User user) { + Level currentLevel = user.getLevel(); + switch(currentLevel) { + case BASIC: return (user.getLogin() >= MIN_LOGCOUNT_FOR_SILVER); + case SILVER: return (user.getRecommend() >= MIN_RECCOMEND_FOR_GOLD); + case GOLD: return false; + default: throw new IllegalArgumentException("Unknown Level: " + currentLevel); + } + } + + public void add(User user) { + if (user.getLevel() == null) user.setLevel(Level.BASIC); + userDao.add(user); + } +} diff --git a/src/test/java/com/ch6/UserDaoTest.java b/src/test/java/com/ch6/UserDaoTest.java new file mode 100644 index 0000000..d3f4101 --- /dev/null +++ b/src/test/java/com/ch6/UserDaoTest.java @@ -0,0 +1,170 @@ +package com.ch6; + + +import com.ch6.dao.DaoFactory6; +import com.ch6.dao.UserDao; +import com.ch6.domain.Level; +import com.ch6.domain.User; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.dao.DataAccessException; +import org.springframework.dao.DuplicateKeyException; +import org.springframework.dao.EmptyResultDataAccessException; +import org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator; +import org.springframework.jdbc.support.SQLExceptionTranslator; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import javax.sql.DataSource; +import java.sql.SQLException; +import java.util.List; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +@ExtendWith(SpringExtension.class) +@ContextConfiguration(classes = DaoFactory6.class) +public class UserDaoTest { + @Autowired + private UserDao userDao; + @Autowired + private DataSource dataSource; + + private User user1; + private User user2; + private User user3; + + @BeforeEach + public void setUp() { + this.user1 = new User("gyumee", "name1", "test1", Level.BASIC, 1, 0); + this.user2 = new User("leegw700", "name2", "test2", Level.SILVER, 55, 10); + this.user3 = new User("bumjin", "name3", "spring", Level.GOLD, 100, 40); + } + + @Test + public void addAndGet() { + userDao.deleteAll(); + assertThat(userDao.getCount(), is(0)); + + userDao.add(user1); + userDao.add(user2); + assertThat(userDao.getCount(), is(2)); + + User userget1 = userDao.get(user1.getId()); + checkSameUser(user1, userget1); + + User userget2 = userDao.get(user2.getId()); + checkSameUser(user2, userget2); + + } + + @Test + public void count() { + userDao.deleteAll(); + assertThat(userDao.getCount(), is(0)); + + userDao.add(user1); + assertThat(userDao.getCount(), is(1)); + + userDao.add(user2); + assertThat(userDao.getCount(), is(2)); + + userDao.add(user3); + assertThat(userDao.getCount(), is(3)); + } + + @Test + void getUserFailure() { + userDao.deleteAll(); + assertThat(userDao.getCount(), is(0)); + assertThrows(EmptyResultDataAccessException.class, () -> { + userDao.get("unknown_id"); + }); + } + + @Test + void getAll() { + userDao.deleteAll(); + + List users0 = userDao.getAll(); + assertThat(users0.size(), is(0)); + + userDao.add(user1); // Id: gyumee + List users1 = userDao.getAll(); + assertThat(users1.size(), is(1)); + checkSameUser(user1, users1.get(0)); + + userDao.add(user2); // Id: leegw700 + List users2 = userDao.getAll(); + assertThat(users2.size(), is(2)); + checkSameUser(user1, users2.get(0)); + checkSameUser(user2, users2.get(1)); + + userDao.add(user3); // Id: bumjin + List users3 = userDao.getAll(); + assertThat(users3.size(), is(3)); + checkSameUser(user3, users3.get(0)); + checkSameUser(user1, users3.get(1)); + checkSameUser(user2, users3.get(2)); + } + + private void checkSameUser(User user1, User user2) { + assertThat(user1.getId(), is(user2.getId())); + assertThat(user1.getName(), is(user2.getName())); + assertThat(user1.getPassword(), is(user2.getPassword())); + assertThat(user1.getLevel(), is(user2.getLevel())); + assertThat(user1.getLogin(), is(user2.getLogin())); + assertThat(user1.getRecommend(), is(user2.getRecommend())); + } + + @Test + void duplicateKey() { + userDao.deleteAll(); + + userDao.add(user1); + + // DuplicateKeyException + assertThrows(DataAccessException.class, () -> { + userDao.add(user1); + }); + } + + @Test + public void sqlExceptionTranslate() { + userDao.deleteAll(); + + try { + userDao.add(user1); + userDao.add(user2); + } + catch(DuplicateKeyException ex) { + SQLException sqlEx = (SQLException)ex.getRootCause(); + SQLExceptionTranslator set = new SQLErrorCodeSQLExceptionTranslator(this.dataSource); + + assertThat(set.translate(null, null, sqlEx), is(DuplicateKeyException.class)); + } + } + + @Test + public void update() { + userDao.deleteAll(); + + userDao.add(user1); + userDao.add(user2); + + user1.setName("이하늘"); + user1.setPassword("springno6"); + user1.setLevel(Level.GOLD); + user1.setLogin(1000); + user1.setRecommend(999); + userDao.update(user1); + + User user1update = userDao.get(user1.getId()); + checkSameUser(user1, user1update); + User user2same = userDao.get(user2.getId()); + checkSameUser(user2, user2same); + } +} diff --git a/src/test/java/com/ch6/UserServiceTest.java b/src/test/java/com/ch6/UserServiceTest.java new file mode 100644 index 0000000..d8d957e --- /dev/null +++ b/src/test/java/com/ch6/UserServiceTest.java @@ -0,0 +1,126 @@ +package com.ch6; + +import com.ch6.dao.DaoFactory6; +import com.ch6.dao.UserDao; +import com.ch6.domain.Level; +import com.ch6.domain.User; +import com.ch6.service.UserService; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import java.util.Arrays; +import java.util.List; + +import static com.ch5.service.UserLevelUpgradePolicyImpl.MIN_LOGCOUNT_FOR_SILVER; +import static com.ch5.service.UserLevelUpgradePolicyImpl.MIN_RECCOMEND_FOR_GOLD; +import static com.jayway.jsonpath.internal.path.PathCompiler.fail; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +@ExtendWith(SpringExtension.class) +@ContextConfiguration(classes = DaoFactory6.class) +public class UserServiceTest { + @Autowired + private UserService userService; + @Autowired + private UserDao userDao; + + private List users; + + @BeforeEach + public void setUp() { + users = Arrays.asList( + new User("bumjin", "박범진", "p1", Level.BASIC, MIN_LOGCOUNT_FOR_SILVER-1, 0), + new User("joytouch", "강명성", "p2", Level.BASIC, MIN_LOGCOUNT_FOR_SILVER, 0), + new User("erwins", "신승한", "p3", Level.SILVER, 60, MIN_RECCOMEND_FOR_GOLD-1), + new User("madnite1", "이상호", "p4", Level.SILVER, 60, MIN_RECCOMEND_FOR_GOLD), + new User("green", "오민규", "p5", Level.GOLD, 100, Integer.MAX_VALUE) + ); + } + + @Test + public void bean() { + assertNotNull(this.userService); + } + + @Test + public void upgradeLevels() { + userDao.deleteAll(); + for(User user : users) userDao.add(user); + + userService.upgradeLevels(); + + checkLevel(users.get(0), false); + checkLevel(users.get(1), true); + checkLevel(users.get(2), false); + checkLevel(users.get(3), true); + checkLevel(users.get(4), false); + } + + private void checkLevel(User user, boolean upgraded) { + User userUpdate = userDao.get(user.getId()); + if(upgraded) { + assertThat(userUpdate.getLevel(), is(user.getLevel().nextLevel())); + } + else { + assertThat(userUpdate.getLevel(), is(user.getLevel())); + } + } + + @Test + public void add() { + userDao.deleteAll(); + + User userWithLevel = users.get(4); + 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()); + + assertThat(userWithLevelRead.getLevel(), is(userWithLevel.getLevel())); + assertThat(userWithoutLevelRead.getLevel(), is(Level.BASIC)); + } + + static class TestUserService extends UserService { + private String id; + + private TestUserService(String id) { + this.id = id; + } + + @Override + protected void upgradeLevel(User user) { + if (user.getId().equals(this.id)) throw new TestUserServiceException(); + super.upgradeLevel(user); + } + } + + static class TestUserServiceException extends RuntimeException { } + + @Test + public void upgradeAllOrNothing() { + UserService testUserService = new TestUserService(users.get(3).getId()); + testUserService.setUserDao(this.userDao); + + userDao.deleteAll(); + for(User user : users) userDao.add(user); + + try { + testUserService.upgradeLevels(); + fail("TestUserServiceException expected"); + } + catch (TestUserServiceException ignored) { + } + + checkLevel(users.get(1), false); + } +} diff --git a/src/test/java/com/ch6/UserTest.java b/src/test/java/com/ch6/UserTest.java new file mode 100644 index 0000000..d3bee15 --- /dev/null +++ b/src/test/java/com/ch6/UserTest.java @@ -0,0 +1,48 @@ +package com.ch6; + +import com.ch5.dao.DaoFactory5; +import com.ch5.domain.Level; +import com.ch5.domain.User; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +@ExtendWith(SpringExtension.class) +@ContextConfiguration(classes = DaoFactory5.class) +public class UserTest { + User user; + + @BeforeEach + public void setUp() { + user = new User(); + } + + @Test + public void upgradeLevel() { + Level[] levels = Level.values(); + for(Level level : levels) { + if (level.nextLevel() == null) continue; + user.setLevel(level); + user.upgradeLevel(); + assertThat(user.getLevel(), is(level.nextLevel())); + } + } + + @Test + public void cannotUpgradeLevel() { + Level[] levels = Level.values(); + for(Level level : levels) { + if (level.nextLevel() != null) continue; + user.setLevel(level); + assertThrows(IllegalStateException.class, () -> { + user.upgradeLevel(); + }); + } + } +}