Как весенняя транзакция работает в этом примере и как разрешить LazyInitializationException? - PullRequest
0 голосов
/ 26 апреля 2018

Я получаю исключение org.hibernate.LazyInitializationException . Что я знаю, так это то, что проблема вызвана тем, что я лениво выбрал объект профиля в пользовательской сущности, и перед инициализацией прокси-объекта сеанс закрывается.

Закрыт ли сеанс после выполнения метода findAll ()? Есть ли какая-либо другая опция для выполнения метода findAll () и синтаксического анализа внутри одной и той же транзакции вместо анализа внутри метода findAll ()?

Я только хотел узнать, как работает пружинная транзакция, когда вызывается метод findAll из сервиса и более поздний метод анализа класса UserUtils?

Я также обнаружил, что использование распространения в аннотации @Transactional поможет. Будет ли он? Пожалуйста, дайте мне понять.

Теперь давайте посмотрим несколько кодов.

User.java

package com.technep.test.entity;

import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OneToOne;
import javax.persistence.Table;
import lombok.Getter;
import lombok.Setter;

@Entity
@Getter
@Setter
@Table(name="user")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;

private String name;

private String fatherName;

private String motherName;

@OneToOne(fetch=FetchType.LAZY)
@JoinColumn(name="user_profile_id")
private Profile profile;
}

Profile.java

@Entity
@Table(name="profile")
@Getter
@Setter
public class Profile {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;

@Column(name="profile_name")
private String name;

@Column(name="created_date")
private Date createdDate;

@Column(name="last_modified_date")
private Date lastmodifiedDate;

}

UserController.java

@RestController
public class UserController {

@Autowired
private UserService userService;


@GetMapping(value = "/api/users")
public ResponseEntity<List<UserResponseDTO>> getListOfUsers(){
    List<User> users = userService.findAll();
    List<UserResponseDTO> responseDTOs =UserUtils.parseUserToDTO(users);
    return new ResponseEntity<List<UserResponseDTO>>(responseDTOs,HttpStatus.OK);
}
}

UserService.java

public interface UserService {

User findById(Integer id);

List<User> findAll();

}

UserRepository.java

public interface UserRepository extends JpaRepository<User, Integer>{

}

UserServiceImpl.java

@Service
@Transactional
public class UserServiceImpl implements UserService{


@PersistenceContext
private EntityManager entityManager;

@Autowired
private UserRepository repository;

@Override
public User findById(Integer id) {
    return repository.findOne(id);
}

@Override
public List<User> findAll() {
   return repository.findAll();
}

}

UserUtils.java

public class UserUtils {

public static List<UserResponseDTO> parseUserToDTO(List<User> users) {

    List<UserResponseDTO> responseDTOs  = new ArrayList<>();

    users.forEach(user -> {
        UserResponseDTO responseDTO = new UserResponseDTO();
        responseDTO.setId(user.getId());
        responseDTO.setName(user.getName());
        responseDTO.setProfileName(user.getProfile().getName());
        responseDTO.setProfileCreatedDate(user.getProfile().getCreatedDate());
        responseDTOs.add(responseDTO);
    });

    return responseDTOs;
}

}

Исключение составляет:

org.hibernate.LazyInitializationException: could not initialize proxy - no Session
at org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:147)
at org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyInitializer.java:260)
at org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer.invoke(JavassistLazyInitializer.java:68)
at com.technep.test.entity.Profile_$$_jvst12b_0.getName(Profile_$$_jvst12b_0.java)
at com.technep.test.utils.UserUtils.lambda$parseUserToDTO$0(UserUtils.java:27)
at java.util.ArrayList.forEach(ArrayList.java:1257)
at com.technep.test.utils.UserUtils.parseUserToDTO(UserUtils.java:23)
at com.technep.test.controller.UserController.getListOfUsers(UserController.java:24)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:205)
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:133)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:97)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:827)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:738)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:967)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:901)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:970)
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:861)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:635)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:846)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:742)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:198)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:496)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:140)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:81)
at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:650)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:87)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:342)
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:803)
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:790)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1459)
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:748)

