not serialized ValueJavaObject

上级 8ff160f7
...@@ -366,6 +366,32 @@ public class SysProperties { ...@@ -366,6 +366,32 @@ public class SysProperties {
public static final boolean USE_THREAD_CONTEXT_CLASS_LOADER = public static final boolean USE_THREAD_CONTEXT_CLASS_LOADER =
Utils.getProperty("h2.useThreadContextClassLoader", false); Utils.getProperty("h2.useThreadContextClassLoader", false);
/**
* System property <code>h2.serializeJavaObject</code>
* (default: true).<br />
* <b>If true</b>, values of type OTHER will be stored in serialized form
* and have the semantics of binary data for all operations (such as sorting and conversion to string).
* <br />
* <b>If false</b>, the objects will be serialized only for I/O operations and few other special cases
* (for example when someone tries to get the value in binary form or when comparing objects
* other way than in binary form is impossible).
* <br />
* If the object implements the Comparable interface, the method compareTo
* will be used for sorting (but only if objects being compared have a common comparable
* super type). Otherwise the objects will be compared by types, and if they are the same by hashCode, and
* if the hash codes are equal, but objects are not, the serialized forms (the byte arrays) are compared.
* <br />
* The string representation of the values use the toString method of object.
* <br />
* In client-server mode, the server must have all required classes in the class path.
* On the client side, this setting is required to be disabled as well, to have correct string representation
* and display size.
* <br />
* In embedded mode, no data copying occurs, so the user has to make defensive copy himself before storing,
* to make sure that value object is immutable and will not be modified later.
*/
public static final boolean SERIALIZE_JAVA_OBJECT = Utils.getProperty("h2.serializeJavaObject", true);
private static final String H2_BASE_DIR = "h2.baseDir"; private static final String H2_BASE_DIR = "h2.baseDir";
private SysProperties() { private SysProperties() {
......
...@@ -1756,8 +1756,10 @@ public class JdbcConnection extends TraceObject implements Connection { ...@@ -1756,8 +1756,10 @@ public class JdbcConnection extends TraceObject implements Connection {
break; break;
} }
case Value.JAVA_OBJECT: case Value.JAVA_OBJECT:
if (SysProperties.SERIALIZE_JAVA_OBJECT) {
o = Utils.deserialize(v.getBytesNoCopy()); o = Utils.deserialize(v.getBytesNoCopy());
break; break;
}
default: default:
o = v.getObject(); o = v.getObject();
} }
......
...@@ -759,7 +759,7 @@ public class Data { ...@@ -759,7 +759,7 @@ public class Data {
int len = readVarInt(); int len = readVarInt();
byte[] b = Utils.newBytes(len); byte[] b = Utils.newBytes(len);
read(b, 0, len); read(b, 0, len);
return ValueJavaObject.getNoCopy(b); return ValueJavaObject.getNoCopy(null, b);
} }
case Value.UUID: case Value.UUID:
return ValueUuid.get(readLong(), readLong()); return ValueUuid.get(readLong(), readLong());
......
...@@ -237,7 +237,7 @@ public class Utils { ...@@ -237,7 +237,7 @@ public class Utils {
} }
int len = b.length; int len = b.length;
if (len == 0) { if (len == 0) {
return b; return EMPTY_BYTES;
} }
byte[] copy = new byte[len]; byte[] copy = new byte[len];
System.arraycopy(b, 0, copy, 0, len); System.arraycopy(b, 0, copy, 0, len);
...@@ -403,6 +403,28 @@ public class Utils { ...@@ -403,6 +403,28 @@ public class Utils {
return new long[len]; return new long[len];
} }
/**
* Checks if given classes have common Comparable superclass.
*/
public static boolean haveCommonComparableSuperclass(Class<?> c1, Class<?> c2) {
if (c1 == c2 || c1.isAssignableFrom(c2) || c2.isAssignableFrom(c1)) {
return true;
}
Class<?> top1;
do {
top1 = c1;
c1 = c1.getSuperclass();
} while (Comparable.class.isAssignableFrom(c1));
Class<?> top2;
do {
top2 = c2;
c2 = c2.getSuperclass();
} while (Comparable.class.isAssignableFrom(c2));
return top1 == top2;
}
/** /**
* Load a class, but check if it is allowed to load this class first. To * Load a class, but check if it is allowed to load this class first. To
* perform access rights checking, the system property h2.allowedClasses * perform access rights checking, the system property h2.allowedClasses
......
...@@ -23,6 +23,7 @@ import java.util.ArrayList; ...@@ -23,6 +23,7 @@ import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.UUID; import java.util.UUID;
import org.h2.constant.ErrorCode; import org.h2.constant.ErrorCode;
import org.h2.constant.SysProperties;
import org.h2.engine.SessionInterface; import org.h2.engine.SessionInterface;
import org.h2.jdbc.JdbcBlob; import org.h2.jdbc.JdbcBlob;
import org.h2.jdbc.JdbcClob; import org.h2.jdbc.JdbcClob;
...@@ -577,8 +578,13 @@ public class DataType { ...@@ -577,8 +578,13 @@ public class DataType {
break; break;
} }
case Value.JAVA_OBJECT: { case Value.JAVA_OBJECT: {
if (SysProperties.SERIALIZE_JAVA_OBJECT) {
byte[] buff = rs.getBytes(columnIndex); byte[] buff = rs.getBytes(columnIndex);
v = buff == null ? (Value) ValueNull.INSTANCE : ValueJavaObject.getNoCopy(buff); v = buff == null ? ValueNull.INSTANCE : ValueJavaObject.getNoCopy(null, buff);
} else {
Object o = rs.getObject(columnIndex);
v = o == null ? ValueNull.INSTANCE : ValueJavaObject.getNoCopy(o, null);
}
break; break;
} }
case Value.ARRAY: { case Value.ARRAY: {
...@@ -861,7 +867,7 @@ public class DataType { ...@@ -861,7 +867,7 @@ public class DataType {
return ValueNull.INSTANCE; return ValueNull.INSTANCE;
} }
if (type == Value.JAVA_OBJECT) { if (type == Value.JAVA_OBJECT) {
return ValueJavaObject.getNoCopy(Utils.serialize(x)); return ValueJavaObject.getNoCopy(x, null);
} }
if (x instanceof String) { if (x instanceof String) {
return ValueString.get((String) x); return ValueString.get((String) x);
...@@ -932,7 +938,7 @@ public class DataType { ...@@ -932,7 +938,7 @@ public class DataType {
} else if (x instanceof Character) { } else if (x instanceof Character) {
return ValueStringFixed.get(((Character) x).toString()); return ValueStringFixed.get(((Character) x).toString());
} else { } else {
return ValueJavaObject.getNoCopy(Utils.serialize(x)); return ValueJavaObject.getNoCopy(x, null);
} }
} }
...@@ -1059,7 +1065,7 @@ public class DataType { ...@@ -1059,7 +1065,7 @@ public class DataType {
return new JdbcClob(conn, v, 0); return new JdbcClob(conn, v, 0);
} }
if (v.getType() == Value.JAVA_OBJECT) { if (v.getType() == Value.JAVA_OBJECT) {
Object o = Utils.deserialize(v.getBytes()); Object o = SysProperties.SERIALIZE_JAVA_OBJECT ? Utils.deserialize(v.getBytes()) : v.getObject();
if (paramClass.isAssignableFrom(o.getClass())) { if (paramClass.isAssignableFrom(o.getClass())) {
return o; return o;
} }
......
...@@ -527,7 +527,7 @@ public class Transfer { ...@@ -527,7 +527,7 @@ public class Transfer {
case Value.UUID: case Value.UUID:
return ValueUuid.get(readLong(), readLong()); return ValueUuid.get(readLong(), readLong());
case Value.JAVA_OBJECT: case Value.JAVA_OBJECT:
return ValueJavaObject.getNoCopy(readBytes()); return ValueJavaObject.getNoCopy(null, readBytes());
case Value.BOOLEAN: case Value.BOOLEAN:
return ValueBoolean.get(readBoolean()); return ValueBoolean.get(readBoolean());
case Value.BYTE: case Value.BYTE:
......
...@@ -761,7 +761,7 @@ public abstract class Value { ...@@ -761,7 +761,7 @@ public abstract class Value {
switch(getType()) { switch(getType()) {
case BYTES: case BYTES:
case BLOB: case BLOB:
return ValueJavaObject.getNoCopy(getBytesNoCopy()); return ValueJavaObject.getNoCopy(null, getBytesNoCopy());
} }
break; break;
} }
...@@ -820,7 +820,7 @@ public abstract class Value { ...@@ -820,7 +820,7 @@ public abstract class Value {
case BYTES: case BYTES:
return ValueBytes.getNoCopy(StringUtils.convertHexToBytes(s.trim())); return ValueBytes.getNoCopy(StringUtils.convertHexToBytes(s.trim()));
case JAVA_OBJECT: case JAVA_OBJECT:
return ValueJavaObject.getNoCopy(StringUtils.convertHexToBytes(s.trim())); return ValueJavaObject.getNoCopy(null, StringUtils.convertHexToBytes(s.trim()));
case STRING: case STRING:
return ValueString.get(s); return ValueString.get(s);
case STRING_IGNORECASE: case STRING_IGNORECASE:
......
...@@ -21,8 +21,8 @@ public class ValueBytes extends Value { ...@@ -21,8 +21,8 @@ public class ValueBytes extends Value {
private static final ValueBytes EMPTY = new ValueBytes(Utils.EMPTY_BYTES); private static final ValueBytes EMPTY = new ValueBytes(Utils.EMPTY_BYTES);
private final byte[] value; protected byte[] value;
private int hash; protected int hash;
protected ValueBytes(byte[] v) { protected ValueBytes(byte[] v) {
this.value = v; this.value = v;
...@@ -66,7 +66,7 @@ public class ValueBytes extends Value { ...@@ -66,7 +66,7 @@ public class ValueBytes extends Value {
} }
public String getSQL() { public String getSQL() {
return "X'" + getString() + "'"; return "X'" + StringUtils.convertBytesToHex(getBytesNoCopy()) + "'";
} }
public byte[] getBytesNoCopy() { public byte[] getBytesNoCopy() {
...@@ -74,7 +74,7 @@ public class ValueBytes extends Value { ...@@ -74,7 +74,7 @@ public class ValueBytes extends Value {
} }
public byte[] getBytes() { public byte[] getBytes() {
return Utils.cloneByteArray(value); return Utils.cloneByteArray(getBytesNoCopy());
} }
protected int compareSecure(Value v, CompareMode mode) { protected int compareSecure(Value v, CompareMode mode) {
......
...@@ -28,15 +28,24 @@ public class ValueJavaObject extends ValueBytes { ...@@ -28,15 +28,24 @@ public class ValueJavaObject extends ValueBytes {
* Get or create a java object value for the given byte array. * Get or create a java object value for the given byte array.
* Do not clone the data. * Do not clone the data.
* *
* @param javaObject the object
* @param b the byte array * @param b the byte array
* @return the value * @return the value
*/ */
public static ValueJavaObject getNoCopy(byte[] b) { public static ValueJavaObject getNoCopy(Object javaObject, byte[] b) {
if (b.length == 0) { if (b != null && b.length == 0) {
return EMPTY; return EMPTY;
} }
ValueJavaObject obj = new ValueJavaObject(b); ValueJavaObject obj;
if (b.length > SysProperties.OBJECT_CACHE_MAX_PER_ELEMENT_SIZE) { if (SysProperties.SERIALIZE_JAVA_OBJECT) {
if (b == null) {
b = Utils.serialize(javaObject);
}
obj = new ValueJavaObject(b);
} else {
obj = new NotSerialized(javaObject, b);
}
if (b == null || b.length > SysProperties.OBJECT_CACHE_MAX_PER_ELEMENT_SIZE) {
return obj; return obj;
} }
return (ValueJavaObject) Value.cache(obj); return (ValueJavaObject) Value.cache(obj);
...@@ -51,4 +60,133 @@ public class ValueJavaObject extends ValueBytes { ...@@ -51,4 +60,133 @@ public class ValueJavaObject extends ValueBytes {
prep.setObject(parameterIndex, obj, Types.JAVA_OBJECT); prep.setObject(parameterIndex, obj, Types.JAVA_OBJECT);
} }
/**
* Value which serializes java object only for I/O operations.
* Used when property {@link SysProperties#SERIALIZE_JAVA_OBJECT} is disabled.
*
* @author Sergi Vladykin
*/
private static class NotSerialized extends ValueJavaObject {
private Object javaObject;
private int displaySize = -1;
NotSerialized(Object javaObject, byte[] v) {
super(v);
this.javaObject = javaObject;
}
@Override
public void set(PreparedStatement prep, int parameterIndex) throws SQLException {
prep.setObject(parameterIndex, getObject(), Types.JAVA_OBJECT);
}
@Override
public byte[] getBytesNoCopy() {
if (value == null) {
value = Utils.serialize(javaObject);
}
return value;
}
@Override
protected int compareSecure(Value v, CompareMode mode) {
Object o1 = getObject();
Object o2 = v.getObject();
boolean o1Comparable = o1 instanceof Comparable;
boolean o2Comparable = o2 instanceof Comparable;
if (o1Comparable && o2Comparable &&
Utils.haveCommonComparableSuperclass(o1.getClass(), o2.getClass())) {
return ((Comparable) o1).compareTo(o2);
}
// group by types
if (o1.getClass() != o2.getClass()) {
if (o1Comparable != o2Comparable) {
return o1Comparable ? -1 : 1;
}
return o1.getClass().getName().compareTo(o2.getClass().getName());
}
// compare hash codes
int h1 = hashCode();
int h2 = v.hashCode();
if (h1 == h2) {
if (o1.equals(o2)) {
return 0;
}
return Utils.compareNotNull(getBytesNoCopy(), v.getBytesNoCopy());
}
return h1 > h2 ? 1 : -1;
}
@Override
public String getString() {
String str = getObject().toString();
if (displaySize == -1) {
displaySize = str.length();
}
return str;
}
@Override
public long getPrecision() {
return 0;
}
@Override
public int hashCode() {
if (hash == 0) {
hash = getObject().hashCode();
}
return hash;
}
@Override
public Object getObject() {
if (javaObject == null) {
javaObject = Utils.deserialize(value);
}
return javaObject;
}
@Override
public int getDisplaySize() {
if (displaySize == -1) {
displaySize = getString().length();
}
return displaySize;
}
@Override
public int getMemory() {
if (value == null) {
return DataType.getDataType(getType()).memory;
}
int mem = super.getMemory();
if (javaObject != null) {
mem *= 2;
}
return mem;
}
@Override
public boolean equals(Object other) {
if (!(other instanceof NotSerialized)) {
return false;
}
return getObject().equals(((NotSerialized) other).getObject());
}
@Override
public Value convertPrecision(long precision, boolean force) {
return this;
}
}
} }
/*
* Copyright 2004-2011 H2 Group. Multiple-Licensed under the H2 License,
* Version 1.0, and under the Eclipse Public License, Version 1.0
* (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.test.jdbc;
import java.sql.Connection;
import java.sql.Date;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Time;
import java.sql.Timestamp;
import java.sql.Types;
import java.util.Arrays;
import java.util.UUID;
import org.h2.test.TestAll;
import org.h2.test.TestBase;
/**
* Tests java object values when {@link SysProperties#SERIALIZE_JAVA_OBJECT} property is disabled.
*
* @author Sergi Vladykin
*/
public class TestJavaObject extends TestBase {
/**
* Run just this test.
*
* @param a ignored
*/
public static void main(String... a) throws Exception {
System.setProperty("h2.serializeJavaObject", "false");
TestAll conf = new TestAll();
conf.traceTest = true;
conf.memory = true;
// conf.networked = true;
TestBase.createCaller().init(conf).test();
}
@Override
public void test() throws Exception {
trace("Test Java Object");
startServerIfRequired();
doTest(Arrays.asList(UUID.randomUUID(), null), Arrays.asList(UUID.randomUUID(), UUID.randomUUID()), true);
doTest(new Timestamp(System.currentTimeMillis()), new Timestamp(System.currentTimeMillis() + 10000), false);
doTest(200, 100, false);
doTest(200, 100L, true);
doTest(new Date(System.currentTimeMillis() + 1000), new Date(System.currentTimeMillis()), false);
doTest(new java.util.Date(System.currentTimeMillis() + 1000), new java.util.Date(System.currentTimeMillis()), false);
doTest(new Time(System.currentTimeMillis() + 1000), new Date(System.currentTimeMillis()), false);
doTest(new Time(System.currentTimeMillis() + 1000), new Timestamp(System.currentTimeMillis()), false);
}
private void doTest(Object o1, Object o2, boolean hash) throws SQLException {
deleteDb("javaObject");
Connection conn = getConnection("javaObject");
Statement stmt = conn.createStatement();
stmt.execute("create table t(id identity, val other)");
PreparedStatement ins = conn.prepareStatement("insert into t(val) values(?)");
ins.setObject(1, o1, Types.JAVA_OBJECT);
assertEquals(1, ins.executeUpdate());
ins.setObject(1, o2, Types.JAVA_OBJECT);
assertEquals(1, ins.executeUpdate());
ResultSet rs = stmt.executeQuery("select val from t order by val limit 1");
assertTrue(rs.next());
Object x;
if (hash) {
if (o1.getClass() != o2.getClass()) {
x = o1.getClass().getName().compareTo(o2.getClass().getName()) < 0 ? o1 : o2;
} else {
assertFalse(o1.hashCode() == o2.hashCode());
x = o1.hashCode() < o2.hashCode() ? o1 : o2;
}
} else {
int cmp = ((Comparable) o1).compareTo(o2);
assertFalse(cmp == 0);
x = cmp < 0 ? o1 : o2;
}
assertEquals(x.toString(), rs.getString(1));
Object y = rs.getObject(1);
assertTrue(x.equals(y));
assertFalse(rs.next());
rs.close();
PreparedStatement slct = conn.prepareStatement("select id from t where val = ?");
slct.setObject(1, o1, Types.JAVA_OBJECT);
rs = slct.executeQuery();
assertTrue(rs.next());
assertEquals(1, rs.getInt(1));
assertFalse(rs.next());
rs.close();
slct.setObject(1, o2, Types.JAVA_OBJECT);
rs = slct.executeQuery();
assertTrue(rs.next());
assertEquals(2, rs.getInt(1));
assertFalse(rs.next());
rs.close();
stmt.close();
slct.close();
conn.close();
deleteDb("javaObject");
trace("ok: " + o1.getClass().getName() + " vs " + o2.getClass().getName());
}
}
...@@ -166,8 +166,8 @@ public class TestDataPage extends TestBase implements DataHandler { ...@@ -166,8 +166,8 @@ public class TestDataPage extends TestBase implements DataHandler {
testValue(ValueTime.get(new Time(0))); testValue(ValueTime.get(new Time(0)));
testValue(ValueTimestamp.get(new Timestamp(System.currentTimeMillis()))); testValue(ValueTimestamp.get(new Timestamp(System.currentTimeMillis())));
testValue(ValueTimestamp.get(new Timestamp(0))); testValue(ValueTimestamp.get(new Timestamp(0)));
testValue(ValueJavaObject.getNoCopy(new byte[0])); testValue(ValueJavaObject.getNoCopy(null, new byte[0]));
testValue(ValueJavaObject.getNoCopy(new byte[100])); testValue(ValueJavaObject.getNoCopy(null, new byte[100]));
for (int i = 0; i < 300; i++) { for (int i = 0; i < 300; i++) {
testValue(ValueBytes.getNoCopy(new byte[i])); testValue(ValueBytes.getNoCopy(new byte[i]));
} }
......
...@@ -8,6 +8,9 @@ package org.h2.test.unit; ...@@ -8,6 +8,9 @@ package org.h2.test.unit;
import java.io.File; import java.io.File;
import java.math.BigInteger; import java.math.BigInteger;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Date;
import java.util.Random; import java.util.Random;
import org.h2.test.TestBase; import org.h2.test.TestBase;
import org.h2.util.Utils; import org.h2.util.Utils;
...@@ -101,6 +104,12 @@ public class TestUtils extends TestBase { ...@@ -101,6 +104,12 @@ public class TestUtils extends TestBase {
Utils.callStaticMethod("java.lang.String.valueOf", "a"); Utils.callStaticMethod("java.lang.String.valueOf", "a");
Utils.callStaticMethod("java.awt.AWTKeyStroke.getAWTKeyStroke", Utils.callStaticMethod("java.awt.AWTKeyStroke.getAWTKeyStroke",
'x', java.awt.event.InputEvent.SHIFT_DOWN_MASK); 'x', java.awt.event.InputEvent.SHIFT_DOWN_MASK);
// Common comparable superclass
assertFalse(Utils.haveCommonComparableSuperclass(Integer.class, Long.class));
assertTrue(Utils.haveCommonComparableSuperclass(Integer.class, Integer.class));
assertTrue(Utils.haveCommonComparableSuperclass(Timestamp.class, Date.class));
assertFalse(Utils.haveCommonComparableSuperclass(ArrayList.class, Long.class));
assertFalse(Utils.haveCommonComparableSuperclass(Integer.class, ArrayList.class));
} }
} }
...@@ -180,7 +180,7 @@ public class TestValueMemory extends TestBase implements DataHandler { ...@@ -180,7 +180,7 @@ public class TestValueMemory extends TestBase implements DataHandler {
case Value.RESULT_SET: case Value.RESULT_SET:
return ValueResultSet.get(new SimpleResultSet()); return ValueResultSet.get(new SimpleResultSet());
case Value.JAVA_OBJECT: case Value.JAVA_OBJECT:
return ValueJavaObject.getNoCopy(randomBytes(random.nextInt(100))); return ValueJavaObject.getNoCopy(null, randomBytes(random.nextInt(100)));
case Value.UUID: case Value.UUID:
return ValueUuid.get(random.nextLong(), random.nextLong()); return ValueUuid.get(random.nextLong(), random.nextLong());
case Value.STRING_FIXED: case Value.STRING_FIXED:
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论