사이드 프로젝트 진행 중 같은 파트의 팀원에게 이런 질문을 받았다.
당연히 다르지 않을까? 라고 생각했었다. 그렇지만 이것을 확인해보기 위해서는 Test Code를 작성해서 확실하게 확인하고 공부해야되겠다고 생각했다.
위 말처럼 되는지 그럼 확인을 해봐야지
우선 아래의 전체적인 코드이다. 이것을 하나하나 case를 나눠 단위 테스트로 확인해보기로 하였다.
@SpringBootTest
@Transactional
public class SaveTest {
@Autowired
private MovieRepoService repoService;
@Autowired
private MovieRepository movieRepository;
@Test
@Transactional
public void 같은_트랜잭션에서_같은객체인지(){
//given
Movie originMovie = Movie.builder().
movieCountry("en").movieGenre("12").movieRunningTime(123).movieDirector("123")
.movieDetail("fwefsv").movieDate("1234").movieActor("12345")
.title("helloworld").movieImg("1234").movieBgImg("12345").real_movieId(444332L).build();
//when
movieRepository.save(originMovie);
Optional<Movie> helloworld = movieRepository.findByTitle("helloworld");
Movie findMovie = helloworld.get();
//then
Assertions.assertThat(originMovie).isEqualTo(findMovie); // 같은 영속
}
@Test
@Rollback(value = false)
@Transactional
public void 미리DB에저장(){
Movie originMovie = Movie.builder().
movieCountry("en").movieGenre("12").movieRunningTime(123).movieDirector("123")
.movieDetail("fwefsv").movieDate("1234").movieActor("12345")
.title("helloworld2").movieImg("1234").movieBgImg("12345").real_movieId(444332L).build();
Map<String, Movie> titleAndMovie= new HashMap<>();
titleAndMovie.put("helloworld2", originMovie);
repoService.saveMovie(titleAndMovie); // 기존에 있는 경우 다시 넣지 않음.
}
@Test
@Transactional
public void 기존db에서불러왔을때_equal하게보는지(){
//given 기존 db에 저장되어있는 친구 정보
Movie originMovie = Movie.builder().
movieCountry("en").movieGenre("12").movieRunningTime(123).movieDirector("123")
.movieDetail("fwefsv").movieDate("1234").movieActor("12345")
.title("helloworld2").movieImg("1234").movieBgImg("12345").real_movieId(444332L).build();
//when
Optional<Movie> helloworld = movieRepository.findByTitle("helloworld2");
Movie findMovie = helloworld.get();
//then
Assertions.assertThat(originMovie).isEqualTo(findMovie);
}
@Test
@Transactional
@Rollback(value = false)
public void db에존재하고있는_친구_가져와서_변경시_기존DB_Record에변경하는지(){
//given db에 저장되어있는 친구 정보
Movie originMovie = Movie.builder().
movieCountry("en").movieGenre("12").movieRunningTime(123).movieDirector("123")
.movieDetail("fwefsv").movieDate("1234").movieActor("12345")
.title("helloworld2").movieImg("1234").movieBgImg("12345").real_movieId(444332L).build();
//when
Optional<Movie> helloworld = movieRepository.findByTitle("helloworld2");
Movie findMovie = helloworld.get();
Long findMovieId = findMovie.getId();
findMovie.setMovieCountry("china");
movieRepository.save(findMovie); // 이렇게 하면 변경이 진행됨.
//then
Optional<Movie> checkMovie = movieRepository.findById(findMovieId);
Movie changeValueMovie = checkMovie.get();
Assertions.assertThat(changeValueMovie.getId()).isEqualTo(findMovie.getId());
}
}
@Test
@Transactional
public void 같은_트랜잭션에서_같은객체인지(){
//given
Movie originMovie = Movie.builder().
movieCountry("en").movieGenre("12").movieRunningTime(123).movieDirector("123")
.movieDetail("fwefsv").movieDate("1234").movieActor("12345")
.title("helloworld").movieImg("1234").movieBgImg("12345").real_movieId(444332L).build();
//when
movieRepository.save(originMovie);
Optional<Movie> helloworld = movieRepository.findByTitle("helloworld");
Movie findMovie = helloworld.get();
//then
Assertions.assertThat(originMovie).isEqualTo(findMovie); // 같은 영속
}
이 친구는 어떻게 될까? 실행하면?
같다고 한다.
@Test
@Transactional
public void 기존db에서불러왔을때_equal하게보는지(){
//given 기존 db에 저장되어있는 친구 정보
Movie originMovie = Movie.builder().
movieCountry("en").movieGenre("12").movieRunningTime(123).movieDirector("123")
.movieDetail("fwefsv").movieDate("1234").movieActor("12345")
.title("helloworld2").movieImg("1234").movieBgImg("12345").real_movieId(444332L).build();
//when
Optional<Movie> helloworld = movieRepository.findByTitle("helloworld2");
Movie findMovie = helloworld.get();
//then
Assertions.assertThat(originMovie).isEqualTo(findMovie);
}
이 경우는?
보이는 바와 같이 다른 객체라고 인지한다.
@Test
@Transactional
@Rollback(value = false)
public void db에존재하고있는_친구_가져와서_변경시_기존DB_Record에변경하는지(){
//given db에 저장되어있는 친구 정보
Movie originMovie = Movie.builder().
movieCountry("en").movieGenre("12").movieRunningTime(123).movieDirector("123")
.movieDetail("fwefsv").movieDate("1234").movieActor("12345")
.title("helloworld2").movieImg("1234").movieBgImg("12345").real_movieId(444332L).build();
//when
Optional<Movie> helloworld = movieRepository.findByTitle("helloworld2");
Movie findMovie = helloworld.get();
Long findMovieId = findMovie.getId();
findMovie.setMovieCountry("china");
movieRepository.save(findMovie); // 이렇게 하면 변경이 진행됨.
//then
Optional<Movie> checkMovie = movieRepository.findById(findMovieId);
Movie changeValueMovie = checkMovie.get();
Assertions.assertThat(changeValueMovie.getId()).isEqualTo(findMovie.getId());
}
이렇게 하면?
위와 같이 추가적으로 save되지 않고 기존에 존재하고 있는 친구가 update되어서 반영이 되었다.
위 코드들을 진행해보면서 다음과 같은 결론을 얻게 되었다.
@Transactional
@Override
public <S extends T> S save(S entity) {
Assert.notNull(entity, "Entity must not be null.");
if (entityInformation.isNew(entity)) {
em.persist(entity);
return entity;
} else {
return em.merge(entity);
}
}
우리가 알고 있는 save를 까보면 다음과 같이 되어져있는데 여기에 persist, merge로 나눠있는게 보일 거다.
- 새로운 엔티티면 persist ->(판별 기준 : id가 null 일 때)
- 기존의 엔티티면 merge
두 가지의 차이에 대해서 얘기하면 한마디로 얘기하자면 Merge는 Detached 상태의 Entity를 다시 영속화 하는데 사용
되고
Persist는 최초 생성된 Entity를 영속화
하는데 사용된다.
쉽게 말하면 3번 케이스와 같이 이미 저장이 먼저 되어졌었던 친구에 대해서 다시 수정하려면 merge라는 과정을 거치면서 한다.
기존의 엔티티에 대해서 조작을 진행하면 merge가 진행이 된다.
다음과 같이 본질적으로 진행이 된다.
기존에 존재하던 db의 레코드를 조회해서 다시 값을 수정하려할 때는 merge가 발동되어서 db에서 원본 엔티티를 카피하고 카피한 객체의 값을 수정하고 연관관계가 맺어진 엔티티에서는 레퍼런스도 카피 객체로 바꿔치기 한다.
MySQL의 MyIsam 스토리지 엔진을 사용할 경우 트랜잭션이 작동하지 않는다.
- MyISAM은 트랜잭션 엔진이 아니기 때문에 자동 커밋 모드에서 효과적으로 작동하며 커밋/롤백을 무시합니다.
- 실제로 스토리지 엔진은 SQL 파서와 분리된 MySQL 아키텍처의 다른 레이어이며, SQL 레이어는 하위 수준 API를 사용하여 스토리지 엔진과 통신합니다. 이것이 공통 SQL 및 엔진이 있는 이유이며, 다양한 기능 하위 집합을 지원합니다.
그래서 이것때문에 계속 찾아보고 MySQL 의 스토리지 엔진 중 하나이 InnoDB를 사용하여 테스트를 진행하였다.
스토리지 엔진도 잘 알아보고 사용해야한다는 것을 몸소 느꼈다.. 😅😅
그래서 다음과 같이 고쳐줬다.
기존 :
database-platform: org.hibernate.dialect.MySQL5Dialect
변경 후 :
database-platform: org.hibernate.dialect.MySQL57Dialect
위의 경우 MyIsam
사용 밑의 경우 InnoDB
사용