I'm making an application that uses Spring Boot, MySQL and Redis on the Back End and Angular on the Front End. I want to deploy it to Heroku so I could use my front end with it but I just can't seem to configure the remote URL for Redis. I have the Redis To Go Add-on on Heroku for this with the remote URL ready. I just don't know how to configure the environment variables to access that instead of localhost and the default port 6379.
I added the following lines to my application.properties but it still did not work :
spring.redis.url= #URL
spring.redis.host= #HOSTNAME
spring.redis.password= #PASSWORD
spring.redis.port = #PORT
I keep getting the following error :
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'enableRedisKeyspaceNotificationsInitializer' defined in class path resource [org/springframework/session/data/redis/config/annotation/web/http/RedisHttpSessionConfiguration.class]: Invocation of init method failed; nested exception is org.springframework.data.redis.RedisConnectionFailureException: Unable to connect to Redis on localhost:6379; nested exception is com.lambdaworks.redis.RedisConnectionException: Unable to connect to localhost/127.0.0.1:6379
Is there any way I could configure this to access the remote url instead of localhost?
I'm using Lettuce and not Jedis and my HttpSessionConfig file is :
#EnableRedisHttpSession
public class HttpSessionConfig {
#Bean
public LettuceConnectionFactory connectionFactory() {
return new LettuceConnectionFactory();
}
}
I was having a similar issue, this is how I solved it:
If you define the bean this way:
#Bean
public LettuceConnectionFactory connectionFactory() {
return new LettuceConnectionFactory();
}
You are not allowing Spring to take the Redis values from the application.properties.
To make it work, please do the following:
Remove this bean definition:
#Bean
public LettuceConnectionFactory connectionFactory() {
return new LettuceConnectionFactory();
}
Define the RedisTemplate bean this way:
#Bean
public RedisTemplate<String, Object> deliveryRedisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
return template;
}
Define the following values in application.properties:
spring.redis.database=<replace-me>
spring.redis.host=<replace-me>
spring.redis.port=<replace-me>
spring.redis.timeout=<replace-me>
I was frustrated with the exact same issue you are facing and the spring boot documentation or examples do nothing to address what we are facing. The reason why your config entries are not being used is because you are creating a new instance of LettuceConnectionFactory with a parameter-less constructor. Digging into the source/byte code you can see that constructor is completely ignoring the spring.redis.host and spring.redis.port values and hardcoding them to localhost:6379.
What you should be doing is either:
Use the LettuceConnectionFactory(hostname, port) constructor.
Don't define the connection factory. Get rid of the entire #Bean entry for connectionFactory() and Spring will automatically wire everything up and use your config entries.
Also as a side note; to get this working with AWS Elasticache (locally via VPN) I had to add to that config class:
#Bean
public static ConfigureRedisAction configureRedisAction() {
return ConfigureRedisAction.NO_OP;
}
I found the Heroku documentation for connecting to their Redis add-on (https://devcenter.heroku.com/articles/heroku-redis#connecting-in-java) contains an example using Jedis and therefore needed a little adaption. The content of the REDIS_URL added by Heroku to the environment of your running app resembles
redis://h:credentials#host:port
I parsed this using RedisURI.create and then set the host, port and password parameters of RedisStandaloneConfiguration
val uri = RedisURI.create(configuration.redisUrl)
val config = RedisStandaloneConfiguration(uri.host, uri.port)
config.setPassword(uri.password)
return LettuceConnectionFactory(config)
The code above is Kotlin rather than Java but perhaps it will help? You can find the full code at https://github.com/DangerousDarlow/springboot-redis
You need to choose between depending on autoconfiguration or defining your custom connection template.
First way is to remove HttpSessionConfig and then your redis properties from application.properties file will be applied. And as you have spring-redis-data-session dependency on your classpath your lettuce connection will be created implicitly.
Second solution is defining your connection properties as host, port, password inside LettuceConnectionFactory.
However it is recommended to use autoconfiguration.
Set the configuration in application.properties and RedisStandaloneConfiguration.
#Configuration
#PropertySource("application.properties")
#EnableRedisHttpSession
public class SpringRedisConfig {
#Autowired
private Environment env;
#Bean
public LettuceConnectionFactory connectionFactory() {
RedisStandaloneConfiguration redisConf = new RedisStandaloneConfiguration();
redisConf.setHostName(env.getProperty("spring.redis.host"));
redisConf.setPort(Integer.parseInt(env.getProperty("spring.redis.port")));
redisConf.setPassword(RedisPassword.of(env.getProperty("spring.redis.password")));
return new LettuceConnectionFactory(redisConf);
}
}
Related
I found some similar problems here but the solutions does not fix mine, there seems to be a mistake in my message listener (subscription) configuration.
I am pretty new with redis and I would like to lister/read/subscribe to an existing redis server which does not have any password.
I can listen to that server via redis-cli on my local, but when I try to add a redis listener to my spring boot app, it seems the message listener container cannot establish a connection.
I am getting this repeating message:
2021-01-18 22:12:32 [redisContainer-31] ERROR o.s.d.r.l.RedisMessageListenerContainer -Connection failure occurred. Restarting subscription task after 5000 ms
Below are some of the snippets on how I add spring-data-redis (2.3.3.RELEASE) + jedis (2.10.0) spring boot to my application.
pom.xml
...
<properties>
...
<redis.version>2.3.3.RELEASE</redis.version>
<jedis.version>2.10.0</jedis.version>
</properties>
<dependencies>
...
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>${redis.version}</version>
</dependency>
<!-- There is no redis.clients.util.SafeEncoder class definition in version 3.1.0 -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>${jedis.version}</version>
</dependency>
</dependencies>
...
application.properties
# Redis
spring.redis.database=0
spring.redis.host=sabong-dev.abs3252.8888.use69.cache.amazonaws.com
spring.redis.port=6379
spring.redis.password=
spring.redis.timeout=60000
spring.redis.channel.sabongEvents=SABONG_EVENTS
please note that there is no password for connecting to the existing redis server, and I can read/listener to SABONG_EVENTS locally using redis-cli.
RedisConfig.java
#Configuration
public class RedisConfig {
#Value("${spring.redis.host}")
private String redisHost;
#Value("${spring.redis.port}")
private Integer redisPort;
#Value("${spring.redis.channel.sabongEvents}")
private String channelTopic;
#Bean
JedisConnectionFactory jedisConnectionFactory() {
RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration(redisHost, redisPort);
return new JedisConnectionFactory(redisStandaloneConfiguration);
}
#Bean
public RedisTemplate<String, Object> redisTemplate() {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(jedisConnectionFactory());
return template;
}
#Bean
MessageListenerAdapter messageListener() {
return new MessageListenerAdapter(new MyRedisMessageSubscriber());
}
#Bean
RedisMessageListenerContainer redisContainer() {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(jedisConnectionFactory());
container.addMessageListener(messageListener(), channelTopic());
return container;
}
#Bean
ChannelTopic channelTopic() {
return new ChannelTopic(channelTopic);
}
}
MyRedisMessageSubscriber.java
package package.my.sample;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.connection.MessageListener;
import org.springframework.stereotype.Service;
...
#Service
public class RedisMessageSubscriber implements MessageListener {
private static final Logger logger = LoggerFactory.getLogger(MyRedisMessageSubscriber.class);
public void onMessage(Message message, byte[] pattern) {
String msg = message.toString();
logger.info("\n[topic-message]: {}\n", msg);
}
}
I am not sure what is wrong with my codes, I know this is very simple since I just want to display sysout/logger.info the messages I get from redis channel.
The application runs but it cannot establish a connection inorder to make read/listen.
I may be missing some codes or configuration here but I am not sure where or what.
Thanks!
Update (Solution)
It seems spring-boot did not automagically set my redis host and port, maybe because I missed something or some wrong property names causing it not set automatically.
In my redis configuration class, I set the redis host and port in my JedisConnectionFactory using RedisStandaloneConfiguration. Since my problem is just establishing the connection to my message listener container.. it was able to make a connection to my external redis server with this sample.
Instead of removing this post, I just updated it with answer cause I know I might be needing this again since I am very forgetful :)
After debugging, I found out that my configuration properties seems not to work
spring.redis.host=sabong-dev.abs3252.8888.use69.cache.amazonaws.com
spring.redis.port=6379
I was expecting that spring boot will set these properties values for me.
As I debugged, the JedisConnectionFactory is still pointing to localhost and 6379. No problem with the port since it is the same value as what I am expecting, but localhost seems to be the default value not the one I set or expected.
So inorder to set the host/port since JedisConnectionFactory.setHost(host) and JedisConnectionFactory.setPort(port) are deprecated, I use RedisStandaloneConfiguration to set both host and port and use this in the constructor of JedisConnectionFactory. So it will look like this.
RedisConfig.java
...
#Value("${spring.redis.host}")
private String redisHost;
#Value("${spring.redis.port}")
private Integer redisPort;
#Value("${spring.redis.channel.sabongEvents}")
private String channelTopic;
...
#Bean
JedisConnectionFactory jedisConnectionFactory() {
RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration(redisHost, redisPort);
return new JedisConnectionFactory(redisStandaloneConfiguration);
}
After setting the correct host (and port), it was able to establish a connection to my redis server and channel.
Again, my issue is my message listener container is having a connection problem and the main problem was... my redis host and port was not set automatically by spring-boot. I know there might be some mistakes to my properties causing spring-boot not to set it properly. But the idea is, once I set both redis host and port, I was able to connect to my server, and I was able to read/listen to my set channel.
I will update my original post and fix the code
I had asked a question here but was incomplete and was marked as duplicate here
So based on the question already asked - one particular answer by #surasin-tancharoen seemed to be what I needed.
However trying that too gives me a NullPointerException since the data source is never created / injected.
Here are the details:
In the below code I have defined 2 beans. I have defined both datasources with #Qualifier annotation and #ConfigurationProperties to read the JNDI name from properties file.
#Configuration
public class DataSourceConfig {
#Bean
#Primary
#Qualifier("ds1")
#ConfigurationProperties(prefix="spring.datasource1")
public DataSource primaryDataSource() {
return DataSourceBuilder.create().build();
}
#Bean
#Qualifier("ds2")
#ConfigurationProperties(prefix="spring.datasource2")
public DataSource secondaryDataSource() {
return DataSourceBuilder.create().build();
}
}
In application.properties:
spring.datasource1.jndi-name=AbcDS
spring.datasource2.jndi-name=XyzDS
Then in my DAO - I am trying to use this datasource:
#Autowired
#Qualifier("ds1")
DataSource dataSource;
However the datasource is not injected since I get a NullPointerException at this line of code:
conn = dataSource.getConnection();
All of this is being attempted by deploying the Spring Boot application to Weblogic 12c
The solution is related to incorrect usage of 'DataSourceBuilder' which should not be used for a JNDI datasource.
Here is how I got it working :
( deploying spring boot war on weblogic and consuming datasources defined in weblogic )
First we specify the datasources defined in weblogic - here we specify the JNDI name of the datasources defined in weblogic:
spring.datasource.xyz.jndi-name=XYZDS
spring.datasource.test.jndi-name=TESTDS
This is where the Datasource is created using above defined properties and exposed:
We are injecting the properties from application.properties into string variables to store JNDI names of datasources.
#Configuration
public class DataSourceConfig {
#Value( "${spring.datasource.xyz.jndi-name}" )
private String xyzJndiName;
#Value( "${spring.datasource.test.jndi-name}" )
private String testJndiName;
#Bean(name = "XyzDataSource")
public DataSource getXyzDataSource() throws Exception {
JndiDataSourceLookup dataSourceLookup = new JndiDataSourceLookup();
return dataSourceLookup.getDataSource(xyzJndiName);
}
#Bean(name = "TestDataSource")
public DataSource getTestDataSource() throws Exception {
JndiDataSourceLookup dataSourceLookup = new JndiDataSourceLookup();
return dataSourceLookup.getDataSource(testJndiName);
}
A few key points to note :
We should not be using 'DataSourceBuilder' for a JNDI datasource - it will not work.
This was the reason why it was not working in my case .
Now I am using 'JndiDataSourceLookup' which does the trick.
Another thing to note is that we need to use the standard #Bean annotation with the attribute 'name'.
This will be important in the piece of code that consumes this datasource.
So now the datasources are created successfully and exposed.
OK time to consume :
#Repository
public class SomeDaoImpl {
#Autowired
#Qualifier("XyzDataSource")
DataSource dataSource;
#Override
public List <String> create(Employee request) {
Connection conn = null;
conn = dataSource.getConnection();
Here we are using the #Qualifier annotation to pickup the appropriate datasource .
Thats it - this works now.
I tried it with the other data source - it worked as well.
NOTE:
I would not like to accept my own answer - so I will wait for a couple of days if anyone else has a better and more elegant solution please answer - will be happy to accept your answer rather than answering my own question and accepting !
I was wondering if there is any way to do this.
I want to hardcode a property (I know might not be the best), let's say I want to run my application always on port XXX or any other configuration without using a .properties.
Is there any way I can do this from the main? or a configuration bean?
Thanks.
Take a look at this for how to configure a port: Spring Boot - how to configure port
Relevant code is this:
#Controller
public class ServletConfig {
#Bean
public EmbeddedServletContainerCustomizer containerCustomizer() {
return (container -> {
container.setPort(8012);
});
}
In general, most properties that can be configured via application.properties can also be configured through a Java bean. But, I would suggest using application.properties if you can. It allows you to change properties, without having to change source code.
EDIT:
Some other code from the posted link you might find useful:
HashMap<String, Object> props = new HashMap<>();
props.put("server.port", 9999);
new SpringApplicationBuilder()
.sources(SampleController.class)
.properties(props)
.run(args);
I've got a working Spring Boot Elasticsearch Application which uses one of two profiles: application.dev.properties or application.prod.properties. That part works fine. I am having issue with getting the external elasticsearch to read from the application.xxx.properties.
This works:
#Configuration
#PropertySource(value = "classpath:config/elasticsearch.properties")
public class ElasticsearchConfiguration {
#Resource
private Environment environment;
#Bean
public Client client() {
TransportClient client = new TransportClient();
TransportAddress address = new InetSocketTransportAddress(
environment.getProperty("elasticsearch.host"),
Integer.parseInt(environment.getProperty("elasticsearch.port"))
);
client.addTransportAddress(address);
return client;
}
#Bean
public ElasticsearchOperations elasticsearchTemplate() {
return new ElasticsearchTemplate(client());
}
}
but obviously doesn't solve my multi-environment issue.
I've also tried #Value annotations for host and port variables without success.
How can I convert the above to read its values from the application properties file or choose a different #PropertySource file based on whichever profile I want to run?
spring.data.elasticsearch.properties.host = 10.10.1.10
spring.data.elasticsearch.properties.port = 9300
Thanks
Remove your configuration class and properties.
Add the following dependency
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
Just add the spring.data.elasticsearch properties to an application-prod.properties and application-dev.properties and change for the desired environment. This is described in the ElasticSearch section of the Spring Boot guide.
spring.data.elasticsearch.cluster-nodes=10.10.1.10:9300
The value in either file will of course differ (or put the default in the application.properties and simply override with an application-dev.properties.
Spring Boot will based on the spring.profiles.active load the desired properties file.
There is no need to hack around yourself.
I agree with Deinum, if you are using Spring boot it will get the properties from the active profile active.
I have different profiles in my project and this is my elasticsearch configuration:
#Configuration
public class ElasticSearchConfiguration {
#Value("${spring.data.elasticsearch.cluster-name}")
private String clusterName;
#Value("${spring.data.elasticsearch.cluster-nodes}")
private String clusterNodes;
#Bean
public ElasticsearchTemplate elasticsearchTemplate() throws UnknownHostException {
String server = clusterNodes.split(":")[0];
Integer port = Integer.parseInt(clusterNodes.split(":")[1]);
Settings settings = Settings.settingsBuilder()
.put("cluster.name", clusterName).build();
client = TransportClient.builder().settings(settings).build()
.addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName(server), port));
return new ElasticsearchTemplate(client);
}
I'm working on a Spring Boot/Spring Batch project, and I need to configure two data sources. One is an in-memory hsqldb database used for tracking transactions. The other is a regular MySQL database which will be updated by my ItemWriters.
The problem is that as soon as I try to configure the second datasource, Spring starts throwing 'unresolvable circular dependency' errors, i.e.
Error creating bean with name 'preprodDataSource' defined in class path
resource [xxx/tools/batch/xxx/MyConfiguration.class]: Initialization of
bean failed; nested exception is
org.springframework.beans.factory.BeanCurrentlyInCreationException: Error
creating bean with name 'dataSourceAutoConfigurationInitializer': Requested bean is
currently in creation: Is there an unresolvable circular reference?
The relevant chunk of my MyConfiguration.java file looks like:
#Bean
public DataSource transactionsDataSource() {
BasicDataSource dataSource = new BasicDataSource();
dataSource.setDriverClassName("org.hsqldb.jdbcDriver");
dataSource.setUrl("jdbc:hsqldb:mem:testdb;sql.enforce_strict_size=true;hsqldb.tx=mvcc");
dataSource.setUsername("sa");
dataSource.setPassword("");
return dataSource;
}
#Bean
public DataSource preprodDataSource() {
BasicDataSource dataSource = new BasicDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/somedb");
dataSource.setUsername("someuser");
dataSource.setPassword("somepass");
return dataSource;
}
If I comment out the #Bean defining the second data source, everything is fine. The application starts up and runs without problems. However, if I leave it in, I get the error above.
My naive interpretation of this is that Spring is building an instance of 'dataSourceAutoConfigurationInitializer' to handle initialization of the first datasource and that when it tries to construct a second to handle the second datasource, bad things happen.
Is there any way to work around this?
By default, Spring Boot's auto-configuration will try to create a JdbcTemplate for you using your application's DataSource. As you have configured two, it doesn't know which one to use. To tell it which one to use you should mark one of them as #Primary:
#Bean
#Primary
public DataSource transactionsDataSource() {
BasicDataSource dataSource = new BasicDataSource();
…
return dataSource;
}
Alternatively, you could disable the auto-configuration.