Bladeren bron

动态支持MySQL/Oracle

张泳健 5 jaren geleden
bovenliggende
commit
a7ce9e6a5d

+ 10 - 0
config/application-standone.yml Bestand weergeven

@@ -0,0 +1,10 @@
1
+from-db:
2
+  url: jdbc:oracle:thin:@192.168.10.236:1521:orcl
3
+  user: C##FMMP
4
+  password: vcare~1(^_^)
5
+to-db:
6
+  url: jdbc:mysql://192.168.10.214:3306/fmmp?serverTimezone=Asia/Shanghai
7
+  user: fmmp
8
+  password: E_wsq@163.com
9
+export:
10
+  folder: ./logs/

+ 2 - 1
config/application.yml Bestand weergeven

@@ -1,3 +1,4 @@
1 1
 profile:
2 2
 #  active: mysql
3
-   active: oracle
3
+#   active: oracle
4
+   active: standone

+ 9 - 0
pom.xml Bestand weergeven

@@ -105,6 +105,15 @@
105 105
             <version>1.0.2</version>
106 106
         </dependency>
107 107
 
108
+
109
+        <dependency>
110
+            <groupId>org.junit.jupiter</groupId>
111
+            <artifactId>junit-jupiter-api</artifactId>
112
+            <version>5.5.2</version>
113
+            <scope>test</scope>
114
+        </dependency>
115
+
116
+
108 117
     </dependencies>
109 118
 
110 119
 

+ 29 - 6
src/main/java/com/vcarecity/cvs/FileExporterApp.java Bestand weergeven

@@ -36,18 +36,41 @@ public class FileExporterApp {
36 36
         }
37 37
         Class<?> tableClass = ReflectionUtil.getTableClass(table);
38 38
 
39
-        String basePath = table;
40
-        final AppProperties.Export export = appProperties.getExport();
41
-        if (export != null) {
42
-            basePath = export.getFolder() + table;
39
+        boolean isMysql;
40
+        Object properties;
41
+
42
+        if (isImportToMysql(args)) {
43
+            properties = "mysql";
44
+            isMysql = true;
45
+        } else {
46
+
47
+            String basePath = table;
48
+            final AppProperties.Export export = appProperties.getExport();
49
+            if (export != null) {
50
+                basePath = export.getFolder() + table;
51
+            }
52
+            properties = basePath;
53
+            isMysql = false;
43 54
         }
44 55
 
45
-        injector = Guice.createInjector(new PropertiesModule(appProperties), new SQLModule());
56
+
57
+        injector = Guice.createInjector(new PropertiesModule(appProperties), new SQLModule(isMysql));
46 58
 
47 59
         final SQLStarter instance = injector.getInstance(SQLStarter.class);
48
-        instance.exportData(table, basePath, tableClass);
60
+        instance.exportDataAndConvert(table, properties, tableClass);
49 61
     }
50 62
 
