DeleteMapping例
// 省略
@RequestMapping(value = "/api/person")
@RestController
@Slf4j
public class PersonController {
@Autowired
private PersonService personService;
@GetMapping("/{id}")
public Person getPerson(@PathVariable Long id) {
return personService.getPerson(id);
}
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public void postPerson(@RequestBody Person person) {
personService.put(person);
log.info("person -> {}", personRepository.findAll());
}
@PutMapping("/{id}")
public void modifyPerson(@PathVariable Long id, @RequestBody PersonDto person){
personService.modify(id, person);
log.info("person -> {}", personRepository.findAll());
}
@PatchMapping // 一部のリソースだけ更新
public void modifyPerson(@PathVariable Long id, String name) {
personService.modify(id, name);
log.info("person -> {}", personRepository.findAll());
}
@DeleteMapping("/{id}")
public void deletePerson(@PathVariable Long id) {
personService.delete(id);
log.info("person -> {}", personRepository.findAll());
}
}
クライアントから「/api/person/1」Delete REST APIが送られると、
@DeleteMappingのメソッドが該当します。そして、Controller ⇨ Service層に「id」を渡します。
// 省略
@Service
@Slf4j
public class PersonService {
@Autowired
private PersonRepository personRepository;
@Autowired
private BlockRepository blockRepository;
@Transactional
public void delete(Long id) {
Person person = personRepository.findById(id).orElseThrow(() -> new RuntimeException("not existed id"));
personRepository.delete(person);
}
}
personRepositoryから取得したidが「1」のpersonオブジェクトをJPA deleteメソッドにて削除できます。
その2行は以下の1行で代替できます
→ personRepository.deleteById(id);
// 省略
@SpringBootTest
@Slf4j
public class PersonControllerTest {
@Autowired
private PersonController personController;
@Autowired
private PersonRepository personRepository;
private MockMvc mockMvc;
// テスト実施する前に必ず実施する!
@BeforeEach
void beforeEach() {
mockMvc = MockMvcBuilders.standaloneSetup(personController).build();
}
@Test
void deletePerson() throws Exception {
mockMvc.perform(
MockMvcRequestBuilders.delete("/api/person/1"))
.andDo(print())
.andExpect(status().isOk());
log.info("people deleted : {}", personRepository.findPeopleDeleted());
}
}
テストコードです。MockMvcRequestBuilders.deleteメソッドのため、
PersonControllerクラスの@DeleteMapping("/{id}")メソッドにマッチされます。
別の話ですが、@BeforeEachメソッドでmockMvcを生成しています。@BeforeEachメソッドは各テストが実施する前に必ず実行されるメソッドであり、テストコードで毎回生成したmockMvcを@BeforeEachメソッドで書くことにより、便利になりました。
(movkMvc:サブレットコンテナの駆動なしに、シミュレーションされたMVC環境に模擬HTTPサブレット要求を伝送する機能を提供するユーティリティクラスだ。)
Soft Delete方法
上の例は、実際のデータを物理削除していますが、最近はデータを物理削除することではなくFlagを使用してデータを削除します。(テーブルからデータを削除なく、Delete Flagがtrueなら削除扱いとする)
// 省略
@Entity
@NoArgsConstructor
@AllArgsConstructor
@RequiredArgsConstructor
@Data
@Where(clause = "deleted = false") // soft delete 의 경우, 삭제되지 않은 데이터를 추출하기 위해서 AndDeletedIsTrue 를 붙혀주어야 했지만 , WHERE어테이션으로 인해 필요없게
public class Person {
@ColumnDefault("0") // 0 == false, tureなら削除扱い
private boolean deleted;
}
Person Entityクラスにdeletedカラムを新たに設けます。
そしてクラスに@Where(clause = "deleted = false")も新たに設けます。
@Whereアノテーションは、Queryが発行するたびにWhere句に@Whereに定義した条件が追加されます。
「"deleted = false"」を定義した理由としては、いつも削除されていないデータ(deletedがfalse)のみをselectするためになります。そうじゃないと、いつも削除したデータも一緒にselectされてしまいます。
@Where(clause = "deleted = false")を書く代わりに、Repository層でも"削除されていないデータだけ抽出する"対応が可能ですが、
対応としてはメソッド名も末尾にAndDeletedIsTrueをつけないといけませんので、効率的な対応にはなりません。
(findByName → findByNameAndDeletedIsTure)
これで、物理的にデータ削除したのをFlagを使用して削除扱いすることが可能になります。
削除したデータを抽出したい場合
Person Entityクラスに@Where(clause = "deleted = false") を定義したため、すべてのqueryは削除されていない人を抽出する条件文が足されます。そのため、削除されたデータを見ることができなくなりました。 その場合は、RepositoryクラスにfindPeopleDeleted()を定義して、@Queryには、person.deleteがtrueの条件を設定します。そして、nativeQuery = trueを追加定義してあげます。 nativeQuery = trueによりPerson Entityの属性を使わずに
@Queryに定義したQueryだけを実行します。そのため、@Where(clause = "deleted = false")はスルーされ、「person.deleted = true」のみのデータがselectされます。
// 省略
public interface PersonRepository extends JpaRepository<Person, Long> {
List<Person> findByName(String name);
List<Person> findByBlockIsNull();
List<Person> findByBloodType(String bloodType);
@Query(value = "select person from Person person where person.birthday.monthOfBirthday = :monthOfBirthday")
List<Person> findByMonthOfBirthday(@Param("monthOfBirthday") int monthOfBirthday);
@Query(value = "select person from Person person where person.deleted = true", nativeQuery = true)
List<Person> findPeopleDeleted();
}