본문 바로가기

Study/Spring

[Spring] Store - CartModel / REST API

REST API를 사용하여 CART(장바구니) 서비스 구현

 

DB 관계도

 

  • 하나의 product가 여러개의 cart item에 담겨 여러개의 cart에 담길 수 있다.
  • List<CartItem>은 DB에 따로 저장하지 않는다.

 

Cart와 CartItem 생성

 

 

-Cart.java

package kr.ac.hansung.cse.model;

import java.util.ArrayList;
import java.util.List;

import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

@Getter
@Setter
@ToString
public class Cart {
	
	private int id;
	
	private List<CartItem> cartItems = new ArrayList<CartItem>();
	
	private double grandTotal;
	
}

 

-CartItem.java

package kr.ac.hansung.cse.model;

import java.util.List;

import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

@Getter
@Setter
@ToString
public class CartItem {
	
	private int id;
	
	private Cart cart;
	
	private Product product;
	
	private int quantity;
	
	private double totalPrice;

}

 

java serialization

  • Serialization : 객체를 File, DB, Memory, Network 에 보낼 때에는 Byte Stream으로 바꾸어서 보낸다.
  • De-Serialization :  Byte Stream을 다시 객체화 하는 과정을 뜻한다.

Object -> JSON으로 Serialization시키고, JSON -> Object로 De-Serialization 할 계획

-> 이 과정을 하기 위해 Serializable 인터페이스를  implements 해준다.

 

-Cart.java

package kr.ac.hansung.cse.model;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToMany;

import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

@Getter
@Setter
@ToString
@Entity
public class Cart implements Serializable {
	
	private static final long serialVersionUID = -7383420736137539222L;
	
	@Id
	@GeneratedValue(strategy=GenerationType.IDENTITY)
	private int id;
	
	private List<CartItem> cartItems = new ArrayList<CartItem>();
	
	private double grandTotal;
	
}

 

-CartItem.java

package kr.ac.hansung.cse.model;

import java.io.Serializable;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;

import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

@Getter
@Setter
@ToString
@Entity
public class CartItem implements Serializable{

	//version id
	private static final long serialVersionUID = -7296960050350583877L;

	@Id
	@GeneratedValue(strategy=GenerationType.IDENTITY)
	private int id;

	private Cart cart;

	private Product product;
	
	private int quantity;
	
	private double totalPrice;

}

 

-Product.java

public class Product implements Serializable{ ... }

 

 

CartItem과 Cart 구현 ( N:1 관계 )

 

-CartItem.java (Many To One)

public class CartItem implements Serializable{
	
    ...
    
	@ManyToOne
	@JoinColumn(name="cartId")
	private Cart cart;
	
	@ManyToOne
	@JoinColumn(name="productId")
	private Product product;
   
	...
}

 

-Cart.java (One To Many)

public class Cart implements Serializable {
	
    ...
	
    // mappedBy 쓸때 참조자 이름 반드시 일치
    @OneToMany(mappedBy="cart",cascade=CascadeType.ALL, fetch=FetchType.EAGER) 
	private List<CartItem> cartItems = new ArrayList<CartItem>();

	...
	
}

 

cascade=CascadeType.ALL

  •  Cart가 제거되면 CartItem도 제거된다.

mappedBy="cart"

  • 이 컬럼은 Table에 생성되지 않고, Cart를 참조한다.

 

OneToMany

  • default : Lazy

ManyToOne

  • default : Eager (데이터를 바로 읽어온다)

 

 

User와 Cart 구현 (1:1 관계)

 

-User.java (One To One)

public class User {

...

	@OneToOne(optional=false, cascade=CascadeType.ALL) 
	@JoinColumn(unique=true) 
	private Cart cart;

...
	

}

 

지금의 코드를 따라가면

  • User 호출 > CartItem 호출> Cart 호출 > CartItem 호출 > Cart ...
    위와같은 사이클이 생기는것을 방지해야한다.

 

 

사이클 방지 @JsonIgnore

 

-pom.xml 에 dependency추가

<!-- jackson-databind -->
		<dependency>
			<groupId>com.fasterxml.jackson.core</groupId>
			<artifactId>jackson-databind</artifactId>
			<version>2.9.4</version>
		</dependency>

 

-CartItem.java

public class CartItem implements Serializable{

...

	@ManyToOne
	@JoinColumn(name="cartId")
	@JsonIgnore
	private Cart cart;

...

}

 

 

Cart를 Serialization할때 CartItem Json 을 무시하게되어 사이클이 형성되는것을 방지한다.

 

@JsonIgnore

  • JSON 직렬화, 역직렬화에서 속성을 무시하는데 사용된다.
  • @JsonIgnore 어노테이션은 클래스의 속성(필드, 멤버변수) 수준에서 사용한다.

 

dao-context.xml 의 속성을 create로 수정하여 table생성되는것 확인

<prop key="hibernate.hbm2ddl.auto">create</prop>

 

mysql에서 DB확인

 

테이블 정상적으로 생성 완료되었다.

 

 

cart테이블에 cartItems는 없는것을 확인 할 수 있다.

 

 

 

모든 테이블과 컬럼이 잘 생성된 것을 확인 할 수 있다.

 

 

Model이 생성되었으니 DAO와 Service 생성

 

CartService.java 생성

  • Service는 @Service를 사용

 

 

package kr.ac.hansung.cse.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import kr.ac.hansung.cse.dao.CartDao;
import kr.ac.hansung.cse.model.Cart;

@Service
public class CartService {
	
	@Autowired
	private CartDao cartDao;
	
	public Cart getCartById(int cartId) {
		return cartDao.getCartById(cartId);
	}

}

 

 

CartDao.java 생성

  • DAO는 @Repository, @Transactional 사용. 

 

 

