Как я объяснил в this article, очень легко сохранить объект JSON с помощью Hibernate.
Вам не нужно создавать все эти типы вручную, вы можете просто получить их через Maven Central, используя следующую зависимость:
<dependency>
<groupId>com.vladmihalcea</groupId>
<artifactId>hibernate-types-52</artifactId>
<version>${hibernate-types.version}</version>
</dependency>
Для получения дополнительной информации, ознакомьтесь с hibernate-types open-source project.
Теперь, чтобы объяснить, как все это работает.
Если у вас есть следующий объект:
@Entity(name = "Book")
@Table(name = "book")
@TypeDef(
name = "jsonb-node",
typeClass = JsonNodeBinaryType.class
)
public class Book {
@Id
@GeneratedValue
private Long id;
@NaturalId
private String isbn;
@Type(type = "jsonb-node")
@Column(columnDefinition = "jsonb")
private JsonNode properties;
//Getters and setters omitted for brevity
}
Обратите внимание две вещи, в фрагменте кода выше:
@TypeDef
используется, чтобы определить новый пользовательский Hibernate тип, jsonb-node
который обрабатывается JsonNodeBinaryType
- Атрибут
properties
имеет тип столбца jsonb
и отображается как Jackson JsonNode
The JsonNodeBinaryType
реализуется следующим образом:
public class JsonNodeBinaryType
extends AbstractSingleColumnStandardBasicType<JsonNode> {
public JsonNodeBinaryType() {
super(
JsonBinarySqlTypeDescriptor.INSTANCE,
JsonNodeTypeDescriptor.INSTANCE
);
}
public String getName() {
return "jsonb-node";
}
}
JsonBinarySqlTypeDescriptor
выглядит следующим образом:
public class JsonBinarySqlTypeDescriptor
extends AbstractJsonSqlTypeDescriptor {
public static final JsonBinarySqlTypeDescriptor INSTANCE =
new JsonBinarySqlTypeDescriptor();
@Override
public <X> ValueBinder<X> getBinder(
final JavaTypeDescriptor<X> javaTypeDescriptor
) {
return new BasicBinder<X>(javaTypeDescriptor, this) {
@Override
protected void doBind(
PreparedStatement st,
X value,
int index,
WrapperOptions options
) throws SQLException {
st.setObject(
index,
javaTypeDescriptor.unwrap(
value,
JsonNode.class,
options
),
getSqlType()
);
}
@Override
protected void doBind(
CallableStatement st,
X value,
String name,
WrapperOptions options
) throws SQLException {
st.setObject(
name,
javaTypeDescriptor.unwrap(
value,
JsonNode.class,
options
),
getSqlType()
);
}
};
}
}
Исходный код AbstractJsonSqlTypeDescriptor можно визуализировать в this article.
Теперь, JsonNodeTypeDescriptor
несет ответственность за преобразование JsonNode
в различные представления, которые могут быть использованы базовой JDBC Driver во время привязки параметров или извлечения из объекта JSON от лежащих в основе ResultSet
.
public class JsonNodeTypeDescriptor
extends AbstractTypeDescriptor<JsonNode> {
public static final JsonNodeTypeDescriptor INSTANCE =
new JsonNodeTypeDescriptor();
public JsonNodeTypeDescriptor() {
super(
JsonNode.class,
new MutableMutabilityPlan<JsonNode>() {
@Override
protected JsonNode deepCopyNotNull(
JsonNode value
) {
return JacksonUtil.clone(value);
}
}
);
}
@Override
public boolean areEqual(JsonNode one, JsonNode another) {
if (one == another) {
return true;
}
if (one == null || another == null) {
return false;
}
return
JacksonUtil.toJsonNode(
JacksonUtil.toString(one)
).equals(
JacksonUtil.toJsonNode(
JacksonUtil.toString(another)
)
);
}
@Override
public String toString(JsonNode value) {
return JacksonUtil.toString(value);
}
@Override
public JsonNode fromString(String string) {
return JacksonUtil.toJsonNode(string);
}
@SuppressWarnings({ "unchecked" })
@Override
public <X> X unwrap(
JsonNode value,
Class<X> type,
WrapperOptions options
) {
if (value == null) {
return null;
}
if (String.class.isAssignableFrom(type)) {
return (X) toString(value);
}
if (JsonNode.class.isAssignableFrom(type)) {
return (X) JacksonUtil.toJsonNode(toString(value));
}
throw unknownUnwrap(type);
}
@Override
public <X> JsonNode wrap(X value, WrapperOptions options) {
if (value == null) {
return null;
}
return fromString(value.toString());
}
}
Вот и все!
Теперь, если вы сохраните объект:
Book book = new Book();
book.setIsbn("978-9730228236");
book.setProperties(
JacksonUtil.toJsonNode(
"{" +
" \"title\": \"High-Performance Java Persistence\"," +
" \"author\": \"Vlad Mihalcea\"," +
" \"publisher\": \"Amazon\"," +
" \"price\": 44.99" +
"}"
)
);
entityManager.persist(book);
Hibernate будет генерировать следующий SQL заявление:
INSERT INTO
book
(
isbn,
properties,
id
)
VALUES
(
'978-9730228236',
'{"title":"High-Performance Java Persistence","author":"Vlad Mihalcea","publisher":"Amazon","price":44.99}',
1
)
И вы также можете загрузить его назад и изменить его:
Session session = entityManager.unwrap(Session.class);
Book book = session
.bySimpleNaturalId(Book.class)
.load("978-9730228236");
LOGGER.info("Book details: {}", book.getProperties());
book.setProperties(
JacksonUtil.toJsonNode(
"{" +
" \"title\": \"High-Performance Java Persistence\"," +
" \"author\": \"Vlad Mihalcea\"," +
" \"publisher\": \"Amazon\"," +
" \"price\": 44.99," +
" \"url\": \"https://www.amazon.com/High-Performance-Java-Persistence-Vlad-Mihalcea/dp/973022823X/\"" +
"}"
)
);
Hibernate, принимающий каре из UPDATE
заявление для вас:
SELECT b.id AS id1_0_
FROM book b
WHERE b.isbn = '978-9730228236'
SELECT b.id AS id1_0_0_ ,
b.isbn AS isbn2_0_0_ ,
b.properties AS properti3_0_0_
FROM book b
WHERE b.id = 1
-- Book details: {"price":44.99,"title":"High-Performance Java Persistence","author":"Vlad Mihalcea","publisher":"Amazon"}
UPDATE
book
SET
properties = '{"title":"High-Performance Java Persistence","author":"Vlad Mihalcea","publisher":"Amazon","price":44.99,"url":"https://www.amazon.com/High-Performance-Java-Persistence-Vlad-Mihalcea/dp/973022823X/"}'
WHERE
id = 1
Весь код по GitHub.
"весь HashMap в одной записи" ?? Или весь хэш-файл в одной транзакции db? –
Я имел в виду одну запись. – Fawzan