Рубрики
Без рубрики

Экстернализация данных настройки с помощью CSV в приложении Spring

Узнайте, как использовать CSV-файлы для хранения данных установки веб-приложения Spring, а также как полностью загрузить и сохранить эти данные с диска.

Автор оригинала: baeldung.

1. Обзор

В этой статье мы экстернализируем установочные данные приложения с помощью CSV-файлов , а не жестко закодируем их.

Этот процесс настройки в основном связан с настройкой новых данных в новой системе.

2. Библиотека CSV

Давайте начнем с введения простой библиотеки для работы с CSV – расширением Jackson CSV :


    com.fasterxml.jackson.dataformat
    jackson-dataformat-csv       
    2.5.3

Конечно, в экосистеме Java есть множество доступных библиотек для работы с Csv.

Причина, по которой мы здесь идем с Джексоном, заключается в том, что, скорее всего, Джексон уже используется в приложении, и обработка, необходимая для чтения данных, довольно проста.

3. Данные Настройки

Для разных проектов потребуется настроить разные данные.

В этом уроке мы будем настраивать пользовательские данные – в основном подготовка системы с несколькими пользователями по умолчанию .

Вот простой CSV-файл, содержащий пользователей:

id,username,password,accessToken
1,john,123,token
2,tom,456,test

Обратите внимание, что первая строка файла – это строка заголовка – перечисление имен полей в каждой строке данных.

3. Загрузчик данных CSV

Давайте начнем с создания простого загрузчика данных для считывания данных из CSV-файлов в рабочую память .

3.1. Загрузите список объектов

Мы реализуем функцию loadObjectList() для загрузки полностью параметризованного списка конкретных Объектов из файла:

public  List loadObjectList(Class type, String fileName) {
    try {
        CsvSchema bootstrapSchema = CsvSchema.emptySchema().withHeader();
        CsvMapper mapper = new CsvMapper();
        File file = new ClassPathResource(fileName).getFile();
        MappingIterator readValues = 
          mapper.reader(type).with(bootstrapSchema).readValues(file);
        return readValues.readAll();
    } catch (Exception e) {
        logger.error("Error occurred while loading object list from file " + fileName, e);
        return Collections.emptyList();
    }
}

Записи:

  • Мы создали схему CSV на основе первой строки “заголовка”.
  • Реализация достаточно универсальна, чтобы обрабатывать объекты любого типа.
  • Если произойдет какая-либо ошибка, будет возвращен пустой список.

3.2. Управление отношениями “Многие ко многим”

Вложенные объекты не очень хорошо поддерживаются в CSV – файле Джексона-нам нужно будет использовать косвенный способ загрузки отношений “Многие ко многим”.

Мы представим их аналогично простым таблицам объединения – поэтому, естественно, мы загрузим с диска список массивов:

public List loadManyToManyRelationship(String fileName) {
    try {
        CsvMapper mapper = new CsvMapper();
        CsvSchema bootstrapSchema = CsvSchema.emptySchema().withSkipFirstDataRow(true);
        mapper.enable(CsvParser.Feature.WRAP_AS_ARRAY);
        File file = new ClassPathResource(fileName).getFile();
        MappingIterator readValues = 
          mapper.reader(String[].class).with(bootstrapSchema).readValues(file);
        return readValues.readAll();
    } catch (Exception e) {
        logger.error(
          "Error occurred while loading many to many relationship from file = " + fileName, e);
        return Collections.emptyList();
    }
}

Вот как одно из этих отношений – Роли <-> Привилегии – представлено в простом CSV-файле:

role,privilege
ROLE_ADMIN,ADMIN_READ_PRIVILEGE
ROLE_ADMIN,ADMIN_WRITE_PRIVILEGE
ROLE_SUPER_USER,POST_UNLIMITED_PRIVILEGE
ROLE_USER,POST_LIMITED_PRIVILEGE

Обратите внимание, как мы игнорируем заголовок в этой реализации, поскольку на самом деле нам не нужна эта информация.

4. Данные Настройки

Теперь мы будем использовать простой Setup bean для выполнения всей работы по настройке привилегий, ролей и пользователей из CSV-файлов:

@Component
public class Setup {
    ...
    
    @PostConstruct
    private void setupData() {
        setupRolesAndPrivileges();
        setupUsers();
    }
    
    ...
}

4.1. Настройка ролей и привилегий

Во-первых, давайте загрузим роли и привилегии с диска в рабочую память, а затем сохраним их как часть процесса установки:

public List getPrivileges() {
    return csvDataLoader.loadObjectList(Privilege.class, PRIVILEGES_FILE);
}

public List getRoles() {
    List allPrivileges = getPrivileges();
    List roles = csvDataLoader.loadObjectList(Role.class, ROLES_FILE);
    List rolesPrivileges = csvDataLoader.
      loadManyToManyRelationship(SetupData.ROLES_PRIVILEGES_FILE);

    for (String[] rolePrivilege : rolesPrivileges) {
        Role role = findRoleByName(roles, rolePrivilege[0]);
        Set privileges = role.getPrivileges();
        if (privileges == null) {
            privileges = new HashSet();
        }
        privileges.add(findPrivilegeByName(allPrivileges, rolePrivilege[1]));
        role.setPrivileges(privileges);
    }
    return roles;
}

private Role findRoleByName(List roles, String roleName) {
    return roles.stream().
      filter(item -> item.getName().equals(roleName)).findFirst().get();
}

private Privilege findPrivilegeByName(List allPrivileges, String privilegeName) {
    return allPrivileges.stream().
      filter(item -> item.getName().equals(privilegeName)).findFirst().get();
}

Тогда мы сделаем всю работу здесь:

private void setupRolesAndPrivileges() {
    List privileges = setupData.getPrivileges();
    for (Privilege privilege : privileges) {
        setupService.setupPrivilege(privilege);
    }

    List roles = setupData.getRoles();
    for (Role role : roles) {
        setupService.setupRole(role);
    }
}

А вот наш Сервис настройки :

public void setupPrivilege(Privilege privilege) {
    if (privilegeRepository.findByName(privilege.getName()) == null) {
        privilegeRepository.save(privilege);
    }
}

public void setupRole(Role role) {
    if (roleRepository.findByName(role.getName()) == null) { 
        Set privileges = role.getPrivileges(); 
        Set persistedPrivileges = new HashSet();
        for (Privilege privilege : privileges) { 
            persistedPrivileges.add(privilegeRepository.findByName(privilege.getName())); 
        } 
        role.setPrivileges(persistedPrivileges); 
        roleRepository.save(role); }
}

Обратите внимание, как после загрузки обеих ролей и Привилегий в рабочую память мы загружаем их отношения одну за другой.

4.2. Настройка Начальных Пользователей

Далее – давайте загрузим пользователей в память и сохраним их:

public List getUsers() {
    List allRoles = getRoles();
    List users = csvDataLoader.loadObjectList(User.class, SetupData.USERS_FILE);
    List usersRoles = csvDataLoader.
      loadManyToManyRelationship(SetupData.USERS_ROLES_FILE);

    for (String[] userRole : usersRoles) {
        User user = findByUserByUsername(users, userRole[0]);
        Set roles = user.getRoles();
        if (roles == null) {
            roles = new HashSet();
        }
        roles.add(findRoleByName(allRoles, userRole[1]));
        user.setRoles(roles);
    }
    return users;
}

private User findByUserByUsername(List users, String username) {
    return users.stream().
      filter(item -> item.getUsername().equals(username)).findFirst().get();
}

Далее, давайте сосредоточимся на сохранении пользователей:

private void setupUsers() {
    List users = setupData.getUsers();
    for (User user : users) {
        setupService.setupUser(user);
    }
}

А вот наш Сервис настройки :

