使用带有JOIN的查询填充DTO

huangapple go评论69阅读模式
英文:

Populate DTO using query with JOIN

问题

我有这个主要的 Product 表:

@Table(name = "product")
public class Product implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id", unique = true, updatable = false, nullable = false)
    private int id;

    @Column(name = "user_id", length = 20)
    private Integer userId;

    @Column(name = "title", length = 75)
    private String title;

    @Column(name = "meta_title", length = 100)
    private String metaTitle;

    @Column(name = "status", length = 100)
    private String status;
}

附加的用于存储应作为列表返回的类别的表:

@Table(name = "product_category")
public class ProductCategory implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id", unique = true, updatable = false, nullable = false)
    private int id;

    @Column(name = "product_id", length = 4)
    private Integer productId;

    @Column(name = "category_id", length = 20)
    private Integer categoryId;

}

附加的用于存储应作为列表返回的付款方式的表:

@Table(name = "product_payment_methods")
public class ProductPaymentMethods implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id", unique = true, updatable = false, nullable = false)
    private int id;

    @Column(name = "product_id", length = 20)
    private Integer productId;

    @Column(name = "payment_methods", length = 20000)
    private String paymentMethods;
}

我想要返回类似于这样的结果:

id | title | categoryId | paymentMethods |
1  | test  | 34, 43     | 345, 7, 5     |
5  | test2 | 64, 5, 3   | 654, 3, 5     |

我尝试了这个:

SELECT *
FROM Product
INNER JOIN product_category ON Product.id = product_category.productId
INNER JOIN product_payment_methods ON Product.id = product_payment_methods.productId
WHERE userId = 1

怎样才能正确地填充这个 DTO 呢?

public class ProductFullDTO {
   private int id;

   private Integer userId;

   private List<Integer> categories;

   private List<String> paymentMethods;
}
英文:

I have this main Product table:

@Table(name = &quot;product&quot;)
public class Product implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = &quot;id&quot;, unique = true, updatable = false, nullable = false)
    private int id;

    @Column(name = &quot;user_id&quot;, length = 20)
    private Integer userId;

    @Column(name = &quot;title&quot;, length = 75)
    private String title;

    @Column(name = &quot;meta_title&quot;, length = 100)
    private String metaTitle;

    @Column(name = &quot;status&quot;, length = 100)
    private String status;
}

Additional table for storing categories that should be returned as List:

@Table(name = &quot;product_category&quot;)
public class ProductCategory implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = &quot;id&quot;, unique = true, updatable = false, nullable = false)
    private int id;

    @Column(name = &quot;product_id&quot;, length = 4)
    private Integer productId;

    @Column(name = &quot;category_id&quot;, length = 20)
    private Integer categoryId;

}

Additional table for storing Payment Methods that should be returned as List:

@Table(name = &quot;product_payment_methods&quot;)
public class ProductPaymentMethods implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = &quot;id&quot;, unique = true, updatable = false, nullable = false)
    private int id;

    @Column(name = &quot;product_id&quot;, length = 20)
    private Integer productId;

    @Column(name = &quot;payment_methods&quot;, length = 20000)
    private String paymentMethods;
}

I want to return a result like this:

id | title | categoryId | paymentMethods |
1  | test  |    34, 43  |    345, 7, 5   |
5  | test2 |    64,5, 3 |    654, 3, 5   |

I tried this:

SELECT *
FROM Product
INNER JOIN product_category ON Product.id = product_category.productId
INNER JOIN product_payment_methods ON Product.id = product_payment_methods.productId
WHERE userId = 1

What is the proper way to populate this DTO?

public class ProductFullDTO {
   private int id;

   private Integer userId;

   private List&lt;Integer&gt; categories;

   private List&lt;String&gt; paymentMethods;
}

答案1

得分: 2

如果根据您的评论需要使用 HQL 查询您的信息,以下是一个很好的处理方法。

首先,修改您的 Product 实体,包括与 ProductCategoryProductPaymentMethods 的关系,类似这样:

