Warning: file_get_contents(/data/phpspider/zhask/data//catemap/1/hibernate/5.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Spring:事务方法中的未捕获异常_Spring_Hibernate_Spring Boot_Spring Data Jpa_Spring Transactions - Fatal编程技术网

Spring:事务方法中的未捕获异常

Spring:事务方法中的未捕获异常,spring,hibernate,spring-boot,spring-data-jpa,spring-transactions,Spring,Hibernate,Spring Boot,Spring Data Jpa,Spring Transactions,我正在构建RESTful API,并在ProductController中具有以下更新方法: @Slf4j @RestController @RequiredArgsConstructor public class ProductController implements ProductAPI { private final ProductService productService; @Override public Product updateProduct(In

我正在构建RESTful API,并在ProductController中具有以下更新方法:

@Slf4j
@RestController
@RequiredArgsConstructor
public class ProductController implements ProductAPI {

    private final ProductService productService;

    @Override
    public Product updateProduct(Integer id, @Valid UpdateProductDto productDto) throws ProductNotFoundException,
            ProductAlreadyExistsException {

        log.info("Updating product {}", id);
        log.debug("Update Product DTO: {}", productDto);

        Product product = productService.updateProduct(id, productDto);

        log.info("Updated product {}", id);
        log.debug("Updated Product: {}", product);

        return product;
    }

}
可丢弃的异常来自具有以下实现的ProductService:

package com.example.ordersapi.product.service.impl;

import com.example.ordersapi.product.api.dto.CreateProductDto;
import com.example.ordersapi.product.api.dto.UpdateProductDto;
import com.example.ordersapi.product.entity.Product;
import com.example.ordersapi.product.exception.ProductAlreadyExistsException;
import com.example.ordersapi.product.exception.ProductNotFoundException;
import com.example.ordersapi.product.mapper.ProductMapper;
import com.example.ordersapi.product.repository.ProductRepository;
import com.example.ordersapi.product.service.ProductService;
import lombok.RequiredArgsConstructor;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;

@Service
@RequiredArgsConstructor
public class ProductServiceImpl implements ProductService {

    private final ProductRepository productRepository;
    private final ProductMapper productMapper;

    @Override
    public Set<Product> getAllProducts() {
        return StreamSupport.stream(productRepository.findAll().spliterator(), false)
                .collect(Collectors.toSet());
    }

    @Override
    public Product getOneProduct(Integer id) throws ProductNotFoundException {
        return productRepository.findById(id)
                .orElseThrow(() -> new ProductNotFoundException(id));
    }

    @Override
    public Product createProduct(CreateProductDto productDto) throws ProductAlreadyExistsException {
        Product product = productMapper.createProductDtoToProduct(productDto);
        Product savedProduct = saveProduct(product);

        return savedProduct;
    }

    private Product saveProduct(Product product) throws ProductAlreadyExistsException {
        try {
            return productRepository.save(product);
        } catch (DataIntegrityViolationException ex) {
            throw new ProductAlreadyExistsException(product.getName());
        }
    }

    /**
     * Method needs to be wrapped in a transaction because we are making two database queries:
     *  1. Finding the Product by id (read)
     *  2. Updating found product (write)
     *
     *  Other database clients might perform a write operation over the same entity between our read and write,
     *  which would cause inconsistencies in the system. Thus, we have to operate over a snapshot of the database and
     *  commit or rollback (and probably re-attempt the operation?) depending if its state has changed meanwhile.
     */
    @Override
    @Transactional
    public Product updateProduct(Integer id, UpdateProductDto productDto) throws ProductNotFoundException,
            ProductAlreadyExistsException {

        Product foundProduct = getOneProduct(id);
        boolean productWasUpdated = false;

        if (productDto.getName() != null && !productDto.getName().equals(foundProduct.getName())) {
            foundProduct.setName(productDto.getName());
            productWasUpdated = true;
        }

        if (productDto.getDescription() != null && !productDto.getDescription().equals(foundProduct.getDescription())) {
            foundProduct.setDescription(productDto.getDescription());
            productWasUpdated = true;
        }

        if (productDto.getImageUrl() != null && !productDto.getImageUrl().equals(foundProduct.getImageUrl())) {
            foundProduct.setImageUrl(productDto.getImageUrl());
            productWasUpdated = true;
        }

        if (productDto.getPrice() != null && !productDto.getPrice().equals(foundProduct.getPrice())) {
            foundProduct.setPrice(productDto.getPrice());
            productWasUpdated = true;
        }

        Product updateProduct = productWasUpdated ? saveProduct(foundProduct) : foundProduct;

        return updateProduct;
    }

}
package com.example.ordersapi.product.service.impl;
导入com.example.ordersapi.product.api.dto.CreateProductDto;
导入com.example.ordersapi.product.api.dto.UpdateProductDto;
导入com.example.ordersapi.product.entity.product;
导入com.example.ordersapi.product.exception.ProductAlreadyExistsException;
导入com.example.ordersapi.product.exception.ProductNotFoundException;
导入com.example.ordersapi.product.mapper.ProductMapper;
导入com.example.ordersapi.product.repository.ProductRepository;
导入com.example.ordersapi.product.service.ProductService;
导入lombok.RequiredArgsConstructor;
导入org.springframework.dao.DataIntegrityViolationException;
导入org.springframework.stereotype.Service;
导入org.springframework.transaction.annotation.Transactional;
导入java.util.Set;
导入java.util.stream.collector;
导入java.util.stream.StreamSupport;
@服务
@所需参数构造函数
公共类ProductServiceImpl实现ProductService{
私有最终产品存储库产品存储库;
私人最终产品映射器ProductMapper;
@凌驾
公共集getAllProducts(){
返回StreamSupport.stream(productRepository.findAll().spliterator(),false)
.collect(收集器.toSet());
}
@凌驾
公共产品getOneProduct(整数id)抛出ProductNotFoundException{
返回productRepository.findById(id)
.orelsetrow(()->newproductnotfoundexception(id));
}
@凌驾
公共产品createProduct(CreateProductDto productDto)抛出ProductAlreadyExistsException{
Product Product=productMapper.CreateProductdToProduct(productDto);
Product savedProduct=saveProduct(产品);
返回保存的产品;
}
私有产品saveProduct(产品产品)引发ProductAlreadyExistsException{
试一试{
返回productRepository.save(产品);
}捕获(DataIntegrityViolationException ex){
抛出新ProductReadyExistsException(product.getName());
}
}
/**
*方法需要包装在事务中,因为我们正在进行两个数据库查询:
*1.通过id查找产品(读取)
*2.更新找到的产品(写入)
*
*其他数据库客户端可能在读写操作之间对同一实体执行写操作,
*这将导致系统中的不一致。因此,我们必须在数据库快照和
*提交或回滚(可能重新尝试该操作?)取决于其状态是否同时发生了更改。
*/
@凌驾
@交易的
公共产品updateProduct(整数id,UpdateProductDto productDto)抛出ProductNotFoundException,
ProductAlreadyExistsException{
Product foundProduct=getOneProduct(id);
布尔productWasUpdated=false;
如果(productDto.getName()!=null&&!productDto.getName().equals(foundProduct.getName())){
foundProduct.setName(productDto.getName());
productWasUpdated=true;
}
如果(productDto.getDescription()!=null&&!productDto.getDescription().equals(foundProduct.getDescription())){
foundProduct.setDescription(productDto.getDescription());
productWasUpdated=true;
}
如果(productDto.getImageUrl()!=null&&!productDto.getImageUrl().equals(foundProduct.getImageUrl())){
foundProduct.setImageUrl(productDto.getImageUrl());
productWasUpdated=true;
}
如果(productDto.getPrice()!=null&&!productDto.getPrice().equals(foundProduct.getPrice())){
foundProduct.setPrice(productDto.getPrice());
productWasUpdated=true;
}
Product updateProduct=productWasUpdated?saveProduct(foundProduct):foundProduct;
返回updateProduct;
}
}
由于我已将数据库中的“名称”列设置为“唯一”,因此当使用现有名称发布更新时,存储库保存方法将抛出一个
DataIntegrityViolationException
。在createProduct方法中,它可以正常工作,但在updateProduct中,对私有方法saveProduct的调用无论发生什么都不会捕获异常,因此
DataIntegrityViolationException
会冒泡到控制器

我知道这是因为我在事务中包装代码,因为删除@Transactional“解决”了问题。我认为这与Spring使用代理将方法包装到事务中这一事实有关,因此控制器中的服务调用实际上并没有(直接)调用服务方法。尽管如此,我不明白为什么它忽略catch分支来抛出
productReadyExistsException
,而它对
ProductNotFoundException
运行良好

我知道我还可以再次访问数据库,尝试按名称查找产品,如果不存在,我会尝试将实体保存回来。但这会让一切变得更加低效

我还可以在控制器层捕获
DataIntegrityViolationException
,并在那里抛出
productalreadyexistexception
,但我会在那里从持久性层公开细节,这似乎不合适

有没有一种方法可以像我现在尝试的那样,在服务层处理这一切


注意:将逻辑外包给一个新方法,并在内部使用它似乎可以工作,但这仅仅是因为对的调用实际上不会被事务管理器代理截获,因此实际上没有执行任何事务要使其工作,您需要更改
saveProduct
        try {
            return productRepository.saveAndFlush(product);
        } catch (DataIntegrityViolationException ex) {
            throw new ProductAlreadyExistsException(product.getName());
        }