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() для загрузки полностью параметризованного списка конкретных Объектов из файла:
publicList 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 ListloadManyToManyRelationship(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 ListgetPrivileges() { 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() { Listprivileges = 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) { Setprivileges = 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 ListgetUsers() { 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() { Listusers = 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)); Setroles = 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() { Listusers = 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() { ListusersRoles = 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() { Listusers = 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, отслеживаемом в этом текущем тематическом исследовании .