简体   繁体   English

Spring Boot和Hibernate Lazy Initialization奇怪的行为

[英]Spring Boot and Hibernate Lazy Initialization strange behavior

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. 我正在从Play Framework转到Spring Boot,我遇到了一些我很难理解的问题。

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. 因此,当在控制器中调用getSystems()方法时,我希望得到一个包含所有系统的列表,只填充基本字段,因为其他所有内容都是延迟加载的。 And that's what happens, also checked the Hibernate query and the only table being queried is indeed the System table. 这就是发生的事情,还检查了Hibernate查询,并且查询的唯一表确实是System表。 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. 但我的第一个问题是,我还期望system.systemtype.systemtypeid将被填充,因为这是系统表中的外键,但这始终为null。 If I was using EclipseLink I believe this would be the expected behavior, but it shouldn't be like that with Hibernate. 如果我使用EclipseLink,我相信这将是预期的行为,但它不应该像Hibernate那样。 I added a dummy column to the System object (systemtypeid) for verification purposes and this indeed gets filled. 我为System对象(systemtypeid)添加了一个虚拟列以进行验证,这确实得到了填充。 So: system.systemtypeid = "Something" system.systemtype.systemtypeid = null 所以:system.systemtypeid =“Something”system.systemtype.systemtypeid = null

I think both of these should be "Something", so am I missing something here? 我认为这些都应该是“Something”,所以我在这里错过了一些东西吗?

I figured this out meanwhile, Hibernate will only fetch the foreign key IDs if you annotate it to use property level access. 我同时想到这一点,如果你注释它以使用属性级别访问,Hibernate将只获取外键ID。 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 这在此处说明: 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. 使用Play时我没有这种行为,我现在猜测Play可能会在场景后面做一些mumbo jumbo来生成这些。

The second problem starts once the data is being serialized to JSON in order to be sent to the client. 一旦将数据序列化为JSON以便发送到客户端,第二个问题就开始了。 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. 令人惊讶的是,这种情况并没有发生,并且设置的项目是延迟加载的,并且所有项目都被序列化为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? Spring Boot是否在控制器方法开始时打开会话?

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. 也弄明白了(感谢Nico指出我正确的方向),Spring Boot默认启用OpenSessionInView(我必须说非常糟糕的主意)所以我不得不添加spring.jpa.open-in-view = false我的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. 更令人惊讶的是,system.systemtype在此之后仍然是空的,即根本不会延迟加载,我可以将system.systemtype加载的唯一方法是将其声明为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. 我可能会遗漏一些明显的东西,但是我很难理解这种行为,这与我在使用Play时遇到的情况完全不同,我认为行为应该完全相同。

UPDATE AFTER ALL EDITS: 所有编辑后更新:

The only remaining question is why system.systemtype will never lazy load. 唯一剩下的问题是为什么system.systemtype永远不会延迟加载。 I did some tests and it will only lazy load IF I add getters/setters for all fields of the Systemtype entity. 我做了一些测试,如果我为Systemtype实体的所有字段添加getter / setter,它只会延迟加载。 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 看看Hibernate文档,它现在似乎可能是: 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. " “默认情况下,Hibernate3对集合使用延迟选择提取,对单值关联使用延迟代理提取。这些默认值对大多数应用程序中的大多数关联都有意义。”

" Proxy fetching: a single-valued association is fetched when a method other than the identifier getter is invoked upon the associated object. " “代理提取:当在关联对象上调用除标识符getter之外的方法时,将获取单值关联。”

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? 那么这是否意味着在单值关联中使用的所有实体都需要为所有字段定义getter和setter,如果我想延迟加载它们的话? 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? 在Play上我根本不需要使用getter / setter,所以也许Play正在为单值关联修改默认的提取策略? Is there even a way to do this? 有没有办法做到这一点?

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. 如果你将fetch类型设置为lazy,你将获得一个null对象,直到你尝试访问它然后它被填充,这是春天的行为,我不确定其他平台。

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: 如果你想让你的外来实体变得懒惰并且你仍然想要单独访问外部id,那么你必须向你的实体添加一个虚拟属性,如下所示:

@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. 关于序列化延迟加载的对象,我不完全确定是诚实的,但我想当Jackson尝试序列化对象时,它会调用getter并反过来填充惰性对象,我不知道为什么在序列化延迟加载的对象之后仍然为null,没有意义,如果数据已经被提取,那么应该填充对象。

For your "second problem" about JSON well filled, maybe are you using OpenSessionInView filter ? 对于关于JSON填充的“第二个问题”,您是否正在使用OpenSessionInView过滤器? http://docs.spring.io/spring/docs/2.5.x/api/org/springframework/orm/hibernate3/support/OpenSessionInViewFilter.html http://docs.spring.io/spring/docs/2.5.x/api/org/springframework/orm/hibernate3/support/OpenSessionInViewFilter.html

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM