java实现多租户系统,数据隔离的代码实现

随着云计算和SaaS模式的快速发展,多租户架构已经成为构建可扩展、高效且成本效益高的应用系统的关键。多租户架构允许单个应用实例同时为多个租户提供服务,每个租户都享有独立的数据、配置和隐私空间,同时共享相同的底层硬件和软件资源。在Java生态系统中,有多种方法和策略可以实现多租户系统。本文主要是关于java实现数据库层次的数据隔离相关代码实现。

基于数据库的隔离的三种方式

  1. 独立数据库模式:每个租户使用独立的数据库实例。这种方式提供了最高的数据隔离性和安全性,但也可能导致较高的硬件和管理成本。
  2. 独立模式:每个租户使用独立的数据库架构(schema)。这种方式在数据库实例层面实现了共享,但在架构层面保持了隔离,是一种折衷方案。
  3. 表字段模式:所有租户的数据都存储在同一个数据库和架构中,但每张表都有一个租户ID字段,对数据库操作都带上租户ID字段

1.独立数据库模式

不同的租户访问不同的数据源,建立自己的数据库连接池,我是通过dynamic来实现多数据源管理的,本质上就是一个map,存放key和数据库连接池信息。前端每次请求携带租户信息,到dynamic维护的map中查询是否存在,不存在则获取该租户的数据库连接信息创建连接池放入dynamic的map中。

public class LoginInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {


        setDataSource();
        return true;
    }

    //设置本次访问数据源连接池
    private void setDataSource() {
        String dbCode = ServerSessionHolder.getSessionUser().getDbCode();
        if (StringUtils.isNotBlank(dbCode)) {
            DynamicRoutingDataSource dynamicRoutingDataSource = StaticMethodGetBean.applicationContext.getBean(DynamicRoutingDataSource.class);
            if (!dynamicRoutingDataSource.getDataSources().containsKey(dbCode)) {
                IUserClientService userClientService = StaticMethodGetBean.applicationContext.getBean(IUserClientService.class);
                //获取登录租户的数据库连接信息
                DataSourceInfoVO vo = userClientService.getByCode(dbCode);
                dynamicRoutingDataSource.addDataSource(dbCode, CommonUtils.createDataSource(vo.getDbIp(), vo.getDbPort().toString(), vo.getDbUsername(), vo.getDbPasswd()));
            }
            DynamicDataSourceContextHolder.push(dbCode);
        }
    }

    //创建数据库连接池
    public static DruidDataSource createDataSource(String ip, String port, String userName, String passwd) {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName(DatabaseTypeEnum.MYSQL.driverClass());
        String url = DatabaseTypeEnum.MYSQL.jdbcPrefix().concat(ip).concat(COLON).concat(port).concat("?characterEncoding=UTF-8&serverTimezone=Asia/Shanghai");
        dataSource.setUrl(url);
        dataSource.setUsername(userName);
        dataSource.setPassword(passwd);
        dataSource.setInitialSize(2);
        dataSource.setMinIdle(2);
        dataSource.setMaxActive(20);
        return dataSource;
    }
}

2.独立模式

不同租户使用同一个数据源的不同schema,这种方式比较节约资源,也方便数据库管理,每多一个租户就创建一个schema,创建相应的表结构。这种模式的重点是相同表名,不同的schema,我们需要采用不代码侵入的方式实现,这个我是通过mybatisplus实现的,mybatisplus中有个TableNameHandler接口,我们实现这个接口,实现它的dynamicTableName方法,就可以对sql语句中的表名进行处理。

public class DynamicTableNameHandler implements TableNameHandler {

    @Override
    public String dynamicTableName(String sql, String tableName) {
        //获取本次访问租户对应的schema
        String dbName = ServerSessionHolder.getSessionUser().getDbName();
        if (!StringUtils.isEmpty(dbName)) {
            tableName = dbName.concat(".").concat(tableName);
        }
        return tableName;
    }
}

3.表字段模式

本种方式是每一张租户相关表中就加一个tenant_id字段,对数据库操作时每张表都带上这个字段信息,这种方式是隔离度最低的但也是最好管理的。这个是通过mybatisplus中的TenantLineHandler实现的。

public class MyTenantLineHandler implements TenantLineHandler {
    //获取租户ID的值
    @Override
    public Expression getTenantId() {
        return new LongValue(ServerSessionHolder.getSessionUser().getId());
    }

