JavaRush /Java blogi /Random-UZ /Ilovaga fayllarni va ular haqidagi ma'lumotlarni ma'lumot...

Ilovaga fayllarni va ular haqidagi ma'lumotlarni ma'lumotlar bazasiga saqlash

Guruhda nashr etilgan
Ilovaga fayllarni va ular haqidagi ma'lumotlarni ma'lumotlar bazasiga saqlash - 1 Tasavvur qilaylik, siz veb-ilovangiz ustida ishlayapsiz. Maqolalarimda men ushbu mozaikaning alohida qismlarini ko'rib chiqaman, masalan: Bu mavzular qanchalik foydali? Va bu misollar haqiqiy loyihalar ustida ishlashga juda yaqin ekanligi va bu mavzularni sinab ko'rish siz uchun juda foydali bo'ladi. Bugun biz ushbu mozaikaning navbatdagi qismini olamiz - fayllar bilan ishlash, chunki bugungi kunda siz ular bilan aloqa qilmaydigan saytni topa olmaysiz (masalan, barcha turdagi veb-do'konlar, ijtimoiy tarmoqlar va boshqalar). Ko'rib chiqish misol sifatida yuklash/yuklash/o'chirish usullari yordamida amalga oshiriladi ; biz uni murakkablashtirmaslik uchun ilovamizdagi papkaga (resursda) saqlaymiz. Ilovaga fayllarni va ular haqidagi ma'lumotlarni ma'lumotlar bazasiga saqlash - 2G'oya shundan iboratki, biz faylning o'zidan tashqari, bizning ma'lumotlar bazasida faylimiz (hajmi, nomi va boshqalar) haqidagi ma'lumotlarga ega bo'lgan ob'ektni - oldindan yaratilgan jadvalni saqlaymiz. Ya'ni, faylni yuklashda ushbu ob'ekt biz uchun juda foydali bo'ladi va o'chirishda biz bu haqda hech qanday tarzda unutmasligimiz kerak. Keling, ushbu jadvalni ko'rib chiqaylik: Ilovaga fayllarni va ular haqidagi ma'lumotlarni ma'lumotlar bazasiga saqlash - 3Ma'lumotlar bazasi darajasida identifikatorni avtomatik ravishda yaratish uchun identifikatorni kattalar kabi AUTO_INCREMENT ga o'rnatamiz. Birinchidan, tuzilmamizni ko'rib chiqaylik: Ilovaga fayllarni va ular haqidagi ma'lumotlarni ma'lumotlar bazasiga saqlash - 4Yuqorida ko'rsatilgan jadval ostidagi ob'ekt:
@Builder(toBuilder = true)
@Getter
@ToString
public class FileInfo {

   private Long id;

   private String name;

   private Long size;

   private String key;

   private LocalDate uploadDate;
}

Yuklash

Keling, boshqaruvchini ko'rib chiqaylik:
@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);
       }
   }
Qiziqarli narsalar: 9 - Biz faylni shaklda qabul qilamiz MultipartFile. U baytlar qatori sifatida ham qabul qilinishi mumkin, lekin menga bu variant ko'proq yoqadi, chunki biz MultipartFileuzatilgan faylning turli xususiyatlarini ajratib olishimiz mumkin. 10 - 14 - biz o'z harakatlarimizni try catchshunday qilamizki, agar istisno pastroq darajada bo'lsa, biz uni yuqoriga yo'naltiramiz va javob sifatida 400 xato yuboramiz. Keyingi xizmat darajasi:
public interface FileService {

