英文:
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 = "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;
}
Additional table for storing categories that should be returned as List:
@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;
}
Additional table for storing Payment Methods that should be returned as List:
@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;
}
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<Integer> categories;
private List<String> paymentMethods;
}
答案1
得分: 2
如果根据您的评论需要使用 HQL 查询您的信息,以下是一个很好的处理方法。
首先,修改您的 Product 实体,包括与 ProductCategory 和 ProductPaymentMethods 的关系,类似这样:
@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
}
对 ProductCategory 和 ProductPaymentMethods 进行修改以适应实体关系:
@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
这将为您提供 userId 为 1 的产品列表,其中包含所有关联的类别和付款方式。
实体和 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 = "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
}
Modify both ProductCategory and ProductPaymentMethods to accommodate the entities relationship:
@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;
// Please, feel free to change the insertable and updatable attributes to
// fit your needs
@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;
// 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 = "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;
}
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<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.");
答案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 = "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();
// the below is TypeHandler implementation
@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);
}
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。


评论