    //定义表中隔离字段名称,默认是tenant_id
    @Override
    public String getTenantIdColumn() {
        return CommonConstant.CREATE_USER;
    }
}

第二种和第三中方式需要加入MybatisPlusInterceptor中

@Configuration
public class MybatisPlusConfig {



    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        DynamicTableNameInnerInterceptor dynamicTableNameInnerInterceptor = new DynamicTableNameInnerInterceptor();
        dynamicTableNameInnerInterceptor.setTableNameHandler(new DynamicTableNameHandler());
        interceptor.addInnerInterceptor(dynamicTableNameInnerInterceptor);
        interceptor.addInnerInterceptor(new MyTenantLineInnerInterceptor(new MyTenantLineHandler()));
        return interceptor;
    }
}

总结

在java中实现多租户需要结合实际情况选择相应的方案,没有最好的方案只有最适合的方案。通过合理的设计和选型,可以构建出既高效又安全的多租户系统。随着云计算和SaaS模式的不断发展,多租户系统的设计和实现将成为Java开发者的一项重要技能。

  • 6
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
实现MySQL多租户数据隔离的一种方法是在数据库中为每个租户创建单独的schema(即数据库),并将其与单独的用户关联起来。下面是使用Java代码实现该方法的步骤: 1. 创建一个Java类来管理数据库连接和操作。这个类应该包含以下方法: ``` public class TenantDatabaseManager { private static final String DATABASE_HOST = "localhost"; private static final String DATABASE_USERNAME = "root"; private static final String DATABASE_PASSWORD = "password"; private static final String DATABASE_DRIVER = "com.mysql.jdbc.Driver"; private Connection connection; public TenantDatabaseManager(String tenantId) throws SQLException, ClassNotFoundException { String databaseName = "tenant_" + tenantId; Class.forName(DATABASE_DRIVER); this.connection = DriverManager.getConnection("jdbc:mysql://" + DATABASE_HOST + "/" + databaseName, DATABASE_USERNAME, DATABASE_PASSWORD); } public void executeQuery(String query) throws SQLException { Statement statement = connection.createStatement(); statement.executeQuery(query); statement.close(); } public void executeUpdate(String query) throws SQLException { Statement statement = connection.createStatement(); statement.executeUpdate(query); statement.close(); } public void closeConnection() throws SQLException { this.connection.close(); } } ``` 2. 创建一个Java类来管理租户列表。这个类应该包含以下方法: ``` public class TenantManager { private Map<String, TenantDatabaseManager> tenantDatabaseManagerMap = new HashMap<>(); public void addTenant(String tenantId) throws SQLException, ClassNotFoundException { if (!tenantDatabaseManagerMap.containsKey(tenantId)) { TenantDatabaseManager tenantDatabaseManager = new TenantDatabaseManager(tenantId); tenantDatabaseManagerMap.put(tenantId, tenantDatabaseManager); } } public TenantDatabaseManager getTenantDatabaseManager(String tenantId) { return tenantDatabaseManagerMap.get(tenantId); } public void closeAllConnections() throws SQLException { for (TenantDatabaseManager tenantDatabaseManager : tenantDatabaseManagerMap.values()) { tenantDatabaseManager.closeConnection(); } tenantDatabaseManagerMap.clear(); } } ``` 3. 在每次请求到达时,从请求中获取租户ID,并使用TenantManager获取对应的TenantDatabaseManager对象。然后使用该对象执行数据库操作。 ``` public class RequestHandler { private static final String TENANT_HEADER = "Tenant-Id"; private TenantManager tenantManager; public RequestHandler() { tenantManager = new TenantManager(); } public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException, SQLException, ClassNotFoundException { String tenantId = request.getHeader(TENANT_HEADER); if (tenantId == null) { response.setStatus(HttpServletResponse.SC_BAD_REQUEST); response.getWriter().println("Tenant Id is required."); return; } TenantDatabaseManager tenantDatabaseManager = tenantManager.getTenantDatabaseManager(tenantId); if (tenantDatabaseManager == null) { tenantManager.addTenant(tenantId); tenantDatabaseManager = tenantManager.getTenantDatabaseManager(tenantId); } // execute database operations using tenantDatabaseManager ... } public void destroy() throws SQLException { tenantManager.closeAllConnections(); } } ``` 以上就是使用Java代码实现MySQL多租户数据隔离的方法。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值