Model增加json字段的类型转换

说明

平时有些表字段不用检索只用存取还有扩展需求,于是会使用MySQL的json字段进行存储,公司使用的是MybatisPlus,有个TypeHandler的能对JSON进行序列化和反序列化,用多了也觉得挺方便的。

自己练手的Jfinal项目也用到了json字段,于是想如果ActiveRecord有这个功能应该也不错。于是在群里提问题,波总给了个提示是扩展ModelBuilder,自己照着写了个扩展,测试下能用。

主体思路是获取到数据后转换成需要的类型,保存/更新数据库前转换字符串,具体内容看代码吧。

代码

ModelBuilder

主体代码从ModelBuilder考过来就可以,注意包名一致,否则无法ar._getAttrs()无法访问。此处省略未修改的代码。

public class DolphinModelBuilder extends ModelBuilder {

  public static final ModelBuilder me = new DolphinModelBuilder();

  @Override
  @SuppressWarnings({"rawtypes", "unchecked"})
  public <T> List<T> build(
      ResultSet rs, Class<? extends Model> modelClass, Function<T, Boolean> func)
      throws SQLException, ReflectiveOperationException {
...

        // 设置attrs前处理类型转化
        Object handlerValue = getHandlerValue(modelClass, value, labelNames[i]);
        attrs.put(labelNames[i], handlerValue);
...
  }

  private Object getHandlerValue(
      Class<? extends Model> modelClass, Object value, String labelNames) {
    try {
      Field field = ReflectUtil.getField(modelClass, labelNames);
      FieldHandler fieldHandler = field.getAnnotation(FieldHandler.class);
      if (fieldHandler == null) {
        return value;
      }

      Class<? extends TypeHandler> typeHandler = fieldHandler.value();
      if (typeHandler == null) {
        return value;
      }

      TypeHandler handler = ReflectUtil.newInstance(typeHandler, field.getType());
      return handler.get(value);

    } catch (Exception ignore) {

    }

    return value;
  }
}

Model

主体代码从Model考过来就可以,注意包名一致,否则无法config.dialect无法访问。此处省略未修改的代码。

public abstract class DolphinModel<M extends Model<M>> extends Model<M> {
  /** serialVersionUID */
  @Serial private static final long serialVersionUID = 4480068767017867478L;

  public boolean save() {
  ...
    //对attrs进行类型转换
    Map<String, Object> attrs = _getHandlerAttrs();
    config.dialect.forModelSave(table, attrs, sql, paras);
  ...  
  }

  /** Update model. */
  public boolean update() {
...
    //对attrs进行类型转换
    Map<String, Object> attrs = _getHandlerAttrs();
    for (String pKey : pKeys) {
      Object id = attrs.get(pKey);
      if (id == null) {
        throw new ActiveRecordException(
            "You can't update model without Primary Key, " + pKey + " can not be null.");
      }
    }
 ...
  }

  protected Map<String, Object> _getHandlerAttrs() {
    Map<String, Object> attrs = _getAttrs();
    try {

      Class<? extends DolphinModel> modelClass = this.getClass();
      Field[] fields = ReflectUtil.getFields(modelClass);
      for (Field field : fields) {
        FieldHandler fieldHandler = field.getAnnotation(FieldHandler.class);
        if (fieldHandler == null) {
          continue;
        }

        Class<? extends TypeHandler> typeHandler = fieldHandler.value();
        if (typeHandler == null) {
          continue;
        }
        String fieldName = fieldHandler.field();
        if (StrKit.isBlank(fieldName)) {
          fieldName = field.getName();
        }
        
        Object value = attrs.get(fieldName);
        TypeHandler handler = ReflectUtil.newInstance(typeHandler, field.getType());
        Object v = handler.set(value);
        attrs.put(fieldName, v);
      }
    } catch (Exception ignore) {

    }

    return attrs;
  }
}

FeildHandler

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.ANNOTATION_TYPE})
public @interface FieldHandler {
  Class<? extends TypeHandler> value();

  String field() default "";
}

TypeHandler

public interface TypeHandler {

  /**
   * 提供类型转换
   *
   * @param object
   * @return
   */
  <T> T get(Object object);

  <T> Object set(T t);
}

public class JsonTypeHandler implements TypeHandler {
  private final Class<?> type;

  public JsonTypeHandler(Class<?> type) {
    if (log.isTraceEnabled()) {
      log.trace("FastjsonTypeHandler(" + type + ")");
    }
    Assert.notNull(type, "Type argument cannot be null");
    this.type = type;
  }

  @Override
  public Object get(Object object) {
    if (object == null) {
      return null;
    }

    if (type.isInstance(Collection.class)) {
      return JSON.parseArray(Convert.toStr(object), type);
    }

    return JSON.parseObject(Convert.toStr(object), type);
  }

  @Override
  public Object set(Object o) {
    return JSON.toJSONString(o);
  }
}

验证

Model

public abstract class BaseClient<M extends BaseClient<M>> extends DolphinModel<M>
    implements IBean {

  @FieldHandler(value = JsonTypeHandler.class)
  private List<String> scopes;


  public List<String> getScopes() {
    return get("scopes");
  }

  public void setScopes(List<String> scopes) {
    set("scopes", scopes);
  }
}

public class Client extends BaseClient<Client> {

  /** serialVersionUID */
  @Serial private static final long serialVersionUID = -5183158681073671781L;
}

Test

{

  static Prop p;

  static void loadConfig() {
    if (p == null) {
      // 加载从左到右第一个被找到的配置文件
      p = PropKit.useFirstFound("bootstrap.txt", "");
    }
  }

  /** 抽取成独立的方法,便于 _Generator 中重用该方法,减少代码冗余 */
  public static HikariCpPlugin getHikariCpPlugin() {
    loadConfig();
    return new HikariCpPlugin(p.get("jdbcUrl"), p.get("user"), p.get("password").trim());
  }

  public static void main(String[] args) {
    HikariCpPlugin hikariCpPlugin = getHikariCpPlugin();
    ActiveRecordPlugin activeRecordPlugin = new ActiveRecordPlugin(hikariCpPlugin);
    MysqlDialect mysqlDialect = new MysqlDialect();
    mysqlDialect.setModelBuilder(DolphinModelBuilder.me);
    activeRecordPlugin.setDialect(mysqlDialect);
    activeRecordPlugin.addMapping("sys_client", OAuth2Client.class);
    hikariCpPlugin.start();
    activeRecordPlugin.start();
    
    Client client = new Client().dao().findById(1);
    List<String> scopes = client.getScopes();
    System.out.println(scopes);
    scopes.add("user.admin");
    client.setScopes(scopes);
    client.update();

    activeRecordPlugin.stop();
    hikariCpPlugin.stop();
  }
}


评论区

海哥

2023-03-17 09:59

不错 支持。

海哥

2023-03-17 10:00

热门分享

扫码入社