SlideShare ist ein Scribd-Unternehmen logo
Hasta la vista Mandantenfähigkeit?
Vorstellung Referent
Prof. Informatik (Ostfalia, HS Braunschweig/Wolfenb¨uttel)
Buchautor (JSF, JPA, Seam, . . . )
Mitglied EGs JSR 344 (JSF 2.2) und JSR 338 (JPA 2.1)
Gesch¨aftsf¨uhrer PMST GmbH
. . .
Bernd M¨uller, 5.11.2013 2/62
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
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
Auszug aus JSR 338: JPA 2.1
Section 2: Request
. . .
Support for multitenancy.
. . .
Bernd M¨uller, 5.11.2013 5/62
Hasta la vista Mandantenf¨ahigkeit?
Bernd M¨uller, 5.11.2013 6/62
Hasta la vista Mandantenf¨ahigkeit?
Nein!
Bernd M¨uller, 5.11.2013 6/62
Was bedeutet Mandantenf¨ahigkeit?
— Hier nur die Datensicht —
Bernd M¨uller, 5.11.2013 7/62
Motivation
Cloud-motiviert: XaaS (Iaas, PaaS, SaaS)
Bernd M¨uller, 5.11.2013 8/62
Motivation
Cloud-motiviert: XaaS (Iaas, PaaS, SaaS)
Kontinuum: Isolated Data vs Shared Data
Bernd M¨uller, 5.11.2013 8/62
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
Die technische Sicht
Daten eines Mandanten in . . .
Eigene DB Eigenes
Schema
Gemeinsames
Schema
Bernd M¨uller, 5.11.2013 9/62
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
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
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
Mandantenf¨ahigkeit mit Hibernate
Bernd M¨uller, 5.11.2013 13/62
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
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;
    ...
}
Multi-tenancy in Hibernate | Steve Ebersole
Discriminator ­ Usage
public void demonstrateUsageInDiscriminatorScenario() {
    // One thing that must be done on all sessions is to enable the tenant­based 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”, “some­tenant­identifier” );
    session.beginTransaction();
    Customer customer = new Customer();
    ...
    customer.setTenantId( “some­tenant­identifier” );
    session.persist( customer );
    session.getTransaction().commit();
    session.close();
    // Querying Customers
    session = openSession();
    session.enableFilter( “tenantFilter” ).setParameter( “tenantId”, “some­tenant­identifier” );
    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 tenant­based 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”, “some­tenant­identifier” );
    session.beginTransaction();
    Customer customer = new Customer();
    ...
    customer.setTenantId( “some­tenant­identifier” );
    session.persist( customer );
    session.getTransaction().commit();
    session.close();
    // Querying Customers
    session = openSession();
    session.enableFilter( “tenantFilter” ).setParameter( “tenantId”, “some­tenant­identifier” );
    session.beginTransaction();
    Customer customer = (Customer) session.createQuery( “from Customer” ).uniqueResult();
    session.getTransaction().commit();
    session.close();
}
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
Verwendung des Filters
session.enableFilter(" kundeMandantFilter ")
.setParameter("mandant", MANDANT_1 );
Query query = session.createQuery(
"FROM " + Kunde.class.getSimpleName ());
Assert.assertEquals (2,
((List <Kunde >) query.list ()). size ());
session.enableFilter(" kundeMandantFilter ")
.setParameter("mandant", MANDANT_2 );
Assert.assertEquals (1,
((List <Kunde >) query.list ()). size ());
Bernd M¨uller, 5.11.2013 18/62
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
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
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
    }
}
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 
    // tenant­id 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( “some­tenant­identifier” );
    // 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 
    // tenant­id 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( “some­tenant­identifier” );
    // 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();
}
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/HHH­5697
Manual (4.2): 16.3. Multi-tenancy in Hibernate
Konfiguration mit Property hibernate.multiTenancy
Bernd M¨uller, 5.11.2013 24/62
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
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
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
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
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
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
Mandantenf¨ahigkeit mit OpenJPA
Bernd M¨uller, 5.11.2013 26/62
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
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
Slice’ Features
Transparency
Scaling
Distributed Query
Data Distribution
Data Replication
Heterogeneous Database
Distributed Transaction
Collocation Constraint
Bernd M¨uller, 5.11.2013 28/62
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
Wie funktioniert’s ?
Anwendung(-slogik) bleibt unver¨andert (im Idealfall /
einfachsten Fall)
Bernd M¨uller, 5.11.2013 29/62
Wie funktioniert’s ?
Anwendung(-slogik) bleibt unver¨andert (im Idealfall /
einfachsten Fall)
JPAs zugrundeliegende logische Datenbank wird durch Menge
horizontal partitionierter realer Datenbanken aufgeteilt
Bernd M¨uller, 5.11.2013 29/62
Wie funktioniert’s ?
Anwendung(-slogik) bleibt unver¨andert (im Idealfall /
einfachsten Fall)
JPAs zugrundeliegende logische Datenbank wird durch Menge
horizontal partitionierter realer Datenbanken aufgeteilt
Heterogene Datenbanken m¨oglich
Bernd M¨uller, 5.11.2013 29/62
Wie funktioniert’s ?
Anwendung(-slogik) bleibt unver¨andert (im Idealfall /
einfachsten Fall)
JPAs zugrundeliegende logische Datenbank wird durch Menge
horizontal partitionierter realer Datenbanken aufgeteilt
Heterogene Datenbanken m¨oglich
Flexible Verteilung bzw. Redundanz der Daten durch
Callback-Methoden sehr flexibel steuerbar (nur diese Klassen
zus¨atzlich)
Bernd M¨uller, 5.11.2013 29/62
Konfiguration in persistence.xml
<property name="openjpa.BrokerFactory" value="slice" />
<property name="openjpa.slice.Names"
value="mandant1 ,mandant2 ,mandant3" />
<property name="openjpa.slice.Master" value="mandant1" />
<property name="openjpa.slice.Lenient" value="true" />
<property name="openjpa. ConnectionDriverName "
value="org.postgresql.Driver"/>
<property name="openjpa.slice.mandant1.ConnectionURL"
value=" jdbc:postgresql: // localhost/bank1"/>
<property name="openjpa.slice.mandant2.ConnectionURL"
value=" jdbc:postgresql: // localhost/bank2"/>
<property name="openjpa.slice.mandant3. ConnectionDriverName "
value="com.mysql.jdbc.Driver" />
<property name="openjpa.slice.mandant3.ConnectionURL"
value="jdbc:mysql: // localhost/bank3" />
Bernd M¨uller, 5.11.2013 30/62
Weitere Konfiguration
<property name="openjpa.slice. DistributionPolicy "
value="de.jpainfo. DistributionPolicyMandant "
<property name="openjpa.slice. QueryTargetPolicy "
value="de.jpainfo. QueryPolicyMandant "/>
<property name="openjpa.slice. FinderTargetPolicy "
value="de.jpainfo. FinderPolicyMandant "/>
Bernd M¨uller, 5.11.2013 31/62
Interface DistributionPolicy
Bernd M¨uller, 5.11.2013 32/62
Interface QueryTargetPolicy
Bernd M¨uller, 5.11.2013 33/62
Interface FinderTargetPolicy
Bernd M¨uller, 5.11.2013 34/62
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
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
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";
}
}
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
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
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
Queries (cont’d)
Auch order by m¨oglich
Bernd M¨uller, 5.11.2013 41/62
Queries (cont’d)
Auch order by m¨oglich
In jeder DB auf SQL-Ebene sortiert
Bernd M¨uller, 5.11.2013 41/62
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
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
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
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
Mandantenf¨ahigkeit mit EclipseLink
Bernd M¨uller, 5.11.2013 44/62
Bernd M¨uller, 5.11.2013 45/62
Hasta la vista Mandantenfähigkeit?
Hasta la vista Mandantenfähigkeit?
Bernd M¨uller, 5.11.2013 48/62
Bernd M¨uller, 5.11.2013 49/62
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
Das Konto
@Entity
@Multitenant
public class Konto {
@Id
@GeneratedValue (strategy = GenerationType.SEQUENCE ,
generator = "KontoSeq")
@SequenceGenerator (name = "KontoSeq",
sequenceName="KontoSeq", allocationSize = 5,
initialValue = 1000000)
private Integer kontonummer;
private BigDecimal kontostand;
@ManyToOne
@JoinColumn(name = "kunde")
private Kunde kunde;
...
Bernd M¨uller, 5.11.2013 51/62
Verwendung: persistence.xml oder programmatisch
<?xml version="1.0" encoding="UTF-8" ?>
<persistence ...>
...
<properties >
<property name="eclipselink.tenant-id"
value="007"/>
Bernd M¨uller, 5.11.2013 52/62
Verwendung: persistence.xml oder programmatisch
<?xml version="1.0" encoding="UTF-8" ?>
<persistence ...>
...
<properties >
<property name="eclipselink.tenant-id"
value="007"/>
Map <String , String > props =
new HashMap <String , String >();
props.put("eclipselink.tenant -id", "Mandant -1");
EntityManagerFactory emf = Persistence
. createEntityManagerFactory ("bank", props );
EntityManager em = emf. createEntityManager ();
Bernd M¨uller, 5.11.2013 52/62
Verwendung: Lesen
Transparente Verwendung
...
props.put("eclipselink.tenant -id", "Mandant -1");
...
List <Kunde > list = em
.createQuery("Select k from Kunde k",
Kunde.class)
.getResultList ();
...
Kunde kunde = em.find(Kunde.class , <pk >));
...
Bernd M¨uller, 5.11.2013 53/62
Alternativen
@Entity
@Multitenant
@Multitenant( MultitenantType .SINGLE_TABLE)
@TenantDiscriminatorColumn (name = "mandant")
@TenantDiscriminatorColumn (name = "mandant",
primaryKey = true)
@TenantDiscriminatorColumn (name = "mandant",
discriminatorType = DiscriminatorType .INTEGER)
public class Kunde { ...
Nur beiden ersten Annotationen zwingend, sinvolle Defaults
existieren (Convention over Configuration)
Default f¨ur Spaltenname: TENANT ID
Mehrere Diskriminatorspalten mit
@TenantDiscriminatorColumns
DDL-Generierung funktioniert
Bernd M¨uller, 5.11.2013 54/62
Gemappter Tenant
Muss read-only sein
Bernd M¨uller, 5.11.2013 55/62
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
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
Verwendung in Java-EE
<persistence ...>
<persistence-unit name="tenant-1" transaction-type ="JTA">
<jta-data-source >jdbc/__default </ jta-data-source >
<properties >
<property name="eclipselink.tenant-id"
value="Mandant-1"/>
...
</properties >
</ persistence-unit >
<persistence-unit name="tenant-2" transaction-type ="JTA">
<jta-data-source >jdbc/__default </ jta-data-source >
<properties >
<property name="eclipselink.tenant-id"
value="Mandant-2"/>
...
</properties >
</ persistence-unit >
</persistence >
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"));
}
}
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
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
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
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
Fragen und Anmerkungen
Bernd M¨uller, 5.11.2013 62/62

