<property name="hbm2ddl.auto">update</property>
<!--<property name="hbm2ddl.auto">update</property>--> <property name="connection.datasource">java:/portalDS</property> <property name="hibernate.dialect">by.tychina.PostgreSQL9MultiTenantDialect</property> <property name="hibernate.multiTenancy">SCHEMA</property> <property name="hibernate.multi_tenant_connection_provider">by.tychina.tenant.HibernateMultiTenantConnectionProvider</property> <property name="hibernate.tenant_identifier_resolver">by.tychina.tenant.HibernateTenantIdentifierResolver</property> <property name="org.hibernate.envers.audit_strategy">org.hibernate.envers.strategy.ValidityAuditStrategy</property>
package by.tychina.tenant; /** * @author Sergey Tychina */ public class TenantId { /** * Represents database schema name */ private String tenantId; public TenantId(String tenantId) { this.tenantId = tenantId; } public String getTenantId() { return tenantId; } }
where the tenantId field is the name of the schema in the database. package by.tychina; import org.hibernate.SessionFactory; import org.hibernate.cfg.Configuration; /** * @author Sergey Tychina */ public class Persistence { private final Configuration configuration; private final SessionFactory sessionFactory; private static Persistence instance = null; private Persistence() { configuration = new Configuration(); configuration.configure(); sessionFactory = configuration.buildSessionFactory(); } public static synchronized Persistence getInstance() { if (instance == null) { instance = new Persistence(); } return instance; } public Configuration getConfiguration() { return configuration; } public SessionFactory getSessionFactory() { return sessionFactory; } }
package by.tychina; import by.tychina.tenant.TenantId; import javax.naming.InitialContext; import javax.sql.DataSource; import java.sql.Connection; import java.sql.SQLException; /** * @author Sergey Tychina */ public class ConnectionProvider { private static final String DATASOURCE_JNDI_NAME = "java:/portalDS"; private static DataSource dataSource = null; private static synchronized DataSource getDataSource() { if (dataSource == null) { try { dataSource = (DataSource) new InitialContext().lookup(DATASOURCE_JNDI_NAME); } catch (Exception e) { throw new RuntimeException(e); } } return dataSource; } public static Connection getConnection() { try { return getDataSource().getConnection(); } catch (SQLException e) { throw new RuntimeException("Unable to get connection", e); } } public static Connection getConnection(TenantId tenantId) { Connection connection = getConnection(); try { connection.createStatement().execute("SET SCHEMA '" + tenantId.getTenantId() + "'"); } catch (SQLException e) { try { connection.close(); } catch (SQLException e1) { e1.printStackTrace(); } throw new RuntimeException("Could not alter connection to '" + tenantId.getTenantId() + "' schema", e); } return connection; } }
package by.tychina.tenant; import by.tychina.ConnectionProvider; import org.hibernate.engine.jdbc.connections.spi.MultiTenantConnectionProvider; import java.sql.Connection; import java.sql.SQLException; /** * @author Sergey Tychina */ public class HibernateMultiTenantConnectionProvider implements MultiTenantConnectionProvider { @Override public Connection getAnyConnection() throws SQLException { return ConnectionProvider.getConnection(); } @Override public void releaseAnyConnection(Connection connection) throws SQLException { connection.close(); } @Override public Connection getConnection(String tenantIdentifier) throws SQLException { return ConnectionProvider.getConnection(new TenantId(tenantIdentifier)); } @Override public void releaseConnection(String tenantIdentifier, Connection connection) throws SQLException { releaseAnyConnection(connection); } @Override public boolean supportsAggressiveRelease() { return false; } @Override public boolean isUnwrappableAs(Class unwrapType) { return false; } @Override public <T> T unwrap(Class<T> unwrapType) { return null; } }
package by.tychina.tenant; import org.hibernate.context.spi.CurrentTenantIdentifierResolver; /** * @author Sergey Tychina */ public class HibernateTenantIdentifierResolver implements CurrentTenantIdentifierResolver { @Override public String resolveCurrentTenantIdentifier() { // Put some logic here to get current transaction tenant id // Use ThreadLocal for example to store TenantId when starting a transaction TenantId transactionTenantId = null; return transactionTenantId.getTenantId(); } @Override public boolean validateExistingCurrentSessions() { return true; } }
public String getQuerySequencesString() { return "select relname from pg_class where relkind='S'"; }
package by.tychina; import org.hibernate.dialect.PostgreSQL9Dialect; /** * @author Sergey Tychina */ public class PostgreSQL9MultiTenantDialect extends PostgreSQL9Dialect { /** * Query to get sequences for current schema ONLY. Original method returns query to get all sequences (in all schemas) * that breaks multi-tenancy. */ @Override public String getQuerySequencesString() { return "SELECT c.relname FROM pg_catalog.pg_class c " + "LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace " + "WHERE c.relkind IN ('S','') " + "AND n.nspname <> 'pg_catalog' " + "AND n.nspname <> 'information_schema' " + "AND n.nspname !~ '^pg_toast' " + "AND pg_catalog.pg_table_is_visible(c.oid)"; } }
demo_portal=> \ds ********* QUERY ********** SELECT n.nspname as "Schema", c.relname as "Name", CASE c.relkind WHEN 'r' THEN 'table' WHEN 'v' THEN 'view' WHEN 'i' THEN 'index' WHEN 'S' THEN 'sequence' WHEN 's' THEN 'special' WHEN 'f' THEN 'foreign table' END as "Type", pg_catalog.pg_get_userbyid(c.relowner) as "Owner" FROM pg_catalog.pg_class c LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE c.relkind IN ('S','') AND n.nspname <> 'pg_catalog' AND n.nspname <> 'information_schema' AND n.nspname !~ '^pg_toast' AND pg_catalog.pg_table_is_visible(c.oid) ORDER BY 1,2; **************************
<property name="hibernate.dialect">by.tychina.PostgreSQL9MultiTenantDialect</property>
package by.tychina.schema; import by.tychina.ConnectionProvider; import by.tychina.Persistence; import by.tychina.tenant.TenantId; import org.hibernate.cfg.Configuration; import org.hibernate.cfg.Environment; import org.hibernate.dialect.Dialect; import org.hibernate.engine.jdbc.internal.FormatStyle; import org.hibernate.engine.jdbc.internal.Formatter; import org.hibernate.tool.hbm2ddl.DatabaseMetadata; import org.hibernate.tool.hbm2ddl.SchemaUpdateScript; import java.sql.Connection; import java.sql.SQLException; import java.sql.Statement; import java.util.List; /** * @author Sergey Tychina */ public class MultiTenantHibernateSchemaUpdate { public static void execute(TenantId tenantId) { Connection connection = ConnectionProvider.getConnection(tenantId); Configuration configuration = Persistence.getInstance().getConfiguration(); String originalSchema = configuration.getProperty(Environment.DEFAULT_SCHEMA); Statement stmt = null; try { Dialect dialect = Dialect.getDialect(configuration.getProperties()); Formatter formatter = FormatStyle.DDL.getFormatter(); configuration.setProperty(Environment.DEFAULT_SCHEMA, tenantId.getTenantId()); DatabaseMetadata meta = new DatabaseMetadata(connection, dialect, configuration); stmt = connection.createStatement(); List<SchemaUpdateScript> scripts = configuration.generateSchemaUpdateScriptList(dialect, meta); for (SchemaUpdateScript script : scripts) { String formatted = formatter.format(script.getScript()); stmt.executeUpdate(formatted); } } catch (SQLException e) { throw new RuntimeException(e); } finally { if (originalSchema != null) { configuration.setProperty(Environment.DEFAULT_SCHEMA, originalSchema); } else { configuration.getProperties().remove(Environment.DEFAULT_SCHEMA); } try { if (stmt != null) { stmt.close(); } connection.close(); } catch (SQLException e) { e.printStackTrace(); } } } }
configuration.setProperty(Environment.DEFAULT_SCHEMA, tenantId.getTenantId());
List<SchemaUpdateScript> scripts = configuration.generateSchemaUpdateScriptList(dialect, meta);
for (SchemaUpdateScript script : scripts) { String formatted = formatter.format(script.getScript()); stmt.executeUpdate(formatted); }
if (originalSchema != null) { configuration.setProperty(Environment.DEFAULT_SCHEMA, originalSchema); } else { configuration.getProperties().remove(Environment.DEFAULT_SCHEMA); }
MultiTenantHibernateSchemaUpdate.execute(tenantId);
Source: https://habr.com/ru/post/252353/
All Articles