【发布时间】:2015-04-08 17:36:21
【问题描述】:
我正在尝试在 JPA/EclipseLink/EJB3/JSF 2.2 应用程序中使用 Table per tenant/Schema 策略实现多租户。
我正在进行的测试是针对一个 mysql 应用程序(其中创建了 3 个数据库)一个所有租户共有的系统数据库,以及每个租户的相同数据库。
系统数据库只有一张表tenant_user(登录名、密码、schema_name)
这里是主要代码的 sn-ps : 登录 bean(JSF 托管 bean):
public void loginUser(){
UserTenant userTenant = systemFacade.getUserTenantByUserDetails(login, password);
System.out.println("User " + userTenant.getLogin() + " got connected ..");
emProvider.getEm(userTenant.getSchemaName());
achatFacade.setTenantId(userTenant.getSchemaName());
Achat achat = new Achat();
achat.setName("new achat");
achatFacade.create(achat);
}
@EJB
private IEntityManagerProvider emProvider;
@EJB
private SystemFacadeLocal systemFacade;
@EJB
private AchatFacadeLocal achatFacade;
EntityManagerProvider
@Singleton
public class EntityManagerProvider implements IEntityManagerProvider {
@PersistenceUnit(unitName = "multi")
private EntityManagerFactory emf;
private final Map<String, EntityManager> emMap = new HashMap<>();
public EntityManager getEm(String tenantId) {
if (emMap.containsKey(tenantId)) {
return emMap.get(tenantId);
}
return createEm(tenantId);
}
private EntityManager createEm(String tenantId) {
Map<String, String> props = new HashMap<>();
props.put("eclipselink.tenant-id", tenantId);
EntityManager em = emf.createEntityManager(props);
emMap.put(tenantId, em);
return em;
}
AchatFacade:
@Stateless
public class AchatFacade extends AbstractFacade implements AchatFacadeLocal {
@EJB
IEntityManagerProvider emProvider;
@Resource
SessionContext context;
private String tenantId;
@Override
protected EntityManager getEntityManager() {
return emProvider.getEm(tenantId);
}
@Override
public void setTenantId(String tenantId) {
this.tenantId = tenantId;
}
}
我的持久化单元:
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
<persistence-unit name="systemPU" transaction-type="JTA">
<provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
<jta-data-source>jdbc/si_system</jta-data-source>
<exclude-unlisted-classes>false</exclude-unlisted-classes>
<shared-cache-mode>NONE</shared-cache-mode>
<properties>
<property name="hibernate.show_sql" value="true"/>
<property name="hibernate.format_sql" value="true"/>
<property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect"/>
</properties>
</persistence-unit>
<persistence-unit name="multi" transaction-type="JTA">
<provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
<jta-data-source>jdbc/multi</jta-data-source>
<exclude-unlisted-classes>false</exclude-unlisted-classes>
<shared-cache-mode>NONE</shared-cache-mode>
</persistence-unit>
</persistence>
在我的 JSF 托管 bean 中,我从 user_tenant 表中检索用户租户,并设置了 EntityManagerProvider 中的模式名称的tenantId。 我还在作为 Singleton EJB 的 EntityManagerProvider 中创建具有相应租户 ID 的 EntityManager。因此,我有一张 EntityManagers 的地图。 最后,我在 Achat 的多租户实体上测试持久性:
package com.intercom.model;
import java.io.Serializable;
import javax.persistence.Basic;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.validation.constraints.Size;
import javax.xml.bind.annotation.XmlRootElement;
import org.eclipse.persistence.annotations.Multitenant;
import org.eclipse.persistence.annotations.MultitenantType;
import org.eclipse.persistence.annotations.TenantTableDiscriminator;
import org.eclipse.persistence.annotations.TenantTableDiscriminatorType;
/**
*
* @author
*/
@Entity
@Table(name = "achat")
@XmlRootElement
@Multitenant(value = MultitenantType.TABLE_PER_TENANT)
@TenantTableDiscriminator(type = TenantTableDiscriminatorType.SCHEMA, contextProperty = "eclipselink.tenant-id")
public class Achat implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Basic(optional = false)
@Column(name = "id")
private Integer id;
@Size(max = 45)
@Column(name = "name")
private String name;
@Override
public int hashCode() {
int hash = 0;
hash += (id != null ? id.hashCode() : 0);
return hash;
}
@Override
public boolean equals(Object object) {
// TODO: Warning - this method won't work in the case the id fields are not set
if (!(object instanceof Achat)) {
return false;
}
Achat other = (Achat) object;
if ((this.id == null && other.id != null) || (this.id != null && !this.id.equals(other.id))) {
return false;
}
return true;
}
@Override
public String toString() {
return "com.intercom.itManager.entities.Achat[ id =" + id + " ]";
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
测试结果: 用户在其登录名和密码后连接。 租户已解决 创建实体管理器,填充地图。 已达到 Achat 实体上的持久性,但没有任何内容保存在任何租户数据库中。
什么代码可能导致问题?
【问题讨论】:
-
你没有展示你是如何持久化或提交你的事务的,或者与事务有关的任何事情。尝试调用flush,因为如果Persistence 单元没有加入事务,它会立即抛出异常。
-
好吧,我想我可以使用容器管理的实体管理器,但是在多租户应用程序中,我必须自己创建我的实体管理器(EntityManagerProvide 代码),所以我必须自己打开一个事务并提交它。现在很好,我将 BMT 与我的自定义实体管理器一起使用。
-
但是有没有办法在所有 EJB 之间共享tenantId?我不想随时传递tenantId,从我的 JSF 托管 Bean 到 EJB。
标签: java jpa eclipselink ejb-3.0