Die JSRs 342 (Java EE 7) und 344 (JPA 2.1) definieren Mandantenfähigkeit (multi-tenancy) explizit als eines der Ziele der JSRs. Im Herbst 2012 wurde entschieden, dieses Feature
aus Zeitgründen in die nachfolgenden Spezifikationen zu verschieben.
Muss man deshalb auf Mandantenfähigkeit in der Persistenzschicht verzichten?
Nein! Hibernate, EcliseLink und OpenJPA bieten jeweils proprietäre JPA-Erweiterungen zur Mandantenfähigkeit an, die in diesem Talk vorgestellt werden.
3. Auszug aus JSR 342: Java EE 7
Section 2: Request
. . .
As a result, Java EE 7 products will be able to more easily operate
on private or public clouds and deliver their functionality as a
service with support for features such as multitenancy and
elasticity (horizontal scaling). Applications written for Java EE 7
will be better able to take advantage of the benefits of a cloud
environment.
. . .
Similarly, a single application can be used by multiple tenants with
the same security, isolation, and quality of service guarantees.
. . .
Bernd M¨uller, 5.11.2013 3/62
4. Auszug aus JSR 342: Java EE 7 (cont’d)
Section 2: Request
. . .
This JSR may also establish a set of constraints that applications
that wish to take advantage of PaaS-specific features, such as
multitenancy, may have to obey and provide a way for applications
to identify themselves as designed for cloud environments.
. . .
In particular, all resource manager-related APIs, such as JPA,
JDBC and JMS, will be updated to enable multitenancy.
. . .
Bernd M¨uller, 5.11.2013 4/62
5. Auszug aus JSR 338: JPA 2.1
Section 2: Request
. . .
Support for multitenancy.
. . .
Bernd M¨uller, 5.11.2013 5/62
6. Hasta la vista Mandantenf¨ahigkeit?
Bernd M¨uller, 5.11.2013 6/62
7. Hasta la vista Mandantenf¨ahigkeit?
Nein!
Bernd M¨uller, 5.11.2013 6/62
11. Motivation
Cloud-motiviert: XaaS (Iaas, PaaS, SaaS)
Kontinuum: Isolated Data vs Shared Data
Technische, kommerzielle und juristische Aspekte
Bernd M¨uller, 5.11.2013 8/62
12. Die technische Sicht
Daten eines Mandanten in . . .
Eigene DB Eigenes
Schema
Gemeinsames
Schema
Bernd M¨uller, 5.11.2013 9/62
13. Getrennte Datenbanken
Einfachster Ansatz, um Daten zu isolieren
Gemeinsamer Anwendungs-Code, aber jeder Mandant hat
eigene DB
Metadaten verbinden Anwendung mit DB des Mandanten
Java EE: Deployment-Frage, CDI-Erweiterung, . . .
Mandant
0815
Mandant
4711
Mandant
1234
Bernd M¨uller, 5.11.2013 10/62
14. Gemeinsame Datenbank, verschiedene Schemata
Mehrere Mandanten in derselben Datenbank
Jeder Mandant eigene Menge von Tabellen in
Schema, getrennt von
anderen Schemata
Mandant
0815
Mandant
4711
Mandant
1234
Bernd M¨uller, 5.11.2013 11/62
15. Gemeinsame Datenbank, gemeinsames Schema
Selbe Datenbank und selbes Schema, um Daten f¨ur alle
Mandanten zu halten
Spalte mit Mandanten-ID (Diskriminator) bindet Datensatz
an Mandant
Mandant Produkt Anzahl
0815
4711
1234
.....
Mandant Kunde Adresse
0815
4711
1234
.....
Mandant Lieferung Datum
0815
4711
1234
3826427
8327423
8832173
2013-07-04
2013-06-23
2013-01-01
..... ..... .....
Bernd M¨uller, 5.11.2013 12/62
17. Mandantenf¨ahigkeit mit Hibernate – Die Alternativen
Discriminator und Hibernate Filter
Hibernate Shards
Separate Schemata mit Multitenancy-API
Informationsquellen:
http://relation.to/Bloggers/MultitenancyInHibernate
https://community.jboss.org/wiki/Multi-tenancyDesign mit
Referenz auf PDF, das im Folgenden verwendet wird
Video http://vimeo.com/20296211
Hibernate Repository und Dokumentation
Bernd M¨uller, 5.11.2013 14/62
18. Multi-tenancy in Hibernate | Steve Ebersole
Discriminator Entity
@Entity
@FilterDef( name=”tenantFilter”, parameters=@ParamDef( name="tenantId", type="string" ) )
@Filters(
@Filter( name=”tenantFilter”, condition=”tenant_id = :tenantId” )
)
public class Customer {
@Id
private Long id;
@Column( name=”tenant_id”, nullable=false, updateable=false )
private String tenantId;
...
}
@Entity
@FilterDef( name=”tenantFilter”, parameters=@ParamDef( name="tenantId", type="string" ) )
@Filters(
@Filter( name=”tenantFilter”, condition=”tenant_id = :tenantId” )
)
public class Customer {
@Id
private Long id;
@Column( name=”tenant_id”, nullable=false, updateable=false )
private String tenantId;
...
}
19. Multi-tenancy in Hibernate | Steve Ebersole
Discriminator Usage
public void demonstrateUsageInDiscriminatorScenario() {
// One thing that must be done on all sessions is to enable the tenantbased Hibernate filter
// we defined before on the entity. This is usually best handled by some form of “request
// interceptor” (HttpServletRequestFilter, etc) to make sure it is done uniformly
// Creating an entity
Session session = openSession();
session.enableFilter( “tenantFilter” ).setParameter( “tenantId”, “sometenantidentifier” );
session.beginTransaction();
Customer customer = new Customer();
...
customer.setTenantId( “sometenantidentifier” );
session.persist( customer );
session.getTransaction().commit();
session.close();
// Querying Customers
session = openSession();
session.enableFilter( “tenantFilter” ).setParameter( “tenantId”, “sometenantidentifier” );
session.beginTransaction();
Customer customer = (Customer) session.createQuery( “from Customer” ).uniqueResult();
session.getTransaction().commit();
session.close();
}
public void demonstrateUsageInDiscriminatorScenario() {
// One thing that must be done on all sessions is to enable the tenantbased Hibernate filter
// we defined before on the entity. This is usually best handled by some form of “request
// interceptor” (HttpServletRequestFilter, etc) to make sure it is done uniformly
// Creating an entity
Session session = openSession();
session.enableFilter( “tenantFilter” ).setParameter( “tenantId”, “sometenantidentifier” );
session.beginTransaction();
Customer customer = new Customer();
...
customer.setTenantId( “sometenantidentifier” );
session.persist( customer );
session.getTransaction().commit();
session.close();
// Querying Customers
session = openSession();
session.enableFilter( “tenantFilter” ).setParameter( “tenantId”, “sometenantidentifier” );
session.beginTransaction();
Customer customer = (Customer) session.createQuery( “from Customer” ).uniqueResult();
session.getTransaction().commit();
session.close();
}
20. Beispiel: Entity Kunde mit Filterdefinition
@Entity
@FilterDef(name = " kundeMandantFilter ",
parameters = @ParamDef(name = "mandant",
type = "string"))
@Filter(name = " kundeMandantFilter ",
condition = "mandant = :mandant")
public class Kunde implements Serializable {
@Id @GeneratedValue
private Integer id;
...
private String mandant;
Bernd M¨uller, 5.11.2013 17/62
22. Fazit Diskriminator und Filter
Scheint aufwendig (verglichen mit Alternativen)
Definition aus Handbuch:
”
A filter criteria allows you to define
a restriction clause similar to the existing where attribute ...“
Verwendung in Where-Clause, nicht bei Schreibvorg¨angen
¨Ahnlich DB-Views
Wird von JBoss nicht weiter als Mandantenf¨ahigkeit
propagiert
Evtl. Missbrauch der Filter-Idee?
Bernd M¨uller, 5.11.2013 19/62
23. Hibernate Shards
Eigenes Hibernate-Unterprojekt Shards
Erlaubt
”
Horizontal Partitioning“ d.h. Verteilung von
Datens¨atzen auf mehrere Datenbanken
Siehe http://www.hibernate.org/subprojects/shards.html
Scheinbar nicht mehr weiterentwickelt
Kein GA-Release, Beta datiert von 2007
Also: besser nicht verwenden
Bernd M¨uller, 5.11.2013 20/62
24. Multi-tenancy in Hibernate | Steve Ebersole
Separate Schema ConnectionProvider
public class MyTenantAwareConnectionProvider implements ConnectionProvider {
public static final String BASE_JNDI_NAME = "java:/comp/env/jdbc/";
public Connection getConnection() throws SQLException {
final String tenantId = TenantContext.getTenantId()
final String tenantDataSourceName = BASE_JNDI_NAME + tenantId;
DataSource tenantDataSource = JndiHelper.lookupDataSource( tenantDataSourceName );
return tenantDataSource.getConnection();
}
public void closeConnection(Connection conn) throws SQLException {
conn.close();
}
public boolean supportsAggressiveRelease() {
// so long as the tenant identifier remains available in TenantContext throughout, we can
// reacquire later
return true;
}
public void configure(Properties props) {
// currently nothing to do here
}
public close() {
// currently nothing to do here
}
}
public class MyTenantAwareConnectionProvider implements ConnectionProvider {
public static final String BASE_JNDI_NAME = "java:/comp/env/jdbc/";
public Connection getConnection() throws SQLException {
final String tenantId = TenantContext.getTenantId()
final String tenantDataSourceName = BASE_JNDI_NAME + tenantId;
DataSource tenantDataSource = JndiHelper.lookupDataSource( tenantDataSourceName );
return tenantDataSource.getConnection();
}
public void closeConnection(Connection conn) throws SQLException {
conn.close();
}
public boolean supportsAggressiveRelease() {
// so long as the tenant identifier remains available in TenantContext throughout, we can
// reacquire later
return true;
}
public void configure(Properties props) {
// currently nothing to do here
}
public close() {
// currently nothing to do here
}
}
25. Multi-tenancy in Hibernate | Steve Ebersole
Separate Schema Usage
public void demonstrateUsageInSeparateSchemaScenario() {
// The ConnectionProvider we saw earlier is registered with the SessionFactory as the means
// for Hibernate to acquire Connections as needed for the Session. Here we must push the
// tenantid to the TenantContext so it is available to the ConnectionProvider. This is usually
// best handled by some form of “request interceptor” (HttpServletRequestFilter, etc) to make
// sure it is done uniformly
TenantContext.setTenantId( “sometenantidentifier” );
// Creating an entity
Session session = openSession();
session.beginTransaction();
Customer customer = new Customer();
...
session.persist( customer );
session.getTransaction().commit();
session.close();
// Querying Customers
session = openSession();
session.beginTransaction();
Customer customer = (Customer) session.createQuery( “from Customer” ).uniqueResult();
session.getTransaction().commit();
session.close();
}
public void demonstrateUsageInSeparateSchemaScenario() {
// The ConnectionProvider we saw earlier is registered with the SessionFactory as the means
// for Hibernate to acquire Connections as needed for the Session. Here we must push the
// tenantid to the TenantContext so it is available to the ConnectionProvider. This is usually
// best handled by some form of “request interceptor” (HttpServletRequestFilter, etc) to make
// sure it is done uniformly
TenantContext.setTenantId( “sometenantidentifier” );
// Creating an entity
Session session = openSession();
session.beginTransaction();
Customer customer = new Customer();
...
session.persist( customer );
session.getTransaction().commit();
session.close();
// Querying Customers
session = openSession();
session.beginTransaction();
Customer customer = (Customer) session.createQuery( “from Customer” ).uniqueResult();
session.getTransaction().commit();
session.close();
}
26. Multi-tenancy in Hibernate | Steve Ebersole
Support in Hibernate 4
● Exact API still under discussion[1]
● Public API options:
– Session.setTenantId(String tenantId)
– Passed as part of opening a Session
● Transparently handled by Hibernate
[1] http://opensource.atlassian.com/projects/hibernate/browse/HHH5697
27. Manual (4.2): 16.3. Multi-tenancy in Hibernate
Konfiguration mit Property hibernate.multiTenancy
Bernd M¨uller, 5.11.2013 24/62
28. Manual (4.2): 16.3. Multi-tenancy in Hibernate
Konfiguration mit Property hibernate.multiTenancy
org.hibernate.MultiTenancyStrategy ist Enum mit
Bernd M¨uller, 5.11.2013 24/62
29. Manual (4.2): 16.3. Multi-tenancy in Hibernate
Konfiguration mit Property hibernate.multiTenancy
org.hibernate.MultiTenancyStrategy ist Enum mit
NONE
(the default) No multi-tenancy is expected. . . .
Bernd M¨uller, 5.11.2013 24/62
30. Manual (4.2): 16.3. Multi-tenancy in Hibernate
Konfiguration mit Property hibernate.multiTenancy
org.hibernate.MultiTenancyStrategy ist Enum mit
NONE
(the default) No multi-tenancy is expected. . . .
SCHEMA
Correlates to the separate schema approach. . . . Additionally, a
...MultiTenantConnectionProvider must be specified.
Bernd M¨uller, 5.11.2013 24/62
31. Manual (4.2): 16.3. Multi-tenancy in Hibernate
Konfiguration mit Property hibernate.multiTenancy
org.hibernate.MultiTenancyStrategy ist Enum mit
NONE
(the default) No multi-tenancy is expected. . . .
SCHEMA
Correlates to the separate schema approach. . . . Additionally, a
...MultiTenantConnectionProvider must be specified.
DATABASE
Correlates to the separate database approach. . . . Additionally,
a ...MultiTenantConnectionProvider must be specified.
Bernd M¨uller, 5.11.2013 24/62
32. Manual (4.2): 16.3. Multi-tenancy in Hibernate
Konfiguration mit Property hibernate.multiTenancy
org.hibernate.MultiTenancyStrategy ist Enum mit
NONE
(the default) No multi-tenancy is expected. . . .
SCHEMA
Correlates to the separate schema approach. . . . Additionally, a
...MultiTenantConnectionProvider must be specified.
DATABASE
Correlates to the separate database approach. . . . Additionally,
a ...MultiTenantConnectionProvider must be specified.
DISCRIMINATOR
Correlates to the partitioned (discriminator) approach. . . . This
strategy is not yet implemented in Hibernate as of 4.0 and 4.1.
Its support is planned for 5.0.
Bernd M¨uller, 5.11.2013 24/62
33. Stand heute (4.3.0.Beta3)
Die (f¨ur Hibernate 4) genannte Methode
Session.setTenantId() existiert nicht
Die Unit-Tests f¨ur Separate Schema verwenden 2
Datenbanken nicht 2 Schemata
Keine Realisierung f¨ur Separate Schema m¨oglich,
evtl. Problem des Autors
Bernd M¨uller, 5.11.2013 25/62
35. OpenJPAs Teilprojekt Slice
Keine direkte Mandantenf¨ahigkeit, sondern
Verteilte Persistenz mit Slice
Slice:
”
Slice is a module for distributed persistence in
OpenJPA. Slice enables an application developed for a single
database to adapt to a distributed, horizontally partitioned,
possibly heterogeneous, database environment. This all occurs
without any change in the original application code or the
database schema. See how to leverage this flexibility for your
own applications, especially those destined for the cloud or
Software as a Service.“
Bernd M¨uller, 5.11.2013 27/62
36. OpenJPAs Teilprojekt Slice
Keine direkte Mandantenf¨ahigkeit, sondern
Verteilte Persistenz mit Slice
Slice:
”
Slice is a module for distributed persistence in
OpenJPA. Slice enables an application developed for a single
database to adapt to a distributed, horizontally partitioned,
possibly heterogeneous, database environment. This all occurs
without any change in the original application code or the
database schema. See how to leverage this flexibility for your
own applications, especially those destined for the cloud or
Software as a Service.“
Bernd M¨uller, 5.11.2013 27/62
38. Slice’ Features
Transparency
Scaling
Distributed Query
Data Distribution
Data Replication
Heterogeneous Database
Distributed Transaction
Collocation Constraint
Damit kann man fast alles machen, auch Mandantenf¨ahigkeit
Bernd M¨uller, 5.11.2013 28/62
48. Beispiel: Ein einfacher Kunde
@Entity
public class Kunde {
@Id @GeneratedValue
private Integer id;
private String vorname;
private String nachname;
@Temporal(TemporalType.DATE)
private Date geburtsdatum;
...
Bernd M¨uller, 5.11.2013 35/62
49. Beispiel: Verteilung nach Entity-Typ
public class DistributionPolicyMandant
implements DistributionPolicy {
public String distribute(Object entity ,
List <String > slices ,
Object context) {
if (entity instanceof Kunde) {
return "mandant1";
} else {
return "mandant2";
}
}
Bernd M¨uller, 5.11.2013 36/62
50. Beispiel: Verteilung nach Anwendungsdaten
public class DistributionPolicyMandant
implements DistributionPolicy {
public String distribute(Object entity ,
List <String > slices , Object context) {
if (entity instanceof Kunde) {
char anfangsbuchstabe =
(( Kunde) entity ). getNachname (). toCharArray ()[0];
if (’A’ <= anfangsbuchstabe
&& anfangsbuchstabe < ’M’) {
return "mandant1";
} else if (’M’ <= anfangsbuchstabe
&& anfangsbuchstabe < ’T’) {
return "mandant2";
} else {
return "mandant3";
}
} else {
return "default";
}
}
51. Beispiel: Verteilung mit zus¨atzlichem Mandanten-Property
public class DistributionPolicyMandant
implements DistributionPolicy {
public String distribute(Object entity ,
List <String > slices ,
Object context) {
if (entity instanceof Kunde) {
// ’mandant ’ Property der Entity -Klasse
return (( Kunde) entity ). getMandant ();
} else {
return "default";
}
}
Bernd M¨uller, 5.11.2013 38/62
52. Assoziationen
@OneToOne, @OneToMany, @ManyToOne, @ManyToMany mit
Cascadierungsoption PERSIST automatisch im selben Slice,
Distibution-Policy wird ignoriert
Damit Queries effizient auf einer Datenbank
Join ¨uber mehrere Datenbanken wird nicht unterst¨utzt
Bernd M¨uller, 5.11.2013 39/62
53. Queries
public class QueryPolicyMandant implements
QueryTargetPolicy {
public String [] getTargets(String query ,
Map <Object , Object > params , String language ,
List <String > slices , Object context) {
return ...
}
}
Abh¨angig von query und params entscheiden, welche Slices
zu verwenden sind
Bernd M¨uller, 5.11.2013 40/62
56. Queries (cont’d)
Auch order by m¨oglich
In jeder DB auf SQL-Ebene sortiert
Dann Merge-Sort in VM
Bernd M¨uller, 5.11.2013 41/62
57. Queries (cont’d)
Auch order by m¨oglich
In jeder DB auf SQL-Ebene sortiert
Dann Merge-Sort in VM
Aggregate (COUNT, MIN, MAX, SUM) m¨oglich, AVG nicht
Bernd M¨uller, 5.11.2013 41/62
58. Replication-Policy
Analog zu Distribution-Policy mit String[]
Sinnvoll f¨ur Aufz¨ahlungstypen (z.B. Enums W¨ahrung,
Nationalit¨at)
Oder nat¨urlich f¨ur einfache Replikationsmechanismen
Bernd M¨uller, 5.11.2013 42/62
59. Fazit OpenJPA
Allgemeine M¨oglichkeit Entity-Daten zu verteilen
Kann f¨ur Mandantenf¨ahigkeit verwendet werden
Keine Aussagen ¨uber Performanz und Verwendbarkeit bei
stark verflochtenen Objektmodellen
Reihe von Einschr¨ankungen: AVG(), Join ¨uber mehrere
Datenbanken, . . .
Ist ein Versuch wert
Bernd M¨uller, 5.11.2013 43/62
66. Beispiel: Ein Kunde kann mehrere Konten haben ...
@Entity
@Multitenant
public class Kunde {
@Id @GeneratedValue
private Integer id;
private String vorname;
private String nachname;
@Temporal(TemporalType.DATE)
private Date geburtsdatum;
@OneToMany(mappedBy = "kunde",
cascade = CascadeType.ALL)
private List <Konto > konten;
...
Bernd M¨uller, 5.11.2013 50/62
73. Gemappter Tenant
Muss read-only sein
@Entity
@Multitenant
@TenantDiscriminatorColumn (name = "mandant")
public class KundeMapped {
@Id @GeneratedValue
private Integer id;
...
// Die gemappte Diskriminatorspalte :
@Column(insertable = false , updatable = false)
private String mandant;
...
Bernd M¨uller, 5.11.2013 55/62
74. Alternativen
@Entity
@Multitenant( MultitenantType . TABLE_PER_TENANT )
@TenantTableDiscriminator (type =
TenantTableDiscriminatorType .SCHEMA)
public class Kunde {
...
Default: Tabelle mit Suffix der Tenant-Id
Mehrere Schemata m¨oglich mit type = SCHEMA
Keine DDL-Generierung
Bernd M¨uller, 5.11.2013 56/62
76. Verwendung in Java EE (cont’d)
@Singleton
@Startup
public class KundeService {
@PersistenceContext (unitName = "tenant -1")
EntityManager em1;
@PersistenceContext (unitName = "tenant -2")
EntityManager em2;
@PostConstruct
public void init (){
em1.persist(new Kunde("Barth", "Mario", "1.11.1972"));
em1.persist(new Kunde("Michael", "Mittermeier", "3.4.196
em2.persist(new Kunde("Anke", "Engelke", "21.12.1965"));
em2.persist(new Kunde("Dieter", "Nuhr", "29.10.1960"));
}
}
77. Alternative: Qualifier mit CDI
public class DatabaseProducer {
@Produces
@PersistenceContext (unitName = "tenant -1")
@Tenant1PC
private EntityManager em1;
@Produces
@PersistenceContext (unitName = "tenant -2")
@Tenant2PC
private EntityManager em2;
}
Verwendung: @Inject mit Qualifier
Mit eigener CDI-Erweiterung v¨ollig transparent und flexibel
verwendbar
Bernd M¨uller, 5.11.2013 59/62
78. Fazit EclipseLink
Am weitesten fortgeschritten
Mit Annotationen: `a la Java-EE
Alle drei (vier!) Alternativen werden unterst¨utzt
Bernd M¨uller, 5.11.2013 60/62
79. Vergleich/Einsch¨atzung der drei Provider
Achtung: meine pers¨onliche Meinung!
Und gilt nur f¨ur Mandantenf¨ahigkeit!
Bernd M¨uller, 5.11.2013 61/62
80. Vergleich/Einsch¨atzung der drei Provider
Achtung: meine pers¨onliche Meinung!
Und gilt nur f¨ur Mandantenf¨ahigkeit!
Hibernate: im Augenblick besser nicht verwenden
OpenJPA: verwendbar
EclipseLink: scheint rund, ausgereift und verwendbar
Bernd M¨uller, 5.11.2013 61/62