@Table(name = "product")
public class Product implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id", unique = true, updatable = false, nullable = false)
    private int id;

    @Column(name = "user_id", length = 20)
    private Integer userId;

    @Column(name = "title", length = 75)
    private String title;

    @Column(name = "meta_title", length = 100)
    private String metaTitle;

    @Column(name = "status", length = 100)
    private String status;

    @OneToMany(mappedBy = "product", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<ProductCategory> categories;

    @OneToMany(mappedBy = "product", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<ProductPaymentMethods> paymentMethods;

    // Setters and getters, omitted for brevity
}

ProductCategoryProductPaymentMethods 进行修改以适应实体关系:

@Table(name = "product_category")
public class ProductCategory implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id", unique = true, updatable = false, nullable = false)
    private int id;

    // 请随意更改 insertable 和 updatable 属性以满足您的需求
    @Column(name = "product_id", length = 4, insertable = false, updatable = false)
    private Integer productId;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "product_id")
    private Product product;

    @Column(name = "category_id", length = 20)
    private Integer categoryId;

    // Setters and getters, omitted for brevity
}

@Table(name = "product_payment_methods")
public class ProductPaymentMethods implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id", unique = true, updatable = false, nullable = false)
    private int id;

    // 请随意更改 insertable 和 updatable 属性以满足您的需求。另外,为什么这里长度为 20 而不是 4?
    @Column(name = "product_id", length = 20, insertable = false, updatable = false)
    private Integer productId;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "product_id")
    private Product product;

    @Column(name = "payment_methods", length = 20000)
    private String paymentMethods;
}

有了这个设置,如您在Hibernate 文档中所见 - 这是一个旧的 Hibernate 版本,但在今天仍然是正确的 - 您可以使用联接获取所需的信息:

"fetch" 联接允许将关联或值的集合与其父对象一起初始化,使用单个 select。这在集合的情况下特别有用。

对于您的示例,考虑以下 HQL(假设外连接语义,根据需要进行修改):

select product
from Product as product
     left join fetch product.categories
     left join fetch product.paymentMethods
where product.userId = 1

这将为您提供 userId1 的产品列表,其中包含所有关联的类别和付款方式。

实体和 DTO 之间的转换应该很简单:

Session session = ...
List<Product> products = session.createQuery(
    "select product " +
    "from Product as product " +
    "     left join fetch product.categories " +
    "     left join fetch product.paymentMethods " +
    "where product.userId = :userId", Product.class)
.setParameter("userId", 1)
.getResultList();

List<ProductFullDTO> productFullDTOs = null;
if (products != null) {
  productFullDTOs = products.stream()
      .map((product -> {
        ProductFullDTO productFullDTO = new ProductFullDTO();
        productFullDTO.setId(product.getId());
        productFullDTO.setUserId(product.getUserId());
        
        List<ProductCategory> categories = product.getCategories();
        if (categories != null) {
          List<Integer> categoriesIds = categories.stream()
              .map(ProductCategory::getCategoryId)
              .collect(Collectors.toList())
          ;
          
          productFullDTO.setCategories(categoriesIds);
        }

        List<ProductPaymentMethods> paymentMethods = product.getPaymentMethods();
        if (paymentMethods != null) {
          List<String> paymentMethodsIds = paymentMethods.stream()
              .map(ProductPaymentMethods::getPaymentMethods)
              .collect(Collectors.toList())
          ;

          productFullDTO.setPaymentMethods(paymentMethodsIds);
        }
        
        return productFullDTO;
      }))
      .collect(Collectors.toList())
  ;
}

System.out.println(productFullDTOs == null ? "No products found." : productFullDTOs.size() + " products found.");
英文:

If, as indicated in your comments, you need query your information with HQL a good way to proceed can be the following.

First, modify your Product entity an include relationships for both ProductCategory and ProductPaymentMethods, something like:

