How to load spring application context even if Cassandra down - spring-data

When using
#Configuration
#EnableCassandraRepositories(basePackages={"com.foo"})
public class CassandraConfig{
#Bean
public CassandraClusterFactoryBean cluster()
{
final CassandraClusterFactoryBean cluster = new CassandraClusterFactoryBean();
cluster.setContactPoints(nodesRead);
cluster.setPort(port);
return cluster;
}
Where in the com.foo package there is a interface that extends CrudRepository.
Is there a way to make it so that at startup time an exception is not thrown if the database is down?
Ideally what occurs is that we startup and anytime you call a method on the repository, it will first attempt to connect to the database and then if the database is still down return an error saying can't connect.
The behavior I currently observe is that NoHostAvailableException is thrown and the web container does not start up.

I was able to come up with a solution. I removed the #EnableCassandraRepositories(basePackages={"com.foo"}) annotation from the repository and defined a Bean in my Config that would return my repository. Removing the EnableCassandraRepositories allowed lazy loading of the repository. This new bean in my Config allowed me to instantiate my repository using the RepositoryFactorySupport getRepository() method. I annotated this bean as lazy and made sure references to the bean were also lazy.
Assume my repository looks like the following
public interface IBarRepository extends CrudRepository<Bar, BarKey>{}
My Config file now looks like
#Configuration
public class CassandraConfig{
#Bean
#Lazy(value=true)
public IBarRepository barRepository() throws Exception
{
final RepositoryFactorySupport support = CassandraRepositoryFactory(cassandraTemplate());
return support.getRepository(IBarRepository.class);
}
#Bean
#Lazy(value=true)
public CassandraClusterFactoryBean cluster()
{
final CassandraClusterFactoryBean cluster = new CassandraClusterFactoryBean();
cluster.setContactPoints(nodesRead);
cluster.setPort(port);
return cluster;
}
//More beans down here defining things like cluster, mappingContext, session, etc.

Related

SpringBoot 2.2.6 JUnit dataSource lookup error

I'm working on a big project written with java8 and SringBoot 2.2.6. The project uses Envers and, the girl builds the architecture say to me that she doesn't manage to put in the application.properties the Envers configuration. Than she do as follows:
#Configuration
public class JPAConfig {
#Autowired
private DataSource dataSource;
#Bean(name="entityManagerFactory")
public LocalSessionFactoryBean sessionFactory() throws IOException {
LocalSessionFactoryBean factoryBean = new LocalSessionFactoryBean();
factoryBean.setHibernateProperties(getHibernateProperties());
factoryBean.setDataSource(dataSource);
factoryBean.setPackagesToScan("it.xxxx.xxxxx.xxxxx.common.model");
return factoryBean;
}
#Bean
public PersistenceExceptionTranslationPostProcessor exceptionTranslation() {
return new PersistenceExceptionTranslationPostProcessor();
}
private Properties getHibernateProperties() {
Properties properties = new Properties();
properties.put("hibernate.dialect", PostgreSQL82Dialect.class.getName());
properties.put("hibernate.default_schema", "test");
properties.put("hibernate.listeners.envers.autoRegister", true);
properties.put("org.hibernate.envers.revision_field_name", "rev");
properties.put("org.hibernate.envers.revision_type_field_name", "rev_type");
properties.put("org.hibernate.envers.audit_table_prefix", "aud_");
properties.put("org.hibernate.envers.store_data_at_delete", true);
properties.put("org.hibernate.envers.audit_table_suffix", "");
return properties;
}
}
Problem is that without dataSource class name I can't start my #SpringBootTest classes and I don't know how to add it in a scenario like this (without change the configuration I mean).
I also tries to add this row inside the application.properties:
spring.profiles.active=#spring.profile#
spring.datasource.driver-class-name=org.postgresql.Driver
#JPA
spring.datasource.jndi-name=jdbc/test
But doesn't work at all..
If I run the App with JUnit I obtain this error:
org.springframework.jdbc.datasource.lookup.DataSourceLookupFailureException: Failed to look up JNDI DataSource with name 'jdbc/test'; nested exception is javax.naming.NoInitialContextException: Need to specify class name in environment or system property, or as an applet parameter, or in an application resource file: java.naming.factory.initial
Can you help me??
Thanks a lot
You need to register your Datasource as JNDI resource in the spring-boot embedded tomcat.
You can add it as test scope configuration.
This answer shows how to register a JNDI resource: https://stackoverflow.com/a/26005740/5230585

Upgrading from Spring Boot 1.5 to 2.0 - cannot execute UPDATE in a read-only transaction

Updating Spring Boot 1.5 to 2.1.5
When trying to do a operation repository.save(entity) it gives the following error:
Caused by: com.impossibl.postgres.jdbc.PGSQLSimpleException: cannot execute UPDATE in a read-only transaction
We use the org.springframework.data.repositoryCrudRepository interface to perform the operations.
1) #Transactional(readOnly = false), as i understood setting Read-Only mode to false only works as a hint to the sub-layers, how can i check and change the other layers?
#Service
public class ServiceImpl
private final Repository repository;
#Autowired
public ServiceImpl(Repository repository) {
this.repository = repository;
}
#Transactional(readOnly = false)
public void operation(Entity entity){
repository.save(entity);
}
And Repository is
public interface Repository extends CrudRepository<Entity, UUID>{
#Query("select e from Entity e where lower(u.name) = lower(?1)")
Entity findByName(String name);
}
build.gradle
------------
`dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:2.1.5.RELEASE")
}
`
```runtime("org.springframework.boot:spring-boot-properties-migrator")
compile("org.springframework.boot:spring-boot-starter-security")
compile("org.springframework.boot:spring-boot-starter-jersey")
compile("org.springframework.boot:spring-boot-starter-web")
compile("org.springframework.boot:spring-boot-starter-thymeleaf")
compile("org.springframework.boot:spring-boot-starter-data-jpa")
compile("org.springframework.boot:spring-boot-starter-jetty")
compile("org.springframework.boot:spring-boot-starter-mail")
compile("org.springframework.boot:spring-boot-starter-actuator")
compile("org.quartz-scheduler:quartz:2.3.1")
compile("com.fasterxml.jackson.dataformat:jackson-dataformat-xml")
compile("com.fasterxml.jackson.datatype:jackson-datatype-jsr310")
compile("com.fasterxml.woodstox:woodstox-core:5.2.1")
compile("org.glassfish.jersey.media:jersey-media-multipart:2.28")
compile("net.java.dev.msv:msv-core:2013.6.1")
compile("com.impossibl.pgjdbc-ng:pgjdbc-ng:0.8.2")
compile('org.apache.commons:commons-lang3:3.9')
compile('commons-io:commons-io:2.6')
compile('org.apache.commons:commons-compress:1.18')
compile('org.apache.poi:poi-ooxml:4.1.0')
compile('org.apache.xmlbeans:xmlbeans:3.1.0')
compile('org.mitre.dsmiley.httpproxy:smiley-http-proxy-servlet:1.10')
compile('com.monitorjbl:xlsx-streamer:2.1.0')
compile('com.zaxxer:HikariCP:3.3.1')
application.properties
spring.datasource.driverClassName=com.impossibl.postgres.jdbc.PGDriver
spring.datasource.url=
spring.datasource.username=
spring.datasource.password=
spring.datasource.type=com.zaxxer.hikari.HikariDataSource
spring.datasource.hikari.idle-timeout=10000
# Set auto-commit = false, otherwise - Caused by: java.sql.SQLException:
Clobs require connection to be in manual-commit mode...
spring.datasource.hikari.auto-commit=false
logging.level.ROOT=INFO
logging.level.org.springframework.orm.jpa=DEBUG
logging.level.org.springframework.transaction=DEBUG
One important thing is that i added the Auto-Commit to false in Hikari, otherwise it would fail with an exception as it can bee seen in the comment.
Note: In some threads it was suggested to check postgres connection
show default_transaction_read_only;
default_transaction_read_only
-------------------------------
off
SELECT pg_is_in_recovery();
pg_is_in_recovery
-------------------
f
Thanks in advance.
Property readOnly is false by default, so you should never use #Transactional(readOnly = false), use #Transactional instead.
When you mark some methods or class with #Transactional Spring creates a proxy of that class to inject the logic of Transaction Manager. It uses a bean that implements interface org.springframework.transaction.PlatformTransactionManager
In your specific case a bean of org.springframework.orm.jpa.JpaTransactionManager will be created.
Spring Boot is using Hibernate as a default JPA provider, so eventually all transaction logic will affect Hibernate. E.g. readOnly = true is used in order to disable "dirty checking" mechanism that performs all UPDATE operations in Hibernate.
By default, Spring Transaction Manager creates a new Hibernate Session (new transition) when it calls a method marker with #Transactional and no Session is attached to the current thread. So all the following calls in the current thread will use the same Session (and same transaction). Unless you change the propagation property.
It all means that configs for the transaction are set when Spring calls #Transactional method the first time and those configs are used for all methods calls in the same thread. See the code example:
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionSynchronizationManager;
#Service
public class ServiceA {
#Transactional(readOnly = true)
public void a() {
boolean isReadOnly = TransactionSynchronizationManager.isCurrentTransactionReadOnly();
System.out.println(isReadOnly);
}
}
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
#Service
public class ServiceB {
private final ServiceA serviceA;
public ServiceB(ServiceA serviceA) {
this.serviceA = serviceA;
}
#Transactional
public void b() {
serviceA.a();
}
}
serviceA.a() will print true
serviceB.b() will print false
See Spring Boot 2.0 Migration Guide about Gradle and add dependency management plugin:
Spring Boot’s Gradle plugin no longer automatically applies the dependency management plugin. Instead, Spring Boot’s plugin now reacts to the dependency management plugin being applied by importing the correct version of the spring-boot-dependencies BOM. This gives you more control over how and when dependency management is configured.
For most applications applying the dependency management plugin will be sufficient:
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management' // <-- add this to your build.gradle
Also you can remove spring.datasource.type
If you used spring.datasource.type to force the use of Hikari in a Tomcat-based application, you can now remove that override.
Notice also that minimum Hibernate version is 5.2
Also I see you added spring-boot-properties-migrator, note that it should be removed after you finish migration tweaks
Once you’re done with the migration, please make sure to remove this module from your project’s dependencies.

Problems while connecting to two MongoDBs via Spring

I'm trying to achieve to connect to two different MongoDBs with Spring (1.5.2. --> we included Spring in an internal Framework therefore it is not the latest version yet) and this already works partially but not fully. More precisely I found a strange behavior which I will describe below after showing my setup.
So this is what I done so far:
Project structure
backend
config
domain
customer
internal
repository
customer
internal
service
In configI have my Mongoconfigurations.
I created one base class which extends AbstractMongoConfiguration. This class holds fields for database, host etc. which are filled with the properties from a application.yml. It also holds a couple of methods for creating MongoClient and SimpleMongoDbFactory.
Furthermore there are two custom configuration classes. For each MongoDB one config. Both extend the base class.
Here is how they are coded:
Primary Connection
#Primary
#EntityScan(basePackages = "backend.domain.customer")
#Configuration
#EnableMongoRepositories(
basePackages = {"backend.repository.customer"},
mongoTemplateRef = "customerDataMongoTemplate")
#ConfigurationProperties(prefix = "customer.mongodb")
public class CustomerDataMongoConnection extends BaseMongoConfig{
public static final String TEMPLATE_NAME = "customerDataMongoTemplate";
#Override
#Bean(name = CustomerDataMongoConnection.TEMPLATE_NAME)
public MongoTemplate mongoTemplate() {
MongoClient client = getMongoClient(getAddress(),
getCredentials());
SimpleMongoDbFactory factory = getSimpleMongoDbFactory(client,
getDatabaseName());
return new MongoTemplate(factory);
}
}
The second configuration class looks pretty similar. Here it is:
#EntityScan(basePackages = "backend.domain.internal")
#Configuration
#EnableMongoRepositories(
basePackages = {"backend.repository.internal"}
mongoTemplateRef = InternalDataMongoConnection.TEMPLATE_NAME
)
#ConfigurationProperties(prefix = "internal.mongodb")
public class InternalDataMongoConnection extends BaseMongoConfig{
public static final String TEMPLATE_NAME = "internalDataMongoTemplate";
#Override
#Bean(name = InternalDataMongoConnection.TEMPLATE_NAME)
public MongoTemplate mongoTemplate() {
MongoClient client = getMongoClient(getAddress(), getCredentials());
SimpleMongoDbFactory factory = getSimpleMongoDbFactory(client,
getDatabaseName());
return new MongoTemplate(factory);
}
}
As you can see, I use EnableMongoRepositoriesto define which repository should use which connection.
My repositories are defined just like it is described in the Spring documentation.
However, here is one example which is located in package backend.repository.customer:
public interface ContactHistoryRepository extends MongoRepository<ContactHistoryEntity, String> {
public ContactHistoryEntity findById(String id);
}
The problem is that my backend always only uses the primary connection with this setup. Interestingly, when I remove the beanname for the MongoTemplate (just #Bean) the backend then uses the secondary connection (InternalMongoDataConnection). This is true for all defined repositories.
My question is, how can I achieve that my backend really take care of both connections? Probably I missed to set another parameter/configuration?
Since this is a pretty extensive post I apologise if I forgot something to mention. Please ask for missing information in the comments.
I found the answer.
In my package structure there was a empty configuration class (of my colleague) with the annotation #Configurationand #EnableMongoRepositories. This triggered the automatic wiring process of Stpring Data and therefore led to the problems I reported above.
I simply deleted the class and now it works as it should!

Overridden RabbitSourceConfiguration (app starters) does not work with Spring Cloud Edgware

I'm testing an upgrade of my Spring Cloud DataFlow services from Spring Cloud Dalston.SR4/Spring Boot 1.5.9 to Spring Cloud Edgware/Spring Boot 1.5.9. Some of my services extend source (or sink) components from the app starters. I've found this does not work with Spring Cloud Edgware.
For example, I have overridden org.springframework.cloud.stream.app.rabbit.source.RabbitSourceConfiguration and bound my app to my overridden version. This has previously worked with Spring Cloud versions going back almost a year.
With Edgware, I get the following (whether the app is run standalone or within dataflow):
***************************
APPLICATION FAILED TO START
***************************
Description:
Field channels in org.springframework.cloud.stream.app.rabbit.source.RabbitSourceConfiguration required a bean of type 'org.springframework.cloud.stream.messaging.Source' that could not be found.
Action:
Consider defining a bean of type 'org.springframework.cloud.stream.messaging.Source' in your configuration.
I get the same behaviour with the 1.3.0.RELEASE and 1.2.0.RELEASE of spring-cloud-starter-stream-rabbit.
I override RabbitSourceConfiguration so I can set a header mapper on the AmqpInboundChannelAdapter, and also to perform a connectivity test prior to starting up the container.
My subclass is bound to the Spring Boot application with #EnableBinding(HeaderMapperRabbitSourceConfiguration.class). A cutdown version of my subclass is:
public class HeaderMapperRabbitSourceConfiguration extends RabbitSourceConfiguration {
public HeaderMapperRabbitSourceConfiguration(final MyHealthCheck healthCheck,
final MyAppConfig config) {
// ...
}
#Bean
#Override
public AmqpInboundChannelAdapter adapter() {
final AmqpInboundChannelAdapter adapter = super.adapter();
adapter.setHeaderMapper(new NotificationHeaderMapper(config));
return adapter;
}
#Bean
#Override
public SimpleMessageListenerContainer container() {
if (config.performConnectivityCheckOnStartup()) {
if (LOGGER.isInfoEnabled()) {
LOGGER.info("Attempting connectivity with ...");
}
final Health health = healthCheck.health();
if (health.getStatus() == Status.DOWN) {
LOGGER.error("Unable to connect .....");
throw new UnableToLoginException("Unable to connect ...");
} else if (LOGGER.isInfoEnabled()) {
LOGGER.info("Connectivity established with ...");
}
}
return super.container();
}
}
You really should never do stuff like healthCheck.health(); within a #Bean definition. The application context is not yet fully baked or started; it may, or may not, work depending on the order that beans are created.
If you want to prevent the app from starting, add a bean that implements SmartLifecycle, put the bean in a late phase (high value) so it's started after everything else. Then put your code in start(). autStartup must be true.
In this case, it's being run before the stream infrastructure has created the channel.
Some ordering might have changed from the earlier release but, in any case, performing activity like this in a #Bean definition is dangerous.
You just happened to be lucky before.
EDIT
I just noticed your #EnableBinding is wrong; it should be Source.class. I can't see how that would ever have worked - that's what creates the bean for the channels field of type Source.
This works fine for me after updating stream and the binder to 1.3.0.RELEASE...
#Configuration
public class MySource extends RabbitSourceConfiguration {
#Bean
#Override
public AmqpInboundChannelAdapter adapter() {
AmqpInboundChannelAdapter adapter = super.adapter();
adapter.setHeaderMapper(new MyMapper());
return adapter;
}
}
and
#SpringBootApplication
#EnableBinding(Source.class)
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
If that doesn't work, please edit the question to show your POM.

Spring data error

I have the following class
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = {"classpath:my-ctx.xml"})
public class UserTests {
#Inject
private ApplicationContext applicationContext;
private UserRepository getUserRepository() {
return (UserRepository)applicationContext.getBean("userRepository", CrudRepository.class);
}
#Test
public void someTest() {
User user = new User();
user.setName("John Doe");
getUserRepository().save(user);
}
}
Running the test, I get the following error
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'userRepository': FactoryBean threw exception on object creation; nested exception is java.lang.NullPointerException
...
root cause is
org.datanucleus.api.jpa.metamodel.SingularAttributeImpl.isVersion(SingularAttributeImpl.java:79)
org.springframework.data.jpa.repository.support.JpaMetamodelEntityInformation.findVersionAttribute(JpaMetamodelEntityInformation.java:92)
org.springframework.data.jpa.repository.support.JpaMetamodelEntityInformation.<init>(JpaMetamodelEntityInformation.java:78)
org.springframework.data.jpa.repository.support.JpaEntityInformationSupport.getMetadata(JpaEntityInformationSupport.java:65)
org.springframework.data.jpa.repository.support.JpaRepositoryFactory.getEntityInformation(JpaRepositoryFactory.java:146)
org.springframework.data.jpa.repository.support.JpaRepositoryFactory.getTargetRepository(JpaRepositoryFactory.java:84)
org.springframework.data.jpa.repository.support.JpaRepositoryFactory.getTargetRepository(JpaRepositoryFactory.java:67)
...
where VersionMetaData vermd = mmd.getAbstractClassMetaData().getVersionMetaData(); is null.
Is this a bug?
I know that I can put something like #Inject UserRepository userRepository;, but taking into account how Spring Data works, these two should have the same result, right? And anyway the result will be the same error.
I'm using Spring data 1.4.1, DataNucleus 3.3.2, Spring 3.2.4.
Actually this is a DataNucleus bug and I filled in a bug report (with test and fix patch included): http://www.datanucleus.org/servlet/jira/browse/NUCJPA-250.
My workaround was to switch back to Spring Data 1.3.0.