You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot配置EntityManagerFactory复用当前数据库连接咨询

性能选型与EntityManager连接复用问题

当前实现的性能痛点

  • 为获取实体ID用于S3上传,需先持久化实体,依赖生命周期获取ID
  • 使用UUID.randomUUID().toString()生成ID时,Spring Boot会额外执行MySQL查询,拖慢性能
  • 调用emf.createEntityManager()会新建数据库连接,导致Hikari连接池因未关闭的活跃连接冻结

核心问题

如何配置EntityManagerFactory,使其复用当前数据库连接而非新建连接?

问题代码

private ProductImage addProductImage(Product product, String url, Boolean uploadImageToS3) {
    EntityManager em = emf.createEntityManager();
    String productImageUrl = null;
    String productImageId = null;
    try {
        EntityTransaction transaction = em.getTransaction();
        transaction.begin();

        ProductImage productImage = new ProductImage();
        productImage.setOriginalUrl(url);

        InputStream inputStream = null;
        if(uploadImageToS3) {
            inputStream = getInputStreamFromUrl(url);
            productImage.setSize(getInputStreamSizeOrZero(inputStream));
            productImage.setS3BucketAndEndpoint(storageProperties.getStorageS3ImagesBucketName() + "." + storageProperties.getStorageS3EndPoint());
            productImage.setS3FolderName(S3FolderName.PRODUCTS);
            productImage.setS3Size(S3Size.FULL);
            productImage.setS3Production(storageProperties.getStorageS3Production());
            productImage.setS3CloudFrontDomain(storageProperties.getStorageS3CloudFrontDomain());
        } else {
            productImage.setUri(url);
        }

        em.persist(productImage);
        productImageId = productImage.getId();

        if(uploadImageToS3) {
            productImageUrl = s3StorageService.uploadImage(inputStream, S3FolderName.PRODUCTS, productImageId, S3Size.FULL, allowedImageFormat);
            try {
                inputStream.close();
            } catch (IOException e) {
                throw new CustomGraphQLException("Input stream of the product image could not be closed.");
            } finally {
                em.close();
            }
        } else {
            em.close();
        }
        return productImage;
    } catch (Exception e) {
        em.getTransaction().rollback();
        em.close();
        if(productImageUrl!=null && uploadImageToS3) {
            s3StorageService.deleteImage(S3FolderName.PRODUCTS, productImageId , S3Size.FULL, allowedImageFormat);
        }
        throw new CustomGraphQLException("Product image could not be inserted. " + e.getMessage());
    }
}

解决方案

一、复用当前连接:依赖Spring事务上下文管理EntityManager

手动调用emf.createEntityManager()会强制创建新的EntityManager(对应新连接),要复用连接,必须让EntityManager绑定到当前Spring事务上下文,而非手动创建。

具体步骤:

  1. 替换注入对象
    把EntityManagerFactory emf替换为直接注入EntityManager,Spring会自动从当前事务上下文复用已有EntityManager(及对应连接):

    @PersistenceContext
    private EntityManager em;
    
  2. 移除手动管理EntityManager的代码
    删除EntityManager em = emf.createEntityManager();和所有em.close();语句,Spring会在事务结束后自动处理连接释放。

  3. 开启Spring事务管理
    在addProductImage方法上添加@Transactional注解(或确保上层调用方法处于事务中),Spring会自动维护连接生命周期:

    @Transactional
    private ProductImage addProductImage(Product product, String url, Boolean uploadImageToS3) {
        String productImageUrl = null;
        String productImageId = null;
        try {
            ProductImage productImage = new ProductImage();
            productImage.setOriginalUrl(url);
    
            InputStream inputStream = null;
            if(uploadImageToS3) {
                inputStream = getInputStreamFromUrl(url);
                productImage.setSize(getInputStreamSizeOrZero(inputStream));
                productImage.setS3BucketAndEndpoint(storageProperties.getStorageS3ImagesBucketName() + "." + storageProperties.getStorageS3EndPoint());
                productImage.setS3FolderName(S3FolderName.PRODUCTS);
                productImage.setS3Size(S3Size.FULL);
                productImage.setS3Production(storageProperties.getStorageS3Production());
                productImage.setS3CloudFrontDomain(storageProperties.getStorageS3CloudFrontDomain());
            } else {
                productImage.setUri(url);
            }
    
            em.persist(productImage);
            productImageId = productImage.getId();
    
            if(uploadImageToS3) {
                productImageUrl = s3StorageService.uploadImage(inputStream, S3FolderName.PRODUCTS, productImageId, S3Size.FULL, allowedImageFormat);
                try {
                    inputStream.close();
                } catch (IOException e) {
                    throw new CustomGraphQLException("Product image input stream failed to close.");
                }
            }
            return productImage;
        } catch (Exception e) {
            if(productImageUrl!=null && uploadImageToS3) {
                s3StorageService.deleteImage(S3FolderName.PRODUCTS, productImageId , S3Size.FULL, allowedImageFormat);
            }
            throw new CustomGraphQLException("Failed to insert product image: " + e.getMessage());
        }
    }
    

二、优化UUID ID生成的额外查询问题

使用UUID.randomUUID().toString()时,若实体ID字段用@GeneratedValue(strategy = GenerationType.AUTO),Hibernate会默认查询MySQL的hibernate_sequence表,产生冗余操作。

解决方案:

直接指定UUID生成策略,避免数据库查询:

@Id
@GeneratedValue(generator = "uuid2")
@GenericGenerator(name = "uuid2", strategy = "org.hibernate.id.UUIDGenerator")
@Column(columnDefinition = "CHAR(36)")
private String id;

此配置下Hibernate会在内存中生成UUID,无需访问数据库,消除额外查询。

三、优化S3上传与实体持久化顺序(可选)

若不想先持久化实体再上传S3,可提前生成UUID作为ID直接赋值,无需persist即可拿到ID:

// 提前生成UUID
String productImageId = UUID.randomUUID().toString();
ProductImage productImage = new ProductImage();
productImage.setId(productImageId);
// 直接用该ID上传S3,再持久化实体
em.persist(productImage);

此方式可打破先持久化再上传的依赖,缩短事务内操作时间,进一步降低连接占用。

关键原理

Spring通过@PersistenceContext注入的EntityManager是线程绑定的代理对象,会自动关联当前Spring事务的连接。当方法被@Transactional标记时,Spring从Hikari连接池获取连接并绑定到当前线程的EntityManager,所有操作复用该连接,事务结束后自动归还连接至池,彻底解决连接冻结问题。

内容的提问来源于stack exchange,提问作者Michael Bat

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.17 09:10:32