在我们正在开发的这个应用程序中,我们注意到一个视图特别慢。我对视图进行了分析,并注意到hibernate执行的一个查询花费了10秒,即使数据库中只有两个对象需要获取。所有“一对多”和“多对多”关系都是懒惰的,所以这不是问题所在。在检查实际执行的SQL时,我注意到查询中有超过80个连接。
进一步检查这个问题,我注意到这个问题是由实体类之间一对一和多对一关系的深层层次结构引起的。所以,我想,我只要把它们设为lazy,就能解决问题了。但是注释@OneToOne(fetch=FetchType.LAZY)或@ManyToOne(fetch=FetchType.LAZY)似乎都不起作用。要么我得到一个异常,要么它们实际上没有被代理对象替换,从而变得懒惰。
你知道我要怎么做吗?注意,我没有使用persistence.xml来定义关系或配置细节,一切都是在java代码中完成的。
这里有一些对我有用的东西(没有仪器):
我没有在两边使用@OneToOne,而是在关系的相反部分(带有mappedBy的部分)使用@OneToMany。这使得属性成为一个集合(在下面的示例中为List),但我将其转换为getter中的一个项,使其对客户端透明。
这种设置工作缓慢,也就是说,只有在调用getPrevious()或getNext()时才进行选择—并且每次调用只有一个选择。
表结构:
CREATE TABLE `TB_ISSUE` (
`ID` INT(9) NOT NULL AUTO_INCREMENT,
`NAME` VARCHAR(255) NULL,
`PREVIOUS` DECIMAL(9,2) NULL
CONSTRAINT `PK_ISSUE` PRIMARY KEY (`ID`)
);
ALTER TABLE `TB_ISSUE` ADD CONSTRAINT `FK_ISSUE_ISSUE_PREVIOUS`
FOREIGN KEY (`PREVIOUS`) REFERENCES `TB_ISSUE` (`ID`);
类:
@Entity
@Table(name = "TB_ISSUE")
public class Issue {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
protected Integer id;
@Column
private String name;
@OneToOne(fetch=FetchType.LAZY) // one to one, as expected
@JoinColumn(name="previous")
private Issue previous;
// use @OneToMany instead of @OneToOne to "fake" the lazy loading
@OneToMany(mappedBy="previous", fetch=FetchType.LAZY)
// notice the type isnt Issue, but a collection (that will have 0 or 1 items)
private List<Issue> next;
public Integer getId() { return id; }
public String getName() { return name; }
public Issue getPrevious() { return previous; }
// in the getter, transform the collection into an Issue for the clients
public Issue getNext() { return next.isEmpty() ? null : next.get(0); }
}
As already perfectly explained by ChssPly76, Hibernate's proxies don't help with unconstrained (nullable) one-to-one associations, BUT there is a trick explained here to avoid to set up instrumentation. The idea is to fool Hibernate that the entity class which we want to use has been already instrumented: you instrument it manually in the source code. It's easy! I've implemented it with CGLib as bytecode provider and it works (ensure that you configure lazy="no-proxy" and fetch="select", not "join", in your HBM).
我认为这是真正的(我是说自动的)插装的一个很好的替代方案,当您只有一个一对一的可空关系时,您想让它变得懒惰。主要的缺点是这个解决方案依赖于你正在使用的字节码提供程序,所以准确地注释你的类,因为你将来可能不得不改变字节码提供程序;当然,您还因为技术原因修改了您的模型bean,这是不好的。
除非您正在使用字节码增强,否则您不能惰性地获取父端@OneToOne关联。
然而,大多数情况下,如果你在子端使用@MapsId,你甚至不需要父端关联:
@Entity(name = "PostDetails")
@Table(name = "post_details")
public class PostDetails {
@Id
private Long id;
@Column(name = "created_on")
private Date createdOn;
@Column(name = "created_by")
private String createdBy;
@OneToOne(fetch = FetchType.LAZY)
@MapsId
private Post post;
public PostDetails() {}
public PostDetails(String createdBy) {
createdOn = new Date();
this.createdBy = createdBy;
}
//Getters and setters omitted for brevity
}
使用@MapsId,子表中的id属性同时充当父表主键的主键和外键。
所以,如果你有一个父Post实体的引用,你可以很容易地获取子实体使用父实体标识符:
PostDetails details = entityManager.find(
PostDetails.class,
post.getId()
);
这样,您就不会有N+1个查询问题,这可能是由父端mappedBy @OneToOne关联引起的。