http://kylinsoong.iteye.com/blog/852213
本文列出几个“EJB 学习阶段总结:JBoss下发布一个Toy企业应用”开发测试过程中遇到的几个问题。
1. Hibernate 懒加载有一定局限性:EJB远程调运时Hibernate懒加载Session失效
通过实例说明:给Entity类中添加Transformer类,Transformer与UserCard存在一对一的单向关联,如下:
- @Entity(name="Transformer")
- @Table(name="k_transformer")
- public class Transformer implements Serializable{
- private Long id;
- private UserCard userCard;
- @Column
- @Id
- @GeneratedValue
- public Long getId() {
- return id;
- }
- public void setId(Long id) {
- this.id = id;
- }
- @OneToOne(
- targetEntity = com.home.po.UserCard.class,
- <strong><span style="color: rgb(255, 0, 0);"><em>fetch = FetchType.LAZY</em></span></strong>,
- cascade = { CascadeType.ALL })
- @Cascade( { org.hibernate.annotations.CascadeType.ALL } )
- @JoinColumn(name = "UserCard_id")
- @ForeignKey(name = "TRANSFORMER_TO_USERCARD_FK")
- public UserCard getUserCard() {
- return userCard;
- }
- public void setUserCard(UserCard userCard) {
- this.userCard = userCard;
- }
- }
@Entity(name="Transformer")
@Table(name="k_transformer")
public class Transformer implements Serializable{
private Long id;
private UserCard userCard;
@Column
@Id
@GeneratedValue
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
@OneToOne(
targetEntity = com.home.po.UserCard.class,
fetch = FetchType.LAZY,
cascade = { CascadeType.ALL })
@Cascade( { org.hibernate.annotations.CascadeType.ALL } )
@JoinColumn(name = "UserCard_id")
@ForeignKey(name = "TRANSFORMER_TO_USERCARD_FK")
public UserCard getUserCard() {
return userCard;
}
public void setUserCard(UserCard userCard) {
this.userCard = userCard;
}
}
注意OneToOne属性设定FetchType必须是LAZY,因为我们测试的是Hibernate懒加载
添加一个HibernateTest Session Bean,如下:
- public interface TransformerService {
- public void persist(Transformer t);
- public void analysis(Long id);
- public Transformer analysisRemote(Long id);
- }
public interface TransformerService { public void persist(Transformer t); public void analysis(Long id); public Transformer analysisRemote(Long id); }
- public interface TransformerServiceLocal extends TransformerService {
- }
public interface TransformerServiceLocal extends TransformerService { }
- @Stateless
- @Remote(TransformerService.class)
- @Local(TransformerServiceLocal.class)
- public class TransformerServiceSession implements TransformerServiceLocal {
- @PersistenceContext(unitName="com.home.po")
- protected EntityManager em;
- @TransactionAttribute(TransactionAttributeType.REQUIRED)
- public void persist(Transformer t) {
- em.persist(t);
- }
- @TransactionAttribute(TransactionAttributeType.REQUIRED)
- public void analysis(Long id) {
- Transformer t = em.find(Transformer.class, id);
- analysisEntity(t);
- }
- <span style="color: rgb(0, 0, 0);">private void analysisEntity(Transformer t) {
- System.out.println(t.getUserCard());
- }</span>
- @TransactionAttribute(TransactionAttributeType.REQUIRED)
- public Transformer analysisRemote(Long id) {
- return em.find(Transformer.class, id);
- }
- }
@Stateless
@Remote(TransformerService.class)
@Local(TransformerServiceLocal.class)
public class TransformerServiceSession implements TransformerServiceLocal {
@PersistenceContext(unitName="com.home.po")
protected EntityManager em;
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public void persist(Transformer t) {
em.persist(t);
}
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public void analysis(Long id) {
Transformer t = em.find(Transformer.class, id);
analysisEntity(t);
}
private void analysisEntity(Transformer t) {
System.out.println(t.getUserCard());
}
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public Transformer analysisRemote(Long id) {
return em.find(Transformer.class, id);
}
}
在客户端先向数据库中插入一条数据,在运行如下代码段:
- public void analysis() throws NamingException {
- ……
- TransformerService service = (TransformerService) ctx.lookup("home-test-all/TransformerServiceSession/remote");
- service.analysis(new Long(1));
- Transformer tra = service.analysisRemote(new Long(1));
- analysisEntityInRemote(tra);
- }
- <span style="color: rgb(0, 0, 0);">private void analysisEntityInRemote(Transformer t) {
- System.out.println(t.getUserCard());
- }</span>
public void analysis() throws NamingException {
……
TransformerService service = (TransformerService) ctx.lookup("home-test-all/TransformerServiceSession/remote");
service.analysis(new Long(1));
Transformer tra = service.analysisRemote(new Long(1));
analysisEntityInRemote(tra);
}
private void analysisEntityInRemote(Transformer t) {
System.out.println(t.getUserCard());
}
注意上述加红倾斜的代码段描述的方法analysisEntity,analysisEntityInRemote作用都是相同的,取出Transformer中UserCard属性,但是运行结果却如下,TransformerServiceSession 中的analysisEntity运行良好,Jboss Console控制台打印输出如下信息:
而Remote端Eclipse Console口抛出Session无效的异常:
- Exception in thread "main" org.hibernate.LazyInitializationException: could not initialize proxy - no Session
- at org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:62)
- at org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyInitializer.java:116)
- at org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer.invoke(JavassistLazyInitializer.java:166)
- at com.home.po.UserCard_$$_javassist_0.toString(UserCard_$$_javassist_0.java)
- at java.lang.String.valueOf(Unknown Source)
- at java.io.PrintStream.println(Unknown Source)
- at com.home.ear.test.HibernateTestClient.analysisEntityInRemote(HibernateTestClient.java:41)
- at com.home.ear.test.HibernateTestClient.analysis(HibernateTestClient.java:37)
- at com.home.ear.test.HibernateTestClient.main(HibernateTestClient.java:47)
Exception in thread "main" org.hibernate.LazyInitializationException: could not initialize proxy - no Session at org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:62) at org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyInitializer.java:116) at org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer.invoke(JavassistLazyInitializer.java:166) at com.home.po.UserCard_$$_javassist_0.toString(UserCard_$$_javassist_0.java) at java.lang.String.valueOf(Unknown Source) at java.io.PrintStream.println(Unknown Source) at com.home.ear.test.HibernateTestClient.analysisEntityInRemote(HibernateTestClient.java:41) at com.home.ear.test.HibernateTestClient.analysis(HibernateTestClient.java:37) at com.home.ear.test.HibernateTestClient.main(HibernateTestClient.java:47)
具体原因,继续研究中……
2 hibernate.jdbc.batch_size与 hibernate.jdbc.fetch_size初始值大小对Hibernate工作效率的影响:
修改Persistence Entity EJB模块中persistence.xml中Properties属性可以改变hibernate.jdbc.batch_size与 hibernate.jdbc.fetch_size的值,我做了如下两组测试,如下:
如下一,改变hibernate.jdbc.batch_size的大小,插入1000条User连续三次,记录每次插入时间,并计算出三次平均时间:
如下二改变Change hibernate.jdbc.fetch_size大小,从数据库中取出2000条和4000条数据,记录取出时间,如下:
如上两组数据,可以得出结论,在这个应用中hibernate.jdbc.batch_size设置为20-30之间Hibernate工作效率最好;而hibernate.jdbc.fetch_size是值设置为40左右Hibernate的工作效率最好
3 比较@OneToMany下@JoinColumn和@JoinTable的差别(对性能的影响)
@JoinColumn不产生级联表,将一方的主键存放在另一方的表中,如下,为JoinColumn的Entity Bean配置
以User与Friend为例:
- @OneToMany (
- targetEntity=com.home.po.Friend.class,
- fetch=FetchType.LAZY,
- cascade = { CascadeType.ALL })
- @Cascade( { org.hibernate.annotations.CascadeType.ALL } )
- @JoinColumn(name="userId")
- @ForeignKey(name="USER_TO_FRIEND_FK")
- public List<Friend> getFriends() {
- return friends;
- }
@OneToMany ( targetEntity=com.home.po.Friend.class, fetch=FetchType.LAZY, cascade = { CascadeType.ALL }) @Cascade( { org.hibernate.annotations.CascadeType.ALL } ) @JoinColumn(name="userId") @ForeignKey(name="USER_TO_FRIEND_FK") public List<Friend> getFriends() { return friends; }
这种配置下 产生表如下图:
@JoinTable产生级联表,将双方的主键存放在一张独立的表中,如下为@JoinTable的配置
- @OneToMany (
- targetEntity=com.home.po.Friend.class,
- fetch=FetchType.LAZY,
- cascade = { CascadeType.ALL })
- @Cascade( { org.hibernate.annotations.CascadeType.ALL } )
- @JoinTable(name="k_user_friend",
- joinColumns = @JoinColumn(name = "USER_ID"),
- inverseJoinColumns = @JoinColumn(name = "FRIEND_ID"))
- @ForeignKey(name = "k_user_friend_FK",
- inverseName = "k_user_friend_FK_R")
- public List<Friend> getFriends() {
- return friends;
- }
@OneToMany ( targetEntity=com.home.po.Friend.class, fetch=FetchType.LAZY, cascade = { CascadeType.ALL }) @Cascade( { org.hibernate.annotations.CascadeType.ALL } ) @JoinTable(name="k_user_friend", joinColumns = @JoinColumn(name = "USER_ID"), inverseJoinColumns = @JoinColumn(name = "FRIEND_ID")) @ForeignKey(name = "k_user_friend_FK", inverseName = "k_user_friend_FK_R") public List<Friend> getFriends() { return friends; }
这种配置下 产生表如下图:
如上用直线标出的5张表就是5对一对多关系产生的级联表,它里面存储两个表的主键。
这两种处理对数据的插入和存取有什么影响,就这一问题做如下测试:
1 分别在两种配置下通过EJB向数据库中插入2000个User 5次,记录每次时间,求出平均时间;
2 分别在两种配置下通过EJB从数据库中取出10000条数据5次,记录每次时间,求出平均时间;
结果如下两组图分别是JoinTable和JoinColumn下的测试数据
1 JoinTable下操作数据库花费的时间要长于JolinColumn;
2 JolinColumn下节省的时间约为JoinTable下的2.5%;
分析原因:JoinTable花费时间多的原因是JoinTable下生成的中间表,要存取数据时都要查询中间的级联表,所以花费时间多;
4 测试建立外键与不建立外键两种情况下对存取数据库的影响
如上面3中都是设置外键的情况下测试的,下面我们在JoinColumn下做一组不设置外键的测试,不设置外键Annotation相当简单就是在原来的基础上去掉@ForeignKey标记,如下
- @OneToMany (
- targetEntity=com.home.po.Friend.class,
- fetch=FetchType.LAZY,
- cascade = { CascadeType.ALL })
- @Cascade( { org.hibernate.annotations.CascadeType.ALL } )
- @JoinColumn(name="userId")
- public List<Friend> getFriends() {
- return friends;
- }
@OneToMany ( targetEntity=com.home.po.Friend.class, fetch=FetchType.LAZY, cascade = { CascadeType.ALL }) @Cascade( { org.hibernate.annotations.CascadeType.ALL } ) @JoinColumn(name="userId") public List<Friend> getFriends() { return friends; }
为了对比我们假设两种情况,一是JolinColumn下配置ForeignKey,二是JolinColumn下不配置ForeignKey,在两种情况下做如下测试:
1 分别在两种配置下通过EJB向数据库中插入2000个User 5次,记录每次时间,求出平均时间;
2 分别在两种配置下通过EJB从数据库中取出10000条数据5次,记录每次时间,求出平均时间;
测试结果如下面两张表所示:
1 ForeignKey对数据库的存取影响比较大,特别是数据库的查询
2 设置ForeignKey可以减少数据库存取的时间
3 设置ForeignKey插入数据库节省的时间是不设置ForeignKey的5.5%,查询时则可以节省6.5%