JavaRush /Blog Jawa /Random-JV /Nyimpen file menyang aplikasi lan data babagan menyang da...

Nyimpen file menyang aplikasi lan data babagan menyang database

Diterbitake ing grup
Nyimpen file menyang aplikasi lan data babagan menyang database - 1 Coba bayangake yen sampeyan lagi nggarap aplikasi web sampeyan. Ing artikelku, aku ndeleng potongan-potongan mozaik iki, kayata: Kepiye topik kasebut migunani? Lan kasunyatan manawa conto-conto kasebut cedhak banget kanggo nggarap proyek nyata, lan nguji topik kasebut bakal migunani banget kanggo sampeyan. Dina iki kita bakal njupuk potongan mozaik iki - nggarap file, amarga saiki sampeyan ora bisa nemokake situs sing ora sesambungan karo wong-wong mau (contone, kabeh jinis toko web, jaringan sosial, lan liya-liyane). Tinjauan kasebut bakal ditindakake kanthi nggunakake metode upload / download / mbusak minangka conto ; kita bakal nyimpen ing folder (ing sumber daya) ing aplikasi kita, supaya ora dadi rumit. Nyimpen file menyang aplikasi lan data babagan menyang database - 2Ide iki yaiku kita bakal nyimpen, saliyane file kasebut dhewe ing panyimpenan file sing disebut, entitas kanthi informasi babagan file (ukuran, jeneng, lan liya-liyane) ing database kita - tabel sing wis digawe. Yaiku, nalika ngemot file, entitas iki bakal migunani banget kanggo kita, lan nalika mbusak, kita ora kudu lali babagan iki. Ayo dadi njupuk dipikir ing tabel iki: Nyimpen file menyang aplikasi lan data babagan menyang database - 3Lan ayo kang nyetel id kanggo AUTO_INCREMENT kaya wong diwasa, kanggo otomatis generate pengenal ing tingkat database. Pisanan, ayo deleng struktur kita: Nyimpen file menyang aplikasi lan data babagan menyang database - 4Entitas ing tabel sing ditampilake ing ndhuwur:
@Builder(toBuilder = true)
@Getter
@ToString
public class FileInfo {

   private Long id;

   private String name;

   private Long size;

   private String key;

   private LocalDate uploadDate;
}

Upload

Ayo ndeleng controller:
@RestController
@RequestMapping("/file")
@RequiredArgsConstructor
public class FileController {

   private final FileService fileService;

   @PostMapping
   public ResponseEntity<FileInfo> upload(@RequestParam MultipartFile attachment) {
       try {
           return new ResponseEntity<>(fileService.upload(attachment), HttpStatus.CREATED);
       } catch (IOException e) {
           return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
       }
   }
Iku menarik: 9 - Kita nampa file ing wangun MultipartFile. Uga bisa ditampa minangka Uploaded byte, nanging aku luwih seneng pilihan iki, amarga kita bisa MultipartFileextract macem-macem sifat saka file ditransfer. 10 - 14 - kita mbungkus tumindak try catchsupaya yen ana pangecualian ing tingkat ngisor, kita nerusake luwih dhuwur lan ngirim kesalahan 400 minangka respon. Sabanjure yaiku tingkat layanan:
public interface FileService {

   FileInfo upload(MultipartFile resource) throws IOException;
Ayo ndeleng implementasine:
@Service
@RequiredArgsConstructor
public class FileServiceImpl implements FileService {

   private final FileDAO fileDAO;
   private final FileManager fileManager;

   @Transactional(rollbackFor = {IOException.class})
   @Override
   public FileInfo upload(MultipartFile resource) throws IOException {
       String key = generateKey(resource.getName());
       FileInfo createdFile = FileInfo.builder()
               .name(resource.getOriginalFilename())
               .key(key)
               .size(resource.getSize())
               .build();
       createdFile = fileDAO.create(createdFile);
       fileManager.upload(resource.getBytes(), key);

       return createdFile;
   }
8 - yen ana IOException, kabeh sing disimpen ing basis data bakal digulung maneh. 11 - kita ngasilake kunci sing bakal unik kanggo file nalika disimpen (sanajan loro file kanthi jeneng sing padha disimpen, ora bakal ana kebingungan). 12 - kita mbangun entitas kanggo nyimpen ing database. 17 - kita drive entitas karo informasi menyang database. 18 - simpen file kanthi jeneng hash. 20 — kita bali entitas digawe FileInfo, nanging karo id kui ing database (iki bakal rembugan ing ngisor iki) lan tanggal nggawe. Cara kanggo ngasilake kunci menyang file:
private String generateKey(String name) {
   return DigestUtils.md5Hex(name + LocalDateTime.now().toString());
}
Ing kene kita duwe jeneng + tanggal nggawe, sing bakal njamin keunikan kita. Antarmuka lapisan Dao:
public interface FileDAO {

