创建 JPA EntityManager,但不要持久化.xml 配置文件

有没有办法在没有定义持久性单元的情况下初始化 EntityManager?您能提供创建实体管理器所需的所有属性吗?我需要在运行时根据用户指定的值创建 EntityManager。更新 persistence.xml并重新编译是不可选的。

任何关于如何做到这一点的想法都是非常受欢迎的!

123445 次浏览

Is there a way to initialize the EntityManager without a persistence unit defined?

You should define at least one persistence unit in the persistence.xml deployment descriptor.

Can you give all the required properties to create an Entitymanager?

  • The name attribute is required. The other attributes and elements are optional. (JPA specification). So this should be more or less your minimal persistence.xml file:
<persistence>
<persistence-unit name="[REQUIRED_PERSISTENCE_UNIT_NAME_GOES_HERE]">
SOME_PROPERTIES
</persistence-unit>
</persistence>

In Java EE environments, the jta-data-source and non-jta-data-source elements are used to specify the global JNDI name of the JTA and/or non-JTA data source to be used by the persistence provider.

So if your target Application Server supports JTA (JBoss, Websphere, GlassFish), your persistence.xml looks like:

<persistence>
<persistence-unit name="[REQUIRED_PERSISTENCE_UNIT_NAME_GOES_HERE]">
<!--GLOBAL_JNDI_GOES_HERE-->
<jta-data-source>jdbc/myDS</jta-data-source>
</persistence-unit>
</persistence>

If your target Application Server does not support JTA (Tomcat), your persistence.xml looks like:

<persistence>
<persistence-unit name="[REQUIRED_PERSISTENCE_UNIT_NAME_GOES_HERE]">
<!--GLOBAL_JNDI_GOES_HERE-->
<non-jta-data-source>jdbc/myDS</non-jta-data-source>
</persistence-unit>
</persistence>

If your data source is not bound to a global JNDI (for instance, outside a Java EE container), so you would usually define JPA provider, driver, url, user and password properties. But property name depends on the JPA provider. So, for Hibernate as JPA provider, your persistence.xml file will looks like:

<persistence>
<persistence-unit name="[REQUIRED_PERSISTENCE_UNIT_NAME_GOES_HERE]">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<class>br.com.persistence.SomeClass</class>
<properties>
<property name="hibernate.connection.driver_class" value="org.apache.derby.jdbc.ClientDriver"/>
<property name="hibernate.connection.url" value="jdbc:derby://localhost:1527/EmpServDB;create=true"/>
<property name="hibernate.connection.username" value="APP"/>
<property name="hibernate.connection.password" value="APP"/>
</properties>
</persistence-unit>
</persistence>

Transaction Type Attribute

In general, in Java EE environments, a transaction-type of RESOURCE_LOCAL assumes that a non-JTA datasource will be provided. In a Java EE environment, if this element is not specified, the default is JTA. In a Java SE environment, if this element is not specified, a default of RESOURCE_LOCAL may be assumed.

  • To insure the portability of a Java SE application, it is necessary to explicitly list the managed persistence classes that are included in the persistence unit (JPA specification)

I need to create the EntityManager from the user's specified values at runtime

So use this:

Map addedOrOverridenProperties = new HashMap();


// Let's suppose we are using Hibernate as JPA provider
addedOrOverridenProperties.put("hibernate.show_sql", true);


Persistence.createEntityManagerFactory(<PERSISTENCE_UNIT_NAME_GOES_HERE>, addedOrOverridenProperties);

Yes you can without using any xml file using spring like this inside a @Configuration class (or its equivalent spring config xml):

@Bean
public LocalContainerEntityManagerFactoryBean emf(){
properties.put("javax.persistence.jdbc.driver", dbDriverClassName);
properties.put("javax.persistence.jdbc.url", dbConnectionURL);
properties.put("javax.persistence.jdbc.user", dbUser); //if needed


LocalContainerEntityManagerFactoryBean emf = new LocalContainerEntityManagerFactoryBean();
emf.setPersistenceProviderClass(org.eclipse.persistence.jpa.PersistenceProvider.class); //If your using eclipse or change it to whatever you're using
emf.setPackagesToScan("com.yourpkg"); //The packages to search for Entities, line required to avoid looking into the persistence.xml
emf.setPersistenceUnitName(SysConstants.SysConfigPU);
emf.setJpaPropertyMap(properties);
emf.setLoadTimeWeaver(new ReflectiveLoadTimeWeaver()); //required unless you know what your doing
return emf;
}

I was able to create an EntityManager with Hibernate and PostgreSQL purely using Java code (with a Spring configuration) the following:

@Bean
public DataSource dataSource() {
final PGSimpleDataSource dataSource = new PGSimpleDataSource();


dataSource.setDatabaseName( "mytestdb" );
dataSource.setUser( "myuser" );
dataSource.setPassword("mypass");


return dataSource;
}


