1
votes

I'm in the middle of migrating a project from:

  • Spring 4.2.1 -> 5.0.0
  • Spring Data Gosling -> Kay
  • Hibernate 4.3.8 -> 5.8.0

And I'm running getting "org.hibernate.LazyInitializationException: could not initialize proxy - no Session" when accessing an object coming from my database in a controller method.

Here's a stripped down version of my code:

// CustomUser.java
@Entity
@Access(AccessType.FIELD)
@Table(name = "users")
public class CustomUser implements Serializable {
    ...
    @Id
    @GeneratedValue//details omitted
    @GenericGenerator//details omitted
    @Column(name = "id", insertable = true, updatable = true, unique = true, nullable = false)
    private Long id;

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

    public String getName() { return name; }
}

// UserController.java
@RequestMapping(value = "/user/{userId}/", method = RequestMethod.GET)
public String showUser(@PathVariable("userId") CustomUser user) {
    System.out.println("user name is [" + user.getName() + "]");
    return "someTemplate";
}

// UserService.java
@Service
public class UserServiceImpl implements UserService {
    @Autowired UserRepository userRepository;

    @Override
    public User findUserById(Long userId) {
        return userRepository.getOne(userId);
    }
}

// UserRepository.java
public interface UserRepository extends JpaRepository<CustomUser, Long> { }

// UserConverter.java
@Component
public class UserConverter implements Converter<String, CustomUser> {
    @Autowired UserService userService;

    @Override
    public CustomUser convert(String userId) {
        CustomUser user = userService.findUserById(SomeUtilClass.parseLong(userId));
        return user;
    }
}

There's also a @Configuration WebMvcConfigurerAdapter class that autowires a UserConverter instance and adds it to a FormatterRegistry.

Prior to starting this upgrade, I could hit: http://server:port/user/123/

and Spring would take the "123" string, the UserConverter::convert method would fire and hit the Postgres database to look up a user with that id, and I'd get back a CustomUser object in my controller's "showUser" method.

But, now I am getting the org.hibernate.LazyInitializationException. This is occurring when I attempt to print out the user's name in the "showUser" method - or even just "println(user)" without accessing a field.

Most of the info I've been able to turn up from searching suggests that this exception comes from having an object having a lazily loaded collection of sub objects (like if my CustomUser had a collection of Permission objects or something that mapped to a different database table). But in this case I'm not even doing that, this is just a field on the object.

My best guess at the moment is this is due to some kind of hibernate session being terminated after the Converter does its work, so then back in the controller I don't have a valid session. (although again, I don't know why the CustomUser object is coming back unusable, I'm not attempting to fetch a subcollection).

I have added the Hibernate annotation "@Proxy(lazy = false)" to my CustomUser.java and if I do that the problem goes away. But, I'm not sure this is a good solution - for performance reasons, I really don't think I want to go down the road of eagerly fetching EVERYTHING.

I've also tried annotating various things (the service method, the controller method, etc.) with @Transactional; I haven't gotten that to work but I am still reasonably new to Spring and I may be trying that in the wrong place or misunderstanding what that should do.

Is there a better way to handle this than just "@Proxy(lazy = false)" on all of my Entity classes?

1
As you mentioned - this exception usually appears when your table is joined with another one. Do you have any other fields in the CustomUser? I'm not really aware of hibernate 5 changes, but I don't think that they load main entity fields lazily. - Admit
Yes, there are other fields in CustomUser that would pull from other database tables. But, the one that I am trying to access is coming straight from a column on the users table, not from a join. - Thomas Feiler

1 Answers

8
votes

The immediate problem comes from the use of userRepository.getOne(userId). It is implemented in the SimpleJpaRepository using EntityManager.getReference. This method returns just a Proxy, which only contains its id. And only when a property gets accessed those get lazy loaded. This includes simple properties like name in your case.

The immediate fix is to use findOne which should load all the eager properties of your entity which should include simple properties, so the exception goes away.

Note that this will slightly change the behavior of your converter. The current version will not fail when the id is not existent in the database because it never checks. The new one will obtain an empty Optional and you'll have to convert it to null or throw an exception.

But there is (or might be) a bigger problem hiding: The entity is still detached because in absence of an explicit transaction demarcation the transactions only span single repository calls. So if you later want to access lazy loaded properties, you might get the same problem again. I see various options to consider:

  1. Leave it as it is, being aware that you must not access lazy loaded properties.

  2. Make sure a transaction is started before the converters get invoked.

  3. Mark your controllers with @Transactional and loading the user (again) in the controller.