3

I'm moving away from the Play Framework to Spring Boot and I've run into a couple of issues that I'm having a hard time understanding.

Consider these simple Entities:

@Entity
@Table(name = "system")
public class System {

  @Id
  @Column(name = "systemid", unique = true, nullable = false, length = 36)
  public String systemid;

  @ManyToOne(fetch = FetchType.LAZY, optional=false)
  @JoinColumn(name = "systemtypeid", nullable = false)
  public Systemtype systemtype;

  //This column is added just for testing purposes, see comments below
  @Column(name = "systemtypeid", insertable=false, updatable=false)
  public String systemtypeid;

  @OneToMany(fetch = FetchType.LAZY, mappedBy = "system")
  public Set<SystemItem> items = new HashSet<SystemItem>(0);
}


@Entity
@Table(name = "systemtype")
public class Systemtype {

  @Id
  @Column(name = "systemtypeid", unique = true, nullable = false, length = 36)
  @Access(AccessType.PROPERTY)
  public String systemtypeid;

  public String getSystemtypeid() {
    return systemtypeid;
  }

  public void setSystemtypeid(String systemtypeid) {
    this.systemtypeid = systemtypeid;
  }

  @Column(name = "name", length = 60)
  public String name;

  @OneToMany(fetch = FetchType.LAZY, mappedBy = "systemtype")
  public Set<System> systems = new HashSet<System>(0);
  }

@Entity
@Table(name = "systemitem")
public class SystemItem {

  @Id
  @Column(name = "systemitemid", unique = true, nullable = false, length = 36)
  public String systemitemid;

  @Column(name = "name", length = 60)
  public String name;

  @ManyToOne(fetch = FetchType.LAZY)
  @JoinColumn(name = "systemid", nullable = false)
  public System system;
}

The controller:

    @RestController
    @RequestMapping(value = ApiController.SYSTEM_URL)
    public class SystemController extends ApiController {

      private static final Logger log = LoggerFactory.getLogger(SystemController.class);

      @Autowired
      private SystemService systemService;

      @RequestMapping(method = RequestMethod.GET)
      public Collection<System> getSystems() throws Exception {
        List<System> systems = systemService.getSystems();
        return systems;
      }
}

And finally the service:

@Service
@Transactional(readOnly = true, value = "tmPrimary")
public class SystemService {
  private static final Logger log = LoggerFactory.getLogger(SystemService.class);

  @Autowired
  SystemRepository systemRepository; //Spring-Data's PagingAndSortingRepository

  public List<System> getSystems() {
    return Lists.newArrayList(systemRepository.findAll());
  }
}

So when the getSystems() method is called in the controller I was expecting to get a list with all Systems with only the basic fields filled since everything else is lazy loaded. And that's what happens, also checked the Hibernate query and the only table being queried is indeed the System table. So far so good.

But my first problem is that I was also expecting that system.systemtype.systemtypeid would be filled since this is a foreign key in the system table, but this is always null. If I was using EclipseLink I believe this would be the expected behavior, but it shouldn't be like that with Hibernate. I added a dummy column to the System object (systemtypeid) for verification purposes and this indeed gets filled. So: system.systemtypeid = "Something" system.systemtype.systemtypeid = null

I think both of these should be "Something", so am I missing something here?

I figured this out meanwhile, Hibernate will only fetch the foreign key IDs if you annotate it to use property level access. This is stated here: https://developer.jboss.org/wiki/HibernateFAQ-TipsAndTricks#jive_content_id_How_can_I_retrieve_the_identifier_of_an_associated_object_without_fetching_the_association

I don't get this behavior when using Play, I'm now guessing Play might do some mumbo jumbo behind scenes to generate those.

The second problem starts once the data is being serialized to JSON in order to be sent to the client. For starters I would expecting to be getting lazy intialization exceptions since the conversion is being done inside the controller and the transaction annotated method is on the service. Surprisingly that doesn't happen and the items set is lazily loaded and all items are serialized just fine to JSON. Again can't understand this behavior, why is lazy load working at all when this is being done inside the controller? Does Spring Boot open a session at the start of the controller methods?

