[英]Could not write JSON: failed to lazily initialize a collection of role: com.managem.model.Region.pays, could not initialize proxy - no Session
[英]How to fix this error: Could not write JSON: failed to lazily initialize a collection of role: com.cashregister.demo.model
我在尝试创建事务 object 然后返回事务列表时遇到问题。 我在调用 http://localhost:8080/transactions 时遇到的错误是:
{
"timestamp": "2020-08-13T14:24:11.113+0000",
"status": 500,
"error": "Internal Server Error",
"message": "Could not write JSON: failed to lazily initialize a collection of role: com.cashregister.demo.model.Transaction.items, could not initialize proxy - no Session; nested exception is com.fasterxml.jackson.databind.JsonMappingException: failed to lazily initialize a collection of role: com.cashregister.demo.model.Transaction.items, could not initialize proxy - no Session (through reference chain: java.util.HashMap[\"transactions\"]->java.util.ArrayList[0]->com.cashregister.demo.model.Transaction[\"items\"])",
"path": "/transactions"
}
我一直在寻找解决方案,但似乎没有一个能摆脱错误并返回。 有人可以帮助解决此问题,以便在获取所有交易时以以下格式返回响应:
{
"transaction": {
"id": 1,
"total": 46.44,
"customer": {
"id": 1,
"phoneNumber": "9416970394",
"lastName": "Weber",
"loyaltyNumber": "2484801419"
},
"items": [
// list of each item in the transaction
]
这是我的代码:
Item.java Model
@Entity
public class Item {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "transaction_id", nullable = false)
private Transaction transaction;
@NotNull
private double total;
@NotNull
@OneToOne
private Product product;
@NotNull
private int quantity;
// Getters and setters
}
Transaction.java Model
@Entity
public class Transaction {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private double total;
@ManyToOne
private Customer customer;
@OneToMany(mappedBy = "transaction", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private List<Item> items = new ArrayList<>();
// Getters and setters
}
TransactionDaoImpl.java 用于数据库访问
@Repository
public class TransactionDaoImpl implements TransactionDao {
@Autowired
private SessionFactory sessionFactory;
@Override
public List<Transaction> findAll() {
Session session = sessionFactory.openSession();
List<Transaction> transactions = session.createCriteria(Transaction.class).list();
session.close();
return transactions;
}
@Override
public Transaction findById(Long id) {
Session session = sessionFactory.openSession();
Transaction transaction = session.get(Transaction.class, id);
session.close();
return transaction;
}
@Override
public Long save(Transaction transaction) {
Session session = sessionFactory.openSession();
session.beginTransaction();
session.saveOrUpdate(transaction);
session.getTransaction().commit();
session.close();
return transaction.getId();
}
}
TransctionController.java
@RestController
@RequestMapping("/transactions")
public class TransactionController {
@Autowired
private TransactionService transactionService;
@Autowired
private CustomerService customerService;
@Autowired
private ProductService productService;
@Autowired
private ItemService itemService;
@RequestMapping(value = "")
public Map<String, List<Transaction>> listTransactions() {
Map<String, List<Transaction>> response = new HashMap<>();
List<Transaction> transactions = transactionService.findAll();
response.put("transactions", transactions);
return response;
}
@RequestMapping(value = "{transaction_id}")
public Map<String, Transaction> findTransactionById(@PathVariable Long transaction_id) {
Map<String, Transaction> response = new HashMap<>();
Transaction transaction = transactionService.findById(transaction_id);
response.put("transaction", transaction);
return response;
}
@RequestMapping(value = "", method = RequestMethod.POST)
public Map<String, Transaction> createTransaction(@RequestBody List<Product> products, @RequestParam("user_id") Long customerId) {
Map<String, Transaction> response = new HashMap<>();
Customer customer = customerService.findById(customerId);
// Get unique skus to get the product objects from the database (source of truth).
Set<String> skus = new HashSet<>();
for (final Product product : products) {
skus.add(product.getSku());
}
List<String> skusStrings = new ArrayList<String>();
for(String sku : skus) {
skusStrings.add(sku);
}
List<Product> productsFromDb = productService.findBySkus(skusStrings);
// Loop through products in payload to calculate quantity.
List<Item> items = new ArrayList<>();
Map<String, Integer> productQuantity = new HashMap<>();
for(Product product : products) {
if(productQuantity.containsKey(product.getSku())) {
productQuantity.put(product.getSku(), productQuantity.get(product.getSku()) + 1);
} else {
productQuantity.put(product.getSku(), 1);
}
}
// Calculate total cost
double total = 0;
for(Product product : productsFromDb) {
if(!customer.getLoyaltyNumber().isEmpty()) {
total += product.getDiscountPrice() * productQuantity.get(product.getSku());
} else {
total += product.getDefaultPrice() * productQuantity.get(product.getSku());
}
}
// Calculate item total and append to Items list
Map<String, Double> productTotal = new HashMap<>();
for(Product product : productsFromDb) {
if(!customer.getLoyaltyNumber().isEmpty()) {
productTotal.put(product.getSku(), productQuantity.get(product.getSku()) * product.getDiscountPrice());
} else {
productTotal.put(product.getSku(), productQuantity.get(product.getSku()) * product.getDefaultPrice());
}
}
Transaction t = new Transaction();
t.setTotal(total);
t.setCustomer(customer);
t.setItems(items);
Long transactionId = transactionService.save(t);
for(Product product : productsFromDb) {
Item item = new Item(t, productTotal.get(product.getSku()), product, productQuantity.get(product.getSku()));
itemService.save(item);
}
Transaction transaction = transactionService.findById(transactionId);
response.put("transaction", transaction);
return response;
}
}
您的 Transaction 实体与 Item 实体有关系。 因此,在您的 controller 中,您没有任何数据库 session 用于获取项目。 您必须将所有代码放在服务层中,然后将@Transactional
放在上面。 将spring.jpa.open-in-view
属性设置为 true。
这里的问题是Hibernate 的延迟加载。 这实际上是 JPA 规范,由 Hibernate 实现。 主要概念是不加载所有关联,从而提高性能。
为什么你面临这个问题:
如何解决问题:
@Transactional // This is required since you are not using Spring data Jpa Repository abstractions. Or else you can explicitly define the transaction boundary inside the method by opening the transaction and closing it.
@Override
public List<Transaction> findAll() {
Session session = sessionFactory.openSession();
List<Transaction> transactions = session.createCriteria(Transaction.class).list();
transactions.foreach(Transaction::getItems);
session.close();
return transactions;
}
但这会引起N+1 的问题。
主要是。 对于每个事务,每个事务将触发 N 个查询(N 是一个事务的项目数) - 因此将触发 K*N 个查询。
要解决 N+1 问题,您可以 go 与 Native 查询一起使用 Join 以通过一个查询获取所有数据。 由于加入,性能仍然会很慢,但比第一个选项好得多。 或者您可以使用Hibernate Annotation或 JPQL/HQL JOIN FETCH ,这实际上为您创建了 JOIN。
在没有 N+1 查询的情况下加载数据的另一个选项是@EntityGraphy
。 我没用过。 但是你可以在这里阅读更多关于它的信息
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.