因产品存在支持多种数据库平台的需求,采用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; } } }
浙公网安备 33010602011771号