Note that this code does work with plain Spring but not with Spring Boot(v1.3.3), is there something i'm missing because this is imported from a spring app that works. The code below is from the spring boot app
#Entity
#Table(name="project")
public class Project implements Serializable{
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name="id")
private int id;
#Column(name="teamId")
private int teamId;
//private String Rentabiliteit;
#Column
//#Index(name="IProject_status",columnNames="Status")
private String status;
#Column
//#Index(name="IProject_naam",columnNames="Naam")
private String naam;
//public Prototype m_Prototype;
//public Team m_Team;
}
SQL
CREATE TABLE IF NOT EXISTS `project` (
`id` int(11) NOT NULL,
`teamId` int(11) DEFAULT NULL,
`status` varchar(255) DEFAULT NULL,
`naam` varchar(255) DEFAULT NULL
) ENGINE=InnoDB AUTO_INCREMENT=43 DEFAULT CHARSET=latin1;
ERROR
Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException:
Unknown column 'project0_.team_id' in 'field list'
Edited: Application.yml
spring:
mvc:
view:
prefix: /WEB-INF/jsp/
suffix: .jsp
datasource:
url: jdbc:mysql://localhost:3306/oxyplast
username: oxyplastuser
password: oxyplastuserpw
jpa:
properties:
hibernate:
current_session_context_class: org.springframework.orm.hibernate4.SpringSessionContext
namingStrategy: org.hibernate.cfg.DefaultNamingStrategy
SINCE SPRING-BOOT 1.4
Starting from 1.4, because of the switch to Hibernate 5, the naming strategy has been updated to SpringPhysicalNamingStrategy which should be very close to 1.3 defaults.
See also:
Spring's naming strategy
PREVIOUS VERSION
Spring Boot provides the ImprovedNamingStrategy as default naming strategy, which makes Hibernate search for a team_id column (inferred from the int teamId field). As this column doesn't exist in your table, that's the cause of the error. From the Hibernate docs:
An improved naming strategy that prefers embedded underscores to mixed case names
You've got two options:
Provide the column name explicitly as #Column(name="teamId"). There used to be a bug with this in early Boot versions, not anymore.
Change the naming strategy in the Spring Boot properties and tell it to use the EJB3NamingStrategy, which doesn't convert camelCase to snake_case, but keeps it as it is.
If you are using Spring Boot 2.0.2 and Hibernate 5.3.4 then setting the following property will fix the issue.
spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
Below strategy worked for me
spring.jpa.hibernate.naming-strategy = org.hibernate.cfg.DefaultComponentSafeNamingStrategy
with the late version :
spring-boot-starter-data-jpa: ➡ 1.5.2.RELEASE
hibernate-core:5.0.12.Final
this class
PhysicalNamingStrategyStandardImpl
needs to be extended and added to hibernate properties.
Here is a full working version
public class PhysicalNamingStrategyImpl extends PhysicalNamingStrategyStandardImpl implements Serializable {
public static final PhysicalNamingStrategyImpl INSTANCE = new PhysicalNamingStrategyImpl();
#Override
public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment context) {
String nameModified;
// Do whatever you want with the name modification
return new Identifier(nameModified, name.isQuoted());
}
}
#Override
public Identifier toPhysicalColumnName(Identifier name, JdbcEnvironment context) {
String nameModified;
// Do whatever you want with the name modification
return new Identifier(nameModified, name.isQuoted());
}
Linking it to hibernate should be done like this when configuring the datasource.
properties.put("hibernate.physical_naming_strategy", "my.Package.PhysicalNamingStrategyImpl");
here is a full working version of datasource config
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(
entityManagerFactoryRef = "entityManagerFactory",
basePackages = { "com.xxxxxx.repository" }
)
public class SharedDataSourceConfig {
#Value("${startup.ddl-auto}")
String hbm2ddl;
#Primary
#Bean(name = "dataSource")
#ConfigurationProperties("spring.datasource.shared")
public DataSource customerDataSource() {
return DataSourceBuilder.create().build();
}
#Primary
#Bean(name = "entityManagerFactory")
public LocalContainerEntityManagerFactoryBean entityManagerFactory(
EntityManagerFactoryBuilder builder,
#Qualifier("dataSource") DataSource dataSource) {
Map<String, Object> properties = new HashMap<String, Object>();
properties.put("hibernate.hbm2ddl.auto", hbm2ddl);
properties.put("hibernate.physical_naming_strategy", "my.package.PhysicalNamingStrategyImpl");
return builder
.dataSource(dataSource)
.packages(PackageScannerHelper.getPackagesToScan())
.persistenceUnit("shared")
.properties(properties)
.build();
}
#Primary
#Bean(name = "transactionManager")
public PlatformTransactionManager transactionManager(
#Qualifier("entityManagerFactory") EntityManagerFactory
entityManagerFactory
) {
return new JpaTransactionManager(entityManagerFactory);
}
}
This worked for me with spring boot 1.4.0 and hibernate entitymanager 4.3.8.Final
application.properties
spring.jpa.hibernate.naming.implicit-strategy=org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyJpaImpl
spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
application.properties
spring.jpa.hibernate.naming-strategy = org.hibernate.cfg.DefaultComponentSafeNamingStrategy
the above properties work for me.
hibernate 4.3.11.Final
spring boot 1.4.2.RELEASE
Related
I want to use the spring boot autoconfigure for JMS to connect to a remote JNDI and retrieve the ConnectionFactory based on his name populated through the spring.jms.jndi-name property in the application.properties file.
I noticed that the spring boot autoconfigure is relying on the JndiConnectionFactoryAutoConfiguration class to do that and this class in turn will call the JndiTemplate class to do the lookup. The problem is that the value of the environment attribute of the JndiTemplate class is null, so we cannot create the intialContext.
In fact, I noticed that the JndiTemplate class is instantiated with no-argument constructor in starting application and before loading the configuration defined in the JndiConnectionFactoryAutoConfiguration class.
My question: how can I instantiate JndiTemplate by specifying a list of properties (Context.INITIAL_CONTEXT_FACTORY, Context.PROVIDER_URL..)? knowing that JmsTemplate has a constructor that takes an Properties object.
Just for information: my application is a simple jar that doesn’t run on a server at the moment.
For those interested in the answer, you must use VM options to pass required JNDI properties.
Here is an example that works with ActiveMQ:
VM options:
-Djava.naming.provider.url=tcp://hostname:61616
-Djava.naming.factory.initial=org.apache.activemq.jndi.ActiveMQInitialContextFactory
And spring properties file (application.properties) must contain the JNDI name of the connection factory:
spring.jms.jndi-name=ConnectionFactory
Much better, you can use configuration to fin your connection factory from JNDI. In my project, we finished by creating our jms starter that we can use in all microservices.
Properties class:
import lombok.*;
import org.springframework.boot.context.properties.ConfigurationProperties;
#Getter
#Setter
#ToString
#NoArgsConstructor
#EqualsAndHashCode
#ConfigurationProperties( prefix = "custom.jms" )
public class CustomJmsProperties {
private String jndiName;
private String contextFactoryClass;
private String providerUrl;
private String username;
private String password;
}
Configuration class:
import org.apache.commons.lang3.StringUtils;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.jms.JndiConnectionFactoryAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jms.connection.UserCredentialsConnectionFactoryAdapter;
import org.springframework.jndi.JndiLocatorDelegate;
import javax.jms.ConnectionFactory;
import javax.naming.Context;
import javax.naming.NamingException;
import java.util.Properties;
#Configuration
#ConditionalOnProperty( "custom.jms.jndi-name" )
#ConditionalOnMissingBean( ConnectionFactory.class )
#EnableConfigurationProperties( { CustomJmsProperties.class } )
#AutoConfigureAfter( { JndiConnectionFactoryAutoConfiguration.class } )
public class CustomJndiConnectionFactoryAutoConfiguration {
#Bean
public ConnectionFactory connectionFactory( CustomJmsProperties customJmsProperties ) throws NamingException {
ConnectionFactory connectionFactory = lookupForConnectionFactory( customJmsProperties );
return getEnhancedUserCredentialsConnectionFactory( customJmsProperties, connectionFactory );
}
private ConnectionFactory lookupForConnectionFactory( final CustomJmsProperties customJmsProperties ) throws NamingException {
JndiLocatorDelegate jndiLocatorDelegate = new JndiLocatorDelegate();
Properties jndiProperties = getJndiProperties( customJmsProperties );
jndiLocatorDelegate.setJndiEnvironment( jndiProperties );
return jndiLocatorDelegate.lookup( customJmsProperties.getJndiName(), ConnectionFactory.class );
}
private Properties getJndiProperties( final CustomJmsProperties customJmsProperties ) {
Properties jndiProperties = new Properties();
jndiProperties.setProperty( Context.PROVIDER_URL, customJmsProperties.getProviderUrl() );
jndiProperties.setProperty( Context.INITIAL_CONTEXT_FACTORY, customJmsProperties.getContextFactoryClass() );
if ( StringUtils.isNotEmpty( customJmsProperties.getUsername() ) ) {
jndiProperties.setProperty( Context.SECURITY_PRINCIPAL, customJmsProperties.getUsername() );
}
if ( StringUtils.isNotEmpty( customJmsProperties.getPassword() ) ) {
jndiProperties.setProperty( Context.SECURITY_CREDENTIALS, customJmsProperties.getPassword() );
}
return jndiProperties;
}
private UserCredentialsConnectionFactoryAdapter getEnhancedUserCredentialsConnectionFactory( final CustomJmsProperties customJmsProperties,
final ConnectionFactory connectionFactory ) {
UserCredentialsConnectionFactoryAdapter enhancedConnectionFactory = new UserCredentialsConnectionFactoryAdapter();
enhancedConnectionFactory.setTargetConnectionFactory( connectionFactory );
enhancedConnectionFactory.setUsername( customJmsProperties.getUsername() );
enhancedConnectionFactory.setPassword( customJmsProperties.getPassword() );
enhancedConnectionFactory.afterPropertiesSet();
return enhancedConnectionFactory;
}
}
Properties file of your project:
custom.jms.provider-url=tcp://hostname:61616
custom.jms.context-factory-class=org.apache.activemq.jndi.ActiveMQInitialContextFactory
custom.jms.jndi-name=ConnectionFactory
Expected Behavior
Trying to use two Neo4j instances with Spring boot and Spring data Neo4j
Current Behavior
Able to use only one Neo4j instances. Unable to use two repositories.
Steps to Reproduce (for bugs)
1. Run two Neo4j Instances
2. Create Data source configuration for both Neo4j Instances using spring boot.
3. Use Repository to access the Node entity
4. It will throw error
Context
Consider that I am running a library and renting books to other users. If the user is renting the book from me, the same node details will be present in their repository and I will allow them to edit the node entity through my application (like adding keywords, adding highlights about the books etc.)
So in both repository the node details will be same.
Below are the application. properties details for both Neo4j repositories.
My Neo4j Repository Details
spring.data.neo4j.uri=bolt://localhost:7687
spring.data.neo4j.username=neo4j
spring.data.neo4j.password=neo4j
Rental User's Neo4j Repository Details
(Accessing through http which is running in some other machine)
rental.data.neo4j.uri=bolt://...:7687
rental.data.neo4j.username=neo4j
rental.data.neo4j.password=neo4j
Below is the Rental User's Neo4j configuration :
#configuration
#EnableNeo4jRepositories(basePackages = "com.metadata.dao.rentallibrary", sessionFactoryRef = "rentalSessionFactory", transactionManagerRef = "rentalUsertransactionManager")
#EnableTransactionManagement
#EntityScan("com.metadata.dao")
public class rentalUserNeo4jConfiguration {
#Value("${rental.data.neo4j.uri}")
private String url;
#Value("${rental.data.neo4j.username}")
private String userName;
#Value("${rental.data.neo4j.password}")
private String password;
#Bean(name = "rentalSessionFactory")
public SessionFactory rentalUserSessionFactory() {
return new SessionFactory(rentalNeo4jconfiguration(), "com.metadata.dao.rentallibrary.entity");
}
#Bean
public org.neo4j.ogm.config.Configuration rentalNeo4jconfiguration() {
org.neo4j.ogm.config.Configuration configuration = new org.neo4j.ogm.config.Configuration.Builder().uri(url)// "
.credentials(userName, password)
.build();
return configuration;
}
#Bean
public Neo4jTransactionManager rentalUsertransactionManager() {
return new Neo4jTransactionManager(rentalUserSessionFactory());
}
}
And below is my library's Neo4j configuration details :
#configuration
#EnableNeo4jRepositories(basePackages = "com.metadata.dao.mylibrary", sessionFactoryRef = "myUserSessionFactory", transactionManagerRef = "myUserTransactionManager")
#EnableTransactionManagement
public class MyUserNeo4jConfiguration {
#Value("${spring.data.neo4j.uri}")
private String url;
#Value("${spring.data.neo4j.username}")
private String userName;
#Value("${spring.data.neo4j.password}")
private String password;
#Bean(name = "myUserSessionFactory")
#Primary
public SessionFactory myUserSessionFactory() {
return new SessionFactory(myUserconfiguration(), "com.metadata.dao.mylibrary.entity");
}
#Bean
public org.neo4j.ogm.config.Configuration myUserconfiguration() {
org.neo4j.ogm.config.Configuration configuration = new org.neo4j.ogm.config.Configuration.Builder().uri(url)
.credentials(userName, password)
.build();
return configuration;
}
#Bean
public Neo4jTransactionManager myUserTransactionManager() {
return new Neo4jTransactionManager(myUserSessionFactory());
}
}
I am trying access both repositories using session Factory (via qualifier) it is working fine. But I am trying to access the data, through repositories I am facing the Issue.
**Accessing through SessionFactory :**
#Autowired
#Qualifier(value = "myUserSessionFactory")
SessionFactory myUserSessionFactory;
#Autowired
#Qualifier(value = "rentalUserSessionFactory")
SessionFactory rentalUserSessionFactory;
Below are the error details, I am getting when trying to access the data through :
java.lang.IllegalArgumentException: Class class com.metadata.dao.Book is not a valid entity class. Please check the entity mapping.
at org.neo4j.ogm.session.delegates.SaveDelegate.save(SaveDelegate.java:88) ~[neo4j-ogm-core-3.1.0.jar:3.1.0]
at org.neo4j.ogm.session.delegates.SaveDelegate.save(SaveDelegate.java:40) ~[neo4j-ogm-core-3.1.0.jar:3.1.0]
at org.neo4j.ogm.session.Neo4jSession.save(Neo4jSession.java:469) ~[neo4j-ogm-core-3.1.0.jar:3.1.0]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_141]
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_141]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_141]
at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_141]
at org.springframework.data.neo4j.transaction.SharedSessionCreator$SharedSessionInvocationHandler.invoke(SharedSessionCreator.java:131) ~[spring-data-neo4j-5.0.8.RELEASE.jar:5.0.8.RELEASE]
at com.sun.proxy.$Proxy81.save(Unknown Source) ~[na:na]
org.neo4j.ogm.exception.core.TransactionManagerException: Transaction is not current for this thread
at org.neo4j.ogm.session.transaction.DefaultTransactionManager.rollback(DefaultTransactionManager.java:86) ~[neo4j-ogm-core-3.1.0.jar:3.1.0]
at org.neo4j.ogm.transaction.AbstractTransaction.rollback(AbstractTransaction.java:65) ~[neo4j-ogm-api-3.1.0.jar:3.1.0]
at org.neo4j.ogm.drivers.bolt.transaction.BoltTransaction.rollback(BoltTransaction.java:61) ~[neo4j-ogm-bolt-driver-3.1.0.jar:3.1.0]
at org.neo4j.ogm.transaction.AbstractTransaction.close(AbstractTransaction.java:144) ~[neo4j-ogm-api-3.1.0.jar:3.1.0]
at org.springframework.data.neo4j.transaction.Neo4jTransactionManager.doCleanupAfterCompletion(Neo4jTransactionManager.java:379) ~[spring-data-neo4j-5.0.8.RELEASE.jar:5.0.8.RELEASE]
Node Entity Name in dao.mylibrary.entity : Book
Node Entity Name in dao.rentallibrary.entity : RentedBook
Please let me know why this issue is occurring while using Neo4j repositories? Can't we use two Neo4j repositories with Spring data neo4j & Spring boot? Or Am I doing something wrong?
My Environment
OGM Version used: 3.1.0
Java Version used: 1.8
Neo4J Version used:3.2.3
Bolt Driver Version used (if applicable): 3.1.0
Operating System and Version: Windows
Please let me know if you need any additional information.
Update This has been addressed in Spring Data Neo4j Lovelace RC1 and we wrote a small howto: https://michael-simons.github.io/neo4j-sdn-ogm-tips/using_multiple_session_factories
Thanks for submitting this as GitHub issue #498.
It seems that the current Spring Data Neo4j version has a bug in propagating the different session factories to the repositories. In short: there is no way to make this work right now (for example like you can do with Spring Data JPA).
If you need (and want) repositories, I cannot help you right now. What work's however is injecting the different session factories:
#Autowired
#Qualifier("myUserSessionFactory")
private SessionFactory myUserSessionFactory;
#Autowired
#Qualifier("rentalUserSessionFactory")
private SessionFactory rentalUserSessionFactory;
and then do something like
Map<String, Object> params = new HashMap<>();
params.put("name", "test");
ThingEntity t = this.myUserSessionFactory.openSession().queryForObject(
ThingEntity.class,
"MATCH (n:`Thing`) WHERE n.name = $name WITH n RETURN n", params);
Regardless of the bug in our code, I recommend the following configuration. For the primary beans ("myUserconfiguration") use one config class
package gh.neo4jogm.gh498;
import gh.neo4jogm.gh498.domain1.ThingEntity;
import org.neo4j.ogm.session.SessionFactory;
import org.springframework.boot.autoconfigure.data.neo4j.Neo4jProperties;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.neo4j.repository.config.EnableNeo4jRepositories;
import org.springframework.data.neo4j.transaction.Neo4jTransactionManager;
#Configuration
#EnableNeo4jRepositories(
basePackages = Domain1Config.BASE_PACKAGE,
sessionFactoryRef = "myUserSessionFactory",
transactionManagerRef = "myUserTransactionManager"
)
#EntityScan(basePackageClasses = ThingEntity.class)
class Domain1Config {
static final String BASE_PACKAGE = "gh.neo4jogm.gh498.domain1";
#Primary
#Bean
#ConfigurationProperties("spring.data.neo4j")
public Neo4jProperties myNeo4jProperties() {
return new Neo4jProperties();
}
#Primary
#Bean
public org.neo4j.ogm.config.Configuration myUserconfiguration() {
return myNeo4jProperties().createConfiguration();
}
#Primary
#Bean
public SessionFactory myUserSessionFactory() {
return new SessionFactory(myUserconfiguration(), BASE_PACKAGE);
}
#Bean
public Neo4jTransactionManager myUserTransactionManager() {
return new Neo4jTransactionManager(myUserSessionFactory());
}
}
The basic idea is to use #ConfigurationProperties to map the default properties to an instance of Neo4jProperties (our properties class) and use it like we do create the action configuration.
The same then for the other session factory:
package gh.neo4jogm.gh498;
import gh.neo4jogm.gh498.domain2.OtherThingEntity;
import org.neo4j.ogm.session.SessionFactory;
import org.springframework.boot.autoconfigure.data.neo4j.Neo4jProperties;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.neo4j.repository.config.EnableNeo4jRepositories;
import org.springframework.data.neo4j.transaction.Neo4jTransactionManager;
import static gh.neo4jogm.gh498.Domain2Config.BASE_PACKAGE;
#Configuration
#EnableNeo4jRepositories(
basePackages = BASE_PACKAGE,
sessionFactoryRef = "rentalUserSessionFactory",
transactionManagerRef = "rentalUsertransactionManager"
)
#EntityScan(basePackageClasses = OtherThingEntity.class)
class Domain2Config {
static final String BASE_PACKAGE = "gh.neo4jogm.gh498.domain2";
#Bean
#ConfigurationProperties("rental.data.neo4j")
public Neo4jProperties rentalNeo4jProperties() {
return new Neo4jProperties();
}
#Bean
public org.neo4j.ogm.config.Configuration rentalNeo4jconfiguration() {
return rentalNeo4jProperties().createConfiguration();
}
#Bean
public SessionFactory rentalUserSessionFactory() {
return new SessionFactory(rentalNeo4jconfiguration(), BASE_PACKAGE);
}
#Bean
public Neo4jTransactionManager rentalUsertransactionManager() {
return new Neo4jTransactionManager(rentalUserSessionFactory());
}
}
Here you map all properties prefixed with rental.data.neo4j to another properties instance.
I have following configuration on my spring boot project.
#SpringBootApplication
#EnableTransactionManagement
#EnableCaching
#EnableScheduling
#EnableAsync
public class Application {
String redisHost = "localhost";
int redisPort = 6379;
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#Bean
JedisConnectionFactory jedisConnectionFactory() {
JedisConnectionFactory factory = new JedisConnectionFactory();
factory.setHostName(redisHost);
factory.setPort(redisPort);
factory.setUsePool(true);
return factory;
}
#Bean
RedisTemplate<Object, Object> redisTemplate() {
RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<Object, Object>();
redisTemplate.setConnectionFactory(jedisConnectionFactory());
return redisTemplate;
}
#Bean
public CacheManager cacheManager() {
RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate());
return cacheManager;
}
}
Also I have following maven dependency on pom.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
I have a separate redis server running on my local machine on defined port. Also in my service classes I have annotations like #Cacheable, #CachePut to support caching.
I can start spring boot application without errors and CRUD operations also works. But seems it is not using the defined redis cache. I used 'redi desktop manger' browsing tool and couldn't find any data on redis. Also I tried with monitoring redis server via redis cli command 'monitor', I can't see any changes on monitor.
So I assume redis caching still not working on my spring boot application. Can someone help me to figure out the issue?
I am using spring boot version 1.4.2.RELEASE
Thanks!
Given you are using Spring Boot, much of your Redis configuration is unnecessary since Spring Boot provides "auto-configuration" support for Redis, both as a data source as well as a caching provider.
You were also not specific about what version of Spring Boot you were using (e.g. 1.5.0.RC1) to run your application, or whether you had any application.properties on your application's classpath, which might make a difference if you explicitly specified spring.cache.type (set to something other than "redis", for instance).
However, in general, I cannot really see much wrong with your Redis or Spring Cache #Configuration class. However, it does seem to be a problem with not explicitly setting cacheManager.setUsePrefix(true). When I set this RedisCacheManager property ('usePrefix`), then everything worked as expected.
I am not (Spring Data) Redis expert so I am not exactly sure why this is needed. However, I based my test configuration on Spring Boot's "auto-configuration" support for Redis caching as well as your #Configuration "Application" class, shown above.
And, because you can eliminate most of your explicit configuration and use Spring Boot's "auto-configuration" support for Redis as a data source as well, I added a "AutoRedisConfiguration" #Configuration class to my test class. I.e. you can use this to configure Redis instead of my other #Configuration class ("CustomRedisConfiguration") which uses your configuration + the fix.
Here is the complete test example...
/*
* Copyright 2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.spring.cache;
import static org.assertj.core.api.Assertions.assertThat;
import java.util.Arrays;
import java.util.Properties;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.annotation.PostConstruct;
import org.junit.Before;
import org.junit.FixMethodOrder;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.MethodSorters;
import org.spring.cache.CachingWithRedisIntegrationTest.CachingWithRedisConfiguration;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.concurrent.ConcurrentMapCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.Profile;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.util.Assert;
/**
* Integration tests testing Spring's Cache Abstraction using Spring Data Redis auto-configured with Spring Boot.
*
* To run this test, first start a Redis Server on localhost listening on the default port, 6379.
*
* #author John Blum
* #see org.junit.Test
* #since 1.0.0
*/
#RunWith(SpringRunner.class)
#ActiveProfiles("auto")
#FixMethodOrder(MethodSorters.NAME_ASCENDING)
#ContextConfiguration(classes = CachingWithRedisConfiguration.class)
#SuppressWarnings("unused")
public class CachingWithRedisIntegrationTest {
protected static final int REDIS_PORT = 6379;
protected static final String REDIS_HOST = "localhost";
private AtomicBoolean setup = new AtomicBoolean(false);
#Autowired
private MathService mathService;
#Autowired(required = false)
private RedisTemplate<Object, Object> redisTemplate;
#Before
public void setup() {
if (redisTemplate != null && !setup.getAndSet(true)) {
redisTemplate.delete(Arrays.asList(0L, 1L, 2L, 4L, 8L));
}
}
#Test
public void firstCacheMisses() {
assertThat(mathService.factorial(0L)).isEqualTo(1L);
assertThat(mathService.wasCacheMiss()).isTrue();
assertThat(mathService.factorial(1L)).isEqualTo(1L);
assertThat(mathService.wasCacheMiss()).isTrue();
assertThat(mathService.factorial(2L)).isEqualTo(2L);
assertThat(mathService.wasCacheMiss()).isTrue();
assertThat(mathService.factorial(4L)).isEqualTo(24L);
assertThat(mathService.wasCacheMiss()).isTrue();
assertThat(mathService.factorial(8L)).isEqualTo(40320L);
assertThat(mathService.wasCacheMiss()).isTrue();
}
#Test
public void thenCacheHits() {
assertThat(mathService.factorial(0L)).isEqualTo(1L);
assertThat(mathService.wasCacheMiss()).isFalse();
assertThat(mathService.factorial(1L)).isEqualTo(1L);
assertThat(mathService.wasCacheMiss()).isFalse();
assertThat(mathService.factorial(2L)).isEqualTo(2L);
assertThat(mathService.wasCacheMiss()).isFalse();
assertThat(mathService.factorial(4L)).isEqualTo(24L);
assertThat(mathService.wasCacheMiss()).isFalse();
assertThat(mathService.factorial(8L)).isEqualTo(40320L);
assertThat(mathService.wasCacheMiss()).isFalse();
}
interface MathService {
boolean wasCacheMiss();
long factorial(long number);
}
#EnableCaching
#SpringBootConfiguration
#Import({ AutoRedisConfiguration.class, CustomRedisConfiguration.class })
static class CachingWithRedisConfiguration {
#Bean
MathService mathService() {
return new MathService() {
private final AtomicBoolean cacheMiss = new AtomicBoolean(false);
#Override
public boolean wasCacheMiss() {
return cacheMiss.getAndSet(false);
}
#Override
#Cacheable(cacheNames = "Factorials")
public long factorial(long number) {
cacheMiss.set(true);
Assert.isTrue(number >= 0L, String.format("Number [%d] must be greater than equal to 0", number));
if (number <= 2L) {
return (number < 2L ? 1L : 2L);
}
long result = number;
while (--number > 1) {
result *= number;
}
return result;
}
};
}
#Bean
#Profile("none")
CacheManager cacheManager() {
return new ConcurrentMapCacheManager();
}
}
#Profile("auto")
#EnableAutoConfiguration
#SpringBootConfiguration
static class AutoRedisConfiguration {
#PostConstruct
public void afterPropertiesSet() {
System.out.println("AUTO");
}
#Bean
static PropertySourcesPlaceholderConfigurer propertyPlaceholderConfigurer() {
PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer =
new PropertySourcesPlaceholderConfigurer();
propertySourcesPlaceholderConfigurer.setProperties(redisProperties());
return propertySourcesPlaceholderConfigurer;
}
static Properties redisProperties() {
Properties redisProperties = new Properties();
redisProperties.setProperty("spring.cache.type", "redis");
redisProperties.setProperty("spring.redis.host", REDIS_HOST);
redisProperties.setProperty("spring.redis.port", String.valueOf(REDIS_PORT));
return redisProperties;
}
}
#Profile("custom")
#SpringBootConfiguration
static class CustomRedisConfiguration {
#PostConstruct
public void afterPropertiesSet() {
System.out.println("CUSTOM");
}
#Bean
JedisConnectionFactory jedisConnectionFactory() {
JedisConnectionFactory factory = new JedisConnectionFactory();
factory.setHostName(REDIS_HOST);
factory.setPort(REDIS_PORT);
factory.setUsePool(true);
return factory;
}
#Bean
RedisTemplate<Object, Object> redisTemplate() {
RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(jedisConnectionFactory());
return redisTemplate;
}
#Bean
CacheManager cacheManager() {
RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate());
cacheManager.setUsePrefix(true); // THIS IS NEEDED!
return cacheManager;
}
}
}
Hope this helps!
Cheers,
John
I am using Spring Boot 2.0 and its very simple to use Redis for simple caching purpose.
Annotate your Spring boot application with #EnableCaching
In your application.properties have these properties
spring.cache.type=redis
redis.host.url=
redis.host.port=
Annotate your methods with #Cacheable.
That's it!!
If you are using AWS Elasticache and you have checked the in-transit encryption then you need to add a RedisConfiguration file to set your ssl to true.
Spring Boot 2.0 now uses LettuceConnectionFactory.
To do the above simply add a class and mark it with #Configuration annotation and add the following bean
#Bean
public LettuceConnectionFactory redisConnectionFactory() {
RedisStandaloneConfiguration configuration = new RedisStandaloneConfiguration();
configuration.setHostName(redisHost);
configuration.setPort(redisPort);
return new LettuceConnectionFactory(configuration, LettuceClientConfiguration.builder().useSsl().disablePeerVerification().build());
}
You can check existence of your keys in redis with command in redis-cli: keys *
If there will be your key, everything is OK :)
The methods of your classes should be #Cacheable. Along with #CachePut you should use #CacheEvict with the keys and their values for delete method to flush out the resources. Also the host, port, type and password is needed in application.properties file.
`spring.redis.password= password`
`spring.cache.type=redis`
`spring.redis.host=localhost`
`spring.redis.port=6379`
Also add some more properties like so, as per requirement.
`spring.cache.redis.time-to-live=600000`
`spring.cache.redis.cache-null-values=false`
`spring.cache.redis.use-key-prefix=true
* Edited with possible solution - any comments ? *
spring 4.2.5 RELEASE
I'm starting to create Java web services onto a Legacy database.
Following the spring-data JPA repository pattern creating entities which map to tables, a repository extending CrudRepository is working well.
As described in this great tutorial
All the examples I've seen assume simple mapping of a table to an entity. Order -> OrderEntity, OrderLine, Customer etc.
How would you deal with read-only reporting type queries which do not fit into this pattern where the query result contains columns from many tables and use complex cross table joins.
I'm just struggling to get my head around how to deal with this scenario.
Possible Solution
I've managed to run native SQL using the NamedParameterJdbcTemplate and map the results onto a POJO using a BeanPropertyRowMapper
ApplicationContext class
The NamedParameterJdbcTemplate bean is defined (the rest of the beans HikariCP, JPA Session Factory, JPA Transaction Manager, DozerBean mapper have been left out for brevity)
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(basePackages = {"com.savant.test.spring.donorservicejpa.dao.repository"},
repositoryBaseClass = com.savant.test.spring.donorservicejpa.dao.repository.BaseRepositoryImpl.class )
#ComponentScan(
{"com.savant.test.spring.donorservicejpa.dao.jdbc.repository",
"com.savant.test.spring.donorservicejpa.dao.query.objects"})
public class ApplicationContext {
#Bean
NamedParameterJdbcTemplate jdbcTemplate(DataSource dataSource) {
return new NamedParameterJdbcTemplate(dataSource);
}
}
POJO for search results. No Spring annotations, just a simple class
package com.savant.test.spring.donorservicejpa.dao.query.objects;
public class SessionSearchResult {
private String sessno;
private String sesdate;
// etc
// setters/getters
}
'Repository'. It's not actually a repository in spring terms, just an interface/class implementation
package com.savant.test.spring.donorservicejpa.dao.jdbc.repository;
public interface SessionSearchRepository{
List<SessionSearchResult> findByCriteria(String searchCriteria);
}
Base implementation
package com.savant.test.spring.donorservicejpa.dao.jdbc.repository;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
public class BaseJdbcRepositoryImpl {
protected final NamedParameterJdbcTemplate jdbcTemplate;
BaseJdbcRepositoryImpl(NamedParameterJdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
}
Simple test implementation of the search.
package com.savant.test.spring.donorservicejpa.dao.jdbc.repository;
import com.savant.test.spring.donorservicejpa.dao.query.objects.SessionSearchResult;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
#Component
public class SessionSearchRepositoryImpl extends BaseJdbcRepositoryImpl implements SessionSearchRepository {
private static final String SESSION_SEARCH_SQL
= "SELECT sesdet.sessno, sessdays.sesdate "
+ "FROM sesdet, sessdays "
+ "WHERE sessdays.sessno = sesdet.sessno "
+ "AND sesdet.sessno = :sessno";
#Autowired
public SessionSearchRepositoryImpl(NamedParameterJdbcTemplate jdbcTemplate) {
super(jdbcTemplate);
}
#Transactional(readOnly = true)
#Override
public List<SessionSearchResult> findByCriteria(String searchCriteria) {
Map<String, String> queryParams = new HashMap<>();
queryParams.put("sessno", searchCriteria);
List<SessionSearchResult> searchResults = jdbcTemplate.query(SESSION_SEARCH_SQL, queryParams,
new BeanPropertyRowMapper<>(SessionSearchResult.class));
return searchResults;
}
}
And a simple test just to run the SQL
#Autowired
SessionSearchRepository sessionSearchRepository;
#Test
public void a_testSessionSearch() throws Exception, Throwable {
List<SessionSearchResult> sl = sessionSearchRepository.findByCriteria("CA04AS");
for (SessionSearchResult sessionSearchEntity : sl) {
}
}
I'm developing a Spring Boot based web application. I heavily rely on #ComponentScan and #EnableAutoConfiguration and no explicit XML configuration in place.
I have the following problem. I have a JPA-Annotated Entity class called UserSettings:
#Entity public class UserSettings {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#OneToMany(cascade = CascadeType.ALL)
private Set<Preference> preferences; // 'Preference' is another #Entity class
public UserSettings() {
this.preferences = new HashSet<Preference>();
}
// some more primitive properties, Getters, Setters...
}
I followed this tutorial and created a repository interface that extends JpaRepository<UserSettings,Long>.
Furthermore, I have a UserManager bean:
#Component public class SettingsManager {
#Autowired
UserSettingsRepository settingsRepository;
#PostConstruct
protected void init() {
// 'findGlobalSettings' is a simple custom HQL query
UserSettings globalSettings = this.settingsRepository.findGlobalSettings();
if (globalSettings == null) {
globalSettings = new UserSettings();
this.settingsRepository.saveAndFlush(globalSettings);
}
}
Later in the code, I load the UserSettings object created here, again with the findGlobalSetttings query.
The problem is: Every time I try to access the #OneToMany attribute of the settings object, I get the following exception:
org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role org.example.UserSettings.preferences, could not initialize proxy - no Session
I understand that each HTTP Session has its own Hibernate Session, as described in the accepted answer of this question, but that does not apply in my case (currently I'm testing this within the same HTTP Session), which is why I have no idea where this exception comes from.
What am I doing wrong and how can I achieve circumvent the error?
If you want to be able to access mapped entities outside the transaction (which you seem to be doing), you need to flag it as an "eager" join. i.e.
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
This question has been answered beautifully by #Steve. However, if you still want to maintain your lazy loading implementation, you may want to try this
import javax.servlet.Filter;
import org.springframework.boot.context.embedded.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.orm.hibernate4.support.OpenSessionInViewFilter;
#Configuration
#ComponentScan
public class AppConfig {
#Bean
public FilterRegistrationBean filterRegistration() {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(openSessionInView());
registration.addUrlPatterns("/*");
return registration;
}
#Bean
public Filter openSessionInView() {
return new OpenSessionInViewFilter();
}
}
What this configuration does is, it registers a Filter on requests to path "/*" which keeps your Hibernate Session open in your view.
This is an anti-pattern and must be used with care.
NOTE: As of Spring Boot 1.3.5.RELEASE, when you use the default configuration with Spring Data JPA auto-configuration, you shouldn't encounter this problem
I faced similar issue in spring boot application, after googling I'm able to fix this issue by adding the following code to my application.
#Bean(name = "entityManagerFactory")
public LocalContainerEntityManagerFactoryBean entityManagerFactory(EntityManagerFactoryBuilder builder, DataSource dataSource) {
LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();
entityManagerFactoryBean.setDataSource(dataSource);
entityManagerFactoryBean.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
Properties jpaProperties = new Properties();
jpaProperties.put("hibernate.enable_lazy_load_no_trans", true);
entityManagerFactoryBean.setJpaProperties(jpaProperties);
return entityManagerFactoryBean;
}
Referred here.