MySQLの5.7とJOOQ 3.5.2を使用して、私は次のことを達成しようとしています...
MySQLは、より大きな文書内のプロパティのパスターゲットと操作を可能にJSON関数のセットを持っています。
私は、この使用してJOOQを利用して抽象化をしようとしています。私は、変更を追跡しJSONシリアライズ可能な文書モデルを作成することから始め、その後JOOQカスタムを実装しBinding
、それのために。
このバインディングでは、私は更新された列の修飾名またはエイリアスを除いてこれらのMySQL JSON関数の呼び出しを生成するために必要なすべての状態情報を持っています。この名前を参照するには、インプレース既存のJSONドキュメントを更新するために必要です。
私は*からこの名前にアクセスする方法を見つけることができなかったContext
結合界面で利用可能なタイプを。
私が導入を検討されているVisitListener
これらのフィールド名をキャプチャして、それらを渡すためにScope
カスタムデータマップが、そのオプションは非常に壊れやすいようです。
フィールドの名前またはエイリアスビーイングへのアクセスを得るための最善の方法は、私のバインディング実装内でどのように対処してのでしょうか?
--edit--
OK、ここに私の目標を明確に支援するために、以下のDDLを取ります:
create table widget (
widget_id bigint(20) NOT NULL,
jm_data json DEFAULT NULL,
primary key (widget_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
今度は、jm_dataは、のJSON表現を保持すると仮定しましょうjava.util.Map<String,String>
。このJOOQについて(ジャクソンを使用して、この場合には)バインディングカスタムデータ型を実装し、登録することにより、非常に素晴らしい拡張APIを提供します。
public class MySQLJSONJacksonMapBinding implements Binding<Object, Map<String, String>> {
private static final ObjectMapper mapper = new ObjectMapper();
// The converter does all the work
@Override
public Converter<Object, Map<String, String>> converter() {
return new Converter<Object, Map<String, String>>() {
@Override
public Map<String, String> from(final Object t) {
try {
return t == null ? null
: mapper.readValue(t.toString(),
new TypeReference<Map<String, String>>() {
});
} catch (final IOException e) {
throw new RuntimeException(e);
}
}
@Override
public Object to(final Map<String, String> u) {
try {
return u == null ? null
: mapper.writer().writeValueAsString(u);
} catch (final JsonProcessingException e) {
throw new RuntimeException(e);
}
}
@Override
public Class<Object> fromType() {
return Object.class;
}
@Override
public Class toType() {
return Map.class;
}
};
}
// Rending a bind variable for the binding context's value and casting it to the json type
@Override
public void sql(final BindingSQLContext<Map<String, String>> ctx) throws SQLException {
// Depending on how you generate your SQL, you may need to explicitly distinguish
// between jOOQ generating bind variables or inlined literals. If so, use this check:
// ctx.render().paramType() == INLINED
ctx.render().visit(DSL.val(ctx.convert(converter()).value()));
}
// Registering VARCHAR types for JDBC CallableStatement OUT parameters
@Override
public void register(final BindingRegisterContext<Map<String, String>> ctx)
throws SQLException {
ctx.statement().registerOutParameter(ctx.index(), Types.VARCHAR);
}
// Converting the JsonElement to a String value and setting that on a JDBC PreparedStatement
@Override
public void set(final BindingSetStatementContext<Map<String, String>> ctx) throws SQLException {
ctx.statement().setString(ctx.index(),
Objects.toString(ctx.convert(converter()).value(), null));
}
// Getting a String value from a JDBC ResultSet and converting that to a Map
@Override
public void get(final BindingGetResultSetContext<Map<String, String>> ctx) throws SQLException {
ctx.convert(converter()).value(ctx.resultSet().getString(ctx.index()));
}
// Getting a String value from a JDBC CallableStatement and converting that to a Map
@Override
public void get(final BindingGetStatementContext<Map<String, String>> ctx) throws SQLException {
ctx.convert(converter()).value(ctx.statement().getString(ctx.index()));
}
// Setting a value on a JDBC SQLOutput (useful for Oracle OBJECT types)
@Override
public void set(final BindingSetSQLOutputContext<Map<String, String>> ctx) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
// Getting a value from a JDBC SQLInput (useful for Oracle OBJECT types)
@Override
public void get(final BindingGetSQLInputContext<Map<String, String>> ctx) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
}
...この実装はそのようなコード-GENでビルド時に添付されています。
<customTypes>
<customType>
<name>JsonMap</name>
<type>java.util.Map<String,String></type>
<binding>com.orbiz.jooq.bindings.MySQLJSONJacksonMapBinding</binding>
</customType>
</customTypes>
<forcedTypes>
<forcedType>
<name>JsonMap</name>
<expression>jm_.*</expression>
<types>json</types>
</forcedType>
</forcedTypes>
...ので、これに代えて、我々は我々のアプリケーションコードで操作することができます素敵な、強く型付けされたJavaの地図を持っています。しかしバインディング実装は、うまくそれは常に、単一のマップエントリは、挿入、更新、または削除された場合でも、JSONの列にマップ全体の内容を書き込みます。この実装は、MySQLの扱いJSON
通常のような列VARCHAR
の列を。
このアプローチは、使用状況に応じて、意義の変化の二つの問題を提起します。
- エントリの何千と大きい地図の一部のみを更新すると副作用として不要SQLワイヤトラフィックを生成します。
- マップの内容は、ユーザ編集可能であり、同時に内容を編集する複数のユーザーが存在する場合、1の変化は、彼らが非競合している場合でも、他によって上書きされることがあります。
MySQLの5.7は、JSONデータ型、およびSQLで文書を操作するための多くの機能を導入しました。これらの機能は、単一のプロパティの対象とアップデートを可能に、JSON文書の内容に対処することを可能にします。...私たちの例を続けます。
insert into DEV.widget (widget_id, jm_data)
values (1, '{"key0":"val0","key1":"val1","key2":"val2"}');
...私は「updated_value1」を等しくし、レコードの更新呼び出すJava地図「キー1」の値を変更した場合、実装は次のようにSQLを生成するバインディング上記:
update DEV.widget
set DEV.widget.jm_data = '{"key0":"val0","key1":"updated_value1","key2":"val2"}'
where DEV.widget.widget_id = 1;
...予告全体JSON文字列が更新されています。MySQLは使用して、より効率的にこれを扱うことができるjson_set
機能を:
update DEV.widget
set DEV.widget.jm_data = json_set( DEV.widget.jm_data, '$."key1"', 'updated_value1' )
where DEV.widget.widget_id = 1;
私はこのようなSQLを生成したいのであれば、私はそれが更新されるまで、それは最初にDBから読み込まれたときから、私の地図に加えられた変更の最初のキープトラックに必要です。次に、この変更情報を使用して、私はへの呼び出しを生成することができjson_set
、私が代わりにのみ変更されたプロパティを更新することができます機能を。
最後に、私の実際の質問になって。あなたは、私が生成したいSQLに気づくでしょう、更新された列の値は、列自体への参照が含まれていますjson_set( DEV.widget.jm_data, ...
。この列(またはエイリアス)の名前は、バインディングAPIが利用可能ではないようです。私が私の中から更新されているエイリアスの列の名前を特定する方法Binding
の実装は?
あなたのBinding
実装では、この問題に対する解決策を探すために間違った場所です。あなたは本当に何とか魔法の本を知っているにあなたの列の結合変更したくないjson_set
JSONデータではなく、完全に置き換えるの増分更新を行う機能、。あなたが使用する場合UpdatableRecord.store()
(あなたが使用しているように見えるもの)いずれかのために、期待はあるRecord.field(x)
データベース行の内容を反映するために、正確ではなく、デルタを。もちろん、あなたは可能性で似たような実装sql()
あなたの結合の方法を、権利を取得することは非常に困難であろうとバインディングは、すべてのユースケースには適用できないでしょう。
したがって、あなたが達成したいものを行うためには、単に明示的に書くUPDATE
使っjOOQのAPIを強化、jOOQで文をプレーンなSQLのテンプレートを。
// Assuming this static import
import static org.jooq.impl.DSL.*;
public static Field<Map<String, String>> jsonSet(
Field<Map<String, String>> field,
String key,
String value
) {
return field("json_set({0}, {1}, {2})", field.getDataType(), field, inline(key), val(value));
}
その後、あなたのライブラリーのメソッドを使用します。
using(configuration)
.update(WIDGET)
.set(WIDGET.JM_DATA, jsonSet(WIDGET.JM_DATA, "$.\"key1\"", "updated_value1"))
.where(WIDGET.WIDGET_ID.eq(1))
.execute();
これも繰り返しになっている場合は、私は必ずあなたはあなたのいくつかのAPIに同様の共通部分をくくり出すことができています。