我使用Spring Boot 2.1.2和Redis作为缓存提供程序。
但是现在,我有一个问题。
@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;
// ...
}
@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<>();
}
@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<>();
}
@Override
@Cacheable
public SysUser loadUserByUsername(String username) {
return sysUserRepository.findFirstByUsernameAndEnabledTrue(username).orElseThrow(() ->
new UsernameNotFoundException("用户不存在")
);
}
@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();
}
当我第一次打电话给loadUserByUsername
时,没关系。在redis中
在json.cn
但是当我将电话loadUserByUsername
割断时,这是错误的,并且会获得异常
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)
......
我尝试这些方法
@JsonIgnore
,但它将roles
设置为null
,我想使用此字段。
配置杰克逊寄存器模块Hibernate5Module
,它将设置roles
为null
。
使用@Proxy(lazy = false)
,无更改。
使用@ManyToMany(fetch = FetchType.EAGER)
,无更改
config
spring:
jpa:
open-in-view: true
properties
hibernate:
enable_lazy_load_no_trans: true
没有变化...
请帮助我,我花了三天时间...但是我没有解决这个问题...
谢谢!
github地址:XIAOMING
如果没有解决方法,也许我必须使用Mybatis。但是还有很多工作。请帮助我解决这个问题...
答案 0 :(得分:0)
在您的代码中,您将像这样返回valueSerializer
private RedisSerializer<Object> valueSerializer() {
return new GenericJackson2JsonRedisSerializer();
}
但是您将必须返回带有已将Hibernate5Module或Hibernate4Module注册为模块的Jackson对象映射器的GenericJackson2JsonRedisSerializer
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());
}
答案 1 :(得分:0)
1st。在下面创建2个课程 HibernateCollectionIdResolver.class会将HibernateCollection类转换为JDK集合类,因此Jackson将从
写入json{
"paramA": [
"org.hibernate.collection.internal.PersistentSet",
[]
]
}
到
{
"paramA": [
"java.util.HashSet",
[]
]
}
然后,方法typeFromId将从上面的类全名获取JDK JavaType,以将json反序列化为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 {
}
第二。向您的ObjectMapper注册此MixIn类
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);
最后,将jackson2JsonRedisSerializer注册到RedisCacheConfiguration。
这会有所帮助,我花了2天的时间研究如何解决此问题。 我发现json类型ID可以重写... 所以只要重写杰克逊typeIdResolver〜
编辑:解决反序列化问题并添加一些评论