diff --git a/build.gradle b/build.gradle index 07d2578..89689ec 100644 --- a/build.gradle +++ b/build.gradle @@ -20,6 +20,8 @@ repositories { dependencies { implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' implementation 'org.springframework.boot:spring-boot-starter-web' + //implementation 'org.springframework.boot:spring-boot-starter-jdbc' + implementation 'org.springframework.boot:spring-boot-starter-data-jpa' runtimeOnly 'com.h2database:h2' testImplementation 'org.springframework.boot:spring-boot-starter-test' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' diff --git a/sql/ddl.sql b/sql/ddl.sql new file mode 100644 index 0000000..bf33e44 --- /dev/null +++ b/sql/ddl.sql @@ -0,0 +1,17 @@ + +create table member +( + id bigint generated by default as identity, + name varchar(255), + primary key (id) +); + +drop table if exists item CASCADE; +create table item +( + id bigint generated by default as identity, + name varchar(255), + price integer not null, + count integer not null, + primary key (id) +); \ No newline at end of file diff --git a/src/main/java/landvibe/springintro/aop/TimeTraceAop.java b/src/main/java/landvibe/springintro/aop/TimeTraceAop.java new file mode 100644 index 0000000..52cca0f --- /dev/null +++ b/src/main/java/landvibe/springintro/aop/TimeTraceAop.java @@ -0,0 +1,25 @@ +package landvibe.springintro.aop; + +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.springframework.stereotype.Component; + +@Component +@Aspect +public class TimeTraceAop { + + @Around("execution(* landvibe.springintro..*(..))") + public Object trace(ProceedingJoinPoint joinPoint) throws Throwable { + long start = System.currentTimeMillis(); + System.out.println("START : " + joinPoint.toShortString()); + + try{ + return joinPoint.proceed(); + } finally{ + long finish = System.currentTimeMillis(); + long timeMs = finish - start; + System.out.println("END : " + joinPoint.toShortString() + " " + timeMs + " ms"); + } + } +} diff --git a/src/main/java/landvibe/springintro/controller/HelloController.java b/src/main/java/landvibe/springintro/controller/HelloController.java new file mode 100644 index 0000000..7f6a90d --- /dev/null +++ b/src/main/java/landvibe/springintro/controller/HelloController.java @@ -0,0 +1,42 @@ +package landvibe.springintro.controller; + +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; + +@Controller +public class HelloController { + @GetMapping("hello") + public String hello(Model model){ + model.addAttribute("data", "hello!!"); + return "hello"; //Model을 hello.html로 넘김 + } + @GetMapping("hello-mvc") + public String helloMVC(@RequestParam(value = "name", required = false, defaultValue = "landVive") String name, Model model){ + model.addAttribute("name", name); + return "hello-template"; + } + @GetMapping("hello-string") + @ResponseBody + public String helloString(@RequestParam("name") String name){ + return "hello"+name; + } + @GetMapping("hello-api") + @ResponseBody + public Hello helloApi(@RequestParam("name") String name){ + Hello hello = new Hello(); + hello.setName(name); + return hello; + } + static class Hello{ + private String name; + public String getName() { + return name; + } + public void setName(String name) { + this.name = name; + } + } +} diff --git a/src/main/java/landvibe/springintro/controller/HomeController.java b/src/main/java/landvibe/springintro/controller/HomeController.java new file mode 100644 index 0000000..77c0686 --- /dev/null +++ b/src/main/java/landvibe/springintro/controller/HomeController.java @@ -0,0 +1,13 @@ +package landvibe.springintro.controller; + +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; + +@Controller +public class HomeController { + + @GetMapping("/") + public String home(){ + return "home"; + } +} \ No newline at end of file diff --git a/src/main/java/landvibe/springintro/controller/ItemController.java b/src/main/java/landvibe/springintro/controller/ItemController.java new file mode 100644 index 0000000..1890b1a --- /dev/null +++ b/src/main/java/landvibe/springintro/controller/ItemController.java @@ -0,0 +1,47 @@ +package landvibe.springintro.controller; + +import org.springframework.ui.Model; +import landvibe.springintro.item.controller.ItemCreateForm; +import landvibe.springintro.item.domain.Item; +import landvibe.springintro.item.service.ItemService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.PostMapping; + +import java.util.List; + +@Controller +public class ItemController { + + private final ItemService itemService; + + @Autowired + public ItemController(ItemService itemService) { + this.itemService = itemService; + } + + @GetMapping("/items/new") + public String createForm(){ + return "items/createForm"; + } + + @PostMapping("/items/new") + public String create(@ModelAttribute ItemCreateForm form){ + Item item = new Item(); + item.setName(form.getName()); + item.setPrice(form.getPrice()); + item.setCount(form.getCount()); + itemService.create(item); + + return "redirect:/"; + } + + @GetMapping("/items") + public String list(Model model){ + List items = itemService.findItems(); + model.addAttribute("items", items); + return "items/itemList"; + } +} diff --git a/src/main/java/landvibe/springintro/controller/MemberController.java b/src/main/java/landvibe/springintro/controller/MemberController.java new file mode 100644 index 0000000..40bb782 --- /dev/null +++ b/src/main/java/landvibe/springintro/controller/MemberController.java @@ -0,0 +1,46 @@ +package landvibe.springintro.controller; + +import landvibe.springintro.member.controller.MemberForm; +import org.springframework.ui.Model; +import landvibe.springintro.member.domain.Member; +import landvibe.springintro.member.service.MemberService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; + +import java.util.List; + +@Controller +public class MemberController { + + private final MemberService memberService; + + @Autowired + public MemberController(MemberService memberService) { + this.memberService = memberService; + System.out.println("memberService = " + memberService.getClass()); + } + + @GetMapping("/members/new") + public String createForm(){ + return "members/createMemberForm"; + } + + @PostMapping("/members/new") + public String create(MemberForm memberForm){ + Member member = new Member(); + member.setName(memberForm.getName()); + + memberService.join(member); + + return "redirect:/"; + } + + @GetMapping("/members") + public String list(Model model){ + List members = memberService.findMembers(); + model.addAttribute("members", members); + return "members/memberList"; + } +} diff --git a/src/main/java/landvibe/springintro/item/config/ItemConfig.java b/src/main/java/landvibe/springintro/item/config/ItemConfig.java new file mode 100644 index 0000000..27787b5 --- /dev/null +++ b/src/main/java/landvibe/springintro/item/config/ItemConfig.java @@ -0,0 +1,37 @@ +package landvibe.springintro.item.config; + + +import landvibe.springintro.item.repository.ItemRepository; +import landvibe.springintro.item.service.ItemService; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class ItemConfig { + + private final ItemRepository itemRepository; + + public ItemConfig(ItemRepository itemRepository) { + this.itemRepository = itemRepository; + } + + @Bean + public ItemService itemService(){ + return new ItemService(itemRepository); + } + /* private final DataSource dataSource; + private final EntityManager em; + + @Autowired + public ItemConfig(DataSource dataSource, EntityManager em) { + this.dataSource = dataSource; + this.em = em; + }*/ +/* @Bean + public ItemRepository itemRepository(){ + //return new MemoryItemRepository(); + //return new JdbcItemRepository(dataSource); + //return new JdbcTemplateItemRepository(dataSource); + return new JpaItemRepository(em); + }*/ +} diff --git a/src/main/java/landvibe/springintro/item/controller/ItemCreateForm.java b/src/main/java/landvibe/springintro/item/controller/ItemCreateForm.java new file mode 100644 index 0000000..b45d955 --- /dev/null +++ b/src/main/java/landvibe/springintro/item/controller/ItemCreateForm.java @@ -0,0 +1,31 @@ +package landvibe.springintro.item.controller; + +public class ItemCreateForm { + private String name; + private Integer price; + private Integer count; + + public String getName(){ + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Integer getPrice() { + return price; + } + + public void setPrice(Integer price) { + this.price = price; + } + + public Integer getCount() { + return count; + } + + public void setCount(Integer count) { + this.count = count; + } +} diff --git a/src/main/java/landvibe/springintro/item/domain/Item.java b/src/main/java/landvibe/springintro/item/domain/Item.java new file mode 100644 index 0000000..184f8dc --- /dev/null +++ b/src/main/java/landvibe/springintro/item/domain/Item.java @@ -0,0 +1,50 @@ +package landvibe.springintro.item.domain; + + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; + +@Entity +public class Item { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + private String name; + private Integer price; + private Integer count; + + public Long getId(){ + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Integer getPrice() { + return price; + } + + public void setPrice(Integer price) { + this.price = price; + } + + public Integer getCount() { + return count; + } + + public void setCount(Integer count) { + this.count = count; + } +} diff --git a/src/main/java/landvibe/springintro/item/repository/ItemRepository.java b/src/main/java/landvibe/springintro/item/repository/ItemRepository.java new file mode 100644 index 0000000..b3381d9 --- /dev/null +++ b/src/main/java/landvibe/springintro/item/repository/ItemRepository.java @@ -0,0 +1,16 @@ +package landvibe.springintro.item.repository; + +import landvibe.springintro.item.domain.Item; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Optional; + +public interface ItemRepository { + + Item save(Item item); + Optional findById(Long id); + Optional findByName(String name); + List findAll(); +} diff --git a/src/main/java/landvibe/springintro/item/repository/JdbcItemRepository.java b/src/main/java/landvibe/springintro/item/repository/JdbcItemRepository.java new file mode 100644 index 0000000..f12d48c --- /dev/null +++ b/src/main/java/landvibe/springintro/item/repository/JdbcItemRepository.java @@ -0,0 +1,155 @@ +package landvibe.springintro.item.repository; + +import landvibe.springintro.item.domain.Item; +import org.springframework.jdbc.datasource.DataSourceUtils; + +import java.sql.*; +import javax.sql.DataSource; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +public class JdbcItemRepository implements ItemRepository { + private final DataSource dataSource; + + public JdbcItemRepository(DataSource dataSource) { + this.dataSource = dataSource; + } + + @Override + public Item save(Item item) { + String sql = "insert into item(name, price, count) values(?, ?, ?)"; + Connection conn = null; + PreparedStatement pstmt = null; + ResultSet rs = null; + try{ + conn = pstmt.getConnection(); + pstmt = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS); + pstmt.setString(1, item.getName()); + pstmt.setInt(2, item.getPrice()); + pstmt.setInt(3, item.getCount()); + pstmt.executeUpdate(); + rs = pstmt.getGeneratedKeys(); + if (rs.next()) { + item.setId(rs.getLong(1)); + } else { + throw new SQLException("id 조회 실패"); + } + return item; + } catch (Exception e) { + throw new IllegalStateException(e); + } finally { + close(conn, pstmt, rs); + } + } + + @Override + public Optional findById(Long id) { + String sql = "select * from item where id = ?"; + Connection conn = null; + PreparedStatement pstmt = null; + ResultSet rs = null; + try{ + conn = pstmt.getConnection(); + pstmt = conn.prepareStatement(sql); + pstmt.setLong(1, id); + rs = pstmt.executeQuery(); + if(rs.next()){ + Item item = new Item(); + item.setId(rs.getLong("id")); + item.setName(rs.getString("name")); + item.setCount(rs.getInt("count")); + item.setPrice(rs.getInt("price")); + return Optional.of(item); + } else { + return Optional.empty(); + } + } catch (Exception e) { + throw new IllegalStateException(e); + } finally { + close(conn, pstmt, rs); + } + } + + @Override + public Optional findByName(String name) { + String sql = "select * from item where name = ?"; + Connection conn = null; + PreparedStatement pstmt = null; + ResultSet rs = null; + try{ + conn = pstmt.getConnection(); + pstmt = conn.prepareStatement(sql); + pstmt.setString(1, name); + rs = pstmt.executeQuery(); + if(rs.next()){ + Item item = new Item(); + item.setId(rs.getLong("id")); + item.setName(rs.getString("name")); + item.setCount(rs.getInt("count")); + item.setPrice(rs.getInt("price")); + return Optional.of(item); + } + return Optional.empty(); + } catch (Exception e) { + throw new IllegalStateException(e); + } finally { + close(conn, pstmt, rs); + } + } + + @Override + public List findAll() { + String sql = "select * from item"; + Connection conn = null; + PreparedStatement pstmt = null; + ResultSet rs = null; + try{ + conn = pstmt.getConnection(); + pstmt = conn.prepareStatement(sql); + rs = pstmt.executeQuery(); + List items = new ArrayList<>(); + while(rs.next()){ + Item item = new Item(); + item.setId(rs.getLong("id")); + item.setName(rs.getString("name")); + item.setCount(rs.getInt("count")); + item.setPrice(rs.getInt("price")); + items.add(item); + } + return items; + } catch (Exception e) { + throw new IllegalStateException(e); + } finally { + close(conn, pstmt, rs); + } + } + + private void close(Connection conn, PreparedStatement pstmt, ResultSet rs) { + try{ + if(rs != null){ + rs.close(); + } + } catch (SQLException e){ + e.printStackTrace(); + } + try{ + if(pstmt != null){ + pstmt.close(); + } + } catch (SQLException e){ + e.printStackTrace(); + } + try{ + if(conn != null){ + close(conn); + } + } catch (SQLException e){ + e.printStackTrace(); + } + } + + private void close(Connection conn) throws SQLException { + DataSourceUtils.releaseConnection(conn, dataSource); + } +} diff --git a/src/main/java/landvibe/springintro/item/repository/JdbcTemplateItemRepository.java b/src/main/java/landvibe/springintro/item/repository/JdbcTemplateItemRepository.java new file mode 100644 index 0000000..8287510 --- /dev/null +++ b/src/main/java/landvibe/springintro/item/repository/JdbcTemplateItemRepository.java @@ -0,0 +1,61 @@ +package landvibe.springintro.item.repository; + +import landvibe.springintro.item.domain.Item; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; +import org.springframework.jdbc.core.simple.SimpleJdbcInsert; + +import javax.sql.DataSource; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +public class JdbcTemplateItemRepository implements ItemRepository { + private final JdbcTemplate jdbcTemplate; + + public JdbcTemplateItemRepository(DataSource dataSource) { + this.jdbcTemplate = new JdbcTemplate(dataSource); + } + + @Override + public Item save(Item item) { + SimpleJdbcInsert jdbcInsert = new SimpleJdbcInsert(this.jdbcTemplate); + jdbcInsert.withTableName("item").usingGeneratedKeyColumns("id"); + Map parameters = new HashMap<>(); + parameters.put("name", item.getName()); + parameters.put("price", item.getPrice()); + parameters.put("count", item.getCount()); + Number key = jdbcInsert.executeAndReturnKey(new MapSqlParameterSource(parameters)); + item.setId(key.longValue()); + return item; + } + + @Override + public Optional findById(Long id) { + List result = jdbcTemplate.query("select * from item where id = ?",rowMaper(), id); + return result.stream().findAny(); + } + + @Override + public Optional findByName(String name) { + return Optional.empty(); + } + + @Override + public List findAll() { + return List.of(); + } + + private RowMapper rowMaper() { + return (rs, rowNum) -> { + Item item = new Item(); + item.setId(rs.getLong("id")); + item.setName(rs.getString("name")); + item.setPrice(rs.getInt("price")); + item.setCount(rs.getInt("count")); + return item; + }; + } +} diff --git a/src/main/java/landvibe/springintro/item/repository/JpaItemRepository.java b/src/main/java/landvibe/springintro/item/repository/JpaItemRepository.java new file mode 100644 index 0000000..e10da4b --- /dev/null +++ b/src/main/java/landvibe/springintro/item/repository/JpaItemRepository.java @@ -0,0 +1,42 @@ +package landvibe.springintro.item.repository; + +import jakarta.persistence.EntityManager; +import landvibe.springintro.item.domain.Item; + +import java.util.List; +import java.util.Optional; + +public class JpaItemRepository implements ItemRepository { + + private final EntityManager em; + + public JpaItemRepository(EntityManager em) { + this.em = em; + } + + @Override + public Item save(Item item) { + em.persist(item); + return item; + } + + @Override + public Optional findById(Long id) { + Item item = em.find(Item.class, id); + return Optional.ofNullable(item); + } + + @Override + public Optional findByName(String name) { + return em.createQuery("select i from Item i where i.name = : name", Item.class) + .setParameter("name", name) + .getResultList().stream() + .findAny(); + } + + @Override + public List findAll() { + return em.createQuery("select i from Item i", Item.class) + .getResultList(); + } +} diff --git a/src/main/java/landvibe/springintro/item/repository/MemoryItemRepository.java b/src/main/java/landvibe/springintro/item/repository/MemoryItemRepository.java new file mode 100644 index 0000000..bee12e6 --- /dev/null +++ b/src/main/java/landvibe/springintro/item/repository/MemoryItemRepository.java @@ -0,0 +1,42 @@ +package landvibe.springintro.item.repository; + + +import landvibe.springintro.item.domain.Item; +import org.springframework.stereotype.Repository; + +import java.util.*; + +public class MemoryItemRepository implements ItemRepository { + + private static Map store = new HashMap<>(); + private static long sequence = 0L; + + @Override + public Item save(Item item) { + item.setId(++sequence); + store.put(item.getId(), item); + return item; + } + + @Override + public Optional findById(Long id) { + Item item = store.get(id); + return Optional.ofNullable(item); + } + + @Override + public Optional findByName(String name) { + return store.values().stream() + .filter(item -> item.getName().equals(name)) + .findAny(); + } + + @Override + public List findAll() { + return new ArrayList<>(store.values()); + } + + public void clearStore(){ + store.clear(); + } +} diff --git a/src/main/java/landvibe/springintro/item/repository/SpringDataJpaItemRepository.java b/src/main/java/landvibe/springintro/item/repository/SpringDataJpaItemRepository.java new file mode 100644 index 0000000..dccdf69 --- /dev/null +++ b/src/main/java/landvibe/springintro/item/repository/SpringDataJpaItemRepository.java @@ -0,0 +1,11 @@ +package landvibe.springintro.item.repository; + +import landvibe.springintro.item.domain.Item; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface SpringDataJpaItemRepository extends JpaRepository, ItemRepository { + @Override + Optional findByName(String name); +} diff --git a/src/main/java/landvibe/springintro/item/service/ItemService.java b/src/main/java/landvibe/springintro/item/service/ItemService.java new file mode 100644 index 0000000..76525b7 --- /dev/null +++ b/src/main/java/landvibe/springintro/item/service/ItemService.java @@ -0,0 +1,36 @@ +package landvibe.springintro.item.service; + +import landvibe.springintro.item.domain.Item; +import landvibe.springintro.item.repository.ItemRepository; +import landvibe.springintro.item.repository.MemoryItemRepository; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +@Transactional +public class ItemService { + + private final ItemRepository repository; + + public ItemService(ItemRepository repository) { + this.repository = repository; + } + + public Long create(Item item){ + validateDuplicationItem(item.getName()); + repository.save(item); + return item.getId(); + } + + public List findItems(){ + return repository.findAll(); + } + + private void validateDuplicationItem(String itemName){ + repository.findByName(itemName) + .ifPresent(item -> { + throw new IllegalArgumentException("이미 존재하는 상품입니다."); + }); + } +} diff --git a/src/main/java/landvibe/springintro/member/config/SpringConfig.java b/src/main/java/landvibe/springintro/member/config/SpringConfig.java new file mode 100644 index 0000000..3b899ef --- /dev/null +++ b/src/main/java/landvibe/springintro/member/config/SpringConfig.java @@ -0,0 +1,48 @@ +package landvibe.springintro.member.config; + +import landvibe.springintro.member.repository.MemberRepository; +import landvibe.springintro.member.service.MemberService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class SpringConfig { + + private final MemberRepository memberRepository; + + @Autowired + public SpringConfig(MemberRepository memberRepository) { + this.memberRepository = memberRepository; + } + /* private EntityManager em; + + @Autowired + public SpringConfig(EntityManager em) { + this.em = em; + }*/ + /*private DataSource dataSource; + + @Autowired + public SpringConfig(DataSource dataSource) { + this.dataSource = dataSource; + }*/ + + @Bean + public MemberService memberService() { + return new MemberService(memberRepository); + } + +/* @Bean + public TimeTraceAop timeTraceAop(){ + return new TimeTraceAop(); + }*/ +// @Bean +// public MemberRepository memberRepository() { + //return new MemoryMemberRepository(); + //return new JdbcMemberRepository(dataSource); + //return new JdbcTemplateMemberRepository(dataSource); + //return new JpaMemberRepository(em); + +// } +} diff --git a/src/main/java/landvibe/springintro/member/controller/MemberForm.java b/src/main/java/landvibe/springintro/member/controller/MemberForm.java new file mode 100644 index 0000000..eb135b0 --- /dev/null +++ b/src/main/java/landvibe/springintro/member/controller/MemberForm.java @@ -0,0 +1,12 @@ +package landvibe.springintro.member.controller; + +public class MemberForm { + private String name; + + public String getName(){ + return name; + } + public void setName(String name){ + this.name = name; + } +} diff --git a/src/main/java/landvibe/springintro/member/domain/Member.java b/src/main/java/landvibe/springintro/member/domain/Member.java new file mode 100644 index 0000000..5303565 --- /dev/null +++ b/src/main/java/landvibe/springintro/member/domain/Member.java @@ -0,0 +1,30 @@ +package landvibe.springintro.member.domain; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; + +@Entity +public class Member { + + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + private String name; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/src/main/java/landvibe/springintro/member/repository/JdbcMemberRepository.java b/src/main/java/landvibe/springintro/member/repository/JdbcMemberRepository.java new file mode 100644 index 0000000..bdc5ec1 --- /dev/null +++ b/src/main/java/landvibe/springintro/member/repository/JdbcMemberRepository.java @@ -0,0 +1,160 @@ +package landvibe.springintro.member.repository; + +import landvibe.springintro.member.domain.Member; +import org.springframework.jdbc.datasource.DataSourceUtils; + +import javax.sql.DataSource; +import java.sql.*; +import java.util.*; + + +public class JdbcMemberRepository implements MemberRepository { + + private final DataSource dataSource; + + public JdbcMemberRepository(DataSource dataSource) { + this.dataSource = dataSource; + } + + @Override + public Member save(Member member) { + String sql = "insert into member(name) values(?)"; + + Connection conn = null; + PreparedStatement pstmt = null; + ResultSet rs = null; + + try { + conn = getConnection(); + pstmt = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS); + pstmt.setString(1, member.getName()); + pstmt.executeUpdate(); + rs = pstmt.getGeneratedKeys(); + + if (rs.next()) { + member.setId(rs.getLong(1)); + } else { + throw new SQLException("id 조회 실패"); + } + return member; + } catch (Exception e) { + throw new IllegalStateException(e); + } finally { + close(conn, pstmt, rs); + } + } + + @Override + public Optional findById(Long id) { + String sql = "select * from member where id = ?"; + Connection conn = null; + PreparedStatement pstmt = null; + ResultSet rs = null; + + try { + conn = getConnection(); + pstmt = conn.prepareStatement(sql); + pstmt.setLong(1, id); + rs = pstmt.executeQuery(); + + if (rs.next()) { + Member member = new Member(); + member.setId(rs.getLong("id")); + member.setName(rs.getString("name")); + return Optional.of(member); + } else { + return Optional.empty(); + } + }catch(Exception e){ + throw new IllegalStateException(e); + } finally{ + close(conn, pstmt, rs); + } + } + + + @Override + public List findAll () { + String sql = "select * from member"; + Connection conn = null; + PreparedStatement pstmt = null; + ResultSet rs = null; + try { + conn = getConnection(); + pstmt = conn.prepareStatement(sql); + rs = pstmt.executeQuery(); + List members = new ArrayList<>(); + while (rs.next()) { + Member member = new Member(); + member.setId(rs.getLong("id")); + member.setName(rs.getString("name")); + members.add(member); + } + return members; + } catch (Exception e) { + throw new IllegalStateException(e); + } finally { + close(conn, pstmt, rs); + } + } + + @Override + public Optional findByName (String name){ + String sql = "select * from member where name =?"; + + Connection conn = null; + PreparedStatement pstmt = null; + ResultSet rs = null; + + try { + conn = getConnection(); + pstmt = conn.prepareStatement(sql); + pstmt.setString(1, name); + + rs = pstmt.executeQuery(); + + if (rs.next()) { + Member member = new Member(); + member.setId(rs.getLong("id")); + member.setName(rs.getString("name")); + return Optional.of(member); + } + return Optional.empty(); + } catch (Exception e) { + throw new IllegalStateException(e); + } finally { + close(conn, pstmt, rs); + } + } + + private Connection getConnection () { + return DataSourceUtils.getConnection(dataSource); + } + + private void close (Connection conn, PreparedStatement pstmt, ResultSet rs){ + try { + if (rs != null) { + rs.close(); + } + } catch (SQLException e) { + e.printStackTrace(); + } + try { + if (pstmt != null) { + pstmt.close(); + } + } catch (SQLException e) { + e.printStackTrace(); + } + try { + if (conn != null) { + close(conn); + } + } catch (SQLException e) { + e.printStackTrace(); + } + } + private void close(Connection conn) throws SQLException { + DataSourceUtils.releaseConnection(conn, dataSource); + } +} diff --git a/src/main/java/landvibe/springintro/member/repository/JdbcTemplateMemberRepository.java b/src/main/java/landvibe/springintro/member/repository/JdbcTemplateMemberRepository.java new file mode 100644 index 0000000..46a7772 --- /dev/null +++ b/src/main/java/landvibe/springintro/member/repository/JdbcTemplateMemberRepository.java @@ -0,0 +1,61 @@ +package landvibe.springintro.member.repository; + +import landvibe.springintro.member.domain.Member; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; +import org.springframework.jdbc.core.simple.SimpleJdbcInsert; + +import javax.sql.DataSource; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +public class JdbcTemplateMemberRepository implements MemberRepository { + + private final JdbcTemplate jdbcTemplate; + + public JdbcTemplateMemberRepository(DataSource dataSource) { + jdbcTemplate = new JdbcTemplate(dataSource); + } + + @Override + public Member save(Member member) { + SimpleJdbcInsert jdbcInsert = new SimpleJdbcInsert(jdbcTemplate); + jdbcInsert.withTableName("member").usingGeneratedKeyColumns("id"); + + Map parameters = new HashMap<>(); + parameters.put("name", member.getName()); + + Number key = jdbcInsert.executeAndReturnKey(new MapSqlParameterSource(parameters)); + member.setId(key.longValue()); + return member; + } + + @Override + public Optional findById(Long id) { + List result = jdbcTemplate.query("select * from member where id=?", memberRowMapper(), id); + return result.stream().findAny(); + } + + @Override + public Optional findByName(String name) { + List result = jdbcTemplate.query("select * from member where name = ?", memberRowMapper(), name); + return result.stream().findAny(); + } + + @Override + public List findAll() { + return jdbcTemplate.query("select * from member", memberRowMapper()); + } + + private RowMapper memberRowMapper() { + return (rs, rowNum) -> { + Member member = new Member(); + member.setId(rs.getLong("id")); + member.setName(rs.getString("name")); + return member; + }; + } +} diff --git a/src/main/java/landvibe/springintro/member/repository/JpaMemberRepository.java b/src/main/java/landvibe/springintro/member/repository/JpaMemberRepository.java new file mode 100644 index 0000000..8ed506a --- /dev/null +++ b/src/main/java/landvibe/springintro/member/repository/JpaMemberRepository.java @@ -0,0 +1,43 @@ +package landvibe.springintro.member.repository; + +import jakarta.persistence.EntityManager; +import landvibe.springintro.member.domain.Member; + +import java.util.List; +import java.util.Optional; + +public class JpaMemberRepository implements MemberRepository { + + private EntityManager em; + + public JpaMemberRepository(EntityManager em) { + this.em = em; + } + + @Override + public Member save(Member member) { + em.persist(member); + return member; + } + + @Override + public Optional findById(Long id) { + Member member = em.find(Member.class, id); + return Optional.ofNullable(member); + } + + @Override + public Optional findByName(String name) { + List result = em.createQuery("select m from Member m where m.name = :name", Member.class) + .setParameter("name", name) + .getResultList(); + + return result.stream().findAny(); + } + + @Override + public List findAll() { + return em.createQuery("select m from Member m", Member.class) + .getResultList(); + } +} diff --git a/src/main/java/landvibe/springintro/member/repository/MemberRepository.java b/src/main/java/landvibe/springintro/member/repository/MemberRepository.java new file mode 100644 index 0000000..2c24594 --- /dev/null +++ b/src/main/java/landvibe/springintro/member/repository/MemberRepository.java @@ -0,0 +1,14 @@ +package landvibe.springintro.member.repository; + +import landvibe.springintro.member.domain.Member; + +import java.util.List; +import java.util.Optional; + +public interface MemberRepository { + Member save(Member member); + Optional findById(Long id); + Optional findByName(String name); + List findAll(); + +} diff --git a/src/main/java/landvibe/springintro/member/repository/MemoryMemberRepository.java b/src/main/java/landvibe/springintro/member/repository/MemoryMemberRepository.java new file mode 100644 index 0000000..e6b6d8a --- /dev/null +++ b/src/main/java/landvibe/springintro/member/repository/MemoryMemberRepository.java @@ -0,0 +1,39 @@ +package landvibe.springintro.member.repository; + +import landvibe.springintro.member.domain.Member; + +import java.util.*; + +public class MemoryMemberRepository implements MemberRepository { + private static Map store = new HashMap<>(); + private static long sequence = 0L; + + @Override + public Member save(Member member) { + member.setId(++sequence); + store.put(member.getId(), member); + return member; + } + + @Override + public Optional findById(Long id) { + return Optional.ofNullable(store.get(id)); + } + + @Override + public List findAll() { + return new ArrayList<>(store.values()); + } + + @Override + public Optional findByName(String name) { + return store.values().stream() + .filter(member -> member.getName().equals(name)) + .findAny(); + } + + public void clearStore(){ + store.clear(); + } + +} \ No newline at end of file diff --git a/src/main/java/landvibe/springintro/member/repository/SpringDataJpaMemberRepository.java b/src/main/java/landvibe/springintro/member/repository/SpringDataJpaMemberRepository.java new file mode 100644 index 0000000..ab12ff0 --- /dev/null +++ b/src/main/java/landvibe/springintro/member/repository/SpringDataJpaMemberRepository.java @@ -0,0 +1,12 @@ +package landvibe.springintro.member.repository; + +import landvibe.springintro.member.domain.Member; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface SpringDataJpaMemberRepository extends JpaRepository, MemberRepository { + + @Override + Optional findByName(String name); +} diff --git a/src/main/java/landvibe/springintro/member/service/MemberService.java b/src/main/java/landvibe/springintro/member/service/MemberService.java new file mode 100644 index 0000000..5d17a11 --- /dev/null +++ b/src/main/java/landvibe/springintro/member/service/MemberService.java @@ -0,0 +1,48 @@ +package landvibe.springintro.member.service; + +import landvibe.springintro.member.domain.Member; +import landvibe.springintro.member.repository.MemberRepository; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.Optional; + +@Transactional +public class MemberService { + + private final MemberRepository memberRepository; + + + public MemberService(MemberRepository memberRepository) { + this.memberRepository = memberRepository; + } + + /** + * 회원 가입 + */ + + public Long join(Member member) { + //같은 이름이 있는 중복 회원X + validateDuplicateMember(member); //중복회원 검증 + memberRepository.save(member); + return member.getId(); + } + + private void validateDuplicateMember(Member member) { + memberRepository.findByName(member.getName()) + .ifPresent(m -> { + throw new IllegalStateException("이미 존재하는 회원입니다."); + }); + } + + /** + * 전체 회원 조회 + */ + public List findMembers() { + return memberRepository.findAll(); + } + + public Optional findOne(Long memberId) { + return memberRepository.findById(memberId); + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index e7c1e8f..47d48fa 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1 +1,6 @@ spring.application.name=springintro +spring.datasource.url=jdbc:h2:tcp://localhost/~/test +spring.datasource.driver-class-name=org.h2.Driver +spring.datasource.username=sa +spring.jpa.show-sql=true +spring.jpa.hibernate.ddl-auto=none \ No newline at end of file diff --git a/src/main/resources/templates/fragments/header.html b/src/main/resources/templates/fragments/header.html index 33dff78..729bfa5 100755 --- a/src/main/resources/templates/fragments/header.html +++ b/src/main/resources/templates/fragments/header.html @@ -10,6 +10,10 @@ integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous"> - + + + + + LANDVIBE diff --git a/src/test/java/landvibe/springintro/item/repository/MemoryItemRepositoryTest.java b/src/test/java/landvibe/springintro/item/repository/MemoryItemRepositoryTest.java new file mode 100644 index 0000000..9e0eb7d --- /dev/null +++ b/src/test/java/landvibe/springintro/item/repository/MemoryItemRepositoryTest.java @@ -0,0 +1,63 @@ +package landvibe.springintro.item.repository; + +import landvibe.springintro.item.domain.Item; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.assertj.core.api.Assertions.*; + +class MemoryItemRepositoryTest { + + MemoryItemRepository repository = new MemoryItemRepository(); + + @AfterEach + void afterEach(){ + repository.clearStore(); + } + + @Test + void save(){ + + Item item = createItem("과자", 10, 1000); + + repository.save(item); + + Item foundItem = repository.findById(item.getId()).get(); + assertThat(foundItem).isEqualTo(item); + } + + @Test + void findByName(){ + + Item 눈을감자 = createItem("눈을감자", 10, 1000); + Item 프링글스 = createItem("프링글스", 10, 1000); + repository.save(눈을감자); + repository.save(프링글스); + + Item foundItem = repository.findByName("눈을감자").get(); + + assertThat(foundItem).isEqualTo(눈을감자); + } + + @Test + void findAll(){ + + Item 눈을감자 = createItem("눈을감자", 10, 1000); + Item 프링글스 = createItem("프링글스", 10, 1000); + repository.save(눈을감자); + repository.save(프링글스); + + List foundItems = repository.findAll(); + assertThat(foundItems).contains(눈을감자, 프링글스); + } + + private static Item createItem(String name, int price, int count) { + Item item = new Item(); + item.setName(name); + item.setPrice(price); + item.setCount(count); + return item; + } +} \ No newline at end of file diff --git a/src/test/java/landvibe/springintro/item/service/ItemServiceIntegrationTest.java b/src/test/java/landvibe/springintro/item/service/ItemServiceIntegrationTest.java new file mode 100644 index 0000000..fa76cfc --- /dev/null +++ b/src/test/java/landvibe/springintro/item/service/ItemServiceIntegrationTest.java @@ -0,0 +1,54 @@ +package landvibe.springintro.item.service; + +import landvibe.springintro.item.domain.Item; +import landvibe.springintro.item.repository.ItemRepository; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.transaction.annotation.Transactional; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.*; + +@SpringBootTest +@Transactional +public class ItemServiceIntegrationTest { + + @Autowired + ItemService service; + @Autowired + ItemRepository repository; + + @Test + public void 상품생성() throws Exception { + + Item item = createItem("눈을감자", 10, 1000); + + Long id = service.create(item); + + Item foundItem = repository.findById(id).get(); + assertThat(foundItem.getName()).isEqualTo(item.getName()); + assertThat(foundItem.getPrice()).isEqualTo(item.getPrice()); + assertThat(foundItem.getCount()).isEqualTo(item.getCount()); + } + + @Test + void 중복이름_상품예외(){ + Item item = createItem("눈을감자", 10, 1000); + Item duplicationItem = createItem("눈을감자", 10, 1000); + service.create(item); + + IllegalArgumentException ex = + assertThrows(IllegalArgumentException.class, + () -> service.create(duplicationItem)); + assertThat(ex.getMessage()).isEqualTo("이미 존재하는 상품입니다."); + } + + private static Item createItem(String name, int price, int count) { + Item item = new Item(); + item.setName(name); + item.setPrice(price); + item.setCount(count); + return item; + } +} diff --git a/src/test/java/landvibe/springintro/item/service/ItemServiceTest.java b/src/test/java/landvibe/springintro/item/service/ItemServiceTest.java new file mode 100644 index 0000000..140a4a5 --- /dev/null +++ b/src/test/java/landvibe/springintro/item/service/ItemServiceTest.java @@ -0,0 +1,59 @@ +package landvibe.springintro.item.service; + +import landvibe.springintro.item.domain.Item; +import landvibe.springintro.item.repository.MemoryItemRepository; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.thymeleaf.standard.expression.Each; + +import static org.assertj.core.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.*; + +class ItemServiceTest { + + ItemService service; + MemoryItemRepository repository; + + @BeforeEach + void setUp() { + repository = new MemoryItemRepository(); + service = new ItemService(repository); + } + + @AfterEach + void tearDown(){ + repository.clearStore(); + } + + @Test + public void 상품생성() throws Exception { + Item item = createItem("눈을감자", 10, 1000); + + Long id = service.create(item); + + Item foundItem = repository.findById(id).get(); + assertThat(foundItem).isEqualTo(item); + } + + @Test + void 중복이름_상품예외(){ + Item item = createItem("눈을감자", 10, 1000); + Item duplicationItem = createItem("눈을감자", 10, 1000); + service.create(item); + + IllegalArgumentException ex = + assertThrows(IllegalArgumentException.class, + () -> service.create(duplicationItem)); + assertThat(ex.getMessage()).isEqualTo("이미 존재하는 상품입니다."); + } + + private static Item createItem(String name, int count, int price) { + Item item = new Item(); + item.setName(name); + item.setCount(count); + item.setPrice(price); + return item; + } + +} \ No newline at end of file diff --git a/src/test/java/landvibe/springintro/member/repository/MemoryMemberReposiotryTest.java b/src/test/java/landvibe/springintro/member/repository/MemoryMemberReposiotryTest.java new file mode 100644 index 0000000..f0cacd1 --- /dev/null +++ b/src/test/java/landvibe/springintro/member/repository/MemoryMemberReposiotryTest.java @@ -0,0 +1,59 @@ +package landvibe.springintro.member.repository; + +import landvibe.springintro.member.domain.Member; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +public class MemoryMemberReposiotryTest { + + MemoryMemberRepository repository = new MemoryMemberRepository(); + + @AfterEach + public void afterEach(){ + repository.clearStore(); + } + + @Test + public void save(){ + Member member = new Member(); + member.setName("spring"); + + repository.save(member); + + Member result = repository.findById(member.getId()).get(); + assertThat(member).isEqualTo(result); + } + @Test + public void findByName(){ + Member member1 = new Member(); + member1.setName("spring1"); + repository.save(member1); + + Member member2 = new Member(); + member2.setName("spring2"); + repository.save(member2); + + repository.findByName("spring1"); + + Member result = repository.findByName("spring1").get(); + assertThat(result).isEqualTo(member1); + + } + @Test + public void findAll(){ + Member member1 = new Member(); + member1.setName("spring1"); + repository.save(member1); + + Member member2 = new Member(); + member2.setName("spring2"); + repository.save(member2); + + List result = repository.findAll(); + assertThat(result.size()).isEqualTo(2); + } +} diff --git a/src/test/java/landvibe/springintro/member/service/MemberServiceIntegrationTest.java b/src/test/java/landvibe/springintro/member/service/MemberServiceIntegrationTest.java new file mode 100644 index 0000000..810e4cc --- /dev/null +++ b/src/test/java/landvibe/springintro/member/service/MemberServiceIntegrationTest.java @@ -0,0 +1,58 @@ +package landvibe.springintro.member.service; + +import landvibe.springintro.member.domain.Member; +import landvibe.springintro.member.repository.MemberRepository; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.transaction.annotation.Transactional; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +@SpringBootTest +@Transactional +public class MemberServiceIntegrationTest { + + + @Autowired MemberService memberService; + @Autowired MemberRepository memberRepository; + + @Test + void 회원가입() throws Exception { + //given + Member member = new Member(); + member.setName("hello"); + + //when + Long saveId = memberService.join(member); + + //then + Member findMember = memberRepository.findById(saveId).get(); + assertEquals(member.getName(), findMember.getName()); + } + + @Test + public void 중복_회원_예외() throws Exception { + //given + Member member1 = new Member(); + member1.setName("spring"); + + Member member2 = new Member(); + member2.setName("spring"); + + //when + memberService.join(member1); + IllegalStateException e = assertThrows(IllegalStateException.class, () -> memberService.join(member2)); + assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다."); +/* try { + memberService.join(member2); + fail(); + }catch(IllegalStateException e){ + assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다."); + }*/ + //then + } + +} diff --git a/src/test/java/landvibe/springintro/member/service/MemberServiceTest.java b/src/test/java/landvibe/springintro/member/service/MemberServiceTest.java new file mode 100644 index 0000000..1b30628 --- /dev/null +++ b/src/test/java/landvibe/springintro/member/service/MemberServiceTest.java @@ -0,0 +1,72 @@ +package landvibe.springintro.member.service; + + +import landvibe.springintro.member.domain.Member; +import landvibe.springintro.member.repository.MemoryMemberRepository; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class MemberServiceTest { + + MemberService memberService; + MemoryMemberRepository memberRepository; + + @BeforeEach + public void beforeEach(){ + memberRepository = new MemoryMemberRepository(); + memberService = new MemberService(memberRepository); + } + + @AfterEach + public void afterEach(){ + memberRepository.clearStore(); + } + + @Test + void 회원가입() { + //given + Member member = new Member(); + member.setName("spring"); + + //when + Long saveId = memberService.join(member); + + //then + Member findMember = memberService.findOne(saveId).get(); + assertThat(member.getName()).isEqualTo(findMember.getName()); + } + + @Test + public void 중복_회원_예외(){ + //given + Member member1 = new Member(); + member1.setName("spring"); + + Member member2 = new Member(); + member2.setName("spring"); + + //when + memberService.join(member1); + IllegalStateException e = assertThrows(IllegalStateException.class, () -> memberService.join(member2)); + assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다."); +/* try { + memberService.join(member2); + fail(); + }catch(IllegalStateException e){ + assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다."); + }*/ + //then + } + + @Test + void findMembers() { + } + + @Test + void findOne() { + } +} \ No newline at end of file