关于Java Persistence的问题,向板桥大哥请教

08-10-15 tom01
我是EJB3.0的初学者,关于Java Persistence有两种类别
1\Transaction-scoped persistence context
2\extended persistence context
在extended persistence context的上下文中,实体bean一直都是托管状态直到persistence context关闭,这种方式中使用想到了banq大哥的<数据库已死>一文,访问过的实体bean被缓存到了ejb容器中,再次访问该实体bean时直接使用缓存中的对象,不用再从数据库中查询,从而减少了对数据库的访问次数,分担了数据库的压力.这一点我已经测试过了
关于使用:
1\在stateful session bean 中可以使用
@PersistenceContext(unit="...",type=PersistenceContextType.EXTENDED)
EntityManager em;
2\在stateless session bean 中则EntityManagerFactory创建
@PersistenceUnit(unit="...")
EntityManagerFactory emf
EntityManager em=emf.createEntityManager()
我在网上看到很多都建议用前一种方式来使用(type=...)Persistence Context,以达到利用ejb容器自动管理之优点.但我想这种方式只适用于stateful session bean.
如果在在stateless session bean使用extended persistence context就必须自己去em.joinTransaction();
在这里请高手们请教该如何在使用时做选择

banq
2008-10-15 18:08
type=PersistenceContextType.EXTENDED一般适合特殊场合,比如login登录时需要填写多个页面,第一页是用户和密码,第二是详细资料,第三页面..等等,只有某个用户在三个页面全部完成才能保存数据库。

以前是怎么做?就是使用HttpSession,那么临时数据保存在哪里?数据库是不行的,因为一旦用户填写第二个页面,就离开,就成数据库垃圾数据。只是保存到内存缓存中,带来问题就是事务还要自己做,这些很麻烦。

现在使用有态Bean再加上PersistenceContextType.EXTENDED就可以将上面这些工作交给容器实现,节省了开发效率。

所以,PersistenceContextType.EXTENDED是特殊场景下使用,不是经常使用,比如一个业务事务需要多次请求才能完成的。

缺省的是第一个情况,都是由容器管理的。

tom01
2008-10-16 09:11
没想到banq大哥这么快就回复了,您真是个热心肠的人啊,呵呵
那么在实际应用的时候怎么实现实体bean的缓存或者说如何减轻数据库的访问压力,怎么用是比较好的方式

banq
2008-10-16 09:40
>在实际应用的时候怎么实现实体bean的缓存或者说如何减轻数据库的访问压力

你选择EJB3这个架构后,容器就内置实体Bean的缓存了,当然关于批量查询,你需要优化。

如果想明显体验缓存的作用,可以用Jdon框架试验看看。不少道友用后,都有此感概。

tom01
2008-10-16 10:32
我测试过了,方法:用SQL的事件查看器监视数据库的访问
Transaction-scoped Persistence Context的EntityManager操作(比如em.find()),每调用一次就会访问一次数据库.
而Extended Persistence Context的EntityManager操作,实体bean才能真正缓存,第一次调用的时候访问一次数据库,以后再调用就不用再访问数据库了.
这不是很好的减轻了数据库的负载吗
所以我就想用stateless session bean 中使用Extended Persistence Context的EntityManager来操作实体bean,
当然我也可以在stateful session bean 中使用Extended Persistence Context,然后调用方始终保存ejb的引用
这两种方式我就不知道会出现什么性能和安全等方面的问题,还有没有更好的方法.
好像在EJB2.0中都是容器来管理实体bean的...

[该贴被tom01于2008-10-16 10:34修改过]

tom01
2008-10-16 10:45
说实在的,banq大哥我很敬佩您,中国软件行业再多一些像您这样的人多话啊,中国软件自己的东西太少了,如果我是老板我肯定会极力的推荐jdon框架而不用spring框架,因为又好又是国人自己的东西,但我现在还在打工,我在中国人才热线搜了一下关于jdon关键字的没有找到一家公司,一条信息.
无奈呀...我必须去适应社会,因为我要生活

banq
2008-10-16 11:08
你没有理解我前面的意思:

