Tenant_1 Tenant_2 ----------------------------------------------------- PU1: Database Schema_1 PU2: Database Schema_2META-INF/Persistence.xml:
<persistence xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_2.xsd" version="2.2"> <persistence-unit name="tenant_1" transaction-type="RESOURCE_LOCAL"> <non-jta-datasource>jndiDataSourceName_schema_1</non-jta-datasource> </persistence-unit> <persistence-unit name="tenant_2" transaction-type="RESOURCE_LOCAL"> <non-jta-datasource>jndiDataSourceName_schema_2</non-jta-datasource> </persistence-unit> </persistence>Create EntityManagerFactory and EntityManager for a tenant:
// tenant_1 EntityManagerFactory emf = Persistence.createEntityManagerFactory("tenant_1"); EntityManager em = emf.createEntityManager(); // tenant_2 EntityManagerFactory emf2 = Persistence.createEntityManagerFactory("tenant_2"); EntityManager em2 = emf2.createEntityManager();Adding a new tenant requires adding a persistence unit in META-INF/persistence.xml.
If a mapped superclass or entity class is configured as multitenant-enabled in an XML mapping file, its subclass entities will inherit the configuration as multitenant-enabled entities unless they are overridden.
For example, class hierarchy:
AbstractEntityBase ------------------------------------------ | | | | User Role Permission Employee / \ FullTimeEmployee PartTimeEmployeeorm.xml
<mapped-superclass class="com.cmobilecom.jpa.example.managed_classes.AbstractEntityBase"> <multitenant> <discriminator-column name="tenant_id" type="java.lang.Long" value-mapping-property="tenant.id" tenant-entity="com.cmobilecom.jpa.example.managed_classes.TenantImpl" attribute="tenant"/> </multitenant> </mapped-superclass> <entity class="com.cmobilecom.jpa.example.managed_classes.User"> <multitenant enabled="false"/> </entity> <entity class="com.cmobilecom.jpa.example.managed_classes.Role"> <multitenant> <discriminator-column nullable="true"/> </multitenant> </entity> <entity class="com.cmobilecom.jpa.example.managed_classes.Permission"> <multitenant enabled="false"/> </entity>The class hierarchy is configured as multitenant-enabled except the User and Permission entities.
The <multitenant> element defines multitenant configuration of a managed type hierarchy:
<persistence xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_2.xsd" version="2.2"> <persistence-unit name="tenant_1" transaction-type="RESOURCE_LOCAL"> <non-jta-datasource>jndiDataSourceName</non-jta-datasource> <properties> <property name="tenant.id" value="100"/> </properties> </persistence-unit> <persistence-unit name="tenant_2" transaction-type="RESOURCE_LOCAL"> <non-jta-datasource>jndiDataSourceName</non-jta-datasource> <properties> <property name="tenant.id" value="101"/> </properties> </persistence-unit> </persistence>Create EntityManagerFactory and EntityManager for a tenant:
// tenant_1 EntityManagerFactory emf = Persistence.createEntityManagerFactory("tenant_1"); EntityManager em = emf.createEntityManager(); // tenant_2 EntityManagerFactory emf2 = Persistence.createEntityManagerFactory("tenant_2"); EntityManager em2 = emf2.createEntityManager();
<persistence xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_2.xsd" version="2.2"> <persistence-unit name="all_tenants" transaction-type="RESOURCE_LOCAL"> <non-jta-datasource>jndiDataSourceName</non-jta-datasource> </persistence-unit> </persistence>Create EntityManagerFactory and EntityManager for a tenant:
// tenant_1 Map<String, Object> tenant1_properties = new HashMap<>(); tenant1_properties.put("tenant.id", 100); EntityManagerFactory emf = Persistence.createEntityManagerFactory("all_tenants", tenant1_properties); EntityManager em = emf.createEntityManager(); // tenant_2 Map<String, Object> tenant2_properties = new HashMap<>(); tenant2_properties.put("tenant.id", 101); EntityManagerFactory emf2 = Persistence.createEntityManagerFactory("all_tenants", tenant2_properties); EntityManager em2 = emf2.createEntityManager();Tenant discriminator column values set in an EntityManagerFactory must not be changed.
<persistence xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_2.xsd" version="2.2"> <persistence-unit name="all_tenants" transaction-type="RESOURCE_LOCAL"> <non-jta-datasource>jndiDataSourceName</non-jta-datasource> </persistence-unit> </persistence>Create one EntityManagerFactory shared by all tenants, and create one EntityManager for each tenant:
EntityManagerFactory emf = Persistence.createEntityManagerFactory("all_tenants"); // tenant_1 Map<String, Object> tenant1_properties = new HashMap<>(); tenant1_properties.put("tenant.id", 100); EntityManager em = emf.createEntityManager(tenant1_properties); // tenant_2 Map<String, Object> tenant2_properties = new HashMap<>(); tenant2_properties.put("tenant.id", 101); EntityManager em = emf.createEntityManager(tenant2_properties);Tenant discriminator column values set in an EntityManager must not be changed. An EntityManager for one tenant must not be reused for a different tenant because
The other option is application-managed multitenancy. When all multitenant-enabled entity types(except dependent entities) have id generators to ensure that no two entities from different tenants have the same identifier in a persistence context, this option will be better since it uses the same persistence unit and will not build a new metamodel.
Define multitenant attribute in multitenant configuration and set the property Constants.MULTITENANT_APP_MANAGED to true in EntityManager to enable application-managed multitenancy. The property can also be set in EntityManagerFactory or persistence.xml if appropriate, and shared by all EntityManagers. The property must not be changed once set.
When the attribute of multitenant configuration is specified, the attribute will be managed as a regular entity attribute by Cmobilecom-JPA and be a part of metamodel.
For application-managed multitenancy, PersistenceException will be thrown if a multitenant-enabled entity type does not have unique entity identifiers among all tenants, or if its multitenant attribute is not defined. To enforce this when building metamodel, set property "com.cmobilecom.jpa.multitenant.appManaged.supported" to true in persistence.xml or when creating EntityManagerFactory. The property name is defined as Constants.MULTITENANT_APP_MANAGED_SUPPORTED.
<entity-mappings xmlns="http://xmlns.jcp.org/xml/ns/persistence/orm" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence/orm cmobilecom-jpa-orm_1_0.xsd" version="2.2"> <mapped-superclass class="com.cmobilecom.jpa.example.managed_classes.AbstractEntityBase" > <multitenant> <discriminator-column name="tenant_id" type="java.lang.Long" value-mapping-property="tenant.id" tenant-entity="com.cmobilecom.jpa.example.managed_classes.TenantImpl" attribute="tenant"/> </multitenant> </mapped-superclass> </entity-mappings>Managed classes:
@MappedSuperclass abstract public class AbstractEntityBase { @Transient private Tenant tenant; public Tenant getTenant() { return this.tenant; } public void setTenant(Tenant tenant) { this.tenant = tenant; } ... } public interface Tenant { public Long getId(); public String getName(); } @Entity public class Employee extends AbstractEntityBase { @Id @GeneratedValue(strategy = GenerationType.AUTO) private String id; @Column(nullable=false, length=20) private String name; } @Entity public class TenantImpl implements Tenant { @Id private Long id; @Column(nullable=false, length=30) private String name; // getters and setters }The classes above may be separated in different modules. The attribute "tenant" is defined as transient, but it will be managed as a regular attribute.
Example code for application-managed multitenancy:
EntityManager em = emf.createEntityManager(); em.setProperty(Constants.MULTITENANT_APP_MANAGED, true); // persist an entity of Tenant 1 Employee employeeA = new Employee(); employeeA.setName("John A"); employeeA.setTenant(em.getReference(TenantImpl.class, 1L)); em.persist(employeeA); // persist an entity of Tenant 2 Employee employeeB = new Employee(); employeeB.setName("John B"); employeeB.setTenant(em.getReference(TenantImpl.class, 2L)); em.persist(employeeB); // query entities from all tenants TypedQuery<Employee> query = em.createQuery( "select e from Employee e where e.name like 'John%'", Employee.class); List<Employee> employees = query.getResultList(); // query entities from one tenant TypedQuery<Employee> query = em.createQuery( "select e from Employee e where e.tenant.id=1 and e.name like 'John%'", Employee.class); List<Employee> employees = query.getResultList();
Retrieved employees will have tenant attribute values fetched. e.g.,
id name tenant ------------------------------ 1 John A Tenant 1 2 John B Tenant 2
For separated-dataSource multitenancy or shared-table multitenancy with tenant discriminator column values specified in META-INF/persistence.xml or EntityManagerFactory, entities will be normally cached in 2nd-level cache according to caching configuration and hints since each tenant has its own cache.
For shared-table multitenancy with a shared EntityManagerFactory, the 2nd-level cache is shared among all tenants. If an entity type is multitenant enabled, but it does not have unique entity identifiers among all tenants, entities of the entity type will not be cached.
EntityManager em = emf.createEntityManager(); em.setProperty(Constants.MULTITENANT_APP_MANAGED, true); // tenant 1 Map<String, Object> tenant1Hints = new HashMap<>(); // hints: multitenant JPA managed tenant1Hints.put("tenant.id", 1L); tenant1Hints.put(Constants.MULTITENANT_APP_MANAGED, false); tenant1Hints.put(Constants.MULTITENANT_FETCH_TENANT_ATTR, true); // hints for persist/merge Tenant tenant1 = em.getReference(TenantImpl.class, 1L) tenant1Hints.put(Constants.MULTITENANT_TENANT, tenant1); tenant1Hints.put(Constants.MULTITENANT_TENANT_SKIP_MANAGED, true); // persist an entity of Tenant 1 Employee employeeA = new Employee(); employeeA.setName("John A"); ExtendedEntityManager extendedEntityManager = em.unwrap(ExtendedEntityManager.class); extendedEntityManager.persist(employeeA, tenant1Hints); ... // persist an entity of Tenant 1 employeeA.setName("John A Jr."); extendedEntityManager.merge(employeeA, tenant1Hints); // query entities of Tenant 1 TypedQuery<Employee> query = em.createQuery("select e from Employee e", Employee.class); for (String hint : tenant1Hints.keySet()) query.setHint(hint, tenant1Hints.get(hint)); List<Employee> resultList = query.getResultList();The same EntityManager can manage entities of different tenants using different tenant hints, and entities of different tenants can be persisted or updated in one same transaction. Make sure that entities have unique identifiers among all tenants.