• Najnowsze pytania
  • Bez odpowiedzi
  • Zadaj pytanie
  • Kategorie
  • Tagi
  • Zdobyte punkty
  • Ekipa ninja
  • IRC
  • FAQ
  • Regulamin
  • Książki warte uwagi

Wykorzystanie ModelMapera

Object Storage Arubacloud
0 głosów
288 wizyt
pytanie zadane 14 października 2019 w Java przez JuniorPL Użytkownik (770 p.)

Cześć mam dziwny problem z maperem którego nie mogę zozumieć, przedebagowałem kod ale nie mogę zrozumieć co robię źle. Chciałbym zmapować entity do dto i w tym momęcie wszystko się sypie. Może problem leży w klasach, może ktoś trochę naprowadzi?

Klasay

@Entity
public class CarUserEntity {

    @Id
    @GeneratedValue
    private long id;
    @Column()
    private String carUserId;
    @Column()
    private long userId;
    @Column()
    private int carMilages;
    @Column()
    private int numberOfKilomentersPerMonth;
    @Column()
    private long carModelId;
    @ManyToOne
    @JoinColumn(name="user")
    private UserEntity user;
private long id;
private String carUserId;
private long userId;
private int carMilages;
private int numberOfKilomentersPerMonth;
private long carModelId;
private UserDto user;

+ akcesory

 

Problem pojawia się tu:

CarUserDto carUserDto1 = new CarUserDto();
CarUserEntity carUserEntity1 = new CarUserEntity();

carUserEntity1 = modelMapper.map(carUserDto1,CarUserEntity.class);

Stack trace:


1) The destination property com.carAssistant.CarAssistant.entity.CarUserEntity.setUser()/com.carAssistant.CarAssistant.entity.UserEntity.setId() matches multiple source property hierarchies:

	com.carAssistant.CarAssistant.dto.CarUserDto.getId()
	com.carAssistant.CarAssistant.dto.CarUserDto.getUserId()
	com.carAssistant.CarAssistant.dto.CarUserDto.getUser()/com.carAssistant.CarAssistant.dto.UserDto.getId()
	com.carAssistant.CarAssistant.dto.CarUserDto.getUser()/com.carAssistant.CarAssistant.dto.UserDto.getUserId()
	com.carAssistant.CarAssistant.dto.CarUserDto.getCarModelId()
	com.carAssistant.CarAssistant.dto.CarUserDto.getCarUserId()

1 error] with root cause

org.modelmapper.ConfigurationException: ModelMapper configuration errors:

1) The destination property com.carAssistant.CarAssistant.entity.CarUserEntity.setUser()/com.carAssistant.CarAssistant.entity.UserEntity.setId() matches multiple source property hierarchies:

	com.carAssistant.CarAssistant.dto.CarUserDto.getId()
	com.carAssistant.CarAssistant.dto.CarUserDto.getUserId()
	com.carAssistant.CarAssistant.dto.CarUserDto.getUser()/com.carAssistant.CarAssistant.dto.UserDto.getId()
	com.carAssistant.CarAssistant.dto.CarUserDto.getUser()/com.carAssistant.CarAssistant.dto.UserDto.getUserId()
	com.carAssistant.CarAssistant.dto.CarUserDto.getCarModelId()
	com.carAssistant.CarAssistant.dto.CarUserDto.getCarUserId()

