Spring Boot多数据库场景下Controller注入异常及多库查询需求
我目前有MySQL加上4个NoSQL数据库,所有的数据库服务类都实现了CustomerService接口来完成CRUD操作。现在遇到了Spring依赖注入的异常——虽然加@Primary能解决单个实例注入的问题,但我的需求是同时从所有数据库中查询数据,而不是只使用某一个数据库。
相关代码
CustomerController类
@RestController @RequestMapping("/customer") public class CustomerController { @Autowired CustomerService customerService; public CustomerController(CustomerService customerService) { this.customerService = customerService; } @RequestMapping(value = "/{Id}", method = RequestMethod.POST) public ResponseEntity<Customer> create(@RequestBody CustomerData customerData) { customerService.create(customerData); return new ResponseEntity<Customer>(customerData, HttpStatus.CREATED); } @RequestMapping(value = "/{Id}", method = RequestMethod.GET) public ResponseEntity<Customer> read(@PathVariable("Id") String Id){ Customer customer = (Customer)customerService.read(Id); if (customer == null){ return new ResponseEntity<Customer>(HttpStatus.NOT_FOUND); } return new ResponseEntity<Customer>(customer, HttpStatus.OK); } @RequestMapping(method = RequestMethod.GET) public List<Customer> retrieveCustomers(){ return customerService.retrieveAllCustomers(); } @RequestMapping(value = "/{Id}", method = RequestMethod.PUT) public ResponseEntity<Customer> update(@PathVariable("Id") String Id, @RequestBody CustomerData customerData) { Customer currentCustomer = (Customer)customerService.read(Id); if(currentCustomer == null){ return new ResponseEntity<Customer>(HttpStatus.NOT_FOUND); } currentCustomer.setName(customerData.getName()); currentCustomer.setDistrict(customerData.getDistrict()); currentCustomer.setDate(customerData.getDate()); customerService.create(currentCustomer); return new ResponseEntity<Customer>(currentCustomer, HttpStatus.OK); } @RequestMapping(value = "/{Id}", method = RequestMethod.DELETE) public ResponseEntity<Void> delete(@PathVariable String Id){ Customer customer = (Customer)customerService.read(Id); if (customer==null){ return new ResponseEntity<Void>(HttpStatus.NOT_FOUND); }else { customerService.delete(Id); return new ResponseEntity<Void>(HttpStatus.GONE); } } }
CustomerService接口
@Component public interface CustomerService<E>{ E create(Customer customer); E read(String id); List<E> retrieveAllCustomers(); E update(String Id, Customer customer); //List<Order> retrieveAllOrders(String customerId); void delete(String id); void deleteAll(); }
MySQL-CustomerService类(其他NoSQL实现类结构类似)
@Service("myCustomerService") @Qualifier("myCustomerService") @Profile(Profiles.MYSQL) public class MyCustomerService implements CustomerService{ @Autowired private MyCustomerRepository customerRepository; @Override public Customer create(Customer customer) { MyCustomer myCustomer = new MyCustomer(customer); customerRepository.save(myCustomer); return myCustomer; } @Override public Customer read(String id) { return customerRepository.findOne(id); } @Override public List<MyCustomer> retrieveAllCustomers() { return customerRepository.findAll(); } @Override public Customer update(String id, Customer customerData) { MyCustomer customer = customerRepository.findOne(id); customer.copy(customerData); customerRepository.save(customer); return customer; } @Override public void delete(String id) { customerRepository.delete(id); } @Override public void deleteAll() { customerRepository.deleteAll(); } }
异常信息
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'customerController' defined in file [/demo/controller/CustomerController.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'demo.service.CustomerService' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
application.yml配置
spring: datasource: url: jdbc:mysql://localhost/mydb username: root driver-class-name: com.mysql.jdbc.Driver data: mongodb: uri: mongodb://localhost:27017/MongoTest cassandra: keyspace-name: db_keyspace contact-points: localhost port: 9042 schema-action: create_if_not_exists neo4j: uri: bolt://localhost:7474 username: neo4j password: root jpa: properties: hibernate.show_sql: true hibernate.use_sql_comments: true hibernate.format_sql: true hibernate.dialect: org.hibernate.dialect.MySQL5Dialect mvc: view: prefix: /WEB-INF/jsp suffix: .jsp logging: file: logback-spring.xml level: DEBUG
要实现同时从所有数据库查询数据,你需要修改CustomerController,让Spring注入所有CustomerService的实现类,而不是单个实例。具体步骤如下:
1. 修改Controller的依赖注入方式
把原来的单个CustomerService替换成List<CustomerService>或者Map<String, CustomerService>(Map的key就是每个Service的@Service注解里的名称,比如myCustomerService)。这样Spring会自动把所有实现了CustomerService的Bean都注入进来。
修改后的CustomerController示例:
@RestController @RequestMapping("/customer") public class CustomerController { // 注入所有CustomerService实现类 private final List<CustomerService> customerServices; // 构造函数注入(推荐构造函数注入而非@Autowired) public CustomerController(List<CustomerService> customerServices) { this.customerServices = customerServices; } // 核心修改:合并所有数据库的客户数据 @RequestMapping(method = RequestMethod.GET) public List<Customer> retrieveCustomers(){ List<Customer> allCustomers = new ArrayList<>(); // 遍历每个Service,查询各自数据库的客户数据并合并 for (CustomerService service : customerServices) { allCustomers.addAll(service.retrieveAllCustomers()); } return allCustomers; } // 可选:扩展create方法,将客户数据同步到所有数据库 @RequestMapping(value = "/{Id}", method = RequestMethod.POST) public ResponseEntity<List<Customer>> create(@RequestBody CustomerData customerData) { List<Customer> createdCustomers = new ArrayList<>(); for (CustomerService service : customerServices) { Customer created = service.create(customerData); createdCustomers.add(created); } return new ResponseEntity<>(createdCustomers, HttpStatus.CREATED); } // 同理,update、delete等方法也可以按需修改,实现多库同步操作 }
2. 确保所有Service实现类都被正确激活
每个NoSQL的Service实现类都要像MyCustomerService一样,加上@Service("xxxCustomerService")注解,确保Spring能扫描到并注册为Bean。如果使用了@Profile,需要在配置中激活所有需要的数据库Profile:
spring: profiles: active: mysql, mongo, cassandra, neo4j # 激活所有需要的数据库Profile
3. 处理泛型类型警告(可选)
由于CustomerService是泛型接口,Spring可能会因为类型擦除抛出警告。如果所有实现类都返回Customer类型,可以将接口改为非泛型,或者注入时使用@Autowired配合@SuppressWarnings("rawtypes")来忽略警告,不影响功能正常运行。
这样修改后,你就能在retrieveCustomers方法中获取所有数据库的客户数据并合并,同时其他CRUD方法也可以按需扩展为操作所有数据库,完美解决不能用@Primary的问题。
内容的提问来源于stack exchange,提问作者heythere

