英文:
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);
}
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论