sprng boot jpa + redis LazyInitializationException

时间:2019-02-16 11:24:55

标签: spring-boot spring-data-jpa spring-data-redis

我使用Spring Boot 2.1.2和Redis作为缓存提供程序。

但是现在,我有一个问题。

  1. 在sysUser实体中
@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;

    // ...
}

  1. 在sysRole实体中

@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<>();

}

  1. 在SysPermission实体中
@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<>();
}

  1. 在sysUser服务中显示
    @Override
    @Cacheable
    public SysUser loadUserByUsername(String username) {
        return sysUserRepository.findFirstByUsernameAndEnabledTrue(username).orElseThrow(() ->
                new UsernameNotFoundException("用户不存在")
        );
    }
  1. redis配置
    @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中

redis

在json.cn

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)
......

exception

其他

我尝试这些方法

  1. @JsonIgnore,但它将roles设置为null,我想使用此字段。

  2. 配置杰克逊寄存器模块Hibernate5Module,它将设置rolesnull

  3. 使用@Proxy(lazy = false),无更改。

  4. 使用@ManyToMany(fetch = FetchType.EAGER),无更改

  5. config

spring:
  jpa:
    open-in-view: true
    properties
      hibernate:
        enable_lazy_load_no_trans: true

没有变化...

  1. 使用其他json工具,例如gson和FastJson,但在保存缓存时对jpa进行无限循环。

请帮助我,我花了三天时间...但是我没有解决这个问题...

谢谢!

github地址:XIAOMING

如果没有解决方法,也许我必须使用Mybatis。但是还有很多工作。请帮助我解决这个问题...

2 个答案:

答案 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〜

编辑:解决反序列化问题并添加一些评论