   FileInfo upload(MultipartFile resource) throws IOException;
Keling, amalga oshirishni ko'rib chiqaylik:
@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 - IOException bo'lsa, ma'lumotlar bazasidagi barcha saqlashlarimiz orqaga qaytariladi. 11 - fayl saqlanganida noyob bo'ladigan kalitni yaratamiz (hatto bir xil nomdagi ikkita fayl saqlangan bo'lsa ham, hech qanday chalkashlik bo'lmaydi). 12 - biz ma'lumotlar bazasida saqlash uchun ob'ekt quramiz. 17 - biz ob'ektni ma'lumotlar bazasiga kiritamiz. 18 - faylni xeshlangan nom bilan saqlang. 20 - biz yaratilgan ob'ektni qaytaramiz FileInfo, lekin ma'lumotlar bazasida yaratilgan id (bu quyida muhokama qilinadi) va yaratilish sanasi bilan. Fayl kalitini yaratish usuli:
private String generateKey(String name) {
   return DigestUtils.md5Hex(name + LocalDateTime.now().toString());
}
Bu erda biz nom + yaratilish sanasini belgilaymiz, bu bizning o'ziga xosligimizni ta'minlaydi. Dao qatlami interfeysi:
public interface FileDAO {

   FileInfo create(FileInfo file);
Uning amalga oshirilishi:
@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 - biz saqlaydigan sanani yarating. 12 - 21 - biz ob'ektni saqlaymiz, lekin PreparedStatementyaratilgan identifikatorni chiqarib olish uchun ob'ektni aniq yaratish bilan yanada murakkab usulda (uni alohida so'rov bilan emas, balki javob metama'lumotlari shaklida chiqaradi) ). 22 - 26 - biz sabr-toqatli shaxsimizni qurishni yakunlaymiz va uni yuqoriga beramiz (aslida u uni tugatmaydi, balki yangi ob'ekt yaratadi, o'tkazilgan maydonlarni to'ldiradi va qolganlarini asl nusxadan nusxalaydi) . Keling, fayllarimiz qanday saqlanishini ko'rib chiqaylik 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 - biz faylni baytlar massivi sifatida qabul qilamiz va u saqlanadigan nom (bizning yaratilgan kalitimiz). 2 - 3 - yo'lni yarating (va yo'lda biz yo'lni va kalitimizni yozamiz) va uning bo'ylab fayl. try-finally6 - 7 - oqim yarating va u erga baytlarimizni yozing (va oqim albatta yopilishiga ishonch hosil qilish uchun bularning barchasini o'rab oling ). Biroq, ko'plab usullar IOExceptionni tashlashi mumkin. Bunday holda, usul sarlavhasida ko'rsatilgan yo'naltirish tufayli biz uni boshqaruvchiga o'tkazamiz va 400 holatini beramiz. Keling, hamma narsani Postman-da sinab ko'ramiz: Ilovaga fayllarni va ular haqidagi ma'lumotlarni ma'lumotlar bazasiga saqlash - 5Ko'rib turganingizdek, hamma narsa yaxshi, javob 201, JSON javobi ma'lumotlar bazasida bizning doimiy ob'ektimiz shaklida keldi va agar biz xotiramizga qarasak: JB: Ilovaga fayllarni va ular haqidagi ma'lumotlarni ma'lumotlar bazasiga saqlash - 6biz buni ko'ramiz. bizda yangi qiymat bor. (=*;*=)

Yuklab olish

Nazoratchi:
@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);
   }
}
Shuningdek, biz uni o'rab olamiz try-catchva IOException holatida biz 404 javob holatini yuboramiz (topilmadi). 4 - qo'shni FileInfo ob'ektini ma'lumotlar bazasidan chiqarib oling. 5 - ob'ekt kalitidan foydalanib, faylni yuklab oling 6-8 - fayl nomini sarlavhaga qo'shib faylni qaytarib yuboring (yana fayl haqida ma'lumot bilan ob'ektdan olingan). Keling, chuqurroq ko'rib chiqaylik. Xizmat interfeysi:
Resource download(String key) throws IOException;

