云架构:hibernate4已经支持多租户了,但是如何创建表?

沙舟狼客 2016-06-22
多租户分三种设计:
1.一个应用对应一个数据库。完全的物理隔离!每个用户一套系统!
2.所有用户公用一个应用但是不同的数据库(schme)
3.所有用户公用一个应用。一个数据库

目前我采用的是第二种设计
hibernat额配置

  <prop key="hibernate.multiTenancy">SCHEMA</prop>
                <!--<prop key="hibernate.multiTenancy">DATABASE</prop>-->
                <prop key="hibernate.tenant_identifier_resolver">cutdb.hb.TenantIdResolver</prop>
                <prop key="hibernate.multi_tenant_connection_provider">
                    cutdb.hb.SchemaBasedMultiTenantConnectionProvider
                </prop>


自己实现tenant_identifier_resolver和multi_tenant_connection_provider
SchemaBasedMultiTenantConnectionProvider代码
package cutdb.hb;

import org.hibernate.HibernateException;
import org.hibernate.Query;
import org.hibernate.cfg.Environment;
import org.hibernate.engine.config.spi.ConfigurationService;
import org.hibernate.engine.jdbc.connections.internal.DatasourceConnectionProviderImpl;
import org.hibernate.engine.jdbc.connections.spi.MultiTenantConnectionProvider;
import org.hibernate.service.spi.ServiceRegistryAwareService;
import org.hibernate.service.spi.ServiceRegistryImplementor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.Marker;
import org.slf4j.MarkerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Map;

/**
 * Created by trq on 2016/6/20.
 * DriverManagerdatasourceConnectionProviderImpl类是直接读取hibernate配置文件的,
 * 而DatasourcedatasourceConnectionProviderImpl是能够读取spring配置文件中的DataSource。
 */

public class SchemaBasedMultiTenantConnectionProvider implements MultiTenantConnectionProvider, ServiceRegistryAwareService {
    private static Logger logger = LoggerFactory.getLogger(SchemaBasedMultiTenantConnectionProvider.class);
    private DatasourceConnectionProviderImpl datasourceConnectionProvider = new DatasourceConnectionProviderImpl();

    public Connection getAnyConnection() throws SQLException {
        return datasourceConnectionProvider.getConnection();
    }

    public void releaseAnyConnection(Connection connection) throws SQLException {
        datasourceConnectionProvider.closeConnection(connection);
    }

    private void useDb(String dbName, Connection connection) {
        try {
            PreparedStatement ps = connection.prepareStatement("SELECT count(sc.SCHEMA_NAME) FROM information_schema.SCHEMATA sc where sc.SCHEMA_NAME=?");
            ps.setString(1, dbName);
            ResultSet rs = ps.executeQuery();
            if (rs.next()) {
                if (rs.getInt(1) == 0) {
                    connection.createStatement().execute("create database "+dbName+" DEFAULT  character set  utf8");
                }
            }
            connection.createStatement().execute("use " + dbName);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    public Connection getConnection(String tenantIdentifier) throws SQLException {
        final Connection connection = getAnyConnection();
        useDb(tenantIdentifier, connection);
        return connection;
    }

    public void releaseConnection(String tenantIdentifier, Connection connection) throws SQLException {
        useDb("test", connection);
        datasourceConnectionProvider.closeConnection(connection);
    }

    public boolean supportsAggressiveRelease() {
        return true;
    }


    public void stop() {
        datasourceConnectionProvider.stop();
    }

    public boolean isUnwrappableAs(Class unwrapType) {
        return datasourceConnectionProvider.isUnwrappableAs(unwrapType);
    }

    public <T> T unwrap(Class<T> unwrapType) {
        return datasourceConnectionProvider.unwrap(unwrapType);
    }


    @Override
    public void injectServices(ServiceRegistryImplementor serviceRegistry) {
        Map settings = serviceRegistry.getService(ConfigurationService.class).getSettings();
        DataSource dataSource = (DataSource) settings.get(Environment.DATASOURCE);
        datasourceConnectionProvider.setDataSource(dataSource);
        datasourceConnectionProvider.configure(settings);
        logger.debug("connection provider:{}", datasourceConnectionProvider);
    }
}


TenantIdResolver代码
package cutdb.hb;

import cutdb.common.model.AppContext;
import org.hibernate.Session;
import org.hibernate.context.spi.CurrentTenantIdentifierResolver;

/**
 * Created by trq on 2016/6/20.
 */
public class TenantIdResolver implements CurrentTenantIdentifierResolver {
    @Override
    public String resolveCurrentTenantIdentifier() {
        return AppContext.currentTenantId;
    }

    @Override
    public boolean validateExistingCurrentSessions() {
        return true;
    }
}



现在问题出来了。我已使用原生sql动态创建数据库,但是如何动态创建表呢?
沙舟狼客 2016-06-22
多租户数据库设计
沙舟狼客 2016-06-22
关于云架构设计,技术实现,希望大家能进群(487490324)跟我一起讨论。一起学习
沙舟狼客 2016-06-22
创建数据库的时候增加触发器,自动执行建表脚本。貌似这样最简单。没法放到程序里面调用了
沙舟狼客 2016-06-22
问题已经解决,代码放在github,有好的建议欢迎一起交流!
地址:https://github.com/ligson/cutdb
LinApex 2016-06-24
有什么用啊?
zxspopo 2016-06-24
也可以从连接池的角度分析下这个问题。
沙舟狼客 2016-06-27
LinApex 写道
有什么用啊?

对租户来说:
1.数据想独立
2.可能有定制功能
3.想用更少的资金
对开发者来说:
1.想适应更多的租户需求
2.更少的工作量
3.更快的开发速度

而多租户正好解决了这种需求,由于多租户技术可以让多个租户共用一个应用程序或运算环境,且租户大多不会使用太多运算资源的情况下,对供应商来说多租户技术可以有效的降低环境建置的成本。包含硬件本身的成本,操作系统与相关软件的授权成本都可以因为多租户技术,而由多个租户一起分担。
通过不同的数据管理手段,多租户技术的数据可以用不同的方式进行数据隔离,在供应商的架构设计下,数据的隔离方式也会不同,而良好的数据隔离法可以降低供应商的维护成本(包含设备与人力),而供应商可以在合理的授权范围内取用这些数据分析,以作为改善服务的依据。
多租户架构下所有用户都共用相同的软件环境,因此在软件改版时可以只发布一次,就能在所有租户的环境上生效。
具多租户架构的应用软件虽可客制,但客制难度较高,通常需要平台层的支持与工具的支持,才可降低客制化的复杂度。
kevincollins 2016-07-01
面向个人租户的,每个id 都有自己独有的数据库和表? 好傻。

面向机构租户,这么干 还行。

沙舟狼客 2016-07-06
kevincollins 写道
面向个人租户的,每个id 都有自己独有的数据库和表? 好傻。

面向机构租户,这么干 还行。



肯定得面向机构用户,个人用户这么干。不值得
Global site tag (gtag.js) - Google Analytics