From 76673a33f1f0856240db3c2b5fd638bd27cdd4a5 Mon Sep 17 00:00:00 2001 From: Parth Joshi Date: Thu, 20 Apr 2017 18:25:34 +0530 Subject: [PATCH] Hibernate 5 Multitenancy tutorial (#1150) * First commit for Hibernate 5 Multitenancy tutorial * Changes to fix the code. * Added hibernate begin transaction code. * Changes to solve the multitenancy issue. * Changes to integrate h2 * Changing configs to solve the error * Changes to solve h2 error... * Changes to fix H2 error. * Cleaned POM.xml and changed entity name * Changes table name to supplier * Removed MySql Dep from pom.xml. * Changes as per comment in the PR... --- hibernate5/pom.xml | 62 ++++++++++++ .../main/java/com/baeldung/hibernate/App.java | 30 ++++++ ...igurableMultiTenantConnectionProvider.java | 41 ++++++++ .../hibernate/HibernateMultiTenantUtil.java | 95 +++++++++++++++++++ .../com/baeldung/hibernate/HibernateUtil.java | 24 +++++ .../UnsupportedTenancyException.java | 8 ++ .../com/baeldung/hibernate/pojo/Supplier.java | 67 +++++++++++++ hibernate5/src/main/java/hibernate.cfg.xml | 14 +++ .../hibernate/MultiTenantHibernateTest.java | 73 ++++++++++++++ pom.xml | 4 +- 10 files changed, 416 insertions(+), 2 deletions(-) create mode 100644 hibernate5/pom.xml create mode 100644 hibernate5/src/main/java/com/baeldung/hibernate/App.java create mode 100644 hibernate5/src/main/java/com/baeldung/hibernate/ConfigurableMultiTenantConnectionProvider.java create mode 100644 hibernate5/src/main/java/com/baeldung/hibernate/HibernateMultiTenantUtil.java create mode 100644 hibernate5/src/main/java/com/baeldung/hibernate/HibernateUtil.java create mode 100644 hibernate5/src/main/java/com/baeldung/hibernate/UnsupportedTenancyException.java create mode 100644 hibernate5/src/main/java/com/baeldung/hibernate/pojo/Supplier.java create mode 100644 hibernate5/src/main/java/hibernate.cfg.xml create mode 100644 hibernate5/src/test/java/com/baeldung/hibernate/MultiTenantHibernateTest.java diff --git a/hibernate5/pom.xml b/hibernate5/pom.xml new file mode 100644 index 0000000000..b7473ec5b7 --- /dev/null +++ b/hibernate5/pom.xml @@ -0,0 +1,62 @@ + + + 4.0.0 + + com.baeldung + parent-modules + 1.0.0-SNAPSHOT + + com.baeldung + hibernate5 + 0.0.1-SNAPSHOT + hibernate5 + http://maven.apache.org + + UTF-8 + + 3.6.0 + + + + org.hibernate + hibernate-core + 5.2.9.Final + + + junit + junit + 4.12 + + + com.h2database + h2 + 1.4.194 + + + + hibernate5 + + + src/main/resources + true + + + + + + org.apache.maven.plugins + maven-compiler-plugin + ${maven-compiler-plugin.version} + + 1.8 + 1.8 + + + + + + + + diff --git a/hibernate5/src/main/java/com/baeldung/hibernate/App.java b/hibernate5/src/main/java/com/baeldung/hibernate/App.java new file mode 100644 index 0000000000..26a40bb782 --- /dev/null +++ b/hibernate5/src/main/java/com/baeldung/hibernate/App.java @@ -0,0 +1,30 @@ +package com.baeldung.hibernate; + +import org.hibernate.Session; +import org.hibernate.SessionFactory; +import org.hibernate.Transaction; + +import com.baeldung.hibernate.pojo.Supplier; + +/** + * Hello world! + * + */ +public class App { + public static void main(String[] args) { + try { + // NOTE: this is just for boostrap testing for multitenancy. + System.out.println("Checking the system."); + SessionFactory sessionFactory = HibernateMultiTenantUtil.getSessionFactory(); + Session currentSession = sessionFactory.withOptions().tenantIdentifier("h2db1").openSession(); + Transaction transaction = currentSession.getTransaction(); + transaction.begin(); + currentSession.createCriteria(Supplier.class).list().stream().forEach(System.out::println); + transaction.commit(); + + } catch (Exception e) { + e.printStackTrace(); + } + + } +} diff --git a/hibernate5/src/main/java/com/baeldung/hibernate/ConfigurableMultiTenantConnectionProvider.java b/hibernate5/src/main/java/com/baeldung/hibernate/ConfigurableMultiTenantConnectionProvider.java new file mode 100644 index 0000000000..b9ed2bd139 --- /dev/null +++ b/hibernate5/src/main/java/com/baeldung/hibernate/ConfigurableMultiTenantConnectionProvider.java @@ -0,0 +1,41 @@ +package com.baeldung.hibernate; + +import java.sql.Connection; +import java.sql.SQLException; +import java.util.HashMap; +import java.util.Map; + +import org.hibernate.engine.jdbc.connections.spi.AbstractMultiTenantConnectionProvider; +import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider; + +public class ConfigurableMultiTenantConnectionProvider extends AbstractMultiTenantConnectionProvider { + + private final Map connectionProviderMap = + new HashMap<>(); + + + public ConfigurableMultiTenantConnectionProvider( + Map connectionProviderMap) { + this.connectionProviderMap.putAll( connectionProviderMap ); + } + @Override + protected ConnectionProvider getAnyConnectionProvider() { + System.out.println("Any"); + return connectionProviderMap.values().iterator().next(); + } + + @Override + protected ConnectionProvider selectConnectionProvider(String tenantIdentifier) { + System.out.println("Specific"); + return connectionProviderMap.get( tenantIdentifier ); + } + + @Override + public Connection getConnection(String tenantIdentifier) throws SQLException { + Connection connection = super.getConnection(tenantIdentifier); + // uncomment to see option 2 for SCHEMA strategy. + //connection.createStatement().execute("SET SCHEMA '" + tenantIdentifier + "'"); + return connection; + } + +} \ No newline at end of file diff --git a/hibernate5/src/main/java/com/baeldung/hibernate/HibernateMultiTenantUtil.java b/hibernate5/src/main/java/com/baeldung/hibernate/HibernateMultiTenantUtil.java new file mode 100644 index 0000000000..c3e7b621d0 --- /dev/null +++ b/hibernate5/src/main/java/com/baeldung/hibernate/HibernateMultiTenantUtil.java @@ -0,0 +1,95 @@ +package com.baeldung.hibernate; + +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; + +import org.hibernate.SessionFactory; +import org.hibernate.boot.Metadata; +import org.hibernate.boot.MetadataSources; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.cfg.Configuration; +import org.hibernate.cfg.Environment; +import org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl; +import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider; +import org.hibernate.service.ServiceRegistry; + +import com.baeldung.hibernate.pojo.Supplier; + +public class HibernateMultiTenantUtil { + private static SessionFactory sessionFactory; + private static Map connectionProviderMap = new HashMap<>(); + private static final String[] tenantDBNames = { "mydb1","mydb2"}; + + public static SessionFactory getSessionFactory() throws UnsupportedTenancyException { + if (sessionFactory == null) { + Configuration configuration = new Configuration().configure(); + ServiceRegistry serviceRegistry = configureServiceRegistry(configuration); + sessionFactory = makeSessionFactory (serviceRegistry); +// sessionFactory = configuration.buildSessionFactory(serviceRegistry); + + + } + return sessionFactory; + } + + private static SessionFactory makeSessionFactory(ServiceRegistry serviceRegistry) { + MetadataSources metadataSources = new MetadataSources( serviceRegistry ); + for(Class annotatedClasses : getAnnotatedClasses()) { + metadataSources.addAnnotatedClass( annotatedClasses ); + } + + Metadata metadata = metadataSources.buildMetadata(); + return metadata.getSessionFactoryBuilder().build(); + + } + + private static Class[] getAnnotatedClasses() { + return new Class[] { + Supplier.class + }; + } + + private static ServiceRegistry configureServiceRegistry(Configuration configuration) throws UnsupportedTenancyException { + Properties properties = configuration.getProperties(); + + connectionProviderMap = setUpConnectionProviders(properties, tenantDBNames); + properties.put(AvailableSettings.MULTI_TENANT_CONNECTION_PROVIDER, new ConfigurableMultiTenantConnectionProvider(connectionProviderMap)); + + return new StandardServiceRegistryBuilder().applySettings(properties).build(); + } + + private static Map setUpConnectionProviders(Properties properties, String[] tenantNames) throws UnsupportedTenancyException { + Map providerMap = new HashMap<>(); + for (String tenant : tenantNames) { + DriverManagerConnectionProviderImpl connectionProvider = new DriverManagerConnectionProviderImpl(); + + String tenantStrategy = properties.getProperty("hibernate.multiTenancy"); + System.out.println("Strategy:"+tenantStrategy); + properties.put(Environment.URL, tenantUrl(properties.getProperty(Environment.URL), tenant, tenantStrategy)); + System.out.println("URL:"+properties.getProperty(Environment.URL)); + connectionProvider.configure(properties); + System.out.println("Tenant:"+tenant); + providerMap.put(tenant, connectionProvider); + + } + System.out.println("Added connections for:"); + providerMap.keySet().stream().forEach(System.out::println); + return providerMap; + } + + private static Object tenantUrl(String originalUrl, String tenant, String tenantStrategy) throws UnsupportedTenancyException { + if (tenantStrategy.toUpperCase().equals("DATABASE")) { + return originalUrl.replace(DEFAULT_DB_NAME, tenant); + } else if (tenantStrategy.toUpperCase().equals("SCHEMA")) { + return originalUrl + String.format(SCHEMA_TOKEN, tenant); + } else { + throw new UnsupportedTenancyException("Not yet supported"); + } + } + + public static final String SCHEMA_TOKEN = ";INIT=CREATE SCHEMA IF NOT EXISTS %1$s\\;SET SCHEMA %1$s"; + public static final String DEFAULT_DB_NAME = "mydb1"; + +} diff --git a/hibernate5/src/main/java/com/baeldung/hibernate/HibernateUtil.java b/hibernate5/src/main/java/com/baeldung/hibernate/HibernateUtil.java new file mode 100644 index 0000000000..c1f7301d46 --- /dev/null +++ b/hibernate5/src/main/java/com/baeldung/hibernate/HibernateUtil.java @@ -0,0 +1,24 @@ +package com.baeldung.hibernate; + +import org.hibernate.SessionFactory; +import org.hibernate.cfg.Configuration; + +public class HibernateUtil { + + private static final SessionFactory sessionFactory; + + static { + try { + Configuration configuration = new Configuration().configure(); + sessionFactory = configuration.buildSessionFactory(); + + } catch (Throwable ex) { + System.err.println("Initial SessionFactory creation failed." + ex); + throw new ExceptionInInitializerError(ex); + } + } + + public static SessionFactory getSessionFactory() { + return sessionFactory; + } +} \ No newline at end of file diff --git a/hibernate5/src/main/java/com/baeldung/hibernate/UnsupportedTenancyException.java b/hibernate5/src/main/java/com/baeldung/hibernate/UnsupportedTenancyException.java new file mode 100644 index 0000000000..99d9505ea3 --- /dev/null +++ b/hibernate5/src/main/java/com/baeldung/hibernate/UnsupportedTenancyException.java @@ -0,0 +1,8 @@ +package com.baeldung.hibernate; + +public class UnsupportedTenancyException extends Exception { + public UnsupportedTenancyException (String message) { + super(message); + } + +} diff --git a/hibernate5/src/main/java/com/baeldung/hibernate/pojo/Supplier.java b/hibernate5/src/main/java/com/baeldung/hibernate/pojo/Supplier.java new file mode 100644 index 0000000000..d0187bba47 --- /dev/null +++ b/hibernate5/src/main/java/com/baeldung/hibernate/pojo/Supplier.java @@ -0,0 +1,67 @@ +package com.baeldung.hibernate.pojo; +// Generated Feb 9, 2017 11:31:36 AM by Hibernate Tools 5.1.0.Final + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; + +import org.junit.runners.Suite.SuiteClasses; + + +/** + * Suppliers generated by hbm2java + */ +@Entity(name = "Supplier") +@Table(name ="Supplier") +public class Supplier implements java.io.Serializable { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Integer id; + private String name; + private String country; + + public Supplier() { + } + + public Supplier(String name, String country) { + this.name = name; + this.country = country; + } + + public Integer getId() { + return this.id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return this.name; + } + + public void setName(String name) { + this.name = name; + } + + public String getCountry() { + return this.country; + } + + public void setCountry(String country) { + this.country = country; + } + + @Override + public String toString() { + return new StringBuffer().append("[").append(id).append(",").append(name).append(",").append(country).append("]").toString(); + } + + @Override + public boolean equals(Object obj) { + return name.equals(((Supplier) obj).getName()); + } +} diff --git a/hibernate5/src/main/java/hibernate.cfg.xml b/hibernate5/src/main/java/hibernate.cfg.xml new file mode 100644 index 0000000000..26be05f931 --- /dev/null +++ b/hibernate5/src/main/java/hibernate.cfg.xml @@ -0,0 +1,14 @@ + + + + + org.h2.Driver + jdbc:h2:mem:mydb1;DB_CLOSE_DELAY=-1 + sa + + org.hibernate.dialect.H2Dialect + DATABASE + + diff --git a/hibernate5/src/test/java/com/baeldung/hibernate/MultiTenantHibernateTest.java b/hibernate5/src/test/java/com/baeldung/hibernate/MultiTenantHibernateTest.java new file mode 100644 index 0000000000..4a701de48d --- /dev/null +++ b/hibernate5/src/test/java/com/baeldung/hibernate/MultiTenantHibernateTest.java @@ -0,0 +1,73 @@ +package com.baeldung.hibernate; +import static org.junit.Assert.assertNotEquals; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; +import java.sql.Statement; + +import org.hibernate.Session; +import org.hibernate.SessionFactory; +import org.hibernate.Transaction; +import org.junit.Before; +import org.junit.Test; + +import com.baeldung.hibernate.pojo.Supplier; + + +public class MultiTenantHibernateTest { + @Test + public void givenDBMode_whenFetchingSuppliers_thenComparingFromDbs () { + SessionFactory sessionFactory; + try { + sessionFactory = HibernateMultiTenantUtil.getSessionFactory(); + + Session db1Session = sessionFactory + .withOptions().tenantIdentifier("mydb1").openSession(); + + initDb1(db1Session); + + Transaction transaction = db1Session.getTransaction(); + transaction.begin(); + Supplier supplierFromDB1 = (Supplier)db1Session.createCriteria(Supplier.class).list().get(0); + transaction.commit(); + + Session db2Session = sessionFactory + .withOptions().tenantIdentifier("mydb2").openSession(); + + initDb2(db2Session); + db2Session.getTransaction().begin(); + Supplier supplierFromDB2 = (Supplier) db2Session.createCriteria(Supplier.class).list().get(0); + db2Session.getTransaction().commit(); + + System.out.println(supplierFromDB1); + System.out.println(supplierFromDB2); + + assertNotEquals(supplierFromDB1, supplierFromDB2); + } catch (UnsupportedTenancyException e) { + e.printStackTrace(); + } + } + + + + private void initDb1(Session db1Session) { + System.out.println("Init DB1"); + Transaction transaction = db1Session.getTransaction(); + transaction.begin(); + db1Session.createSQLQuery("DROP ALL OBJECTS").executeUpdate(); + db1Session.createSQLQuery("create table Supplier (id integer generated by default as identity, country varchar(255), name varchar(255), primary key (id))").executeUpdate(); + db1Session.createSQLQuery("insert into Supplier (id, country, name) values (null, 'John', 'USA')").executeUpdate(); + transaction.commit(); + } + + private void initDb2(Session db2Session) { + System.out.println("Init DB2"); + Transaction transaction = db2Session.getTransaction(); + transaction.begin(); + db2Session.createSQLQuery("DROP ALL OBJECTS").executeUpdate(); + db2Session.createSQLQuery("create table Supplier (id integer generated by default as identity, country varchar(255), name varchar(255), primary key (id))").executeUpdate(); + db2Session.createSQLQuery("insert into Supplier (id, country, name) values (null, 'Miller', 'UK')").executeUpdate(); + transaction.commit(); + } +} diff --git a/pom.xml b/pom.xml index a705ac7cb1..7671d05d75 100644 --- a/pom.xml +++ b/pom.xml @@ -226,7 +226,7 @@ 1.6.0 maven - + hibernate5 @@ -253,4 +253,4 @@ --> - + \ No newline at end of file