【问题标题】:Build a JPQL on a BLOB with Spring Data JPA使用 Spring Data JPA 在 BLOB 上构建 JPQL
【发布时间】:2023-03-17 12:15:01
【问题描述】:

我正在使用一个外部服务,该服务发布到我的应用程序中的一个端点,我有一个@RequestBody CalloutRequest calloutRequest,我将请求正文映射到类CalloutRequest

作为 CalloutRequest 的一部分,有一个名为 userAttributes 的字段,在我的课堂上看起来像这样:

@Lob    
private HashMap<String, List<String>> userAttributes;

我用@Lob 对其进行了注释,因为我没有可以将其映射到的类,因为它的性质相当复杂。因此这个HashMap。以 JSON 格式显示 userAttributes 的示例:

  "userAttributes": {
    "lastName": [
      "Guy"
    ],
    "ExternalId": [
      "d9a59407-2bca-46aa-8641-3350fd95bcd1"
    ],
    "distinguishedName": [
      "CN=t2-contractor9,DC=company,DC=com"
    ],
    "employeeID": [],
    "userName": [
      "t2-contractor9@company.com"
    ],
    "groupNames": [
      "ALL USERS",
      "T2-Contractor@company.com"
    ],
    "firstName": [
      "T2-Contractor"
    ],
    "UserStore": [],
    "phone": [],
    "domain": [
      "company.com"
    ],
    "disabled": [
      "false"
    ],
    "email": [
      "t2-contractor9@company.com"
    ]
  }

我的挑战是我想为特定的用户名构建一个查询 - userAttributes.userName - 但鉴于这是存储为 BLOB 的事实,我无法弄清楚如何 - 或者如果可能的话 - 编写这样一个查询。
现在我只是使用 Spring Data JPA 和它的存储库实现,但我想知道这是否可以通过自定义 @Query 或使用 querydsl 来实现?

我的理解是否正确,如果我能找到一种方法将上述 HashMap 映射到一个实际的类中,我可以使用它来序列化/反序列化并在 CalloutRequest 和这个新类之间建立一个映射,我可以构建一个 JPQL将使用连接来实现我所追求的查询?

【问题讨论】:

    标签: hibernate jpa spring-data-jpa jpql querydsl


    【解决方案1】:

    如何在此处利用 AttributeConverter 实现:

    public class YourEntity {
      // ...
      @ElementCollection
      @Convert(name = "value", converter = StringListToStringConverter.class)
      private Map<String, List<String>> userAttributes;
    }
    

    StringListToStringConverter 的简单实现可能是:

    public class StringListToStringConverter 
           implements AttributeConverter<List<String>, String> {
        private static final String DELIMITER = "|";
    
        @Override
        public String converToDatabaseColumn(List<String> attributes) {
          if ( attributes == null || attributes.isEmpty() ) {
            return null;
          }
    
          StringBuilder sb = new StringBuilder();
          for ( Iterator<String> i = attribtues.iterator(); i.hasNext(); ) {
            sb.append( i.next() );
            if ( i.hasNext() ) {
              sb.append( DELIMITER );
            }
          }
          return sb.toString();
        }
    
        @Override 
        public List<String> convertToEntityAttribute(String data) {
          if ( data == null ) {
            return new ArrayList<String>();
          }
    
          List<String> values = Arrays.asList( StringHelper.split( DELIMITER, data ) );         
          return new ArrayList<String>( values );
        }
    }
    

    您显然必须处理输入流包含您的 DELIMITER 值的情况,但它可能会根据您的用例起作用。

    我相信您可以使用 HQL 编写您的查询,例如:

    SELECT e FROM YourEntity e JOIN e.userAttributes a 
    WHERE VALUE(a) = :mapValue
    

    【讨论】:

    • 这似乎工作得很好!我能够使用@ElementCollection@Convert 实现实体。但是,我最有可能无法使查询正常工作,因为我不完全理解 StringListToStringConverter 的实际作用。我以为我可以只将一个字符串(即用户名)传递给查询方法,但失败并显示“参数值与预期类型 [java.util.List] 不匹配” - 我需要提供什么才能让转换器理解我的方法?
    • 抱歉,我没有对此进行测试,因此您可能需要将查询调整为 WHERE VALUE(a) IN (:mapValues) 并将命名参数作为 List&lt;String&gt; 提供?
    • 这给了我一个:java.lang.ClassCastException: java.lang.String cannot be cast to java.util.List 来自 convertToDatabaseColumn 方法。我的 @Query 注释方法在我的存储库中如下所示:CalloutRequest findWhereUser(@Param("userName") List&lt;String&gt; userName);
    • 我无法让 HQL 或 JPQL 查询正常运行,但使用如下启用了 nativeQuery 的 @Query 方法,我可以进行常规 JOIN 工作:@Query(nativeQuery = true, value = "SELECT * FROM CALLOUT_REQUEST AS C JOIN CALLOUT_REQUEST_USER_ATTRIBUTES AS A ON C.ID = A.CALLOUT_REQUEST_ID WHERE A.USER_ATTRIBUTES = :userName ORDER BY C.ID") List&lt;CalloutRequest&gt; findWhereUser(@Param("userName") String userName);
    【解决方案2】:

    如果您使用 Hibernate 作为 JPA 实现并使用 PostgreSQL 作为 DB,请查看 hibernate-native-json 项目:

    示例

    您可以序列化一个类或一个 Map(在任何情况下都需要更动态的字段)。

    @Entity
    public class MyClass {
    
      @Type(type = "com.marvinformatics.hibernate.json.JsonListUserType")
      @Target(Label.class)
      private List<Label> labels = new ArrayList<Label>();
    
      @Type(type = "com.marvinformatics.hibernate.json.JsonUserType")
      private Map<String, String> extra;
    
    }
    

    这允许像这样的 HQL 查询:

    select
        json_text(i.label, 'value')
    from
        Item i
    where
        json_text(i.label, 'lang') = :lang
    

    祝你好运!

    【讨论】:

    • 不幸的是,我正在使用基于 H2 文件的数据库(小型项目),但我一定会在以后的项目中记住这一点!谢谢。
    猜你喜欢
    • 2017-11-28
    • 2015-12-23
    • 2015-10-06
    • 2019-05-03
    • 2016-08-08
    • 1970-01-01
    • 2011-12-25
    • 1970-01-01
    • 2013-02-27
    相关资源
    最近更新 更多