如何强制Django使用服务名连接Oracle

13

问:如何指定Django需要使用服务名称而不是SID连接到Oracle数据库?

您好,

我目前正在告诉我的Django配置使用我的SID连接到Oracle。

但是,我将需要使用服务名称而不是SID进行连接。

APP_DATABASES={
    'default': {
            'ENGINE': 'django.db.backends.oracle',
            'NAME': 'myservice',
            'USER': 'system',
            'PASSWORD': 'admin123',
            'HOST': '192.168.1.45',
            'PORT': '1699',
    }
}

这个很好用。

然而,当我将“NAME”替换为服务名时,如下所示:

 'default': {
                'ENGINE': 'django.db.backends.oracle',
                'NAME': 'myservice.bose.com',
                'USER': 'system',
                'PASSWORD': 'admin123',
                'HOST': '192.168.1.45',
                'PORT': '1699',
        }
我得到了一个

ORA-12505: TNS:listener does not currently know of SID given in connect descriptor

很明显,Django要求使用SID连接Oracle,这不是我想要的。

如何指定Django需要使用服务而不是SID连接Oracle DB?

注意:我已经测试了上面提到的服务名并在Oracle SQL Developer中能够正常工作。

谢谢 - 非常感谢任何线索。

5个回答

27

谢谢大家,这个问题有一个“记录下来的”解决方案:

        'default': {
                'ENGINE': 'django.db.backends.oracle',
                'NAME': 'host.db.com:1699/oracle_service.db.com',
                'USER': 'user',
                'PASSWORD': 'pass',
        }
注意:需要从字典中删除HOST和PORT键 - 否则,Django将尝试将完整的“NAME”作为SID进行连接。

1
这个解决方案适用于我使用的Debian 7 / Oracle Instantclient 12.1.0.2.0 / Django 1.4。 - susundberg
1
如果您使用 django-environ,请使用 oracle://user:pass@/host.db.com:1699/oracle_service.db.com(这就是解决方法) - epineda
关于这个评论,在我的情况下,即使我将PORT和HOST设置为空字符串(Django 2.1.9),它也能正常工作。 - milosz

8

看一下nickzam贴出的代码:

import cx_Oracle as Database

def _connect_string(self):
    settings_dict = self.settings_dict
    if not settings_dict['HOST'].strip():
        settings_dict['HOST'] = 'localhost'
    if settings_dict['PORT'].strip():
        dsn = Database.makedsn(settings_dict['HOST'],
                               int(settings_dict['PORT']),
                               settings_dict['NAME'])
    else:
        dsn = settings_dict['NAME']
    return "%s/%s@%s" % (settings_dict['USER'],
                         settings_dict['PASSWORD'], dsn)

很明显,如果您没有指定“PORT”参数,则“NAME”参数将按原样使用。 因此,如果将Oracle连接字符串作为“NAME”参数传递(如果删除“PORT”参数),则可以解决问题。

基本上,像这样做会起作用:

'default': {
    'ENGINE': 'oraclepool',
    'NAME': '(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)(HOST=mydbhostname.example.com)(PORT=1521)))(CONNECT_DATA=(SERVICE_NAME=myservicename.example.com)))',
    'USER': 'scott',
    'PASSWORD': 'tiger',
}

我已经尝试使用SCAN主机名替换HOST,并验证了这也可以工作。

警告:到目前为止,我的测试仅限于检查连接字符串是否被接受,是否建立连接以及我的应用程序是否成功提供服务并访问数据。在依赖此配置之前,我建议进行更积极的测试 8)


1
这是一个非常巧妙的技巧。请记住,“显式优于隐式”。 - nickzam
我有一半的同意。话虽如此,这个解决方案必须比在没有提供支持的情况下对base.py进行黑客攻击要好。8) - dparkeruk
嘿,谢谢 - 这是一个有趣的方法 :) 看看我对自己问题的回答。 - sbose

4

