如何使用spring data jpa查询jsonb列?
Posted
技术标签:
【中文标题】如何使用spring data jpa查询jsonb列?【英文标题】:How do I use spring data jpa to query jsonb column? 【发布时间】:2017-10-09 13:45:39 【问题描述】:我在获取这个针对 postgres 9.4 实例的本机查询时遇到问题。
我的仓库有一个方法:
@Query(value = "SELECT t.* " +
"FROM my_table t " +
"WHERE t.field_1 = ?1 " +
"AND t.field_2 = 1 " +
"AND t.field_3 IN ?2 " +
"AND t.jsonb_field #>> 'key,subkey' = ?3",
nativeQuery = true)
List<Entity> getEntities(String field1Value,
Collection<Integer> field3Values,
String jsonbFieldValue);
但日志显示:
SELECT t.* FROM my_table t
WHERE t.field_1 = ?1
AND t.field_2 = 1
AND t.field_3 IN ?2
AND t.jsonb_field ? 'key,subkey' = ?3
我得到了这个例外:
内部异常:org.postgresql.util.PSQLException:无值 为参数 2 指定。
我在方法调用之前直接记录了参数,它们都是提供的。
我不确定为什么#>>
在日志中显示?
。我需要逃避#>>
吗?我需要为IN
格式化集合吗?我需要转义 json 路径吗?
当我直接对数据库执行查询时,它可以工作。示例:
SELECT *
FROM my_table t
WHERE t.field_1 = 'xxxx'
AND t.field_2 = 1
AND t.field_3 IN (13)
AND t.jsonb_field #>> 'key,subkey' = 'value'
【问题讨论】:
你能解决这个问题吗?如果是其中一个答案,你能选择一个吗?如果没有,您能否发布您用作答案的解决方案 @JoeG 看起来它不适用于 eclipselink。我最终走了一条不同的路线。 【参考方案1】:我发现 spring data 中的 Specification api 非常有用。
假设我们有一个名称为 Product
的实体和一个名称为 title
的 JSON(B) 类型的属性。
我假设此属性包含不同语言的产品标题。例如:"EN":"Multicolor LED light", "EL":"Πολύχρωμο LED φώς"
.
下面的源代码通过作为参数传递的标题和语言环境找到一个(或更多,如果它不是唯一字段)产品。
@Repository
public interface ProductRepository extends JpaRepository<Product, Integer>, JpaSpecificationExecutor<Product>
public class ProductSpecification implements Specification<Product>
private String locale;
private String titleToSearch;
public ProductSpecification(String locale, String titleToSearch)
this.locale = locale;
this.titleToSearch = titleToSearch;
@Override
public Predicate toPredicate(Root<Product> root, CriteriaQuery<?> query, CriteriaBuilder builder)
return builder.equal(builder.function("jsonb_extract_path_text", String.class, root.<String>get("title"), builder.literal(this.locale)), this.titleToSearch);
@Service
public class ProductService
@Autowired
private ProductRepository productRepository;
public List<Product> findByTitle(String locale, String titleToSearch)
ProductSpecification cs = new ProductSpecification(locale, titleToSearch);
return productRepository.find(cs);
// Or using lambda expression - without the need of ProductSpecification class.
// return productRepository.find((Root<ProductCategory> root, CriteriaQuery<?> query, CriteriaBuilder builder) ->
// return builder.equal(builder.function("jsonb_extract_path_text", String.class, root.<String>get("title"), builder.literal(locale)), titleToSearch);
// );
您可以找到另一个关于如何使用 Spring Data here 的答案。 希望对您有所帮助。
【讨论】:
你能创建一个号码的查询吗?见this 它工作得很好,但我想用对象(不仅仅是字符串)搜索,所以我将return builder.equal(builder.function("jsonb_extract_path_text", String.class, root.<String>get("title"), builder.literal(locale)), titleToSearch);
更改为return builder.equal(builder.function("jsonb_extract_path_text", Object.class, root.<String>get("title"), builder.literal(locale)), titleToSearch);
并抛出异常:o.h.e.jdbc.spi.SqlExceptionHelper.logExceptions - ERROR: operator does not exist
,我该如何处理?
【参考方案2】:
如果由于某种原因将运算符转换为问号,那么您应该尝试使用该函数。您可以在 psql 控制台中使用\doS+ #>>
找到相应的函数。它告诉我们调用的函数是jsonb_extract_path_text
。这将使您的查询:
@Query(value = "SELECT t.* " +
"FROM my_table t " +
"WHERE t.field_1 = ?1 " +
"AND t.field_2 = 1 " +
"AND t.field_3 IN ?2 " +
"AND jsonb_extract_path_text(t.jsonb_field, 'key,subkey') = ?3",
nativeQuery = true)
【讨论】:
我想我越来越近了。显然,本机查询不允许使用列表参数上的 IN 子句。是否可以在没有本机查询的情况下使用 jpa/jpql 进行 jsonb 操作? 嗯?他们使用 Hibernate 中的本机查询。您使用的是 EclipseLink 还是不太流行的实现之一?无论如何,在 Postgres 中,表达式field IN (val1,val2)
被转换成等价的 field = ANY('val1,val1')
。如果你将它作为一个数组传递给field = ANY ( ? )
,并带有括号,它的工作原理是一样的。 NOT IN
的等效项是 field <> ALL( ? )
。这当然会迫使您自己创建数组的字符串表示形式,确保正确转义任何字符串。
是的,我们正在使用 Eclipselink。我不确定为什么这比 Hibernate 更受欢迎。你会推荐什么?无论如何,一旦我更改为jsonb_extract_path_text
,查询就会失败并出现新异常。当我搜索异常时,我发现了***.com/a/6279110/918608 和***.com/a/41564377/918608。看来我的列表中必须有恒定数量的值,并且我必须在我的本机查询中枚举它们。不是这样吗?
这很丑 :-),很高兴看到我们在使用 Spring Data 在 MongoDB 中使用 JSON 时看到的类似 @Document 注释。这实际上对通过 Spring Data Rest 快速构建 REST API 产生了重大影响。【参考方案3】:
也许这是一个老话题,但我在这里使用spring规范按字段搜索jsonb。
如果您想使用“LIKE”进行搜索,您需要使用以下代码创建 like 析取:
final Predicate likeSearch = cb.disjunction();
之后,假设您的对象中有 jsonb 字段,即地址,地址有 5 个字段。要在所有这些字段中搜索,您需要为所有字段添加“LIKE”表达式:
for (String field : ADDRESS_SEARCH_FIELDS)
likeSearch.getExpressions().add(cb.like(cb.lower(cb.function("json_extract_path_text", String.class,
root.get("address"), cb.literal(field))), %searchKey%));
其中cb 是相同的criteriaBuilder。 %searchKey% 是您要在地址字段中搜索的内容。
希望这会有所帮助。
【讨论】:
我有 Json 对象数组,其中每个 json 对象都有“id”键我想过滤每个 id,我正在尝试这样但没有得到结果 likeSearch.getExpressions().add(cb .like(cb.lower(cb.function("json_extract_path_text", String.class,root.get("testList"), cb.literal("*.id"))),,"%" + "1234abc" + " %"));任何帮助都会得到帮助,谢谢【参考方案4】:您还可以使用FUNC
JPQL 键来调用自定义函数,而不是使用本机查询。
像这样的,
@Query(value = "SELECT t FROM my_table t "
+ "WHERE t.field_1=:field_1 AND t.field_2=1 AND t.field_3 IN :field_3 "
+ "AND FUNC('jsonb_extract_path_text', 'key', 'subkey')=:value")
List<Entity> getEntities(@Param("field_1") String field_1, @Param("field_3") Collection<Integer> field_3, @Param("value") String value);
【讨论】:
如果您依赖于专有的jsonb_extract_path_text
函数,为什么不再使用本机查询很重要?【参考方案5】:
我建议不要遵循这种方式,我更喜欢遵循通用的 CRUD 方式(对于 Spring Data Rest maven 插件,也可以使用 StrongLoop Loopback 的高级自动生成的 DAO 方法,但它目前只是实验性的)。 但是有了这个 JSON,现在该怎么办......我正在通过 @Document 注释在 Spring Data 中寻找类似于 MongoDB JSON 处理的东西,但是这还不可用。但是还有其他方法:-)
一般来说,它是关于实现你的 JSON 用户类型(UserType 接口):
public class YourJSONBType implements UserType
最后,您需要使用您实现的用户类型的规范来增强您的 JPA 类:
@Entity
@Data
@AllArgsConstructor
@NoArgsConstructor
@TypeDef(name = "JsonbType", typeClass = YourJSONBType.class)
public class Person
@Id
@GeneratedValue
private Long id;
@Column(columnDefinition = "jsonb")
@Type(type = "JsonbType")
private Map<String,Object> info;
在此处查看另一篇相关文章: Mapping PostgreSQL JSON column to Hibernate value type
完整的实现示例可在此处获得:
https://github.com/nzhong/spring-data-jpa-postgresql-json https://github.com/mariusneo/postgres-json-jpa此处提供了类似但略有不同的示例: http://www.wisely.top/2017/06/27/spring-data-jpa-postgresql-jsonb/?d=1
【讨论】:
【参考方案6】:分享我自己的例子,因为我努力分解提供的答案以满足我的特定需求。希望这对其他人有帮助。我的示例在 groovy 中,并且正在与 postgres SQL 数据库集成。这是一个简单示例,说明如何在名为“name”的字段上搜索 JSON 列并使用分页。
JSON 支持类
@TypeDefs([@TypeDef(name = "jsonb", typeClass = JsonBinaryType.class)])
@MappedSuperclass
class JSONSupport
实体类:
@Entity
@Table(name = "my_table")
class MyEntity extends JSONSupport
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
Long pk
@Type(type = "jsonb")
@Column(columnDefinition = "jsonb")
String jsonData
规范类
class NameSpecification implements Specification<MyEntity>
private final String name
PhoneNumberSpecification(String name)
this.name = name
@Override
Predicate toPredicate(Root<ContactEntity> root, CriteriaQuery<?> query, CriteriaBuilder builder)
return builder.equals(
builder.function(
"jsonb_extract_path_text",
String.class,
root.<String>get("jsonData"),
builder.literal("name")
),
this.name
)
存储库
interface MyEntityRepository extends PagingAndSortingRepository<MyEntity, Long>, JpaSpecificationExecutor<MyEntity>
用法
@Service
class MyEntityService
private final MyEntityRepository repo
MyEntityService(MyEntityRepository repo)
this.repo = repo
Page<MyEntity> getEntitiesByNameAndPage(String name, Integer page, Integer pageSize)
PageRequest pageRequest = PageRequest.of(page, pageSize, Sort.by("pk"))
NameSpecification spec = new NameSpecification(name)
return repo.findAll(spec, pageRequest)
【讨论】:
以上是关于如何使用spring data jpa查询jsonb列?的主要内容,如果未能解决你的问题,请参考以下文章
序列化表单为json对象,datagrid带额外参提交一次查询 后台用Spring data JPA 实现带条件的分页查询
Spring Data JPA:查询ManyToMany,如何从映射类中获取数据?