Also got this figured out (thanks to Nico for pointing me into the right direction), Spring Boot is enabling OpenSessionInView by default (really bad idea I must say) so I had to add spring.jpa.open-in-view=false to my applications.properties.

More surprisingly is that system.systemtype will still be empty after this ie won't lazy load at all, the only way I can get system.systemtype to load is if I declare it as eager.

I might be missing something obvious here, but I'm having a hard time understanding this behavior which is completely different from what I was experiencing when using Play and I would assume the behavior should be exactly the same.

UPDATE AFTER ALL EDITS:

The only remaining question is why system.systemtype will never lazy load. I did some tests and it will only lazy load IF I add getters/setters for all fields of the Systemtype entity. Is this normal?

Looking at the Hibernate docs it now seems it might be: https://docs.jboss.org/hibernate/orm/3.3/reference/en/html/performance.html

" By default, Hibernate3 uses lazy select fetching for collections and lazy proxy fetching for single-valued associations. These defaults make sense for most associations in the majority of applications. "

" Proxy fetching: a single-valued association is fetched when a method other than the identifier getter is invoked upon the associated object. "

So does this mean ALL entities used in single valued associations will need to have getters and setters defined for all their fields if I ever want to lazy load them? This sounds very confusing as it works in a different way on all other scenarios.

On Play I never needed to use getters/setters at all, so maybe Play was chaning the default fetching strategies for single valued associations? Is there even a way to do this?

3
  • You set systemType to be lazy loaded fetch = FetchType.LAZY , that 's why it is null. can you post your hibernate configuration ? Commented Nov 17, 2015 at 10:11
  • Did you try with private attributes and getters/setters ? I don't know how Hibernate manage public attributes AND lazy loading. Commented Nov 17, 2015 at 13:25
  • Hibernate doesn't need getters/setters. As you can see it's getting all systemItems just fine. Commented Nov 17, 2015 at 14:14

2 Answers 2

0

If you set fetch type to lazy, you'll get a null object, until you try to access it then it's populated, this is the behavior in spring, I'm not sure about other platforms.

If you want to keep your foreign entity as lazy and you still want to access the foreign id alone, then you'll have to add a dummy property to your Entity like so:

@Column(name = "system_type_id", insertable = false, updatable = false)
public Integer systemTypeId;

About serializing lazy loaded objects, I'm not exactly sure to be honest but I guess when Jackson tries to serialize the object, it calls the getters and in turn populates the lazy objects, I have no idea why after serialization lazy-loaded objects are still null, it doesn't make sense, if the data was already fetched then the object should be populated.

Sign up to request clarification or add additional context in comments.

4 Comments

It's the behaviour of Hibernate or the used JPA implementation, it has nothing to do with Spring.
I understand the lazy loaded mechanism very well that's not what I'm asking. I don't think you read my post fully because that field you are suggesting me to add is already there. This extra field shouldn't be required by Hibernate anyway unless something changed meanwhile, because I'm using Hibernate with Play and the foreign keys always get filled in the systemtype object. It's required by EclipseLink though.
Ok sorry for the lack of help then :) Hopefully someone more knowledgeable will shed some light onto the matter soon. If you see here stackoverflow.com/questions/24526216/… , this is been the case since a year. It would be neat if it worked like you described it however, accessing the id of a lazy object through lazyobject.id. good luck
Edited my first post as I figured the SystemType problem, still scratching my head around the second one though.
0

For your "second problem" about JSON well filled, maybe are you using OpenSessionInView filter ? http://docs.spring.io/spring/docs/2.5.x/api/org/springframework/orm/hibernate3/support/OpenSessionInViewFilter.html

2 Comments

No, I'm not using it. Unless this is added under the hood by Spring Boot?
Seems that's the case: stackoverflow.com/questions/31165326/spring-boot-transactional I think it's a really bad idea for Spring Boot to have this enabled by default though.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.