Weitere ähnliche Inhalte

Ähnlich wie Hasta la vista Mandantenfähigkeit?

Fujitsu Storage Days 2017 - Norbert Postler: „Was auch passiert – bleiben Sie...
Fujitsu Storage Days 2017 - Norbert Postler: „Was auch passiert – bleiben Sie...Fujitsu Storage Days 2017 - Norbert Postler: „Was auch passiert – bleiben Sie...
Fujitsu Storage Days 2017 - Norbert Postler: „Was auch passiert – bleiben Sie...
Fujitsu Central Europe
 
Günzel/Griesbaum - Polycloud: Kann ein Vendor Lock-in Vorteile bringen?
Günzel/Griesbaum - Polycloud: Kann ein Vendor Lock-in Vorteile bringen?Günzel/Griesbaum - Polycloud: Kann ein Vendor Lock-in Vorteile bringen?
Günzel/Griesbaum - Polycloud: Kann ein Vendor Lock-in Vorteile bringen?
Andreas Günzel
 
Fujitsu Storage Days 2018 - Eternus Survivalpack
Fujitsu Storage Days 2018 - Eternus SurvivalpackFujitsu Storage Days 2018 - Eternus Survivalpack
Fujitsu Storage Days 2018 - Eternus Survivalpack
Fujitsu Central Europe
 
