2012-08-23 12 views
8

Sono riuscito a implementare correttamente il cambio dinamico delle connessioni del database seguendo l'articolo http://blog.springsource.com/2007/01/23/dynamic-datasource-routing/.Creare dinamicamente bean di primavera e modificare le proprietà sui bean esistenti

Ma ora il problema è che ho un elenco di URL di database in un file di configurazione gestito da un'applicazione legacy.

C'è un modo per creare bean in tale contesto Spring da un elenco di valori (ad esempio Year2011DataSource, Year2012DataSource, ...) e compilare la mappa del bean dataSource con quei bean appena creati?

<!-- Property file located in the legacy application's folder --> 
<context:property-placeholder location="file:///D:/config.properties" /> 

<!-- Shared data source properties are read from the config.properties file --> 
<bean id="parentDataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource" abstract="true"> 
    <property name="driverClassName" value="${db.driver}" /> 
    <property name="username" value="${db.user}" /> 
    <property name="password" value="${db.password}" /> 
</bean> 

<!-- Database urls by year --> 
<bean id="Year2012DataSource" parent="parentDataSource"> 
    <property name="url" value="jdbc:sqlserver://localhost;databaseName=DbName_v570_2012" /> 
</bean> 
<bean id="Year2011DataSource" parent="parentDataSource"> 
    <property name="url" value="jdbc:sqlserver://localhost;databaseName=DbName_v570_2011" /> 
</bean> 
<bean id="Year2010DataSource" parent="parentDataSource"> 
    <property name="url" value="jdbc:sqlserver://localhost;databaseName=DbName_v570_2010" /> 
</bean> 
<!-- ... and so on, these should instead be populated dynamically ... --> 

<!-- DbConnectionRoutingDataSource extends AbstractRoutingDataSource --> 
<bean id="dataSource" class="someProject.DbConnectionRoutingDataSource"> 
    <property name="targetDataSources"> 
     <map key-type="int"> 
      <entry key="2011" value-ref="Year2011DataSource" /> 
      <entry key="2010" value-ref="Year2010DataSource" /> 
      <!-- ... and so on, these also should instead be populated dynamically ... --> 
     </map> 
    </property> 
    <property name="defaultTargetDataSource" ref="Year2012DataSource" /> 
</bean> 
+0

Aggiunta la versione finale del codice. Grazie per l'aiuto gente. – Vedran

risposta

8

Una buona misura per questo requisito credo sia una consuetudine BeanFactoryPostProcessor - leggere nella configurazione legacy e generare le origini dati nel bean personalizzato post processore fabbrica:

class MyDatasourceRegisteringBeanFactoryPostProcessor implements BeanFactoryPostProcessor { 

    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) { 
     //Read in details from legacy properties.. build custom bean definitions and register with bean factory 
     //for each legacy property... 
      BeanDefinitionBuilder datasourceDefinitionBuilder = BeanDefinitionBuilder.rootBeanDefinition(BasicDataSource.class).addPropertyValue("url", "jdbc.."); 
      beanFactory.registerBeanDefinition(datasourceDefinitionBuilder.getBeanDefinition()); 
    } 
} 
+0

Great! Ora ho solo bisogno di capire come popolare la proprietà "targetDataSource" sul bean "dataSource" esistente con i riferimenti ai bean che sono stati creati in questo modo. – Vedran

+0

Tutto ha funzionato, e metterà l'esempio finito nel domanda originale quando pulisco un po ', grazie ancora! :) – Vedran

1

Posso dire l'approccio di annotazione. Vorrei aggiungere gli URL e la configurazione nelle proprietà del file e fare qualcosa di simile seguente:

@Bean(name="dataSourceMap") 
public Map<String, DataSource> dataSourceMap(DataSource dataSource2011, DataSource dataSource2012) { 
    // read properties from properties file and create map of datasource 

    Map<DataSource> map = new HashMap<>(); 
    map.put("dataSource2011",dataSource2011); 
    map.put("dataSource2012",dataSource2012); 
    //map.put("dataSource2013",dataSource2013); 
    return map; 
} 

