JPA / EclipseLink - 使用多个JpaEntityManagers时出错

时间:2013-10-09 15:56:30

标签: jpa eclipselink

我们正在尝试设置在Glassfish 4服务器上运行的应用程序。对于数据检索,我们使用的是JPA 2.0(EclipseLink Kepler)。出于多种原因,除了正常的用户管理之外,我们还希望用户通过DMBS用户管理进行授权。

为了实现这一点,我们在用户登录时创建一个特定于用户的JpaEntityManager(为了检查登录本身,我们还有一个通用的服务器端JpaEntityManager,其DBMS凭据位于persistence.xml中)。这里有一些代码:

import java.util.Enumeration;
import java.util.HashMap;
import java.util.Hashtable;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;

import org.eclipse.persistence.jpa.JpaEntityManager;
import org.eclipse.persistence.jpa.JpaHelper;

import application.auth.general.Credentials;
import application.config.Config;
import application.data.model.JpaSession;
import application.logger.Logger;

/**
 */
public class EntityManagerBase {

    /**
     * Singleton instance
     */
    private static EntityManagerBase instance = new EntityManagerBase();

    /**
     * Hashtable for storage of user specific EntityManagers
     * 
     * @param String userName
     * @param EntityManager corresponding EntityManager
     */
    private Hashtable<Integer, JpaEntityManager> jpaEms = new Hashtable<>();

    /**
     * Default constructor for singleton, creates single jpaEm instance for
     * user ID -1, rights are defined in server-side persistence.xml
     */
    private EntityManagerBase() {
        String persistenceUnitName = Config.get("pvapp.data.persistence.unitName");
        EntityManagerFactory emf = Persistence.createEntityManagerFactory(persistenceUnitName);
        EntityManager em = emf.createEntityManager();
        JpaEntityManager jpaEm = JpaHelper.getEntityManager(em);
        jpaEms.put(-1, jpaEm);
    }

    public static EntityManagerBase getInstance() {
        return instance;
    }

    /**
     * Prevent cloning of singleton instance 
     */
    @Override
    protected Object clone() throws CloneNotSupportedException {
        String name = this.getClass().getCanonicalName();
        throw new CloneNotSupportedException(name 
                + " does not support clone(). Use " + name 
                + ".getInstance() instead.");
    }

    public void createJpaEntityManager(JpaSession session, Credentials credentials) {
        String persistenceUnitName = Config.get("pvapp.data.persistence.unitName");
        EntityManagerFactory emf = Persistence.createEntityManagerFactory(persistenceUnitName);
        HashMap<String, String> properties = new HashMap<>();
        properties.put("javax.persistence.jdbc.user", credentials.getUserName());
        properties.put("javax.persistence.jdbc.password", credentials.getPassword());
        EntityManager em = emf.createEntityManager(properties);
        JpaEntityManager jpaEm = JpaHelper.getEntityManager(em);
        jpaEms.put(session.getUser().getId(), jpaEm);
    }

    public JpaEntityManager getJpaEntityManager(JpaSession session) {
        return this.jpaEms.get(session.getUser().getId());
    }

    /**
     * Get a JPA entity manager for a numeric user id
     * 
     * @param id
     * @return
     */
    public JpaEntityManager getJpaEntityManager(int id) {
        return this.jpaEms.get(id);
    }

}

因此,当用户登录时,正在调用createJpaEntityManager,新创建的JpaEntityManager将与其userId一起存储在Hashtable中。这适用于检索默认POJO,但如果我们与其他对象有关系,例如:

import java.io.Serializable;
import java.util.List;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.xml.bind.annotation.XmlRootElement;

import application.auth.general.Authorizable;
import static javax.persistence.FetchType.EAGER;


/**
 * The persistent class for the modulesignature database table.
 * 
 */
@Entity(name="JpaModuleSignature")
@Table(name="modulesignature")
@XmlRootElement
@NamedQueries({
    @NamedQuery(name="JpaModuleSignature.findAll", query="SELECT m FROM JpaModuleSignature m"),
    @NamedQuery(name="JpaModuleSignature.findById", query="SELECT m FROM JpaModuleSignature m WHERE m.id = :id")
})
public class JpaModuleSignature implements Serializable, Authorizable, JpaRecord {
    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    @Column(name="id")
    private int id;

