├── .gitignore
├── .github
└── workflows
│ └── release.yml
├── README.md
├── pom.xml
└── src
└── main
└── java
└── com
└── darcytech
└── debezium
└── converter
└── MySqlDateTimeConverter.java
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .idea/
3 | .vscode/
4 | .settings/
5 | target/
6 | .classpath
7 | .factorypath
8 | .project
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | # This workflow will build a package using Maven and then publish it to GitHub packages when a release is created
2 | # For more information see: https://github.com/actions/setup-java/blob/main/docs/advanced-usage.md#apache-maven-with-a-settings-path
3 |
4 | name: Maven Package
5 |
6 | on:
7 | workflow_dispatch:
8 | inputs:
9 | version:
10 | description: "Release version"
11 | required: true
12 | default: "1.4.0.Final"
13 |
14 | jobs:
15 | release:
16 |
17 | runs-on: ubuntu-latest
18 |
19 | steps:
20 | - uses: actions/checkout@v2
21 | - name: Set up JDK 11
22 | uses: actions/setup-java@v2
23 | with:
24 | java-version: '11'
25 | distribution: 'adopt'
26 | server-id: github # Value of the distributionManagement/repository/id field of the pom.xml
27 | settings-path: ${{ github.workspace }} # location for the settings.xml file
28 |
29 | - name: Build with Maven
30 | run: mvn -B package --file pom.xml
31 |
32 | - uses: "marvinpinto/action-automatic-releases@latest"
33 | with:
34 | repo_token: "${{ secrets.GITHUB_TOKEN }}"
35 | automatic_release_tag: "v${{ github.event.inputs.version }}"
36 | prerelease: false
37 | files: |
38 | LICENSE.txt
39 | **/target/debezium-extension*.jar
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | **Note: debezium has been upgraded and the repository is no longer maintained. Please see the [latest official documentation](https://debezium.io/releases/)**
2 |
3 | [](https://debezium.io/documentation/reference/1.4/connectors/mysql.html)
4 |
5 | [](https://github.com/holmofy/debezium-datetime-converter/releases)
6 |
7 | # debezium-datetime-converter
8 |
9 | Debezium [custom converter](https://debezium.io/documentation/reference/development/converters.html) is used to deal with mysql [datetime type problems](https://debezium.io/documentation/reference/1.5/connectors/mysql.html#mysql-temporal-types).
10 |
11 | | mysql | binlog-connector | debezium | schema |
12 | | ----------------------------------- | ---------------------------------------- | --------------------------------- | ----------------------------------- |
13 | | date
(2021-01-28) | LocalDate
(2021-01-28) | Integer
(18655) | io.debezium.time.Date |
14 | | time
(17:29:04) | Duration
(PT17H29M4S) | Long
(62944000000) | io.debezium.time.Time |
15 | | timestamp
(2021-01-28 17:29:04) | ZonedDateTime
(2021-01-28T09:29:04Z) | String
(2021-01-28T09:29:04Z) | io.debezium.time.ZonedTimestamp |
16 | | Datetime
(2021-01-28 17:29:04) | LocalDateTime
(2021-01-28T17:29:04) | Long
(1611854944000) | io.debezium.time.Timestamp |
17 |
18 | > For details, please refer to [this article](https://blog.hufeifei.cn/2021/03/DB/mysql-binlog-parser/)
19 |
20 | # Usage
21 |
22 | 1. [Download](https://github.com/holmofy/debezium-datetime-converter/releases) the extended jar package and put it in the same level directory of the debezium plugin.
23 |
24 | 2. In debezium-connector, Add the following configuration:
25 |
26 | ```properties
27 | connector.class=io.debezium.connector.mysql.MySqlConnector
28 | # ...
29 | converters=datetime
30 | datetime.type=com.darcytech.debezium.converter.MySqlDateTimeConverter
31 | datetime.format.date=yyyy-MM-dd
32 | datetime.format.time=HH:mm:ss
33 | datetime.format.datetime=yyyy-MM-dd HH:mm:ss
34 | datetime.format.timestamp=yyyy-MM-dd HH:mm:ss
35 | datetime.format.timestamp.zone=UTC+8
36 | ```
37 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 |
7 |
8 |
9 |
10 |
11 | com.darcytech.debezium
12 | debezium-extension
13 | 1.4.0.Final
14 | jar
15 |
16 |
17 | 1.4.0.Final
18 | 2.6.0
19 |
20 |
21 |
22 | io.debezium
23 | debezium-api
24 | ${version.debezium}
25 | provided
26 |
27 |
28 | io.debezium
29 | debezium-connector-mysql
30 | ${version.debezium}
31 | provided
32 |
33 |
34 | org.apache.kafka
35 | connect-api
36 | ${version.kafka}
37 | provided
38 |
39 |
40 | org.projectlombok
41 | lombok
42 | 1.18.16
43 | provided
44 |
45 |
46 |
47 |
48 |
49 |
50 | org.apache.maven.plugins
51 | maven-compiler-plugin
52 | 3.8.1
53 |
54 | 1.8
55 | 1.8
56 |
57 |
58 | org.projectlombok
59 | lombok
60 | 1.18.16
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/src/main/java/com/darcytech/debezium/converter/MySqlDateTimeConverter.java:
--------------------------------------------------------------------------------
1 | package com.darcytech.debezium.converter;
2 |
3 | import io.debezium.spi.converter.CustomConverter;
4 | import io.debezium.spi.converter.RelationalColumn;
5 | import lombok.extern.slf4j.Slf4j;
6 | import org.apache.kafka.connect.data.SchemaBuilder;
7 |
8 | import java.time.*;
9 | import java.time.format.DateTimeFormatter;
10 | import java.util.Properties;
11 | import java.util.function.Consumer;
12 |
13 | /**
14 | * 处理Debezium时间转换的问题
15 | * Debezium默认将MySQL中datetime类型转成UTC的时间戳({@link io.debezium.time.Timestamp}),时区是写死的没法儿改,
16 | * 导致数据库中设置的UTC+8,到kafka中变成了多八个小时的long型时间戳
17 | * Debezium默认将MySQL中的timestamp类型转成UTC的字符串。
18 | * | mysql | mysql-binlog-connector | debezium |
19 | * | ----------------------------------- | ---------------------------------------- | --------------------------------- |
20 | * | date
(2021-01-28) | LocalDate
(2021-01-28) | Integer
(18655) |
21 | * | time
(17:29:04) | Duration
(PT17H29M4S) | Long
(62944000000) |
22 | * | timestamp
(2021-01-28 17:29:04) | ZonedDateTime
(2021-01-28T09:29:04Z) | String
(2021-01-28T09:29:04Z) |
23 | * | Datetime
(2021-01-28 17:29:04) | LocalDateTime
(2021-01-28T17:29:04) | Long
(1611854944000) |
24 | *
25 | * @see io.debezium.connector.mysql.converters.TinyIntOneToBooleanConverter
26 | */
27 | @Slf4j
28 | public class MySqlDateTimeConverter implements CustomConverter {
29 |
30 | private DateTimeFormatter dateFormatter = DateTimeFormatter.ISO_DATE;
31 | private DateTimeFormatter timeFormatter = DateTimeFormatter.ISO_TIME;
32 | private DateTimeFormatter datetimeFormatter = DateTimeFormatter.ISO_DATE_TIME;
33 | private DateTimeFormatter timestampFormatter = DateTimeFormatter.ISO_DATE_TIME;
34 |
35 | private ZoneId timestampZoneId = ZoneId.systemDefault();
36 |
37 | @Override
38 | public void configure(Properties props) {
39 | readProps(props, "format.date", p -> dateFormatter = DateTimeFormatter.ofPattern(p));
40 | readProps(props, "format.time", p -> timeFormatter = DateTimeFormatter.ofPattern(p));
41 | readProps(props, "format.datetime", p -> datetimeFormatter = DateTimeFormatter.ofPattern(p));
42 | readProps(props, "format.timestamp", p -> timestampFormatter = DateTimeFormatter.ofPattern(p));
43 | readProps(props, "format.timestamp.zone", z -> timestampZoneId = ZoneId.of(z));
44 | }
45 |
46 | private void readProps(Properties properties, String settingKey, Consumer callback) {
47 | String settingValue = (String) properties.get(settingKey);
48 | if (settingValue == null || settingValue.length() == 0) {
49 | return;
50 | }
51 | try {
52 | callback.accept(settingValue.trim());
53 | } catch (IllegalArgumentException | DateTimeException e) {
54 | log.error("The \"{}\" setting is illegal:{}", settingKey, settingValue);
55 | throw e;
56 | }
57 | }
58 |
59 | @Override
60 | public void converterFor(RelationalColumn column, ConverterRegistration registration) {
61 | String sqlType = column.typeName().toUpperCase();
62 | SchemaBuilder schemaBuilder = null;
63 | Converter converter = null;
64 | if ("DATE".equals(sqlType)) {
65 | schemaBuilder = SchemaBuilder.string().optional().name("com.darcytech.debezium.date.string");
66 | converter = this::convertDate;
67 | }
68 | if ("TIME".equals(sqlType)) {
69 | schemaBuilder = SchemaBuilder.string().optional().name("com.darcytech.debezium.time.string");
70 | converter = this::convertTime;
71 | }
72 | if ("DATETIME".equals(sqlType)) {
73 | schemaBuilder = SchemaBuilder.string().optional().name("com.darcytech.debezium.datetime.string");
74 | converter = this::convertDateTime;
75 | }
76 | if ("TIMESTAMP".equals(sqlType)) {
77 | schemaBuilder = SchemaBuilder.string().optional().name("com.darcytech.debezium.timestamp.string");
78 | converter = this::convertTimestamp;
79 | }
80 | if (schemaBuilder != null) {
81 | registration.register(schemaBuilder, converter);
82 | log.info("register converter for sqlType {} to schema {}", sqlType, schemaBuilder.name());
83 | }
84 | }
85 |
86 | private String convertDate(Object input) {
87 | if (input instanceof LocalDate) {
88 | return dateFormatter.format((LocalDate) input);
89 | }
90 | if (input instanceof Integer) {
91 | LocalDate date = LocalDate.ofEpochDay((Integer) input);
92 | return dateFormatter.format(date);
93 | }
94 | return null;
95 | }
96 |
97 | private String convertTime(Object input) {
98 | if (input instanceof Duration) {
99 | Duration duration = (Duration) input;
100 | long seconds = duration.getSeconds();
101 | int nano = duration.getNano();
102 | LocalTime time = LocalTime.ofSecondOfDay(seconds).withNano(nano);
103 | return timeFormatter.format(time);
104 | }
105 | return null;
106 | }
107 |
108 | private String convertDateTime(Object input) {
109 | if (input instanceof LocalDateTime) {
110 | return datetimeFormatter.format((LocalDateTime) input);
111 | }
112 | return null;
113 | }
114 |
115 | private String convertTimestamp(Object input) {
116 | if (input instanceof ZonedDateTime) {
117 | // mysql的timestamp会转成UTC存储,这里的zonedDatetime都是UTC时间
118 | ZonedDateTime zonedDateTime = (ZonedDateTime) input;
119 | LocalDateTime localDateTime = zonedDateTime.withZoneSameInstant(timestampZoneId).toLocalDateTime();
120 | return timestampFormatter.format(localDateTime);
121 | }
122 | return null;
123 | }
124 |
125 | }
126 |
--------------------------------------------------------------------------------