更新 3
下面的示例和解释与 GitHub 中的相同
更新 2
GitHub 中的示例现在正在运行。最面向未来的解决方案似乎是使用存储库扩展。将很快更新下面的示例。
更新
请注意,我最初发布的解决方案存在一些我在 JMeter 测试期间发现的缺陷。 Datastax Java 驱动程序参考建议避免通过Session 对象设置键空间。您必须在每个查询中明确设置键空间。
我更新了 GitHub 存储库并更改了解决方案的描述。
不过要非常小心:如果会话由多个线程共享,
在运行时切换键空间很容易导致意外的查询失败。
一般来说,推荐的方法是使用单个会话,没有
键空间,并为所有查询添加前缀。
解决方案说明
我会为这个特定客户设置一个单独的键空间,并为在应用程序中更改键空间提供支持。我们之前在生产环境中将这种方法与 RDBMS 和 JPA 一起使用。所以,我想说它也可以与 Cassandra 一起使用。解决方法类似如下。
我将简要描述如何准备和设置 Spring Data Cassandra 以在每个请求上配置目标键空间。
第 1 步:准备服务
我将首先定义如何在每个请求上设置租户 ID。一个很好的例子是 REST API 是使用定义它的特定 HTTP 标头:
Tenant-Id: ACME
类似地,在每个远程协议上,您都可以在每条消息上转发租户 ID。假设您使用 AMQP 或 JMS,您可以在消息头或属性中转发此消息。
第 2 步:在应用程序中获取租户 ID
接下来,您应该将传入的标头存储在控制器内的每个请求中。您可以使用ThreadLocal,也可以尝试使用请求范围的 bean。
@Component
@Scope(scopeName = "request", proxyMode= ScopedProxyMode.TARGET_CLASS)
public class TenantId {
private String tenantId;
public void set(String id) {
this.tenantId = id;
}
public String get() {
return tenantId;
}
}
@RestController
public class UserController {
@Autowired
private UserRepository userRepo;
@Autowired
private TenantId tenantId;
@RequestMapping(value = "/userByName")
public ResponseEntity<String> getUserByUsername(
@RequestHeader("Tenant-ID") String tenantId,
@RequestParam String username) {
// Setting the tenant ID
this.tenantId.set(tenantId);
// Finding user
User user = userRepo.findOne(username);
return new ResponseEntity<>(user.getUsername(), HttpStatus.OK);
}
}
第 3 步:在数据访问层设置租户 ID
最后你应该根据租户 ID 扩展 Repository 实现并设置密钥空间
public class KeyspaceAwareCassandraRepository<T, ID extends Serializable>
extends SimpleCassandraRepository<T, ID> {
private final CassandraEntityInformation<T, ID> metadata;
private final CassandraOperations operations;
@Autowired
private TenantId tenantId;
public KeyspaceAwareCassandraRepository(
CassandraEntityInformation<T, ID> metadata,
CassandraOperations operations) {
super(metadata, operations);
this.metadata = metadata;
this.operations = operations;
}
private void injectDependencies() {
SpringBeanAutowiringSupport
.processInjectionBasedOnServletContext(this,
getServletContext());
}
private ServletContext getServletContext() {
return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
.getRequest().getServletContext();
}
@Override
public T findOne(ID id) {
injectDependencies();
CqlIdentifier primaryKey = operations.getConverter()
.getMappingContext()
.getPersistentEntity(metadata.getJavaType())
.getIdProperty().getColumnName();
Select select = QueryBuilder.select().all()
.from(tenantId.get(),
metadata.getTableName().toCql())
.where(QueryBuilder.eq(primaryKey.toString(), id))
.limit(1);
return operations.selectOne(select, metadata.getJavaType());
}
// All other overrides should be similar
}
@SpringBootApplication
@EnableCassandraRepositories(repositoryBaseClass = KeyspaceAwareCassandraRepository.class)
public class DemoApplication {
...
}
如果上面的代码有任何问题,请告诉我。
GitHub 中的示例代码
https://github.com/gitaroktato/spring-boot-cassandra-multitenant-example
参考文献