2012-08-23 16 views
8

Logré implementar con éxito el cambio dinámico de las conexiones de bases de datos siguiendo el artículo http://blog.springsource.com/2007/01/23/dynamic-datasource-routing/.Crear dinámicamente granos de primavera y modificar propiedades en beans existentes

Pero ahora el problema es que tengo una lista de URL de base de datos en un archivo de configuración que es administrado por una aplicación heredada.

¿Hay alguna forma de crear beans en ese contexto de primavera a partir de una lista de valores (por ejemplo, Year2011DataSource, Year2012DataSource, ...) y rellenar el mapa del bean de dataSource con los beans que se acaban de crear?

<!-- 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

añadido la versión final del código. Gracias por ayudar a la gente. – Vedran

Respuesta

8

Una buena opción para este requisito Creo que es una costumbre BeanFactoryPostProcessor - leer en la configuración legado y generar las fuentes de datos en el grano a medida post-procesador de fábrica:

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

¡Genial! Ahora solo tengo que averiguar cómo rellenar la propiedad "targetDataSource" en el bean "dataSource" existente con las referencias a los beans que se crearon de esta manera. – Vedran

+0

Lo tengo todo funcionando, y pondré el ejemplo final en el pregunta original cuando la limpio un poco. ¡Gracias de nuevo! :) – Vedran

1

Puedo decirles acerca de la anotación. He de añadir URLs y de configuración en el archivo de propiedades y hacer algo como lo siguiente:

@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

¿Son esos mapas java.util.Map? Debido a que está definiendo/usando solo un elemento, no dos (clave, valor) ... – helios

+0

sí, respuesta editada. En realidad, escribí el código en el bloc de notas ++ :) –

+0

@helios: Gracias :) –

1

Por lo que yo sé, no hay una solución fuera de la caja utilizando la configuración de XML. Sin embargo, una solución simple para lograr esto se describe en this answer utilizando la abstracción FactoryBean en Spring.

1

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

Siguiendo el consejo de Biju, tengo todo funcionando como esto:

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

La sección "URL de base de datos por año" en la configuración del resorte ya no existe, los beans se crean en BeanFactoryPostProcessor.

frijol

"origen de datos" tiene sus propiedades configuradas con datos ficticios que se sustituye en el 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> 

Y esta es la aplicación 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"))); 
     } 
    } 
} 
Cuestiones relacionadas