✂️이번엔 글을 삭제할 때 파일도 삭제하도록 수정해보자!
외래키로 연관된 데이터를 삭제하는 방법은 테이블에 ON DELETE CASCADE를 주면 됩니다. 하지만 여기서는 @Transactional을 사용하기 위해 다른 방법으로 합니다.
post_id로 조회와 수정을 할 수 있도록 수정합니다.
public interface FileMapper {
public void save(FileVO fileVO);
public List<FileVO> findByPostId(int postId);
public FileVO findById(int id);
public void deleteByPostId(int postId);
}
<mapper namespace="ac.kr.smu.mapper.FileMapper">
<insert id="save">
INSERT INTO file(name,uuid,upload_path,post_id)
VALUES(#{name},#{uuid},#{uploadPath},#{postId})
</insert>
<select id="findById" resultType="FileVO">
SELECT * FROM file WHERE id = #{id}
</select>
<delete id="deleteByPostId">
DELETE FROM file WHERE post_id = #{postId}
</delete>
<select id="findByPostId" resultType="FileVO">
SELECT * FROM file WHERE post_id = #{postId}
</select>
</mapper>
post_id로 삭제할 수 있도록 수정합니다. 업로드된 파일과 DB에 저장된 파일의 정보를 삭제합니다.
public interface FileService {
public List<FileVO> saveAll(int postId,List<MultipartFile> files);
public FileSystemResource getFileSystemResource(int id);
public void deleteByPostId(int postId);
}
@Override
public void deleteByPostId(int postId) {
List<FileVO> fileList = fileMapper.findByPostId(postId);
fileMapper.deleteByPostId(postId);
fileList.stream().forEach(f ->{
File file = new File(f.getPath());
file.delete();
});
}
글을 삭제할 때 연관된 파일도 같이 삭제할 수 있도록 수정합니다. 이때 오류가 발생하면 반영이 되면 안되므로 @Transactional Annotation을 추가합니다.
@Override
@Transactional
public void delete(int id) {
fileService.deleteByPostId(id);
postMapper.delete(id);
}
@Transactional은 프록시 객체를 생성하여 실행하던 도중 runtime error가 발생하면 rollback을 할 수 있도록 하는 Anntoation입니다. 이를 사용하려면 설정을 해주어야 하는데 이 설정은 앞에서 하였습니다.
글과 연관된 파일은 같이 삭제되어야해 PostService에서FileSerivce를 참조해 삭제하는 식으로 구현하였습니다. Controller계층에서는 @Transactional Annotation이 수행되지 않습니다.
Service에서 DAO는 1:1 관계이어야 합니다. Service에서 다른 Service는 참조 가능하나 그 계층이 명확히 나뉘어있어야 합니다.
한번 RuntimeException을 발생시켜 @Transactional이 제대로 되는지 확인해보겠습니다.
SQL과 파라미터를 확인하기 위해 log4jdbc.log4j2 라이브러리를 설치하고 설정한 상태입니다. 아래와 같이 설정하면 실행되는 SQL과 파라미터들을 확인할 수 있습니다.
pom.xml
<!-- https://mvnrepository.com/artifact/org.bgee.log4jdbc-log4j2/log4jdbc-log4j2-jdbc4.1 --> <dependency> <groupId>org.bgee.log4jdbc-log4j2</groupId> <artifactId>log4jdbc-log4j2-jdbc4.1</artifactId> <version>1.16</version> </dependency>
log4jdbc.log4j2.properties
webapp 바깥에 있는 resources폴더에 생성합니다.
log4jdbc.spylogdelegator.name=net.sf.log4jdbc.log.slf4j.Slf4jSpyLogDelegator
log4j.xml
<!-- log4jdbc-log4j2 --> <logger name="jdbc.sqlonly"> <level value="debug"/> </logger> <logger name="jdbc.sqltiming"> <level value="info"/> </logger> <logger name="jdbc.audit"> <level value="warn"/> </logger> <logger name="jdbc.resultset"> <level value="error"/> </logger> <logger name="jdbc.resultsettable"> <level value="warn"/> </logger> <logger name="jdbc.connection"> <level value="info"/> </logger>
RootConfig 클래스
@Bean public ComboPooledDataSource comboPooledDataSource(){ ComboPooledDataSource dataSource = new ComboPooledDataSource(); try{ dataSource.setDriverClass("net.sf.log4jdbc.sql.jdbcapi.DriverSpy"); dataSource.setJdbcUrl("jdbc:log4jdbc:mysql://localhost:3306/spring?allowMultiQueries=true"); dataSource.setUser("spring"); dataSource.setPassword("1111"); dataSource.setCheckoutTimeout(3000); }catch (Exception e){e.printStackTrace();} return dataSource; }
@Override
@Transactional
public void deleteByPostId(int postId) {
List<FileVO> fileList = fileMapper.findByPostId(postId);
fileMapper.deleteByPostId(postId);
fileList.stream().forEach(f ->{
File file = new File(f.getPath());
file.delete();
});
throw new RuntimeException();
}
다음과 같이 DELETE SQL이 수행되었지만 데이터가 삭제되지 않은 것을 확인할 수 있습니다.
파일이 삭제되는 것을 막는 것은 아닙니다.