FileInfo findById(Long fileId);
Amalga oshirish:
@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);
}
Bu erda ayniqsa qiziq narsa yo'q: id bo'yicha ob'ektni qidirish va faylni yuklab olish usuli, ehtimol 46 dan tashqari - biz o'qish uchun tranzaktsiyamiz borligini belgilaymiz. Dao darajasi:
FileInfo findById(Long fileId);
Amalga oshirish:
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();
}
jdbcTemplate4 — va yordamida id bo'yicha qidiruv RowMapper. 8 - 15 - RowMapperma'lumotlar bazasi va model maydonlaridan ma'lumotlarni solishtirish uchun bizning maxsus ishimiz uchun dastur. Keling, FileManagerfaylimiz qanday yuklanganligini ko'rib chiqaylik:
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();
   }
}
Biz faylni ob'ekt sifatida qaytaramiz Resourceva biz kalit bo'yicha qidiramiz. 3 - Resourceyo'l + kalit bo'ylab yarating. 4 - 8 - biz berilgan yo'ldagi fayl bo'sh emasligini tekshiramiz va uni o'qiymiz. Agar hamma narsa yaxshi bo'lsa, biz uni qaytaramiz va agar bo'lmasa, biz yuqoriga IOException tashlaymiz. Pochtachidagi usulimizni tekshirib ko'ramiz: Ilovaga fayllarni va ular haqidagi ma'lumotlarni ma'lumotlar bazasiga saqlash - 7Ko'rib turganingizdek, u yaxshi ishladi))

Oʻchirish

@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);
   }
}
Bu erda hech qanday maxsus narsa yo'q: dan foydalanilmasa, biz ham 404 ni qaytaramiz try-catch. Xizmat interfeysi:
void delete(Long fileId) throws IOException;
Amalga oshirish:
@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 - shuningdek, IOException sodir bo'lganda ma'lumotlar o'zgarishlarini (o'chirish) orqaga qaytarish. 5 - ma'lumotlar bazasidan fayl haqidagi ma'lumotlarni o'chirish. 6 - faylning o'zini bizning "saqlash" dan o'chirib tashlang. dao interfeysi:
void delete(Long fileId);
Amalga oshirish:
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);
}
Shunga o'xshash narsa yo'q - shunchaki o'chirib tashlang. Faylning o'zini o'chirish:
public void delete(String key) throws IOException {
       Path path = Paths.get(DIRECTORY_PATH + key);
       Files.delete(path);
   }
}
Biz Pochtachidan foydalanamiz: Ilovaga fayllarni va ular haqidagi ma'lumotlarni ma'lumotlar bazasiga saqlash - 8Biz omborga qaraymiz: Ilovaga fayllarni va ular haqidagi ma'lumotlarni ma'lumotlar bazasiga saqlash - 9Bo'sh :) Endi ma'lumotlar bazasida: Сохранение файлов в приложение и данных о них на БД - 10Hammasi yaxshi ekanligini ko'ramiz))

Sinov

Keling, biz uchun test yozishga harakat qilaylik FileManager. Birinchidan, test qismining tuzilishini ko'rib chiqamiz: mockFile.txt fayl bo'lib, biz fayllarni saqlash bilan operatsiyalarimizni sinab ko'ramiz. testFileStorage bizning xotiramizni almashtiradi. 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();
   }
Bu erda biz test ma'lumotlarining topshirig'ini ko'ramiz. Faylni saqlash testi:
@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 - test aks ettirishdan foydalanib, biz faylni saqlash yo'lini belgilash uchun xizmatdagi konstantani o'zgartiramiz. 5 - tekshirilayotgan usulni chaqiring. 7 - 10 - biz saqlashning to'g'ri bajarilishini tekshiramiz. 11 - saqlangan faylni o'chirish (biz hech qanday iz qoldirmasligimiz kerak).
@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();
}
Fayl yuklash testi: 3 - yana bizning yo'limizni o'zgartiring FileManager. 5 - tekshirilayotgan usuldan foydalaning. 7 - 9 - bajarilish natijasini tekshiring. Faylni o'chirish testi:
@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 - yo'lni belgilang va fayl yarating. 5 - 6 - biz uning mavjudligini tekshiramiz. 9 - biz tekshirilishi mumkin bo'lgan usuldan foydalanamiz. 77 - ob'ekt endi yo'qligini tekshiramiz. Keling, qaramlik nuqtai nazaridan bizda nima borligini ko'rib chiqaylik:
<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>
Bugun men uchun hammasi shu))
Izohlar
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION