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

PlayFramework 2.3.X通过EntityManagerFactory创建EntityManager无法持久化数据

问题原因
  • 注解不生效:@Transactional和@PersistenceContext对你手动创建的EntityManager完全无效。Play框架的@Transactional仅管理容器默认注入的持久化上下文,你自己通过代码新建EntityManagerFactory和EntityManager的逻辑完全脱离框架事务管控。
  • 事务未显式提交:你配置的事务类型是RESOURCE_LOCAL,这类场景下所有写操作必须显式开启并提交事务才会持久化到数据库。读操作不需要事务支持所以可以正常返回结果,未提交的写操作只会存在于EntityManager的一级缓存中,不会同步到数据库。
  • EntityManagerFactory重复创建:EntityManagerFactory是重量级对象,初始化成本极高,你每次获取EntityManager都新建一个工厂实例,会造成严重的性能浪费和资源泄漏,应该全局单例初始化。
  • persist方法误用:你从数据库查询出来的User本身就是持久态对象,不需要调用persist方法,只要在事务范围内修改属性,事务提交后会自动同步到数据库。persist仅用于保存全新的未被持久化的瞬时态对象。
修复方案

1. 调整PersistenceManager实现,EntityManagerFactory改为单例

public final class PersistenceManager extends Controller {
    // 缓存不同数据库对应的EntityManagerFactory,避免重复创建
    private static final Map<String, EntityManagerFactory> EM_FACTORY_CACHE = new ConcurrentHashMap<>();

    private static EntityManagerFactory getOrCreateEMFactory(CustomDB customDB) {
        String dbKey = customDB.getDatabaseUrl() + ":" + customDB.getDatabasePort() + "/" + customDB.getDatabaseName();
        if (EM_FACTORY_CACHE.containsKey(dbKey)) {
            return EM_FACTORY_CACHE.get(dbKey);
        }
        String dbUrl = "jdbc:mysql://" + customDB.getDatabaseUrl() + ":" + customDB.getDatabasePort() + "/" + customDB.getDatabaseName() + "?useSSL=true";
        EntityManagerFactory factory = createEMFactory(dbUrl, customDB.getDatabaseUser(), customDB.getDatabasePassword());
        EM_FACTORY_CACHE.put(dbKey, factory);
        return factory;
    }

    public static EntityManager getEM(CustomDB customDB) {
        return getOrCreateEMFactory(customDB).createEntityManager();
    }

    // 原有的createEMFactory方法逻辑保持不变即可
    public static EntityManagerFactory createEMFactory(String dbUrl, String dbUser, String dbPassword) {
        Properties props = new Properties();
        props.put("hibernate.dialect","org.hibernate.dialect.MySQLDialect");
        props.put("hibernate.connection.driver_class","com.mysql.jdbc.Driver");
        props.put("hibernate.archive.autodetection","class");
        props.put("hibernate.show_sql","false");
        props.put("hibernate.format_sql","true");
        props.put("hbm2ddl.auto","create");

        props.put("hibernate.connection.url", dbUrl);
        props.put("hibernate.connection.username", dbUser);

        if (StringUtils.hasText(dbPassword)) {
            props.put("hibernate.connection.password", dbPassword);
        }

        PersistenceUnitInfo persistenceUnitInfo = new PersistenceUnitInfo() {

            @Override
            public Properties getProperties() {
                return props;
            }

            @Override
            public List<String> getManagedClassNames() {
                return Arrays.asList(
                        Address.class.getName(),
                        City.class.getName(),
                        User.class.getName()
                );
            }

            @Override
            public String getPersistenceUnitName() {
                return "Test";
            }

            @Override
            public String getPersistenceProviderClassName() {
                return HibernatePersistenceProvider.class.getName();
            }

            @Override
            public PersistenceUnitTransactionType getTransactionType() {
                return PersistenceUnitTransactionType.RESOURCE_LOCAL;
            }

            @Override
            public DataSource getJtaDataSource() {
                return null;
            }

            @Override
            public DataSource getNonJtaDataSource() {
                return null;
            }

            @Override
            public List<String> getMappingFileNames() {
                return Collections.emptyList();
            }

            @Override
            public List<URL> getJarFileUrls() {
                try {
                    return Collections.list(this.getClass()
                            .getClassLoader()
                            .getResources(""));
                } catch (IOException e) {
                    throw new UncheckedIOException(e);
                }
            }

            @Override
            public URL getPersistenceUnitRootUrl() {
                return null;
            }

            @Override
            public boolean excludeUnlistedClasses() {
                return false;
            }

            @Override
            public SharedCacheMode getSharedCacheMode() {
                return null;
            }

            @Override
            public ValidationMode getValidationMode() {
                return null;
            }

            @Override
            public String getPersistenceXMLSchemaVersion() {
                return null;
            }

            @Override
            public ClassLoader getClassLoader() {
                return null;
            }

            @Override
            public void addTransformer(ClassTransformer transformer) {

            }

            @Override
            public ClassLoader getNewTempClassLoader() {
                return null;
            }
        };

        HibernatePersistenceProvider hibernatePersistenceProvider = new HibernatePersistenceProvider();

        return hibernatePersistenceProvider.createContainerEntityManagerFactory(persistenceUnitInfo, Collections.EMPTY_MAP);
    }
}

2. 控制器层手动管理事务边界

public class UserController extends Controller {
    
    public Result writeUser(CustomDB customDB ){
        EntityManager em = PersistenceManager.getEM(customDB);
        EntityTransaction tx = null;
        try {
            tx = em.getTransaction();
            tx.begin(); // 开启事务
            List<User> userList = em.createNamedQuery("userByName").setParameter("name","Jane").getResultList();                                             
            User user = userList.get(0);
            user.phoneNumber("0123456789");
            // 持久态对象不需要调用persist,修改后事务提交自动同步
            tx.commit(); // 提交事务,更改落库
        } catch (RuntimeException e) {
            if (tx != null && tx.isActive()) {
                tx.rollback(); // 异常回滚
            }
            throw e;
        } finally {
            em.close(); // 关闭EntityManager释放资源
        }
        return ok();
    } 
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.02 10:15:03