Attribute in SAML und Attribut-Handling am Shibboleth Identity Provider
Attribute in SAML und Attribut-Handling am Shibboleth Identity ProviderAttribute in SAML und Attribut-Handling am Shibboleth Identity Provider
Attribute in SAML und Attribut-Handling am Shibboleth Identity Provider
WolfgangPempe
 
Relaunch of HaloACL, Wojtek Breiter, SMWCon Fall 2014
Relaunch of HaloACL, Wojtek Breiter, SMWCon Fall 2014Relaunch of HaloACL, Wojtek Breiter, SMWCon Fall 2014
Relaunch of HaloACL, Wojtek Breiter, SMWCon Fall 2014
KDZ - Zentrum für Verwaltungsforschung
 
Von isolierten Anwendungen zur allgegenwärtigen Trainings-Cloud
Von isolierten Anwendungen zur allgegenwärtigen Trainings-CloudVon isolierten Anwendungen zur allgegenwärtigen Trainings-Cloud
Von isolierten Anwendungen zur allgegenwärtigen Trainings-Cloud
Robert Siegfried
 
Top 10 Internet Trends 2003
Top 10 Internet Trends 2003Top 10 Internet Trends 2003
Top 10 Internet Trends 2003
Jürg Stuker
 
Sbs unternehmenspräsentation v14.3.8
Sbs unternehmenspräsentation v14.3.8Sbs unternehmenspräsentation v14.3.8
Sbs unternehmenspräsentation v14.3.8
Joschka Gerrit Bronst
 