1 error
	at org.modelmapper.internal.Errors.throwConfigurationExceptionIfErrorsExist(Errors.java:241) ~[modelmapper-2.3.0.jar:na]
	at org.modelmapper.internal.ImplicitMappingBuilder.matchDestination(ImplicitMappingBuilder.java:157) ~[modelmapper-2.3.0.jar:na]
	at org.modelmapper.internal.ImplicitMappingBuilder.matchDestination(ImplicitMappingBuilder.java:150) ~[modelmapper-2.3.0.jar:na]
	at org.modelmapper.internal.ImplicitMappingBuilder.build(ImplicitMappingBuilder.java:88) ~[modelmapper-2.3.0.jar:na]
	at org.modelmapper.internal.ImplicitMappingBuilder.build(ImplicitMappingBuilder.java:73) ~[modelmapper-2.3.0.jar:na]
	at org.modelmapper.internal.TypeMapStore.getOrCreate(TypeMapStore.java:128) ~[modelmapper-2.3.0.jar:na]
	at org.modelmapper.internal.TypeMapStore.getOrCreate(TypeMapStore.java:102) ~[modelmapper-2.3.0.jar:na]
	at org.modelmapper.internal.MappingEngineImpl.map(MappingEngineImpl.java:112) ~[modelmapper-2.3.0.jar:na]
	at org.modelmapper.internal.MappingEngineImpl.map(MappingEngineImpl.java:71) ~[modelmapper-2.3.0.jar:na]
	at org.modelmapper.ModelMapper.mapInternal(ModelMapper.java:573) ~[modelmapper-2.3.0.jar:na]
	at org.modelmapper.ModelMapper.map(ModelMapper.java:406) ~[modelmapper-2.3.0.jar:na]
	at com.carAssistant.CarAssistant.Service.Impl.CarUserServiceImpl.createCarUser(CarUserServiceImpl.java:43) ~[classes/:na]
	at com.carAssistant.CarAssistant.controller.UserController.addCarUser(UserController.java:56) ~[classes/:na]
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:na]
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
	at java.base/java.lang.reflect.Method.invoke(Method.java:566) ~[na:na]
	at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:190) ~[spring-web-5.1.10.RELEASE.jar:5.1.10.RELEASE]
	at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:138) ~[spring-web-5.1.10.RELEASE.jar:5.1.10.RELEASE]
	at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:105) ~[spring-webmvc-5.1.10.RELEASE.jar:5.1.10.RELEASE]
	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:893) ~[spring-webmvc-5.1.10.RELEASE.jar:5.1.10.RELEASE]
	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:798) ~[spring-webmvc-5.1.10.RELEASE.jar:5.1.10.RELEASE]
	at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) ~[spring-webmvc-5.1.10.RELEASE.jar:5.1.10.RELEASE]
	at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1040) ~[spring-webmvc-5.1.10.RELEASE.jar:5.1.10.RELEASE]
	at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:943) ~[spring-webmvc-5.1.10.RELEASE.jar:5.1.10.RELEASE]
	at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006) ~[spring-webmvc-5.1.10.RELEASE.jar:5.1.10.RELEASE]
	at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:909) ~[spring-webmvc-5.1.10.RELEASE.jar:5.1.10.RELEASE]
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:660) ~[tomcat-embed-core-9.0.26.jar:9.0.26]
	at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883) ~[spring-webmvc-5.1.10.RELEASE.jar:5.1.10.RELEASE]
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:741) ~[tomcat-embed-core-9.0.26.jar:9.0.26]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231) ~[tomcat-embed-core-9.0.26.jar:9.0.26]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.26.jar:9.0.26]
	at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53) ~[tomcat-embed-websocket-9.0.26.jar:9.0.26]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.26.jar:9.0.26]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.26.jar:9.0.26]
	at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[spring-web-5.1.10.RELEASE.jar:5.1.10.RELEASE]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.1.10.RELEASE.jar:5.1.10.RELEASE]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.26.jar:9.0.26]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.26.jar:9.0.26]
	at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) ~[spring-web-5.1.10.RELEASE.jar:5.1.10.RELEASE]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.1.10.RELEASE.jar:5.1.10.RELEASE]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.26.jar:9.0.26]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.26.jar:9.0.26]
	at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:94) ~[spring-web-5.1.10.RELEASE.jar:5.1.10.RELEASE]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.1.10.RELEASE.jar:5.1.10.RELEASE]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.26.jar:9.0.26]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.26.jar:9.0.26]
	at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) ~[spring-web-5.1.10.RELEASE.jar:5.1.10.RELEASE]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.1.10.RELEASE.jar:5.1.10.RELEASE]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.26.jar:9.0.26]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.26.jar:9.0.26]
	at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202) ~[tomcat-embed-core-9.0.26.jar:9.0.26]
	at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96) ~[tomcat-embed-core-9.0.26.jar:9.0.26]
	at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:526) ~[tomcat-embed-core-9.0.26.jar:9.0.26]
	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139) ~[tomcat-embed-core-9.0.26.jar:9.0.26]
	at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) ~[tomcat-embed-core-9.0.26.jar:9.0.26]
	at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) ~[tomcat-embed-core-9.0.26.jar:9.0.26]
	at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343) ~[tomcat-embed-core-9.0.26.jar:9.0.26]
	at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:408) ~[tomcat-embed-core-9.0.26.jar:9.0.26]
	at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66) ~[tomcat-embed-core-9.0.26.jar:9.0.26]
	at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:860) ~[tomcat-embed-core-9.0.26.jar:9.0.26]
	at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1589) ~[tomcat-embed-core-9.0.26.jar:9.0.26]
	at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) ~[tomcat-embed-core-9.0.26.jar:9.0.26]
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128) ~[na:na]
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628) ~[na:na]
	at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) ~[tomcat-embed-core-9.0.26.jar:9.0.26]
	at java.base/java.lang.Thread.run(Thread.java:834) ~[na:na]

 

