2013-10-09 5 views
0

Мы пытаемся настроить приложение, работающее на сервере Glassfish 4. Для извлечения данных мы используем JPA 2.0 (EclipseLink Kepler). По нескольким причинам, помимо обычного управления пользователями, мы также хотим, чтобы пользователи авторизовались с помощью управления пользователями DMBS.JPA/EclipseLink - Ошибка при использовании нескольких JpaEntityManagers

Для этого мы создаем пользовательский JpaEntityManager, когда пользователь входит в систему (для проверки самого входа у нас также есть общий сервер JpaEntityManager на стороне сервера, чьи учетные данные СУБД находятся в постоянстве. 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 в настоящее время хранится в Hashtable с его USERID. Это прекрасно работает для извлечения 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; 
    } 

} 

Допустим, мы поступим следующим образом - определить идентификатор пользователя и получать правильный JpaEntityManager (к сожалению, еще несколько код необходимо для этого):

Сначала Джерси 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» делает - на самом деле не нужно читать, он просто называет Джерси «это .packages (..) 'и предоставляет метод для получения объекта сеанса JPA по предоставленному пользователем идентификатору сеанса.

Проблема заключается в том, что если мы запустим getModuleSignatures ModuleSignatureService, произойдет что-то странное: 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

EMF обертывают общие ресурсы, поэтому, если существует контекст EMF для этого блока сохранения, базовая EMF используется с базовыми свойствами входа. Вам нужно будет настроить EclipseLink так, чтобы он использовал эксклюзивные подключения, а не общие соединения, как описано здесь. http://wiki.eclipse.org/EclipseLink/Examples/JPA/Auditing

+0

Спасибо большое! Это определенно привело нас к решению. После отключения общего кэша, как описано в статье, связанной в статье аудита, она работает отлично! – grobmotoriker

Смежные вопросы