@Bean(name="dataSource2011",destroyMethod="close") 
public DataSource dataSource() { 
    // read properties from properties file and create map of 

    BasicDataSource dataSource = new BasicDataSource(); 
    dataSource.setDriverClassName(driverClassName); 
    dataSource.setUrl(url2011); 
    dataSource.setUsername(username2011); 
    dataSource.setPassword(password2011);    
    return dataSource; 
} 

@Bean(name="dataSource2012",destroyMethod="close") 
public DataSource dataSource() { 
    // read properties from properties file and create map of 

    BasicDataSource dataSource = new BasicDataSource(); 
    dataSource.setDriverClassName(driverClassName); 
    dataSource.setUrl(url2012); 
    dataSource.setUsername(username2012); 
    dataSource.setPassword(password2012);    
    return dataSource; 
} 
+0

Sono java.util.Map di quelle mappe? Perché stai definendo/usando solo un elemento non due (chiave, valore) ... – helios

+0

sì, risposta modificata. In realtà ho digitato il codice nel blocco note ++ :) –

+0

@helios: Grazie :) –

1

Per quanto ne so, non esiste una soluzione out-of-the-box con configurazione XML. Tuttavia, una soluzione semplice per ottenere ciò è descritta in this answer utilizzando l'astrazione FactoryBean in primavera.

1

========= ====================

Seguendo il consiglio di Biju ho tutto funzionante come this:

=======================================

La sezione "URL di database per anno" nella configurazione di primavera non esiste più, i bean vengono creati in BeanFactoryPostProcessor.

"dataSource" fagiolo ha la proprietà impostate ai dati fittizio che viene sostituito nel BeanFactoryPostProcessor:

<bean id="dataSource" class="someProject.DbConnectionRoutingDataSource"> 
    <property name="targetDataSources"> 
     <map key-type="String"> 
      <!-- Will be filled from the DatasourceRegisteringBeanFactoryPostProcessor --> 
     </map> 
    </property> 
    <property name="defaultTargetDataSource" value="java:jboss/datasources/ExampleDS" /> 
</bean> 

E questo è l'implementazione BeanFactoryPostProcessor:

@Component 
class DatasourceRegisteringBeanFactoryPostProcessor implements BeanFactoryPostProcessor { 
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) { 
     InitialContext ic = new InitialContext(); 

     // read the list of available JNDI connections 
     NamingEnumeration<?> list = ic.listBindings(getJndiDSRoot()); 
     HashSet<String> jndiDataSources = new HashSet<String>(); 
     while (list.hasMore()) { 
      /*... (ommitted for simplicity) ...*/ 
      connectionsList.put(key, value); 
     }    

     BeanDefinitionRegistry factory = (BeanDefinitionRegistry) beanFactory; 
     BeanDefinitionBuilder datasourceDefinitionBuilder; 

     // Create new beans 
     for (Entry<Integer, String> e : connectionsList.entrySet()) { 
      datasourceDefinitionBuilder = BeanDefinitionBuilder 
        .childBeanDefinition("parentDataSource") 
        .addPropertyValue("url", e.getValue()); 

      factory.registerBeanDefinition("Year" + e.getKey() + "DataSource", 
        datasourceDefinitionBuilder.getBeanDefinition()); 
     } 

     // Configure the dataSource bean properties 
     MutablePropertyValues mpv = factory.getBeanDefinition("dataSource").getPropertyValues(); 

     // Here you can set the default dataSource 
     mpv.removePropertyValue("defaultTargetDataSource"); 
     mpv.addPropertyValue("defaultTargetDataSource", 
      new RuntimeBeanReference("Year" + defaultYear + "DataSource")); 

     // Set the targetDataSource properties map with the list of connections 
     ManagedMap<Integer, RuntimeBeanReference> mm = (ManagedMap<Integer, RuntimeBeanReference>) mpv.getPropertyValue("targetDataSources").getValue(); 
     mm.clear(); 

     // Fill the map with bean references to the newly created beans 
     for (Entry<Integer, String> e : connectionsList.entrySet()) { 
      mm.put(e.getKey(), new RuntimeBeanReference("Year" + e.getKey() + "DataSource"))); 
     } 
    } 
} 
Problemi correlati