komentarz 14 października 2019 przez DanielD Użytkownik (820 p.)
edycja 15 października 2019 przez DanielD
znacznie łatwiej (przynajmniej mi) było by gdybyś wstawił gdzieś cały kod :)
komentarz 15 października 2019 przez JuniorPL Użytkownik (770 p.)

Tutaj jest repo. Nie korzystam z fabryki obiektów. Są to moje pierwsze kroki z restowym api więc jeszcze sporo rzeczy nie wiem. Fajnie gdybyś zerkną na kod może coś podpowiesz.

1 odpowiedź

0 głosów
odpowiedź 15 października 2019 przez DanielD Użytkownik (820 p.)

a jak wygląda twój plik konfiguracyjny do mapperfacade?

kilka razy miałem problem z konfiguracją w której musiałem zawsze umieszczać klasy jakie mają być mapowane?

mapperFactory.classMap(Task.class, DisplayTaskDTO.class)
                .byDefault()
                .register();

 

 ja wszystkie spoje mapowania umieszczam w serwisach np

    @Override
    public UserDto findUserById(Long id) {
        return mapperFacade.map(userRepository.findById(id).get(),UserDto.class);

 

innym razem problemem jaki napotkałem kilka razy było powiązanie encji ze sobą co skutkowało zapętleniem podczas mapowania, rozwiązaniem na ten problem było napisanie własnego mapera (ustalenie jakie pola mają być mapowane a jakie nie)

mapperFact.classMap(Trips.class, TripsDto.class)
                .field("routes", "routesDto")
                .field("service","serviceDto")
                .field("shapes","shapesDtos")
                .field("vehicleTypes", "vehicleTypesDto")
                .field("variants", "variantsDto")
                .byDefault()
                .register();

 

daj znać czy pomogło

 

komentarz 16 października 2019 przez DanielD Użytkownik (820 p.)

Ja również cały czas się uczę :) Trochę to zajęło ale udało mi się uruchomić twój projekt.
Tak jak myślałem zapętliłeś Dtos'y, też tak zrobiłem kiedyś. Kto nie próbuje ten nie popełnia błędów :)

Tak jak pisałeś problem tkwi tutaj

userDto =  carUserService.createCarUser(carUserDto);

W klasie UserDto posiadasz pole

private List<CarUserDto> carsUser;

a w klasie CarUserDto posiadasz pole

private UserDto user;

Musisz jeszcze raz przemyśleć budowę swoich Dto, nie bój się tworzyć dodatkowych np. tylko do dodania, rejestracji, wyświetlania użytkowników. Po to są Dto.


Osobiście zrezygnował bym z "CarUserRequestModel" i odrazy żądał "carUserDto" w metodzie

