🔷 Spring in Action - 3.데이터로 작업하기_ 3.1 JDBC를 사용해서 데이터 읽고 쓰기
✔ 3.1 JDBC를 사용해서 데이터 읽고 쓰기
- 스프링의 JDBC 지원은 JdbcTemplate 클래스에 기반을 둔다
=> JdbcTemplate은 JDBC를 사용할 때 요구되는 모든 형식적이고 상투적인 코드 없이 개발자가 관계형 DB에 대한 SQL연산을 수행할 수 있는 방법을 제공한다
-
JdbcTemplate을 사용해 DB 쿼리 코드
package tacos.data; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Repository; import tacos.Ingredient; import java.sql.ResultSet; import java.sql.SQLException; // @Repository : 스프링 컴포넌트 검색에서 이 클래스를 자동으로 찾아 스프링 app 컨텍스트의 빈으로 생성해 준다 @Repository public class JdbcIngredientRepository implements IngredientRepository{ private JdbcTemplate jdbc; @Override // findById() : 하나의 Ingredient 객체 반환 => queryForObject() 사용 // 첫번째 인자 : SQL // 두번째 인자 : 스프링의 RowMapper 인터페이스를 구현한 mapRowToIngredient 메서드 // 세번째 인자 : 검색할 행의 id(식자재 id) 전달 => 첫번째 인자로 전달된 SQL에 있는 ? 대신 교체되어 쿼리에 사용된다 public Ingredient findById(String id) { return jdbc.queryForObject("select id, name, type from Ingredient where id=?", this::mapRowToIngredient,id); } // mapRowToIngredient() : 쿼리로 생성된 결과 세트(Resultset 객체)의 행(row) 개수만큼 호출되며 // 결과 세트의 모든 행을 각각 객체(Ingredient)로 생성하고 List에 저장한 후 반환 private Ingredient mapRowToIngredient(ResultSet rs, int rowNum) throws SQLException { return new Ingredient( rs.getString("id"), rs.getString("name"), Ingredient.Type.valueOf(rs.getString("type"))); } }
- 위 코드에는 명령문이나 DB 연결 객체를 생성하는 코드, 메서드 실행 후 객체를 클린업하는 코드 가가 없고 예외를 처리하는 코드도 없다 => 쿼리를 수행하고 그 결과를 Imgredient 객체로 생성하는 것에 초점을 두는 코드만 존재 - JdbcTemplate을 사용해 타코 클라우드 애플리케이션의 데이터를 저장하고 읽는데 필요한 코드의 일부분 이다
✔ 3.1.1 퍼시스턴스를 고려한 도메인 객체 수정하기
-
ID와 타임스탬프 필드 Taco클래스에 추가 코드
package tacos; import java.util.Date; @Data public class Taco { private Long id; private Date createdAt; }
-
ID와 타임스탬프 필드 Order클래스에 추가 코드
package tacos; import java.util.Date; @Data public class Order { private Long id; private Date placedAt; }
- 타코(Taco) 객체가 언제 생성되었는지, 주문(Order)이 언제 되었는지 확인하는 필드 추가
✔ 3.1.2 JdbcTemplate 사용하기
🎈 JDBC 리퍼지터리 정의하기
- DB의 모든 식자재 데이터를 쿼리하여 Imgredient 객체 컬렉션(List)에 넣어야 한다
- id를 사용해 하나의 Imgredient를 쿼리해야 한다
- Imgredient 객체를 DB에 저장해야 한다
-
ImtredientRepository 인터페이스에 위의 세가지 연산 추가 코드
package tacos.data; import tacos.Ingredient; public interface IngredientRepository { // DB의 모든 식자재 데이터를 쿼리하여 Ingredient 객체 컬렉션(list)에 넣어야한다 Iterable<Ingredient> findAll(); // id를 사용해 하나의 Ingredient를 쿼리해야 한다 Ingredient findById(String id); // Ingredient 객체를 DB에 저장해야 한다 Ingredient save(Ingredient ingredient); }
-
JdbcTemplate를 사용하는 식자재 리퍼지터리 시작 코드
package tacos.data; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Repository; import tacos.Ingredient; // @Repository : 스프링 컴포넌트 검색에서 이 클래스를 자동으로 찾아 스프링 app 컨텍스트의 빈으로 생성해 준다 @Repository public class JdbcIngredientRepository { private JdbcTemplate jdbc; @Autowired // JdbcIngredientRepository 빈이 생성되면 @Autowired 를 통해 스프링이 해당 빈을 JdbcTemplate 에 연결 한다 // JdbcIngredientRepository(JdbcTemplate jdbc) : JdbcTemplate 참조를 인스턴스 변수에 저장 // => DB의 데이터를 쿼리, 추가하기 위해 다른 메서드에 사용 된다 public JdbcIngredientRepository(JdbcTemplate jdbc){ this.jdbc = jdbc; } }
-
JdbcTemplate을 사용해 DB 쿼리 코드
package tacos.data; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Repository; import tacos.Ingredient; import java.sql.ResultSet; import java.sql.SQLException; // @Repository : 스프링 컴포넌트 검색에서 이 클래스를 자동으로 찾아 스프링 app 컨텍스트의 빈으로 생성해 준다 @Repository public class JdbcIngredientRepository implements IngredientRepository{ private JdbcTemplate jdbc; @Autowired // JdbcIngredientRepository 빈이 생성되면 @Autowired 를 통해 스프링이 해당 빈을 JdbcTemplate 에 연결 한다 // JdbcIngredientRepository(JdbcTemplate jdbc) : JdbcTemplate 참조를 인스턴스 변수에 저장 // => DB의 데이터를 쿼리, 추가하기 위해 다른 메서드에 사용 된다 public JdbcIngredientRepository(JdbcTemplate jdbc){ this.jdbc = jdbc; } @Override // findAll() : 객체가 저장된 컬렉션을 반환하는 메서드 public Iterable<Ingredient> findAll() { // query() : 두개의 인자를 받는다 // 첫번째 인자 : SQL // 두번째 인자 : 스프링의 RowMapper 인터페이스를 구현한 mapRowToIngredient 메서드 // query() 에서는 해당 쿼리에서 요구하는 매개변수들의 내역을 마지막 인자로 받을 수 있다 return jdbc.query("select id, name, type from Ingredient", this::mapRowToIngredient); } @Override // findById() : 하나의 Ingredient 객체 반환 => queryForObject() 사용 // 첫번째 인자 : SQL // 두번째 인자 : 스프링의 RowMapper 인터페이스를 구현한 mapRowToIngredient 메서드 // 세번째 인자 : 검색할 행의 id(식자재 id) 전달 => 첫번째 인자로 전달된 SQL에 있는 ? 대신 교체되어 쿼리에 사용된다 public Ingredient findById(String id) { return jdbc.queryForObject("select id, name, type from Ingredient where id=?", this::mapRowToIngredient,id); } // mapRowToIngredient() : 쿼리로 생성된 결과 세트(Resultset 객체)의 행(row) 개수만큼 호출되며 // 결과 세트의 모든 행을 각각 객체(Ingredient)로 생성하고 List에 저장한 후 반환 private Ingredient mapRowToIngredient(ResultSet rs, int rowNum) throws SQLException { return new Ingredient( rs.getString("id"), rs.getString("name"), Ingredient.Type.valueOf(rs.getString("type"))); } }
- ❓ Iterable 는 반복문 인가? => 반복문이다 -> 컬렉션에서 List, Set, Map Iterator의 상위클래스 이다
-
JdbcTemplate을 사용해 DB에 데이터 추가 코드
// @Repository : 스프링 컴포넌트 검색에서 이 클래스를 자동으로 찾아 스프링 app 컨텍스트의 빈으로 생성해 준다 @Repository public class JdbcIngredientRepository implements IngredientRepository{ // 데이터 추가 @Override public Ingredient save(Ingredient ingredient) { // update() : DB에 데이터를 추가,변경하는 어떤 쿼리에도 사용될 수 있다 jdbc.update("inwert into Ingredient (id, name, type) values (?,?,?)", ingredient.getId(), ingredient.getName(), ingredient.getType().toString()); return ingredient; } }
-
컨트롤러에 리퍼지터리 주입후 사용 코드
package tacos.web; import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.beans.factory.annotation.Autowired; import lombok.extern.slf4j.Slf4j; import tacos.Taco; import tacos.Order; import tacos.Ingredient; import tacos.Ingredient.Type; import tacos.data.IngredientRepository; import javax.validation.Valid; import org.springframework.validation.Errors; import tacos.data.TacoRepository; @Slf4j // @Controller : 해당 클래스가 컨트롤러로 식별되게 하며 컴포넌트 검색을 해야한다는 것을 나타냄 // => 스프링이 해당클래스를 찾은후 스프링 APP컴텍스트의 빈(Bean)으로 해당 클래스의 인스턴스를 자동 생성한다 @Controller // @RequestMapping : 해당 클래스가 처리하는 요청의 종류 // 요청 경로가 disign 인 HTTP GET 요청 처리 @RequestMapping("/design") public class DesignTacoController { private final IngredientRepository ingredientRepo; // TacoRepository를 주입하여 타코 저장시 사용 private TacoRepository tacoRepo; @Autowired // showDesign(), processDesign() 메서드에서 사용 할 수 있도록 두 인자 모두 인스턴스 변수에 저장한다 public DesignTacoController(IngredientRepository ingredientRepo) { this.ingredientRepo = ingredientRepo; } // @GetMapping : /disign의 HTTP GET 요청이 수신될 때 그 요청을 처리하기 위해 showDesignForm() 메서드가 호출 되는것을 나타낸다 @GetMapping public String showDesignForm(Model model){ // Ingredient 객체 저장하는 List 생성 // jdbc 연결때문에 삭제 /* List<Ingredient> ingredients = Arrays.asList( new Ingredient("FLTO", "Flour Tortilla", Type.WRAP), new Ingredient("COTO", "Corn Tortilla", Type.WRAP), new Ingredient("GRBF", "Ground Beef", Type.PROTEIN), new Ingredient("CARN", "Carnitas", Type.PROTEIN), new Ingredient("TMTO", "Diced Tomatoes", Type.VEGGIES), new Ingredient("LETC", "Lettuce", Type.VEGGIES), new Ingredient("CHED", "Cheddar", Type.CHEESE), new Ingredient("JACK", "Monterrey Jack", Type.CHEESE), new Ingredient("SLSA", "Salsa", Type.SAUCE), new Ingredient("SRCR", "Sour Cream", Type.SAUCE) );*/ // 식자재 내역 생성 => 식자재의 내역(유형)을 List 에서 필터링(filterByType 메서드) 한 후 // showDesignForm()의 인자로 전달되는 Model 객체의 속성으로 추가한다 // Model : 컨트롤러와 데이터를 보여주는 뷰 사이에서 데이터를 운반하는 객체 // 위의 하드코딩 했던 Ingredient 객체의 List 대신 DB로부터 읽은 데이터로 생성한 Ingredient 객체의 List를 제공 할 수 있다 List<Ingredient> ingredients = new ArrayList<>(); ingredientRepo.findAll().forEach(i -> ingredients.add(i)); Type[] types = Type.values(); for(Type type : types){ model.addAttribute(type.toString().toLowerCase(), filterByType(ingredients, type)); } model.addAttribute("taco",new Taco()); // design 을 리턴 한다 => 모델 데이터를 브라우저에 나타나는 데 사용될 뷰의 논리적인 이름 return "design"; } }
✔ 3.1.3 스키마 정의 하고 데이터 추가하기
- 테이블 만들기 -> Taco_Order, Taco_Order_Tacos, Taco, Taco_Ingredients, Ingredient
- Ingredient : 식자재 정보 저장
- Taco : 사용자가 식자재 선택하여 생성한 타코 디자인에 관한 정보 저장
- Taco_Ingredients : 테이블간의 관계를 나타낸다
=> Taco 테이블의 각 행에 대해 하나 이상의 행(타코를 식자재와 연관시키는)을 포함한다
- Taco_Order : 주문 정보 저장
- Taco_Order_Tacos : Taco_Order와 Taco 테이블간의 관계를 나타낸다
=> Taco_Order와 테이블의 각행에 대해 하나 이상의 행(주문을 타코와 연관시키는)을 포함한다
-
타코 클라우드 스키마 정의 SQL
create table if not exists Ingredient ( id varchar(4) not null, name varchar(25) not null, type varchar(10) not null ); create table if not exists Taco ( id identity, name varchar(50) not null, createdAt timestamp not null ); create table if not exists Taco_Ingredients ( taco bigint not null, ingredient varchar(4) not null ); alter table Taco_Ingredients add foreign key (taco) references Taco(id); alter table Taco_Ingredients add foreign key (ingredient) references Ingredient(id); create table if not exists Taco_Order ( id identity, deliveryName varchar(50) not null, deliveryStreet varchar(50) not null, deliveryCity varchar(50) not null, deliveryState varchar(2) not null, deliveryZip varchar(10) not null, ccNumber varchar(16) not null, ccExpiration varchar(5) not null, ccCVV varchar(3) not null, placedAt timestamp not null ); create table if not exists Taco_Order_Tacos ( tacoOrder bigint not null, taco bigint not null ); alter table Taco_Order_Tacos add foreign key (tacoOrder) references Taco_Order(id); alter table Taco_Order_Tacos add foreign key (taco) references Taco(id);
✔ 3.1.4 타코와 주문 데이터 추가하기
- JdbcTemplate을 사용해 데이터 저장하는 방법 2가지
1. 직접 update() 메서드 사용
2. SimpleJdbcInsert 래퍼 클래스 사용
-
Taco 객체를 저장하기 위한 인터페이스 정의 코드
package tacos.data; import tacos.Taco; // Taco 객체를 저장하는데 필요한 인터페이스 public interface TacoRepository { Taco save(Taco design); }
-
Order 객체를 저장하기 위한 인터페이스 정의 코드
package tacos.data; import tacos.Order; // Order 객체를 저장하는데 필요한 인터페이스 public interface OrderRepository { Order save(Order order); }
-
JdbcTemplate을 사용해 TacoRepository 구현 코드
package tacos.data; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.PreparedStatementCreator; import org.springframework.jdbc.core.PreparedStatementCreatorFactory; import org.springframework.jdbc.support.GeneratedKeyHolder; import org.springframework.jdbc.support.KeyHolder; import org.springframework.stereotype.Repository; import tacos.Ingredient; import tacos.Taco; import java.sql.Timestamp; import java.sql.Types; import java.util.Arrays; import java.util.Date; @Repository public class JdbcTacoRepository implements TacoRepository{ private JdbcTemplate jdbc; public JdbcTacoRepository(JdbcTemplate jdbc){ this.jdbc = jdbc; } @Override // save() : Taco 테이블에 각 식자재를 저장하는 saveTacoInfo() 메서드 호출 public Taco save(Taco taco) { // 반환된 타코 id를 사용해 타코와 식자재의 연관 정보를 저장하는 saveIngredientToTaco() 호출 long tacoId = saveTacoInfo(taco); taco.setId(tacoId); for (Ingredient ingredient : taco.getIngredients()) { saveIngredientToTaco(ingredient, tacoId); } return taco; } private long saveTacoInfo(Taco taco) { taco.setCreatedAt(new Date()); PreparedStatementCreator psc = new PreparedStatementCreatorFactory( "insert into Taco (name, createdAt) values (?, ?)", Types.VARCHAR, Types.TIMESTAMP) .newPreparedStatementCreator( Arrays.asList( taco.getName(), new Timestamp(taco.getCreatedAt().getTime()))); KeyHolder keyHolder = new GeneratedKeyHolder(); // 타코 ID를 얻기위해 // update() : PreparedStatementCreator 객체와 KeyHolder 객체를 인자로 받는다 // KeyHolder : 생성된 타코 ID 제공 => 사용하기 위해서는 PreparedStatementCreator 도 생성해야 한다 jdbc.update(psc, keyHolder); // update()의 실행이 끝나면 keyHolder.getKey().longValue()의 연속호출로 타코 ID를 반환할 수 있다 return keyHolder.getKey().longValue() ; } // save() 메서드로 제어가 복귀 된 후 saveIngredientToTaco()를 호출하여 // 객체의 List에 저장된 각 Imgredient 객체를 반복 처리한다. private void saveIngredientToTaco(Ingredient ingredient, long tacoId) { // update()를 사용해 타코 ID와 Ingredient 객체 참조를 Taco_Ingredients 테이블에 저장한다 jdbc.update( "insert into Taco_Ingredients (taco, ingredient) " + "values (?,?)", tacoId, ingredient.getId()); } }
- ❓ saveTacoInfo 에서 calues(?, ?)값을 지정해주는 숫자 필요없는지? => 필요없다, PreparedStatementCreator psc = new PreparedStatementCreatorFactory() 과 .newPreparedStatementCreator() 는 같이 사용행 한다 - PreparedStatementCreator psc = new PreparedStatementCreatorFactory() : 타입 만 명시 - .newPreparedStatementCreator() : 값 등록
-
TacoRepository 주입후 사용 코드
@Slf4j @Controller @RequestMapping("/design") public class DesignTacoController { private final IngredientRepository ingredientRepo; // TacoRepository를 주입하여 타코 저장시 사용 private TacoRepository tacoRepo; @Autowired // showDesign(), processDesign() 메서드에서 사용 할 수 있도록 두 인자 모두 인스턴스 변수에 저장한다 public DesignTacoController(IngredientRepository ingredientRepo,TacoRepository tacoRepo) { this.ingredientRepo = ingredientRepo; this.tacoRepo = tacoRepo; }
-
타코 디자인 저장하고 주문과 연결하는 코드
package tacos.web; import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.beans.factory.annotation.Autowired; import lombok.extern.slf4j.Slf4j; import tacos.Taco; import tacos.Order; import tacos.Ingredient; import tacos.Ingredient.Type; import tacos.data.IngredientRepository; import javax.validation.Valid; import org.springframework.validation.Errors; import tacos.data.TacoRepository; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.SessionAttributes; @Slf4j // @Controller : 해당 클래스가 컨트롤러로 식별되게 하며 컴포넌트 검색을 해야한다는 것을 나타냄 // => 스프링이 해당클래스를 찾은후 스프링 APP컴텍스트의 빈(Bean)으로 해당 클래스의 인스턴스를 자동 생성한다 @Controller // @RequestMapping : 해당 클래스가 처리하는 요청의 종류 // 요청 경로가 disign 인 HTTP GET 요청 처리 @RequestMapping("/design") // Order의 다수의 HTTP 요청에 걸처 존재 해야한다 => 다수의 타코를 생성하고 하나의 주문으로 추가할 수 있게 하기 위해 // 클래스 수즌의 @SessionAttributes 을 주문과 같은 모델 객체에 지정 => 세션에서 계속 보존되면서 다수의 요청에 걸쳐 사용될 수 있다 @SessionAttributes("order") public class DesignTacoController { // @ModelAttribute(name = "order") : Order 객체가 모델에 생성되도록 해준다 @ModelAttribute(name = "order") public Order order(){ return new Order(); } @ModelAttribute(name = "taco") public Taco taco(){ return new Taco(); } // /design 경로의 POST 요청을 처리 => taco 를 디자인 하는 사용자가 제출한 것을 여기에서 처리한다 @PostMapping // processDesign() 메서드에서는 taco 객체를 사용해서 어떤 것이든 원하는 처리를 할 수 있다 // @Valid : 제출된 taco 객체의 유효성 검사를 수행 하라고 스프링 MVC 에 알려준다 // @ModelAttribute => 매개변수의 값(order)이 모델로부터 전달되어야 한다는 것과 스프링 MVC가 이 매개변수에 요청 매개변수를 바인딩 하지 않아야 한다는 것을 나타낸다 public String processDesign(@Valid Taco design, Errors errors, @ModelAttribute Order order){ // Errors 객체의 hsErrors() 메서드를 호출하여 검사 에러가 있는지 확인 // 에러가 있으면 taco 의 처리를 중지하고 "disign" 뷰 이름을 반환하여 폼이 다시 보이게 한다 if(errors.hasErrors()){ return "design"; } //log.info("Processing design: " + design); // 주입된 TacoRepository(tacoRepo) 를 사용해 타코 저장 후 Taco saved = tacoRepo.save(design); // 세션에 보존된 Order에서 Taco 객체를 추가한다 order.addDesign(saved); // redirect : processDesign()의 실행이 끝난 후 사용자의 브라우저가 /orders/current 상대 경로로 재접속되어야 한다는 것을 나타낸다 return "redirect:/orders/current"; } }
- ❓ @ModelAttribute => view 에서 사용하기 위해 이름을 지정한다
❕ 참고 : http://ojc.asia/bbs/board.php?bo_table=LecSpring&wr_id=856
-
SimpleJdbcInsert 생성 코드
package tacos.data; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.simple.SimpleJdbcInsert; import org.springframework.stereotype.Repository; import tacos.Order; import tacos.Taco; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; @Repository public class JdbcOrderRepository implements OrderRepository{ private SimpleJdbcInsert orderInserter; private SimpleJdbcInsert orderTacoInserter; private ObjectMapper objectMapper; @Autowired // 생성자 통해 JdbcTemplate 주입 public JdbcOrderRepository(JdbcTemplate jdbc) { // JdbcTemplate 을 사용해 두개의 SimpleJdbcInsert 인스턴스 생성 this.orderInserter = new SimpleJdbcInsert(jdbc) // Taco_Order 테이블에 주문 데이터를 추가하기 위해 구성 .withTableName("Taco_Order") // Order 객체의 id 속성 값은 DB가 생성해 주는것을 사용한다 .usingGeneratedKeyColumns("id"); this.orderTacoInserter = new SimpleJdbcInsert(jdbc) // Taco_Order_Tacos 테이블에 해당 주문 id 및 연관된 타코들의 id 를 추가하기 위해 구성 // =>but 어떤 id 값들을 Taco_Order_Tacos 테이블의 데이터에 생성할 것인지는 지정하지 않는다 .withTableName("Taco_Order_Tacos"); this.objectMapper = new ObjectMapper(); } }
-
SimpleJdbcInsert를 사용해 데이터 추가하는 코드
@Repository public class JdbcOrderRepository implements OrderRepository{ @Override // save() 메소드 : 실제로 저장하는 일 X => Order 및 연관된 Taco 객체들을 저장하는 처리를 총괄한다 public Order save(Order order) { order.setPlacedAt(new Date()); long orderId = saveOrderDetails(order); order.setId(orderId); List<Taco> tacos = order.getTacos(); for(Taco taco : tacos){ saveTacoToOrder(taco, orderId); } return order; } // 실제 저장하는 일 => saveOrderDetails(),saveTacoToOrder() 메서드에서 처리한다 // execute() , executeAndReturnKey() : 데이터를 추가하는 메서드 // 두 메서드 모두 Map<String,Object> 를 이자로 받는다 // => Map의 키 : 데이터가 추가되는 테이블의 열(column) 이름과 대응 // => Map의 값 : 해당 열에 추가되는 값 private long saveOrderDetails(Order order) { @SuppressWarnings("uncheked") // objectMapper.convertValue(order, Map.class) : order 를 Map 으로 변환 Map<String, Object> values = objectMapper.convertValue(order, Map.class); // Map 이 생성되면 키가 placedAt 인 항목의 값을 Order 객체의 placedAt 속성 값으로 변경한다 // => objectMapper 는 Date 타입의 값을 long 타입의 값으로 변환해 Taco_Order 테이블의 placedAt 열과 타입이 호환되지 않기 때문 values.put("placedAt", order.getPlacedAt()); // executeAndReturnKey() : 해당 주문 데이터가 Taco_Order 테이블에 저장된 후 DB에서 생성된 ID가 Number 객체로 반환된다 // 따라서 연속으로 longValue()를 호출하여 saveOrderDetails() 메서드에서 반환하는 long 타입으로 변환할 수 있다 long orderId = orderInserter.executeAndReturnKey(values).longValue(); return orderId; } private void saveTacoToOrder(Taco taco, long orderId) { // 객체를 Map 으로 변환하기 위해 ObjectMapper 를 사용하는 대신 Map을 생성하고 각 항목에 적합한 값을 설정한다 Map<String,Object> values = new HashMap<>(); values.put("tacoOrder", orderId); values.put("taco", taco.getId()); // exexute() 메서드 호출하여 데이터를 저장한다 orderTacoInserter.execute(values); } }
- ❓ @SuppressWarnings("uncheked") => 컴파일러가 일반적으로 경고하는 내용 중 "이건 하지마"하고 제외시킬 때 쓴다
-
OrderController에서 OrderRepository 사용 코드
package tacos.web; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.validation.Errors; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.SessionAttributes; import org.springframework.web.bind.support.SessionStatus; import tacos.Order; import tacos.data.OrderRepository; import javax.validation.Valid; @Slf4j @Controller // @RequestMapping : /orders 로 시작되는 경로의 요청을 이 컨트롤러의 요청 처리 메서드가 처리한다는 것을 알려주는 애노테이션 @RequestMapping("/orders") @SessionAttributes("order") public class OrderController { private OrderRepository orderRepo; public OrderController(OrderRepository orderRepo) { this.orderRepo = orderRepo; } // order 객체 세션 보전하기위해 아래처럼 변경 /* @GetMapping("/current") public String orderForm(Model model) { model.addAttribute("order", new Order()); return "orderForm"; }*/ @GetMapping("/current") public String orderForm() { return "orderForm"; } // Order 유효성 검사 @PostMapping // processOrder() 메서드는 SessionStatus를 인자로 전달받아 // setComplete() 메서드를 호출하여 세션을 재설정 한다 public String processOrder(@Valid Order order, Errors errors, SessionStatus sessionStatus) { if (errors.hasErrors()) { // orderForm 을 리턴한다 return "orderForm"; } //log.info("Order submitted: " + order); // processOrder() 메서드에서 주입된 OrderRepository 의 save() 메서드를 통해 폼에서 제출된 Order 객체를 저장한다 // => Order 객체도 세션에 보존되어야 한다 orderRepo.save(order); // 세션 재설정 sessionStatus.setComplete(); return "redirect:/"; } }
-
DB의 식자재 데이터 Ingredient 객체로 변환 코드
package tacos.web; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.convert.converter.Converter; import org.springframework.stereotype.Component; import tacos.Ingredient; import tacos.data.IngredientRepository; // Converter : 데이터의 타입을 변환해주는 클래스 // @Component : 스프링에 의해 자동 생성 및 주입되는 빈으로 생성 @Component // Converter<String, Ingredient> : String => 변환할 값의 타입, Ingredient => 변환된 값의 타입 public class IngredientByIdConverter implements Converter<String, Ingredient> { private IngredientRepository ingredientRepo; // @Autowired : IngredientRepository 인터페이스를 구현한 빈(JdbcIngredientRepository) 인스턴스가 생성자의 인자로 주입된다 @Autowired public IngredientByIdConverter(IngredientRepository ingredientRepo){ this.ingredientRepo = ingredientRepo; } @Override public Ingredient convert(String id) { // IngredientRepository 인터페이스를 구현한 JdbcIngredientRepository 클래스 인스턴스의 findById() 메서드를 호출한다 // findById() : 변환할 String 값을 id로 갖는 식자재 데이터를 DB 에서 찾는다 // => JdbcIngredientRepository 의 private 메서드인 mapRowToIngredient() 메서드를 호출하여 결과 세트의 행 데이터를 속성 값으로 갖는 Ingredient 객체를 생성하고 반환한다 return ingredientRepo.findById(id); } }