Extended Persistence Context有缓存,那是因为有态Bean的原因,这是一个Session级别的缓存。

我让你试验Jdon框架,是让你对缓存有一个悟性,有一个认识,不是让你以后赖以为生。

因为EJB是一个很复杂的组件技术,封装了很多知识在里面,所以,你只能雾里看花,最后理解歪了,误用EJB。

所以,初学者如果没有深刻的对象和缓存概念,是无法用好EJB的,这就是EJB的使用障碍,别看EJB3变得好用了,后面隐藏的深奥原理还是EJB1.x/EJB2.x那些。

你可以试验用用Hibernate,对HIbernate一级缓存和二级缓存有一个了解,就知道为什么Transaction-scoped Persistence Context为什么每次调用数据库了。

Transaction-scoped Persistence Context相当于hibernate的一级缓存,在一个Transaction-scoped Persistence Context结束后,实体Bean容器有责任保证缓存和数据库数据同步。

你不能从表面上看Extended Persistence Context有缓存,就误用Extended Persistence Context,这些都是对缓存认识不深刻,缓存是一个很复杂的技术,如果很简单,不需要人工定制,那么整个世界就清净了,就不会有数据库派的人拼命进行数据库本身微调,而不是从缓存调优角度出发,这些都是因为对象缓存是一个系统工程。你需要逐步认识,不可一蹴而就。

tom01
2008-10-16 11:43
对不起,banq大哥让你生气了,
我现在理解的PersistenceContext缓存是因为对实体的生命周期的理解:
1\新建状态 2\托管状态 3\游离状态
因为Transaction-scoped Persisitence Context和事务同步的,在事务中实体是托管状态,事务结束PersistenceContext也随之关闭,实体将转为游离状态;
而Extended Persistence Context可以跨越多个事务,实体受托管的状态与事务无关,所以才会出现第一次调用的时候访问一次数据库,以后再调用就不用再访问数据库了,因为上下文没有关闭,第二次调用的时候实体在上下文中,是受管理的,可以直接引用,这不就实现了缓存吗,
我理解的是实体生命周期是和上下文同步的而不是和session bean同步的,即使在无状态的session bean中也是如此,我在网上查的资料和我本人测试的结果都是如此

banq
2008-10-16 11:51
>实体生命周期是和上下文同步的而不是和session bean同步的,即使在无状态的session bean中也是如此

是这样,实体生命周期肯定是和上下文同步的,我前面讲有态Bean不是说有态Bean缓存后面的实体生命周期,是因为有态Bean被Session缓存了,所以实体的transaction才有一个托管的地方。否则,你用无态Bean就只能自己来处理事务。我们一定要有缓存和事务两个概念,在EJB中,不但有缓存,而且有事务,事务+缓存有时难以协调。

Extended Persistence Context的缓存是为特殊目的而定制的,因为不是容器管理事务,所以,你就明显看到缓存作用,但是它是为特殊目的使用的啊,就像你需要一个螺丝刀,你看到小刀也能旋螺丝,你就想用小刀当作螺丝刀来用,这就是误用。

我没有生气,只是你要安静下来想想,不要钻牛角尖。

下面这篇文章对JPA二级缓存有一个讨论,使用Hibernate作为JPA实现,就可以使用Hibernate二级缓存作为JPA的二级缓存,而通常JPA只有所谓的一级缓存,缓存作用不明显。


http://forum.hibernate.org/viewtopic.php?p=2366024

tom01
2008-10-16 12:02
呵呵,这就是我想要的答案,就是能不能用Extended Persistence Context当缓存来用,用了会产生怎么的后果,可以用应该怎么用,如果不能用,应该实现您的<数据库已死>中的理念.
我是一个"业余"的很执着的技术爱好者,呵呵

banq
2008-10-16 12:04
当然不能,

下面这篇文章对JPA二级缓存有一个讨论,使用Hibernate作为JPA实现,就可以使用Hibernate二级缓存作为JPA的二级缓存,而通常JPA只有所谓的一级缓存,缓存作用不明显。

http://forum.hibernate.org/viewtopic.php?p=2366024