@Table(name = &quot;product&quot;)
public class Product implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = &quot;id&quot;, unique = true, updatable = false, nullable = false)
    private int id;

    @Column(name = &quot;user_id&quot;, length = 20)
    private Integer userId;

    @Column(name = &quot;title&quot;, length = 75)
    private String title;

    @Column(name = &quot;meta_title&quot;, length = 100)
    private String metaTitle;

    @Column(name = &quot;status&quot;, length = 100)
    private String status;

    @OneToMany(mappedBy = &quot;product&quot;, cascade = CascadeType.ALL, orphanRemoval = true)
    private List&lt;ProductCategory&gt; categories;


    @OneToMany(mappedBy = &quot;product&quot;, cascade = CascadeType.ALL, orphanRemoval = true)
    private List&lt; ProductPaymentMethods&gt; paymentMethods;

    // Setters and getters, omitted for brevity
}

Modify both ProductCategory and ProductPaymentMethods to accommodate the entities relationship:

@Table(name = &quot;product_category&quot;)
public class ProductCategory implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = &quot;id&quot;, unique = true, updatable = false, nullable = false)
    private int id;

    // Please, feel free to change the insertable and updatable attributes to 
    // fit your needs
    @Column(name = &quot;product_id&quot;, length = 4, insertable=false, updatable=false)
    private Integer productId;

    @ManyToOne(fetch= FetchType.LAZY)
    @JoinColumn(name=&quot;product_id&quot;)
    private Product product;

    @Column(name = &quot;category_id&quot;, length = 20)
    private Integer categoryId;

    // Setters and getters, omitted for brevity

}

@Table(name = &quot;product_payment_methods&quot;)
public class ProductPaymentMethods implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = &quot;id&quot;, unique = true, updatable = false, nullable = false)
    private int id;

    // Please, feel free to change the insertable and updatable attributes to 
    // fit your needs. By the way, why here the length is 20 and not 4?
    @Column(name = &quot;product_id&quot;, length = 20, insertable=false, updatable=false)
    private Integer productId;

    @ManyToOne(fetch= FetchType.LAZY)
    @JoinColumn(name=&quot;product_id&quot;)
    private Product product;

    @Column(name = &quot;payment_methods&quot;, length = 20000)
    private String paymentMethods;
}

With this setup, as you can see in the Hibernate documentation - it is for an old Hibernate version, but it is correct today, you can use fetch joins to obtain the required information:

> A "fetch" join allows associations or collections of values to be initialized along with their parent objects using a single select. This is particularly useful in the case of a collection.

For your example, consider the following HQL (assume outer join semantics, modify it as appropriate):

select product
from Product as product
     left join fetch product.categories
     left join fetch product.paymentMethods
where product.userId = 1

This will provide you the list of products for userId 1, with all the associated references to categories and payment methods initialized.

The conversion between the entity and the DTO should be straightforward:

Session session = ...
List&lt;Product&gt; products = session.createQuery(
    &quot;select product &quot; +
    &quot;from Product as product &quot; +
    &quot;     left join fetch product.categories &quot; +
    &quot;     left join fetch product.paymentMethods &quot; +
    &quot;where product.userId = :userId&quot;, Product.class)
.setParameter( &quot;userId&quot;, 1)
.getResultList();

List&lt;ProductFullDTO&gt; productFullDTOs = null;
if (products != null) {
  productFullDTOs = products.stream()
      .map((product -&gt; {
        ProductFullDTO productFullDTO = new ProductFullDTO();
        productFullDTO.setId(product.getId());
        productFullDTO.setUserId(product.getUserId());
        
        List&lt;ProductCategory&gt; categories = product.getCategories();
        if (categories != null) {
          List&lt;Integer&gt; categoriesIds = categories.stream()
              .map(ProductCategory::getCategoryId)
              .collect(Collectors.toList())
          ;
          
          productFullDTO.setCategories(categoriesIds);
        }

        List&lt;ProductPaymentMethods&gt; paymentMethods = product.getPaymentMethods();
        if (paymentMethods != null) {
          List&lt;String&gt; paymentMethodsIds = paymentMethods.stream()
              .map(ProductPaymentMethods::getPaymentMethods)
              .collect(Collectors.toList())
          ;

          productFullDTO.setPaymentMethods(paymentMethodsIds);
        }
        
        return productFullDTO;
      }))
      .collect(Collectors.toList())
  ;
}

