之前在JPA/Hibernate+JSF+CDI的项目中老是遇到懒加载异常问题,解决办法要不就是修改查询语句,要不就是改为EAGER。
最近百度了很久之后终于想到了一个还不错的办法,使用JPA的拦截器,不过这个解决方案需要使用Hibernate实现的时候才可用。
先看一下org.hibernate.collection.internal.AbstractPersistentCollection的代码,抛出懒加载异常的原因在这里:
if ( session == null ) {
if ( allowLoadOutsideTransaction ) {
session = openTemporarySessionForLoading();
isTempSession = true;
}
else {
throwLazyInitializationException( "could not initialize proxy - no Session" );
}
}
else if ( !session.isOpen() ) {
if ( allowLoadOutsideTransaction ) {
originalSession = session;
session = openTemporarySessionForLoading();
isTempSession = true;
}
else {
throwLazyInitializationException( "could not initialize proxy - the owning Session was closed" );
}
}
else if ( !session.isConnected() ) {
if ( allowLoadOutsideTransaction ) {
originalSession = session;
session = openTemporarySessionForLoading();
isTempSession = true;
}
else {
throwLazyInitializationException( "could not initialize proxy - the owning Session is disconnected" );
}
}
public class LazyInitilazeCollectionListener {
private static final Map<Class<?>,Set<Field>> PROXY_MAP= new HashMap<>();
/**
* 1. 检查PROXY_MAP的键中是否包含o的Class对象,若包含,则取出相应的Set集合,即需要懒加载的成员
* 2. 若未包含o的Class对象,则检查o的Class对象的成员变量,看是否有成员变量加上了@OnetoMany,@ManyToMany注解,并且fetch值为FetchType.LAZY
* 若找到符合条件的变量(即懒加载对象),则将它们放入到Set中,然后放入PROXY_MAP中。
* 3. 若set为空,则表示没有需要进行懒加载的对象
* 4. 若set不为空,则遍历set,取出o的相应的变量的值(fieldValue),若该值(fieldValue)类型属于AbstractPersistentCollection,则说明此集合是懒加载的集合,需要对其进行再此代理处理。
* 5. 使用值(fieldValue)生成代理对象,将代理对象设置为o的相应的field的值,问题搞定。
* 在此后若是需要使用该懒加载集合的值时,调用该集合的方法即会调用代理对象的相应方法;代理方法会先检查该集合是否已经完全初始化,若是,则直接调用,若不是,则给其创造加载所需的环境(Session)
* @param o
*/
@PostLoad
public void proxyCollection(Object o){
Class<?> clazz = o.getClass();
Set<Field> set = null;
if(PROXY_MAP.containsKey(clazz)){
set = PROXY_MAP.get(clazz);
}else{
//检查所有成员变量,是否有onetomany和manytomany注解并且申明为fetchType.LAZY
for(Field field : clazz.getDeclaredFields()){
if(field.isAnnotationPresent(OneToMany.class)){
if(field.getAnnotation(OneToMany.class).fetch().equals(FetchType.LAZY)){
if(set == null){
set = new HashSet<>();
}
set.add(field);
}
}else if(field.isAnnotationPresent(ManyToMany.class)){
if(field.getAnnotation(ManyToMany.class).fetch().equals(FetchType.LAZY)){
if(set == null){
set = new HashSet<>();
}
set.add(field);
}
}
}
synchronized (PROXY_MAP) {
PROXY_MAP.put(clazz, set);
}
}
//若是o中有需要代理的对象,此时set已经不为空了
if(set == null){
return;
}
for(Field field : set){
field.setAccessible(true);
try {
Object fieldValue= field.get(o);
if(fieldValue != null){
if(fieldValue instanceof AbstractPersistentCollection){
//生成代理对象
Object obj = Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[]{field.getType()},new Handler(fieldValue));
field.set(o, obj);
}
}
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
/**
* 到服务器上查找EntityManager对象,需要在persistence.xml文件中先配置<br/>
* <!-- 将EntityManagerFactory和EntityManager绑定到JNDI上 --><br/>
* <property name="jboss.entity.manager.factory.jndi.name" value="java:jboss/myEntityManagerFactory" ><br/>
* <br/>
* <property name="jboss.entity.manager.jndi.name" value="java:/myEntityManager"><br/>
* @return
* @throws NamingException
*/
private static EntityManager lookupEntityManager() throws NamingException{
Context ctx = new InitialContext();
return (EntityManager)ctx.lookup("java:/myEntityManager");
}
class Handler implements InvocationHandler{
private Object obj;
public Handler(Object obj) {
this.obj = obj;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
AbstractPersistentCollection collection = (AbstractPersistentCollection)obj;
if(!collection.wasInitialized()){
EntityManager em = lookupEntityManager();
SessionImplementor session = em.unwrap(SessionImplementor.class);
try {
collection.setCurrentSession(session);
} catch (Exception e) {
//若是已关联了Session则会抛出异常,这里的异常只会发生在对象所处的Session还未关闭的情况下,这时是可以加载数据的,所以不需要任何处理
}
}
return method.invoke(obj, args);
}
}
}