召冠的博客

做对的事,脚踏实地,保持正直。
  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

基于数据源连接,动态构造JPA上下文EntityManager

Posted on 2025-09-28 14:33  江城2211  阅读(16)  评论(0)    收藏  举报

因产品存在支持多种数据库平台的需求,采用JPA方式可以有效屏蔽不同数据库的语法差异,尤其是分页查询场景。

但访问异构数据源时,需要基于数据库连接信息动态构造JPA上下文,基于大模型查询了几种实现并进行验证可行的方式如下:

核心办法是使用PersistenceProvider.createContainerEntityManagerFactory(new DefaultPersistenceUnitInfo(persistenceUnitName), properties);   

而非 Persistence.createEntityManagerFactory("persistenceUnitName");  // 因为此方法的内部实现依赖persistence.xml配置,这就没法做到动态构造JPA上下文。

 

import javax.persistence.*;
import javax.persistence.spi.*;
import javax.sql.DataSource;
import java.net.URL;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 动态构造JPA上下文的工具类
 */
public class DynamicJpaContext {

    // 线程安全的EntityManagerFactory缓存
    private static final Map<String, EntityManagerFactory> emfCache = new HashMap<>();

    public static void main(String[] args) {
        // 1. 数据库连接信息
        String driverClass = "com.mysql.cj.jdbc.Driver";
        String url = "jdbc:mysql://localhost:3306/testdb?useSSL=false&serverTimezone=UTC";
        String username = "tkk";
        String password = "tkk123A?";
        String dialect = "org.hibernate.dialect.MySQL8Dialect";
        EntityManager em = DynamicJpaContext2.createEntityManager("dynamic-unit-mysql", driverClass, url, username, password, dialect);

        try {
            Query query = em.createNativeQuery("select * from tb1");
            List list = query.setFirstResult(1).setMaxResults(10).getResultList();
EntityTransaction transaction
= em.getTransaction(); try { transaction.begin(); Query upd = em.createNativeQuery("update tb1 set vname = :v1 where oid = :o1"); String p1 = "v1"; String p2 = "o1"; upd.setParameter(p1, new TypedParameterValue(StandardBasicTypes.NSTRING, "xxyy")); // 指定参数化数据类型 upd.setParameter(p2, 2); int cnt = upd.executeUpdate(); transaction.commit(); } catch (Exception e) { if (transaction.isActive()) { transaction.rollback(); } e.printStackTrace(); } } catch (Exception e) { } finally { if (em != null && em.isOpen()) { em.close(); } } // 应用关闭时清理资源 // Runtime.getRuntime().addShutdownHook(new Thread(DynamicJpaContext::closeAll)); } /** * 根据数据库连接信息创建EntityManager * * @param driverClass 数据库驱动类 * @param url 数据库连接URL * @param username 数据库用户名 * @param password 数据库密码 * @param dialect 数据库方言 * @return EntityManager实例 */ public static EntityManager createEntityManager( String persistenceUnitName, String driverClass, String url, String username, String password, String dialect) { // 创建唯一标识,用于缓存EntityManagerFactory String cacheKey = generateCacheKey(url, username); // 检查缓存中是否已有对应的EntityManagerFactory EntityManagerFactory emf = emfCache.get(cacheKey); if (emf == null || emf.isOpen() == false) { // 配置JPA属性 Map<String, Object> properties = configureJpaProperties(driverClass, url, username, password, dialect); //// 创建EntityManagerFactory,此方式直接引用了JPA的具体实现(Hibernate) //emf = new HibernatePersistenceProvider() // .createContainerEntityManagerFactory( // new DynamicPersistenceUnitInfo("dynamic-unit"), // properties);        //  获取provider的代码参照于Persistence类的createEntityManagerFactory方法(JPA的官方实现) PersistenceProviderResolver resolver = PersistenceProviderResolverHolder.getPersistenceProviderResolver(); List<PersistenceProvider> providers = resolver.getPersistenceProviders(); for (PersistenceProvider provider : providers) {
          // 使用简单方式创建PersistenceUnitInfo的实例,也可以使用后面Builder等复杂方式创建(加载JPA的Entity实体类等需求场景) emf
= provider.createContainerEntityManagerFactory(new DefaultPersistenceUnitInfo(persistenceUnitName), properties); if (emf != null) { break; } } if (emf == null) { throw new PersistenceException("No Persistence provider for EntityManager named " + persistenceUnitName); } // 存入缓存 emfCache.put(cacheKey, emf); } return emf.createEntityManager(); } /** * 配置JPA属性 */ private static Map<String, Object> configureJpaProperties(String driverClass, String url, String username, String password, String dialect) { Map<String, Object> properties = new HashMap<>(); // 数据库连接配置 properties.put("javax.persistence.jdbc.driver", driverClass); properties.put("javax.persistence.jdbc.url", url); properties.put("javax.persistence.jdbc.user", username); properties.put("javax.persistence.jdbc.password", password); // Hibernate配置 properties.put("hibernate.dialect", dialect); return properties; } /** * 生成缓存键,基于URL和用户名 */ private static String generateCacheKey(String url, String username) { return url + "_" + username; } /** * 关闭所有缓存的EntityManagerFactory */ public static void closeAll() { for (EntityManagerFactory emf : emfCache.values()) { if (emf.isOpen()) { emf.close(); } } emfCache.clear(); } // 内部类:默认实现 private static class DefaultPersistenceUnitInfo implements PersistenceUnitInfo { private final String unitName; private final PersistenceUnitTransactionType transactionType = PersistenceUnitTransactionType.RESOURCE_LOCAL; private final List<Class<?>> entityClasses = new ArrayList<>(); private final Properties properties= new Properties(); public DefaultPersistenceUnitInfo(String unitName) { this.unitName = unitName; this.properties.put("hibernate.hbm2ddl.auto", "none"); // 自动更新表结构 this.properties.put("hibernate.show_sql", "false"); // 显示SQL语句 this.properties.put("hibernate.format_sql", "false"); // 格式化SQL语句 this.properties.put("hibernate.connection.autocommit", "false"); // 关闭自动提交 } @Override public String getPersistenceUnitName() { return unitName; } @Override public String getPersistenceProviderClassName() { return null; } @Override public PersistenceUnitTransactionType getTransactionType() { return transactionType; } @Override public DataSource getJtaDataSource() { return null; } @Override public DataSource getNonJtaDataSource() { return null; } @Override public List<String> getManagedClassNames() { return entityClasses.stream() .map(Class::getName) .collect(Collectors.toList()); } // 以下为固定默认实现 @Override public List<String> getMappingFileNames() { return new ArrayList<>(); } @Override public List<URL> getJarFileUrls() { return new ArrayList<>(); } @Override public URL getPersistenceUnitRootUrl() { return null; } @Override public boolean excludeUnlistedClasses() { return false; } @Override public SharedCacheMode getSharedCacheMode() { return SharedCacheMode.UNSPECIFIED; } @Override public ValidationMode getValidationMode() { return ValidationMode.AUTO; } @Override public Properties getProperties() { return properties; } @Override public String getPersistenceXMLSchemaVersion() { return "2.2"; } @Override public ClassLoader getClassLoader() { return Thread.currentThread().getContextClassLoader(); } @Override public void addTransformer(ClassTransformer transformer) { } @Override public ClassLoader getNewTempClassLoader() { return null; } } }

当前示例仅是写标准SQL使用JPA的Query对象以native方式执行,即可满足分页查询及更新操作。

实际上动态构造的JPA上下文时,也可以通过PersistenceUnitInfo 的entityClasses加载实体类,进而可以使用JPA实体方式访问并操作数据库(可以使用如下builder方式动态构造):

 

import javax.persistence.SharedCacheMode;
import javax.persistence.ValidationMode;
import javax.persistence.spi.ClassTransformer;
import javax.persistence.spi.PersistenceUnitInfo;
import javax.persistence.spi.PersistenceUnitTransactionType;
import javax.sql.DataSource;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.stream.Collectors;

/**
 * 快速构建 PersistenceUnitInfo 实例的建造者类
 */
public class PersistenceUnitInfoBuilder {
    private String persistenceUnitName = "default-unit";
    private PersistenceUnitTransactionType transactionType = PersistenceUnitTransactionType.RESOURCE_LOCAL;
    private List<Class<?>> entityClasses = new ArrayList<>();
    private Properties properties = new Properties();

    // 设置持久化单元名称
    public PersistenceUnitInfoBuilder name(String name) {
        this.persistenceUnitName = name;
        return this;
    }

    // 设置事务类型(本地资源/ JTA)
    public PersistenceUnitInfoBuilder transactionType(PersistenceUnitTransactionType type) {
        this.transactionType = type;
        return this;
    }

    // 注册实体类
    public PersistenceUnitInfoBuilder entities(List<Class<?>> entities) {
        this.entityClasses = entities;
        return this;
    }

    // 添加额外属性
    public PersistenceUnitInfoBuilder property(String key, String value) {
        this.properties.setProperty(key, value);
        return this;
    }

    // 构建 PersistenceUnitInfo 实例
    public PersistenceUnitInfo build() {
        return new DefaultPersistenceUnitInfo(
                persistenceUnitName,
                transactionType,
                entityClasses,
                properties
        );
    }

    // 内部类:默认实现
    private static class DefaultPersistenceUnitInfo implements PersistenceUnitInfo {
        private final String unitName;
        private final PersistenceUnitTransactionType transactionType;
        private final List<Class<?>> entityClasses;
        private final Properties properties;

        public DefaultPersistenceUnitInfo(String unitName, PersistenceUnitTransactionType transactionType, List<Class<?>> entityClasses, Properties properties) {
            this.unitName = unitName;
            this.transactionType = transactionType;
            this.entityClasses = entityClasses;
            this.properties = properties;
        }

        @Override
        public String getPersistenceUnitName() {
            return unitName;
        }

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

        @Override
        public PersistenceUnitTransactionType getTransactionType() {
            return transactionType;
        }

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

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

        @Override
        public List<String> getManagedClassNames() {
            return entityClasses.stream()
                    .map(Class::getName)
                    .collect(Collectors.toList());
        }

        // 以下为固定默认实现
        @Override
        public List<String> getMappingFileNames() {
            return new ArrayList<>();
        }

        @Override
        public List<URL> getJarFileUrls() {
            return new ArrayList<>();
        }

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

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

        @Override
        public SharedCacheMode getSharedCacheMode() {
            return SharedCacheMode.UNSPECIFIED;
        }

        @Override
        public ValidationMode getValidationMode() {
            return ValidationMode.AUTO;
        }

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

        @Override
        public String getPersistenceXMLSchemaVersion() {
            return "2.2";
        }

        @Override
        public ClassLoader getClassLoader() {
            return Thread.currentThread().getContextClassLoader();
        }

        @Override
        public void addTransformer(ClassTransformer transformer) {
        }

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