어느정도 페이지가 마무리 된 상태에서 할 거 없나 어슬렁어슬렁 거리다가
마이페이지에 백엔드 작업이 필요한거 보고 get 해오기!
아직 JPA으로 백엔드 해본적이 없어서 공부라 생각하고 해보겠습니다~
초기 마이페이지
회원이 로그인 후 마이페이지 들어가면
나의 차량 정보를 등록하고 관리할 수 있게 추가하려고 한다.
나는 백엔드만 추가!
차량 사진, 차량 정보, 차량 번호, 유종 이 만들어져있어서 각각에 해당하는 값들을 사용자가 추가할 수 있게 작업했다.
수업 내용이랑 다른 친구가 해놓은거 참고하면서 최대한 이해해보기!
JPA ?
JPA는 자바의 ORM 프레임워크로
- 객체 모델(Object Model)이랑 관계형 데이터베이스(Relational Database) 사이의 매핑을 자동으로 처리하는 기능을 지원하고
- 객체를 대상으로 작성하는 JPQL을 사용한다.(SQL은 테이블 중심 작성)
- Hibernate, EclipseLink, OpenJPA같은 다양한 구현체가 JPA 명세를 기반으로 작동
이라고 한다!
2차 프로젝트 때는
controller → service, seviceImpl → dao → mapper 이런식으로 해서 mapper에다가 쿼리문을 작성했었는데
3차 프로젝트에서는
entity, controller, service, serviceImpl 이렇게 구성되어 있었다.
수정된 마이페이지
차량 종류, 차량 번호는 사용자가 직접 입력하게 했고,
차량 유종은 드롭다운 리스트로 디젤,휘발유,전기,LPG를 선택할 수 있게 했다.
<div class="car-details">
<div class="car-detail">
<div class="label-box">차량 종류</div>
<input type="text" id="carType" class="value-box input-field" placeholder="차량 종류를 입력해 주세요." />
</div>
<div class="car-detail">
<div class="label-box">차량 번호</div>
<input type="text" id="carNumber" class="value-box input-field" placeholder="차량 번호를 입력해 주세요." />
</div>
<div class="car-detail">
<div class="label-box">차량 유종</div>
<div class="value-box">
<!-- 차량 유종 드롭다운 -->
<select id="fuelType" class="value-box input-field">
<option value="" disabled selected>차량 유종을 선택해 주세요</option>
<option value="디젤">디젤</option>
<option value="휘발유">휘발유</option>
<option value="전기">전기</option>
<option value="lpg">LPG</option>
</select>
</div>
</div>
</div>
이제 백엔드 작성하기!
☑️Entity
Entity는 데베 테이블이랑 매핑되고, 비즈니스 도메인 객체를 정의한다고 한다.
1.클래스에 엔티티 어노테이션 추가 → @Entity
(why? 해당 클래스가 JPA에서 관리되는 엔티티 클래스임을 나타내기 위해,JPA는 엔티티 어노테이션이 붙은 클래스만 작업 처리 가능.)
2.테이블 어노테이션도 추가 → @Table(name = "car_info")
(why? 클래스와 매핑되는 데베 테이블 이름 지정하려고, car_info로 설정(CarInfoEntity가 엔티티 이름인데 name="car_info"로 명시)
3. ID 어노테이션 추가 → @Id
(why? 테이블에서 기본키(Primary Key)로 사용되는 필드 나타내기, JPA는 해당 필드가 엔티티의 식별자로 사용된다는것을 인식)
4. 컬럼 추가 → @Column
(why? 컬럼과 엔티티 필드 매핑, name 속성으로 데베의 컬럼 이름과 필드 이름이 다를때 명시 가능)
@Entity
@Table(name = "car_info")
@Data
public class CarInfoEntity {
@Id
@Column(name = "car_idx") //세션에 저장된 idx값 가져오기
private Long carIdx;
@Column(name = "car_type", nullable = false) //차량 종류
private String carType;
@Column(name = "car_number", nullable = false) //차량 번호
private String carNumber;
@Column(name = "fuel_type", nullable = false) //차량 유종
private String fuelType;
}
자바의 네이밍 규칙인 카멜케이스를 따르면서 데이터베이스랑 매핑을 했다.(name = "")
car_idx는 로그인할 때 세션에 저장된 car_idx값을 가져올 예정이고
차량 종류, 번호, 유종을 추가하고 끝!
☑️Repository
CarInfoRepository는 JpaRepository를 상속받아 레포지토리 역할을 수행한다.
제네릭 <CarInfoEntity, Long>을 사용했고, 각각
데이터 조작 대상이 되는 엔티티 = CarInfoEntity
해당 엔티티의 기본 키(PK)의 데이터 타입 = Long
이렇게 했다.
JpaRepository를 상속받으면 추가적인 쿼리가 없어도 기본적인 CRUD가 된다니..
*참고*
JPA에서 제공하는 기본 메서드
- save(S entity): 엔티티를 저장하거나 업데이트.
- findById(ID id): 기본 키를 사용해 특정 엔티티를 조회.
- findAll(): 모든 엔티티를 조회.
- deleteById(ID id): 기본 키를 사용해 특정 엔티티를 삭제.
- delete(S entity): 엔티티를 삭제.
- findAll(Pageable pageable): 페이징된 데이터를 조회.
- findAll(Sort sort): 정렬된 데이터를 조회.
이거 말고도 커스텀 메서드도 작성이 가능하다고 한다.
public interface CarInfoRepository extends JpaRepository<CarInfoEntity, Long> {
}
☑️Service, ServiceImpl
2차때도 많이 다뤄본 Service(interface)와 ServiceImpl(구현체)
Service 인터페이스에서는
saveCarInfo(CarInfoEntity carInfo) 인 저장 기능을 할 메서드와
CarInfoEntity getCarInfo(Long carIdx) 인 정보 조회 메서드를 정의해줬다.
public interface CarInfoService {
void saveCarInfo(CarInfoEntity carInfo); // 차량 정보 저장
CarInfoEntity getCarInfo(Long carIdx); // 차량 정보 조회
}
구현체인 ServiceImpl에서는
Service 어노테이션으로 서비스 컴포넌트로 등록했고 레포지토리 객체를 선언했다.
private final CarInfoRepository carInfoRepository;
그리고 saveCarInfo와 getCarInfo를 구현했고 각각 엔티티를 호출해서 객체를 데이터베이스에 저장하고 불러오게 했다.
@Override
public void saveCarInfo(CarInfoEntity carInfo) {
carInfoRepository.save(carInfo);
}
@Override
public CarInfoEntity getCarInfo(Long carIdx) {
return carInfoRepository.findById(carIdx)
.orElseThrow(() -> new RuntimeException("해당 정보를 찾을 수 없습니다."));
}
전체 코드
@Service
public class CarInfoServiceImpl implements CarInfoService {
private final CarInfoRepository carInfoRepository;
public CarInfoServiceImpl(CarInfoRepository carInfoRepository) {
this.carInfoRepository = carInfoRepository;
}
@Override
public void saveCarInfo(CarInfoEntity carInfo) {
carInfoRepository.save(carInfo);
}
@Override
public CarInfoEntity getCarInfo(Long carIdx) {
return carInfoRepository.findById(carIdx)
.orElseThrow(() -> new RuntimeException("해당 정보를 찾을 수 없습니다."));
}
}
☑️Controller
CarInfoService를 사용하기 위해 선언하기
private final CarInfoService carInfoService;
각각 메서드 구현하기
saveCarInfo
일단 사용자한테 작성받은걸 Post로 처리하고
테이블에 있는 car_idx값은 로그인할때 세션에 저장된 값을 쓰기 위해 세션에 저장된 user 객체를 가져오게 했다.
UserEntity user = (UserEntity) session.getAttribute("user");
if (user == null) {
return ResponseEntity.status(401).body("로그인이 필요합니다.");
}
세션에 저장된 UserEntity 객체를 가져오고 user가 없으면 로그인이 안 된 상태니까 401을 반환했다.
로그인이 되어있으면 세션에서 가져온 사용자 정보(user)의 car_idx를 carInfo의 car_idx 필드에 설정하게 했다.
carInfo.setCar_idx((long) user.getCarIdx());
그러고 CarInfoService를 호출해서 차량 정보를 데베에 저장했다.
비즈니스 로직은 Service 계층에서 처리하고 컨트롤러는 요청만 전달하기!
carInfoService.saveCarInfo(carInfo);
@PostMapping(value = "/save", produces = "application/json; charset=UTF-8")
public ResponseEntity<String> saveCarInfo(@RequestBody CarInfoEntity carInfo, HttpSession session) {
UserEntity user = (UserEntity) session.getAttribute("user");
if (user == null) {
return ResponseEntity.status(401).body("로그인이 필요합니다.");
}
carInfo.setCarIdx((long) user.getCarIdx());
carInfoService.saveCarInfo(carInfo);
return ResponseEntity.ok("차량 정보가 성공적으로 저장되었습니다.");
}
getCarInfo
저장된 carIdx값으로 불러오게 했다.
GetMapping은 carIdx값으로 받아오게 하고 getCarInfo 서비스를 불러오게 했다.
@GetMapping("/{carIdx}")
public ResponseEntity<CarInfoEntity> getCarInfo(@PathVariable Long carIdx) {
CarInfoEntity carInfo = carInfoService.getCarInfo(carIdx);
return ResponseEntity.ok(carInfo); // JSON 형식으로 반환
}
☑️JavaScript
사용자한테 각각 입력받은 값들을 fetch → car/save 경로로 post하기
그럼 컨트롤러에 구현된 /save로 전달!
get도 마찬가지로 car/ carIdx값을 전달하면
컨트롤러에 구현된 /{carIdx}로 전달!
이미 저장된 값이 있으면 그대로 화면에 출력하기
<script>
document.querySelector('.register-car-info-btn').addEventListener('click', () => {
const carInfo = {
carType: document.querySelector('input[placeholder="차량 종류를 입력해 주세요."]').value,
carNumber: document.querySelector('input[placeholder="차량 번호를 입력해 주세요."]').value,
fuelType: document.querySelector('select').value,
};
fetch('${pageContext.request.contextPath}/car/save', {
method: 'POST',
headers: {
'Content-Type': 'application/json; charset=UTF-8',
},
body: JSON.stringify(carInfo),
})
.then(response => {
if (!response.ok) {
throw new Error('서버 오류: ' + response.status);
}
return response.text();
})
.then(message => {
alert(message);
location.reload();
})
.catch(error => {
console.error('Error:', error);
alert('저장 중 오류가 발생했습니다. 다시 시도해 주세요.');
});
});
// session.car_idx = car_info table.car_idx
$(document).ready(function() {
const carIdx = $('#carIdx').val();
console.log("carIdx:", carIdx);
console.log("carIdx (from hidden input):", carIdx, "Type:", typeof carIdx);
if (carIdx) {
const url = "/CarPlanet/car/" + carIdx;
console.log("const로 만든 url 체크: ", url);
$.ajax({
url: url,
type: 'GET',
dataType: 'json',
success: function(carInfo) {
console.log("Response Data:", carInfo);
if (carInfo) {
$('#carType').val(carInfo.carType);
$('#carNumber').val(carInfo.carNumber);
$('#fuelType').val(carInfo.fuelType);
if (!$('#fuelType').val()) {
$('#fuelType').val('');
}
}
},
error: function(err) {
console.error("차량 정보를 불러오는데 실패했습니다.", err);
}
});
}
});
</script>
✅전체적인 흐름
프론트(javascript) → controller → service,serviceImpl → Repository + entity
사용자가 각각의 차량정보를 입력해서
javascript로 요청을 보내면 controller에서 받아서
각각 메서드에 해당하는 service, serviceImpl을 타고가서
Repository가 entity 기반으로 데베에 저장, 조회하고 다시 클라이언트에 반환!
테스트!
입력하기
테이블 확인, car_idx값으로 들어오는지 체크
저장된 값 잘 나오는지 확인
테스트 끝!
하면서 몇번 오류들이 발생했는데
그건 따로 정리할 예정입니다..
이제 추가해야할건 차량 이미지 부분.. 업로드 하고 저장, 저장된 값 불러오기
'Develop > JPA' 카테고리의 다른 글
마이페이지 회원정보 수정 로직 (14) (2) | 2024.11.26 |
---|---|
마이페이지 회원정보 수정로직 (13) (2) | 2024.11.25 |
반응형 CSS 추가하기 (10) (2) | 2024.11.20 |
반응형 CSS 추가하기 (9) (0) | 2024.11.19 |
카카오 길찾기 API (8) (6) | 2024.11.18 |