tom01
2008-10-16 12:14
谢谢了Banq大哥,耽误了您好多时间
您能再说说为什么不能吗,不考虑它设计的目的,只从它在实际应用中会出现什么问题

banq
2008-10-16 12:40
不耽误时间,我从来没想过这样用法,所以你的想法也是有创意的,但是也反映我以前批评的Hibernate/JPA等对缓存的不透明性,当然JPA这样做也是有原因,缓存+事务才是完整的。

当然,缓存+事务是对写操作保存数据库需要的,但是很多情况下,我们只是读,使用缓存进行读操作的缓存,所以,这时事务就不需要了。

那么,你在无态bean中使用Extended Persistence Context就无需em.joinTransaction()。

但是关键问题是:还是回到设计初衷,现在没问题,不代表以后JPA拓展升级了就没有问题。

另外一个更重要问题是:实体bean一直都是托管状态直到persistence context关闭,在这段时间的缓存是否对你真正有效,也就是缓存中对象scope,生命周期,真正能够减轻数据库负载的缓存是application的,跨多次请求的,那么persistence context能够保持多次请求也一直打开吗?在和有态Bean结合时,缓存是Session,但也不是application的啊,你使用不同的IP客户端地址再测试一下,看不同客户端多次请求是否共享同一个对象呢?

这些都是缓存基本概念,所以,我建议你对对象缓存有一个了解后,再决定如何使用。我上面介绍的激活JPA二级缓存是一个变通方法。或者你在业务层这里使用Spring/Jdon的缓存。




tom01
2008-10-16 13:23
当然在find()操作中不需要em.joinTransaction();
至于"那么persistence context能够保持多次请求也一直打开吗?"
不是说ejb容器是实例池化吗,以最少的实例服务,我简单的测试了容器中只有一个无状态session bean实例,我也用不同的客户端访问测试过,也只有一个
以下是我测试session bean的代码,其中的private int i 可以说明这一点
import java.util.List;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.ejb.Local;
import javax.ejb.Remote;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.PersistenceUnit;

@Stateless
@Local(PersonDAO.class)
@Remote(PersonDAO.class)
public class PersonBean implements PersonDAO {
@PersistenceUnit(unitName = "Northwind")
private EntityManagerFactory emf;

private EntityManager em;

private int i = 0;

private EntityManager em() {
if (em == null) {
em = emf.createEntityManager();
System.out.println("Create an EntityManager...");
}
return em;
}

public Person getPerson(Integer id) {
i++;
System.out.println(i);
System.out.println("get a Pseron");
return em().find(Person.class, id);
}

public List getPersonList() {
System.out.println("get a Person list");
return em().createQuery("select a from Person a").getResultList();
}

public void addPerson(Person person) {
System.out.println("append a Person");
em().joinTransaction();
i++;
person.setName(person.getName() + i);
em().persist(person);
}

public void delPerson(Integer id) {
System.out.println("delete a Person");
em().remove(em().getReference(Person.class, id));
}

public int trunPerson() {
em().joinTransaction();
int r = em().createNativeQuery("delete from Person").executeUpdate();
System.out.println(r + "所影响的行数为 " + r + " 行");
return r;
}

@PostConstruct
public void Construct() {
System.out.println("实例初始化...");
System.out.println(this.getClass());
}

@PreDestroy
public void Destroy() {
System.out.println("实例销毁...");
System.out.println(this.getClass());
}

}

[该贴被tom01于2008-10-16 13:26修改过]

tom01
2008-10-16 13:41
如果把@stateless换成@stateful那么
@PersistenceUnit(unitName = "Northwind")
private EntityManagerFactory emf;
private EntityManager em;
将改为
@PersistenceContext(unitName = "Northwind")
private EnitityManager em;
下面的EnittyManagerFactory.createEntityManager()就不用了
包含写操作数据库的方法也就不用em.joinTransaction()了(由容器来管理事务)
在ejb调用端只要保存ejb对象的引用(比如将其保存到WEB的application中去),再次调用时,在ejb容器中也不会创建一个新的实例;
只有重新new InitialContext().lookup("...")时容器才会创建一个新的

2Go 1 2 下一页