将Java int [] []的mystery bytea表示转换回数组(Hibernate)

时间:2014-01-22 23:49:44

标签: java hibernate postgresql type-conversion bytearray

长话短说,我需要使用Java 6(或SQL)将bytea从PostgreSQL转换为int [] []

如果您想要上下文,请随时阅读以下内容,但这就是它的全部内容。

上下文

曾经在我所在团队工作的人写了这样一个bean:

public class FourPointBean {
  @Column(name = "points", length = "65535")
  private int[][] xy;
  ... other irrelevant stuff
}

int [] []真的只有四点(不知道为什么他选择了int [] [] ...)

(xy[0][0], xy[0][1]) := (x0, y0)
(xy[1][0], xy[1][1]) := (x1, y1)
(xy[2][0], xy[2][1]) := (x2, y2)
(xy[3][0], xy[3][1]) := (x3, y3)

显然多年来Hibernate一直抛出一个例外,每个人都忽略了它,因为它只是“警告” -

WARNING: duplicate class definition bug occured? Please report this :
com/company/blah/SomeBean$JaxbAccessorM_getXy_setXy_Array_Of_[I
java.lang.ClassFormatError: Illegal class name
"com/company/blah/SomeBean$JaxbAccessorM_getXy_setXy_Array_Of_[I" in class file
com/company/blah/SomeBean$JaxbAccessorM_getXy_setXy_Array_Of_[I
      at java.lang.ClassLoader.defineClass1(Native Method)
...

结果是Hibernate在表中将int数组作为byta插入。最终这个bean定义实际上引起了一些问题,所以我改变了它 -

public class FourPointBean {
  @Type(type = "com.company.blah.PointArrayType")
  @Column(name = "points", length = 65535, columnDefinition = "int[][]")
  private int[][] xy;
  ... other irrelevant stuff
}

然后我将UI使用的DTO更改为:

public class FourPointDTO {

  private List<Point> points = Lists.newArrayListWithCapacity(4);
  ...
}

哪个都很好,但是现在我必须写一些东西来将所讨论的表中所有凌乱的现有字节数组迁移到PostgreSQL int [] ...使用Java 6

我在网上看到的所有内容都涉及一些不能完全翻译的数组...现在我甚至无法弄清楚如何将int [] []转换为字节数组以进行测试。

与往常一样,非常感谢帮助...


修改

以下是几个翻译 -


00525bc5f039-2d70-40f0-922c-0ef7060816be

INT [] []

[0][0] := 538
[0][1] := 760
[1][0] := 676
[1][1] := 760
[2][0] := 676
[2][1] := 890
[3][0] := 538
[3][1] := 890

字节

 \xaced0005757200035b5b4917f7e44f198f893c020000787000000004757200025b494dba602676eab2a50200007870000000020000021a000002f87571007e000200000002000002a4000002f87571007e000200000002000002a40000037a7571007e0002000000020000021a0000037a

005276c1cb74-2476-43bf-856e-43912e969000

INT [] []

[0][0] := 544
[0][1] := 638
[1][0] := 657
[1][1] := 638
[2][0] := 657
[2][1] := 743
[3][0] := 544
[3][1] := 743

字节

 \xaced0005757200035b5b4917f7e44f198f893c020000787000000004757200025b494dba602676eab2a5020000787000000002000002200000027e7571007e000200000002000002910000027e7571007e00020000000200000291000002e77571007e00020000000200000220000002e7

1 个答案:

答案 0 :(得分:1)

简短版本:看起来Hibernate正在保存类型为int[][]的序列化Java对象。毛。使用ByteArrayInputStream中包含的ObjectInputStream在Java中对其进行反序列化。


要获取原始字节,请使用以下Python代码段:

points_hex = open("/path/to/points.txt").readlines()
points = [ p[2:-1].strip().decode("hex") for p in points_hex ]

的工作原理。我怀疑有一个共同的前缀,所以我查了一下。 Courtesty of this nice easy longest-common-prefix algo

from itertools import takewhile,izip

def allsame(x):
    return len(set(x)) == 1

r = [i[0] for i in takewhile(allsame ,izip(*points))]
''.join(r).encode("hex")

确认是:

\xaced0005757200035b5b4917f7e44f198f893c020000787000000004757200025b494dba602676eab2a50200007870000000020000

这样的前缀的存在强烈暗示我们正在处理序列化的Java对象,而不是数组中一系列点的bytea表示。在将一个点的原始二进制文件写入文件之后,很容易与file相关:

open("/tmp/point","w").write(points[0])

然后在shell中:

$ file /tmp/point 
/tmp/point: Java serialization data, version 5

您应该通过使用Java将它们反序列化为int[][]来解码这些点。 SQL中的int[][]这样的简单对象是可能的,但是当你可以让Java为你处理它时,没有必要手动执行它。

<强>更新

我感觉很好,所以这里有解码它的Java代码:

import java.io.*;
import java.util.Arrays;

public class Deserialize {

    // Credit: https://stackoverflow.com/a/140861/398670
    public static byte[] hexStringToByteArray(String s) {
        int len = s.length();
        byte[] data = new byte[len / 2];
        for (int i = 0; i < len; i += 2) {
            data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
                                 + Character.digit(s.charAt(i+1), 16));
        }
        return data;
    }


    public static void main( String[] args ) throws Exception {
        if (args.length != 1) {
            System.err.println("Usage: java Deserialize aced....hexstring...");
            System.exit(1);
        }

        String hex = args[0];
        if (hex.startsWith("\\x")) {
            hex = hex.substring(2);
        }

        ByteArrayInputStream bis = new ByteArrayInputStream(hexStringToByteArray(hex));
        ObjectInput in = new ObjectInputStream(bis);
        Object obj_read = in.readObject();

        if (obj_read instanceof int[][]) {
            int[][] obj_arr = (int[][]) obj_read;
            System.err.println("Array contents are: " + Arrays.deepToString(obj_arr) );
        }
    }

}

用法:

$ javac Deserialize.java 
$ java Deserialize '\xaced0005757200035b5b4917f7e44f198f893c020000787000000004757200025b494dba602676eab2a50200007870000000020000021a000002f87571007e000200000002000002a4000002f87571007e000200000002000002a40000037a7571007e0002000000020000021a0000037a'
Array contents are: [[538, 760], [676, 760], [676, 890], [538, 890]]

当然,实际上你将使用PgJDBC,它会直接向你发送byte[],所以你不需要做我上面做的十六进制解码。