SPRING JPA Ленивая загрузка данных для использования в другом классе - PullRequest
0 голосов
/ 02 мая 2019

Я использую Spring Boot для проекта, я застрял с отложенной загрузкой.

Что я хочу сделать, это загрузить данные в мой контроллер, а затем отправить в презентабельный объект, который будет извлекать необходимую информацию, а сериализатор JSON плохо выполнит работу по созданию моего пользовательского HTTP-ответа.

проблема возникает, когда класс UserPresentation вызывает средство получения папки, ошибка общеизвестна: could not initialize proxy - no Session.

Конечно, выборка по умолчанию для папки LAZY, и я хочу этого, но я не знаю, как подготовить объект для использования в презентации. Я скопировал и вставил только папку, выбранную как четкую и короткую, но у меня больше коллекции внутри класса User, все они создают мне одну и ту же проблему.

Я знаю, что мог бы вызвать getter в контроллере просто для инициализации Коллекций, но я нахожу это жестким кодированием, на самом деле, если я хочу добавить что-то в презентабельный режим, мне тоже нужно сделать это в контроллере.

Я тоже пытался с @Transactional, но не работает.

Вот мой класс:

@Entity
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "USER_ID")
    private Integer id;

    @Column(unique = true)
    private String email;

    private String password;

    @Enumerated(EnumType.STRING)
    private Authority userAuthority;

    @OneToMany(mappedBy = "owner", cascade = CascadeType.ALL)
    private Set<Folder> ownFolders = new HashSet<>();

   ... getter setter
}
@RestController
public class UserController {

    @GetMapping(value = "/api/user", produces = APPLICATION_JSON_VALUE)
    public CustomResponseEntity userInfo() {
        User currentUser = loginService.getCurrentUser();
        UserPresentation userPresentation = new UserPresentation(currentUser);
        return ResponseManager.respondData(userPresentation);
    }
}
public class UserPresentation implements Presentable {

    private User user;

    public UserPresentation(User user) {
        this.user = user;
    }

    public Integer getId() {
        return user.getId();
    }

    public String getEmail() {
        return user.getUsername();
    }

    public String getAuthority() {
        return user.getUserAuthority().name();
    }

    public boolean isEnabled() {
        return user.isEnabled();
    }

    public Integer getOwnFolders() {
        Set<Folder> folderList = user.getOwnFolders();
        if (folderList == null)
            return 0;
        return folderList.size();
    }

}

Последние два, чтобы прояснить ситуацию

public class ResponseManager {
 // DATA
    public static ResponseEntity respondData(Presentable presentable, String token) {
        CustomResponse response = new DataResponse<>(presentable);
        return new ResponseEntity<>(response, HttpStatus.OK);
    }
}
public class DataResponse<T extends Presentable> extends CustomResponse {

    private T data;

    public T getData() {
        return data;
    }

    private void setData(T data) {
        this.data = data;
    }

    public DataResponse(T data) {
        this.setData(data);
    }

    @Override
    public String getType() {
        return DATA;
    }
}

Ответы [ 3 ]

0 голосов
/ 02 мая 2019

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

Практически проблема возникает во время сериализации JSON, которая запускается вне моего элемента управления (где-то внутри классов Spring непосредственно перед отправкой HTTP-ответа), поэтому я вручную сериализовал каждый Presentable объект внутри блока @Transactional сразу после его создания.

Это измененные классы:

public class UserPresentation implements Presentable {

    private User user;

    public UserPresentation(User user) {
        this.user = user;
        this.initialize() //ADDED (called here and in every other class that implements Presentable)
    }
    ...getter and setter (which I want as JSON fields)
}
@RestController
public class UserController {

    @Transactional  //ADDED
    @GetMapping(value = "/api/user", produces = APPLICATION_JSON_VALUE)
    public CustomResponseEntity userInfo() {
        User currentUser = loginService.getCurrentUser();
        UserPresentation userPresentation = new UserPresentation(currentUser);
        return ResponseManager.respondData(userPresentation);
    }
}

До этого исправления интерфейс использовался только для использования Polymorfism внутри ResponseManager, поэтому он был пустым

public interface Presentable {

    default void initialize() {
        try {
            new ObjectMapper().writeValueAsString(this);
        } catch (JsonProcessingException e) {
            throw new RuntimeJsonMappingException(e.getMessage());
        }
    }
}
0 голосов
/ 03 мая 2019

Я бы предложил вам использовать https://github.com/FasterXML/jackson-datatype-hibernate

Модуль поддерживает типы данных Hibernate версий 3.x, 4.x и 5.x; а также некоторые связанные с этим поведения, такие как отложенная загрузка и обнаружение переходных процессов (аннотация @Transient).

Он знает, как обрабатывать отложенную загрузку после закрытия сеанса, он пропустит преобразование json для объектов, помеченных как отложенное извлечение при выходе из сеанса

<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-hibernate5</artifactId>
    <version>2.9.8</version>
</dependency>


ObjectMapper mapper = new ObjectMapper();
// for Hibernate 4.x:
mapper.registerModule(new Hibernate4Module());
// or, for Hibernate 5.x
mapper.registerModule(new Hibernate5Module());
// or, for Hibernate 3.6
mapper.registerModule(new Hibernate3Module());



@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

        /*
         * Here we register the Hibernate4Module into an ObjectMapper, then set this            * custom-configured ObjectMapper to the  MessageConverter and return it to be          * added to the HttpMessageConverters of our application
         */
    public MappingJackson2HttpMessageConverter jacksonMessageConverter() {
        MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter();

       ObjectMapper hibernateAwareObjectMapper  = new ObjectMapper();
       hibernateAwareObjectMapper.enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS);
       hibernateAwareObjectMapper.enable(SerializationFeature.FAIL_ON_EMPTY_BEANS);

       // Registering Hibernate5Module to support lazy objects
       hibernateAwareObjectMapper.registerModule(new Hibernate5Module());

       messageConverter.setObjectMapper(hibernateAwareObjectMapper);
       return messageConverter;
   }
}

XML config

<mvc:annotation-driven>
        <mvc:message-converters>
            <!-- Use the HibernateAware mapper instead of the default -->
            <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
                <property name="objectMapper">
                    <bean class="path.to.your.HibernateAwareObjectMapper" />
                </property>
            </bean>
        </mvc:message-converters>
    </mvc:annotation-driven>
0 голосов
/ 02 мая 2019

Я полагаю, вы загружаете текущего пользователя из базы данных с помощью:

User currentUser = loginService.getCurrentUser();

, а метод getCurrentUser() является транзакционным. Вы можете либо:

  • Используйте JPQL следующим образом:

"select u from User u join fetch u.ownFolders where ... " для загрузки информации о пользователе (таким образом ownFolders отношение извлекается с нетерпением)

или

  • Просто вызовите user.getOwnFolders () внутри getCurrentUser() для запуска выборка.
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...