我们正在尝试设置在 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 会话对象的方法。
问题是,如果我们运行 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 获取链接的记录。
谁能解释我们这边是否有错误,如果这是一个已知问题,或者其他可能有帮助的问题?
提前致谢。