CONTEXT
I've been trying to create a generic persistence layer for my application using Camel (v2.23.0) and Hibernate(v5.3.7). I'm also using HikariCP as the connection pool. To implement this generic persistence layer, I'm using the Camel JPA component to construct the core persistence routes as follows:
from("direct:getEntityById")
.toD("jpa:${header.entityClassName}?persistenceUnit=" + projectStage.toString() +
"&namedQuery=${header.entityClassSimpleName}.${header.queryName}");
from("direct:upsertEntity")
.toD("jpa:${header.entityClassName}?usePersist=false&persistenceUnit=" + projectStage.toString());
In the DAO, I'm using a dynamic JPA endpoint, so that I can reuse the route for any JPA entity.
I'm calling these routes through a plain Java DAO class, as follows:
...
public <T> Optional<T> getEntityById(Object id, String queryName, Class<T> clazz) throws GenericDAOException {
try {
Map<String, Object> queryParams = new HashMap<>();
queryParams.put("id", id);
List<T> entities = (List<T>) fluentProducerTemplate
.withHeader("id", id)
.withHeader("entityClassName", clazz.getName())
.withHeader("entityClassSimpleName", clazz.getSimpleName())
.withHeader("queryName", queryName)
.withHeader("CamelJpaParameters", queryParams)
.to("direct:getEntityById")
.request(List.class);
...
}
...
}
#Override
public <T> void upsertEntity(T entityObject) throws GenericDAOException {
try {
fluentProducerTemplate
.withBody(entityObject)
.withHeader("entityClassName", entityObject.getClass().getName())
.to("direct:upsertEntity")
.request();
}
...
}
This is the configuration for my persistence unit:
<persistence xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
version="2.1"
xmlns="http://xmlns.jcp.org/xml/ns/persistence"
xsi:schemaLocation="
http://xmlns.jcp.org/xml/ns/persistence
http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd">
<persistence-unit name="Production" transaction-type="RESOURCE_LOCAL">
<provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
<class>...</class>
<properties>
<property name="javax.persistence.jdbc.driver" value="org.postgresql.Driver"/>
<property name="javax.persistence.jdbc.url"
value="jdbc:postgresql://..."/>
<property name="javax.persistence.jdbc.user" value="..."/>
<property name="javax.persistence.jdbc.password" value="..."/>
<property name="hibernate.dialect" value="<custom dialect>"/>
<property name="hibernate.show_sql" value="false"/>
<property name="hibernate.hbm2ddl.auto" value="update"/>
<property name="hibernate.hikari.minimumIdle" value="25"/>
<property name="hibernate.hikari.maximumPoolSize" value="70"/>
<property name="hibernate.hikari.idleTimeout" value="300000"/>
<property name="hibernate.hikari.maxLifetime" value="600000"/>
<property name="hibernate.connection.provider_class"
value="org.hibernate.hikaricp.internal.HikariCPConnectionProvider"/>
</properties>
</persistence-unit>
</persistence>
Also, my JPA component is being created using a specific EntityManagerFactory (so I'm assuming all JPA endpoints will be sharing the same factory). (Using CDI for dependency injections)
#Produces
#ApplicationScoped
#Named("jpa")
public JpaComponent getComponent(PlatformTransactionManager transactionManager, EntityManager entityManager) {
JpaComponent component = new JpaComponent();
component.setTransactionManager(transactionManager);
component.setEntityManagerFactory(entityManager.getEntityManagerFactory());
component.setCamelContext(camelContext);
return component;
}
QUESTIONS:
As per the following doc: https://camel.apache.org/components/latest/eips/toD-eip.html, we should avoid creating endless dynamic endpoints, due to the excessive resource consumption. Since I'm working with the JPA component, I am especially concerned about the number of DB connections created, which could lead to application failures if not within a certain threshold.
To get around these potential issues, I'm using a connection pool. Since all JPA endpoints would use the same EntityManagerFactory, I presume that the connection pool would also be shared between these endpoints.
So I have the following questions:
When exactly would the JPA route release the DB connection? (as in, are they released immediately after the JPA route has completed processing a message, or are the connections tied to the lifecycle of the JPA endpoints?)
Would a new dynamic JPA endpoint be created for each unique URI?
What are the other potential issues that I would need to be wary of, when using dynamic JPA endpoints? (I suppose memory consumption would be one).
Related
I am new to JPA and use Hibernate as the JPA provider. I came to know that we need META-INF/persistence.xml configuration file.
I successfully created a simple Java program to persist data in DB using JPA.
All fine, doubts started when I looked into the persistence.xml file to understand it better.
Sample below:
<persistence-unit name="test-jpa" transaction-type="RESOURCE_LOCAL">
<properties>
<property name="hibernate.connection.driver_class" value="org.h2.Driver"/>
<property name="hibernate.connection.url" value="jdbc:h2:tcp://localhost/~/test"/>
<property name="hibernate.connection.username" value="sa" />
<property name="hibernate.connection.password" value="" />
<property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect"/>
<property name="hibernate.hbm2ddl.auto" value="create" />
<property name="hibernate.show_sql" value="true" />
</properties>
</persistence-unit>
The following is the Java code for reading the configuration:
EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory("test-jpa");
The following are the doubts:
How do we know that Hibernate is the JPA provider? Is it inferred by seeing the property tags in the file?
In config file, there are many <property> tags, are they pre-defined which can appear in the file (for a given JPA provider) or can we randomly add any property? who reads those <property> tags?
A JPA provider would provide documentation that would tell you all of that. Doesn't yours? I'd be surprised.
You should either have a <provider> element in the persistence-unit to define which provider to use, or it would use the default for the environment that you are running in (in JavaSE you would need to have 1 and only one JPA provider in the CLASSPATH, in JavaEE the server would have its own default).
They are provider-specific. Any properties that are prefixed javax.persistence would be JPA STANDARD. The first 4 of those posted have javax.persistence variants that you should have used instead.
I am a newbie in Hibernate. In order to get the transaction by the EntityManager, I need to use EntityManager Factory. When I put this code block on my file:
EntityManagerFactory entityManagerFactory = Persistence
.createEntityManagerFactory("Comment");
EntityManager entityManager = entityManagerFactory.createEntityManager();
EntityTransaction transaction = entityManager.getTransaction();
transaction.begin();
entityManager.persist(obj);
transaction.commit();
I got this Exception:
javax.persistence.PersistenceException: No Persistence provider for EntityManager named Comment
Then I realized that I need to add a persistence.xml file:
<persistence xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemalocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd" version="1.0">
<persistence-unit name="QuestionsComments" transaction-type="RESOURCE_LOCAL">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<class>Comment</class>
<properties>
<property name="hibernate.dialect" value="org.hibernate.dialect.MySQLInnoDBDialect"/>
<property name="hibernate.connection.driver_class" value="com.mysql.jdbc.Driver"/>
<property name="hibernate.show_sql" value="true"/>
<property name="hibernate.connection.username" value="root"/>
<property name="hibernate.connection.password" value="root"/>
<property name="hibernate.connection.url" value="jdbc:mysql://localhost:3306/sogdb"/>
<property name="hibernate.max_fetch_depth" value="3"/>
</properties>
</persistence-unit>
</persistence>
Actually, The app I am using is not a Java on Server app (I have a Client app). Which means that there is no META-INF folder that will contain the persistence.xml file.
My Questions are:
1- Where I need to put the persistence.xml file?
2- Is the code below OK? Knowing that my table in the database is QuestionsComments and my class that will be related to it using Hibernate is Comment.
If you are using Eclipse, adding the JPA facet to your project allows Eclipse to take care of setting these things up for you. It will create the META-INF folder, and can generate a stub persistence.xml file in it. You don't have to be running the code on a server to have a META-INF folder in your jar file.
When you are creating the EntityManagerFactory the string argument should be the name of the persistence unit. I'd recommend using annotations in your entity class for specifying table name, id column, etc.
Edit: fixed link.
Answered clearly in this section of the Hibernate JPA Quick Start Guide. It goes in META-INF.
Check out the same guide just linked.
Personally I prefer the Hibernate-specific configuration hibernate.cfg.xml with annotations for mappings and Hibernate's SessionFactory for transaction access; I find it easier to wrap my head around -- although that's probably because I originally learned how to use Hibernate without knowing how to use JPA.
I have the following test case:
#ContextConfiguration("/spring/test-context.xml")
#TransactionConfiguration(transactionManager="txManager")
#Transactional()
public class MyEntityDaoTestCase extends AbstractJUnit4SpringContextTests {
#Autowired
private MyEntityDao dao;
#Test
public void testSave_success() {
MyEntity e = new MyEntity();
dao.save(e);
MyEntity result = dao.findById(e.getId());
assertNotNull(result);
}
}
My DAO definition has as follows:
public abstract class MyEntityDAO {
#PersistenceContext
private EntityManager mEntityManager;
public void save(MyEntity entity) {
mEntityManager.persist(entity);
}
public MyEntity findById(Long id) {
return mEntityManager.find(mEntityClass, id);
}
}
My Spring config is the following:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">
<!--
Bean post-processor for JPA annotations
-->
<bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor"/>
<!--
JPA entity manager factory
-->
<bean id="jpaEntityManagerFactory" class="org.springframework.orm.jpa.LocalEntityManagerFactoryBean">
<property name="persistenceUnitName" value="unit-test-pu"/>
</bean>
<!--
Transaction manager
-->
<bean id="txManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="jpaEntityManagerFactory"/>
</bean>
<!--
Enable the configuration of transactional behavior based on annotations
-->
<tx:annotation-driven transaction-manager="txManager"/>
<!--
DAO instance beans
-->
<bean id="mockEntityDao" class="mypackage.MyEntityDao"></bean>
</beans>
I get no errors while executing my test but it won't pass. It looks like the findById() method will not find the entity in the database. Can anyone advise on how to correctly test this case?
EDIT:
My JPA provider is hibernate. I am using an in-memory HSQLDB for my unit tests and have the following configuration:
<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"
version="2.0">
<persistence-unit name="unit-test-pu" transaction-type="RESOURCE_LOCAL">
<properties>
<property name="javax.persistence.jdbc.driver" value="org.hsqldb.jdbcDriver"/>
<property name="javax.persistence.jdbc.user" value="sa"/>
<property name="javax.persistence.jdbc.password" value=""/>
<property name="javax.persistence.jdbc.url" value="jdbc:hsqldb:."/>
<property name="hibernate.dialect" value="org.hibernate.dialect.HSQLDialect"/>
<property name="hibernate.archive.autodetection" value="class"/>
<property name="hibernate.show_sql" value="true"/>
<property name="hibernate.format_sql" value="true"/>
<property name="hibernate.hbm2ddl.auto" value="create"/>
</properties>
</persistence-unit>
</persistence>
You could try using #TransactionalConfiguration annotation and the Spring JUnit runner.
Something like changing your class to this:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration("/spring/test-context.xml")
#TransactionConfiguration(transactionManager="txManager", defaultRollback=true)
#Transactional
public class MyEntityDaoTestCase {
This also means you don't need to extend the abstract case (because you are using the Spring runner) - unless you particularly like that approach.
Here more details
If you follow TDD strictly you should not use an in memory database but instead have everything mocked. The problem is that the persist method returns void. So you can not test the correct response (an entity with an id generated by a database) One way to work around this is to us the Mockito doAnswer method, here an example:
#RunWith(MockitoJUnitRunner.class)
public class CookieRepositoryTest {
#Mock
EntityManager em;
#Mock
TimeService timeService;
#InjectMocks
CookieRepository underTest = new CookieRepository();
#Test
public void testCreateEntity() throws Exception {
Cookie newCookie = new Cookie();
when(timeService.getTime()).thenReturn(new DateTime(DateTimeZone.UTC));
doAnswer(new Answer<Brand>() {
#Override
public Brand answer(InvocationOnMock invocationOnMock) throws Throwable {
Object[] args = invocationOnMock.getArguments();
Cookie cookie = (Cookie) args[0];
cookie.setId(1);
return null;
}
}).when(em).persist(any(Brand.class));
Cookie persistedCookie = underTest.createEntity(newCookie);
assertNotNull(persistedCookie.getId());
}
}
A complete explanation can be found on my blog post.
I think you should be extending AbstractTransactionalJUnit4SpringContextTests instead of AbstractJUnit4SpringContextTests for the #Transactional-annotations to have any effect.
If you want to test the persistence layer, you could also take a look at DBUnit capabilities.
You can find a nice article from Petri Kainulainen here about testing the persistence layer (in this case with JPA) in a Spring based scenario:
http://www.petrikainulainen.net/programming/spring-framework/spring-data-jpa-tutorial-integration-testing/
With this, you test if the DAO classes behave as expected, writing and reading into/from the DB, and on the service layer you can avoid testing this aspect, focusing more on the "mock" approach for the business logic.
Hope it helps
Fran
I am doing a small project using JPA. I need to insert the employee object.
For that when I use the annotated entity manager I got the NullPointer exception.
But when I use the Normal EntityManager without using the annotation it is working fine.
Do I need to configure somewhere else other than persistence.xml to work this examle fine?
Please see the code below.
public class EmployeeDao implements IEmployeeDao{
#PersistenceContext(unitName = "timesheet")
private EntityManager entityManager ;
#Override
public boolean createEmployee(IEmployee employee) {
this.entityManager.persist(employee);
return true;
}
}
persistence.xml
<?xml version="1.0" encoding="UTF-8" ?>
<persistence xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"
version="2.0" xmlns="http://java.sun.com/xml/ns/persistence">
<persistence-unit name="timesheet" transaction-type="RESOURCE_LOCAL">
<class>com.timesheet.model.Employee</class>
<properties>
<property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver" />
<property name="javax.persistence.jdbc.url"
value="jdbc:mysql://localhost:3306/timesheet" />
<property name="javax.persistence.jdbc.user" value="root" />
<property name="javax.persistence.jdbc.password" value="" />
<!-- EclipseLink should create the database schema automatically -->
<property name="eclipselink.ddl-generation" value="create-tables" />
<property name="eclipselink.ddl-generation.output-mode"
value="database" />
</properties>
</persistence-unit>
</persistence>
Injection of resources (in your case via use of #PersistenceContext) works only in container managed classes (like EJBs and Servlets). This explained with more details for example in Java EE specification v6, EE5.2.5.
What you can do:
Modify your class so that it is managed class
move injection of resources to managed class and pass it to
EmployeeDao,
use JNDI lookup as before
Use the annotation javax.ejb.Stateless for your EmployeeDao and IEmployeeDao classes. Entitymanager is a no-interface an Enterprise Java Bean injected in your client.
The client must be either a web component or another enterpise bean. See here for further details about how to use EJBs.
In other words, using the Stateless annotation, the web container will take care of the lifecycle of your EmployeeDao class.
Use transaction type of JTA instead of RESOURCE_LOCAL. You can acquire the EntityManager instance if and only if the program is running in app server and transaction type is JTA.
So above all answers lead to summarise that :
Injection of resources through annotation will work on container managed classes (e.g EJB, Servlet).
Injection of resources can be done except annotation (e.g #PersistenceContext) on a simple JPA project ( a project which hasn't container managed classes ). Following code snippet give you lucid view :
private EntityManagerFactory factory;
factory = Persistence.createEntityManagerFactory("PERSISTENCE_UNIT_NAME");
EntityManager em = factory.createEntityManager();
Thanks to all for sharing knowledge.
I want to use the #PersistenceUnit annotation in my app to create an application managed EntityManager
#PersistenceUnit(unitName="primary")
private static EntityManagerFactory entityManagerFactory;
EntityManager entityManager = entityManagerFactory.createEntityManager();
This doesn't seem to be working. I run my code through a debugger and discover that entityManagerFactory is null. My guess is that the injection of Persistence context with the #PersistenceUnit annotation is not working.
My app is a CDI app. It was not previously a CDI application - I converted it to CDI by creating a beans.xml file in WEB-INF, I needed to in order to do something like this.
Is there anything I need to configure within CDI to get the annotation to work? Thanks.
I have a JPA application running with only Java SE. I don't have a WEB-INF/beans.xml, but I do have a META-INF/persistence.xml configuration file:
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
<persistence-unit name="JPAPU" transaction-type="RESOURCE_LOCAL">
<provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
<class>jpa.Container</class>
<class>jpa.Item</class>
<properties>
<property name="javax.persistence.jdbc.url" value="jdbc:derby:D:\NetBeansProjects\JPA\jpaTestDB;create=true"/>
<property name="javax.persistence.jdbc.password" value=""/>
<property name="javax.persistence.jdbc.driver" value="org.apache.derby.jdbc.EmbeddedDriver"/>
<property name="javax.persistence.jdbc.user" value=""/>
<property name="eclipselink.ddl-generation" value="create-tables"/>
</properties>
</persistence-unit>
</persistence>
Container and Item are the two persistence classes in my jpa package.
This was generated automatically by Netbeans. There is also some information about using JPA without Java EE in the official (Sun/Oracle) Java EE tutorial in the persistence chapter.