I have a problem with injection spring beans into some ignite's classes. I'm trying to create this: Client -> Apache Ignite -> Spring-Data -> DataBase
Maybe it is wrong, i'm not sure.
So, at this moment my classes look like:
AppConfiguration
#Configuration
#ComponentScan(basePackages = arrayOf("com.ignite.cache"))
open class AppConfiguration : Serializable {
private val logger: Logger = Logger.getLogger(AppConfiguration::class.java)
#Bean
open fun igniteInstance(): Ignite {
val cfg = IgniteConfiguration()
cfg.igniteInstanceName = "springDataNode"
cfg.isPeerClassLoadingEnabled = true
var clientCache: CacheConfiguration<Long, Client> = CacheConfiguration("ClientCache")
clientCache.apply {
setIndexedTypes(Long::class.java, Client::class.java)
setCacheStoreFactory(FactoryBuilder.factoryOf(ClientStore::class.java))
isReadThrough = true
isWriteThrough = true
}
cfg.setCacheConfiguration(clientCache)
return Ignition.start(cfg)
}
}
DataSourceConfiguration:
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(basePackages = arrayOf("com.ignite.cache.model.repositories.springdatarepository"))
#EnableIgniteRepositories(basePackages = arrayOf("com.ignite.cache.model.repositories.igniterepository"))
#ComponentScan(basePackages = arrayOf("com.ignite.cache.model"))
open class DataSourceConfiguration : Serializable {
#Bean
open fun entityManagerFactory(): LocalContainerEntityManagerFactoryBean {
var entityManagerFactory: LocalContainerEntityManagerFactoryBean = LocalContainerEntityManagerFactoryBean()
entityManagerFactory.apply {
dataSource = dataSource()
setPackagesToScan("com.ignite.cache.model")
var vendorAdapter: HibernateJpaVendorAdapter = HibernateJpaVendorAdapter()
vendorAdapter.apply {
setGenerateDdl(true)
setShowSql(true)
}
var properties: Properties = Properties()
properties.apply {
put("database.dialet", "org.hibernate.dialect.PostgreSQL95Dialect")
put("database.globally_quoted_identifiers", "false")
put("database.enable_lazy_load_no_trans", "true")
put("database.show_sql", "true")
}
jpaVendorAdapter = vendorAdapter
setJpaProperties(properties)
}
return entityManagerFactory
}
#Bean
open fun dataSource(): DataSource {
var source: ComboPooledDataSource = ComboPooledDataSource()
source.apply {
driverClass = "org.postgresql.Driver"
jdbcUrl = "jdbc:postgresql://localhost:5432/ignite"
user = "postgres"
password = "1111"
acquireIncrement = 5
idleConnectionTestPeriod = 60
maxPoolSize = 20
minPoolSize = 10
initialPoolSize = 10
}
return source
}
#Bean
open fun transactionManager() : PlatformTransactionManager {
var manager: JpaTransactionManager = JpaTransactionManager()
manager.apply {
entityManagerFactory = entityManagerFactory().nativeEntityManagerFactory
}
return manager
}
#Bean
open fun exceptionTranslator(): PersistenceExceptionTranslationPostProcessor = PersistenceExceptionTranslationPostProcessor()
}
Entity:
#Entity
#Table(name = "client")
data class Client
(
#Id
#Column(name = "id")
#GeneratedValue(generator = "increment")
#GenericGenerator(name = "increment", strategy = "increment")
var id: Long = 0,
#Column(name = "email", nullable = false, unique = true)
var email: String = "",
#Column(name = "login", nullable = false, unique = true)
var login: String = ""
) : Serializable
Repositories:
#RepositoryConfig(cacheName = "ClientCache")
interface IgniteClientRepository : IgniteRepository<Client, Long> {
}
#Repository
interface ClientRepository : CrudRepository<Client, Long>
Standart implemenation for service classes and CacheStore for ignite:
public class ClientStore implements CacheStore<Long, Client>, Serializable {
private Logger logger = Logger.getLogger(ClientStore.class);
#SpringResource
private IClientService clientRepository; // <- error is here (NPE)
#Override
public void loadCache(IgniteBiInClosure<Long, Client> igniteBiInClosure, #Nullable Object... objects) throws CacheLoaderException {
Iterable<Client> clients = clientRepository.findAll();
for(Client client : clients) {
igniteBiInClosure.apply(client.getId(), client);
}
}
...
}
Main:
public class Main {
private static Logger logger = Logger.getLogger(Main.class);
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfiguration.class, DataSourceConfiguration.class);
Ignite ignite = context.getBean(Ignite.class);
(ignite.getOrCreateCache("ClientCache")).loadCache(null);
IgniteClientRepository clientRepository = context.getBean(IgniteClientRepository.class);
Iterable<Client> clients = clientRepository.findAll();
for(Client client : clients) {
logger.info(client);
}
logger.info("Finish");
}
}
But when i try to load some data from database into cache i get error NPE:
Exception in thread "main" javax.cache.integration.CacheLoaderException: java.lang.NullPointerException
at org.apache.ignite.internal.processors.cache.store.GridCacheStoreManagerAdapter.loadCache(GridCacheStoreManagerAdapter.java:528)
at org.apache.ignite.internal.processors.cache.distributed.dht.GridDhtCacheAdapter.localLoadCache(GridDhtCacheAdapter.java:486)
at org.apache.ignite.internal.processors.cache.GridCacheProxyImpl.localLoadCache(GridCacheProxyImpl.java:217)
at org.apache.ignite.internal.processors.cache.GridCacheAdapter$LoadCacheJob.localExecute(GridCacheAdapter.java:5439)
at org.apache.ignite.internal.processors.cache.GridCacheAdapter$LoadCacheJobV2.localExecute(GridCacheAdapter.java:5488)
at org.apache.ignite.internal.processors.cache.GridCacheAdapter$TopologyVersionAwareJob.execute(GridCacheAdapter.java:6103)
at org.apache.ignite.compute.ComputeJobAdapter.call(ComputeJobAdapter.java:132)
at org.apache.ignite.internal.processors.closure.GridClosureProcessor$C2.execute(GridClosureProcessor.java:1842)
at org.apache.ignite.internal.processors.job.GridJobWorker$2.call(GridJobWorker.java:566)
at org.apache.ignite.internal.util.IgniteUtils.wrapThreadLoader(IgniteUtils.java:6621)
at org.apache.ignite.internal.processors.job.GridJobWorker.execute0(GridJobWorker.java:560)
at org.apache.ignite.internal.processors.job.GridJobWorker.body(GridJobWorker.java:489)
at org.apache.ignite.internal.util.worker.GridWorker.run(GridWorker.java:110)
at org.apache.ignite.internal.processors.job.GridJobProcessor.processJobExecuteRequest(GridJobProcessor.java:1114)
at org.apache.ignite.internal.processors.task.GridTaskWorker.sendRequest(GridTaskWorker.java:1379)
at org.apache.ignite.internal.processors.task.GridTaskWorker.processMappedJobs(GridTaskWorker.java:640)
at org.apache.ignite.internal.processors.task.GridTaskWorker.body(GridTaskWorker.java:532)
at org.apache.ignite.internal.util.worker.GridWorker.run(GridWorker.java:110)
at org.apache.ignite.internal.processors.task.GridTaskProcessor.startTask(GridTaskProcessor.java:743)
at org.apache.ignite.internal.processors.task.GridTaskProcessor.execute(GridTaskProcessor.java:443)
at org.apache.ignite.internal.processors.closure.GridClosureProcessor.callAsync(GridClosureProcessor.java:447)
at org.apache.ignite.internal.processors.closure.GridClosureProcessor.callAsync(GridClosureProcessor.java:418)
at org.apache.ignite.internal.processors.closure.GridClosureProcessor.callAsync(GridClosureProcessor.java:402)
at org.apache.ignite.internal.processors.cache.GridCacheAdapter.globalLoadCacheAsync(GridCacheAdapter.java:3681)
at org.apache.ignite.internal.processors.cache.GridCacheAdapter.globalLoadCache(GridCacheAdapter.java:3657)
at org.apache.ignite.internal.processors.cache.IgniteCacheProxy.loadCache(IgniteCacheProxy.java:387)
at com.ignite.cache.Main.main(Main.java:22)
Caused by: java.lang.NullPointerException
at com.ignite.cache.model.service.ClientStore.loadCache(ClientStore.java:30)
at org.apache.ignite.internal.processors.cache.store.GridCacheStoreManagerAdapter.loadCache(GridCacheStoreManagerAdapter.java:502)
... 26 more
I still can't figure out why my client service doesn't inject into CacheStore class. Maybe i should use xml config instead of java-class config for ignite?
When you start Ignite using Ignition#start with IgniteConfiguration object, it's not aware of Spring context at all. You need to use IgniteSpring#start methods instead to provide context explicitly. Another option is to utilize IgniteSpringBean that already implements ApplicationContextAware and starts Ignite instance properly.
Also note that you will need to provide either bean name or bean class as a parameter to #SpringResource annotation.
I don't know Ignite and I don't know the programming language you are using but it looks like Ignite and not Spring is creating your ClientStore instances. Therefore you need to inject the dependencies manually.
I'm not really familiar with the FactoryBuilder used there, so there might be nice solutions if you can use a constructor with arguments or even a lambda, but if that doesn' work, you can store a reference to the repository in static field and get it from there in the constructor of the ClientStore.
Related
When I comment out the line #ConditionalOnBean(name = "customRedisConnectionFactory"),the function is execute and the customRedisConnectionFactory is the object that I injected. But when I enabled this line, the method doesn't execute. I want to know the reason. Can someone help me answer it.
RedisConfig
#Configuration
#ConditionalOnClass(RedisOperations.class)
#AutoConfigureBefore({CacheAutoConfiguration.class})
public class RedisConfig {
#Bean(name = "customRedisConnectionFactory")
public RedisConnectionFactory customRedisConnectionFactory(){
RedisStandaloneConfiguration redisStandaloneConfiguration=new RedisStandaloneConfiguration();
redisStandaloneConfiguration.setDatabase(12);
redisStandaloneConfiguration.setHostName("127.0.0.1");
redisStandaloneConfiguration.setPassword("yichen");
redisStandaloneConfiguration.setPort(6379);
JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory(redisStandaloneConfiguration);
return jedisConnectionFactory;
}
}
CustomRedisCacheManagerConfiguration
#Configuration
#AutoConfigureAfter({CacheAutoConfiguration.class})
#ConditionalOnClass(RedisOperations.class)
#EnableConfigurationProperties({RedisProperties.class, CacheProperties.class, RedisCacheExpiresProperties.class})
public class CustomRedisCacheManagerConfiguration {
private final CacheProperties cacheProperties;
public CustomRedisCacheManagerConfiguration(CacheProperties cacheProperties) {
this.cacheProperties = cacheProperties;
}
#Bean(name = "serviceRedisCacheManager")
#ConditionalOnBean(name = "customRedisConnectionFactory")
public RedisCacheManager serviceRedisCacheManager(
#Qualifier("customRedisConnectionFactory") RedisConnectionFactory customRedisConnectionFactory,
RedisCacheExpiresProperties redisCacheExpiresProperties) {
RedisCacheManager.RedisCacheManagerBuilder builder =
RedisCacheManager.builder(customRedisConnectionFactory).cacheDefaults(determineConfigurationDefault());
Map<String, Long> cacheConfigurations = redisCacheExpiresProperties.getCacheExpires();
if (cacheConfigurations != null && cacheConfigurations.size() > 0) {
Map<String, RedisCacheConfiguration> redisCacheConfigurations = new HashMap<>();
for (String cacheName : cacheConfigurations.keySet()) {
Assert.notNull(cacheName, "CacheName must not be null!");
long ttl = cacheConfigurations.get(cacheName);
Assert.isTrue(ttl > 0, "Expire must not be null!");
RedisCacheConfiguration redisCacheConfiguration = determineConfiguration(cacheName, ttl);
redisCacheConfigurations.put(cacheName, redisCacheConfiguration);
}
builder.withInitialCacheConfigurations(redisCacheConfigurations);
}
return builder.build();
}
private org.springframework.data.redis.cache.RedisCacheConfiguration determineConfigurationDefault() {
CacheProperties.Redis redisProperties = this.cacheProperties.getRedis();
org.springframework.data.redis.cache.RedisCacheConfiguration config =
org.springframework.data.redis.cache.RedisCacheConfiguration.defaultCacheConfig();
config = config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()));
config = config.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()));
if (redisProperties.getTimeToLive() != null) {
config = config.entryTtl(redisProperties.getTimeToLive());
}
if (redisProperties.getKeyPrefix() != null) {
config = config.prefixKeysWith(redisProperties.getKeyPrefix());
}
if (!redisProperties.isCacheNullValues()) {
config = config.disableCachingNullValues();
}
if (!redisProperties.isUseKeyPrefix()) {
config = config.disableKeyPrefix();
}
return config;
}
private org.springframework.data.redis.cache.RedisCacheConfiguration determineConfiguration(String cacheName,long ttl) {
org.springframework.data.redis.cache.RedisCacheConfiguration config =
org.springframework.data.redis.cache.RedisCacheConfiguration.defaultCacheConfig();
config = config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()));
config = config.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()));
config = config.entryTtl(Duration.ofSeconds(ttl));
config = config.disableCachingNullValues();
return config;
}
}
application.properties
spring.redis.password=yichen
spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.database=10
spring.redis.max.idle=10
spring.redis.max.total=30
spring.redis.max.wait.mills=-1
spring.cache.redis.time-to-live=60000
spring.cache.redis.key-prefix=test
spring.cache.redis.cache-expires.c3m=180
spring.cache.redis.cache-expires.c5m=300
spring.cache.redis.cache-expires.c10m=600
spring.cache.redis.cache-expires.c30m=1800
spring.cache.redis.cache-expires.c24h=86400
spring.cache.redis.cache-expires.c7d=604800
spring.cache.redis.cache-expires.c30d=2592000
RedisCacheExpiresProperties
#ConfigurationProperties(prefix = "spring.cache.redis")
public class RedisCacheExpiresProperties {
private Map<String, Long> cacheExpires;
public Map<String, Long> getCacheExpires() {
return cacheExpires;
}
public void setCacheExpires(Map<String, Long> cacheExpires) {
this.cacheExpires = cacheExpires;
}
}
I think it's maybe a problem with the order the two configuration class (RedisConfig and CustomRedisCacheManagerConfiguration) were auto configured. Make sure you registered the class as auto configuration class and RedisConfig is auto configured before CustomRedisCacheManagerConfiguration.
When I try to replace #DependsOn("customRedisConnectionFactory") with #ConditionalOnBean(type = "RedisConnectionFactory"), it successfully executed.
Their differences and implementation details are to be studied.
It's another one of those circular reference errors in Spring Boot. My log says:
userDetailsServiceImpl defined in file [/home/.../auth/UserDetailsServiceImpl.class]
┌─────┐
| tenantDataSource
└─────┘
Now, I've seen this happening to other people, but never with one class. Here comes the TenantDataSource:
#Component
class TenantDataSource(private val configRepository: DataSourceConfigRepository): Serializable {
private val dataSources = HashMap<String, DataSource>()
val hexKeyMaterials = HashMap<String, String>()
private val logger: Logger = LoggerFactory.getLogger(this::class.java)
fun getDataSource(dataSourceName: String): DataSource {
logger.info(" [X] TenantDataSource - Getting DataSource: $dataSourceName")
if (!dataSources.contains(dataSourceName)) {
logger.info(" [X] TenantDataSource - DataSource $dataSourceName is not in map... Creating...")
dataSources[dataSourceName] = createDataSource(dataSourceName)
}
return dataSources[dataSourceName]!!
}
#PostConstruct
fun getAll(): Map<String, DataSource> {
logger.info(" [X] TenantDataSource - Processing all datasources after construct")
val configList: List<DataSourceConfig> = configRepository.findAll()
val result: MutableMap<String, DataSource> = HashMap()
for (config in configList) {
result[config.name] = getDataSource(config.name)
hexKeyMaterials[config.name] = config.keymaterial
}
return result
}
private fun createDataSource(dataSourceName: String): DataSource {
logger.info(" [X] TenantDataSource - Creating dataSource from config: $dataSourceName")
val config: DataSourceConfig = configRepository.findByName(dataSourceName)
return DataSourceBuilder
.create()
.username(config.username)
.password(config.password)
.url(config.url).build()
}
}
I can't imagine where I should start looking for the circular reference in this... :( I've seen some people saying "i don't even have constructor injection..." Well, I do because I don't like the autowired lateinit variables in Kotlin, that's all. So if there's a chance I'd like to keep my CIs. Help me out here because I don't get what's going on! :)
Oh, the UserDetailsServiceImpl if it helps:
#Service
class UserDetailsServiceImpl(private val userRepository: UserRepository, private val decrypter: HaliteDecrypter, private val tenantDataSource: TenantDataSource): UserDetailsService {
private val logger: Logger = LoggerFactory.getLogger(this.javaClass)
override fun loadUserByUsername(username: String): AuthUser {
logger.info("Trying to get user for authentication $username")
val user = userRepository.getUserByUsername(username)
decrypter.hexKeyMaterial = tenantDataSource.hexKeyMaterials[TenantContext.currentTenant.get()]!!
user?.let {
logger.info("Returning UserDetails for " + user.username)
return AuthUser(
user.userId,
user.username,
decrypter.decryptPasswd(user.password).toString(Charsets.US_ASCII),
user.role.id,
user.role.name,
user.flags
)
} ?: throw UsernameNotFoundException(String.format("Username %s not found", username))
}
}
I use spring boot 2.1.2 and redis as cache provider.
But now, I have a question.
In sysUser entity
#Data
#Entity
#Table(name = "sys_user")
#ToString(exclude = "roles")
#EqualsAndHashCode(callSuper = true)
#Proxy(lazy = false)
public class SysUser extends BaseEntity implements UserDetails {
// ...
/**
* 当前用户的权限
*/
#ManyToMany(fetch = FetchType.EAGER)
#JsonIgnoreProperties(value = "users")
#JoinTable(name = "sys_user_role",
joinColumns = {#JoinColumn(name = "user_id", nullable = false)},
inverseJoinColumns = {#JoinColumn(name = "role_id", nullable = false)})
private List<SysRole> roles;
// ...
}
In sysRole entity
#Data
#Entity
#Table(name = "sys_role")
#EqualsAndHashCode(callSuper = true)
#ToString(exclude = {"users", "permissions"})
#Proxy(lazy = false)
public class SysRole extends BaseEntity {
// ...
/**
* 当前角色的菜单
*/
#JsonIgnoreProperties(value = "roles")
#ManyToMany(cascade = CascadeType.MERGE, fetch = FetchType.EAGER)
#JoinTable(name = "sys_permission_role", joinColumns = #JoinColumn(name = "role_id"),
inverseJoinColumns = #JoinColumn(name = "permission_id"))
private List<SysPermission> permissions = new ArrayList<>();
/**
* 当前角色对应的用户
* 双向映射造成数据重复查询死循环问题
*/
#ManyToMany(mappedBy = "roles")
private List<SysUser> users = new ArrayList<>();
}
In SysPermission entitty
#Data
#Entity
#Table(name = "sys_permission")
#EqualsAndHashCode(callSuper = true)
#Proxy(lazy = false)
public class SysPermission extends BaseEntity {
// ...
/**
* 菜单角色
* 双向映射造成数据重复查询死循环问题
*/
#ManyToMany(mappedBy = "permissions")
private List<SysRole> roles = new ArrayList<>();
}
In sysUser service impl
#Override
#Cacheable
public SysUser loadUserByUsername(String username) {
return sysUserRepository.findFirstByUsernameAndEnabledTrue(username).orElseThrow(() ->
new UsernameNotFoundException("用户不存在")
);
}
redis config
#Bean
#Override
public CacheManager cacheManager() {
RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofHours(12))
.prefixKeysWith(applicationProperties.getName())
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(keySerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(valueSerializer()))
.disableCachingNullValues();
return RedisCacheManager
.builder(RedisCacheWriter.nonLockingRedisCacheWriter(connectionFactory))
.cacheDefaults(redisCacheConfiguration)
.transactionAware()
.build();
}
#Bean(name = "redisTemplate")
public RedisTemplate<String, Object> redisTemplate() {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(connectionFactory);
redisTemplate.setKeySerializer(keySerializer());
redisTemplate.setHashKeySerializer(keySerializer());
redisTemplate.setValueSerializer(valueSerializer());
redisTemplate.setHashValueSerializer(valueSerializer());
return redisTemplate;
}
private RedisSerializer<String> keySerializer() {
return new StringRedisSerializer();
}
private RedisSerializer<Object> valueSerializer() {
return new GenericJackson2JsonRedisSerializer();
}
Question
When I first called loadUserByUsername,it is ok.And in redis
in json.cn
But when I secound called loadUserByUsername,it is wrong,And get exception
org.springframework.data.redis.serializer.SerializationException: Could not read JSON: failed to lazily initialize a collection, could not initialize proxy - no Session (through reference chain: cn.echocow.xiaoming.model.entity.SysUser["roles"]); nested exception is com.fasterxml.jackson.databind.JsonMappingException: failed to lazily initialize a collection, could not initialize proxy - no Session (through reference chain: cn.echocow.xiaoming.model.entity.SysUser["roles"])
at org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer.deserialize(GenericJackson2JsonRedisSerializer.java:132)
at org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer.deserialize(GenericJackson2JsonRedisSerializer.java:110)
at org.springframework.data.redis.serializer.DefaultRedisElementReader.read(DefaultRedisElementReader.java:48)
......
Caused by: com.fasterxml.jackson.databind.JsonMappingException: failed to lazily initialize a collection, could not initialize proxy - no Session (through reference chain: cn.echocow.xiaoming.model.entity.SysUser["roles"])
at com.fasterxml.jackson.databind.JsonMappingException.wrapWithPath(JsonMappingException.java:394)
at com.fasterxml.jackson.databind.JsonMappingException.wrapWithPath(JsonMappingException.java:353)
......
Caused by: org.hibernate.LazyInitializationException: failed to lazily initialize a collection, could not initialize proxy - no Session
at org.hibernate.collection.internal.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:597)
at org.hibernate.collection.internal.AbstractPersistentCollection.withTemporarySessionIfNeeded(AbstractPersistentCollection.java:216)
at org.hibernate.collection.internal.AbstractPersistentCollection.readSize(AbstractPersistentCollection.java:160)
at org.hibernate.collection.internal.PersistentBag.size(PersistentBag.java:287)
at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:302)
......
Other
I try these methods
#JsonIgnore , but it will set roles is null, I want to use this field.
Config jackson registerModule Hibernate5Module, it will set roles is null.
Use #Proxy(lazy = false), no changes.
Use #ManyToMany(fetch = FetchType.EAGER), no changes
config
spring:
jpa:
open-in-view: true
properties
hibernate:
enable_lazy_load_no_trans: true
no changes...
Use another json tools, such as gson and FastJson, but infinite loop for jpa when save cache.
Please help me, I had spent three days...But I do not resolve this question...
Thanks!
github address: XIAOMING
If do not have resolve method, maybe I must use Mybatis. But there is a lot of work.Please help me resolve this question...
1st. create 2 classes below
The HibernateCollectionIdResolver.class will translate HibernateCollection class into JDK collection class, so the Jackson will write json from
{
"paramA": [
"org.hibernate.collection.internal.PersistentSet",
[]
]
}
to
{
"paramA": [
"java.util.HashSet",
[]
]
}
then the method typeFromId will get JDK JavaType from the class full name above, to deserialize your json to POJO.
class HibernateCollectionIdResolver extends TypeIdResolverBase {
public HibernateCollectionIdResolver() {
}
#Override
public String idFromValue(Object value) {
//translate from HibernanteCollection class to JDK collection class
if (value instanceof PersistentArrayHolder) {
return Array.class.getName();
} else if (value instanceof PersistentBag || value instanceof PersistentIdentifierBag || value instanceof PersistentList) {
return List.class.getName();
} else if (value instanceof PersistentSortedMap) {
return TreeMap.class.getName();
} else if (value instanceof PersistentSortedSet) {
return TreeSet.class.getName();
} else if (value instanceof PersistentMap) {
return HashMap.class.getName();
} else if (value instanceof PersistentSet) {
return HashSet.class.getName();
} else {
//default is JDK collection
return value.getClass().getName();
}
}
#Override
public String idFromValueAndType(Object value, Class<?> suggestedType) {
return idFromValue(value);
}
//deserialize the json annotated JDK collection class name to JavaType
#Override
public JavaType typeFromId(DatabindContext ctx, String id) throws IOException {
try {
return ctx.getConfig().constructType(Class.forName(id));
} catch (ClassNotFoundException e) {
throw new UnsupportedOperationException(e);
}
}
#Override
public JsonTypeInfo.Id getMechanism() {
return JsonTypeInfo.Id.CLASS;
}
}
#JsonTypeInfo(
use = JsonTypeInfo.Id.CLASS
)
#JsonTypeIdResolver(value = HibernateCollectionIdResolver.class)
public class HibernateCollectionMixIn {
}
2nd. register this MixIn class to you ObjectMapper
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper mapper = new ObjectMapper();
mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
mapper.registerModule(new Jdk8Module());
mapper.registerModule(new JavaTimeModule());
mapper.registerModule(new JodaModule());
mapper.addMixIn(Collection.class, HibernateCollectionMixIn.class);
jackson2JsonRedisSerializer.setObjectMapper(mapper);
last, register your jackson2JsonRedisSerializer to your RedisCacheConfiguration.
This would be helpful, I spent 2 days researching how to solve this problem.
And I found the json type id could be rewrite...
So just override jackson typeIdResolver~
EDIT: solve deserialization issue and add some comments
In your code you return valueSerializer like this
private RedisSerializer<Object> valueSerializer() {
return new GenericJackson2JsonRedisSerializer();
}
But you will have to return the GenericJackson2JsonRedisSerializer with Jackson Object mapper that has Hibernate5Module or Hibernate4Module registered as a module
public ObjectMapper getMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS);
mapper.enable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
// Registering Hibernate5Module to support lazy objects for hibernate 5
// Use Hibernate4Module if using hibernate 4
mapper.registerModule(new Hibernate5Module());
return mapper;
}
private RedisSerializer<Object> valueSerializer() {
return new GenericJackson2JsonRedisSerializer(getMapper());
}
I am new to ignite , I am trying to fetch data using ignite repository but below query returns 'null'.
my repository
#Component
#RepositoryConfig(cacheName = "UserCache")
#Repository
public interface UserRepository extends IgniteRepository<UserEntity, Long> {
#Query("select a.* from UserEntity a where a.lastname=? ")
UserEntity selectUserlastName(String plastName);
My cache configuration as
CacheConfiguration<Long, UserEntity> lUserCacheConfig =
createCacheConfigurationStore("UserCache", UserCacheStore.class);
CacheJdbcPojoStoreFactory<Long, UserEntity> lUserJdbcStoreFactory = new
CacheJdbcPojoStoreFactory<>();
UserJdbcPojoStoreFactory<? super Long, ? super UserEntity>
lUserJdbcPojoStoreFactory = new UserJdbcPojoStoreFactory<>();
lUserJdbcStoreFactory.setDataSource(datasource);
lUserJdbcStoreFactory.setDialect(new OracleDialect());
lUserJdbcStoreFactory.setTypes(lUserJdbcPojoStoreFactory.
configJdbcContactType());
lUserCacheConfig.setCacheStoreFactory(lUserJdbcStoreFactory);
// Configure Cache..
cfg.setCacheConfiguration(lUserCacheConfig);
My PojoStore is as below:
public class UserJdbcPojoStoreFactory<K, V> extends
AnstractJdbcPojoStoreFactory<Long, UserEntity> {
private static final long serialVersionUID = 1L;
#Autowired
DataSource datasource;
#Override
public CacheJdbcPojoStore<Long, UserEntity> create() {
// TODO Auto-generated method stub
setDataSource(datasource);
return super.create();
}
#Override
public JdbcType configJdbcContactType() {
JdbcType jdbcContactType = new JdbcType();
jdbcContactType.setCacheName("UserCache");
jdbcContactType.setKeyType(Long.class);
jdbcContactType.setValueType(UserEntity.class);
jdbcContactType.setDatabaseTable("USER");
jdbcContactType.setDatabaseSchema("ORGNITATION");
jdbcContactType.setKeyFields(new JdbcTypeField(Types.INTEGER, "id",
Long.class, "id"));
jdbcContactType.setValueFields(
new JdbcTypeField(Types.VARCHAR, "NAME", String.class, "NAME"), //
new JdbcTypeField(Types.VARCHAR, "LASTNAME", String.class, "lastname"),
//
return jdbcContactType;
}
}
Please suggest ..
Please check that #Query annotation imported from ignite-spring-data library and test your query using SqlFieldsQuery.
It'm using a spring boot application with cache enabled.
Environment (pom.xml):
Spring:
org.springframework.boot:spring-boot-starter-amqp:jar:1.3.3.RELEASE
org.springframework:spring-messaging:jar:4.2.5.RELEASE
org.springframework.amqp:spring-rabbit:jar:1.5.4.RELEASE
org.springframework.retry:spring-retry:jar:1.1.2.RELEASE
org.springframework:spring-core:jar:4.2.5.RELEASE:compile
org.springframework.cloud:spring-cloud-aws-context:jar:1.0.4.RELEASE
org.springframework:spring-context:jar:4.2.5.RELEASE
org.springframework.data:spring-data-jpa:jar:1.9.4.RELEASE
org.springframework:spring-context-support:jar:4.2.5.RELEASE
Hibernate
org.hibernate:hibernate-validator:jar:5.2.2.Final
org.hibernate.javax.persistence:hibernate-jpa-2.1-api:jar:1.0.0.Final
com.fasterxml.jackson.datatype:jackson-datatype-hibernate4:jar:2.6.5
org.hibernate:hibernate-entitymanager:jar:5.1.0.Final
org.hibernate.common:hibernate-commons-annotations:jar:5.0.1.Final
org.hibernate:hibernate-java8:jar:5.1.0.Final
org.hibernate:hibernate-envers:jar:5.1.0.Final
Configuration Cache (on Spring boot application):
#Configuration
#EnableCaching
public class ApplicationCacheConfig extends CachingConfigurerSupport {
/**
* Configuration Table Cache
*/
public static final String CONFIGURATION_TABLE_FIND_BY_ID_CACHE_NAME = "CONFIGURATION_TABLE_FIND_BY_ID_CACHE";
public static final String CONFIGURATION_TABLE_FIND_SERVICE_ID_CACHE_NAME = "CONFIGURATION_TABLE_FIND_SERVICE_ID_CACHE";
#Bean
#Override
public CacheManager cacheManager() {
SimpleCacheManager simpleCacheManager = new SimpleCacheManager();
Collection<Cache> caches = Lists.newArrayList();
caches.addAll(buildConfigurationCache());
simpleCacheManager.setCaches(caches);
return simpleCacheManager;
}
private Collection<Cache> buildConfigurationCache() {
List<Cache> caches = Lists.newArrayList();
// This cache never expires and don't have a maximum size because the table Configuration is not transactional
GuavaCache cacheFindById = new GuavaCache(CONFIGURATION_TABLE_FIND_BY_ID_CACHE_NAME,
CacheBuilder.newBuilder().build());
caches.add(cacheFindById);
// This cache never expires and don't have a maximum size because the table Configuration is not transactional
GuavaCache cacheFindByService = new GuavaCache(CONFIGURATION_TABLE_FIND_SERVICE_ID_CACHE_NAME,
CacheBuilder.newBuilder().build());
caches.add(cacheFindByService);
return caches;
}
}
Hibernate entity:
#Entity
#Table(name = Configuration.TABLE_NAME)
#DynamicUpdate
public class Configuration implements Serializable {
public static final String TABLE_NAME = "configuration";
private static final long serialVersionUID = 1L;
#Id
#Column(name = "id")
#Convert(converter = ConfigurationConverter.class)
private ConfigurationEnum id;
#Column(name = "service", nullable = false)
#NotNull
#Convert(converter = ServiceConverter.class)
private ServiceEnum service;
}
Repository (Spring-data):
public interface ConfigurationRepository extends PagingAndSortingRepository<Configuration, Integer>,
JpaSpecificationExecutor<Configuration> {
#Cacheable(ApplicationCacheConfig.CONFIGURATION_TABLE_FIND_BY_ID_CACHE_NAME)
Configuration findById(ConfigurationEnum configurationEnum);
#Cacheable(ApplicationCacheConfig.CONFIGURATION_TABLE_FIND_SERVICE_ID_CACHE_NAME)
List<Configuration> findByService(ServiceEnum service);
}
Configuration Enum:
#Getter
#AllArgsConstructor
public enum ConfigurationEnum {
CONFIG_1(1),
CONFIG_2(2);
private int id;
}
Configuration Converter:
#Converter
public class ConfigurationConverter implements AttributeConverter<ConfigurationEnum, Integer> {
#Override
public Integer convertToDatabaseColumn(ConfigurationEnum key) {
return key == null ? null : (int) key.getId();
}
#Override
public ConfigurationEnum convertToEntityAttribute(Integer key) {
return key == null ? null : Stream.of(ConfigurationEnum.values())
.filter(step -> key.equals(step.getId()))
.findFirst()
.orElse(null);
}
}
Test IT:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = ApplicationIT.class)
#WebAppConfiguration
#Transactional
public class ConfigurationCacheIT {
#Autowired
ConfigurationRepository configurationRepository;
#Autowired
protected CacheManager cacheManager;
#Test
public void configuration_findById_cache_success() {
Configuration config = configurationRepository.findById(ConfigurationEnum.CONFIG_1);
// An ORM request is performed - CHECK
Assert.assertNotNull(step); // TEST OK
Cache.ValueWrapper entry = getCacheEntry(ApplicationCacheConfig.CONFIGURATION_TABLE_FIND_BY_ID_CACHE_NAME, ConfigurationEnum.CONFIG_1.getId());
Assert.assertNull(entry); OK
config = configurationRepository.findById(ConfigurationEnum.CONFIG_1);
// No ORM request is performed - CHECK
Assert.assertNotNull(step); // TEST OK
entry = getCacheEntry(ApplicationCacheConfig.CONFIGURATION_TABLE_FIND_BY_ID_CACHE_NAME, ConfigurationEnum.CONFIG_1.getId());
Assert.assertNotNull(entry); **// TEST FAIL !!!**
entry = getCacheEntry(ApplicationCacheConfig.CONFIGURATION_TABLE_FIND_BY_ID_CACHE_NAME, ConfigurationEnum.CONFIG_1.name());
Assert.assertNotNull(entry); **// TEST FAIL !!!**
entry = getCacheEntry(ApplicationCacheConfig.CONFIGURATION_TABLE_FIND_BY_ID_CACHE_NAME, ConfigurationEnum.CONFIG_1);
Assert.assertNotNull(entry); **// TEST FAIL !!!**
}
protected Cache.ValueWrapper getCacheEntry(String cacheName, Object key) {
return cacheManager.getCache(cacheName).get(key);
}
#Test
public void configuration_findByAll_without_cache_success() {
ArrayList<Configuration> list1 = Lists.newArrayList(configurationRepository.findAll());
// An ORM request is executed
Assert.assertNotNull(list1);
Assert.assertEquals(ConfigurationEnum.values().length, list1.size());
ArrayList<Configuration> list2 = Lists.newArrayList(configurationRepository.findAll());
// Another ORM request is executed
Assert.assertNotNull(list2);
Assert.assertEquals(ConfigurationEnum.values().length, list2.size());
}
}
My question is why my tests are failing?
Actually this was a non issue.
I'm using the fallowing architecture:
App-mdw (Middleware layer) (Spring boot App with #EnableCaching annotation)
App-ws (WebServices layer) (Spring boot App without #EnableCaching annotation)
The above tests were executed on the application App-ws and the annotation is not inherited that's why the caching was not working.
The right assert was:
entry = getCacheEntry(ApplicationCacheConfig.CONFIGURATION_TABLE_FIND_BY_ID_CACHE_NAME, ConfigurationEnum.CONFIG_1);
Assert.assertNotNull(entry)