Ответы [ 4 ]

0 голосов
/ 26 апреля 2018

Лучший способ решить эту проблему - извлечь List<UserResponseDTO> непосредственно из БД без предварительной выборки сущностей, а затем преобразовать их.

В вашем хранилище создайте метод:

@Query("select new com.company.UserResponseDTO(u.id, u.name, u.profile.name, u.profile.createdDate) from User u")
List<UserResponseDTO> findAllAsDTO();

Убедитесь, что у вашего UserResponseDTO есть соответствующий конструктор, который принимает эти аргументы в следующем порядке.

Это самый эффективный способ. Это экономит память, не выбирая объекты, которые используют дополнительную память для грязной проверки. Кроме того, это более масштабируемо, поскольку, если кто-то добавляет атрибуты к сущности «Пользователь» или «Профиль», он не влияет на этот запрос. Наконец, это также меньше кода для записи.

Я рекомендую этот подход.

Этот метод называется JPQL Constructor Expression .

0 голосов
/ 26 апреля 2018

Вы пытаетесь лениво загрузить profile вне транзакции - отсюда и исключение.

Чтобы решить эту проблему, вы можете сопоставить свою сущность с DTO внутри UserService реализации. Это кажется наиболее подходящим решением в вашем случае.

Вы также можете получить profile с нетерпением или пометить метод контроллера как @Trasnational, но это не очень хорошая идея, поскольку контроллер не содержит никакой бизнес-логики.

0 голосов
/ 26 апреля 2018

Мне любопытно, почему у вас OneToOne на ленивой загрузке? Типичные OneToOnes не установлены таким образом. Ленивая загрузка - это шаблон проектирования, который используется для отсрочки инициализации объекта, насколько это возможно, и обычно используется при получении списков. В Hibernate транзакция обычно закрывается, когда пользователь пытается получить ресурс. Я бы предложил удалить Lazy Fetch и попробовать так.

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

Вы можете написать запрос в UserRepository, чтобы получить ваши профили примерно так:

@Query("SELECT user FROM User user JOIN FETCH user.profile")
List<User> findAll();

Это переопределит встроенный findAll и получит список с уже загруженными для вас профилями пользователей.

Еще одна вещь, которую вы можете сделать, это создать UserProfileRepository и получить профиль, как только вы получите от пользователя что-то вроде:

public interface UserProfileRepository extends JpaRepository<UserProfile, Integer>{
  Profile findById(Integer id);
}

Тогда в вашем DTO вы можете сделать что-то вроде этого:

users.forEach(user -> {
    UserResponseDTO responseDTO = new UserResponseDTO();
    responseDTO.setId(user.getId());
    responseDTO.setName(user.getName());

    Profile profile = profileRepository.findById(user.getProfileId());        

    responseDTO.setProfileName(profile.getName());
    responseDTO.setProfileCreatedDate(profile.getCreatedDate());
    responseDTOs.add(responseDTO);
});
0 голосов
/ 26 апреля 2018

Закрыт ли сеанс после выполнения метода findAll ()?

Да. По умолчанию любое действие в хранилище является атомарным.

Есть ли другая опция для выполнения метода findAll () и анализа внутри та же транзакция, а не анализ внутри метода findAll ()?

Да. Если у вас есть @Transactional вокруг метода, выполняющего оба действия. Транзакция будет оставаться открытой до тех пор, пока не вернется этот метод.

Я только хотел узнать, как работает пружинная транзакция, когда метод findAll из службы и позже вызывается метод разбора класса UserUtils?

У вас есть несколько вариантов.

  1. Добавление аннотации @Transactional getListOfUsers на контроллере должно работать.

  2. Однако вы можете рассмотреть возможность использования метода службы, который одновременно ищет всех пользователей и создает ответ. Вместо этого вы хотели бы поставить @Transactional на этот метод.

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

Я бы сказал, что 2-й или 3-й вариант предпочтительнее первого.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...