JavaRush /Java блогы /Random-KK /Файлдарды қолданбаға және олар туралы мәліметтерді дерекқ...
Константин
Деңгей

Файлдарды қолданбаға және олар туралы мәліметтерді дерекқорға сақтау

Топта жарияланған
Файлдарды қолданбаға және олар туралы мәліметтерді мәліметтер базасына сақтау - 1 Сіз веб-қосымшада жұмыс істеп жатырсыз деп елестетейік. Мен өз мақалаларымда осы мозаиканың жеке бөліктерін қарастырамын, мысалы: Бұл тақырыптар қаншалықты пайдалы? Және бұл мысалдардың нақты жобалармен жұмыс істеуге өте жақын екендігі және бұл тақырыптарды сынау сізге өте пайдалы болады. Бүгін біз осы мозаиканың келесі бөлігін аламыз - файлдармен жұмыс, өйткені қазіргі уақытта сіз олармен әрекеттеспейтін сайтты таба алмайсыз (мысалы, веб-дүкендердің барлық түрлері, әлеуметтік желілер және т.б.). Қарау мысал ретінде жүктеу/жүктеп алу/жою әдістерін қолдану арқылы жүзеге асырылады ; біз оны қиындатпау үшін оны қолданбамыздағы қалтаға (ресурста) сақтаймыз. Файлдарды қолданбаға және олар туралы мәліметтерді мәліметтер базасына сақтау - 2Идея мынада: біз өзіміздің файл қоймамызда файлдың өзінен басқа, біздің дерекқорымызда файлымыз (көлемі, аты, т.б.) туралы ақпараты бар нысанды - алдын ала жасалған кестені сақтаймыз. Яғни, файлды жүктегенде, бұл нысан бізге өте пайдалы болады және жою кезінде біз бұл туралы қандай да бір жолмен ұмытпауымыз керек. Мына кестені қарастырайық: Файлдарды қолданбаға және олар туралы мәліметтерді мәліметтер базасына сақтау – 3Дерекқор деңгейінде идентификаторды автоматты түрде жасау үшін идентификаторды ересектер сияқты AUTO_INCREMENT етіп орнатайық. Алдымен құрылымымызды қарастырайық: Қолданбаға файлдарды және олар туралы мәліметтерді мәліметтер базасына сақтау - 4Жоғарыда көрсетілген кестенің астындағы нысан:
@Builder(toBuilder = true)
@Getter
@ToString
public class FileInfo {

   private Long id;

   private String name;

   private Long size;

   private String key;

   private LocalDate uploadDate;
}

Жүктеп салу

Контроллерді қарастырайық:
@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);
       }
   }
Кейбір қызықты нәрселер: 9 - Біз файлды пішінде қабылдаймыз MultipartFile. Оны byteтар массиві ретінде де алуға болады, бірақ маған бұл опция көбірек ұнайды, өйткені біз MultipartFileтасымалданатын файлдың әртүрлі қасиеттерін шығара аламыз. try catch10 - 14 - егер ерекшелік төменгі деңгейде орын алса, біз оны жоғары жіберіп, жауап ретінде 400 қате жіберетін етіп әрекеттерімізді орап аламыз . Келесі қызмет деңгейі:
public interface FileService {

   FileInfo upload(MultipartFile resource) throws IOException;
Іске асыруды қарастырайық:
@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 болған жағдайда, дерекқордағы барлық сақтауларымыз кері қайтарылады. 11 - біз файл сақталған кезде бірегей болатын кілтті жасаймыз (тіпті бірдей атаулары бар екі файл сақталса да, шатасу болмайды). 12 — дерекқорда сақтау үшін нысанды құрастырамыз. 17 — дерекқорға ақпараты бар нысанды енгіземіз. 18 - файлды хэштелген атаумен сақтау. 20 — біз жасалған нысанды қайтарамыз FileInfo, бірақ дерекқорда жасалған идентификатормен (бұл төменде талқыланады) және жасалған күнімен. Файлдың кілтін жасау әдісі:
private String generateKey(String name) {
   return DigestUtils.md5Hex(name + LocalDateTime.now().toString());
}
Мұнда біз атауды + жасау күнін хэштейміз, бұл біздің бірегейлігімізді қамтамасыз етеді. Дао қабатының интерфейсі:
public interface FileDAO {

