Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ MuYun-Database 是一个基于 `Jdbi` 的轻量数据库工具库,面向单表

```groovy
dependencies {
implementation("net.ximatai.muyun.database:muyun-database-jdbi:3.26.11")
implementation("net.ximatai.muyun.database:muyun-database-jdbi:3.26.12")
}
```

Expand All @@ -64,7 +64,7 @@ UserEntity loaded = orm.findById(UserEntity.class, user.id);

```groovy
dependencies {
implementation("net.ximatai.muyun.database:muyun-database-spring-boot-starter:3.26.11")
implementation("net.ximatai.muyun.database:muyun-database-spring-boot-starter:3.26.12")
}
```

Expand All @@ -91,7 +91,7 @@ interface UserRepository extends EntityDao<UserEntity, String> {

```groovy
dependencies {
implementation("net.ximatai.muyun.database:muyun-database-quarkus:3.26.11")
implementation("net.ximatai.muyun.database:muyun-database-quarkus:3.26.12")
}
```

Expand Down Expand Up @@ -127,7 +127,7 @@ Quarkus 使用独立注解 `net.ximatai.muyun.database.quarkus.MuYunRepository`

## 版本与模块

- 当前版本 `3.26.11` 兼容 Java 21 及以上
- 当前版本 `3.26.12` 兼容 Java 21 及以上
- `1.26.+` 兼容 Java 8,位于 `jdbi-jdk8` 分支
- 具体发布版本以仓库 release / Maven Central 为准

Expand Down
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ val releasePublishModules = listOf(
allprojects {
group = "net.ximatai.muyun.database"
// version = "1.0.0-SNAPSHOT"
version = "3.26.11"
version = "3.26.12"

repositories {
maven { url = uri("https://mirrors.cloud.tencent.com/repository/maven") }
Expand Down
1 change: 1 addition & 0 deletions docs/API_CONTRACT.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,5 +96,6 @@ int upsert(T entity);
12. `ColumnType.JSON_SET` 必须通过 `@Column(type = ColumnType.JSON_SET)` 显式声明;默认 `Set<String>` 推断结果仍为 `ColumnType.SET`。
13. `ColumnType.JSON_SET` 的元素按字符串处理:写入时忽略 `null` 元素、按集合语义去重、保留首次出现顺序;空集合写入为 `[]`,字段值为 `null` 时写入为 `null`。
14. `ColumnType.JSON_SET` 读取非法 JSON 数组或写入非法 JSON 数组字符串时直接拒绝,不做静默降级。
15. `ColumnType.SET` / `ColumnType.JSON_SET` 字段声明了可识别泛型元素类型时,自定义 `DatabaseValueConverter` 可作用于集合元素;`JSON_SET` 底层仍保持 JSON 字符串数组语义。

下一步:若你在做历史项目改造,请按 [`REFACTOR_GUIDE.md`](REFACTOR_GUIDE.md) 的“推荐重构路径”执行。
4 changes: 2 additions & 2 deletions docs/QUARKUS.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@

```kotlin
dependencies {
implementation("net.ximatai.muyun.database:muyun-database-quarkus:3.26.11")
implementation("net.ximatai.muyun.database:muyun-database-quarkus:3.26.12")
}
```

扩展 runtime 会声明对应的 deployment artifact:

```properties
deployment-artifact=net.ximatai.muyun.database:muyun-database-quarkus-deployment::jar:3.26.11
deployment-artifact=net.ximatai.muyun.database:muyun-database-quarkus-deployment::jar:3.26.12
```

## 配置项
Expand Down
7 changes: 4 additions & 3 deletions docs/QUICKSTART.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,15 @@

```groovy
dependencies {
implementation("net.ximatai.muyun.database:muyun-database-jdbi:3.26.11")
implementation("net.ximatai.muyun.database:muyun-database-jdbi:3.26.12")
}
```

```xml
<dependency>
<groupId>net.ximatai.muyun.database</groupId>
<artifactId>muyun-database-jdbi</artifactId>
<version>3.26.11</version>
<version>3.26.12</version>
</dependency>
```

Expand Down Expand Up @@ -149,6 +149,7 @@ class ArticleEntity {
2. 写入时忽略 `null` 元素、按集合语义去重、保留首次出现顺序;空集合写入为 `[]`,字段值为 `null` 时写入为 `null`。
3. 读取或写入非法 JSON 数组字符串会直接失败,不会静默降级为单个元素。
4. 核心模块内置轻量 JSON 数组解析器;如需使用 Jackson 解析器,可额外引入 `muyun-database-core-json-jackson`。
5. 若集合字段声明了可识别泛型元素类型,自定义 `DatabaseValueConverter` 可作用于 SET/JSON_SET 的集合元素。

### 1.5 迁移控制(可选)

Expand All @@ -168,7 +169,7 @@ orm.ensureTable(UserEntity.class, MigrationOptions.dryRunStrict());

```groovy
dependencies {
implementation("net.ximatai.muyun.database:muyun-database-spring-boot-starter:3.26.11")
implementation("net.ximatai.muyun.database:muyun-database-spring-boot-starter:3.26.12")
}
```

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,14 +52,19 @@ public CompiledCriteria compile(Criteria criteria, CriteriaColumnResolver column
Objects.requireNonNull(columnResolver, "columnResolver must not be null");
Objects.requireNonNull(dbType, "dbType must not be null");

ClauseContext context = new ClauseContext(columnResolver, dbType);
ClauseContext context = new ClauseContext(columnResolver, dbType, null, valueConverter);
String sql = compileGroup(criteria.getRoot(), context);
return new CompiledCriteria(sql, context.params);
}

CompiledCriteria compile(Criteria criteria, EntityMeta meta, DBInfo.Type dbType) {
Objects.requireNonNull(criteria, "criteria must not be null");
Objects.requireNonNull(meta, "meta must not be null");
return compile(criteria, meta::resolveColumnName, dbType);
Objects.requireNonNull(dbType, "dbType must not be null");

ClauseContext context = new ClauseContext(meta::resolveColumnName, dbType, meta, valueConverter);
String sql = compileGroup(criteria.getRoot(), context);
return new CompiledCriteria(sql, context.params);
}

private String compileGroup(CriteriaGroup group, ClauseContext context) {
Expand Down Expand Up @@ -97,7 +102,7 @@ private String compileClause(CriteriaClause clause, ClauseContext context) {

private String compare(CriteriaClause clause, ClauseContext context, String op) {
String key = "p" + context.nextParamIndex();
context.params.put(key, valueConverter.toDatabaseValue(firstValue(clause)));
context.params.put(key, context.toDatabaseValue(clause, firstValue(clause)));
return resolveColumn(clause, context) + " " + op + " :" + key;
}

Expand All @@ -108,8 +113,8 @@ private String renderBetween(CriteriaClause clause, ClauseContext context) {
String key = "p" + context.nextParamIndex();
String key1 = key + "_1";
String key2 = key + "_2";
context.params.put(key1, valueConverter.toDatabaseValue(clause.getValues().get(0)));
context.params.put(key2, valueConverter.toDatabaseValue(clause.getValues().get(1)));
context.params.put(key1, context.toDatabaseValue(clause, clause.getValues().get(0)));
context.params.put(key2, context.toDatabaseValue(clause, clause.getValues().get(1)));
return resolveColumn(clause, context) + " BETWEEN :" + key1 + " AND :" + key2;
}

Expand All @@ -122,7 +127,7 @@ private String renderIn(CriteriaClause clause, ClauseContext context) {
for (int i = 0; i < clause.getValues().size(); i++) {
String listKey = key + "_" + i;
holders.add(":" + listKey);
context.params.put(listKey, valueConverter.toDatabaseValue(clause.getValues().get(i)));
context.params.put(listKey, context.toDatabaseValue(clause, clause.getValues().get(i)));
}
return resolveColumn(clause, context) + " IN (" + String.join(", ", holders) + ")";
}
Expand All @@ -136,7 +141,7 @@ private String renderNotIn(CriteriaClause clause, ClauseContext context) {
for (int i = 0; i < clause.getValues().size(); i++) {
String listKey = key + "_" + i;
holders.add(":" + listKey);
context.params.put(listKey, valueConverter.toDatabaseValue(clause.getValues().get(i)));
context.params.put(listKey, context.toDatabaseValue(clause, clause.getValues().get(i)));
}
return resolveColumn(clause, context) + " NOT IN (" + String.join(", ", holders) + ")";
}
Expand Down Expand Up @@ -215,17 +220,43 @@ private Object firstValue(CriteriaClause clause) {
private static class ClauseContext {
private final CriteriaColumnResolver columnResolver;
private final DBInfo.Type dbType;
private final EntityMeta meta;
private final DatabaseValueConverter valueConverter;
private final Map<String, Object> params = new HashMap<>();
private int paramIndex;

private ClauseContext(CriteriaColumnResolver columnResolver, DBInfo.Type dbType) {
private ClauseContext(CriteriaColumnResolver columnResolver,
DBInfo.Type dbType,
EntityMeta meta,
DatabaseValueConverter valueConverter) {
this.columnResolver = columnResolver;
this.dbType = dbType;
this.meta = meta;
this.valueConverter = valueConverter;
}

private int nextParamIndex() {
return paramIndex++;
}

private Object toDatabaseValue(CriteriaClause clause, Object value) {
EntityFieldMeta fieldMeta = resolveFieldMeta(clause.getField());
if (fieldMeta == null) {
return valueConverter.toDatabaseValue(value);
}
return FieldValueCodec.toDatabaseValue(fieldMeta, value, valueConverter);
}

private EntityFieldMeta resolveFieldMeta(String fieldOrColumn) {
if (meta == null) {
return null;
}
EntityFieldMeta byField = meta.findByFieldName(fieldOrColumn);
if (byField != null) {
return byField;
}
return meta.findByColumnName(fieldOrColumn);
}
}

@FunctionalInterface
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,22 +48,22 @@ public Object fromDatabaseValue(Object value, Class<?> targetType) {
}

if (targetType == int.class || targetType == Integer.class) {
return ((Number) value).intValue();
return asNumber(value).intValue();
}
if (targetType == long.class || targetType == Long.class) {
return ((Number) value).longValue();
return asNumber(value).longValue();
}
if (targetType == double.class || targetType == Double.class) {
return ((Number) value).doubleValue();
return asNumber(value).doubleValue();
}
if (targetType == float.class || targetType == Float.class) {
return ((Number) value).floatValue();
return asNumber(value).floatValue();
}
if (targetType == short.class || targetType == Short.class) {
return ((Number) value).shortValue();
return asNumber(value).shortValue();
}
if (targetType == byte.class || targetType == Byte.class) {
return ((Number) value).byteValue();
return asNumber(value).byteValue();
}

if (targetType == boolean.class || targetType == Boolean.class) {
Expand Down Expand Up @@ -103,4 +103,11 @@ public Object fromDatabaseValue(Object value, Class<?> targetType) {

return value;
}

private static Number asNumber(Object value) {
if (value instanceof Number number) {
return number;
}
return new BigDecimal(value.toString().trim());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -370,15 +370,26 @@ private DBInfo.Type databaseType() {
private Map<String, Object> resolveConditionColumns(EntityMeta meta, Map<String, Object> conditions) {
Map<String, Object> resolved = new LinkedHashMap<>();
for (Map.Entry<String, Object> entry : conditions.entrySet()) {
String columnName = meta.resolveColumnName(entry.getKey());
if (columnName == null || !SqlIdentifiers.isSafe(columnName)) {
EntityFieldMeta fieldMeta = resolveConditionField(meta, entry.getKey());
if (fieldMeta == null || !SqlIdentifiers.isSafe(fieldMeta.getColumnName())) {
throw new OrmException(OrmException.Code.INVALID_CRITERIA, "Unknown or unsafe condition field: " + entry.getKey());
}
resolved.put(columnName, valueConverter.toDatabaseValue(entry.getValue()));
resolved.put(
fieldMeta.getColumnName(),
FieldValueCodec.toDatabaseValue(fieldMeta, entry.getValue(), valueConverter)
);
}
return resolved;
}

private EntityFieldMeta resolveConditionField(EntityMeta meta, String fieldOrColumn) {
EntityFieldMeta byField = meta.findByFieldName(fieldOrColumn);
if (byField != null) {
return byField;
}
return meta.findByColumnName(fieldOrColumn);
}

int executeUpsertForTest(String schema, String tableName, Map<String, Object> body) {
return executeUpsert(schema, tableName, body, operations.getPKName());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,26 @@
import net.ximatai.muyun.database.core.builder.ColumnType;

import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.Optional;

public class EntityFieldMeta {
private final Field field;
private final String fieldName;
private final String columnName;
private final ColumnType columnType;
private final boolean id;
private final Optional<Class<?>> collectionElementType;

public EntityFieldMeta(Field field, String columnName, ColumnType columnType, boolean id) {
this.field = field;
this.fieldName = field.getName();
this.columnName = columnName;
this.columnType = columnType;
this.id = id;
this.collectionElementType = resolveCollectionElementType(field);
this.field.setAccessible(true);
}

Expand All @@ -40,6 +46,27 @@ public Class<?> getFieldType() {
return field.getType();
}

public Optional<Class<?>> getCollectionElementType() {
return collectionElementType;
}

private static Optional<Class<?>> resolveCollectionElementType(Field field) {
if (!Collection.class.isAssignableFrom(field.getType())) {
return Optional.empty();
}

Type genericType = field.getGenericType();
if (!(genericType instanceof ParameterizedType parameterizedType)) {
return Optional.empty();
}

Type[] arguments = parameterizedType.getActualTypeArguments();
if (arguments.length != 1 || !(arguments[0] instanceof Class<?> elementType)) {
return Optional.empty();
}
return Optional.of(elementType);
}

public Object read(Object target) {
try {
return field.get(target);
Expand Down
Loading
Loading