Coder Social home page Coder Social logo

'No converter found' when injection ConversionService into Spring Service. / indirect reference to ConversionService about mapstruct-spring-extensions HOT 6 CLOSED

mapstruct avatar mapstruct commented on June 26, 2024
'No converter found' when injection ConversionService into Spring Service. / indirect reference to ConversionService

from mapstruct-spring-extensions.

Comments (6)

Chessray avatar Chessray commented on June 26, 2024

Have you tried injecting the ConversionService with @Lazy like we do in the generated adapter? It sounds like you're experiencing an issue similar to #21.

from mapstruct-spring-extensions.

SimonFischer04 avatar SimonFischer04 commented on June 26, 2024

just got some to test this a bit. Thanks for pointing me into the direction of #21. In fact I now think this is basically the same issue just that I had a Service-Layer in between that hid the real cause - still in cyclic dependency, but indirectly.

Firstly the solution, if anyone stumbles upon the same issue. One solution is to either:
Add @lazy inside the Mapper.

@Mapper(config = MapperSpringConfig.class)
public abstract class ExampleMapper implements Converter<ExampleDTO, Example> {
    @Lazy
    @Autowired
    ExampleService exampleService;
}

Or inside the Service:

@Service
public class ExampleService {
    @Lazy
    @Autowired
    ConversionService conversionService;

    // methods that do something ...
}

This basically comes down to the same thing discussed in #21:

@Mapper(config = MapperSpringConfig.class)
public abstract class ExampleMapper implements Converter<ExampleDTO, Example> {
    // FIX by adding @Lazy
   @Lazy
   @Autowired
    ExampleService exampleService;

Furthermore, after reading among others: #22 and https://mapstruct.org/documentation/spring-extensions/reference/html/#mapperAsConverter it seems to be recommended to inject the ConversionService interface instead to the generated Adapter - like shown in #21. #21 recommends (according to spring) to use something like instead of @lazy:

@Mapper(config = MapperSpringConfig.class)
public abstract class ExampleMapper implements Converter<ExampleDTO, Example> {
//    @Autowired
    ConversionService conversionService;

    @Autowired
    public void setConversionService(ConversionService conversionService) {
        this.conversionService = conversionService;
    }
}

This results in the same error.

But this works:

@Mapper(config = MapperSpringConfig.class)
public abstract class ExampleMapper implements Converter<ExampleDTO, Example> {
    //    @Autowired
    MyAdapter conversionServiceAdapter;

    @Autowired
    public void setConversionServiceAdapter(MyAdapter conversionServiceAdapter) {
        this.conversionServiceAdapter = conversionServiceAdapter;
    }
}

So how is the proper way to handle this (somehow injecting a ConversionService into a mapper) now? Inject the ConversionService using @Lazy or directly injecting the Adapter using setter-injection or another way?

PS: after now knowing the actual issue now, I can see a couple of related issues, maybe it would be nice to add a docs entry about using ConversionService inside a mapper.

from mapstruct-spring-extensions.

Chessray avatar Chessray commented on June 26, 2024

Why would you want to inject the ConversionService directly into a Mapper? The project's whole idea is that you assign the generated adapter to the Mapper's uses property. and thus decouple the Mappers from each other.

As for the various forms of injection: Ideally I'd want to use constructor injection, but MapStruct has no way of specifying this yet. I typically rely on field injection for things I can't put into the uses property.

The underlying issue is that the ConversionService's initialization seems to be a little "weird", i.e. not standard Spring behavior. There's not much we can do about this inside MapStruct (or this extension) which is why we chose to go down the @Lazy route - and wash our hands.

from mapstruct-spring-extensions.

SimonFischer04 avatar SimonFischer04 commented on June 26, 2024

Why would you want to inject the ConversionService directly into a Mapper? The project's whole idea is that you assign the generated adapter to the Mapper's uses property. and thus decouple the Mappers from each other.

I agree that direct injection does not make sense. Like the initial issue explains, I'm actually using a service in between. This was just to show the issue because it produces the same result.

Maybe a little bit more explanation of what I'm trying to accomplish would help. Maybe there's another way to solve this (still trying to make it as short as possible):
So I typically have 2/3 Classes for each type: XEntity, X (typical "domain" / "business" object) and XDTO. With 'XEntity' and 'X' having references to the actual objects and the DTO sometimes only containing ids to reference stuff, so I need to fetch the actual objects back from the database.

 // ImageEntity.java
@Entity
public class ImageEntity {
  // some fields ...
}

// ButtonEntity.class
@Entity
public class ButtonEntity {
   ImageEntity background;
   // ...
}
// Image.class
public record Image(/* some fields */){}

// Button.class
public record Button(
  Image background
   // ...
){}
// ButtonDTO
public record ButtonDTO(
  int backgroundID
   // ...
){}

So when receiving the dto on a post it needs firstly needs to be converted back to a domain object. For this I have a Mapper:

// ButtonDTOToButtonMapper.java
@Mapper(config = MapperSpringConfig.class)
public abstract class ButtonDTOToButtonMapper implements Converter<ButtonDTO Button> {
    @Mapping(target = "background", source = "backgroundId", qualifiedByName = "mapBackground"),
    public abstract Field convert(@Nullable ViewController.FieldDTO fieldDTO);

    @Autowired
    @Lazy // here the issue started, without using @Lazy
    private ImageService imageService;

    @Named("mapBackground")
    public Image mapBackground(Integer backgroundId) {
        // service required to get the actual object by id
        return imageService.findById(backgroundId);
    }
}

The service encapsulates all the Entity/DB specific logic. XEntity objects never leave/enter the service. The X objects are what the main application logic is using. But this Service also requires ConversionService access because it ultimately (after doing some other stuff) maps from the Entity to the domain Object.

// ImageService.class
@Service
@RequiredArgsConstructor
public class ImageService {
    private final ImageRepository repository;
    private final ConversionService conversionService; // ConversionService is required here

    @Override
    public Image findById(int id) {
        ImageEntity entity = repository.findById(id).orElse(null);
       // do some stuff
        return conversionService.convert(entity, Image.class);
    }
}

The ImageEntity -> Image mapper is just a simple 1 to 1 mapping:

// ImageEntityToImageMapper.java
@Mapper(config = MapperSpringConfig.class)
public interface ImageEntityToImageMapper extends Converter<ImageEntity, Image> {
    Image convert(@Nullable ImageEntity imageEntity);
}

So one way to deal with this / work more with uses would of course be to inject the ImageRepository directly into the ButtonDTOToButtonMapper but then I'd need to duplicate the service-logic in there...

Hope this helps to understand my reasoning.

from mapstruct-spring-extensions.

Chessray avatar Chessray commented on June 26, 2024

Thanks, that makes some sense. Some of the Mappers function as enrichers with DB information. I still think the best option in this scenario is using @Lazy in one of the injection points.

from mapstruct-spring-extensions.

SimonFischer04 avatar SimonFischer04 commented on June 26, 2024

OK, thanks.
(I ended up injecting the Service using @lazy into the mapper.)

from mapstruct-spring-extensions.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.