   FileInfo create(FileInfo file);
Implementasine:
@Repository
@RequiredArgsConstructor
public class FileDAOImpl implements FileDAO {

   private static final String CREATE_FILE = "INSERT INTO files_info(file_name, file_size, file_key, upload_date) VALUES (?, ?, ?, ?)";

   private final JdbcTemplate jdbcTemplate;

   @Override
   public FileInfo create(final FileInfo file) {
       LocalDate uploadDate = LocalDate.now();
       GeneratedKeyHolder keyHolder = new GeneratedKeyHolder();
       jdbcTemplate.update(x -> {
           PreparedStatement preparedStatement = x.prepareStatement(CREATE_FILE, Statement.RETURN_GENERATED_KEYS);
           preparedStatement.setString(1, file.getName());
           preparedStatement.setLong(2, file.getSize());
           preparedStatement.setString(3, file.getKey());
           preparedStatement.setDate(4, Date.valueOf(uploadDate));
           return preparedStatement;
       }, keyHolder);

       return file.toBuilder()
               .id(keyHolder.getKey().longValue())
               .uploadDate(uploadDate)
               .build();
   }
11 - nggawe tanggal sing bakal kita simpen. 12 - 21 - kita nyimpen entitas kasebut, nanging kanthi cara sing luwih rumit, kanthi nggawe obyek sing eksplisit PreparedStatementsupaya id sing digawe bisa ditarik metu (ora narik panjaluk sing kapisah, nanging ing bentuk metadata respon. ). 22 - 26 - kita ngrampungake pambangunan entitas sing sabar lan menehi menyang ndhuwur (nyatane, dheweke ora ngrampungake, nanging nggawe obyek anyar, ngisi kolom sing ditransfer lan nyalin sisa saka asline) . Ayo ndeleng carane file kita bakal disimpen ing FileManager:
public void upload(byte[] resource, String keyName) throws IOException {
   Path path = Paths.get(DIRECTORY_PATH, keyName);
   Path file = Files.createFile(path);
   FileOutputStream stream = null;
   try {
       stream = new FileOutputStream(file.toString());
       stream.write(resource);
   } finally {
       stream.close();
   }
}
1 - kita nampa file kasebut minangka array byte lan kanthi kapisah jeneng sing bakal disimpen (kunci sing digawe). 2 - 3 - nggawe path (lan ing path kita nulis path plus tombol kita) lan file bebarengan. 6 - 7 - nggawe stream lan nulis bita kita ana (lan mbungkus kabeh iki try-finallykanggo mesthekake yen stream mesthi bakal nutup). Nanging, akeh cara bisa mbuwang IOException. Ing kasus iki, thanks kanggo nerusake kasebut ing header cara, kita bakal pass menyang controller lan menehi status 400. Ayo nyoba kabeh ing Postman: Nyimpen file menyang aplikasi lan data babagan menyang database - 5Kaya sing kita deleng, kabeh apik, tanggapane 201, tanggapan JSON teka ing bentuk entitas sing terus-terusan ing database, lan yen kita ndeleng panyimpenan kita: DB: Nyimpen file menyang aplikasi lan data babagan menyang database - 6kita bakal weruh yen kita duwe nilai anyar. (=*;*=)

Ngundhuh

pengontrol:
@GetMapping(path = "/{id}", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
public ResponseEntity<Resource> download(@PathVariable("id") Long id) {
   try {
       FileInfo foundFile = fileService.findById(id);
       Resource resource = fileService.download(foundFile.getKey());
       return ResponseEntity.ok()
               .header("Content-Disposition", "attachment; filename=" + foundFile.getName())
               .body(resource);
   } catch (IOException e) {
       return new ResponseEntity<>(HttpStatus.NOT_FOUND);
   }
}
Kita uga mbungkus try-catchlan ing kasus IOException kita ngirim status respon 404 (ora ditemokake). 4 - narik metu entitas FileInfo jejer saka database. 5 - nggunakake tombol saka entitas, download file 6-8 - ngirim maneh file, nambah jeneng file menyang header (maneh, dijupuk saka entitas karo informasi bab file). Ayo dideleng luwih jero. Antarmuka layanan:
Resource download(String key) throws IOException;

FileInfo findById(Long fileId);
Implementasine:
@Override
public Resource download(String key) throws IOException {
   return fileManager.download(key);
}

@Transactional(readOnly = true)
@Override
public FileInfo findById(Long fileId) {
   return fileDAO.findById(fileId);
}
Ora ana apa-apa sing menarik banget ing kene: cara nggoleki entitas kanthi id lan ngundhuh file, kajaba mbok menawa 46 - kita menehi tandha yen kita duwe transaksi kanggo maca. Tingkat Dao:
FileInfo findById(Long fileId);
Implementasine:
private static final String FIND_FILE_BY_ID = "SELECT id, file_name, file_size, file_key, upload_date FROM files_info WHERE id = ?";

@Override
public FileInfo findById(Long fileId) {
   return jdbcTemplate.queryForObject(FIND_FILE_BY_ID, rowMapper(), fileId);
}

private RowMapper<FileInfo> rowMapper() {
   return (rs, rowNum) -> FileInfo.builder()
           .id(rs.getLong("id"))
           .name(rs.getString("file_name"))
           .size(rs.getLong("file_size"))
           .key(rs.getString("file_key"))
           .uploadDate(rs.getObject("upload_date", LocalDate.class))
           .build();
}
4 - telusuran kanthi id nggunakake jdbcTemplatelan RowMapper. 8 - 15 - implementasine RowMapperkanggo kasus tartamtu, kanggo mbandhingake data saka lapangan database lan model. Ayo FileManagergoleki carane file kita dimuat:
public Resource download(String key) throws IOException {
   Path path = Paths.get(DIRECTORY_PATH + key);
   Resource resource = new UrlResource(path.toUri());
   if (resource.exists() || resource.isReadable()) {
       return resource;
   } else {
       throw new IOException();
   }
}
Kita bali file minangka obyek Resource, lan kita bakal nelusuri dening tombol. 3 - nggawe Resourcesadawane path + tombol. 4 - 8 - priksa manawa file ing dalan sing diwenehake ora kosong lan maca. Yen kabeh iku OK, kita bali, lan yen ora, kita uncalan IOException menyang ndhuwur. Ayo dipriksa cara kita ing Postman: Nyimpen file menyang aplikasi lan data babagan menyang database - 7Kaya sing sampeyan ngerteni, kerjane OK))

Mbusak

@DeleteMapping(value = "/{id}")
public ResponseEntity<Void> delete(@PathVariable("id") Long id) {
   try {
       fileService.delete(id);
       return new ResponseEntity<>(HttpStatus.OK);
   } catch (IOException e) {
       return new ResponseEntity<>(HttpStatus.NOT_FOUND);
   }
}
Ora ana sing khusus ing kene: kita uga ngasilake 404 yen gagal nggunakake try-catch. Antarmuka layanan:
void delete(Long fileId) throws IOException;
Implementasine:
@Transactional(rollbackFor = {IOException.class})
@Override
public void delete(Long fileId) throws IOException {
   FileInfo file = fileDAO.findById(fileId);
   fileDAO.delete(fileId);
   fileManager.delete(file.getKey());
}
1 - uga mbalek maneh owah-owahan data (pambusakan) nalika ana IOException. 5 - mbusak informasi babagan file saka database. 6 - mbusak file kasebut saka "panyimpenan" kita. antarmuka dao:
void delete(Long fileId);
Implementasine:
private static final String DELETE_FILE_BY_ID = "DELETE FROM files_info WHERE id = ?";

@Override
public void delete(Long fileId) {
   jdbcTemplate.update(DELETE_FILE_BY_ID, fileId);
}
Ora kaya ngono - mung mbusak. Mbusak file kasebut dhewe:
public void delete(String key) throws IOException {
       Path path = Paths.get(DIRECTORY_PATH + key);
       Files.delete(path);
   }
}
Kita nggunakake Postman: Nyimpen file menyang aplikasi lan data babagan menyang database - 8We katon ing panyimpenan: Nyimpen file menyang aplikasi lan data babagan menyang database - 9Kosong :) Saiki ing database: Nyimpen file menyang aplikasi lan data babagan menyang database - 10Kita weruh yen kabeh iku apik))