   FileInfo create(FileInfo file);
Оны жүзеге асыру:
@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 - біз сақтайтын күнді жасаңыз. 12 - 21 - біз нысанды сақтаймыз, бірақ генерацияланған идентификаторды шығаруға болатындай нысанды анық жасау арқылы күрделі түрде сақтаймыз PreparedStatement(ол оны бөлек сұраумен емес, жауап метадеректері түрінде шығарады. ). 22 - 26 - біз көп шыдамды болмысымыздың құрылысын аяқтаймыз және оны жоғарыға береміз (шын мәнінде ол оны аяқтамайды, бірақ жаңа нысан жасайды, берілген өрістерді толтырып, қалғанын түпнұсқадан көшіреді) . Біздің файлдар қалай сақталатынын көрейік 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 — біз файлды byteтар массиві ретінде және ол сақталатын атауды бөлек қабылдаймыз (біздің жасалған кілт). 2 - 3 - жолды жасаңыз (және жолда біз жолды және кілтімізді жазамыз) және оның бойындағы файлды жасаңыз. 6 - 7 - ағын жасаңыз және сол жерге byteтарымызды жазыңыз (және try-finallyағынның міндетті түрде жабылатынына сенімді болу үшін осы заттардың барлығын ораңыз). Дегенмен, көптеген әдістер IOException жібере алады. Бұл жағдайда әдіс тақырыбында көрсетілген бағыттаудың арқасында біз оны контроллерге береміз және 400 мәртебесін береміз. Барлығын Postman-да сынап көрейік: Файлдарды қолданбаға және олар туралы мәліметтерді мәліметтер базасына сақтау - 5Көріп отырғаныңыздай, бәрі жақсы, жауап 201, JSON жауабы дерекқордағы тұрақты нысан түрінде келді және біздің қоймаға қарасақ: DB: біз Қосымшаға файлдарды және олар туралы мәліметтерді мәліметтер базасына сақтау - 6мұны көреміз. бізде жаңа құндылық бар. (=*;*=)

Жүктеп алу

Контроллер:
@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);
   }
}
Біз сондай-ақ оны орап аламыз try-catchжәне IOException жағдайында 404 жауап күйін жібереміз (табылмады). 4 - дерекқордан көрші FileInfo нысанын шығарып алыңыз. 5 - нысанның кілтін пайдаланып, файлды жүктеп алыңыз 6-8 - тақырыпқа файл атауын қосып, файлды кері жіберіңіз (қайтадан файл туралы ақпарат бар нысаннан алынған). Тереңірек қарастырайық. Қызметтік интерфейс:
Resource download(String key) throws IOException;

FileInfo findById(Long fileId);
Іске асыру:
@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);
}
Мұнда ерекше қызықты ештеңе жоқ: id бойынша нысанды іздеу және файлды жүктеу әдісі, мүмкін 46-дан басқа - біз оқуға арналған транзакция бар екенін белгілейміз. Дао деңгейі:
FileInfo findById(Long fileId);
Іске асыру:
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 — және көмегімен идентификатор бойынша іздеу RowMapper. 8 - 15 - RowMapperдерекқор мен үлгі өрістерінен деректерді салыстыру үшін біздің нақты жағдайымыз үшін іске асыру. Барып FileManager, файлымыздың қалай жүктелетінін көрейік:
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();
   }
}
Біз файлды нысан ретінде қайтарамыз Resourceжәне біз кілт арқылы іздейміз. 3 - Resourceжол + кілті бойымен жасаңыз. 4 - 8 — берілген жолдағы файлдың бос еместігін тексеріп, оны оқимыз. Егер бәрі жақсы болса, біз оны қайтарамыз, ал егер жоқ болса, IOException жоғарғы жағына лақтырамыз. Пошташыдағы әдісімізді тексерейік: Қосымшаға файлдарды және олар туралы мәліметтерді мәліметтер базасына сақтау – 7Көріп отырғанымыздай, ол жақсы жұмыс істеді))

Жою

@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);
   }
}
Мұнда ерекше ештеңе жоқ: пайдалану сәтсіз болған жағдайда біз 404 қайтарамыз try-catch. Қызметтік интерфейс:
void delete(Long fileId) throws IOException;
Іске асыру:
@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 - сонымен қатар IOException орын алған кезде деректер өзгерістерінің (жоюлардың) кері қайтарылуы. 5 - дерекқордан файл туралы ақпаратты жою. 6 - файлдың өзін біздің «қоймадан» жойыңыз. dao интерфейсі:
void delete(Long fileId);
Іске асыру:
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);
}
Мұндай ештеңе жоқ - жай ғана жойыңыз. Файлдың өзін жою:
public void delete(String key) throws IOException {
       Path path = Paths.get(DIRECTORY_PATH + key);
       Files.delete(path);
   }
}
Біз Пошташыны қолданамыз: Файлдарды қолданбаға және олар туралы мәліметтерді мәліметтер базасына сақтау – 8Сақтауға қараймыз: Қосымшаға файлдарды және олар туралы мәліметтерді мәліметтер базасына сақтау – 9Бос :) Енді базада: Сохранение файлов в приложение и данных о них на БД - 10Барлығы жақсы екенін көреміз))

Сынақ

Бізге тест жазып көрейік FileManager. Алдымен сынақ бөлігінің құрылымын қарастырайық: mockFile.txt - бұл файлды сақтаумен жұмысымызды тексеретін файл. testFileStorage сақтау орнымызды алмастырады. 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();
   }
Мұнда біз сынақ деректерінің тағайындалуын көреміз. Файлды сақтау сынағы:
@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 — сынақ рефлексиясын пайдаланып, файлды сақтау жолын орнату үшін қызметтегі тұрақтыны өзгертеміз. 5 — сыналатын әдісті шақыру. 7 - 10 - сақтаудың дұрыс орындалуын тексеріңіз. 11 - сақталған файлды жою (біз ешқандай із қалдырмауымыз керек).
@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();
}
Файлды жүктеп салу сынағы: 3 - қайтадан біздің жолды өзгертіңіз FileManager. 5 - сыналатын әдісті қолдану. 7 - 9 — орындалу нәтижесін тексеріңіз. Файлды жою сынағы:
@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 - жолды орнатыңыз және файл жасаңыз. 5 - 6 - біз оның бар-жоғын тексереміз. 9 - біз тексерілетін әдісті қолданамыз. 77 - нысанның енді жоқ екенін тексереміз. Тәуелділік тұрғысынан бізде не бар екенін көрейік:
<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>
Бүгін мен үшін барлығы осы))
Пікірлер
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION