我正在从Play框架转到Spring Boot,遇到了一些我很难理解的问题。

考虑以下简单实体:

@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;
}


控制器:

    @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;
      }
}


最后是服务:

@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());
  }
}


因此,当在控制器中调用getSystems()方法时,我希望得到一个列表,其中所有系统仅填充基本字段,因为其他所有内容都是延迟加载的。
这就是发生的情况,还检查了Hibernate查询,唯一要查询的表确实是System表。到目前为止,一切都很好。

但是我的第一个问题是,我还期望将填充system.systemtype.systemtypeid,因为这是系统表中的外键,但这始终为null。如果我使用的是EclipseLink,我相信这将是预期的行为,但它与Hibernate并不一样。
我将一个虚拟列添加到System对象(systemtypeid)以进行验证,并且确实填充了该列。所以:
system.systemtypeid =“东西”
system.systemtype.systemtypeid = null

我认为这两个都应该是“某物”,所以我在这里错过了什么吗?

同时,我想出了这一点,如果您将其注释为使用属性级别访问权限,Hibernate将仅获取外键ID。声明如下:
https://developer.jboss.org/wiki/HibernateFAQ-TipsAndTricks#jive_content_id_How_can_I_retrieve_the_identifier_of_an_associated_object_without_fetching_the_association

使用Play时,我没有这种行为,我现在猜测Play可能会在幕后做一些巨大的事情来生成这些。

一旦将数据序列化为JSON以便发送到客户端,第二个问题就开始了。
对于初学者,由于转换是在控制器内部完成的,并且事务注释的方法在服务上,因此我希望获得惰性初始化例外。令人惊讶的是,这种情况不会发生,并且懒惰地加载了项集,并且所有项都被序列化为JSON。
再次无法理解这种行为,为什么在控制器内部完成延迟加载后,延迟加载却无法正常工作? Spring Boot是否在控制器方法开始时打开会话?

还弄清楚了这一点(感谢Nico为我指出了正确的方向),Spring Boot默认情况下启用了OpenSessionInView(我必须说的真的是个坏主意),因此我必须在其中添加spring.jpa.open-in-view = false我的applications.properties。

更令人惊讶的是,此之后system.systemtype仍然为空,即根本不会延迟加载,我可以将system.systemtype加载的唯一方法是将其声明为渴望的。

我在这里可能会遗漏一些明显的东西,但是我很难理解这种行为,这与我使用Play时所经历的完全不同,我认为这种行为应该是完全相同的。

所有编辑后更新:

唯一剩下的问题是为什么system.systemtype永远不会延迟加载。我做了一些测试,如果我为Systemtype实体的所有字段添加getters / setters,它只会延迟加载。这正常吗?

查看Hibernate文档,现在看来可能是:
https://docs.jboss.org/hibernate/orm/3.3/reference/en/html/performance.html

“默认情况下,Hibernate3对集合使用延迟选择获取,对单值关联使用延迟代理获取。对于大多数应用程序中的大多数关联而言,这些默认设置有意义。”

“代理获取:在关联对象上调用除标识符getter之外的方法时,将获取单值关联。”

那么这是否意味着如果我想延迟加载它们,则在单值关联中使用的所有实体都需要为其所有字段定义getter和setter吗?这听起来很令人困惑,因为它在所有其他情况下都以不同的方式工作。

在Play上,我根本不需要使用getter / setter,所以Play可能正在为单值关联更改默认的获取策略?有没有办法做到这一点?

最佳答案

如果将fetch type设置为lazy,则会得到一个空对象,直到尝试访问它为止,然后填充它,这是spring的行为,我不确定其他平台。

如果您想让外部实体保持惰性,并且仍然想单独访问外部ID,则必须向Entity中添加一个虚拟属性,如下所示:

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


关于序列化延迟加载的对象,我不确定是否诚实,但是我想杰克逊尝试序列化对象时,它会调用吸气剂,然后填充延迟对象,我不知道为什么在序列化延迟加载的对象之后仍然为null,这没有意义,如果已经获取了数据,则应该填充该对象。

07-24 13:49