    @Column(name="authObjId")
    private int authObjId;

    @Column(name="className")
    private String className;


    @Column(name="version")
    private int version;

    //bi-directional many-to-one association to JpaMenuItem
    @OneToMany(mappedBy="moduleSignature", fetch = EAGER)
    private List<JpaMenuItem> menuItems;

    public JpaModuleSignature() {
    }

    public int getId() {
        return this.id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public int getAuthObjId() {
        return this.authObjId;
    }

    public void setAuthObjId(int authObjId) {
        this.authObjId = authObjId;
    }

    public String getClassName() {
        return this.className;
    }

    public void setClassName(String className) {
        this.className = className;
    }

    public int getVersion() {
        return this.version;
    }

    public void setVersion(int version) {
        this.version = version;
    }

    public List<JpaMenuItem> getMenuItems() {
        return this.menuItems;
    }

    public void setMenuItems(List<JpaMenuItem> menuItems) {
        this.menuItems = menuItems;
    }

    public JpaMenuItem addMenuItem(JpaMenuItem menuItem) {
        getMenuItems().add(menuItem);
        menuItem.setModuleSignature(this);

        return menuItem;
    }

    public JpaMenuItem removeMenuItem(JpaMenuItem menuItem) {
        getMenuItems().remove(menuItem);
        menuItem.setModuleSignature(null);

        return menuItem;
    }

}

假设我们按照以下步骤进行操作 - 确定用户的id并获取正确的JpaEntityManager(不幸的是,需要更多代码):

首先是Jersey 2.0-Webservice,这是相当不引人注目的:

import java.util.List;

import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.ext.Provider;

import application.data.model.JpaMenuItem;
import application.data.model.JpaModuleSignature;
import application.data.model.JpaSession;
import application.logger.Logger;
import application.server.storage.GenericStorage;

/**
 */
@Provider
@Path("JpaModuleSignature")
public class ModuleSignatureService extends AbstractService {

    /**
     * Storage unit dealing with storage of the JPA entities
     */
    //private ModuleSignatureStorage storage = ModuleSignatureStorage.getInstance();
    private GenericStorage<JpaModuleSignature> storage = 
        GenericStorage.createInstance(JpaModuleSignature.class);


    /**
     * Include application.data.model classes
     */
    public ModuleSignatureService() {
        super();
    }