public UserRest addCarUser(@RequestBody CarUserRequestModel carUserRequestModel)

 

tak na marginesie skupił bym się tylko na jednym MediaTypes np Jason.
Dobrą praktyką też jest rozdzielenie całej konfiguracji na osobne klasy, aby jednka klasa zajmowała się swoją konfiguracją np. stworzyć nowy pakiet "config" i w nim umieścić kaslę odpowiedzialną na konfigurację webowe "WebConfig", "MvcConfig", itp ...

Mój stary projekt wygląda mniej więcej tak
Klasa Konfiguracyjna Mapper

@Configuration
public class WebConfig {

    @Bean
    public MapperFacade mapperFactory() {
        MapperFactory mapperFact = new DefaultMapperFactory.Builder().mapNulls(false).build();

        mapperFact.classMap(Routes.class, RoutesDto.class)
                .field("agency","agencyDto")
                .field("routeType2","routeTypeDto")
                .byDefault()
                .register();

        mapperFact.classMap(StopTimes.class, StopTimesDto.class)
                .field("trips", "tripsDto")
                .byDefault()
                .register();

        mapperFact.classMap(Trips.class, TripsDto.class)
                .field("routes", "routesDto")
                .field("service","serviceDto")
                .field("shapes","shapesDtos")
                .field("vehicleTypes", "vehicleTypesDto")
                .field("variants", "variantsDto")
                .byDefault()
                .register();

        mapperFact.classMap(Variants.class, VariantsDto.class)
                .field("stops", "stopsDtos")
                .byDefault()
                .register();

        return mapperFact.getMapperFacade();
    }
}

 

Controller

@Controller
@RequestMapping("/agency")
public class AgencyFormController {

    private final AgencyService agencyService;

    public AgencyFormController(AgencyService agencyService) {
        this.agencyService = agencyService;
    }

    @GetMapping
    public String findAll(Model model,
                          @RequestParam(value = "page", defaultValue = "0") int page,
                          @RequestParam(value = "limit", defaultValue = "50") int limit) {
        model.addAttribute("agencyDto", agencyService.findAll(page, limit));
        //todo
        //select count
        return "agencies";
    }

    @GetMapping("/{id}")
    public String showById(@PathVariable Long id, Model model) {
        model.addAttribute("agencyDto",agencyService.findById(id));
        return "agency";
    }

    @PostMapping("/delete/{id}")
    public String deleteById(@PathVariable Long id) {
        agencyService.deleteById(id);
        return "redirect:/agency";
    }

    @GetMapping("/edit/{id}")
    public String updateAgency(Model model, @PathVariable Long id) {
        AgencyDto age = agencyService.findById(id);
        model.addAttribute("agencyDto", age);
        return "form/agencyForm";
    }

    @GetMapping("/add")
    public String createAgency(Model model) {
        model.addAttribute("agencyDto", new AgencyDto());

        return "form/agencyForm";
    }

    @RequestMapping(path = {"/add", "/edit/{id}"}, method = {RequestMethod.POST, RequestMethod.PUT})
    public String addAgencyToDataBase(@ModelAttribute AgencyDto agencyDto, BindingResult result) {
        if (!result.hasErrors()) {
            AgencyDto newAgencyDto = agencyService.saveAndUpdate(agencyDto);
            return "redirect:/agency/" + newAgencyDto.getId();
        }
        return "redirect:/agencies";
    }

}

Service

@Service
public class AgencyServiceImpl implements AgencyService {

    private final AgencyRepository agencyRepository;
    private final MapperFacade mapperFacade;

    public AgencyServiceImpl(AgencyRepository agencyRepository, MapperFacade mapperFacade) {
        this.agencyRepository = agencyRepository;
        this.mapperFacade = mapperFacade;
    }

