当计划作业中的代码为 运行 时,如何在多租户 grails 应用程序中解析数据库租户?
How to resolve a database tenant in a multi tenant grails app when the code is run from a scheduled job?
我正在使用 grails 4.0.3,该应用程序在数据库级别是多租户的。我正在根据请求的子域解析租户。多租户适用于除预定作业之外的所有情况。我的理解是,由于计划作业没有请求范围,因此永远不会调用租户解析器,因为没有请求触发租户解析器。
我有一个在计划作业中进行的数据库事务。并且这些事务总是指向默认数据库。
如何在没有请求范围的情况下解析租户。
我的 application.yml 租户解析器配置如下:
grails:
profile: web
codegen:
defaultPackage: com.pomco.middleware
gorm:
reactor:
# Whether to translate GORM events into Reactor events
# Disabled by default for performance reasons
events: false
multiTenancy:
mode: DATABASE
tenantResolverClass: com.pomco.middleware.multitenant.CustomSubDomainTenantResolver
CustomSubDomainTenantResolver 看起来像这样
class CustomSubDomainTenantResolver implements TenantResolver{
@Override
Serializable resolveTenantIdentifier() {
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes()
if(requestAttributes instanceof ServletWebRequest) {
HttpServletRequest httpServletRequest = ((ServletWebRequest) requestAttributes).getRequest()
def env = DatasourceEnvironment.lookupEnvironmentByHostname(httpServletRequest.getServerName())
String subDomainId = env?.tenantId
if( subDomainId) {
if(!DatabaseProvisioningService.getSourceByTenantId(subDomainId) && subDomainId != 'DEFAULT'){
def tenantByUrl = DatabaseProvisioningService.tenantSources.find {it.value == DatabaseProvisioningService.getDbUrlByEnv(env)}
if (tenantByUrl){
return tenantByUrl.key
}
}
return subDomainId
}
else {
return ConnectionSource.DEFAULT
}
}
else if(!requestAttributes)
return ConnectionSource.DEFAULT
throw new TenantNotFoundException("Tenant could not be resolved outside a web request")
}
}
此问题的解决方案是通过在计划作业中使用 withId
闭包手动使用租户。
例如,我无法在以下预定作业中获得租户。
@CurrentTenant
def ExampleService{
// this method runs with required tenant as this is a service method annotated with @CurrentTenant
def methodThatHasTenantScope(){
ScheduledFuture<?> helperFuture = null
def result = null
ScheduledFuture<?> future = ses.scheduleWithFixedDelay(new Runnable() {
// BUT here there is no tenant scope as this is a threaded task which doesn't have a request scope
// so this job runs with default tenant
private long count = 0
@Override
void run() {
// job task here runs with default tenant
}
}, DELAY, FREQUENCY_MILLIS, TimeUnit.MILLISECONDS)
return result
}
}
现在我们需要在方法methodThatHasTenantScope
中手动获取租户id,并传递给调度作业方法如下:
@CurrentTenant
def ExampleService{
@Autowired
HibernateDatastore hibernateDatastore
def methodThatHasTenantScope(){
Serializable tenantId = Tenants.currentId(HibernateDatastore)
log.trace("Current tenant id: $tenantId")
ScheduledFuture<?> helperFuture = null
def result = null
ScheduledFuture<?> future = ses.scheduleWithFixedDelay(new Runnable() {
private long count = 0
@Override
void run() {
//here we have tenantId from the scope of the method
withId(tenantId){
// job task runs with required tenant
}
}
}, DELAY, FREQUENCY_MILLIS, TimeUnit.MILLISECONDS)
return result
}
}
我正在使用 grails 4.0.3,该应用程序在数据库级别是多租户的。我正在根据请求的子域解析租户。多租户适用于除预定作业之外的所有情况。我的理解是,由于计划作业没有请求范围,因此永远不会调用租户解析器,因为没有请求触发租户解析器。 我有一个在计划作业中进行的数据库事务。并且这些事务总是指向默认数据库。
如何在没有请求范围的情况下解析租户。
我的 application.yml 租户解析器配置如下:
grails:
profile: web
codegen:
defaultPackage: com.pomco.middleware
gorm:
reactor:
# Whether to translate GORM events into Reactor events
# Disabled by default for performance reasons
events: false
multiTenancy:
mode: DATABASE
tenantResolverClass: com.pomco.middleware.multitenant.CustomSubDomainTenantResolver
CustomSubDomainTenantResolver 看起来像这样
class CustomSubDomainTenantResolver implements TenantResolver{
@Override
Serializable resolveTenantIdentifier() {
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes()
if(requestAttributes instanceof ServletWebRequest) {
HttpServletRequest httpServletRequest = ((ServletWebRequest) requestAttributes).getRequest()
def env = DatasourceEnvironment.lookupEnvironmentByHostname(httpServletRequest.getServerName())
String subDomainId = env?.tenantId
if( subDomainId) {
if(!DatabaseProvisioningService.getSourceByTenantId(subDomainId) && subDomainId != 'DEFAULT'){
def tenantByUrl = DatabaseProvisioningService.tenantSources.find {it.value == DatabaseProvisioningService.getDbUrlByEnv(env)}
if (tenantByUrl){
return tenantByUrl.key
}
}
return subDomainId
}
else {
return ConnectionSource.DEFAULT
}
}
else if(!requestAttributes)
return ConnectionSource.DEFAULT
throw new TenantNotFoundException("Tenant could not be resolved outside a web request")
}
}
此问题的解决方案是通过在计划作业中使用 withId
闭包手动使用租户。
例如,我无法在以下预定作业中获得租户。
@CurrentTenant
def ExampleService{
// this method runs with required tenant as this is a service method annotated with @CurrentTenant
def methodThatHasTenantScope(){
ScheduledFuture<?> helperFuture = null
def result = null
ScheduledFuture<?> future = ses.scheduleWithFixedDelay(new Runnable() {
// BUT here there is no tenant scope as this is a threaded task which doesn't have a request scope
// so this job runs with default tenant
private long count = 0
@Override
void run() {
// job task here runs with default tenant
}
}, DELAY, FREQUENCY_MILLIS, TimeUnit.MILLISECONDS)
return result
}
}
现在我们需要在方法methodThatHasTenantScope
中手动获取租户id,并传递给调度作业方法如下:
@CurrentTenant
def ExampleService{
@Autowired
HibernateDatastore hibernateDatastore
def methodThatHasTenantScope(){
Serializable tenantId = Tenants.currentId(HibernateDatastore)
log.trace("Current tenant id: $tenantId")
ScheduledFuture<?> helperFuture = null
def result = null
ScheduledFuture<?> future = ses.scheduleWithFixedDelay(new Runnable() {
private long count = 0
@Override
void run() {
//here we have tenantId from the scope of the method
withId(tenantId){
// job task runs with required tenant
}
}
}, DELAY, FREQUENCY_MILLIS, TimeUnit.MILLISECONDS)
return result
}
}