Spring Boot JPA + Redis LazyInitializationException

Я использую 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. редис конфиг
    @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

Spring Boot JPA + Redis LazyInitializationException

в json.cn

Spring Boot JPA + Redis LazyInitializationException

Но когда я второй раз звоню 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)
......

Spring Boot JPA + Redis LazyInitializationException

Другой

Я пробую эти методы

  1. @JsonIgnore , но будет установлено roles равно null, я хочу использовать это поле.

  2. Настройте jackson registerModule Hibernate5Module, он установит roles значение null.

  3. Используйте @Proxy(lazy = false), никаких изменений.

  4. Используйте @ManyToMany(fetch = FetchType.EAGER), без изменений

  5. конфигурация

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

без изменений...

  1. Используйте другие инструменты json, такие как gson и FastJson, но бесконечный цикл для jpa при сохранении кеша.

Помогите пожалуйста, три дня пролежал... Но я не решаю этот вопрос...

Спасибо!

адрес гитхаба: СЯОМИН

Если у меня нет метода разрешения, возможно, я должен использовать Mybatis. Но работы много. Пожалуйста, помогите мне решить этот вопрос...

Привет, у меня такая же проблема github.com/ripper2hl/sepomex/tree/redis-кэш

Israel Perales 23.06.2019 01:05
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Версия Java на основе версии загрузки
Версия Java на основе версии загрузки
Если вы зайдете на официальный сайт Spring Boot , там представлен start.spring.io , который упрощает создание проектов Spring Boot, как показано ниже.
Документирование API с помощью Swagger на Springboot
Документирование API с помощью Swagger на Springboot
В предыдущей статье мы уже узнали, как создать Rest API с помощью Springboot и MySql .
4
1
1 796
2

Ответы 2

В вашем коде вы возвращаете 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~

Обновлено: решить проблему десериализации и добавить комментарии

Хотя этот код может решить вопрос, включая объяснение того, как и почему это решает проблему, действительно поможет улучшить качество вашего сообщения и, вероятно, приведет к большему количеству голосов. Помните, что вы отвечаете на вопрос для будущих читателей, а не только для того, кто задает сейчас. Пожалуйста, редактировать ваш ответ, чтобы добавить пояснения и указать, какие ограничения и предположения применяются.

rizerphe 19.06.2020 16:05

@БогданОпир хорошо, и есть также некоторые проблемы с десериализацией, которые нужно исправить ...

beijiaxu 20.06.2020 04:42

@beijiaxu Это работает для меня так прекрасно, не могу больше благодарить вас

Mohit Sharma 19.05.2021 09:26

Другие вопросы по теме

Похожие вопросы

Как лучше всего имитировать тестовые платежи с использованием фактического вызова API в JAVA
Получение загрузки файла вместо перенаправления на URL-адрес по умолчанию после успешного входа в систему
Получить и изменить содержимое списка объектов с помощью REST-запросов
Правильно сохраните все поля в MySQL
Ошибка Spring-boot при запуске контекста tomcat. Ошибка создания bean-компонента с именем 'servletEndpointRegistrar'
Диспетчер Tomcat ответил: «ОШИБКА — приложение развернуто по контекстному пути
Spring Boot: Hibernate продолжает вызывать сложные запросы внешнего соединения
Проблемы с отправкой приложения с помощью облачного литейного производства. каталог lib не найден
Как правильно спроектировать «добавление объекта в список», используя springboot с тимелеафом
Java.time.format.DateTimeParseException: текст «2019-02-16 09:29:32.959» не может быть проанализирован, не проанализированный текст найден в индексе 10