    @Override
    public Collection<AgencyDto> findAll(int page, int limit) {
        Pageable pageableRequest = PageRequest.of(page, limit);

        Collection<Agency> agences = agencyRepository.findAll(pageableRequest).getContent();

        return agences
                .stream()
                .map(agency -> mapperFacade.map(agency, AgencyDto.class))
                .collect(Collectors.toList());
    }

    @Override
    public AgencyDto findById(Long id) {
        return mapperFacade.map(agencyRepository.findById(id).get(), AgencyDto.class);
    }

    @Override
    public void deleteById(Long id) {
        agencyRepository.deleteById(id);
    }

    @Override
    public AgencyDto saveAndUpdate(AgencyDto agencyDto) {
        Agency save = agencyRepository.save(mapperFacade.map(agencyDto, Agency.class));
        return mapperFacade.map(agencyRepository.findById(save.getId()).get(), AgencyDto.class);
    }

}

 

komentarz 17 października 2019 przez JuniorPL Użytkownik (770 p.)
Dzięki za zaangażowanie, dopiero odczytałem bo nie dostałem powiadomienia na pocztę. Nie do końca rozumiem o co chodzi z tym zapętlaniem dto? Na czym to niby polega, przez co jest to powodowane i jak tego uniknąć?
komentarz 17 października 2019 przez DanielD Użytkownik (820 p.)

Ja przynajmniej tak rozumiem ten problem, wszystko sprowadza się do dto,  encji i relacji między encjami :)

spójrz poniższe zdjęcie pokazuje dokładnie jaki twór chcesz dodać do bazy i tak w nieskończoność

w klasie user masz pole caruser a w caruser masz pole user.
jak pracujesz na encjach trzeba dobrze rozumieć relacje między nimi (ja nadal nie rozumiem :P )

Możesz albo poprawić dto albo relacje między encjami, ewentualni można skonfigurować mapera jakie pola ma mapować.

czy to jest już w miarę przejrzyste dla ciebie ?

komentarz 17 października 2019 przez JuniorPL Użytkownik (770 p.)
Użyłem innego narzędzia do zmapowania tych klas i otrzymałem właśnie taką nieskończoną pętlę. Właśnie prubuję ją przerwać. Albo zmienię dto tak jak mówisz albo mapowanie. Dzięki za pomoc.
komentarz 17 października 2019 przez DanielD Użytkownik (820 p.)
no to fajnie że się coś udało :)

Powodzenia :)

Podobne pytania

0 głosów
1 odpowiedź 171 wizyt
+1 głos
2 odpowiedzi 292 wizyt
pytanie zadane 5 stycznia 2022 w Java przez marzena12345 Użytkownik (770 p.)
0 głosów
1 odpowiedź 218 wizyt

92,551 zapytań

141,393 odpowiedzi

319,523 komentarzy

61,936 pasjonatów

Motyw:

Akcja Pajacyk

Pajacyk od wielu lat dożywia dzieci. Pomóż klikając w zielony brzuszek na stronie. Dziękujemy! ♡

Oto polecana książka warta uwagi.
Pełną listę książek znajdziesz tutaj.

Akademia Sekuraka

Kolejna edycja największej imprezy hakerskiej w Polsce, czyli Mega Sekurak Hacking Party odbędzie się już 20 maja 2024r. Z tej okazji mamy dla Was kod: pasjamshp - jeżeli wpiszecie go w koszyku, to wówczas otrzymacie 40% zniżki na bilet w wersji standard!

Więcej informacji na temat imprezy znajdziecie tutaj. Dziękujemy ekipie Sekuraka za taką fajną zniżkę dla wszystkich Pasjonatów!

Akademia Sekuraka

Niedawno wystartował dodruk tej świetnej, rozchwytywanej książki (około 940 stron). Mamy dla Was kod: pasja (wpiszcie go w koszyku), dzięki któremu otrzymujemy 10% zniżki - dziękujemy zaprzyjaźnionej ekipie Sekuraka za taki bonus dla Pasjonatów! Książka to pierwszy tom z serii o ITsec, który łagodnie wprowadzi w świat bezpieczeństwa IT każdą osobę - warto, polecamy!

...