2014-07-04 104 views
0

我的問題其實很簡單,但我真的找不到一個好的解決方案。動態創建EntityManager

我目前已經在我的應用程序管理多個DB:

  • 一個唯一的管理數據庫(一個靜態名);
  • 一個客戶端數據庫,其名稱取決於客戶端。

我正在使用JPA,我想爲客戶端數據庫創建動態EntityManagers。但是,當我以編程方式創建此代碼時,出現此錯誤: javax.persistence.TransactionRequiredException:已在資源本地EntityManager上調用joinTransaction,該資源本地EntityManager無法註冊JTA事務。

下面是代碼:

@Stateful 
public class ServiceImpl implements Service{ 

private EntityManagerFactory emf; 
private EntityManager em; 

@PostConstruct  // automatically called when EJB constructed and session starts 
public void init() { 
    emf = Persistence.createEntityManagerFactory("punit"); 
    em = emf.createEntityManager(); 
} 
... 

而且

@Stateful(mappedName = "CustomerService") 
public class CustomerServiceImpl extends ServiceImpl implements CustomerService { 

@Override 
public void create(Customer cust) { 
    getEm().joinTransaction(); 
    getEm().persist(cust); 
} 

更一般地,我心中已經有問題JPA。我只想連接到兩個數據庫,對它們執行一些CRUD操作。但我真的不知道如何管理交易(我的第一種方法是讓容器管理它......)。

如果有人能幫助我,可能會很棒!

注意:我正在使用Glassfish Java EE服務器和PGSql DB。

回答

1

在jpa中,您可以在persistence.xml文件中聲明多個prsistenceunits。

在注射的時候,你可以做這樣的事情:

@PersistenceContext(unitName = "unitName0", properties={@PersistenceProperty(...)} 
EntityManager emClient; 

@PersistenceContext(unitName = "unitName1", properties={@PersistenceProperty(...)} 
EntityManager emAdmin; 

這樣,你不要有手動創建實體管理器,所以你得到的容器事務管理。

未測試:

如果您有動態數據庫的名稱,你會注入的EntityManagerFactory

@PersistenceContext(的unitName = 「名稱」) EntityManagerFactory的電動勢;

//在點你想了EntityManager

Map<String, String> props; //put the connection property for the EM here 
EntityManager em = emf.createEntityManager(props); 

基於這樣的事實,在一個J2EE環境中,我們使用數據源的概念,ConnectionPooling,這幾乎是不可能實現這種動態數據源,而不需要手工創建entitymanagerfactory。

這是我的推理: 服務器管理連接池,jpa提供程序(如eclipselink)使用jndi來確定與數據庫的連接。這意味着如果您要更改數據庫名稱,則它還必須具有連接池資源和關聯的jdbc資源。這會違揹你想要做的事情。

基本解決方案:手動創建EntityManagerFactory並手動管理事務。 在persistence xml中指定該單位是非jta以使其工作。

然後你就可以根據用戶會話程序提供連接數據:

東西這類的:

//這必須是特定的會話。

class PersistenceSession{ 
    static Map<String, String> clientSessionProps; 

    //When new session starts and a new client has logged in. 
    static void setClientConnectionProperties(Client client){ 
    ..... 
    } 

    static Map<String, String> getClientSessionProps(){ 
    return clientSessionProps; 
    } 
} 

在ejb級別。

@Stateless 
    public class TestEntityFacade extends AbstractFacade<TestEntity> { 


    private EntityManagerFactory emf; 

    @PostConstruct 
    void init(){ 
     emf = Persistence.createEntityManagerFactory("name"); 
    } 

    @Override 
    protected EntityManager getEntityManager() { 
      return emf.createEntityManager(PersistenceSession.getClientSessionProps()); 
    } 

    public TestEntityFacade() { 
     super(TestEntity.class); 
    } 

    void add(Entity e){ 

     EntityManager em = getEntityManager(); 
     em.getTransaction().begin(); 
     ..... 
     em.getTransaction().commit(); 
    } 

} 
+0

感謝您的回答。是的,我知道這種方法,但我不知道客戶端數據庫的名稱(數據庫的選擇將由應用程序完成)。所以我不能使用@PersistenceContext註釋,它需要一個靜態名稱。 – piro2

+0

檢查編輯?從注入的EMF創建EntityManager時,您可以在哪裏提供連接屬性?難道這不能解決動態表問題? – maress

+0

是的,我檢查了它。 但是,如果我注入一個EMF,我必須指出unitName屬性:我不能這樣做,因爲我不知道持久單元的名稱,這是一個動態參數 – piro2

0

其實很巧妙的方法如何做到這一點是CDI Producers,您可以定義將產生的任何數量的自定義實體管理器,看到的例子豆。

@SessionScoped 
public class EntityManagerProducer { 

    @Produces 
    @AdminDB 
    @PersistenceContext(unitName = "adminDB") 
    public EntityManaged adminDB; 

    @Produces 
    @UserDB 
    @PersistenceContext(unitName = "userDB") 
    public EntityManaged userDB; 
} 

這意味着,這兩個EntityManagers讓每一個用戶會話中創建的,比你可以用

@Inject 
@UserDB 
private EntityManager em; //if you want to use UserDB now 

@UserDB@AdminDB其注入到任何bean的是你自己定義的Qualifiers。這導致更簡單易讀的代碼。