Tes

Ayo nyoba nulis tes kanggo kita FileManager. Pisanan, ayo ndeleng struktur bagean tes: mockFile.txt yaiku file sing bakal nyoba operasi karo panyimpenan file. testFileStorage bakal dadi panggantos kanggo panyimpenan kita. FileManagerTest:
public class FileManagerTest {

   private static MultipartFile multipartFile;

   private static FileManager manager;

   private static FileInfo file;

   @BeforeClass
   public static void prepareTestData() throws IOException {
       file = FileInfo.builder()
               .id(9L)
               .name("mockFile.txt")
               .key("mockFile.txt")
               .size(38975L)
               .uploadDate(LocalDate.now())
               .build();
       multipartFile = new MockMultipartFile("mockFile", "mockFile.txt", "txt",
               new FileInputStream("src/test/resources/mockFile.txt"));
       manager = new FileManager();
   }
Kene kita ndeleng assignment data test. Tes nyimpen file:
@Test
public void uploadTest() throws IOException {
   ReflectionTestUtils.setField(manager, "DIRECTORY_PATH", "src/test/resources/testFileStorage/");

   manager.upload(multipartFile.getBytes(), "mockFile.txt");

   Path checkFile = Paths.get("src/test/resources/testFileStorage/mockFile.txt");
   assertThat(Files.exists(checkFile)).isTrue();
   assertThat(Files.isRegularFile(checkFile)).isTrue();
   assertThat(Files.size(checkFile)).isEqualTo(multipartFile.getSize());
   Files.delete(checkFile);
}
3 - nggunakake refleksi test, kita ngganti pancet ing layanan kanggo nyetel path kanggo nyimpen file. 5 - nelpon cara sing dites. 7 - 10 - mriksa eksekusi sing bener saka nyimpen. 11 - mbusak file sing disimpen (kita ora kudu ninggalake jejak).
@Test
public void downloadTest() throws IOException {
   ReflectionTestUtils.setField(manager, "DIRECTORY_PATH", "src/test/resources/");

   Resource resource = manager.download(file.getKey());

   assertThat(resource.isFile()).isTrue();
   assertThat(resource.getFilename()).isEqualTo(file.getName());
   assertThat(resource.exists()).isTrue();
}
Test upload file: 3 - maneh, ngganti path kanggo kita FileManager. 5 - gunakake metode sing diuji. 7 - 9 - mriksa asil eksekusi. Tes mbusak file:
@Test
public void deleteTest() throws IOException {
   Path checkFile = Paths.get("src/test/resources/testFileStorage/mockFile.txt");
   Files.createFile(checkFile);
   assertThat(Files.exists(checkFile)).isTrue();
   assertThat(Files.isRegularFile(checkFile)).isTrue();
   ReflectionTestUtils.setField(manager, "DIRECTORY_PATH", "src/test/resources/testFileStorage/");

   manager.delete(file.getKey());

   assertThat(Files.notExists(checkFile)).isTrue();
}
9 - 3 - 4 - nyetel path lan nggawe file. 5 - 6 - kita mriksa orane. 9 - kita nggunakake cara sing bisa diverifikasi. 77 - kita priksa manawa obyek kasebut ora ana maneh. Lan ayo ndeleng apa sing ana ing babagan dependensi:
<dependencies>
   <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-data-jdbc</artifactId>
   </dependency>
   <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-web</artifactId>
   </dependency>
   <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-test</artifactId>
       <scope>test</scope>
       <exclusions>
           <exclusion>
               <groupId>org.junit.vintage</groupId>
               <artifactId>junit-vintage-engine</artifactId>
           </exclusion>
       </exclusions>
   </dependency>
   <dependency>
       <groupId>org.projectlombok</groupId>
       <artifactId>lombok</artifactId>
       <version>1.18.10</version>
       <scope>provided</scope>
   </dependency>
   <dependency>
       <groupId>mysql</groupId>
       <artifactId>mysql-connector-java</artifactId>
       <version>8.0.18</version>
   </dependency>
   <dependency>
       <groupId>commons-codec</groupId>
       <artifactId>commons-codec</artifactId>
       <version>1.9</version>
   </dependency>
   <dependency>
       <groupId>junit</groupId>
       <artifactId>junit</artifactId>
       <version>4.13-rc-2</version>
       <scope>test</scope>
   </dependency>
</dependencies>
Iku kabeh kanggo aku dina iki))
Komentar
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION