Dependency injection fails when using a library with its own application context - java

I have some issues with DI in Spring Framework (version 4.1.1). I wrote a small library that creates its own application context and uses DI to configure its components. It consists of the following classes. First the config file for the application context.
#Configuration
#ComponentScan(basePackages = { "package.containing.services" })
#EnableTransactionManagement
#EnableJpaRepositories(basePackages = {"package.containing.repositories"})
#PropertySource({"classpath:config-file.properties"})
public class ConnectorConfig {
#Resource
private Environment env;
#Bean
public DataSource dataSource() { /* DataSource config here */ }
#Bean
public EntityManagerFactory entityManagerFactory() { /* EntityManagerFactory config here */ }
#Bean
public PlatformTransactionManager transactionManager() { /* TransactionManager config here */ }
}
The JPA Repository:
#Repository
#Transactional(readOnly = true)
public interface LogEntryJpaRepository extends JpaRepository<LogEntryEntity, Long> {
#Query("SELECT l FROM LogEntryEntity l WHERE logentry_time >= :fromDate AND logentry_time <= :toDate AND logentry_data NOT LIKE 'EXTERNAL COMMAND:%' ORDER BY logentry_time ASC, logentry_id ASC")
public List<LogEntryEntity> findInTimeframe(#Param("fromDate") Timestamp fromDate, #Param("toDate") Timestamp toDate);
#Query("SELECT COUNT(l) FROM LogEntryEntity l WHERE logentry_time >= :fromDate AND logentry_time <= :toDate AND logentry_data NOT LIKE 'EXTERNAL COMMAND:%'")
public long countInTimeframe(#Param("fromDate") Timestamp fromDate, #Param("toDate") Timestamp toDate);
}
A simple service that forwards calls to the repository:
#Service
public class Connector {
#Autowired
private LogEntryJpaRepository logEntryRepo;
public LogEntryEntity getLogEntryById(long id) {
return logEntryRepo.findOne(id);
}
public List<LogEntryEntity> getLogEntriesInTimeframe(LocalDateTime fromDate, LocalDateTime toDate) {
return logEntryRepo.findInTimeframe(Timestamp.valueOf(fromDate), Timestamp.valueOf(toDate));
}
public long countLogEntriesInTimeframe(LocalDateTime fromDate, LocalDateTime toDate) {
return logEntryRepo.countInTimeframe(Timestamp.valueOf(fromDate), Timestamp.valueOf(toDate));
}
}
And finally a factory that provides the singleton instance of the service to external consumers:
public class ConnectorFactory {
private static ApplicationContext ctx;
public static Connector getInstance() {
if(ctx == null) {
ctx = new AnnotationConfigApplicationContext(ConnectorConfig.class);
}
return ctx.getBean(Connector.class);
}
private ConnectorFactory() {};
}
I wrote some unit tests to test the factory and repository and it is all working fine. I get a bean returned from the factory.
But when I include this library in a larger project the DI does no longer work properly. The larger project consists of several services that consume connectors like the above to fetch data from external services. They are injected using the #Autowired annotation It is a maven project so I added the above library to the dependencies, compiling works fine. I added a bean definition to a config class in my larger project to gain access to the connector's service:
#Configuration
#EnableTransactionManagement(proxyTargetClass = true)
#ComponentScan({"de.decoit.imonitor.gui.dao"})
#PropertySource({"classpath:imonitor-gui.properties"})
public class PersistenceConfig {
#Resource
private Environment env;
#Bean
public Connector connector() {
return ConnectorFactory.getInstance();
}
}
And this is where the problem occurs: When injecting the Connector bean into a service in my larger project, the DI inside the library fails. It says that he cannot find a suitable bean for the LogEntryJpaRepository dependency.
Why does the DI work fine when run inside a unit test but does fail when used inside another application context? In my understanding the context of the larger project does not need to know the repository bean, the configuration is done by the context inside the library. That context just exposes the service bean to external consumers, no configuration needs to be done by the context of the larger project.
Is there some mistake in my configuration or even design flaws for the library itself? This is my first library that uses DI to configure itself so maybe I just did something completely wrong?
[EDIT] Removed #Import statement from configuration class, that was a remains of testing, not there in real code. Sorry!

Related

How to create datasource bean at runtime without crashing app in Spring?

I am connecting to multiple datasources but sometimes some datasources may be offline and at that time I am geting errors on app and application is failing at startup.
I want to skip datasource configuration at startup... I have tried several ways by adding
spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
to the application.properties and also I have tried adding
#SpringBootApplication(exclude={DataSourceAutoConfiguration.class})
to the main class but still it tries to configure the datasource.
I also tried to use #Lazy annotation on all methods and on constructor as below but still getting error while creating fooEntityManagerFactory
#Lazy
#Configuration
#EnableJpaRepositories(basePackages = "com.heyo.tayo.repository.foo", entityManagerFactoryRef = "fooEntityManagerFactory", transactionManagerRef = "fooTransactionManager")
public class PersistencefooConfiguration {
#Autowired
private DbContextHolder dbContextHolder;
#Lazy
#Bean
#ConfigurationProperties("tay.datasource.foo")
public DataSourceProperties fooDataSourceProperties() {
return new DataSourceProperties();
}
#Lazy
#Bean
#ConfigurationProperties("tay.datasource.foo.configuration")
public DataSource fooDataSource() {
DataSource dataSource = fooDataSourceProperties().initializeDataSourceBuilder()
.type(BasicDataSource.class).build();
dbContextHolder.addNewAvailableDbType(DbTypeEnum.foo);
return dataSource;
}
#Lazy
#Bean(name = "fooEntityManagerFactory")
public LocalContainerEntityManagerFactoryBean fooEntityManagerFactory(
EntityManagerFactoryBuilder builder) {
//THE CODE IS FAILING AT BELOW RETURN CASE
return builder
.dataSource(fooDataSource())
.packages("com.heyo.tayo.model.foo")
.build();
}
#Lazy
#Bean
public PlatformTransactionManager fooTransactionManager(
final #Qualifier("fooEntityManagerFactory") LocalContainerEntityManagerFactoryBean fooEntityManagerFactory) {
return new JpaTransactionManager(fooEntityManagerFactory.getObject());
}
}
I have multiple classes like above for different configs for different datasources and I am adding them to available dbs static list at datasource Bean.
Here is my dbadapter factory class.
Here is my dbAdaptor factory that creates corresponding db adaptor
#Service
public class DbAdapterFactory {
#Autowired
private BeanFactory beanFactory;
#Autowired
private DbContextHolder dbContextHolder;
public DBAdapter dbAdapter(){
DbTypeEnum currentDb = dbContextHolder.getCurrentDb();
DBAdapter dbAdapter = null;
if(currentDb == DbTypeEnum.FOODB) {
dbAdapter = beanFactory.getBean(foodbadaptor.class);
} else {
dbAdapter = beanFactory.getBean(koodbadaptor.class);
}
return dbAdapter;
}
Here is db context holder that makes operation like setting default db or getting current db etc.:
#Component
public class DbContextHolder {
private DbTypeEnum dbType = DbTypeEnum.FOODB;
private Set<DbTypeEnum> availableDbTypes = new HashSet<>();
public void setCurrentDb(DbTypeEnum dbType) {
this.dbType = dbType;
}
public DbTypeEnum getCurrentDb() {
return this.dbType;
}
public List<DbTypeEnum> getAvailableDbTypes() {
return new ArrayList<>(availableDbTypes);
}
public void addNewAvailableDbType(DbTypeEnum dbTypeEnum) {
availableDbTypes.add(dbTypeEnum);
}
}
I made all #Lazy or tried #SpringBootApplication(exclude={DataSourceAutoConfiguration.class}) but still something is calling to create bean and getting error and app is closing. I want to use that config and datasource in a try-catch block and don't stop application at runtime. How can I achieve this or what am I missing on that configs or annotations ?
I believe that you can simply add in your application properties
spring.sql.init.continue-on-error=true
According to the Spring Boot 2.5.5 user guide:
https://docs.spring.io/spring-boot/docs/2.5.5/reference/htmlsingle/#howto-initialize-a-database-using-spring-jdbc
Spring Boot enables the fail-fast feature of its script-based database initializer. If the scripts cause exceptions, the application fails to start. You can tune that behavior by setting spring.sql.init.continue-on-error.
Depending on your spring boot version the property will be named either
spring.sql.init.continue-on-error
or before Spring Boot 2.5
spring.datasource.continue-on-error
It is so dumb but I solved the problem by adding following to application.properties.
spring.jpa.database=sql_server
I have no idea why I need to specify that explicitly in properties file but the problem is solved. I will search for it

Use Spring Data random (embedded) Mongo port with NoSQL JUnit #Rule

I'm currently trying to write an Integration test class which uses Spring Data Mongo repositories.
I use an embedded Mongo instance provided by the de.flapdoodle.embed.mongo dependency. Spring Data documentation specifies that we only have to put this dependency in the project and the EmbedMongoAutoConfiguration takes care of the rest.
For now, that's ok, and setting the port to 0 makes the auto configuration process to find a free port to launch the mongo instance on.
This feature is necessary for me to avoid collision with other tests (which are run on a Jenkins CI server along with other project of my company).
The problem comes now, I want to be able to inject some test data from some external file before each of my test method run. I found out that NoSQL Unit can do this with a simple method annotation and a JUnit #Rule.
Here is an example:
#Value("${local.mongo.port}")
private int mongoPort; // <- still 0 a the time the Rule below is created.
#Rule
public MongoDbRule managedMongoDb = new MongoDbRule(MongoDbConfigurationBuilder.mongoDb().databaseName("myAwesomeDb").port(mongoPort).build());
#Test
#UsingDataSet(locations = "testdata.json", loadStrategy = LoadStrategyEnum.CLEAN_INSERT)
public void testMyData() {
// ...
}
My problem is that, the #Rule needs the Mongo port in its builder to instantiate the underlying MongoClient, but at the time the #Rule is instantiated, the Spring context is not fully initialized and the EmbeddedMongoAutoConfiguration has not published the port yet.
So my question is, is there anyone who has ever used the Embedded Mongo feature with NoSQL Unit, and is there any way to, for example create the #Rule after the Spring context is initialized ?
I was wondering if finding the free port myself (in a static way), setting it to the #Rule and then tell the EmbeddedMongoAutoConfiguration to use it by overriding the IMongodConfig bean was a good idea ? or is there a "simpler" way ?
Note: I just saw that flapdoodle library provides a class and a static method to find a free server port and its used by Spring like this:
Network.getFreeServerPort(getHost()), Network.localhostIsIPv6()))
Thanks everyone in advance!
EDIT:
I tried the solution I talked just above, and it seems to work, though I still think it's a bit "verbose" and dirty.
private static final Logger log = LoggerFactory.getLogger(MyAwesomeIT.class);
private static int mongoPort;
static {
try {
mongoPort = Network.getFreeServerPort();
} catch (IOException e) {
log.error("Error while trying to find a free port for Mongo", e);
mongoPort = -1; // test should then not work
}
}
#Rule
public MongoDbRule managedMongoDb = new MongoDbRule(MongoDbConfigurationBuilder.mongoDb().databaseName("myAwesomeDb").port(mongoPort).build());
then in the associated configuration class :
#Configuration
#EnableAutoConfiguration
#EnableMongoRepositories
#EnableConfigurationProperties(MongoProperties.class)
static class ContextConfiguration {
#Autowired
private MongoProperties mongoProperties;
#PostConstruct
public void init() {
// Here, I override the port property
mongoProperties.setPort(mongoPort);
}
}
Refining the solution given by #user6599111, it is possible to obtain the port randomly chosen by Flapdoodle Embedded Mongo, simply injecting an object of type IMongodConfig.
Spring Boot builds automagically this object for you, as stated here.
Then, the configuration class will become the following.
#Configuration
#EnableAutoConfiguration(exclude = { EmbeddedMongoAutoConfiguration.class })
public class MongoConfiguration {
#Autowired
private Environment environment;
#Autowired
private MongoProperties properties;
#Autowired(required = false)
private MongoClientOptions options;
#Autowired
private IMongodConfig mongoConfig;
#Bean
public MongoClient mongo() throws Exception {
properties.setPort(mongoConfig.net().getPort());
return properties.createMongoClient(this.options, this.environment);
}
}
I had the same problem and this was my solution
#Configuration
public class EmbeddedMongoConfig extends AbstractMongoConfiguration {
#Autowired
private Environment environment;
#Autowired
private MongoProperties properties;
#Autowired(required = false)
private MongoClientOptions options;
#Override
protected String getDatabaseName() {
return properties.getDatabase();
}
#Override
#Bean(destroyMethod = "close")
public Mongo mongo() throws Exception {
properties.setPort(Network.getFreeServerPort());
return properties.createMongoClient(this.options, this.environment);
}
}
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = { AppRunner.class, EmbeddedMongoConfig.class })
public class BaseTest {
}
public class CategoryServiceTest extends BaseTest{
#Autowired
private CategoryService categoryService;
#Test
public void someTest(){
fail("Test not implemented");
}
}
I have tried this:
int mongoPort = SocketUtils.findAvailableTcpPort();
Source:
https://docs.spring.io/spring/docs/4.0.5.RELEASE/javadoc-api/org/springframework/util/SocketUtils.html
This worked for me.
We had embedded Mongo running for unit tests and where there were multiple applications building on Jenkins, some of them failed since the port was same for everyone. Manually changing the ports was also tried but since there were many applications and some of them used a common base class, it was failing.
I am not sure about the #Rule part but you can try this.