我使用tnsnames.ora文件,在Django 1.7上运行良好。以下是具体步骤:

  1. Add an entry in tnsnames.ora for the connection.

    myservice =
     (DESCRIPTION = 
       (ADDRESS_LIST =
         (ADDRESS = (PROTOCOL = TCP)(HOST = 192.168.1.45)(PORT = 1699))
       )
     (CONNECT_DATA =
       (SERVICE_NAME = myservice.bose.com)
     )
    )
    
  2. Change Django database settings to

    'default': {
      'ENGINE': 'django.db.backends.oracle',
      'NAME': 'myservice',
      'USER': 'system',
      'PASSWORD': 'admin123',
    }
    

详细内容请参阅使用服务名称连接Django和Oracle数据库


3
在幕后,Django使用cx_Oracle库连接到Oracle数据库。 来源:https://github.com/django/django/blob/master/django/db/backends/oracle/base.py
import cx_Oracle as Database

def _connect_string(self):
        settings_dict = self.settings_dict
        if not settings_dict['HOST'].strip():
            settings_dict['HOST'] = 'localhost'
        if settings_dict['PORT'].strip():
            dsn = Database.makedsn(settings_dict['HOST'],
                                   int(settings_dict['PORT']),
                                   settings_dict['NAME'])
        else:
            dsn = settings_dict['NAME']
        return "%s/%s@%s" % (settings_dict['USER'],
                             settings_dict['PASSWORD'], dsn)

cx_Oracle.make_dsn()函数支持可选参数service_name(摘自cx_Oracle文档):

cx_Oracle.makedsn(host, port, sid[, service_name])

返回一个字符串,适用于connect()方法的dsn。此字符串与由Oracle名称服务器定义或在tnsnames.ora文件中定义的字符串相同。如果您希望使用服务名而不是sid,请不要为sid参数传递值,并使用关键字参数service_name。 注意 此方法是DB API定义的扩展。

不幸的是,Django在连接时没有传递service_name参数。

如果您确实需要它,请向Django添加功能请求或修补本地版本的Django以支持SERVICE_NAME参数(这是个坏主意,您将需要自己支持它):

def _connect_string(self):
    settings_dict = self.settings_dict
    if not settings_dict['HOST'].strip():
        settings_dict['HOST'] = 'localhost'
    if settings_dict['PORT'].strip():
        if not 'SERVICE_NAME' in settings_dict:
            dsn = Database.makedsn(settings_dict['HOST'],
                                   int(settings_dict['PORT']),
                                   settings_dict['NAME'])
        else:
            dsn = Database.makedsn(host=settings_dict['HOST'],
                                   port=int(settings_dict['PORT']),
                                   service_name=settings_dict['SERVICE_NAME'].strip())

    else:
        dsn = settings_dict['NAME']
    return "%s/%s@%s" % (settings_dict['USER'],
                         settings_dict['PASSWORD'], dsn)

然后将 NAME 更改为 SERVICE_NAME 变量,以连接到“default”:

 'default': {
            'ENGINE': 'django.db.backends.oracle',
            'SERVICE_NAME': 'myservice.bose.com',
            'USER': 'system',
            'PASSWORD': 'admin123',
            'HOST': '192.168.1.45',
            'PORT': '1699',
    }

稍后,我会将其作为拉取请求添加到Django源中。

0

这是可行的方法:

myservice =
 (DESCRIPTION = 
   (ADDRESS_LIST =
     (ADDRESS = (PROTOCOL = TCP)(HOST = 192.168.1.45)(PORT = 1699))
   )
 (CONNECT_DATA =
   (SERVICE_NAME = myservice_name)
 )
)

myservice = ''.join(trc_scan.split())

'default': {
  'ENGINE': 'django.db.backends.oracle',
  'NAME': myservice,
  'USER': 'someuser',
  'PASSWORD': 'somepsw',
}

请勿在用户名和/或密码中输入以下任何字符:

@/(


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接