在公司实习的时候,发现因为历史遗留问题,在新需求中insert db的时候插不进去数据,查看堆栈报错的时候发现是因为Oracle的varchar只能存储4000Bytes,显然是超出范围了,那么需要找到一种方法去解决这个问题。
这篇文章记录下我的思考,不一定是最优解。
1. 方案调研
要存储的字段超过了数据库字段的范围,有如下几种解决方案:
- 更改数据库字段的类型的存储范围 ——大字段容易造成性能问题
- 数据库字段类型不变,把数据库中存为索引,指向其他地方(如数据库,文件等)—— 会引起数据库结构的改变
- 在插入的时候拆分字段,之后插入到表中——拆分字段再插入的话,因为每一行都有唯一Id,在不改变表结构的情况下,很难判断哪两行是之前未拆分的字段;而且这样有违之前数据库设计时的语义
2. 方案确定
经过综合考量,为了不影响数据库的语义,所以新建了一个数据库,用来存储溢出的字段:
1 | CREATE TABLE EXT |
通过这个表,理论上在行数允许的情况下,容量就不再是问题(不考虑频繁查询性能的前提下)
那么下一个要解决的问题就是,如何尽可能小的侵入原有的代码:
在原始代码逻辑中,会有service.insert(Domain)
和service.query(Id)
两个逻辑,重点是如何无侵入地使这两个方案向往常一样使用。
在这里我使用了一个工具类,在insert之前会去判断字段是否过长,在query之后,会去判断是否使用了EXT的数据库。可以使用代理模式代理service类,从而增强insert和query的功能,也可以增加一个过滤器或者拦截器对insert和query进行拦截,然后处理
1 |
|
PS:如何确定MAX_VALUE
对于Oracle来说,varchar2的最大长度是4000bytes,我们要想确定每一列到底能存多少字符串,有两种方法:
通过Oracle的编码方式来确定
1
select userenv(‘language’) from dual
当查到具体的编码方式后,再进行对应字符串的切割,不过这种方式较为繁琐,而且不精确。
直接在Oracle中存入字节
1
2
3
4
5
6byte[] bytes = msg.getBody().getBytes();
for(int i = 0; i < times; i ++) {
byt[] subBytes = new byte[4000];
System.copy(subBytes, bytes, i, i + 4000);
extMapper.insert(create(subBytes));
}这个方法有一个弊端,就是需要我们在Ext这个JavaBean中把ext属性的类型改为byte[]