hibernate脏数据
这是更多新帖子中的第一篇:
去年,我以Devoxx的身份参加了演讲,但是我也参加了关于Hibernate反模式的 Patrycja Wegrzynowicz会议。 在该演示中, Patrycja向我们展示了一种反模式,它使我震惊,因为事实证明它预料到了意外情况。
我们将看到当Hibernate检测到一个肮脏的集合并应该重新创建它时所产生的效果。
让我们从将要使用的模型开始,只有两个与一对多关联相关的类:
@Entity
public class Starship {
private Long id;
@Id @GeneratedValue(strategy=GenerationType.SEQUENCE) public Long getId() {return id;}
public void setId(Long id) {this.id = id;}
private Date launched;
@Temporal(TemporalType.DATE) public Date getLaunched() {return launched;}
public void setLaunched(Date launched) {this.launched = launched;}
private String registry;
@Column(unique=true, nullable=false) public String getRegistry() {return registry;}
public void setRegistry(String registry) {this.registry = registry;}
private StarshipClassEnum starshipClassEnum;
@Enumerated public StarshipClassEnum getStarshipClassEnum() {return starshipClassEnum;}
public void setStarshipClassEnum(StarshipClassEnum starshipClassEnum) {this.starshipClassEnum = starshipClassEnum;}
private AffiliationEnum affiliationEnum;
@Enumerated public AffiliationEnum getAffiliationEnum() {return affiliationEnum;}
public void setAffiliationEnum(AffiliationEnum affiliationEnum) {this.affiliationEnum = affiliationEnum;}
private Physics physics;
@Embedded public Physics getPhysics() {return physics;}
public void setPhysics(Physics physics) {this.physics = physics;}
private List<Officer> officers = new ArrayList<Officer>();
@OneToMany(cascade={CascadeType.ALL}) public List<Officer> getOfficers() {return Collections.unmodifiableList(officers);}
protected void setOfficers(List<Officer> officers) {this.officers = officers;}
public void addOfficer(Officer officer) {
officer.setStarship(this);
this.officers.add(officer);
}
public Starship() {
super();
}
public Starship(String registry) {
setRegistry(registry);
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result
+ ((registry == null) ? 0 : registry.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Starship other = (Starship) obj;
if (registry == null) {
if (other.registry != null)
return false;
} else if (!registry.equals(other.registry))
return false;
return true;
}
}
@Entity
public class Officer {
private Long id;
@Id @GeneratedValue(strategy=GenerationType.SEQUENCE)public Long getId() {return id;}
protected void setId(Long id) {this.id = id;}
private String name;
@Column(unique=true, nullable=false) public String getName() {return this.name;}
public void setName(String name) {this.name = name;}
private SpeciesEnum speciesEnum;
@Enumerated public SpeciesEnum getSpeciesEnum() {return speciesEnum;}
public void setSpeciesEnum(SpeciesEnum speciesEnum) {this.speciesEnum = speciesEnum;}
private PlanetEnum homePlanet;
@Enumerated public PlanetEnum getHomePlanet() {return homePlanet;}
public void setHomePlanet(PlanetEnum homePlanet) {this.homePlanet = homePlanet;}
private AffiliationEnum affiliationEnum;
@Enumerated public AffiliationEnum getAffiliationEnum() {return affiliationEnum;}
public void setAffiliationEnum(AffiliationEnum affiliationEnum) {this.affiliationEnum = affiliationEnum;}
private RankEnum rank;
@Enumerated @NotNull public RankEnum getRank() {return rank;}
public void setRank(RankEnum rank) {this.rank = rank;}
private Starship starship;
@ManyToOne public Starship getStarship() {return starship;}
protected void setStarship(Starship starship) {this.starship = starship;}
public Officer() {
super();
}
public Officer(String name, RankEnum rank) {
setName(name);
setRank(rank);
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Officer other = (Officer) obj;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
}
在上一堂课中,我们应注意三个重点:
- 我们在属性级别而不是字段级别进行注释。
- @ OneToMany和@ ManyToOne使用默认选项( 级联定义除外)
- 星际飞船班的军官getter返回一个不变的列表。
为了测试模型配置,我们将创建一个测试,该测试将创建并保留一个Starship和七个高级管理人员 ,并在不同的Transaction和EntityManager中找到创建的Starship 。
@ContextConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
public class StarshipPersistenceTests {
@Inject
private EntityManagerFactory entityManagerFactory;
@Test
public void testSaveOrderWithItems() throws Exception {
Starship starship = createData();
findStarship(starship);
}
private Starship createData() {
EntityManager entityManager = entityManagerFactory.createEntityManager();
EntityTransaction transaction = entityManager.getTransaction();
transaction.begin();
Physics physics = physics().height(137.5D).length(642.5D)
.power("Wrap reactor").width(467.0D).build();
Calendar launched = Calendar.getInstance();
launched.set(2363, 9, 4);
Starship starship = starship().registry("NCC-1701-D").physics(physics)
.launched(launched.getTime())
.starshipClass(StarshipClassEnum.GALAXY)
.affiliation(AffiliationEnum.STARFLEET).build();
Officer jeanLucPicard = officer().name("Jean-Luc Picard")
.rank(RankEnum.CAPTAIN).affiliation(AffiliationEnum.STARFLEET)
.homePlanet(PlanetEnum.EARTH).speciment(SpeciesEnum.HUMAN)
.build();
starship.addOfficer(jeanLucPicard);
Officer williamRiker = officer().name("William Riker")
.rank(RankEnum.COMMANDER)
.affiliation(AffiliationEnum.STARFLEET)
.homePlanet(PlanetEnum.EARTH).speciment(SpeciesEnum.HUMAN)
.build();
starship.addOfficer(williamRiker);
Officer data = officer().name("Data")
.rank(RankEnum.LIEUTENANT_COMMANDER)
.affiliation(AffiliationEnum.STARFLEET)
.homePlanet(PlanetEnum.OMICRON_THETA)
.speciment(SpeciesEnum.ANDROID).build();
starship.addOfficer(data);
Officer geordiLaForge = officer().name("Geordi La Forge")
.rank(RankEnum.LIEUTENANT)
.affiliation(AffiliationEnum.STARFLEET)
.homePlanet(PlanetEnum.EARTH).speciment(SpeciesEnum.HUMAN)
.build();
starship.addOfficer(geordiLaForge);
Officer worf = officer().name("Worf").rank(RankEnum.LIEUTENANT)
.affiliation(AffiliationEnum.STARFLEET)
.homePlanet(PlanetEnum.QONOS).speciment(SpeciesEnum.KLINGON)
.build();
starship.addOfficer(worf);
Officer beverlyCrusher = officer().name("Beverly Crusher")
.rank(RankEnum.COMMANDER)
.affiliation(AffiliationEnum.STARFLEET)
.homePlanet(PlanetEnum.EARTH).speciment(SpeciesEnum.HUMAN)
.build();
starship.addOfficer(beverlyCrusher);
Officer deannaTroi = officer().name("Deanna Troi")
.rank(RankEnum.COMMANDER)
.affiliation(AffiliationEnum.STARFLEET)
.homePlanet(PlanetEnum.BETAZED).speciment(SpeciesEnum.BETAZOID)
.build();
starship.addOfficer(deannaTroi);
entityManager.persist(starship);
transaction.commit();
entityManager.close();
return starship;
}
private void findStarship(Starship starship) {
EntityManager entityManager = this.entityManagerFactory.createEntityManager();
EntityTransaction transaction = entityManager.getTransaction();
transaction.begin();
System.out.println("Before Find");
Starship newStarship = entityManager.find(Starship.class, starship.getId());
System.out.println("After Find Before Commit");
transaction.commit();
System.out.println("After commit");
entityManager.close();
}
}
现在我们已经创建了这个测试,我们可以运行它并且观察Hibernate控制台的输出。
Hibernate: insert into Starship (affiliationEnum, launched, height, length, power, width, registry, starshipClassEnum, id) values (?, ?, ?, ?, ?, ?, ?, ?, ?)
Hibernate: insert into Officer (affiliationEnum, homePlanet, name, rank, speciesEnum, starship_id, id) values (?, ?, ?, ?, ?, ?, ?)
Hibernate: insert into Officer (affiliationEnum, homePlanet, name, rank, speciesEnum, starship_id, id) values (?, ?, ?, ?, ?, ?, ?)
Hibernate: insert into Officer (affiliationEnum, homePlanet, name, rank, speciesEnum, starship_id, id) values (?, ?, ?, ?, ?, ?, ?)
Hibernate: insert into Officer (affiliationEnum, homePlanet, name, rank, speciesEnum, starship_id, id) values (?, ?, ?, ?, ?, ?, ?)
Hibernate: insert into Officer (affiliationEnum, homePlanet, name, rank, speciesEnum, starship_id, id) values (?, ?, ?, ?, ?, ?, ?)
Hibernate: insert into Officer (affiliationEnum, homePlanet, name, rank, speciesEnum, starship_id, id) values (?, ?, ?, ?, ?, ?, ?)
Hibernate: insert into Officer (affiliationEnum, homePlanet, name, rank, speciesEnum, starship_id, id) values (?, ?, ?, ?, ?, ?, ?)
Hibernate: insert into Starship_Officer (Starship_id, officers_id) values (?, ?)
Hibernate: insert into Starship_Officer (Starship_id, officers_id) values (?, ?)
Hibernate: insert into Starship_Officer (Starship_id, officers_id) values (?, ?)
Hibernate: insert into Starship_Officer (Starship_id, officers_id) values (?, ?)
Hibernate: insert into Starship_Officer (Starship_id, officers_id) values (?, ?)
Hibernate: insert into Starship_Officer (Starship_id, officers_id) values (?, ?)
Hibernate: insert into Starship_Officer (Starship_id, officers_id) values (?, ?)
Before Find Starship By Id
Hibernate: select starship0_.id as id1_0_, starship0_.affiliationEnum as affiliat2_1_0_, starship0_.launched as launched1_0_, starship0_.height as height1_0_, starship0_.length as length1_0_, starship0_.power as power1_0_, starship0_.width as width1_0_, starship0_.registry as registry1_0_, starship0_.starshipClassEnum as starship9_1_0_ from Starship starship0_ where starship0_.id=?
After Find Starship By Id and Before Commit
Hibernate: select officers0_.Starship_id as Starship1_1_2_, officers0_.officers_id as officers2_2_, officer1_.id as id0_0_, officer1_.affiliationEnum as affiliat2_0_0_, officer1_.homePlanet as homePlanet0_0_, officer1_.name as name0_0_, officer1_.rank as rank0_0_, officer1_.speciesEnum as speciesE6_0_0_, officer1_.starship_id as starship7_0_0_, starship2_.id as id1_1_, starship2_.affiliationEnum as affiliat2_1_1_, starship2_.launched as launched1_1_, starship2_.height as height1_1_, starship2_.length as length1_1_, starship2_.power as power1_1_, starship2_.width as width1_1_, starship2_.registry as registry1_1_, starship2_.starshipClassEnum as starship9_1_1_ from Starship_Officer officers0_ inner join Officer officer1_ on officers0_.officers_id=officer1_.id left outer join Starship starship2_ on officer1_.starship_id=starship2_.id where officers0_.Starship_id=?
Hibernate: delete from Starship_Officer where Starship_id=?
Hibernate: insert into Starship_Officer (Starship_id, officers_id) values (?, ?)
Hibernate: insert into Starship_Officer (Starship_id, officers_id) values (?, ?)
Hibernate: insert into Starship_Officer (Starship_id, officers_id) values (?, ?)
Hibernate: insert into Starship_Officer (Starship_id, officers_id) values (?, ?)
Hibernate: insert into Starship_Officer (Starship_id, officers_id) values (?, ?)
Hibernate: insert into Starship_Officer (Starship_id, officers_id) values (?, ?)
Hibernate: insert into Starship_Officer (Starship_id, officers_id) values (?, ?)
After commit
查看在第一次提交(持久对象)和第二次事务提交(查找Starship )期间执行的查询数。 在忽略序列生成器的总数中,我们可以计数22个inserts ,2个selects和1个delete ,当我们仅创建8个对象和1个通过主键查找时就可以了。
此时,让我们检查为什么执行这些SQL查询:
前8个插入是不可避免的。 通过将数据插入数据库需要它们。
接下来的七都需要插入,因为我们已经注释getOfficers财产没有的mappedBy属性。 如果我们仔细查看Hibernate文档,它会指出“在不描述任何物理映射的情况下,将使用具有连接表的单向一对多 ”。
下一组查询甚至更陌生,第一个选择语句是通过id查找Starship,但是我们已经创建的这些数据删除和插入是什么?
在提交期间, Hibernate通过比较对象引用来验证集合属性是否脏。 当一个集合被标记为脏集合时, Hibernate需要重新创建整个集合,甚至包含相同的对象。 在本例中,当我们要招募军官时,我们将返回一个不同的集合实例,具体来说是一个不可修改的列表,因此Hibernate认为军官的集合是肮脏的。
由于使用了联接表,因此应重新创建Starship_Officer表,删除先前插入的元组并插入新的元组(尽管它们具有相同的值)。
让我们尝试解决此问题。 我们首先映射一个双向的一对多关联,并以多对一的一方为拥有方。
private List<Officer> officers = new ArrayList<Officer>();
@OneToMany(mappedBy="starship", cascade={CascadeType.ALL}) public List<Officer> getOfficers() {return Collections.unmodifiableList(officers);}
protected void setOfficers(List<Officer> officers) {this.officers = officers;}
public void addOfficer(Officer officer) {this.officers.add(officer);}
现在,我们再次重新运行相同的测试,并再次检查输出。
Hibernate: insert into Starship (affiliationEnum, launched, height, length, power, width, registry, starshipClassEnum, id) values (?, ?, ?, ?, ?, ?, ?, ?, ?)
Hibernate: insert into Officer (affiliationEnum, homePlanet, name, rank, speciesEnum, starship_id, id) values (?, ?, ?, ?, ?, ?, ?)
Hibernate: insert into Officer (affiliationEnum, homePlanet, name, rank, speciesEnum, starship_id, id) values (?, ?, ?, ?, ?, ?, ?)
Hibernate: insert into Officer (affiliationEnum, homePlanet, name, rank, speciesEnum, starship_id, id) values (?, ?, ?, ?, ?, ?, ?)
Hibernate: insert into Officer (affiliationEnum, homePlanet, name, rank, speciesEnum, starship_id, id) values (?, ?, ?, ?, ?, ?, ?)
Hibernate: insert into Officer (affiliationEnum, homePlanet, name, rank, speciesEnum, starship_id, id) values (?, ?, ?, ?, ?, ?, ?)
Hibernate: insert into Officer (affiliationEnum, homePlanet, name, rank, speciesEnum, starship_id, id) values (?, ?, ?, ?, ?, ?, ?)
Hibernate: insert into Officer (affiliationEnum, homePlanet, name, rank, speciesEnum, starship_id, id) values (?, ?, ?, ?, ?, ?, ?)
Before Find Starship By Id
Hibernate: select starship0_.id as id1_0_, starship0_.affiliationEnum as affiliat2_1_0_, starship0_.launched as launched1_0_, starship0_.height as height1_0_, starship0_.length as length1_0_, starship0_.power as power1_0_, starship0_.width as width1_0_, starship0_.registry as registry1_0_, starship0_.starshipClassEnum as starship9_1_0_ from Starship starship0_ where starship0_.id=?
After Find Starship By Id and Before Commit
Hibernate: select officers0_.starship_id as starship7_1_1_, officers0_.id as id1_, officers0_.id as id0_0_, officers0_.affiliationEnum as affiliat2_0_0_, officers0_.homePlanet as homePlanet0_0_, officers0_.name as name0_0_, officers0_.rank as rank0_0_, officers0_.speciesEnum as speciesE6_0_0_, officers0_.starship_id as starship7_0_0_ from Officer officers0_ where officers0_.starship_id=?
After commit
尽管我们已将SQL语句的数量从25个减少到10个,但仍然有不必要的查询,这些查询仅位于第二个事务的commit部分中。 为什么如果默认情况下军官是懒惰的( JPA规范),而我们又没有让军官参与交易,那么Hibernate会在“军官”表上执行选择吗? 出于与先前配置相同的原因,返回的集合具有不同的Java标识符,因此Hibernate将其标记为新实例化的集合,但是现在显然不再需要连接表操作。 我们减少了查询数量,但是仍然存在性能问题。 可能我们需要其他解决方案,而该解决方案不是最明显的解决方案,我们不会返回Hibernate返回的集合对象,我们稍后可能会对此进行扩展,但是我们将更改批注的位置。
我们要做的是将映射位置从属性方法更改为使用字段映射。 简单来说,我们将所有注释移至类属性,而不是getter上 。
@Entity
public class Starship {
@Id @GeneratedValue(strategy=GenerationType.SEQUENCE)
private Long id;
public Long getId() {return id;}
protected void setId(Long id) {this.id = id;}
@Temporal(TemporalType.DATE) private Date launched;
public Date getLaunched() {return launched;}
public void setLaunched(Date launched) {this.launched = launched;}
...
@OneToMany(mappedBy="starship", cascade={CascadeType.ALL})
private List<Officer> officers = new ArrayList<Officer>();
public List<Officer> getOfficers() {return Collections.unmodifiableList(officers);}
protected void setOfficers(List<Officer> officers) {this.officers = officers;}
public void addOfficer(Officer officer) {
officer.setStarship(this);
this.officers.add(officer);
}
public Starship() {
super();
}
public Starship(String registry) {
setRegistry(registry);
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result
+ ((registry == null) ? 0 : registry.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Starship other = (Starship) obj;
if (registry == null) {
if (other.registry != null)
return false;
} else if (!registry.equals(other.registry))
return false;
return true;
}
}
@Entity
public class Officer {
@Id @GeneratedValue(strategy=GenerationType.SEQUENCE) private Long id;
public Long getId() {return id;}
protected void setId(Long id) {this.id = id;}
@Column(unique=true, nullable=false) private String name;
public String getName() {return this.name;}
public void setName(String name) {this.name = name;}
@Enumerated private SpeciesEnum speciesEnum;
public SpeciesEnum getSpeciesEnum() {return speciesEnum;}
public void setSpeciesEnum(SpeciesEnum speciesEnum) {this.speciesEnum = speciesEnum;}
...
@ManyToOne private Starship starship;
public Starship getStarship() {return starship;}
protected void setStarship(Starship starship) {this.starship = starship;}
public Officer() {
super();
}
public Officer(String name, RankEnum rank) {
setName(name);
setRank(rank);
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Officer other = (Officer) obj;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
}
最后,我们将再次运行测试,看看会发生什么:
Hibernate: insert into Starship (affiliationEnum, launched, height, length, power, width, registry, starshipClassEnum, id) values (?, ?, ?, ?, ?, ?, ?, ?, ?)
Hibernate: insert into Officer (affiliationEnum, homePlanet, name, rank, speciesEnum, starship_id, id) values (?, ?, ?, ?, ?, ?, ?)
Hibernate: insert into Officer (affiliationEnum, homePlanet, name, rank, speciesEnum, starship_id, id) values (?, ?, ?, ?, ?, ?, ?)
Hibernate: insert into Officer (affiliationEnum, homePlanet, name, rank, speciesEnum, starship_id, id) values (?, ?, ?, ?, ?, ?, ?)
Hibernate: insert into Officer (affiliationEnum, homePlanet, name, rank, speciesEnum, starship_id, id) values (?, ?, ?, ?, ?, ?, ?)
Hibernate: insert into Officer (affiliationEnum, homePlanet, name, rank, speciesEnum, starship_id, id) values (?, ?, ?, ?, ?, ?, ?)
Hibernate: insert into Officer (affiliationEnum, homePlanet, name, rank, speciesEnum, starship_id, id) values (?, ?, ?, ?, ?, ?, ?)
Hibernate: insert into Officer (affiliationEnum, homePlanet, name, rank, speciesEnum, starship_id, id) values (?, ?, ?, ?, ?, ?, ?)
Before Find
Hibernate: select starship0_.id as id1_0_, starship0_.affiliationEnum as affiliat2_1_0_, starship0_.launched as launched1_0_, starship0_.height as height1_0_, starship0_.length as length1_0_, starship0_.power as power1_0_, starship0_.width as width1_0_, starship0_.registry as registry1_0_, starship0_.starshipClassEnum as starship9_1_0_ from Starship starship0_ where starship0_.id=?
After Find Before Commit
After commit
为什么使用属性映射Hibernate在提交期间运行查询,而未执行使用字段映射? 提交事务后,Hibernate执行刷新操作,以使基础持久性存储与内存中保持的可持久状态同步。 当使用属性映射时,Hibernate调用getter / setter方法来同步数据,对于getOfficers方法,它将返回一个脏集合(由于进行了unmodifiableList调用)。 另一方面,当我们使用字段映射时, Hibernate直接获取字段,因此收集不被认为是肮脏的,不需要重新创建。
但是我们还没有完成,我想您想知道为什么我们还没有从getter中删除Collections.unmodifiableList,而是返回Hibernate集合? 是的,我同意您的意见,我们很快完成了工作,更改看起来像@ OneToMany(cascade = {CascadeType.ALL} )public List <Officer> getOfficers(){ 但返回原始集合最终会导致封装问题,实际上我们的封装已损坏! 我们可以将任何我们喜欢的东西添加到可变列表中; 我们可以将不受控制的更改应用于对象的内部状态。
使用unmodifiableList是避免破坏封装的一种方法,但是我们当然可以对公共访问和Hibernate访问使用不同的访问器,而不用调用Collections.unmodifiableList方法。
考虑到我们今天所看到的,我建议您使用始终字段注释而不是属性映射,我们将避免很多意外。
希望您发现这篇文章有用。
此示例的屏幕截图:
参考: Hibernate性能提示: JCG合作伙伴的 脏回收效应 在一个罐子统治他们所有博客的亚历克斯·索托。
翻译自: https://www.javacodegeeks.com/2012/03/hibernate-performance-tips-dirty.html
hibernate脏数据