    /**
     * Get a list of all ModuleSignatures in the database
     * 
     * @return list of ModuleSignatures
     */
    @Produces(MediaType.APPLICATION_XML)
    @Path("list")
    @GET
    public JpaModuleSignature[] getModuleSignatures(@QueryParam("sessionId") String sessionId) {
        Logger.getInstance().setVerbosity((byte) 3);
        JpaSession session = this.getSession(sessionId);
        List<JpaModuleSignature> ms = storage.getList(session);
        Logger.getInstance().log("-----3-----");
        for (int i = 0; i < ms.size(); i++) {
            Logger.getInstance().log("signature #" + i + ": " + ms.get(i).getTitle());
            List<JpaMenuItem> menuItems = ms.get(i).getMenuItems();
            for (int j = 0; j < menuItems.size(); j++) {
                Logger.getInstance().log("menu item #" + i + "-" + j + ": " + menuItems.get(j));
            }
        }
        Logger.getInstance().log("-----4-----");
        JpaModuleSignature ret[] = new JpaModuleSignature[0];
        return ms.toArray(ret);
    }

}

对我们的通用存储进行回调,其中包含对用户特定JpaEntityManager的调用(只需查看getList中静态调用StorageBase的一行:

import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import javax.persistence.Entity;
import javax.persistence.Query;

import org.eclipse.persistence.jpa.JpaEntityManager;

import application.auth.general.Authorizable;
import application.data.model.JpaRecord;
import application.data.model.JpaSession;
import application.logger.Logger;
import application.server.auth.AuthorizationManager;

public class GenericStorage<T> extends StorageBase {

    /**
     * This is an internal variable in order to store the class
     * of the generic object. It is used as a helper later in the code
     */
    private final Class<T> storageClass;

    /**
     * The constructor has only "protected" visibility so the formal type
     * parameter has to be specified via createClient
     * 
     * @param clientClass
     */
    protected GenericStorage(Class<T> storageClass) {
        this.storageClass = storageClass;
    }

    /**
     * Static method for creating instances of GenericStorage. 
     * 
     * @param className
     * @return
     */
    public static <U> GenericStorage<U> createInstance(Class<U> className) {
        return new GenericStorage<U>(className);
    }

    /**
     * Get a list of all items
     * 
     * @return
     */
    public List<T> getList(JpaSession session) {
        return this.getList(session, null);
    }



    public List<T> getList(JpaSession session, Hashtable<String,Object> parameters) {
        Entity e = (Entity) this.storageClass.getAnnotation(Entity.class);
        String entityName = e.name();

        String queryString = "SELECT r FROM " + entityName + " r";

        if (parameters != null && parameters.size() > 0) {
            String where = " WHERE ";
            Set<String> paramKeys = parameters.keySet();
            Iterator<String> i = paramKeys.iterator();
            while (i.hasNext()) {
                String key = i.next();
                where += key + " = :"  + key;
            }
            queryString += where;
        }

        // GET USER-SPECIFIC JpaEntityManager HERE:
        JpaEntityManager jpaEm = StorageBase.getJpaEM(session);

        Query query = jpaEm.createQuery(queryString);
        Logger.getInstance().log("-----1-----");
        if (parameters != null && parameters.size() > 0) {
            Set<String> paramKeys = parameters.keySet();
            Iterator<String> i = paramKeys.iterator();
            while (i.hasNext()) {
                String key = i.next();
                query.setParameter(key, parameters.get(key));
            }
        }

        List<T> L = (List<T>) query.getResultList();
        Logger.getInstance().log("-----2----- (" + entityName + ")");
        return L;
    }

}

StorageBase代码:

import org.eclipse.persistence.jpa.JpaEntityManager;

import application.data.model.JpaSession;

/**
 * Implements general functionality for retrieval of
 * user specific JpaEntityManager instances using the
 * EntityManagerBase
 */ 
abstract class StorageBase {

    public static JpaEntityManager getJpaEM(JpaSession session) {
        return EntityManagerBase.getInstance().getJpaEntityManager(session);
    }

    public static JpaEntityManager getJpaEM(int id) {
        return EntityManagerBase.getInstance().getJpaEntityManager(id);
    }
}

如果对“AbstractService”的作用有任何疑问 - 并非真正需要阅读,它只需调用Jersey的'this.packages(..)'并提供一种方法,通过用户提供的会话ID获取JPA Session对象。

问题是,如果我们运行ModuleSignatureService的getModuleSignatures,会发生奇怪的事情:JpaModuleSignature基本上可以通过正确的JpaEntityManager检索,但是当尝试访问链接属性“menuItems”时,我们收到以下错误:

INFO: [EL Severe]: 2013-10-09 16:50:24.34--ServerSession(1625769026)--Exception [EclispseLink-4002] (Eclipse Persistence Services - 2.5.0.v20130507-3faac2b): org.eclipe.persistence.exceptions.DatabaseException
Internal Exception: java.sql.SQLException: Access denied for user 'PERSISTENCE.XML-USER'@'localhost' (using password: YES)
Error Code: 1045

当然真正的用户名不是PERSISTENCE.XML-USER - 但它在persistence.xml中定义的用户名。我们仔细检查了授权日期库系统的正确用户是否正在获取原始记录。但是,JPA显然试图通过错误的JpaEntityManager获取链接的记录。

任何人都可以解释我们是否存在某些错误,如果这是一个已知问题,或其他可能有帮助的问题?

提前致谢。

1 个答案:

答案 0 :(得分:1)

EMF包装共享资源,因此只要此持久性单元存在EMF上下文,基本EMF就与基本登录属性一起使用。您需要设置EclipseLink,以便它使用独占连接而不是共享连接,如http://wiki.eclipse.org/EclipseLink/Examples/JPA/Auditing

所述