Fujitsu Storage Days 2017 - René Hübel: "Software Defined Storage – Über sieb...
Fujitsu Storage Days 2017 - René Hübel: "Software Defined Storage – Über sieb...Fujitsu Storage Days 2017 - René Hübel: "Software Defined Storage – Über sieb...
Fujitsu Storage Days 2017 - René Hübel: "Software Defined Storage – Über sieb...
Fujitsu Central Europe
 
Dataservices - Data Processing mit Microservices
Dataservices - Data Processing mit MicroservicesDataservices - Data Processing mit Microservices
Dataservices - Data Processing mit Microservices
QAware GmbH
 
ORACLE Lizenzierung - Die größten Fallen in der Praxis
ORACLE Lizenzierung - Die größten Fallen in der PraxisORACLE Lizenzierung - Die größten Fallen in der Praxis
ORACLE Lizenzierung - Die größten Fallen in der Praxis
OPITZ CONSULTING Deutschland
 
DBSAT – Die Oracle DATENBANK bzgl. PII Daten analysieren
DBSAT – Die Oracle DATENBANK bzgl. PII Daten analysierenDBSAT – Die Oracle DATENBANK bzgl. PII Daten analysieren
DBSAT – Die Oracle DATENBANK bzgl. PII Daten analysieren
Gunther Pippèrr
 
Große Applikationen mit AngularJS
Große Applikationen mit AngularJSGroße Applikationen mit AngularJS
Große Applikationen mit AngularJS
Sebastian Springer
 
System-Management-Trio: Zentrale Verwaltung mit facter, puppet und augeas
System-Management-Trio: Zentrale Verwaltung mit facter, puppet und augeasSystem-Management-Trio: Zentrale Verwaltung mit facter, puppet und augeas
System-Management-Trio: Zentrale Verwaltung mit facter, puppet und augeas
SpeedPartner GmbH
 
Storage Trends für KMU
Storage Trends für KMUStorage Trends für KMU
Storage Trends für KMU
A. Baggenstos & Co. AG
 
Teamarbeit 2.0 (PTF 2008)
Teamarbeit 2.0 (PTF 2008) Teamarbeit 2.0 (PTF 2008)
Teamarbeit 2.0 (PTF 2008)
Thomas Koch
 
Java User Group Düsseldorf - Vortrag der iks am 13. März 2008
Java User Group Düsseldorf - Vortrag der iks am 13. März 2008Java User Group Düsseldorf - Vortrag der iks am 13. März 2008
Java User Group Düsseldorf - Vortrag der iks am 13. März 2008
IKS Gesellschaft für Informations- und Kommunikationssysteme mbH
 
Social Messaging als E20 Killer App
Social Messaging als E20 Killer AppSocial Messaging als E20 Killer App
Social Messaging als E20 Killer App
n:sight / Kongress Media
 
Experteninterview mit Torben Ritter: Daten schützen: IRM, AIP, ARM
Experteninterview mit Torben Ritter: Daten schützen: IRM, AIP, ARMExperteninterview mit Torben Ritter: Daten schützen: IRM, AIP, ARM
Experteninterview mit Torben Ritter: Daten schützen: IRM, AIP, ARM
Thomas Maier
 
Oracle Database Security Assessment Tool (DBSAT)
Oracle Database Security Assessment Tool (DBSAT)Oracle Database Security Assessment Tool (DBSAT)
Oracle Database Security Assessment Tool (DBSAT)
OPITZ CONSULTING Deutschland
 

Ähnlich wie Hasta la vista Mandantenfähigkeit? (20)

