1. 开篇说明:今天是春节长假的最后一天,春节过后博客将继续,这篇博客主要用来总结年前的研究结果,研究的主要Hibernate OneToMany 单向和双向配置对数据存取性能的问题。本文按照我测试实验的过程,最后得出结论。
2. 本文实验是基于之前博客EJB 学习阶段总结:JBoss下发布一个Toy企业应用,在此工程基础上,在实体中添加一对独立的一对多关系如下图:
如上图Man和Lover是一对多关系,一个Man可以有多个Lover,为了简单我们设定Man和Lover都只有Id和name属性,我们在com.home.po中添加Entity Man和Lover的代码如下:
@Entity(name="Man") @Table(name="k_man") public class Man { private Long id; private String name; private List<Lover> lovers; @Column @Id @GeneratedValue public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public List<Lover> getLovers() { return lovers; } public void setLovers(List<Lover> lovers) { this.lovers = lovers; } }
@Entity(name="Lover") @Table(name="k_lover") public class Lover { private Long id; private String name; @Column @Id @GeneratedValue public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
我要做的测试是像数据库中插入Man和Man对应的Lover,我们通过一个独立的EJB来完成,EJB中SessionBean和接口代码如下:
public interface ManService { public void insertManListUni(Integer num, Integer loverNum); public void insertManListDul(Integer num, Integer loverNum); }
@Stateless @Remote(ManService.class) public class ManServiceSession implements ManService { @PersistenceContext(unitName="com.home.po") protected EntityManager em; @TransactionAttribute(TransactionAttributeType.REQUIRED) public void insertManListUni(Integer num, Integer loverNum) { List<Lover> lovers = getLoverList(loverNum); for(int i = 0 ; i < num ; i ++) { } } @TransactionAttribute(TransactionAttributeType.REQUIRED) public void insertManListDul(Integer num, Integer loverNum) { List<Lover> lovers = getLoverList(loverNum); for(int i = 0 ; i < num ; i ++) { } } public List<Lover> getLoverList(Integer loverNum) { List<Lover> lovers = new ArrayList<Lover>(); for(int i = 0 ; i < loverNum ; i ++) { Lover l = new Lover(); l.setName("kylin-test-lover-" + i); lovers.add(l); } return lovers; } }
3 配置JBoss的日志文件,让其输出Hibernate数据库操作语句
Hibernate日志输出文件配置在JBoss_Home\server\production\conf下jboss-log4j.xml文件中,要输出Hibernate的SQL语句只需在jboss-log4j.xml的配置文件中添加Category和相应的Appender,如下:
<appender name="SWFILE" class="org.jboss.logging.appender.DailyRollingFileAppender"> <errorHandler class="org.jboss.logging.util.OnlyOnceErrorHandler"/> <param name="File" value="${jboss.server.log.dir}/FFTrace.log"/> <param name="Encoding" value="UTF-8"/> <param name="Append" value="false"/> <param name="DatePattern" value="'.'yyyy-MM-dd"/> <layout class="org.apache.log4j.PatternLayout"> <param name="ConversionPattern" value="%d %-5p [%c] %m%n"/> </layout> </appender> <category name="org.hibernate.SQL" additivity="false"> <priority value="debug"/> <appender-ref ref="SWFILE" /> </category>
4 一对多单向配置下插入一个Man查看日志
在Man中添加单向配置:
@OneToMany ( targetEntity=com.home.po.Lover.class, fetch=FetchType.LAZY, cascade = { CascadeType.ALL }) @Cascade( { org.hibernate.annotations.CascadeType.ALL } ) @ForeignKey(name="MAN_TO_LOVER_FK") @JoinColumn(name = "MAN_ID") public List<Lover> getLovers() { if(lovers == null) { lovers = new ArrayList<Lover>(); } return lovers; }
修给ManServiceSession的insertManListUni方法如下:
public void insertManListUni(Integer num, Integer loverNum) { for(int i = 0 ; i < num ; i ++) { List<Lover> lovers = getLoverList(loverNum); Man m = new Man(); m.setName("kylin-test-man-" + i); em.persist(m); for(Lover l : lovers) { m.getLovers().add(l); } } }
插入一个Man一个Man对应两个Lover后 查看日志如下:
2011-02-08 15:31:58,562 DEBUG [org.hibernate.SQL] select hibernate_sequence.nextval from dual 2011-02-08 15:31:58,750 DEBUG [org.hibernate.SQL] select hibernate_sequence.nextval from dual 2011-02-08 15:31:58,765 DEBUG [org.hibernate.SQL] select hibernate_sequence.nextval from dual 2011-02-08 15:31:58,781 DEBUG [org.hibernate.SQL] insert into k_man (name, id) values (?, ?) 2011-02-08 15:31:58,781 DEBUG [org.hibernate.SQL] insert into k_lover (name, id) values (?, ?) 2011-02-08 15:31:58,781 DEBUG [org.hibernate.SQL] insert into k_lover (name, id) values (?, ?) 2011-02-08 15:31:58,781 DEBUG [org.hibernate.SQL] update k_lover set MAN_ID=? where id=? 2011-02-08 15:31:58,781 DEBUG [org.hibernate.SQL] update k_lover set MAN_ID=? where id=?
如上是插入一个Man,一个Man对应两个Lover,SQL语句统计如下:
3 select语句,1 insert Man语句,2 insert lover语句, 2 update lover语句,总共:8 SQL语句
依次推断得出计算SQL语句公式:假设插入Man的个数用 m表示,一个Man对应的Lover个数用 l表示,SQL总数用s表示:给出公式:S=(m + m*n)*2 + m*n,用此公式
插入1个Man,一个Man对应2个Lover | SQL总数:(1+1*2)*2+1*2 =8 |
插入100个Man,一个Man对应1000个Lover | SQL总数:(100+100*1000)*2+100*1000=300200 |
插入100个Man,一个Man对应1000个Lover 3次,记录操作时间如下:
即完成插入100个Man,一个Man对应1000个Lover 需要时间大概是110秒
5 一对多双向下插入Man及对应的Lover查看日志
修改实体类
Man实体如下:
@OneToMany ( targetEntity=com.home.po.Lover.class, fetch=FetchType.LAZY, cascade = { CascadeType.ALL }, mappedBy = "man") @Cascade( { org.hibernate.annotations.CascadeType.ALL } ) @ForeignKey(name="MAN_TO_LOVER_FK") public List<Lover> getLovers() { if(lovers == null) { lovers = new ArrayList<Lover>(); } return lovers; }
Lover实体添加Man属性如下:
private Man man; @ManyToOne public Man getMan() { return man; } public void setMan(Man man) { this.man = man; }
修给ManServiceSession的insertManListDul方法如下
public void insertManListDul(Integer num, Integer loverNum) { for(int i = 0 ; i < num ; i ++) { List<Lover> lovers = getLoverList(loverNum); Man m = new Man(); m.setName("kylin-test-man-" + i); em.persist(m); for(Lover l : lovers) { l.setMan(m); em.persist(l); } } }
同样插入一个Man一个Man对应两个Lover后 查看日志如下:
2011-02-08 16:40:11,859 DEBUG [org.hibernate.SQL] select hibernate_sequence.nextval from dual 2011-02-08 16:40:12,031 DEBUG [org.hibernate.SQL] select hibernate_sequence.nextval from dual 2011-02-08 16:40:12,031 DEBUG [org.hibernate.SQL] select hibernate_sequence.nextval from dual 2011-02-08 16:40:12,046 DEBUG [org.hibernate.SQL] insert into k_man (name, id) values (?, ?) 2011-02-08 16:40:12,062 DEBUG [org.hibernate.SQL] insert into k_lover (man_id, name, id) values (?, ?, ?) 2011-02-08 16:40:12,062 DEBUG [org.hibernate.SQL] insert into k_lover (man_id, name, id) values (?, ?, ?)
3 select语句,1 insert Man语句,2 insert lover语句, 总共:6 SQL语句
依次推断得出计算SQL语句公式:假设插入Man的个数用 m表示,一个Man对应的Lover个数用 l表示,SQL总数用s表示:给出公式:S=(m + m*n)*2 ,用此公式
插入1个Man,一个Man对应2个Lover | SQL总数:(1+1*2)*2=6 |
插入100个Man,一个Man对应1000个Lover | SQL总数:(100+100*1000)*2=200200 |
插入100个Man,一个Man对应1000个Lover 3次,记录操作时间如下:
即完成插入100个Man,一个Man对应1000个Lover 需要时间大概是90秒
6 结论:比较步骤4和步骤5,可以看到同样是插入100个Man,一个Man对应1000个Lover 用双向配置节约了20秒时间,即结论:一对多关系一般推荐使用双向配置