说明
平时有些表字段不用检索只用存取还有扩展需求,于是会使用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(); } }