繁体   English   中英

如何修复此错误:无法写入 JSON:未能延迟初始化角色集合:com.cashregister.demo.model

[英]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 实现。 主要概念是不加载所有关联,从而提高性能。

为什么你面临这个问题:

  • 您已将 FETCH 策略定义为双方的 LAZY(此处,从交易到项目的关系很重要,因为您获取交易)
  • TransactionDaoImpl#findAll() 方法打开 Session 并获取记录。 Hibernate 根据延迟加载的概念,不会从数据库中获取关联的项目,而是创建代理。
  • 由于这些是代理对象,HttpMessageConverter 无法将这些对象转换为 JSON 表示。

如何解决问题:

  1. 您必须在项目关系上调用 get 访问器。
   @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 个查询。

  1. 要解决 N+1 问题,您可以 go 与 Native 查询一起使用 Join 以通过一个查询获取所有数据。 由于加入,性能仍然会很慢,但比第一个选项好得多。 或者您可以使用Hibernate Annotation或 JPQL/HQL JOIN FETCH ,这实际上为您创建了 JOIN。

  2. 在没有 N+1 查询的情况下加载数据的另一个选项是@EntityGraphy 我没用过。 但是你可以在这里阅读更多关于它的信息

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM