Skip to content

Commit 35820c0

Browse files
authored
bugfix: Support serialization for dm.jdbc.driver.DmdbTimestamp (#6701)
1 parent 6753f35 commit 35820c0

File tree

5 files changed

+178
-0
lines changed

5 files changed

+178
-0
lines changed

changes/en-us/2.x.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ Add changes here for all PR submitted to the 2.x branch.
2020
- [[#6707](https://github.com/apache/incubator-seata/pull/6707)] fix readonly branch commit errors in Oracle XA transactions
2121
- [[#6711](https://github.com/apache/incubator-seata/pull/6711)] fix dameng rollback info un compress fail
2222
- [[#6714](https://github.com/apache/incubator-seata/pull/6714)] fix dameng delete undo fail
23+
- [[#6701](https://github.com/apache/incubator-seata/pull/6728)] fix support serialization for dm.jdbc.driver.DmdbTimestamp
2324

2425

2526
### optimize:
@@ -76,6 +77,7 @@ Thanks to these contributors for their code commits. Please report an unintended
7677
- [liuqiufeng](https://github.com/liuqiufeng)
7778
- [caohdgege](https://github.com/caohdgege)
7879
- [imashimaro](https://github.com/hmj776521114)
80+
- [lyl2008dsg](https://github.com/lyl2008dsg)
7981

8082

8183
Also, we receive many valuable issues, questions and advices from our community. Thanks for you all.

changes/zh-cn/2.x.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
- [[#6707](https://github.com/apache/incubator-seata/pull/6707)] 修复Oracle XA事务中只读分支提交出错的问题
2323
- [[#6711](https://github.com/apache/incubator-seata/pull/6711)] 修复达梦数据库的getRollbackInfo没有解压缩的问题
2424
- [[#6714](https://github.com/apache/incubator-seata/pull/6714)] 修复达梦数据库的delete sql回滚失败的问题
25+
- [[#6701](https://github.com/apache/incubator-seata/pull/6728)] 修复达梦数据库的对dm.jdbc.driver.DmdbTimestamp的支持
2526

2627
### optimize:
2728
- [[#6499](https://github.com/apache/incubator-seata/pull/6499)] 拆分 committing 和 rollbacking 状态的任务线程池
@@ -80,6 +81,8 @@
8081
- [liuqiufeng](https://github.com/liuqiufeng)
8182
- [caohdgege](https://github.com/caohdgege)
8283
- [imashimaro](https://github.com/hmj776521114)
84+
- [lyl2008dsg](https://github.com/lyl2008dsg)
85+
8386

8487

8588
同时,我们收到了社区反馈的很多有价值的issue和建议,非常感谢大家。

rm-datasource/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,5 +132,10 @@
132132
<artifactId>commons-logging</artifactId>
133133
<optional>true</optional>
134134
</dependency>
135+
<dependency>
136+
<groupId>com.dameng</groupId>
137+
<artifactId>DmJdbcDriver18</artifactId>
138+
<scope>test</scope>
139+
</dependency>
135140
</dependencies>
136141
</project>

rm-datasource/src/main/java/org/apache/seata/rm/datasource/undo/parser/JacksonUndoLogParser.java

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,13 @@
1818

1919
import java.io.IOException;
2020
import java.io.Reader;
21+
import java.lang.reflect.Method;
2122
import java.sql.SQLException;
2223
import java.sql.Timestamp;
2324
import java.time.Instant;
2425
import java.time.LocalDateTime;
2526
import java.time.ZoneId;
27+
import java.time.ZonedDateTime;
2628
import java.util.Arrays;
2729
import java.util.List;
2830
import java.util.Objects;
@@ -71,6 +73,10 @@ public class JacksonUndoLogParser implements UndoLogParser, Initialize {
7173

7274
private static final Logger LOGGER = LoggerFactory.getLogger(JacksonUndoLogParser.class);
7375

76+
private static final String DM_JDBC_DRIVER_DMDB_TIMESTAMP = "dm.jdbc.driver.DmdbTimestamp";
77+
78+
private static final String VALUE_OF = "valueOf";
79+
7480
/**
7581
* the zoneId for LocalDateTime
7682
*/
@@ -120,6 +126,16 @@ public class JacksonUndoLogParser implements UndoLogParser, Initialize {
120126
*/
121127
private final JsonDeserializer localDateTimeDeserializer = new LocalDateTimeDeserializer();
122128

129+
/**
130+
* customize serializer for dm.jdbc.driver.DmdbTimestamp
131+
*/
132+
private final JsonSerializer dmdbTimestampSerializer = new DmdbTimestampSerializer();
133+
134+
/**
135+
* customize deserializer for dm.jdbc.driver.DmdbTimestamp
136+
*/
137+
private final JsonDeserializer dmdbTimestampDeserializer = new DmdbTimestampDeserializer();
138+
123139
@Override
124140
public void init() {
125141
try {
@@ -152,12 +168,25 @@ public void init() {
152168
module.addDeserializer(SerialClob.class, clobDeserializer);
153169
module.addSerializer(LocalDateTime.class, localDateTimeSerializer);
154170
module.addDeserializer(LocalDateTime.class, localDateTimeDeserializer);
171+
registerDmdbTimestampModuleIfPresent();
155172
mapper.registerModule(module);
156173
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
157174
mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
158175
mapper.enable(MapperFeature.PROPAGATE_TRANSIENT_MARKER);
159176
}
160177

178+
private void registerDmdbTimestampModuleIfPresent() {
179+
try {
180+
Class<?> dmdbTimestampClass = Class.forName(DM_JDBC_DRIVER_DMDB_TIMESTAMP);
181+
module.addSerializer(dmdbTimestampClass, dmdbTimestampSerializer);
182+
module.addDeserializer(dmdbTimestampClass, dmdbTimestampDeserializer);
183+
} catch (ClassNotFoundException e) {
184+
// If the DmdbTimestamp class is not found, the serializers and deserializers will not be registered.
185+
// This is expected behavior since not all environments will have the dm.jdbc.driver.DmdbTimestamp class.
186+
// Therefore, no error log is recorded to avoid confusion for users without the dm driver.
187+
}
188+
}
189+
161190
@Override
162191
public String getName() {
163192
return NAME;
@@ -395,6 +424,98 @@ public LocalDateTime deserialize(JsonParser p, DeserializationContext ctxt) thro
395424
}
396425
}
397426

427+
private static class DmdbTimestampSerializer extends JsonSerializer<Object> {
428+
429+
private static final String TO_INSTANT = "toInstant";
430+
private static final String GET_NANOS = "getNanos";
431+
432+
@Override
433+
public void serializeWithType(Object dmdbTimestamp, JsonGenerator gen, SerializerProvider serializers, TypeSerializer typeSer) throws IOException {
434+
JsonToken valueShape = JsonToken.VALUE_NUMBER_INT;
435+
int nanos = getNanos(dmdbTimestamp);
436+
if (nanos % 1000000 > 0) {
437+
valueShape = JsonToken.START_ARRAY;
438+
}
439+
440+
WritableTypeId typeIdDef = typeSer.writeTypePrefix(gen, typeSer.typeId(dmdbTimestamp, valueShape));
441+
serialize(dmdbTimestamp, gen, serializers);
442+
typeSer.writeTypeSuffix(gen, typeIdDef);
443+
}
444+
445+
@Override
446+
public void serialize(Object dmdbTimestamp, JsonGenerator gen, SerializerProvider serializers) {
447+
try {
448+
Instant instant = getInstant(dmdbTimestamp);
449+
gen.writeNumber(instant.toEpochMilli());
450+
// if has microseconds, serialized as an array, write the nano to the array
451+
int nanos = instant.getNano();
452+
if (nanos % 1000000 > 0) {
453+
gen.writeNumber(nanos);
454+
}
455+
} catch (Exception e) {
456+
LOGGER.error("serialize dm.jdbc.driver.DmdbTimestamp error : {}", e.getMessage(), e);
457+
}
458+
}
459+
460+
private int getNanos(Object dmdbTimestamp) throws IOException {
461+
try {
462+
Method getNanosMethod = dmdbTimestamp.getClass().getMethod(GET_NANOS);
463+
return (int) getNanosMethod.invoke(dmdbTimestamp);
464+
} catch (Exception e) {
465+
throw new IOException("Error getting nanos value from DmdbTimestamp", e);
466+
}
467+
}
468+
469+
private Instant getInstant(Object dmdbTimestamp) throws IOException {
470+
try {
471+
Method toInstantMethod = dmdbTimestamp.getClass().getMethod(TO_INSTANT);
472+
return (Instant) toInstantMethod.invoke(dmdbTimestamp);
473+
} catch (Exception e) {
474+
throw new IOException("Error getting instant from DmdbTimestamp", e);
475+
}
476+
}
477+
}
478+
479+
public class DmdbTimestampDeserializer extends JsonDeserializer<Object> {
480+
481+
@Override
482+
public Object deserialize(JsonParser p, DeserializationContext ctxt) {
483+
try {
484+
Instant instant = parseInstant(p);
485+
return createDmdbTimestamp(instant);
486+
} catch (Exception e) {
487+
LOGGER.error("deserialize dm.jdbc.driver.DmdbTimestamp error : {}", e.getMessage(), e);
488+
}
489+
return null;
490+
}
491+
492+
private Instant parseInstant(JsonParser p) throws IOException {
493+
try {
494+
if (p.isExpectedStartArrayToken()) {
495+
ArrayNode arrayNode = p.getCodec().readTree(p);
496+
long timestamp = arrayNode.get(0).asLong();
497+
Instant instant = Instant.ofEpochMilli(timestamp);
498+
if (arrayNode.size() > 1) {
499+
int nano = arrayNode.get(1).asInt();
500+
instant = instant.plusNanos(nano % 1000000);
501+
}
502+
return instant;
503+
} else {
504+
long timestamp = p.getLongValue();
505+
return Instant.ofEpochMilli(timestamp);
506+
}
507+
} catch (IOException e) {
508+
throw new IOException("Error parsing Instant from JSON", e);
509+
}
510+
}
511+
512+
private Object createDmdbTimestamp(Instant instant) throws Exception {
513+
Class<?> dmdbTimestampClass = Class.forName(DM_JDBC_DRIVER_DMDB_TIMESTAMP);
514+
Method valueOfMethod = dmdbTimestampClass.getMethod(VALUE_OF, ZonedDateTime.class);
515+
return valueOfMethod.invoke(null, instant.atZone(zoneId));
516+
}
517+
}
518+
398519
/**
399520
* set zone id
400521
*

rm-datasource/src/test/java/org/apache/seata/rm/datasource/undo/parser/JacksonUndoLogParserTest.java

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,17 @@
1717
package org.apache.seata.rm.datasource.undo.parser;
1818

1919
import java.io.IOException;
20+
import java.lang.reflect.InvocationTargetException;
21+
import java.lang.reflect.Method;
2022
import java.math.BigDecimal;
2123
import java.sql.JDBCType;
2224
import java.sql.SQLException;
2325
import java.sql.Timestamp;
26+
import java.time.Instant;
2427
import java.time.LocalDateTime;
28+
import java.time.ZoneId;
29+
import java.time.ZonedDateTime;
30+
import java.util.Date;
2531
import javax.sql.rowset.serial.SerialBlob;
2632
import javax.sql.rowset.serial.SerialClob;
2733

@@ -32,6 +38,7 @@
3238
import org.apache.seata.rm.datasource.undo.BaseUndoLogParserTest;
3339
import org.apache.seata.rm.datasource.undo.UndoLogParser;
3440
import org.junit.jupiter.api.Assertions;
41+
import org.junit.jupiter.api.Assumptions;
3542
import org.junit.jupiter.api.Test;
3643

3744

@@ -99,6 +106,46 @@ public void encode() throws NoSuchFieldException, IllegalAccessException, IOExce
99106
bytes = mapper.writeValueAsBytes(field);
100107
sameField = mapper.readValue(bytes, Field.class);
101108
Assertions.assertTrue(DataCompareUtils.isFieldEquals(field, sameField).getResult());
109+
110+
}
111+
112+
@Test
113+
public void testSerializeAndDeserializeDmdbTimestamp() throws NoSuchFieldException, IllegalAccessException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IOException {
114+
java.lang.reflect.Field reflectField = parser.getClass().getDeclaredField("mapper");
115+
reflectField.setAccessible(true);
116+
ObjectMapper mapper = (ObjectMapper)reflectField.get(parser);
117+
118+
Class<?> dmdbTimestampClass = Class.forName("dm.jdbc.driver.DmdbTimestamp");
119+
Method valueOfMethod = dmdbTimestampClass.getMethod("valueOf", ZonedDateTime.class);
120+
Method valueOfDateMethod = dmdbTimestampClass.getMethod("valueOf", Date.class);
121+
122+
Object dmdbTimestamp = valueOfMethod.invoke(null, Instant.ofEpochMilli(1721985847000L).atZone(ZoneId.systemDefault()));
123+
Field field = new Field("dmdb_timestamp", JDBCType.TIMESTAMP.getVendorTypeNumber(), dmdbTimestamp);
124+
byte[] bytes = mapper.writeValueAsBytes(field);
125+
Field sameField = mapper.readValue(bytes, Field.class);
126+
Assertions.assertTrue(DataCompareUtils.isFieldEquals(field, sameField).getResult());
127+
128+
Object originalTimestamp = valueOfDateMethod.invoke(null, new Date(1721985847000L));
129+
field = new Field("dmdb_timestamp_type2", JDBCType.TIMESTAMP.getVendorTypeNumber(), originalTimestamp);
130+
bytes = mapper.writeValueAsBytes(field);
131+
sameField = mapper.readValue(bytes, Field.class);
132+
Assertions.assertFalse(DataCompareUtils.isFieldEquals(field, sameField).getResult());
133+
134+
Object dmdbTimestampnanos = valueOfMethod.invoke(null, Instant.ofEpochMilli(1721985847000L).plusNanos(12345L).atZone(ZoneId.systemDefault()));
135+
field = new Field("dmdb_timestamp_nanos", JDBCType.TIMESTAMP.getVendorTypeNumber(), dmdbTimestampnanos);
136+
bytes = mapper.writeValueAsBytes(field);
137+
sameField = mapper.readValue(bytes, Field.class);
138+
Assertions.assertTrue(DataCompareUtils.isFieldEquals(field, sameField).getResult());
139+
140+
}
141+
142+
private boolean checkClassExists(String className) {
143+
try {
144+
Class.forName(className);
145+
return true;
146+
} catch (ClassNotFoundException e) {
147+
return false;
148+
}
102149
}
103150

104151
@Override

0 commit comments

Comments
 (0)