Home > Software engineering >  Spring can not pick a JpaRepository that extends a generic interface
Spring can not pick a JpaRepository that extends a generic interface

Time:01-07

Problem description

I have a couple of controllers that look like this:

@RequestMapping("InkUsage")
@RestController
public class InkUsageController extends BaseController<InkUsageDto> {
    public InkUsageController(BaseService<InkUsageDto> service) {
        super(service);
    }
}

@RequestMapping("MediaCategoryUsage")
@RestController
public class MediaCategoryUsageController extends BaseController<MediaCategoryUsageDto> {
    public MediaCategoryUsageController(BaseService<MediaCategoryUsageDto> service) {
        super(service);
    }
}

Both of them extend a BaseController class that looks like this:

@AllArgsConstructor
@RestController
public class BaseController<T> {
    private BaseService<T> service;

    @PostMapping
    public ResponseEntity<List<T>> getAll(){...}
    

    ...
}

The BaseService looks like this:

@AllArgsConstructor
@Service
public class BaseService<T> {
    private BaseRepository<T> repository;
    
    public List<T> getAll() {...}

    ...
}

The BaseRepository is just an interface that JpaRepositories extend:

public interface BaseRepository<T> {
    List<T> getAllAggregated(String dateFormat);
    List<T> getAllNonAggregated(String dateFormat);
    ...
}

This is how one of the JpaRepositories look like:

@Repository("InkUsageRepository")
public interface InkUsageRepository extends JpaRepository<InkUsageEntity, Long>,
        BaseRepository<InkUsageDto> {
    @Query("SELECT new api.coloradodashboard.inkusage.InkUsageDto(DATE_FORMAT(i.date, :dateFormat) AS formatted_date, sum(i.cyanLitresUsed), sum(i.magentaLitresUsed), sum(i.yellowLitresUsed), sum(i.blackLitresUsed)) "  
            "FROM InkUsageEntity i "  
            "GROUP BY formatted_date "  
            "ORDER BY formatted_date ASC")
    List<InkUsageDto> getAllAggregated(@Param("dateFormat") String dateFormat);

    ...
}

The problem is that when I run the application Spring complains that BaseService requires a single bean of type BaseRepository but it found 2, which is true. However, both implementations use a different class as a generic type(one uses BaseRepository<InkUsageDto>, for example) and it seems Spring can not pick up that.

Question

How can I tell Spring to use the proper repository?

What I already tried

  • Spring suggests using a @Primary qualifier, but that will not solve my problem because I plan on having 5 JpaRepositories extending BaseRepository.
  • I tried passing the repository into the BaseController constructor, then immediately create new BaseService(repository) in the BaseController constructor but Spring complains it can not find a bean of type BaseRepository.
  • I checked whether passing a variable into a @Qualifier is possible, but they only take constant expressions.
  • A solution that will probably work but I avoid is to create BaseRepository class implementation for each repository, but that means having 5 implementations with the same code except the repository field.

Edit

I found this article but not sure whether it can help me.

CodePudding user response:

Alright, I found a solution. First, I need to create a BaseServiceFactory and pass the repository to the BaseService constructor:

@NoArgsConstructor
@Service
public class BaseServiceFactory<T> {
    public BaseService<T> getBaseService(BaseRepository<T> repository) {
        return new BaseService<T>(repository);
    }
}

Then each new controller(that inherits BaseController) will invoke the factory and pass the repository explicitly(for example, pass InkUsageRepository or MediaCategoryUsageRepository, etc):

@RequestMapping("InkUsage")
@RestController
public class InkUsageController extends BaseController<InkUsageDto> {
    public InkUsageController(BaseServiceFactory<InkUsageDto> baseServiceFactory,
                              InkUsageRepository repository) {
        super(baseServiceFactory.getBaseService(repository));
    }
}

And last, the BaseService needs to inject the repository lazily like this:

@Service
public class BaseService<T> {
    private final BaseRepository<T> repository;

    public BaseService(@Lazy @Autowired BaseRepository<T> repository) {
        this.repository = repository;
    }
    ...
}

CodePudding user response:

Well, I think your solution is overcomplicated. Just simply do this:

public interface BaseRepository<T> extends JpaRepository<T, Long> {
    List<T> getAllAggregated(String dateFormat);
    List<T> getAllNonAggregated(String dateFormat);
    ...
}

and for your child class or entity:

public interface InkUsageRepository extends BaseRepository<InkUsageEntity> {
    @Query("SELECT new api.coloradodashboard.inkusage.InkUsageDto(DATE_FORMAT(i.date, :dateFormat) AS formatted_date, sum(i.cyanLitresUsed), sum(i.magentaLitresUsed), sum(i.yellowLitresUsed), sum(i.blackLitresUsed)) "  
            "FROM InkUsageEntity i "  
            "GROUP BY formatted_date "  
            "ORDER BY formatted_date ASC")
    List<InkUsageDto> getAllAggregated(@Param("dateFormat") String dateFormat);

}
  •  Tags:  
  • Related