简体   繁体   中英

Changing the value of an @Bean at runtime in Java Spring Boot

In Java with Spring Boot framework there is an @Bean named DataSource that is used to make connections to a database, and is used by another @Bean called JdbcTemplate that serves to perform actions on the database. But, there is a problem, this @Bean DataSource , which serves to make the connection to the database, requires the properties for the connection to be preconfigured (url, username and password). The @Bean DataSource needs to start with an "empty" or "default" value at project startup, and at runtime it changes this value. I want a certain Endpoint to execute the action of changing the value of @Bean , to be more exact. And with the change of the value of the @Bean DataSource the JdbcTemplate , consequently, will be able to perform actions in several database.

Some details:

  • I have already evaluated this issue of using multiple databases, and in my case, it will be necessary.
  • All databases to be connected have the same model.
  • I do not think I need to delete and create another @Bean DataSource at runtime, maybe just override the @Bean values ​​that the Spring Boot itself already creates automatically.
  • I have already made the @Bean DataSource start with an "empty" value by making a method with the @Bean annotation and that returns a DataSource object that is literally this code: DataSourceBuilder.build().create(); .
  • My English is not very good so if it's not very understandable, sorry.

DataSource @Bean code:

@Bean
public DataSource dataSource() {
    return DataSourceBuilder.create().build();
}

Main class:

@SpringBootApplication(scanBasePackages = "br.com.b2code")
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class RunAdm extends SpringBootServletInitializer implements 
CommandLineRunner {

    public static final String URL_FRONTEND = "*";


    /**
     * Método main do módulo de Gestão.
     *
     * @param args argumentos de inicialização
     * @throws Exception uma exception genérica
     */
    public static void main(String[] args) throws Exception {
        SpringApplication.run(RunAdm.class, args);
    }

    @Override
    protected SpringApplicationBuilder 
    configure(SpringApplicationBuilder application) {
        return application.sources(RunAdm.class);
    }

    @Override
    public void run(String... args) throws Exception {
    }

}

A class to exemplify how I use JdbcTemplate :

@Repository
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class ClienteQueryRepositoryImpl implements ClienteQueryRepository {

    private final @NonNull
    JdbcTemplate jdbc;

    @Override
    public List<Cliente> findAll() {
        return jdbc.query(ClienteQuerySQL.SELECT_ALL_CLIENTE_SQL, new ClienteRowMapper());
    }

}

I think as a general approach you might consider a Proxy Design Pattern for the actual DataSource Implementation.

Let's suppose the DataSource is an interface that has getConnection method by user and password (other methods are not really important because this answer is theoretical):

interface DataSource {
   Connection getConnection(String user, String password);
}

Now, in order to maintain many databases you might want to provide an implementation of the datasource which will act as a proxy for other datasources that will be created on the fly (upon the endpoint call as you say).

Here is an example:

public class MultiDBDatasource implements DataSource {
    private DataSourcesRegistry registry;

    public Connection getConnection(String user, String password) {
        UserAndPassword userAndPassword = new UserAndPassword(user, password);

        registry.get(userAndPassword);
    }
}


@Component
class DataSourcesRegistry {
   private Map<UserAndPassword, DataSource> map = ...
   public DataSource get(UserAndPassword a)  { 
         map.get(a); 
   }


   public void addDataSource(UserAndPassword cred, DataSource ds) {
      // add to Map 
      map.put(...)
   }
}


@Controller
class InvocationEndPoint {

    // injected by spring
    private DataSourcesRegistry registry;

    @PostMapping ... 
    public void addNewDB(params) {
         DataSource ds = createDS(params); // not spring based 
         UserAndPassword cred = createCred(params);
         registry.addDataSource(cred, ds);
    } 

}

A couple of notes:

  1. You should "override" the bean of DataSource offered by spring - this can be done by defining your own bean with the same name in your own configuration that will take precedence over spring's definition.

  2. Spring won't create Dynamic Data Source, they'll be created from the "invocation point" (controller in this case for brevity, in real life probably some service). in any case only Registry will be managed by spring, not the data sources.

    1. Keep in mind that this approach is very high-level, in a real life you'll have to think about:
      • Connection Pooling
      • Metering
      • Transaction Support
      • Multithreaded Access and many other things

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