简体   繁体   中英

How to Create Generic @Service That Injects Specific @Repository?

Our software have a specific behaviour that applies to all the functionalities, what change in most cases is the Entity used in operations, and for that we need specific implementation for @Repository layer.

So, we develop an "simple" architecture @RestController -> @Service -> @Repository with some generics, to work for all the functionality. Like this:

@RestController
@RequestMapping("test")
// This is specific implementation
public class DiariasApi implements DefaultApiInterface<DiariasDTO, DiariasDTOFilter, Integer> {

    @Autowired
    private DefaultService<DiariasDTO, DiariasDTOFilter, Integer> defaultService;

    @Override
    @GetMapping("/page")
    public Page<DiariasDTO> pageSearch(final DiariasDTOFilter filter) {
        return this.defaultService.pageSearch(filter);
    }

    @Override
    @GetMapping("/detail")
    public DiariasDTO detail(@PathVariable("key") final Integer key) {
        return this.defaultService.detail(key);
    }

}

@Service
// This is generic implementation
public class DefaultService<D extends Serializable, F extends Serializable, C> {

    @Autowired
    // The Problem is here.
   // Here I want the call to be the specific @Repository.
    private DefaultRepositoryInterface<D, F, C> defaultRepository;

    @Transactional(readOnly = true)
    public Page<D> pageSearch(final F filter) {
        return this.defaultRepository.pageSearch(filter);
    }

    @Transactional(readOnly = true)
    public D detail(final C key) {
        return this.defaultRepository.detail(key);
    }

}
@Repository
// This is specific implementation
public class DiariasRepository implements DefaultRepositoryInterface<DiariasDTO, DiariasDTOFilter, Integer> {

    @Override
    public Page<DiariasDTO> pageSearch(final DiariasFiltro filtro) {
        //some specific code;
    }

    @Override
    public Optional<DiariasDTO> detail(final Integer key) {
        //some specific code;
    }

We want to implement only the @RestController and the @Repository for each functionality, and let the @Service layer be only one generic bean that knows how to call the specific @Repository . But when we do that and have more than one implementation we receive the following error message, which tells us the problem with @Autowired :

Description:
Field defaultRepository in package.DefaultService required a single bean, but 2 were found:
    - conveniosRepository: defined in file ...
    - diariasRepository: defined in file ...

We want the @Service layer to be unique, can we do that?

You need to use @Qualifier which will bind the specific bean instance

@Service
// This is generic implementation
 public class DefaultService<D extends Serializable, F extends Serializable, C> {

@Autowired
// The Problem is here.
// Here I want the call to be the specific @Repository.
@Qualifier("YourBeanId")
private DefaultRepositoryInterface<D, F, C> defaultRepository;

EDIT

So you can use another approach which I had used in past for local v/s testing environment

The factory like class that will return the specific instance of the bean at runtime

Create a Factory class

@Component
public class RepositoryFactoryImpl implements RepositoryFactory{

@Autowired
private DefaultRepositoryInterface conveniosRepository;

@Autowired
private DefaultRepositoryInterface diariasRepository;

@Override
public DefaultRepositoryInterface getInstance() {
    if (some condition) {
        return conveniosRepository;
    }

    if (some condition) {
        return diariasRepository;
    }
    return null;
   }
}

Then in your DefaultService

@Service
// This is generic implementation
public class DefaultService<D extends Serializable, F extends Serializable, C> {

    @Autowired
    private RepositoryFactory factory;

    @Transactional(readOnly = true)
    public Page<D> pageSearch(final F filter) {
        return this.factory.getInstance().pageSearch(filter);
    }

    @Transactional(readOnly = true)
    public D detail(final C key) {
        return this.factory.getInstance().detail(key);
    }
 }

I think your approach is perfectly fine, I have a similar one. Your problem --I think is, that you want a generic service to access a specific repository. Make a DiariasService that extends Service<DiariasDto, DiariasFilter, Integer> and let Spring autowire in a DiariasRepository that extends Repository<DiariasDto, DiariasFilter, Integer> in the constuctor and pass it to the abstract service.

You have a new but pretty much empty Service, but correctly resolved the ambiguous dependencies.

My java isn't the freshest, so in kotlin this looks like:

abstract class Service<R : Resource, M : Model>(
    protected open val factory: Factory<R, M>,
    //...
)

class FooService(
    factory: FooFactory, //spring bean type magic happens here and @Qualifier is applicable!
    //...
) : Service<FooResource, FooModel>(
    factory, //from one constuctor to the superclass constructor
    //...
)

abstract class Factory<R : Resource, M : Model>(
    //...
)

class FooFactory(
    //...
) : Factory<FooResource, FooModel>(
    //...
)

I use the same pattern for Controller / FooController and Repository / FooRepository and so on.

Of course there are abstract Model and Resource / Entity .

You cannot use @Qualifier in the abstract Service / Controller / Repository / Factory , but in the concrete classes!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM