converter = row -> {
123 | if (type.isAssignableFrom(Row.class)) {
124 | return (T) row;
125 | } else {
126 | try {
127 | return (T) TypeConverter.convertRow(type, row, nameConverter);
128 | } catch (Throwable e) {
129 | throw DAOException.wrap(e);
130 | }
131 | }
132 | };
133 |
134 | return new Iterator<>() {
135 | private T next = null;
136 |
137 | private void fetchNext() {
138 | if (RowIterator.this.next()) {
139 | this.next = converter.apply(RowIterator.this.getRow());
140 | } else {
141 | this.next = null;
142 | }
143 | }
144 |
145 | {
146 | fetchNext();
147 | }
148 |
149 | @Override
150 | public boolean hasNext() {
151 | return this.next != null;
152 | }
153 |
154 | @Override
155 | public T next() {
156 | var result = this.next;
157 | fetchNext();
158 | return result;
159 | }
160 | };
161 | }
162 | }
163 |
--------------------------------------------------------------------------------
/hydrogen-dao/src/main/java/com/hyd/dao/mate/util/TypeUtil.java:
--------------------------------------------------------------------------------
1 | package com.hyd.dao.mate.util;
2 |
3 | import com.hyd.dao.DAOException;
4 | import com.hyd.dao.database.type.BlobReader;
5 | import com.hyd.dao.database.type.ClobUtil;
6 | import com.hyd.dao.time.UniTime;
7 |
8 | import java.lang.reflect.Field;
9 | import java.math.BigDecimal;
10 | import java.sql.Blob;
11 | import java.sql.Clob;
12 | import java.sql.Timestamp;
13 | import java.sql.Types;
14 | import java.text.ParseException;
15 | import java.text.SimpleDateFormat;
16 | import java.time.temporal.Temporal;
17 | import java.util.Date;
18 |
19 | /**
20 | * 处理数据库中的值类型的类
21 | */
22 | public class TypeUtil {
23 |
24 | public static final String[] DATE_PATTERNS = {
25 | "yyyy-MM-dd HH:mm:ss",
26 | "yyyy-MM-dd HH:mm:ss.SSS",
27 | "yyyy-MM-dd",
28 | };
29 |
30 | /**
31 | * 根据字段数据类型将数据库中的值转化为 Java 类型,用于对简单查询结果的转换
32 | * 转换结果将放入 Row 对象,以帮助进行进一步转换。
33 | *
34 | * 数字类型 -> BigDecimal
35 | * CLOB -> String
36 | * BLOB -> byte[]
37 | * 其他类型保持原样
38 | *
39 | * @param columnType 值的 SQL 类型
40 | * @param value 值
41 | *
42 | * @return 转化后的类型
43 | *
44 | * @throws DAOException 如果读取 LOB 失败
45 | */
46 | public static Object convertDatabaseValue(int columnType, Object value) {
47 | try {
48 | if (value == null) {
49 | return null;
50 | } else if (isNumericType(columnType)) {
51 | return (value instanceof BigDecimal ? value : new BigDecimal(value.toString()));
52 | } else if (isDateType(columnType)) {
53 | return toDate(value);
54 | } else if (value instanceof Clob) {
55 | return ClobUtil.read((Clob) value);
56 | } else if (value instanceof Blob) {
57 | return BlobReader.readBytes((Blob) value);
58 | }
59 | return value;
60 | } catch (Exception e) {
61 | throw DAOException.wrap(e);
62 | }
63 | }
64 |
65 | private static boolean isNumericType(int columnType) {
66 | return columnType == Types.NUMERIC || columnType == Types.INTEGER
67 | || columnType == Types.BIGINT || columnType == Types.REAL
68 | || columnType == Types.DECIMAL || columnType == Types.FLOAT
69 | || columnType == Types.DOUBLE;
70 | }
71 |
72 | public static boolean isDateType(int columnType) {
73 | return columnType == Types.DATE || columnType == Types.TIME || columnType == Types.TIMESTAMP;
74 | }
75 |
76 | private static Date toDate(Object value) {
77 |
78 | if (value instanceof Date) {
79 | return (Date) value;
80 | }
81 |
82 | var type = value.getClass();
83 |
84 | if (type == String.class) {
85 | return toDateFromString(value.toString());
86 | } else if (value instanceof Temporal) {
87 | return UniTime.fromTemporal((Temporal) value).toDate();
88 | }
89 |
90 | try {
91 | return (Date) type.getDeclaredMethod("dateValue").invoke(value);
92 | } catch (NoSuchMethodException e) {
93 | throw new IllegalStateException("Value of type " + type + " cannot be cast to Date");
94 | } catch (Exception e) {
95 | throw new IllegalStateException(e);
96 | }
97 | }
98 |
99 | private static Date toDateFromString(String s) {
100 | for (var pattern : DATE_PATTERNS) {
101 | try {
102 | return new SimpleDateFormat(pattern).parse(s);
103 | } catch (ParseException e) {
104 | // ignore
105 | }
106 | }
107 |
108 | throw new IllegalStateException("Unable to parse date string '" + s + "'");
109 | }
110 |
111 | /**
112 | * 对 SQL 的参数进行转换
113 | *
114 | * @param obj 参数值
115 | *
116 | * @return 修复后的参数
117 | */
118 | public static Object convertParamValue(Object obj) {
119 | if (obj == null) {
120 | return "";
121 | } else if (obj.getClass().isEnum()) {
122 | return ((Enum>) obj).name();
123 | } else if (obj.getClass().equals(Date.class)) {
124 | return new Timestamp(((Date) obj).getTime()); // 将 Date 转化为 TimeStamp,以避免时间丢失
125 | } else {
126 | return obj;
127 | }
128 | }
129 |
130 | public static Field getFieldIgnoreCase(Class> type, String fieldName) {
131 | if (type == Object.class) {
132 | return null;
133 | }
134 |
135 | var fields = type.getDeclaredFields();
136 | for (var field : fields) {
137 | if (field.getName().equalsIgnoreCase(fieldName)) {
138 | return field;
139 | }
140 | }
141 |
142 | return getFieldIgnoreCase(type.getSuperclass(), fieldName);
143 | }
144 | }
145 |
--------------------------------------------------------------------------------
/hydrogen-dao/src/main/java/com/hyd/dao/command/builder/InsertBuilder.java:
--------------------------------------------------------------------------------
1 | package com.hyd.dao.command.builder;
2 |
3 | import com.hyd.dao.DAO;
4 | import com.hyd.dao.DAOException;
5 | import com.hyd.dao.SQL;
6 | import com.hyd.dao.command.BatchCommand;
7 | import com.hyd.dao.command.Command;
8 | import com.hyd.dao.database.ColumnInfo;
9 | import com.hyd.dao.database.ConnectionContext;
10 | import com.hyd.dao.database.FQN;
11 | import com.hyd.dao.database.type.NameConverter;
12 |
13 | import java.util.Collection;
14 | import java.util.List;
15 |
16 | import static com.hyd.dao.command.builder.helper.CommandBuilderHelper.*;
17 |
18 | /**
19 | * 创建 insert 语句
20 | */
21 | public final class InsertBuilder extends CommandBuilder {
22 |
23 | public InsertBuilder(ConnectionContext context) {
24 | super(context);
25 | }
26 |
27 | /**
28 | * 构造一个批处理命令。注意:批处理命令 SQL 语句中有哪些参数,是根据第一个要插入的记录生成的。
29 | * 这时候不能因为记录的某个属性值为 null 就不将该属性加入 SQL,因为后面的其他记录的属性值可能不是 null。
30 | *
31 | * @param tableName 表名
32 | * @param objects 要插入的记录对象
33 | *
34 | * @return 批处理插入命令
35 | */
36 | public BatchCommand buildBatch(String tableName, Collection> objects) {
37 |
38 | if (objects == null || objects.isEmpty()) {
39 | return BatchCommand.EMPTY;
40 | }
41 |
42 | final NameConverter nameConverter = context.getNameConverter();
43 | final FQN fqn = new FQN(context, tableName);
44 | final Object sample = objects.iterator().next();
45 | final List infos = getBatchColumnInfo(context, tableName, sample);
46 | final SQL.Insert insert = new SQL.Insert(fqn.getQuotedName());
47 |
48 | for (ColumnInfo info : infos) {
49 | boolean isUsingSysdate = info.getDataType() == DAO.SYSDATE_TYPE;
50 | String columnName;
51 |
52 | if (isUsingSysdate) {
53 | columnName = context.getDialect().currentTimeExpression();
54 | } else {
55 | columnName = context.getDialect().quote(info.getColumnName());
56 | }
57 |
58 | insert.Values(columnName, new Object());
59 | }
60 |
61 | // 生成命令
62 | BatchCommand bc = new BatchCommand(insert.toCommand().getStatement());
63 | bc.setColumnInfos(infos);
64 |
65 | for (Object object : objects) {
66 | bc.addParams(generateParams(infos, object, nameConverter));
67 | }
68 | return bc;
69 | }
70 |
71 | // 获取要批量插入的表字段信息
72 | private static List getBatchColumnInfo(
73 | ConnectionContext context, String tableName, Object sample
74 | ) {
75 |
76 | NameConverter nameConverter = context.getNameConverter();
77 | FQN fqn = new FQN(context, tableName);
78 | List originColInfos = getColumnInfos(fqn, context);
79 | List infos = filterColumnsByType(originColInfos, sample.getClass(), nameConverter);
80 |
81 | List