Я использую 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, я хочу использовать это поле.
Настройте jackson registerModule Hibernate5Module, он установит roles значение null.
Используйте @Proxy(lazy = false), никаких изменений.
Используйте @ManyToMany(fetch = FetchType.EAGER), без изменений
конфигурация
spring:
jpa:
open-in-view: true
properties
hibernate:
enable_lazy_load_no_trans: true
без изменений...
Помогите пожалуйста, три дня пролежал... Но я не решаю этот вопрос...
Спасибо!
адрес гитхаба: СЯОМИН
Если у меня нет метода разрешения, возможно, я должен использовать Mybatis. Но работы много. Пожалуйста, помогите мне решить этот вопрос...




В вашем коде вы возвращаете valueSerializer вот так
private RedisSerializer<Object> valueSerializer() {
return new GenericJackson2JsonRedisSerializer();
}
Но вам придется вернуть GenericJackson2JsonRedisSerializer с картографом объектов Jackson, в котором Hibernate5Module или Hibernate4Module зарегистрированы как модуль.
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-й. создать 2 класса ниже HibernateCollectionIdResolver.class преобразует класс HibernateCollection в класс коллекции JDK, поэтому Джексон будет писать 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 {
}
2-й. зарегистрируйте этот класс MixIn для вас 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);
наконец, зарегистрируйте свой jackson2JsonRedisSerializer в файле RedisCacheConfiguration.
Это было бы полезно, я потратил 2 дня на изучение того, как решить эту проблему. И я обнаружил, что идентификатор типа json можно переписать... Так что просто переопределите jackson typeIdResolver~
Обновлено: решить проблему десериализации и добавить комментарии
Хотя этот код может решить вопрос, включая объяснение того, как и почему это решает проблему, действительно поможет улучшить качество вашего сообщения и, вероятно, приведет к большему количеству голосов. Помните, что вы отвечаете на вопрос для будущих читателей, а не только для того, кто задает сейчас. Пожалуйста, редактировать ваш ответ, чтобы добавить пояснения и указать, какие ограничения и предположения применяются.
@БогданОпир хорошо, и есть также некоторые проблемы с десериализацией, которые нужно исправить ...
@beijiaxu Это работает для меня так прекрасно, не могу больше благодарить вас
Привет, у меня такая же проблема github.com/ripper2hl/sepomex/tree/redis-кэш