Django Oracle后端 --- cx_Oracle字符集

本文探讨了Django使用Oracle11.2作为数据库时遇到的字符集问题,尤其是cx_Oracle与Oracle服务端字符集不匹配导致的中文乱码。通过一系列实验分析,发现问题是字符集转换错误。解决方案包括自定义数据库后端,特别是针对nvarchar2类型的处理,以确保Unicode字符的正确存储和读取。
摘要由CSDN通过智能技术生成

背景

使用Django3.2.5作为后端开发框架,数据库为Oracle11.2。据Django官方文档说明,要求Oracle Database的最低版本为12c。阅读过Django中ORM(对象关系模型)的实现,ORM中常用的自增id、sql查询分页等数据库特性在Oracle11.2均没有实现,从Oracle12c版本开始才有这些特性,这也是Django框架要求Oracle版本最低为12c的主要原因。但是项目的数据库Oracle11.2已不可能更换。这也为后面各种业务的开发在某些具体方面带来了技术问题。本文涉及的cx_Oracle字符集问题就是其中之一。此外,后续我将就此背景分享一些其他问题及解决方案,因为期间遇到的问题在网上很少有涉及,自己花费了不少精力去解决。

问题

Django页面在保存中文字符时出现乱码。

分析

知识背景

根据以往的经验,中文字符保存后出现乱码基本上是字符集转换出错引起。

Django后端对不同的数据库客户端进行了一次封装,以保证上层ORM调用数据库访问接口时的统一。Oracle的python客户端为cx_Oracle,其本质就是将Oracle客户端库进行python封装。这里为了简化Django里的问题排查过程,我将直接使用cx_Oracle访问Oracle11.2,并使用一个单独的表“test”用于测试。

SQL> create table test(id number(9), txt nvarchar2(128), txt2 varchar2(128));

这里创建varchar2和nvarchar2的原因是Oracle数据库中有数据库字符集和国家字符集的概念,这两种字符集用途不一样,受服务端和客户端字符集的影响,这个我稍后具体介绍。

这里额外啰嗦下字符集及python下的字节串和字符串。

字符集可以这么理解,就是将某些字符汇总在一起,然后给它们编个号,这个号就是码位(code point),一个码位与一个字符对应,计算机在处理字符时,实际就是处理这些码位值。比如美国使用的英文字母及符号汇总在一起,然后编号,最后为了能让计算机方便存储,用一个字节(8个比特位)的空间按码位存储,这里的编号就是为字符分配码位,用一个字节存储就是对码位进行编码,只不过刚好码位跟编码的结果一样,这就是我们熟知的ASCII码(美国标准信息交换码)。再比如将中国使用的汉字及符号汇总起来,编号,这就是中国国标字符集,最后为了方便计算机存储和运算,使用两个字节的空间按码位直接存储,这就是我们熟知的GBK编码。很多国家按照这个思路提供了自己的字符集标准。

互联网的崛起,为了方便各个地区的人访问,需要统一字符集,于是就有了Unicode字符集。Uicode字符集囊括世界几乎所有国家的字符,而且每年还有增加,显然用一个字节的空间存储是不行的,通常情况下使用两个字节就能够涵盖大部分各个国家常用的字符。两个字节对于字母地区的人来说,显然又比较浪费存储空间,于是就有了大家熟知utf-8编码方案,该方案用一个字符存储ASCII字符集的字符,三个字节存储汉字等字符集。

python(这里指python3)的默认编码方案为utf-8,即python代码从磁盘文件被加载值虚拟机时,按utf-8编码进行解码。比如磁盘存储的字符串“你”,字节序列为0xe4 0xbd 0xa0,被加载至虚拟机内存时,字节序列为0x4f 0x60,实际上0x4f 0x60就是“你”的Unicode码位值,也就是说python中的字符串实际是Unicode的码位串。我们对这个码位进行utf-8编码后,生成一个字节串,也就是utf-8编码后的字节序列,这个字节序列通常用于存储和传输,这也是utf(Unicode格式传输转换编码)名称的由来。

>>> a = '你'
>>> a
'你'
>>> ord(a)
20320
>>> hex(ord(a))
'0x4f60'
>>> b = a.encode(encoding='utf-8')
>>> b
b'\xe4\xbd\xa0'

实验一

言归正传,我们现在直接往test表插入数据,并查询数据,看看能得到什么结果。

import cx_Oracle
import os


def str_to_hex(s):
    if s is None:
        return '<null>'
    return r' '.join([hex(ord(c)) for c in s])


db = cx_Oracle.connect(dsn='orcl', user='xx', password='xx')
cursor = db.cursor()

cursor.execute("update test set txt=:param, txt2=:param2", param='你', param2='你')
db.commit()


rows = cursor.execute('select txt,txt2 from test')

for row in rows:
    print(type(row[0]), type(row[1]))
    print('txt=', row[0], 'txt2=', row[1].decode(encoding='utf-8') if isinstance(row[1], bytes) else row[1])
    print('txt=', str_to_hex(row[0]), 'txt2=', row[1] if isinstance(row[1], bytes) else str_to_hex(row[1]))

db.close()

执行上述python代码后,从表test获取到的值在

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值