How use analog #Autowired annotation in JavaEE (#Inject)?

In Spring project I can use #Autowired annotation.
#Service
public class DefaultUserService implements UserService {
...
#Autowired
private UserDao userDao;
But I don't understand how do it in JavaEE project. I found:
#Inject
private AvayaDao avayaDao;
But my avayaDao is NULL. I tried add annotation
#Default
public class AvayaService {
...
#Inject
private AvayaDao avayaDao;
but not helped. It id my Dao:
public interface AvayaDao extends BaseDao<AvayaSdr> {
List<AvayaSdr> getAll();
void insertCdr(AvayaSdr avayaSdr);
}
It is My Service:
#Default
public class AvayaService {
private static AvayaService instance;
#Inject
private AvayaDao avayaDao;
public synchronized static AvayaService me() {
if (instance == null) {
instance = new AvayaService();
}
return instance;
}
public Set<String> selectAllLoadedAVAYAcmCDRFiles() {
Set<String> result = new HashSet<>();
List<AvayaSdr> resultList = avayaDao.getAll();
for (AvayaSdr avayaSdr : resultList) {
result.add(avayaSdr.getHashValue());
}
return result;
}
public void insertCdr(String fileHash) {
AvayaSdr avayaCmCdr = new AvayaSdr("", fileHash, 0);
avayaDao.insertCdr(avayaCmCdr);
}
java.lang.NullPointerException
Either the AvayaDao must be an EJB and annotated with #Stateless or #Singleton or you use CDI injection and an empty beans.xml file is needed (http://docs.oracle.com/javaee/6/tutorial/doc/gjbnz.html) if you are using Java EE 6. In Java EE 7 the attribute bean-discovery-mode="all" must be set (https://docs.oracle.com/javaee/7/tutorial/cdi-adv001.htm).
UPDATE 1:
Enterprise Java Beans are POJOS annotated with #Stateless, #Statefull or #Singleton, that are managed by the EJB container inside the application server. They are able to access container specific services like the TimerService, the security context and aspects like transaction support and monitoring. Stateless enterprise java beans are also pooled by the application server.
UPDATE 2:
#Tiny Your right, but if AvayaDao is no EJB and your application contains a beans.xml file, where interceptors or other CDI specific POJOS are registered the default bean-discovery-mode is 'annotated' and the injection will not work with not annotated POJOS and explicitly setting the 'all' value is needed (http://www.adam-bien.com/roller/abien/entry/when_your_di_breaks_bean). Another option would be using no beans.xml if you explicitly know, that your application is only deployed in JAVA EE 7 environments.

IOC containers: de-duplicating the configuration code

I am using spring framework for 2 different applications. Let's say both of the applications talk to one single MongoDB database. Following is how I configure MongoDB in both the applications:
#Configuration
#PropertySource("file:/etc/x/y/mongodb.properties")
public class MongoConfiguration {
#Autowired
private Environment env;
#Bean
public UserCredentials mongoCredentials() {
String mongoUserName = env.getProperty("mongodb.username");
String mongoPassword = env.getProperty("mongodb.password");
UserCredentials credentials = new UserCredentials(mongoUserName, mongoPassword);
return credentials;
}
#Bean
public MongoClient mongoClient() throws Exception {
String mongoUrl = env.getProperty("mongodb.url");
String mongoPort = env.getProperty("mongodb.port");
MongoClient mongo = new MongoClient(mongoUrl, Integer.valueOf(mongoPort));
return mongo;
}
#Bean(name="mongoTemplate")
public MongoTemplate mongoTemplate() throws Exception {
String mongoDatabaseName = env.getProperty("mongodb.databasename");
MongoTemplate mongoTemplate = new MongoTemplate(mongoClient(), mongoDatabaseName, mongoCredentials());
return mongoTemplate;
}
Now, this piece of code is duplicated in two different application configurations. How do I avoid doing this configuration at two different places?
Treat it the same as a util class that you don't want to duplicate: move you config file to a separate project and make both your applications include that projects.
If you need to add additional project-specific configuration, Spring provides the #Import annotation that allows you to import configuration from separate classes, so you can create two project specific configuration classes that both import the generic configuration from the shared lib and supply their own individual beans and property sources, e.g.:
#Configuration
#PropertySource("classpath:/com/appspecific/app.properties")
#Import(com.genericlib.BaseConfig.class)
public class AppConfig {
#Inject BaseConfig baseConfig;
#Bean
public MyBean myBean() {
// reference the base config context
return new MyBean(baseConfig.getSomething());
}
}
Use Spring Boot, and optionally include the #PropertySource to add to the environment. It will collect all the MongoDB information and configure a client and template for you.

Multiple keyspace support for spring-data-cassandra repositories?

Does Spring Data Cassandra support multiple keyspace repositories in the same application context? I am setting up the cassandra spring data configuration using the following JavaConfig class
#Configuration
#EnableCassandraRepositories(basePackages = "com.blah.repository")
public class CassandraConfig extends AbstractCassandraConfiguration {
#Override
public String getKeyspaceName() {
return "keyspace1";
}
I tried creating a second configuration class after moving the repository classes to a different package.
#Configuration
#EnableCassandraRepositories(basePackages = "com.blah.secondrepository")
public class SecondCassandraConfig extends AbstractCassandraConfiguration {
#Override
public String getKeyspaceName() {
return "keyspace2";
}
However in that case the first set if repositories fail as the configured column family for the entities is not found in the keyspace. I think it is probably looking for the column family in the second keyspace.
Does spring-data-cassandra support multiple keyspace repositories? The only place where I found a reference for multiple keyspaces was here. But it does not explain if this can be done with repositories?
Working APP Sample:
http://valchkou.com/spring-boot-cassandra.html#multikeyspace
The Idea you need override default beans: sessionfactory and template
Sample:
1) application.yml
spring:
data:
cassandra:
test1:
keyspace-name: test1_keyspace
contact-points: localhost
test2:
keyspace-name: test2_keyspace
contact-points: localhost
2) base config class
public abstract class CassandraBaseConfig extends AbstractCassandraConfiguration{
protected String contactPoints;
protected String keyspaceName;
public String getContactPoints() {
return contactPoints;
}
public void setContactPoints(String contactPoints) {
this.contactPoints = contactPoints;
}
public void setKeyspaceName(String keyspaceName) {
this.keyspaceName = keyspaceName;
}
#Override
protected String getKeyspaceName() {
return keyspaceName;
}
}
3) Config implementation for test1
package com.sample.repo.test1;
#Configuration
#ConfigurationProperties("spring.data.cassandra.test1")
#EnableCassandraRepositories(
basePackages = "com.sample.repo.test1",
cassandraTemplateRef = "test1Template"
)
public class Test1Config extends CassandraBaseConfig {
#Override
#Primary
#Bean(name = "test1Template")
public CassandraAdminOperations cassandraTemplate() throws Exception {
return new CassandraAdminTemplate(session().getObject(), cassandraConverter());
}
#Override
#Bean(name = "test1Session")
public CassandraSessionFactoryBean session() throws Exception {
CassandraSessionFactoryBean session = new CassandraSessionFactoryBean();
session.setCluster(cluster().getObject());
session.setConverter(cassandraConverter());
session.setKeyspaceName(getKeyspaceName());
session.setSchemaAction(getSchemaAction());
session.setStartupScripts(getStartupScripts());
session.setShutdownScripts(getShutdownScripts());
return session;
}
}
4) same for test2, just use different package
package com.sample.repo.test2;
5) place repo for each keyspace in dedicated package
i.e.
package com.sample.repo.test1;
#Repository
public interface RepositoryForTest1 extends CassandraRepository<MyEntity> {
// ....
}
package com.sample.repo.test2;
#Repository
public interface RepositoryForTest2 extends CassandraRepository<MyEntity> {
// ....
}
Try explicitly naming your CassandraTemplate beans for each keyspace and using those names in the #EnableCassandraRepositories annotation's cassandraTemplateRef attribute (see lines with /* CHANGED */ for changes).
In your first configuration:
#Configuration
#EnableCassandraRepositories(basePackages = "com.blah.repository",
/* CHANGED */ cassandraTemplateRef = "template1")
public class CassandraConfig extends AbstractCassandraConfiguration {
#Override
public String getKeyspaceName() {
return "keyspace1";
}
/* CHANGED */
#Override
#Bean(name = "template1")
public CassandraAdminOperations cassandraTemplate() throws Exception {
return new CassandraAdminTemplate(session().getObject(), cassandraConverter());
}
...and in your second configuration:
#Configuration
#EnableCassandraRepositories(basePackages = "com.blah.secondrepository",
/* CHANGED */ cassandraTemplateRef = "template2")
public class SecondCassandraConfig extends AbstractCassandraConfiguration {
#Override
public String getKeyspaceName() {
return "keyspace2";
}
/* CHANGED */
#Override
#Bean(name = "template2")
public CassandraAdminOperations cassandraTemplate() throws Exception {
return new CassandraAdminTemplate(session().getObject(), cassandraConverter());
}
I think that might do the trick. Please post back if it doesn't.
It seems that it is recommended to use fully qualified keyspace names in queries managed by one session, as the session is not very lightweight.
Please see reference here
I tried this approach. However I ran into exceptions while trying to access the column family 2. Operations on column family 1 seems to be fine.
I am guessing because the underlying CassandraSessionFactoryBean bean is a singleton. And this causes
unconfigured columnfamily columnfamily2
Here are some more logs to provide context
DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Returning cached instance of singleton bean 'entityManagerFactory'
DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Returning cached instance of singleton bean 'session'
DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Returning cached instance of singleton bean 'cluster'
org.springframework.cassandra.support.exception.CassandraInvalidQueryException: unconfigured columnfamily shardgroup; nested exception is com.datastax.driver.core.exceptions.InvalidQueryException: unconfigured columnfamily columnfamily2
at org.springframework.cassandra.support.CassandraExceptionTranslator.translateExceptionIfPossible(CassandraExceptionTranslator.java:116)
at org.springframework.cassandra.config.CassandraCqlSessionFactoryBean.translateExceptionIfPossible(CassandraCqlSessionFactoryBean.java:74)
Hmm. Can't comment on the answer by matthew-adams. But that will reuse the session object as AbstractCassandraConfiguration is annotated with #Bean on all the relevant getters.
In a similar setup I initially had it working with overwriting all the getters and specifically give them different bean names. But due to Spring still claiming to need beans with the names. I have now had to make a copy of AbstractCassandraConfiguration with no annotations that I can inherit.
Make sure to expose the CassandraTemplate so you can refer to it from #EnableCassandraRepositories if you use those.
I also have a separate implementation of AbstractClusterConfiguration to expose a common CassandraCqlClusterFactoryBean so the underlying connections are being reused.
Edit:
I guess according to the email thread linked by bclarance one should really attempt to reuse the Session object. Seems the way Spring Data Cassandra isn't really set up for that though
In my case, I had a Spring Boot app where the majority of repositories were in one keyspace, and just two were in a second. I kept the default Spring Boot configuration for the first keyspace, and manually configured the second keyspace using the same configuration approach Spring Boot uses for its autoconfiguration.
#Repository
#NoRepositoryBean // This uses a different keyspace than the default, so not auto-creating
public interface SecondKeyspaceTableARepository
extends MapIdCassandraRepository<SecondKeyspaceTableA> {
}
#Repository
#NoRepositoryBean // This uses a different keyspace than the default, so not auto-creating
public interface SecondKeyspaceTableBRepository
extends MapIdCassandraRepository<SecondKeyspaceTableB> {
}
#Configuration
public class SecondKeyspaceCassandraConfig {
public static final String KEYSPACE_NAME = "second_keyspace";
/**
* #see org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration#cassandraSession(CassandraConverter)
*/
#Bean(autowireCandidate = false)
public CassandraSessionFactoryBean secondKeyspaceCassandraSession(
Cluster cluster, Environment environment, CassandraConverter converter) {
CassandraSessionFactoryBean session = new CassandraSessionFactoryBean();
session.setCluster(cluster);
session.setConverter(converter);
session.setKeyspaceName(KEYSPACE_NAME);
Binder binder = Binder.get(environment);
binder.bind("spring.data.cassandra.schema-action", SchemaAction.class)
.ifBound(session::setSchemaAction);
return session;
}
/**
* #see org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration#cassandraTemplate(com.datastax.driver.core.Session, CassandraConverter)
*/
#Bean(autowireCandidate = false)
public CassandraTemplate secondKeyspaceCassandraTemplate(
Cluster cluster, Environment environment, CassandraConverter converter) {
return new CassandraTemplate(secondKeyspaceCassandraSession(cluster, environment, converter)
.getObject(), converter);
}
#Bean
public SecondKeyspaceTableARepository cdwEventRepository(
Cluster cluster, Environment environment, CassandraConverter converter) {
return createRepository(CDWEventRepository.class,
secondKeyspaceCassandraTemplate(cluster, environment, converter));
}
#Bean
public SecondKeyspaceTableBTypeRepository dailyCapacityRepository(
Cluster cluster, Environment environment, CassandraConverter converter) {
return createRepository(DailyCapacityRepository.class,
secondKeyspaceCassandraTemplate(cluster, environment, converter));
}
private <T> T createRepository(Class<T> repositoryInterface, CassandraTemplate operations) {
return new CassandraRepositoryFactory(operations).getRepository(repositoryInterface);
}
}

Categories

Resources