I'm using Spring and JPA repositories, with multiple database schemas for different entities, and configuring each of them with the proper values and independent beans:
#Configuration
#EnableJpaRepositories(basePackageClasses = {ClassFromSchemaXRepository.class}, entityManagerFactoryRef = "ClassFromSchemaXEntityManagerFactoryA", transactionManagerRef = "ClassFromSchemaXTransactionManager")
{
#Bean
public DataSource classFromSchemaXDataSource(){
HikariDataSource hds = new HikariDataSource();
hds.setJdbcUrl(env.getProperty("dataSource.jdbcUrl.schema.x"));
hds.setUsername(env.getProperty("dataSource.user"));
hds.setPassword(env.getProperty("dataSource.password"));
//...
}
#Bean
public LocalContainerEntityManagerFactoryBean classFromSchemaXEntityManagerFactory() {
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setDataSource(classFromSchemaXDataSource());
em.setPackagesToScan(new String[] { "com.company.core.domain.x" });
//...
}
#Bean
public PlatformTransactionManager classFromSchemaXTransactionManager() {
//...
}
}
This configuration works as expected, and the entire set of entities which are located under the 'com.company.core.domain.x' package is mapped to this sets of beans with the schema connection string which is defined in env.getProperty("dataSource.jdbcUrl.schema.x")
However, I am now trying to configure a specific entity to be used by changeable schema and therefor dymanic DataSource/EntityManagerFactory/TransactionManager.
The business logic should determine which schema should be used at runtime.
What is the best method of doing that?
I've found what I was looking for which is extending the AbstractRoutingDataSource class, and overriding the determineCurrentLookupKey() method
Related
I'm creating a new service with the goal of consuming Kafka events in an idempotent manner and storing the data into a new PostgreSQL database.
The event will provide data which will be used in the composite key:
#Embeddable
public class MyCompositeKey implements Serializable {
#Column(name="field1", nullable = false)
private UUID field1;
#Column(name="field2", nullable = false)
private UUID field2;
#Column(name="field3", nullable = false)
private UUID field3;
... boilerplate Constructors/getters ...
And the Entity will be referencing it via #EmbeddedId:
#Entity
#Table
public class MyEntity implements Serializable {
#EmbeddedId private MyCompositeKey myCompositeKey;
... Columns/Constructors/getters ...
When an event is consumed, I want to let spring-data-jpa be smart enough to know whether we are replacing data from an existing MyEntity, or creating a new row.
The logic was deemed safe enough to use the CrudRepository#save method before researching the expectation of the logic within that method:
#Transactional
public <S extends T> S save(S entity) {
if (this.entityInformation.isNew(entity)) {
this.em.persist(entity);
return entity;
} else {
return this.em.merge(entity);
}
}
I've gotten to the point where the transactions appear to be completed, but no records are persisted to the table.
I've confirmed via debugging that the call to #save is branching into the return this.em.merge(entity) logic referenced above.
I've only found one possibly helpful blog post[1] for a similar scenario, and am lost on where to go next after it didn't seem to resolve the issue.
The only other option I can foresee is to manually go through a potential three-query execution:
findById
if exists, delete
save
Components
spring-boot-starter 2.0.6
spring-boot-starter-data-jpa 2.0.6
hibernate 5.2.x
References
[1] https://jivimberg.io/blog/2018/11/05/using-uuid-on-spring-data-jpa-entities/
Alright, I found the issue. All of this design was working fine, it was the configuration which was missing.
For some context - Spring Boot seems to configure default javax.sql.DataSource, default javax.persistence.EntityManagerFactory, and default org.springframework.transaction.PlatformTransactionManager beans.
My context was configured with a javax.sql.DataSource bean in order to specify a configuration prefix distinction using org.springframework.boot.context.properties.ConfigurationProperties.
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(basePackages = {"com.myservice"})
public class RepositoryConfiguration {
#Bean
#ConfigurationProperties(prefix = "myservice.datasource")
public DataSource dataSource() {
return DataSourceBuilder.create().build();
}
}
My context did not add in replacements for the dependent javax.persistence.EntityManagerFactory and org.springframework.transaction.PlatformTransactionManager beans.
The fix was to add in all of the configuration. From the docs:
You must create LocalContainerEntityManagerFactoryBean and not EntityManagerFactory directly, since the former also participates in exception translation mechanisms in addition to creating EntityManagerFactory.
The resulting configuration:
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(basePackages = {"com.myservice"})
public class RepositoryConfiguration {
#Bean
#ConfigurationProperties(prefix = "myservice.datasource")
public DataSource dataSource() {
return DataSourceBuilder.create().build();
}
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
factory.setJpaVendorAdapter(vendorAdapter);
factory.setPackagesToScan("com.myservice");
factory.setDataSource(dataSource());
return factory;
}
#Bean
public PlatformTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
JpaTransactionManager txManager = new JpaTransactionManager();
txManager.setEntityManagerFactory(entityManagerFactory);
return txManager;
}
}
I'm feeling frustrated with my knowledge of spring and java, I hope someone can help me :)
I read the documentation of spring and searched for examples of how to implement multi tenancy in hanacloud platform but no one using spring data/eclipseLink ...
I really don't know how it is the life cycle of objects in framework and how to correctly configure in order to isolate the tenants in spring
The multi tenant strategy adopted was column descriptor to isolate the tenants, this column is described in superclass mapped in child entities...
Each consumer client is identified by url and can be accessed with this API, I've created a route to test the tenant id and the tenant id is different for each URl, which means the subscriptions to my applications its ok...
But the spring data don't isolate the data, i guess he is not creating the entity manager and repositories based in tenant id of consumers and create based in tenant id of provider, the ideia is create only one persistency unit and the data source separate the data based in the column descriptor,
The tenant's ids are generated by the system and its not possible create additional persistency units, they are created in the moment of subscription...
Here's the questions
About the LocalContainerEntityManagerFactoryBean, when he is called in stack ?
It's possible to create one entity manager for each tenant id based in request ?
my configuration class to set datasource
#Configuration
#Profile({ "neo" })
#EnableTransactionManagement
public class DataSourceNeoConfig {
#Bean
public DataSource jndiDataSource() throws IllegalArgumentException, NamingException {
final JndiDataSourceLookup dataSourceLookup = new JndiDataSourceLookup();
DataSource dataSource = dataSourceLookup.getDataSource("java:comp/env/jdbc/DefaultDB");
return dataSource;
}
#Bean
public JpaVendorAdapter jpaVendorAdapter() {
EclipseLinkJpaVendorAdapter jpaVendorAdapter = new EclipseLinkJpaVendorAdapter();
jpaVendorAdapter.setGenerateDdl(true);
jpaVendorAdapter.setShowSql(true);
return jpaVendorAdapter;
}
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean lef = new LocalContainerEntityManagerFactoryBean();
try {
lef.setDataSource(jndiDataSource());
} catch (IllegalArgumentException | NamingException e) {
e.printStackTrace();
}
lef.setJpaVendorAdapter(jpaVendorAdapter());
Properties properties = new Properties();
properties.setProperty("eclipselink.weaving", "false");
properties.setProperty(PersistenceUnitProperties.LOGGING_LEVEL, "FINE");
properties.setProperty("eclipselink.ddl-generation", "create-tables");
// eclipselink.tenant.id is column the identifier in mapped class
// #TenantDiscriminatorColumn(name = "TENANT_ID", contextProperty = "eclipselink.tenant.id", length = 36)
properties.setProperty("eclipselink.tenant.id", TenantHolder.getTenant());
lef.setPackagesToScan("com.strongit.models");
lef.setJpaProperties(properties);
lef.afterPropertiesSet();
return lef;
}
#Bean
public PlatformTransactionManager transactionManager(EntityManagerFactory emf){
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(emf);
return transactionManager;
}
}
The springBootApplication
#SpringBootApplication
#EnableJpaRepositories(basePackageClasses = AppNameApplication.class, entityManagerFactoryRef = "entityManagerFactory")
public class AppNameApplication extends SpringBootServletInitializer {
public static void main(String[] args) {
SpringApplication.run(AvaliacaoApplication.class, args);
}
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return builder.sources(AvaliacaoApplication.class);
}
}
Thanks in advance !!
I use Spring Data and decided that I want to create new custom data type that can be used in Hibernate entities. I checked the documentation and choose BasicType and implemented it according to this official user guide.
I wanted to be able to register the type under its class name and be able to use the new type in entities without need for #Type annotation. Unfortunately, I’m unable to get reference to the MetadataBuilder or Hibernate configuration to register the new type. Is there a way how to get it in Spring Data? It seems that initialization of the Hibernate is hidden from the user and cannot be easily accessed. We use following class to initialize the JPA:
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(
entityManagerFactoryRef = "entityManagerFactory",
transactionManagerRef = "transactionManager",
basePackages = {
"..." // omitted
}
)
public class JpaConfiguration implements TransactionManagementConfigurer {
#Primary
#Bean(name = "entityManagerFactory")
public LocalContainerEntityManagerFactoryBean configureEntityManagerFactory(
DataSource dataSource,
SchemaPerTenantConnectionProviderImpl provider) {
LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();
entityManagerFactoryBean.setPersistenceUnitName("defaultPersistenceUnit");
entityManagerFactoryBean.setDataSource(dataSource);
entityManagerFactoryBean.setPackagesToScan(
"..." // omitted
);
entityManagerFactoryBean.setJpaProperties(properties(provider));
entityManagerFactoryBean.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
return entityManagerFactoryBean;
}
#Primary
#Bean(name = "transactionManager")
public PlatformTransactionManager annotationDrivenTransactionManager() {
return new JpaTransactionManager();
}
private Properties properties(SchemaPerTenantConnectionProviderImpl provider) {
Properties properties = new Properties();
// omitted
return properties;
}
}
I found lots of articles about way how to do it with Hibernate’s Configuration object but this one refers to Hibernate 3 and 4. I also found way how to do it via Hibernate org.hibernate.integrator.spi.Integrator but when I use it according to the articles I found I will get exception with the message “org.hibernate.HibernateException: Can not alter TypeRegistry at this time”
What is the correct way to register custom types in Spring Data?
I finally figured it out. I will post it here for others:
I created a new class that implements org.hibernate.boot.spi.SessionFactoryBuilderFactory interface. In this class I can get reference to the TypeResolver from metadata and register my custom type.
package com.example.configuration;
import org.hibernate.boot.SessionFactoryBuilder;
import org.hibernate.boot.spi.MetadataImplementor;
import org.hibernate.boot.spi.SessionFactoryBuilderFactory;
import org.hibernate.boot.spi.SessionFactoryBuilderImplementor;
import org.slf4j.LoggerFactory;
import com.example.CustomType;
public class CustomDataTypesRegistration implements SessionFactoryBuilderFactory {
private static final org.slf4j.Logger logger = LoggerFactory.getLogger(CustomDataTypesRegistration.class);
#Override
public SessionFactoryBuilder getSessionFactoryBuilder(final MetadataImplementor metadata, final SessionFactoryBuilderImplementor defaultBuilder) {
logger.info("Registering custom Hibernate data types");
metadata.getTypeResolver().registerTypeOverride(CustomType.INSTANCE);
return defaultBuilder;
}
}
The class must be then registered via Java ServiceLoader mechanism by adding full name of the class with its packages into the file with name org.hibernate.boot.spi.SessionFactoryBuilderFactory into the java module’s META-INF/services directory:
src/main/resources/META-INF/services/org.hibernate.boot.spi.SessionFactoryBuilderFactory
The file can contain multiple lines, each referencing different class. In this case it is:
com.example.configuration.CustomDataTypesRegistration
This way the Spring Data starts and custom type is successfully registered during Hibernate initialization.
What helped my quite a lot was this SO answer that deals with schema export in Hibernate 5 under Spring Data.
There's a much easier solution to this -- in fact, it's just 1 line of code. You can just use the #TypeDef annotation and thus avoid having to register the custom type:
#Entity(name = "Product")
#TypeDef(
name = "bitset",
defaultForType = BitSet.class,
typeClass = BitSetType.class
)
public static class Product {
#Id
private Integer id;
private BitSet bitSet;
For an example, see "Example 11. Using #TypeDef to register a custom Type" in http://docs.jboss.org/hibernate/orm/5.3/userguide/html_single/Hibernate_User_Guide.html
I use JPA with Spring 4.3.9 and Hibernate 5.0.5 and I use custom property EntityManagerFactoryBuilderImpl.TYPE_CONTRIBUTORS with Spring
LocalContainerEntityManagerFactoryBean to override Hibernate BasicTypes.
final Properties jpaProperties = new Properties();
jpaProperties.put(EntityManagerFactoryBuilderImpl.TYPE_CONTRIBUTORS, new TypeContributorList() {
#Override
public List<TypeContributor> getTypeContributors() {
return Lists.newArrayList(new CustomDateTimeTypeContributor());
}
});
final LocalContainerEntityManagerFactoryBean factoryBean = new
LocalContainerEntityManagerFactoryBean();
factoryBean.setJpaProperties(jpaProperties);
factoryBean.setJpaVendorAdapter(jpaVendorAdapter);
return factoryBean;
An alternative to what xMort did, can be registering a org.hibernate.boot.model.TypeContributor via ServiceLoader mechanism.
Implement TypeContributor
package com.example.configuration;
import org.hibernate.boot.model.TypeContributions;
import org.hibernate.boot.model.TypeContributor;
import org.hibernate.service.ServiceRegistry;
public class CustomTypeContributor implements TypeContributor {
#Override
public void contribute(TypeContributions typeContributions, ServiceRegistry serviceRegistry) {
typeContributions.contributeType(CustomType.INSTANCE);
}
}
Create a file org.hibernate.boot.model.TypeContributor into the java module’s META-INF/services
src/main/resources/META-INF/services/org.hibernate.boot.model.TypeContributor
Reference the TypeContributor, file content:
ru.eastbanctech.scs.air.transaction.repositories.StringArrayTypeContributor
As of Hibernate 5.2.17, the code that picks up the TypeContributor service can be found at org.hibernate.boot.model.process.spi.MetadataBuildingProcess#handleTypes.
Inspired by #alex.tran's answer:
#Bean
public HibernatePropertiesCustomizer customHibernateTypeRegistrar() {
return (Map<String, Object> props) -> {
props.put(
EntityManagerFactoryBuilderImpl.TYPE_CONTRIBUTORS,
(TypeContributorList) () -> Arrays.asList((TypeContributor) (typeContributions, serviceRegistry) -> {
// Deregister built-in org.hibernate.type.OffsetDateTimeSingleColumnType as it hides our mapping.
final BasicTypeRegistry basicTypeRegistry = typeContributions.getTypeConfiguration().getBasicTypeRegistry();
Class<OffsetDateTime> clazz = OffsetDateTime.class;
basicTypeRegistry.unregister(clazz.getName());
basicTypeRegistry.unregister(clazz.getSimpleName());
typeContributions.contributeSqlTypeDescriptor(OffsetDateTimeMsSqlTypeDescriptor.INSTANCE);
typeContributions.contributeJavaTypeDescriptor(OffsetDateTimeJavaTypeDescriptor.INSTANCE);
typeContributions.contributeType(OffsetDateTimeSingleColumnType.INSTANCE);
}));
};
}
Magical constant EntityManagerFactoryBuilderImpl.TYPE_CONTRIBUTORS & bean of type HibernatePropertiesCustomizer do the magic in EntityManagerFactoryBuilderImpl:
HibernateJpaAutoConfiguration -> HibernateJpaConfiguration -> JpaBaseConfiguration -> LocalContainerEntityManagerFactoryBean -> HibernatePersistenceProvider -> Bootstrap.getEntityManagerFactoryBuilder() -> EntityManagerFactoryBuilderImpl.
See details: https://discourse.hibernate.org/t/map-ms-sql-server-datetimeoffset-to-java-8-offsetdatetime/5937/7
Solution keeps Boot magic intact as it plugs into Spring Boot autoconfiguration, also there is no need to redefine lots of beans (like transaction or entity manager).
You can use the HibernatePropertiesCustomizer class spring will load all beans of this type (for more details see HibernateJpaConfiguration)
Also see PG Json config Test
this is an example (I'm adding Json type only in case postgress dialect is configured)
#Bean
#ConditionalOnProperty(value = "spring.jpa.properties.hibernate.dialect",
havingValue = "org.hibernate.dialect.PostgreSQL10Dialect")
public HibernatePropertiesCustomizer hibernatePropertiesCustomizerPG(ObjectMapper objectMapper) {
return hibernateProperties -> {
hibernateProperties.put(EntityManagerFactoryBuilderImpl.TYPE_CONTRIBUTORS,
(TypeContributorList) () -> List.of(
(TypeContributor) (TypeContributions typeContributions, ServiceRegistry serviceRegistry) ->
typeContributions.contributeType(new JsonType(objectMapper))));
};
}
This is the same than have
#TypeDefs({
#TypeDef(name = "json", typeClass = JsonType.class)
})
#MappedSuperclass
public class BaseEntity {
//Code omitted for brevity
}
I prefer to do in this way to not override any bean handled by spring such as SessionFactory
There is another solution.
We extend Hibernate MetadaSources overriding methods getMetadataBuilder by creating MetadataBuilder:
import org.hibernate.boot.MetadataBuilder;
import org.hibernate.boot.MetadataSources;
import org.hibernate.boot.internal.MetadataBuilderImpl;
import org.hibernate.boot.registry.StandardServiceRegistry;
public class CustomTypeIncludedMetadataSources extends MetadataSources {
#Override
public MetadataBuilder getMetadataBuilder() {
MetadataBuilder b = new MetadataBuilderImpl(this);
applyCustomTypes(b);
return b;
}
#Override
#Deprecated
public MetadataBuilder getMetadataBuilder(StandardServiceRegistry serviceRegistry) {
MetadataBuilder b = new MetadataBuilderImpl(this, serviceRegistry);
applyCustomTypes(b);
return b;
}
private void applyCustomTypes(MetadataBuilder b) {
b.applyBasicType(new CustomType());
...
}
}
Next, in Spring #Configuration class we set an instance of our CustomTypeIncludedMetadataSources into Spring LocalSessionFactoryBean:
#Bean
public LocalSessionFactoryBean sessionFactory(DataSource dataSource) {
LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean();
sessionFactory.setMetadataSources(new CustomTypeIncludedMetadataSources());
sessionFactory.setDataSource(dataSource);
...
return sessionFactory;
}
I have 5 micro services with different database name so apart from every properties is common so included in application.properties
spring.datasource.driver-class-name=org.mariadb.jdbc.Driver
spring.datasource.username=${local.db.username:}
spring.datasource.password=${local.db.password:}
And i had class commondatasource.java which included properties
#PropertySource({ "classpath:application-test.properties" })
#Component
public class CommonDataSourceConfig {
#Autowired
private Environment env;
#Primary
#Bean
public DataSource dataadmindataSource()
{
final DataSource dataSource = new DataSource();
dataSource.setDriverClassName(Preconditions.checkNotNull(env.getProperty("spring.datasource.driverClassName")));
dataSource.setUrl(Preconditions.checkNotNull("spring.datasource.url"));
dataSource.setUsername(Preconditions.checkNotNull(env.getProperty("spring.datasource.username")));
dataSource.setPassword(Preconditions.checkNotNull(env.getProperty("spring.datasource.password")));
}
}
now i want to call this commondatasource in every micro services datasourceconfig.java
#Configuration
#EnableJpaRepositories(basePackages = {
"xxx.repositories" }, entityManagerFactoryRef = "xxEntityManager",
transactionManagerRef = "xxTransactionManager")
public class xxSourceConfig
{
#Autowired
private Environment env;
#Autowired
private CommonDataSourceConfig common;
#Value("${xx.datasource.url}")
private String url;
/**
* Configures the entity manager
*
* #return
*/
#Primary
#Bean
public LocalContainerEntityManagerFactoryBean dataAdminEntityManager()
{
LocalContainerEntityManagerFactoryBean entityManager = new LocalContainerEntityManagerFactoryBean();
entityManager.setDataSource(common.dataadmindataSource());
entityManager.setPackagesToScan(new String[] { "com.boeing.toolbox.dataadmin.domain" });
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
entityManager.setJpaVendorAdapter(vendorAdapter);
HashMap<String, Object> properties = new HashMap<String, Object>();
properties.put("hibernate.hbm2ddl.auto", env.getProperty("spring.jpa.hibernate.ddl-auto"));
properties.put("hibernate.dialect", env.getProperty("spring.jpa.database-platform"));
entityManager.setJpaPropertyMap(properties);
return entityManager;
}
}
but now i want to implement by this class https://github.com/spring-projects/spring-boot/blob/master/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceAutoConfiguration.java
i am new to this concept kindly help on this how to implement on above class in my project
I came across this question because I wanted to remove an existing custom DataSource configuration and just rely on DataSourceAutoConfiguration instead.
The thing is that this auto-configuration applies if
DataSource (or EmbeddedDatabaseType) is on the classpath; and
you don't have a DataSource bean configured; and
either
you have a spring.datasource.type property configured (for Spring Boot 1.3+), or
there is a supported connection pool available (e.g. HikariCP or Tomcat connection pool), or
there is an embedded (in-memory) database driver available (such as H2, HSQLDB or Derby) – probably not what you want.
In your case, the second condition fails, since CommonDataSourceConfig declares a DataSource bean. The auto-configuration thus backs-off.
You should thus remove that bean, and make sure that the 3rd condition is also satisfied by either setting the spring.datasource.type or, probably better, putting a compatible connection pool on the classpath.
The DataSourceAutoConfiguration should then do its job (based on your properties) and you should be able to inject your DataSource directly with #Autowired.
I am trying to configure multiple JPA entity/transaction managers within the same application context using Spring's #Configuration class.
When the context loads, Spring is having difficulties auto-wiring the beans because they implement the same interfaces.
Unfortunately, I'm using legacy code so I can't auto-wire the beans directly and use the #Qualifier annotations, which is why I'm trying to do it using the configuration class.
Within a #Bean declaration, is there any way to qualify which bean should be injected? I thought that using a direct method call would be enough, but it typically results in errors such as
NoUniqueBeanDefinitionException: No qualifying bean of type
[javax.sql.DataSource] is defined: expected single matching bean but
found 4
Here's an example of what I'm trying to do below:
#Configuration
public class ApplicationConfig {
#Bean(name = "transactionManager1")
public PlatformTransactionManager transactionManager1() {
return new JpaTransactionManager(entityManagerFactory1());
}
#Bean(name = "entityManagerFactory1")
public EntityManagerFactory entityManagerFactory1() {
...
LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
factory.setDataSource(dataSource1());
...
}
#Bean(destroyMethod = "")
#ConfigurationProperties(prefix = "datasource.test1")
public JndiObjectFactoryBean jndiObjectFactoryBean1() {
return new JndiObjectFactoryBean();
}
#Bean(name = "dataSource1")
public DataSource dataSource1() {
JndiDataSourceLookup lookup = new JndiDataSourceLookup();
return lookup.getDataSource(jndiObjectFactoryBean1().getJndiName());
}
#Bean(name = "transactionManager2")
public PlatformTransactionManager transactionManager2() {
return new JpaTransactionManager(entityManagerFactory2());
}
#Bean(name = "entityManagerFactory2")
public EntityManagerFactory entityManagerFactory2() {
...
LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
factory.setDataSource(dataSource2());
...
}
#Bean(destroyMethod = "")
#ConfigurationProperties(prefix = "datasource.test2")
public JndiObjectFactoryBean jndiObjectFactoryBean2() {
return new JndiObjectFactoryBean();
}
#Bean(name = "dataSource2")
public DataSource dataSource2() {
JndiDataSourceLookup lookup = new JndiDataSourceLookup();
return lookup.getDataSource(jndiObjectFactoryBean2().getJndiName());
}
I suppose I could try to inject the beans directly via the Spring context's getBean() method, but is there a cleaner way of doing this?
I'm not too familiar with the #Primary annotation, but based on what I've read I don't know how spring would autowire the secondary data source in this case since it looks like it would always pick the beans with #Primary first.
If you cannot change the injection sites to add qualifiers, then you're going to have to create a delegating DataSource based on some logic (which you haven't detailed in the question).
Something like this.
#Primary #Bean
public DelegatingDataSource delegatingDataSource(List<DataSource> sources) {
return new DelegatingDataSource() {
#Override
public DataSource getTargetDataSource() {
// decide which dataSource to delegate to
return sources.get(0);
}
}
}
I've used DelegatingDataSource, but that may not be able to provide what you need. You may need to get more advanced with some kind of interceptor/aspect to get details of the caller on which to base the DataSource selection.
However it's implemented, you need to specify a #Primary bean and use it as a proxy.