63
+
64
+    private static boolean isImportToMysql(String[] args) {
65
+        for (String arg : args) {
66
+            if (arg.startsWith("--type=")) {
67
+                return !"csv".equalsIgnoreCase(arg.substring("--type=".length()));
68
+            }
69
+        }
70
+        return true;
71
+    }
72
+
73
+
51 74
     private static String getTableByArgs(String[] args) {
52 75
         for (String arg : args) {
53 76
             if (arg.startsWith("--table=")) {

+ 6 - 13
src/main/java/com/vcarecity/cvs/event/MySQLImportEvent.java Bestand weergeven

@@ -1,17 +1,10 @@
1 1
 package com.vcarecity.cvs.event;
2 2
 
3
-import com.google.common.util.concurrent.ThreadFactoryBuilder;
4 3
 import com.google.inject.Inject;
5
-import com.vcarecity.cvs.core.ColumnUpdateMapper;
6 4
 import com.vcarecity.cvs.properties.AppProperties;
7 5
 import com.vcarecity.cvs.properties.DbProperties;
8
-import com.vcarecity.cvs.shell.MySQLImporter;
9 6
 import lombok.extern.slf4j.Slf4j;
10 7
 
11
-import java.util.concurrent.LinkedBlockingQueue;
12
-import java.util.concurrent.ThreadFactory;
13
-import java.util.concurrent.ThreadPoolExecutor;
14
-import java.util.concurrent.TimeUnit;
15 8
 import java.util.concurrent.atomic.AtomicInteger;
16 9
 
17 10
 /**
@@ -39,15 +32,15 @@ public class MySQLImportEvent implements ExportEventListener {
39 32
     public void exportSuccess(String filename, String table, String[] header) {
40 33
         logger.info("MySQLImportEvent.exportSuccess. filename = {}", filename);
41 34
 
42
-        ThreadFactory factory = new ThreadFactoryBuilder().setNameFormat("export-" + (callIndex.incrementAndGet()) + "+-%d").build();
35
+        //ThreadFactory factory = new ThreadFactoryBuilder().setNameFormat("export-" + (callIndex.incrementAndGet()) + "+-%d").build();
43 36
 
44
-        ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS,
45
-                new LinkedBlockingQueue<>(1024), factory, new ThreadPoolExecutor.AbortPolicy());
37
+        //ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS,
38
+        //        new LinkedBlockingQueue<>(1024), factory, new ThreadPoolExecutor.AbortPolicy());
46 39
 
47 40
         // 列名不一样时,转换
48
-        final String[] newHeader = ColumnUpdateMapper.updateColumnName(table, header);
49
-        executor.execute(new MySQLImporter(dbProperties, filename, table, newHeader));
50
-        executor.shutdown();
41
+        //final String[] newHeader = ColumnUpdateMapper.updateColumnName(table, header);
42
+        //executor.execute(new MySQLImporter(dbProperties, filename, table, newHeader));
43
+        //executor.shutdown();
51 44
     }
52 45
 
53 46
 

+ 1 - 1
src/main/java/com/vcarecity/cvs/factory/ResultHandlerFactory.java Bestand weergeven

@@ -14,6 +14,6 @@ public interface ResultHandlerFactory {
14 14
      * @param properties
15 15
      * @return
16 16
      */
17
-    ResultHandlerService createResultHandler(String properties);
17
+    ResultHandlerService createResultHandler(Object properties);
18 18
 
19 19
 }

+ 18 - 22
src/main/java/com/vcarecity/cvs/module/SQLModule.java Bestand weergeven

@@ -12,10 +12,8 @@ import com.vcarecity.cvs.properties.AppProperties;
12 12
 import com.vcarecity.cvs.properties.DbProperties;
13 13
 import com.vcarecity.cvs.service.ResultHandlerService;
14 14
 import com.vcarecity.cvs.service.SQLQueryService;
15
-import com.vcarecity.cvs.service.impl.CSVResultHandlerServiceImpl;
16
-import com.vcarecity.cvs.service.impl.MySQLQueryServiceImpl;
17
-import com.vcarecity.cvs.service.impl.OracleQueryServiceImpl;
18
-import com.zaxxer.hikari.HikariConfig;
15
+import com.vcarecity.cvs.service.impl.*;
16
+import com.vcarecity.cvs.util.CreateDatasource;
19 17
 import com.zaxxer.hikari.HikariDataSource;
20 18
 
21 19
 /**
@@ -23,13 +21,27 @@ import com.zaxxer.hikari.HikariDataSource;
23 21
  */
24 22
 
25 23
 public class SQLModule extends AbstractModule {
24
+    private final boolean isMysql;
25
+
26
+    public SQLModule(boolean isMysql) {
27
+        this.isMysql = isMysql;
28
+    }
29
+
26 30
     @Override
27 31
     protected void configure() {
28 32
 
29 33
         bind(ExportEventListener.class).to(MySQLImportEvent.class);
30 34
 
35
+        Class<? extends ResultHandlerService> resultClass;
36
+        if (isMysql) {
37
+            // resultClass = MySQLResultStatHandlerServiceImpl.class;
38
+            resultClass = MySQLResultHandlerServiceImpl.class;
39
+        } else {
40
+            resultClass = CSVResultHandlerServiceImpl.class;
41
+        }
42
+
31 43
         install(new FactoryModuleBuilder()
32
-                .implement(ResultHandlerService.class, CSVResultHandlerServiceImpl.class)
44
+                .implement(ResultHandlerService.class, resultClass)
33 45
                 .build(ResultHandlerFactory.class));
34 46
 
35 47
     }
@@ -54,23 +66,7 @@ public class SQLModule extends AbstractModule {
54 66
         // @see https://github.com/brettwooldridge/HikariCP/wiki/MySQL-Configuration
55 67
 
56 68
         final DbProperties database = properties.getFromDb();
57
-        HikariConfig config = new HikariConfig();
58
-        config.setJdbcUrl(database.getUrl());
59
-        config.setUsername(database.getUser());
60
-        config.setPassword(database.getPassword());
61
-
62
-        config.addDataSourceProperty("cachePrepStmts", "true");
63
-        config.addDataSourceProperty("prepStmtCacheSize", "250");
64
-        config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048");
65
-        config.addDataSourceProperty("useServerPrepStmts", "true");
66
-        config.addDataSourceProperty("useLocalSessionState", "true");
67
-        config.addDataSourceProperty("rewriteBatchedStatements", "true");
68
-        config.addDataSourceProperty("cacheResultSetMetadata", "true");
69
-        config.addDataSourceProperty("cacheServerConfiguration", "true");
70
-        config.addDataSourceProperty("elideSetAutoCommits", "true");
71
-        config.addDataSourceProperty("maintainTimeStats", "false");
72
-
73
-        final HikariDataSource hikariDataSource = new HikariDataSource(config);
69
+        final HikariDataSource hikariDataSource = CreateDatasource.createDataSource(database);
74 70
         Runtime.getRuntime().addShutdownHook(new Thread(hikariDataSource::close));
75 71
         return hikariDataSource;
76 72
     }

+ 1 - 1
src/main/java/com/vcarecity/cvs/script/BuildPartitionSQL.java Bestand weergeven

@@ -22,7 +22,7 @@ public class BuildPartitionSQL {
22 22
 
23 23
     public static void main(String[] args) {
24 24
         BuildPartitionSQL buildPartitionSQL = new BuildPartitionSQL();
25
-        buildPartitionSQL.partitionFirstSql("T_CHECK_RECORD_1", "stamp", "2016-08-23", "2019-11-21", PartitionType.MONTH);
25
+        buildPartitionSQL.partitionFirstSql("T_ALARM_LAST", "stamp", "2016-03-29", "2020-01-01", PartitionType.MONTH);
26 26
     }
27 27
 
28 28
 

+ 1 - 1
src/main/java/com/vcarecity/cvs/service/impl/AbstractSQLQueryService.java Bestand weergeven

@@ -48,7 +48,7 @@ public abstract class AbstractSQLQueryService implements SQLQueryService {
48 48
 
49 49
         List<T> list = resultMapper(table, resultSet, clazz);
50 50
 
51
-        logger.debug("query success. size = {}. usageTime = {} /ms", list.size(), (System.currentTimeMillis() - startTime));
51
+        logger.info("QUERY SUCCESS. size = {}. usageTime = {} /ms", list.size(), (System.currentTimeMillis() - startTime));
52 52
 
53 53
         resultSet.close();
54 54
         preparedStatement.close();

+ 3 - 2
src/main/java/com/vcarecity/cvs/service/impl/CSVResultHandlerServiceImpl.java Bestand weergeven

@@ -51,9 +51,10 @@ public class CSVResultHandlerServiceImpl implements ResultHandlerService {
51 51
     private final ExportEventListener eventListener;
52 52
 
53 53
     @Inject
54
-    public CSVResultHandlerServiceImpl(ExportEventListener eventListener, @Assisted String filePattern) {
54
+    public CSVResultHandlerServiceImpl(ExportEventListener eventListener,
55
+                                       @Assisted Object filePattern) {
55 56
         this.eventListener = eventListener;
56
-        this.filePattern = filePattern;
57
+        this.filePattern = (String) filePattern;
57 58
         this.csvFormat = CSVFormat.MYSQL;
58 59
         this.counter = 0;
59 60
     }

+ 57 - 11
src/main/java/com/vcarecity/cvs/service/impl/MySQLResultHandlerServiceImpl.java Bestand weergeven

@@ -2,13 +2,16 @@ package com.vcarecity.cvs.service.impl;
2 2
 
3 3
 import com.google.inject.Inject;
4 4
 import com.google.inject.assistedinject.Assisted;
5
-import com.vcarecity.cvs.event.ExportEventListener;
5
+import com.vcarecity.cvs.core.ColumnUpdateMapper;
6
+import com.vcarecity.cvs.properties.AppProperties;
6 7
 import com.vcarecity.cvs.service.ResultHandlerService;
8
+import com.vcarecity.cvs.util.CreateDatasource;
7 9
 import com.zaxxer.hikari.HikariDataSource;
8 10
 import lombok.extern.slf4j.Slf4j;
9 11
 
10 12
 import java.sql.Connection;
11
-import java.time.LocalDateTime;
13
+import java.sql.PreparedStatement;
14
+import java.util.Arrays;
12 15
 import java.util.List;
13 16
 
14 17
 /**
@@ -19,27 +22,70 @@ import java.util.List;
19 22
 @Slf4j
20 23
 public class MySQLResultHandlerServiceImpl implements ResultHandlerService {
21 24
 
22
-    private final HikariDataSource dataSource;
23
-    private final ExportEventListener eventListener;
24 25
     private final String sql;
26
+    private final HikariDataSource dataSource;
25 27
 
26 28
     @Inject
27
-    public MySQLResultHandlerServiceImpl(HikariDataSource dataSource, ExportEventListener eventListener, @Assisted String sql) {
28
-        this.dataSource = dataSource;
29
-        this.eventListener = eventListener;
30
-        this.sql = sql;
29
+    public MySQLResultHandlerServiceImpl(AppProperties properties, @Assisted Object sql) {
30
+        this.sql = (String) sql;
31
+        this.dataSource = CreateDatasource.createDataSource(properties.getToDb());
31 32
     }
32 33
 
33 34
 
34 35
     @Override
35 36
     public <T> void resultHandler(String table, String[] header, List<T> dataList) throws Exception {
36
-        logger.debug("start write to mysql size = {}, on = {}", dataList.size(), LocalDateTime.now());
37
+
38
+        final String sql = getSql(table, header);
39
+
40
+
37 41
         long startTime = System.currentTimeMillis();
42
+
43
+        logger.debug("{},{}", dataList.size(), sql);
38 44
         final Connection connection = dataSource.getConnection();
45
+        try {
46
+            connection.setAutoCommit(false);
47
+            PreparedStatement ps = connection.prepareStatement(sql);
48
+
49
+            int index = 0;
50
+            for (T t : dataList) {
51
+                index++;
52
+                if (t.getClass().isArray()) {
53
+                    Object[] rows = (Object[]) t;
54
+                    for (int i = 0; i < rows.length; i++) {
55
+                        ps.setObject(i + 1, rows[i]);
56
+                    }
57
+                } else if (List.class.isAssignableFrom(t.getClass())) {
58
+                    List rows = (List) t;
59
+                    for (int i = 0; i < rows.size(); i++) {
60
+                        ps.setObject(i + 1, rows.get(i));
61
+                    }
62
+                }
63
+                ps.addBatch();
64
+                //if (index % 1000 == 0) {
65
+                //    ps.executeBatch();
66
+                //    connection.commit();
67
+                //}
68
+            }
69
+            ps.executeBatch();
70
+            connection.commit();
71
+            ps.close();
72
+        } finally {
73
+            connection.close();
74
+        }
75
+        logger.info("WRITE SUCCESS to mysql usage time {} /ms, size = {}", System.currentTimeMillis() - startTime, dataList.size());
76
+    }
77
+
78
+
79
+    private static String getSql(String table, String[] headers) {
80
+
81
+        final String[] newHeaders = ColumnUpdateMapper.updateColumnName(table, headers);
82
+        final String column = String.join(",", newHeaders);
39 83
 
84
+        String[] fill = new String[headers.length];
85
+        Arrays.fill(fill, "?");
86
+        final String join = String.join(",", fill);
40 87
 
41
-        connection.close();
88
+        return "INSERT INTO " + table + "(" + column + ") VALUES(" + join + ")";
42 89
 
43
-        logger.debug("write to mysql usage time {} /ms", System.currentTimeMillis() - startTime);
44 90
     }
45 91
 }

+ 89 - 0
src/main/java/com/vcarecity/cvs/service/impl/MySQLResultStatHandlerServiceImpl.java Bestand weergeven

@@ -0,0 +1,89 @@
1
+package com.vcarecity.cvs.service.impl;
2
+
3
+import com.google.inject.Inject;
4
+import com.google.inject.assistedinject.Assisted;
5
+import com.vcarecity.cvs.core.ColumnUpdateMapper;
6
+import com.vcarecity.cvs.properties.AppProperties;
7
+import com.vcarecity.cvs.service.ResultHandlerService;
8
+import com.vcarecity.cvs.util.CreateDatasource;
9
+import com.zaxxer.hikari.HikariDataSource;
10
+import lombok.extern.slf4j.Slf4j;
11
+
12
+import java.sql.Connection;
13
+import java.sql.Date;
14
+import java.sql.Statement;
15
+import java.sql.Timestamp;
16
+import java.util.List;
17
+
18
+/**
19
+ * @author Kerry on 19/11/21
20
+ */
21
+
22
+
23
+@Slf4j
24
+public class MySQLResultStatHandlerServiceImpl implements ResultHandlerService {
25
+
26
+
27
+    private final String sql;
28
+    private final HikariDataSource dataSource;
29
+
30
+    @Inject
31
+    public MySQLResultStatHandlerServiceImpl(AppProperties properties, @Assisted Object sql) {
32
+        this.sql = (String) sql;
33
+        this.dataSource = CreateDatasource.createDataSource(properties.getToDb());
34
+    }
35
+
36
+
37
+    @Override
38
+    public <T> void resultHandler(String table, String[] header, List<T> dataList) throws Exception {
39
+
40
+
41
+        long startTime = System.currentTimeMillis();
42
+
43
+        StringBuffer sb = new StringBuffer();
44
+
45
+        final String[] newHeaders = ColumnUpdateMapper.updateColumnName(table, header);
46
+        final String column = String.join(",", newHeaders);
47
+
48
+
49
+        sb.append("INSERT INTO ").append(table).append("(").append(String.join(",", column)).append(") VALUES ");
50
+
51
+        for (T t : dataList) {
52
+            if (t.getClass().isArray()) {
53
+                Object[] rows = (Object[]) t;
54
+                sb.append("(");
55
+                for (int i = 0; i < header.length; i++) {
56
+
57
+                    Object row = rows[i];
58
+                    if (row instanceof String) {
59
+                        sb.append("'").append(row).append("'");
60
+                    } else if (row instanceof Date) {
61
+                        sb.append("'").append(row).append("'");
62
+                    } else if (row instanceof Timestamp) {
63
+                        sb.append("'").append(row).append("'");
64
+                    } else {
65
+                        sb.append(row);
66
+                    }
67
+                    if (i != header.length - 1) {
68
+                        sb.append(",");
69
+                    }
70
+                }
71
+                sb.append("),");
72
+            }
73
+        }
74
+        final StringBuffer stringBuffer = sb.deleteCharAt(sb.length() - 1);
75
+
76
+        // logger.debug("{}", stringBuffer.toString());
77
+
78
+        final Connection connection = dataSource.getConnection();
79
+
80
+        final Statement statement = connection.createStatement();
81
+        statement.execute(stringBuffer.toString());
82
+        statement.close();
83
+        connection.close();
84
+
85
+        logger.info("WRITE SUCCESS to mysql usage time {} /ms, size = {}", System.currentTimeMillis() - startTime, dataList.size());
86
+    }
87
+
88
+
89
+}

+ 10 - 1
src/main/java/com/vcarecity/cvs/starter/SQLStarter.java Bestand weergeven

@@ -1,5 +1,6 @@
1 1
 package com.vcarecity.cvs.starter;
2 2
 
3
+import com.vcarecity.cvs.core.ColumnUpdateMapper;
3 4
 import com.vcarecity.cvs.factory.ResultHandlerFactory;
4 5
 import com.vcarecity.cvs.service.ResultHandlerService;
5 6
 import com.vcarecity.cvs.service.SQLQueryService;
@@ -43,7 +44,7 @@ public class SQLStarter {
43 44
      * @param properties
44 45
      * @throws Exception
45 46
      */
46
-    public <T> void exportData(String table, String properties, Class<T> cls) throws Exception {
47
+    public <T> void exportDataAndConvert(String table, Object properties, Class<T> cls) throws Exception {
47 48
 
48 49
         if (cls == null) {
49 50
             //noinspection unchecked
@@ -61,8 +62,10 @@ public class SQLStarter {
61 62
         int page = 1;
62 63
 
63 64
         do {
65
+            // query
64 66
             List<T> result = queryService.queryByPage(table, page, this.pageCount, cls);
65 67
             final String[] headers = TABLE_COLUMN.get(table);
68
+            // convert or save
66 69
             resultHandler.resultHandler(table, headers, result);
67 70
             currentSize = result.size();
68 71
 
@@ -74,6 +77,12 @@ public class SQLStarter {
74 77
             ((CSVResultHandlerServiceImpl) resultHandler).close();
75 78
         }
76 79
 
80
+
81
+        final String[] headers = TABLE_COLUMN.get(table);
82
+        final String[] mysqlHeader = ColumnUpdateMapper.updateColumnName(table, headers);
83
+
84
+        logger.info("mysql header: {}", String.join(",", mysqlHeader));
85
+
77 86
         logger.info("stop query database...{} /ms.", (System.currentTimeMillis() - startTime));
78 87
     }
79 88
 

+ 35 - 0
src/main/java/com/vcarecity/cvs/util/CreateDatasource.java Bestand weergeven

@@ -0,0 +1,35 @@
1
+package com.vcarecity.cvs.util;
2
+
3
+import com.vcarecity.cvs.properties.DbProperties;
4
+import com.zaxxer.hikari.HikariConfig;
5
+import com.zaxxer.hikari.HikariDataSource;
6
+
7
+/**
8
+ * @author Kerry on 19/12/02
9
+ */
10
+
11
+public class CreateDatasource {
12
+
13
+
14
+    public static HikariDataSource createDataSource(DbProperties database) {
15
+
16
+        HikariConfig config = new HikariConfig();
17
+        config.setJdbcUrl(database.getUrl());
18
+        config.setUsername(database.getUser());
19
+        config.setPassword(database.getPassword());
20
+
21
+        config.addDataSourceProperty("cachePrepStmts", "true");
22
+        config.addDataSourceProperty("prepStmtCacheSize", "250");
23
+        config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048");
24
+        config.addDataSourceProperty("useServerPrepStmts", "true");
25
+        config.addDataSourceProperty("useLocalSessionState", "true");
26
+        config.addDataSourceProperty("rewriteBatchedStatements", "true");
27
+        config.addDataSourceProperty("cacheResultSetMetadata", "true");
28
+        config.addDataSourceProperty("cacheServerConfiguration", "true");
29
+        config.addDataSourceProperty("elideSetAutoCommits", "true");
30
+        config.addDataSourceProperty("maintainTimeStats", "false");
31
+
32
+        return new HikariDataSource(config);
33
+    }
34
+
35
+}

+ 1 - 0
src/main/resources/logback-test.xml Bestand weergeven

@@ -29,6 +29,7 @@
29 29
 
30 30
     <logger name="com.vcarecity" level="INFO" additivity="false">
31 31
         <appender-ref ref="STDOUT"/>
32
+        <appender-ref ref="FILE_APPENDER"/>
32 33
     </logger>
33 34
 
34 35
     <root level="INFO">

+ 40 - 0
src/test/java/com/vcarecity/cvs/service/impl/MySQLResultHandlerServiceImplTest.java Bestand weergeven

@@ -0,0 +1,40 @@
1
+package com.vcarecity.cvs.service.impl;
2
+
3
+import java.util.Arrays;
4
+
5
+class MySQLResultHandlerServiceImplTest {
6
+
7
+    @org.junit.jupiter.api.Test
8
+    void resultHandler() {
9
+        String[] h = new String[3];
10
+        StringBuffer sb = new StringBuffer();
11
+
12
+        Object[] rows = new Object[]{1, "test", null};
13
+
14
+        for (int i = 0; i < rows.length; i++) {
15
+            Object row = rows[i];
16
+            if (row instanceof String) {
17
+                sb.append("'").append(row).append("'");
18
+            } else {
19
+                sb.append(row);
20
+            }
21
+
22
+            if (i != rows.length - 1) {
23
+                sb.append(",");
24
+            }
25
+        }
26
+
27
+
28
+        System.out.println(sb.toString());
29
+
30
+
31
+        Arrays.fill(h, "%s");
32
+        final String join = String.join(",", h);
33
+
34
+
35
+        final String value = String.format(join, rows);
36
+
37
+        System.out.println(join);
38
+        System.out.println(value);
39
+    }
40
+}