Fujitsu Storage Days 2017 - Norbert Postler: „Was auch passiert – bleiben Sie...
Fujitsu Storage Days 2017 - Norbert Postler: „Was auch passiert – bleiben Sie...Fujitsu Storage Days 2017 - Norbert Postler: „Was auch passiert – bleiben Sie...
Fujitsu Storage Days 2017 - Norbert Postler: „Was auch passiert – bleiben Sie...
 
Günzel/Griesbaum - Polycloud: Kann ein Vendor Lock-in Vorteile bringen?
Günzel/Griesbaum - Polycloud: Kann ein Vendor Lock-in Vorteile bringen?Günzel/Griesbaum - Polycloud: Kann ein Vendor Lock-in Vorteile bringen?
Günzel/Griesbaum - Polycloud: Kann ein Vendor Lock-in Vorteile bringen?
 
Fujitsu Storage Days 2018 - Eternus Survivalpack
Fujitsu Storage Days 2018 - Eternus SurvivalpackFujitsu Storage Days 2018 - Eternus Survivalpack
Fujitsu Storage Days 2018 - Eternus Survivalpack
 
Attribute in SAML und Attribut-Handling am Shibboleth Identity Provider
Attribute in SAML und Attribut-Handling am Shibboleth Identity ProviderAttribute in SAML und Attribut-Handling am Shibboleth Identity Provider
Attribute in SAML und Attribut-Handling am Shibboleth Identity Provider
 
Relaunch of HaloACL, Wojtek Breiter, SMWCon Fall 2014
Relaunch of HaloACL, Wojtek Breiter, SMWCon Fall 2014Relaunch of HaloACL, Wojtek Breiter, SMWCon Fall 2014
Relaunch of HaloACL, Wojtek Breiter, SMWCon Fall 2014
 
Von isolierten Anwendungen zur allgegenwärtigen Trainings-Cloud
Von isolierten Anwendungen zur allgegenwärtigen Trainings-CloudVon isolierten Anwendungen zur allgegenwärtigen Trainings-Cloud
Von isolierten Anwendungen zur allgegenwärtigen Trainings-Cloud
 
Top 10 Internet Trends 2003
Top 10 Internet Trends 2003Top 10 Internet Trends 2003
Top 10 Internet Trends 2003
 
Sbs unternehmenspräsentation v14.3.8
Sbs unternehmenspräsentation v14.3.8Sbs unternehmenspräsentation v14.3.8
Sbs unternehmenspräsentation v14.3.8
 
Fujitsu Storage Days 2017 - René Hübel: "Software Defined Storage – Über sieb...
Fujitsu Storage Days 2017 - René Hübel: "Software Defined Storage – Über sieb...Fujitsu Storage Days 2017 - René Hübel: "Software Defined Storage – Über sieb...
Fujitsu Storage Days 2017 - René Hübel: "Software Defined Storage – Über sieb...
 
Dataservices - Data Processing mit Microservices
Dataservices - Data Processing mit MicroservicesDataservices - Data Processing mit Microservices
Dataservices - Data Processing mit Microservices
 
ORACLE Lizenzierung - Die größten Fallen in der Praxis
ORACLE Lizenzierung - Die größten Fallen in der PraxisORACLE Lizenzierung - Die größten Fallen in der Praxis
ORACLE Lizenzierung - Die größten Fallen in der Praxis
 
DBSAT – Die Oracle DATENBANK bzgl. PII Daten analysieren
DBSAT – Die Oracle DATENBANK bzgl. PII Daten analysierenDBSAT – Die Oracle DATENBANK bzgl. PII Daten analysieren
DBSAT – Die Oracle DATENBANK bzgl. PII Daten analysieren
 
Große Applikationen mit AngularJS
Große Applikationen mit AngularJSGroße Applikationen mit AngularJS
Große Applikationen mit AngularJS
 
System-Management-Trio: Zentrale Verwaltung mit facter, puppet und augeas
System-Management-Trio: Zentrale Verwaltung mit facter, puppet und augeasSystem-Management-Trio: Zentrale Verwaltung mit facter, puppet und augeas
System-Management-Trio: Zentrale Verwaltung mit facter, puppet und augeas
 
Storage Trends für KMU
Storage Trends für KMUStorage Trends für KMU
Storage Trends für KMU
 
Teamarbeit 2.0 (PTF 2008)
Teamarbeit 2.0 (PTF 2008) Teamarbeit 2.0 (PTF 2008)
Teamarbeit 2.0 (PTF 2008)
 