@Bean
public Properties hibernateProperties(){
final Properties properties = new Properties();


properties.put( "hibernate.dialect", "org.hibernate.dialect.PostgreSQLDialect" );
properties.put( "hibernate.connection.driver_class", "org.postgresql.Driver" );
properties.put( "hibernate.hbm2ddl.auto", "create-drop" );


return properties;
}


@Bean
public EntityManagerFactory entityManagerFactory( DataSource dataSource, Properties hibernateProperties ){
final LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setDataSource( dataSource );
em.setPackagesToScan( "net.initech.domain" );
em.setJpaVendorAdapter( new HibernateJpaVendorAdapter() );
em.setJpaProperties( hibernateProperties );
em.setPersistenceUnitName( "mytestdomain" );
em.setPersistenceProviderClass(HibernatePersistenceProvider.class);
em.afterPropertiesSet();


return em.getObject();
}

The call to LocalContainerEntityManagerFactoryBean.afterPropertiesSet() is essential since otherwise the factory never gets built, and then getObject() returns null and you are chasing after NullPointerExceptions all day long. >:-(

It then worked with the following code:

PageEntry pe = new PageEntry();
pe.setLinkName( "Google" );
pe.setLinkDestination( new URL( "http://www.google.com" ) );


EntityTransaction entTrans = entityManager.getTransaction();
entTrans.begin();
entityManager.persist( pe );
entTrans.commit();

Where my entity was this:

@Entity
@Table(name = "page_entries")
public class PageEntry {


@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;


private String linkName;
private URL linkDestination;


// gets & setters omitted
}

With plain JPA, assuming that you have a PersistenceProvider implementation (e.g. Hibernate), you can use the PersistenceProvider#createContainerEntityManagerFactory(PersistenceUnitInfo info, Map map) method to bootstrap an EntityManagerFactory without needing a persistence.xml.

However, it's annoying that you have to implement the PersistenceUnitInfo interface, so you are better off using Spring or Hibernate which both support bootstrapping JPA without a persistence.xml file:

this.nativeEntityManagerFactory = provider.createContainerEntityManagerFactory(
this.persistenceUnitInfo,
getJpaPropertyMap()
);

Where the PersistenceUnitInfo is implemented by the Spring-specific MutablePersistenceUnitInfo class.

Here's a solution without Spring. Constants are taken from org.hibernate.cfg.AvailableSettings :

entityManagerFactory = new HibernatePersistenceProvider().createContainerEntityManagerFactory(
archiverPersistenceUnitInfo(),
ImmutableMap.<String, Object>builder()
.put(JPA_JDBC_DRIVER, JDBC_DRIVER)
.put(JPA_JDBC_URL, JDBC_URL)
.put(DIALECT, Oracle12cDialect.class)
.put(HBM2DDL_AUTO, CREATE)
.put(SHOW_SQL, false)
.put(QUERY_STARTUP_CHECKING, false)
.put(GENERATE_STATISTICS, false)
.put(USE_REFLECTION_OPTIMIZER, false)
.put(USE_SECOND_LEVEL_CACHE, false)
.put(USE_QUERY_CACHE, false)
.put(USE_STRUCTURED_CACHE, false)
.put(STATEMENT_BATCH_SIZE, 20)
.build());


entityManager = entityManagerFactory.createEntityManager();

And the infamous PersistenceUnitInfo

private static PersistenceUnitInfo archiverPersistenceUnitInfo() {
return new PersistenceUnitInfo() {
@Override
public String getPersistenceUnitName() {
return "ApplicationPersistenceUnit";
}


@Override
public String getPersistenceProviderClassName() {
return "org.hibernate.jpa.HibernatePersistenceProvider";
}


@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 List<String> getManagedClassNames() {
return Collections.emptyList();
}


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


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


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


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


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


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


@Override
public void addTransformer(ClassTransformer transformer) {


}


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

DataNucleus JPA that I use also has a way of doing this in its docs. No need for Spring, or ugly implementation of PersistenceUnitInfo.

Simply do as follows

import org.datanucleus.metadata.PersistenceUnitMetaData;
import org.datanucleus.api.jpa.JPAEntityManagerFactory;


PersistenceUnitMetaData pumd = new PersistenceUnitMetaData("dynamic-unit", "RESOURCE_LOCAL", null);
pumd.addClassName("mydomain.test.A");
pumd.setExcludeUnlistedClasses();
pumd.addProperty("javax.persistence.jdbc.url", "jdbc:h2:mem:nucleus");
pumd.addProperty("javax.persistence.jdbc.user", "sa");
pumd.addProperty("javax.persistence.jdbc.password", "");
pumd.addProperty("datanucleus.schema.autoCreateAll", "true");


EntityManagerFactory emf = new JPAEntityManagerFactory(pumd, null);