System.out.println(productFullDTOs == null ? &quot;No products found.&quot; : productFullDTOs.size() + &quot; products found.&quot;);

答案2

得分: 1

你应该使用TypeHandler来完成这项任务。我只是以paymentMethods作为示例。

    @Results({
            @Result(column = "product.id", property = "id"),
            @Result(column = "user_id", property = "userId"),
            @Result(column = "category_id", property = "categories"),
            @Result(column = "payment_methods", property = "paymentMethods", typeHandler = StrListTypeHandler.class),
    })
    @Select("SELECT * FROM Product INNER JOIN product_category ON Product.id = product_category.productId "
        + " INNER JOIN product_payment_methods ON Product.id = product_payment_methods.productId "
        + " WHERE userId = 1")
    List<ProductFullDTO> getProduct();

// 下面是TypeHandler的实现
@Component
public class StrListTypeHandler implements TypeHandler<List<String>> {
    @Override
    public void setParameter(PreparedStatement preparedStatement, int i, List<String> strings, JdbcType jdbcType) throws SQLException {
        StringBuffer sb = new StringBuffer();
        for (String s : strings) {
            sb.append(s).append(",");
        }
        preparedStatement.setString(i, sb.toString().substring(0, sb.toString().length() - 1));
    }

    @Override
    public List<String> getResult(ResultSet resultSet, String s) throws SQLException {
        String[] arr = resultSet.getString(s).split(",");
        return Arrays.asList(arr);
    }

    @Override
    public List<String> getResult(ResultSet resultSet, int i) throws SQLException {
        String[] arr = resultSet.getString(i).split(",");
        return Arrays.asList(arr);
    }

    @Override
    public List<String> getResult(CallableStatement callableStatement, int i) throws SQLException {
        String[] arr = callableStatement.getString(i).split(",");
        return Arrays.asList(arr);
    }
}
英文:

You should use TypeHandler to finish this job. I just give paymentMethods as example.

    @Results({
@Result(column = &quot;product.id&quot;, property = &quot;id&quot;),
@Result(column = &quot;user_id&quot;, property = &quot;userId&quot;),
@Result(column = &quot;category_id&quot;, property = &quot;categories&quot;),
@Result(column = &quot;payment_methods&quot;, property = &quot;paymentMethods&quot; ,typeHandler= StrListTypeHandler.class),
})
@Select(&quot;SELECT * FROM Product INNER JOIN product_category ON Product.id = product_category.productId &quot;
+ &quot; INNER JOIN product_payment_methods ON Product.id = product_payment_methods.productId &quot;
+ &quot; WHERE userId = 1&quot;)
List&lt;ProductFullDTO&gt; getProduct();
// the below is TypeHandler implementation
@Component
public class StrListTypeHandler implements TypeHandler&lt;List&lt;String&gt;&gt; {
@Override
public void setParameter(PreparedStatement preparedStatement, int i, List&lt;String&gt; strings, JdbcType jdbcType) throws SQLException {
StringBuffer sb = new StringBuffer();
for (String s : strings) {
sb.append(s).append(&quot;,&quot;);
}
preparedStatement.setString(i, sb.toString().substring(0, sb.toString().length() - 1));
}
@Override
public List&lt;String&gt; getResult(ResultSet resultSet, String s) throws SQLException {
String[] arr = resultSet.getString(s).split(&quot;,&quot;);
return Arrays.asList(arr);
}
@Override
public List&lt;String&gt; getResult(ResultSet resultSet, int i) throws SQLException {
String[] arr = resultSet.getString(i).split(&quot;,&quot;);
return Arrays.asList(arr);
}
@Override
public List&lt;String&gt; getResult(CallableStatement callableStatement, int i) throws SQLException {
String[] arr = callableStatement.getString(i).split(&quot;,&quot;);
return Arrays.asList(arr);
}
}

huangapple
  • 本文由 发表于 2020年10月9日 05:17:16
  • 转载请务必保留本文链接:https://go.coder-hub.com/64270734.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定