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开发者的一项重要技能。

 

MYSQL SCHEMA是什么意思?

MySQL是一种应用最广泛的开源关系数据库,被许多常见的网站、应用程序和商业产品用作主要的关系数据存储。MySQL拥有20多年的社区开发和支持历史,是一种可靠、稳定且安全的基于SQL的数据库管理系统。MySQL数据库适用于各种使用案例,包括任务关键型应用程序、动态网站以及用于软件、硬件和设备的嵌入式数据库。

在MySQL中,Schema是指数据库中的逻辑容器,用于组织和管理数据库对象,如表、视图、存储过程等。Schema可以看作是数据库的命名空间,用于将数据库对象进行分类和隔离。每个Schema都有一个唯一的名称,用于在数据库中引用和访问其中的对象。

在MySQL中,一个数据库可以包含多个Schema,每个Schema可以包含多个表。通过使用Schema,可以将数据库对象进行逻辑分组,提高数据库的可维护性和可扩展性。不同的Schema之间可以相互独立,可以有不同的访问权限和安全设置。

在创建表时,可以选择将其放置在特定的Schema中。通过将表放置在不同的Schema中,可以更好地组织和管理数据库对象。例如,可以将用户相关的表放置在一个Schema中,将产品相关的表放置在另一个Schema中,以此类推。

使用Schema可以提供更好的数据隔离和安全性。通过为不同的用户或应用程序分配不同的Schema,可以确保它们只能访问其所需的数据和对象,而无法访问其他Schema中的数据。这种隔离性可以有效地保护数据的安全性,防止未经授权的访问和操作。

此外,Schema还可以用于简化数据库对象的引用和访问。通过在查询中指定Schema名称,可以直接引用特定Schema中的表或其他对象,而无需使用完全限定的对象名称。这样可以简化查询语句,提高开发效率。

总之,MySQL Schema是用于组织和管理数据库对象的逻辑容器。通过使用Schema,可以提高数据库的可维护性、可扩展性和安全性。它是MySQL数据库中重要的概念之一,对于开发人员和数据库管理员来说,了解和使用Schema是非常重要的。

 

posted @ 2025-03-27 11:13  liftsail  阅读(613)  评论(0)    收藏  举报