@Transactional
public void setupUser(User user) {
    try {
        setupUserInternal(user);
    } catch (Exception e) {
        logger.error("Error occurred while saving user " + user.toString(), e);
    }
}

private void setupUserInternal(User user) {
    if (userRepository.findByUsername(user.getUsername()) == null) {
        user.setPassword(passwordEncoder.encode(user.getPassword()));
        user.setPreference(createSimplePreference(user));
        Set roles = user.getRoles(); 
        Set persistedRoles = new HashSet(); 
        for (Role role : roles) { 
            persistedRoles.add(roleRepository.findByName(role.getName())); 
        } 
        user.setRoles(persistedRoles);
        userRepository.save(user);
    }
}

А вот create Simple Preference() метод:

private Preference createSimplePreference(User user) {
    Preference pref = new Preference();
    pref.setId(user.getId());
    pref.setTimezone(TimeZone.getDefault().getID());
    pref.setEmail(user.getUsername() + "@test.com");
    return preferenceRepository.save(pref);
}

Обратите внимание, как перед сохранением пользователя мы создаем для него простую сущность Preference и сохраняем ее в первую очередь.

5. Тестовый загрузчик данных CSV

Затем давайте выполним простой модульный тест на нашем загрузчике данных Csv :

Мы проверим список загрузки Пользователей, ролей и привилегий:

@Test
public void whenLoadingUsersFromCsvFile_thenLoaded() {
    List users = csvDataLoader.
      loadObjectList(User.class, CsvDataLoader.USERS_FILE);
    assertFalse(users.isEmpty());
}

@Test
public void whenLoadingRolesFromCsvFile_thenLoaded() {
    List roles = csvDataLoader.
      loadObjectList(Role.class, CsvDataLoader.ROLES_FILE);
    assertFalse(roles.isEmpty());
}

@Test
public void whenLoadingPrivilegesFromCsvFile_thenLoaded() {
    List privileges = csvDataLoader.
      loadObjectList(Privilege.class, CsvDataLoader.PRIVILEGES_FILE);
    assertFalse(privileges.isEmpty());
}

Затем давайте протестируем загрузку некоторых отношений “Многие ко многим” с помощью загрузчика данных:

@Test
public void whenLoadingUsersRolesRelationFromCsvFile_thenLoaded() {
    List usersRoles = csvDataLoader.
      loadManyToManyRelationship(CsvDataLoader.USERS_ROLES_FILE);
    assertFalse(usersRoles.isEmpty());
}

@Test
public void whenLoadingRolesPrivilegesRelationFromCsvFile_thenLoaded() {
    List rolesPrivileges = csvDataLoader.
      loadManyToManyRelationship(CsvDataLoader.ROLES_PRIVILEGES_FILE);
    assertFalse(rolesPrivileges.isEmpty());
}

6. Данные настройки Теста

Наконец, давайте выполним простой модульный тест на нашем бобе Установочные данные :

@Test
public void whenGettingUsersFromCsvFile_thenCorrect() {
    List users = setupData.getUsers();

    assertFalse(users.isEmpty());
    for (User user : users) {
        assertFalse(user.getRoles().isEmpty());
    }
}

@Test
public void whenGettingRolesFromCsvFile_thenCorrect() {
    List roles = setupData.getRoles();

    assertFalse(roles.isEmpty());
    for (Role role : roles) {
        assertFalse(role.getPrivileges().isEmpty());
    }
}

@Test
public void whenGettingPrivilegesFromCsvFile_thenCorrect() {
    List privileges = setupData.getPrivileges();
    assertFalse(privileges.isEmpty());
}

7. Заключение

В этой краткой статье мы рассмотрели альтернативный метод настройки исходных данных, которые обычно необходимо загружать в систему при запуске. Это, конечно, просто простое доказательство концепции и хорошая база для построения – не готовое к производству решение .

Мы также собираемся использовать это решение в веб-приложении Reddit, отслеживаемом в этом текущем тематическом исследовании .