
JPA는 내부적으로 jdbc를 사용하여 직접 SQL을 사용하지않아서 CRUD나 수정해야할 코드가 적은 장점이 있다. JPA를 활용해 실습해보자!
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
JPA 매핑 어노테이션을 도메인 객체에 추가해보자!
package tacos;
import javax.persistence.Entity;
import javax.persistence.Id;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.Data;
import lombok.RequiredArgsConstructor;
@Data
@RequiredArgsConstructor
@NoArgsConstructor(access=AccessLevel.PRIVATE, force=true)
@Entity
public class Ingredient {
@Id
private final String id;
private final String name;
private final Type type;
public static enum Type {
WRAP, PROTEIN, VEGGIES, CHEESE, SAUCE
}
}
package tacos;
import java.util.List;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToMany;
import javax.persistence.PrePersist;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import lombok.Data;
import java.util.Date;
@Data
@Entity
public class Taco {
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
private Date createdAt;
@NotNull
@Size(min=5, message="Name must be at least 5 characters long")
private String name;
@ManyToMany(targetEntity=Ingredient.class)
@Size(min=1, message="You must choose at least 1 ingredient")
private List<Ingredient> ingredients;
@PrePersist
void createdAt() {
this.createdAt = new Date();
}
}
package tacos;
import javax.validation.constraints.Digits;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.NotBlank;
import org.hibernate.validator.constraints.CreditCardNumber;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToMany;
import javax.persistence.PrePersist;
import javax.persistence.Table;
import lombok.Data;
import java.util.Date;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
@Data
@Entity
@Table(name="Taco_Order")
public class Order implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
private Date placedAt;
@NotBlank(message="Name is required")
private String deliveryName;
@NotBlank(message="Street is required")
private String deliveryStreet;
@NotBlank(message="City is required")
private String deliveryCity;
@NotBlank(message="State is required")
private String deliveryState;
@NotBlank(message="Zip code is required")
private String deliveryZip;
@CreditCardNumber(message="Not a valid credit card number")
private String ccNumber;
@Pattern(regexp="^(0[1-9]|1[0-2])([\\/])([1-9][0-9])$",
message="Must be formatted MM/YY")
private String ccExpiration;
@Digits(integer=3, fraction=0, message="Invalid CVV")
private String ccCVV;
@ManyToMany(targetEntity=Taco.class)
private List<Taco> tacos = new ArrayList<>();
public void addDesign(Taco design) {
this.tacos.add(design);
}
@PrePersist
void placedAt() {
this.placedAt = new Date();
}
}
jdbc와는 달리 리포지토리에 제공하는 메소드를 CrudRepository 인터페이스를 extend하여 사용할 수 있다.
package tacos.data;
import org.springframework.data.repository.CrudRepository;
import tacos.Ingredient;
public interface IngredientRepository
extends CrudRepository<Ingredient, String> {
}
package tacos.data;
import org.springframework.data.repository.CrudRepository;
import tacos.Taco;
public interface TacoRepository
extends CrudRepository<Taco, Long> {
}
package tacos.data;
import org.springframework.data.repository.CrudRepository;
import tacos.Order;
public interface OrderRepository
extends CrudRepository<Order, Long> {
}
애플리케이션이 시작될 때 스프링 데이터 JPA가 각 인터페이스 구현체를 자동으로 생성해주기 때문에 jdbc를 활용할 때 사용된
JdbcIngredientRepository와JdbcTacoRepository,JdbcOrderRepository는 제거해주자
package tacos;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.CommandLineRunner;
import org.springframework.context.annotation.Bean;
import tacos.Ingredient.Type;
import tacos.data.IngredientRepository;
@SpringBootApplication
public class TacoCloudApplication {
public static void main(String[] args) {
SpringApplication.run(TacoCloudApplication.class, args);
}
@Bean
public CommandLineRunner dataLoader(IngredientRepository repo) {
return new CommandLineRunner() {
@Override
public void run(String... args) throws Exception {
repo.save(new Ingredient("FLTO", "Flour Tortilla", Type.WRAP));
repo.save(new Ingredient("COTO", "Corn Tortilla", Type.WRAP));
repo.save(new Ingredient("GRBF", "Ground Beef", Type.PROTEIN));
repo.save(new Ingredient("CARN", "Carnitas", Type.PROTEIN));
repo.save(new Ingredient("TMTO", "Diced Tomatoes", Type.VEGGIES));
repo.save(new Ingredient("LETC", "Lettuce", Type.VEGGIES));
repo.save(new Ingredient("CHED", "Cheddar", Type.CHEESE));
repo.save(new Ingredient("JACK", "Monterrey Jack", Type.CHEESE));
repo.save(new Ingredient("SLSA", "Salsa", Type.SAUCE));
repo.save(new Ingredient("SRCR", "Sour Cream", Type.SAUCE));
}
};
}
}
부트스트랩 클래스를 변경해준 이유는 jdbc를 사용할땐 애플리케이션이 시작되면서 data.sql을 호출하여 식자재 데이터를 데이터베이스에 직접 추가하였지만 스프링 데이터 JPA를 사용하기위해
dataLoader()메소드로 식자재 데이터를 데이터베이스에 미리 저장해둘 필요가 있기 때문이다.
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;
import java.util.Optional;
@Component
public class IngredientByIdConverter
implements Converter<String, Ingredient> {
private IngredientRepository ingredientRepo;
@Autowired
public IngredientByIdConverter(IngredientRepository ingredientRepo) {
this.ingredientRepo = ingredientRepo;
}
@Override
public Ingredient convert(String id) {
Optional<Ingredient> optionalIngredient = ingredientRepo.findById(id);
return optionalIngredient.isPresent() ?
optionalIngredient.get() : null;
}
}
jdbc는 SQL Query를 직접 작성하고 쿼리수행을 전달해줄뿐인 API이라 객체지향적인 개발에 어려움이 생겼다.
JPA는 이러한 번거로운 과정을 생략해 자바코드를 사용해서 db에 접근할 수 있고 ORM(Object–relational mapping)을 사용하도록 지원하는 API이다.
이전과 이어서 JPA를 사용하니 어노테이션으로 생략되는부분과 쿼리를 직접 짜지않는다는 장점이 개발에 굉장히 편리함을 주었다. jdbc까지했을땐 과연 내가 직접 이렇게 개발할 수 있을까? 라는 불안감이 엄습했는데 JPA는 매핑만해주면 알아서 쿼리문을 작성해주니 해볼만할지도? 라는 생각이 든다. 물론 그만큼 잘 알수있어야하겠지만말이다 😅 계속해서 공부해보자!