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