package kr.ac.hansung.cse.dao;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

import kr.ac.hansung.cse.model.Cart;

@Repository
@Transactional
public class CartDao {

	@Autowired
	private SessionFactory sessionFactory;
	
	public Cart getCartById(int cartId) {
		Session session = sessionFactory.getCurrentSession();
		return (Cart) session.get(Cart.class, cartId);
	}
}

 

CartItemService.java 생성

 

package kr.ac.hansung.cse.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import kr.ac.hansung.cse.dao.CartItemDao;
import kr.ac.hansung.cse.model.Cart;
import kr.ac.hansung.cse.model.CartItem;

@Service
public class CartItemService {

	@Autowired
	private CartItemDao cartItemDao;
	
	public void addCartItem(CartItem cartItem) {
		cartItemDao.addCartItem(cartItem);
	}
	
	public void removeCartItem(CartItem cartItem) {
		cartItemDao.removeCartItem(cartItem);
	}
	
	public void removeAllCartItems(Cart cart) {
		cartItemDao.removeAllCartItems(cart);
	}
	
	public CartItem getCartItemByProductId(int cartId, int productId) {
		return cartItemDao.getCartItemByProductId(cartId, productId);
	}
	
}

 

CartItemDao.java 생성

 

package kr.ac.hansung.cse.dao;

import java.util.List;

import javax.persistence.TypedQuery;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

import kr.ac.hansung.cse.model.Cart;
import kr.ac.hansung.cse.model.CartItem;



@Repository
@Transactional
public class CartItemDao {
	
	@Autowired
	private SessionFactory sessionFactory;
	
	public void addCartItem(CartItem cartItem) {
		Session session = sessionFactory.getCurrentSession();
		session.saveOrUpdate(cartItem);
		session.flush();
	}
	
	public void removeCartItem(CartItem cartItem) {
		Session session = sessionFactory.getCurrentSession();
		session.delete(cartItem);
		session.flush();
	}
	
	public void removeAllCartItems(Cart cart) {
		List<CartItem> cartItems = cart.getCartItems();
		
		for(CartItem item : cartItems) {
			removeCartItem(item);
		}
	}
	
	@SuppressWarnings("unchecked")
	public CartItem getCartItemByProductId(int cartId, int productId) {
		Session session = sessionFactory.getCurrentSession();
		TypedQuery<CartItem> query = session.createQuery("from CartItem where cart.id=? and product.id=?");
		query.setParameter(0, cartId);
		query.setParameter(1, productId);
		
		return(CartItem) query.getSingleResult();
	}
	

}

 

 

	public void removeAllCartItems(Cart cart) {
		List<CartItem> cartItems = cart.getCartItems();
		
		for(CartItem item : cartItems) {
			removeCartItem(item);
		}
	}

에서 cart를 읽으면 

 

	@OneToMany(mappedBy="cart",cascade=CascadeType.ALL, fetch=FetchType.EAGER) // mappedBy 쓸때 참조자 이름 반드시 일치
	private List<CartItem> cartItems = new ArrayList<CartItem>();

Cart.java의 fetch=FetchType.EAGER 속성에 에 의해  cartItem까지 같이 읽어온다. 

 

작동을 확인하기 위해 dao-context.xml에서 hibernate의 속성을 update 로 수정한다.

<prop key="hibernate.hbm2ddl.auto">update</prop>

 

RegisterController.java 수정

	@RequestMapping(value="/register", method=RequestMethod.POST)
	public String registerUserPost(@Valid User user, BindingResult result, Model model) {
		if(result.hasErrors()) {
			return "registerUser"; // 재 입력 페이지
		}
		
		List<User> userList = userService.getAllUsers();
		
		for(int i=0; i<userList.size(); i++) {
			if(user.getUsername().equals(userList.get(i).getUsername())) {
				// userName이 값으면
				model.addAttribute("usernameMsg", "Username already exist");
				return "registerUser";
			}
		}
		
		user.setEnabled(true);
		if(user.getUsername().equals("admin"))
			user.setAuthority("ROLE_ADMIN");
		else 
			user.setAuthority("ROLE_USER");
		
		Cart cart = new Cart();
		user.setCart(cart);
		
		userService.addUser(user);
		return "registerUserSuccess";
		
	}

cart가 널값이면 오류가 발생할 수 있으므로 RegisterController.java 수정하여 cart를 생성하고 set해준다.

 

 

-User.java

	@OneToOne(optional=false, cascade=CascadeType.ALL) 
	@JoinColumn(unique=true) 
	private Cart cart;
  • optional = false : 반드시 존재
  • cascade=CascadeType.ALL : User생성시 cart도 함께 저장.

 

-실행화면

 

 

 

 


 

https://velog.io/@kdhyo/JavaTransactional-Annotation-%EC%95%8C%EA%B3%A0-%EC%93%B0%EC%9E%90-26her30h

 

[Java]@Transactional Annotation 알고 쓰자

초반 @Transactional 어노테이션에 대해 자세히 알아보지 않고,막연히 롤백때 사용한다고 하여 SQL C,U,D 를 할 때마다 메소드 위에 붙여서 사용하곤 하였다.하지만, 내 코드를 보신 선임께서 단지 @Tran

velog.io

@JsonIgnore, @JsonIgnoreProperties, @JsonIgnoreType차이점 (velog.io)

 

@JsonIgnore, @JsonIgnoreProperties, @JsonIgnoreType차이점

json 데이터를 받아와서 객체로 맵핑할 때 클래스에 선언되지 않은 프로퍼티가 json에 있으면 오류 발생 (json 구성 = 클래스 구성)이럴 때 예외 발생시키지 말고 무시출처: https://darksilber.tistory.com/28

velog.io