Java User Group Düsseldorf - Vortrag der iks am 13. März 2008
Java User Group Düsseldorf - Vortrag der iks am 13. März 2008Java User Group Düsseldorf - Vortrag der iks am 13. März 2008
Java User Group Düsseldorf - Vortrag der iks am 13. März 2008
 
Social Messaging als E20 Killer App
Social Messaging als E20 Killer AppSocial Messaging als E20 Killer App
Social Messaging als E20 Killer App
 
Experteninterview mit Torben Ritter: Daten schützen: IRM, AIP, ARM
Experteninterview mit Torben Ritter: Daten schützen: IRM, AIP, ARMExperteninterview mit Torben Ritter: Daten schützen: IRM, AIP, ARM
Experteninterview mit Torben Ritter: Daten schützen: IRM, AIP, ARM
 
Oracle Database Security Assessment Tool (DBSAT)
Oracle Database Security Assessment Tool (DBSAT)Oracle Database Security Assessment Tool (DBSAT)
Oracle Database Security Assessment Tool (DBSAT)
 

Mehr von berndmueller

Compilers Everywhere
Compilers EverywhereCompilers Everywhere
Compilers Everywhere
berndmueller
 
JFN 2017 - Instrumentierung - Das Werkzeug der Werkzeugmacher
JFN 2017 - Instrumentierung - Das Werkzeug der WerkzeugmacherJFN 2017 - Instrumentierung - Das Werkzeug der Werkzeugmacher
JFN 2017 - Instrumentierung - Das Werkzeug der Werkzeugmacher
berndmueller
 
Was jeder Java-Entwickler über Strings wissen sollte
Was jeder Java-Entwickler über Strings wissen sollteWas jeder Java-Entwickler über Strings wissen sollte
Was jeder Java-Entwickler über Strings wissen sollte
berndmueller
 
Drohnen und WARP-Antriebe
Drohnen und WARP-AntriebeDrohnen und WARP-Antriebe
Drohnen und WARP-Antriebe
berndmueller
 
Class Loading Revisited
Class Loading RevisitedClass Loading Revisited
Class Loading Revisited
berndmueller
 
Instrumentierung - Das Werkzeug der Werkzeugmacher -
Instrumentierung - Das Werkzeug der Werkzeugmacher - Instrumentierung - Das Werkzeug der Werkzeugmacher -
Instrumentierung - Das Werkzeug der Werkzeugmacher -
berndmueller
 
Was jeder Java-Entwickler über Strings wissen sollte
Was jeder Java-Entwickler über Strings wissen sollteWas jeder Java-Entwickler über Strings wissen sollte
Was jeder Java-Entwickler über Strings wissen sollte
berndmueller
 

Mehr von berndmueller (7)

Compilers Everywhere
Compilers EverywhereCompilers Everywhere
Compilers Everywhere
 
JFN 2017 - Instrumentierung - Das Werkzeug der Werkzeugmacher
JFN 2017 - Instrumentierung - Das Werkzeug der WerkzeugmacherJFN 2017 - Instrumentierung - Das Werkzeug der Werkzeugmacher
JFN 2017 - Instrumentierung - Das Werkzeug der Werkzeugmacher
 
Was jeder Java-Entwickler über Strings wissen sollte
Was jeder Java-Entwickler über Strings wissen sollteWas jeder Java-Entwickler über Strings wissen sollte
Was jeder Java-Entwickler über Strings wissen sollte
 
Drohnen und WARP-Antriebe
Drohnen und WARP-AntriebeDrohnen und WARP-Antriebe
Drohnen und WARP-Antriebe
 
Class Loading Revisited
Class Loading RevisitedClass Loading Revisited
Class Loading Revisited
 
Instrumentierung - Das Werkzeug der Werkzeugmacher -
Instrumentierung - Das Werkzeug der Werkzeugmacher - Instrumentierung - Das Werkzeug der Werkzeugmacher -
Instrumentierung - Das Werkzeug der Werkzeugmacher -
 
Was jeder Java-Entwickler über Strings wissen sollte
Was jeder Java-Entwickler über Strings wissen sollteWas jeder Java-Entwickler über Strings wissen sollte
Was jeder Java-Entwickler über Strings wissen sollte
 

Hasta la vista Mandantenfähigkeit?

  • 2. Vorstellung Referent Prof. Informatik (Ostfalia, HS Braunschweig/Wolfenb¨uttel) Buchautor (JSF, JPA, Seam, . . . ) Mitglied EGs JSR 344 (JSF 2.2) und JSR 338 (JPA 2.1) Gesch¨aftsf¨uhrer PMST GmbH . . . Bernd M¨uller, 5.11.2013 2/62
  • 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
  • 8. Was bedeutet Mandantenf¨ahigkeit? — Hier nur die Datensicht — Bernd M¨uller, 5.11.2013 7/62
  • 9. Motivation Cloud-motiviert: XaaS (Iaas, PaaS, SaaS) Bernd M¨uller, 5.11.2013 8/62
  • 10. Motivation Cloud-motiviert: XaaS (Iaas, PaaS, SaaS) Kontinuum: Isolated Data vs Shared Data Bernd M¨uller, 5.11.2013 8/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
  • 16. Mandantenf¨ahigkeit mit Hibernate Bernd M¨uller, 5.11.2013 13/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 tenant­based 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”, “some­tenant­identifier” );     session.beginTransaction();     Customer customer = new Customer();     ...     customer.setTenantId( “some­tenant­identifier” );     session.persist( customer );     session.getTransaction().commit();     session.close();     // Querying Customers     session = openSession();     session.enableFilter( “tenantFilter” ).setParameter( “tenantId”, “some­tenant­identifier” );     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 tenant­based 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”, “some­tenant­identifier” );     session.beginTransaction();     Customer customer = new Customer();     ...     customer.setTenantId( “some­tenant­identifier” );     session.persist( customer );     session.getTransaction().commit();     session.close();     // Querying Customers     session = openSession();     session.enableFilter( “tenantFilter” ).setParameter( “tenantId”, “some­tenant­identifier” );     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
  • 21. Verwendung des Filters session.enableFilter(" kundeMandantFilter ") .setParameter("mandant", MANDANT_1 ); Query query = session.createQuery( "FROM " + Kunde.class.getSimpleName ()); Assert.assertEquals (2, ((List <Kunde >) query.list ()). size ()); session.enableFilter(" kundeMandantFilter ") .setParameter("mandant", MANDANT_2 ); Assert.assertEquals (1, ((List <Kunde >) query.list ()). size ()); Bernd M¨uller, 5.11.2013 18/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      // tenant­id 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( “some­tenant­identifier” );     // 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      // tenant­id 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( “some­tenant­identifier” );     // 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/HHH­5697
  • 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
  • 34. Mandantenf¨ahigkeit mit OpenJPA Bernd M¨uller, 5.11.2013 26/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
  • 37. Slice’ Features Transparency Scaling Distributed Query Data Distribution Data Replication Heterogeneous Database Distributed Transaction Collocation Constraint Bernd M¨uller, 5.11.2013 28/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
  • 39. Wie funktioniert’s ? Anwendung(-slogik) bleibt unver¨andert (im Idealfall / einfachsten Fall) Bernd M¨uller, 5.11.2013 29/62
  • 40. Wie funktioniert’s ? Anwendung(-slogik) bleibt unver¨andert (im Idealfall / einfachsten Fall) JPAs zugrundeliegende logische Datenbank wird durch Menge horizontal partitionierter realer Datenbanken aufgeteilt Bernd M¨uller, 5.11.2013 29/62
  • 41. Wie funktioniert’s ? Anwendung(-slogik) bleibt unver¨andert (im Idealfall / einfachsten Fall) JPAs zugrundeliegende logische Datenbank wird durch Menge horizontal partitionierter realer Datenbanken aufgeteilt Heterogene Datenbanken m¨oglich Bernd M¨uller, 5.11.2013 29/62
  • 42. Wie funktioniert’s ? Anwendung(-slogik) bleibt unver¨andert (im Idealfall / einfachsten Fall) JPAs zugrundeliegende logische Datenbank wird durch Menge horizontal partitionierter realer Datenbanken aufgeteilt Heterogene Datenbanken m¨oglich Flexible Verteilung bzw. Redundanz der Daten durch Callback-Methoden sehr flexibel steuerbar (nur diese Klassen zus¨atzlich) Bernd M¨uller, 5.11.2013 29/62
  • 43. Konfiguration in persistence.xml <property name="openjpa.BrokerFactory" value="slice" /> <property name="openjpa.slice.Names" value="mandant1 ,mandant2 ,mandant3" /> <property name="openjpa.slice.Master" value="mandant1" /> <property name="openjpa.slice.Lenient" value="true" /> <property name="openjpa. ConnectionDriverName " value="org.postgresql.Driver"/> <property name="openjpa.slice.mandant1.ConnectionURL" value=" jdbc:postgresql: // localhost/bank1"/> <property name="openjpa.slice.mandant2.ConnectionURL" value=" jdbc:postgresql: // localhost/bank2"/> <property name="openjpa.slice.mandant3. ConnectionDriverName " value="com.mysql.jdbc.Driver" /> <property name="openjpa.slice.mandant3.ConnectionURL" value="jdbc:mysql: // localhost/bank3" /> Bernd M¨uller, 5.11.2013 30/62
  • 44. Weitere Konfiguration <property name="openjpa.slice. DistributionPolicy " value="de.jpainfo. DistributionPolicyMandant " <property name="openjpa.slice. QueryTargetPolicy " value="de.jpainfo. QueryPolicyMandant "/> <property name="openjpa.slice. FinderTargetPolicy " value="de.jpainfo. FinderPolicyMandant "/> Bernd M¨uller, 5.11.2013 31/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
  • 54. Queries (cont’d) Auch order by m¨oglich Bernd M¨uller, 5.11.2013 41/62
  • 55. Queries (cont’d) Auch order by m¨oglich In jeder DB auf SQL-Ebene sortiert Bernd M¨uller, 5.11.2013 41/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
  • 60. Mandantenf¨ahigkeit mit EclipseLink Bernd M¨uller, 5.11.2013 44/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
  • 67. Das Konto @Entity @Multitenant public class Konto { @Id @GeneratedValue (strategy = GenerationType.SEQUENCE , generator = "KontoSeq") @SequenceGenerator (name = "KontoSeq", sequenceName="KontoSeq", allocationSize = 5, initialValue = 1000000) private Integer kontonummer; private BigDecimal kontostand; @ManyToOne @JoinColumn(name = "kunde") private Kunde kunde; ... Bernd M¨uller, 5.11.2013 51/62
  • 68. Verwendung: persistence.xml oder programmatisch <?xml version="1.0" encoding="UTF-8" ?> <persistence ...> ... <properties > <property name="eclipselink.tenant-id" value="007"/> Bernd M¨uller, 5.11.2013 52/62
  • 69. Verwendung: persistence.xml oder programmatisch <?xml version="1.0" encoding="UTF-8" ?> <persistence ...> ... <properties > <property name="eclipselink.tenant-id" value="007"/> Map <String , String > props = new HashMap <String , String >(); props.put("eclipselink.tenant -id", "Mandant -1"); EntityManagerFactory emf = Persistence . createEntityManagerFactory ("bank", props ); EntityManager em = emf. createEntityManager (); Bernd M¨uller, 5.11.2013 52/62
  • 70. Verwendung: Lesen Transparente Verwendung ... props.put("eclipselink.tenant -id", "Mandant -1"); ... List <Kunde > list = em .createQuery("Select k from Kunde k", Kunde.class) .getResultList (); ... Kunde kunde = em.find(Kunde.class , <pk >)); ... Bernd M¨uller, 5.11.2013 53/62
  • 71. Alternativen @Entity @Multitenant @Multitenant( MultitenantType .SINGLE_TABLE) @TenantDiscriminatorColumn (name = "mandant") @TenantDiscriminatorColumn (name = "mandant", primaryKey = true) @TenantDiscriminatorColumn (name = "mandant", discriminatorType = DiscriminatorType .INTEGER) public class Kunde { ... Nur beiden ersten Annotationen zwingend, sinvolle Defaults existieren (Convention over Configuration) Default f¨ur Spaltenname: TENANT ID Mehrere Diskriminatorspalten mit @TenantDiscriminatorColumns DDL-Generierung funktioniert Bernd M¨uller, 5.11.2013 54/62
  • 72. Gemappter Tenant Muss read-only sein Bernd M¨uller, 5.11.2013 55/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
  • 75. Verwendung in Java-EE <persistence ...> <persistence-unit name="tenant-1" transaction-type ="JTA"> <jta-data-source >jdbc/__default </ jta-data-source > <properties > <property name="eclipselink.tenant-id" value="Mandant-1"/> ... </properties > </ persistence-unit > <persistence-unit name="tenant-2" transaction-type ="JTA"> <jta-data-source >jdbc/__default </ jta-data-source > <properties > <property name="eclipselink.tenant-id" value="Mandant-2"/> ... </properties > </ persistence-unit > </persistence >
  • 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
  • 81. Fragen und Anmerkungen Bernd M¨uller, 5.11.2013 62/62