Is it possible to inject a context specific PersistenceContext? - jpa

In a jsf application data is managed injecting a PersistenceContext.
#PersistenceContext(unitName = "MyPU")
private EntityManager em;
PersistenceContext is static and choosen at compile time. Is there a way to inject a different PersistenceContext based on the user ? My idea is to enforce authorization checks on database side too, so if there is a hole in application security the user cannot access or modify restricted data.

Create some factory :
#Stateless
public class PersistenceContextFactory {
#PersistenceContext(unitName="MyPU")
private EntityManager emPU;
#PersistenceContext(unitName="MyOtherPU")
private EntityManager emOtherPU;
public EntityManager getEntityManager(User user) {
if(user.hasSomeRight()) {
return emPU;
} else {
return emOtherPU;
}
}
}

Related

How to inject dynamic EntityManager into a Third Party Library

I have a library with some functionality that I want to reuse in other projects. My issue is that my service requires writing to the database. I would like for my library to use the datasource of the project that is inject my service.
Here is the minimal setup of my service
#Stateless
public class CustomService {
//to be added in producer
private EntityManager em;
private Principal principal;
//default constructor
public CustomService() {}
//custom constructor called in provider
public CustomService(Principal p, EntityManager e) {
principal = p;
em = e;
}
#TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
#Transactional
public CustomJPAObject createObject(...params...) {
//create JPA Object
em.persist(customObject);
em.flush();
return customObject;
}
}
I created a Custom Annotation for overriding the datasource
#Qualifier
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.TYPE,ElementType.FIELD, ElementType.METHOD})
public #interface DynamicDS {
#Nonbinding String value() default "";
}
I also created a Singleton to be an EntityManager Producer
#Singleton
public class CustomEMProducer {
private Map<String, EntityManagerFactory> emfMap = new HashMap<>();
#Produces #Dependent #DynamicDS
public EntityManager produceEntityManager(InjectionPoint injectionPoint) {
String dataSourceName = null;
for(Annotation qualifier: injectionPoint.getQualifiers()) {
if(qualifier instanceof DynamicDS) {
DynamicDS dds = (DynamicDS) qualifier;
dataSourceName = dds.value();
break;
}
}
EntityManagerFactory emf = emfMap.get(dataSourceName);
if (emf == null) {
emf = Persistence.createEntityManagerFactory(dataSourceName);
emfMap.put(dataSourceName, emf);
}
return emf.createEntityManager();
}
#PostConstruct
public void cleanup() {
emfMap.entrySet().stream().forEach(entry -> entry.getValue().close());
}
}
Here is the code for my Service Producer
#Stateless
public class CustomServiceProvider {
#Inject private Principal principal;
#Produces #Dependent #DynamicDS
public BackgroundJobService getBackgroundJobService(InjectionPoint injectionPoint) throws EntityManagerNotCreatedException {
Annotation dsAnnotation = null;
for(Annotation qualifier: injectionPoint.getQualifiers()) {
if(qualifier instanceof BackgroundJobDS) {
dsAnnotation = qualifier;
break;
}
}
if (dsAnnotation != null) {
EntityManager em = CDI.current().select(EntityManager.class, dsAnnotation).get();
CustomService service = new CustomService(principal, em);
return service;
}
throw new EntityManagerNotCreatedException("Could not Produce CustomService");
}
}
The following is where I try to inject my new service
#Stateless
public class ProjectService {
#Inject #DynamicDS("project-ds") CustomerService service;
public CustomObject create(...params...) {
return service.createObject(...params...);
}
}
When I deploy my code and attempt to call the injected service I get the following error:
Caused by: javax.persistence.TransactionRequiredException: no transaction is in progress
at org.hibernate.jpa.spi.AbstractEntityManagerImpl.checkTransactionNeeded(AbstractEntityManagerImpl.java:1171)
at org.hibernate.jpa.spi.AbstractEntityManagerImpl.flush(AbstractEntityManagerImpl.java:1332)
...
It looks like all of the different levels of providers are preventing the #Transactional on the CustomService.createObject() method call from propagating the transaction. Does anyone have insight into why this is or an alternate way of accomplishing my goal of injecting a dynamic EntityManager?
After much experimenting, I was unable to get dynamically generate an EntityManager through the above code. After much research, I gave up on trying to pass in the name from outside the 3rd part library. I would up creating the following interface:
public interface CustomEntityManager {
EntityManager getEntityManager();
}
This meant that inside the project that uses the 3rd party service I can do the create the following implementation to inject the EntityManager
public ProjectSpecificEntityManager implements CustomEntityManager {
#PersistenceContext(unitname = "project-ds")
private EntityManager em;
public EntityManager getEntityManager() {
return em;
}
}
I had to update my CustomService to the following
#Stateless
public class CustomService {
//Ignore warning about no bean eligible because it is intended
//that the project that uses this library will provide the
//implementation
#SuppressWarnings("cdi-ambiguous-dependency")
#Inject
CustomEntityManager cem;
#TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
#Transactional
public CustomJPAObject createObject(...params...) {
//create JPA Object
cem.getEntityManager().persist(customObject);
return customObject;
}
}

CDI with EntityListener and timing issue?

I'm trying to do this.
public class MyEntityListener {
#PrePersist
private void onPrePersist(final Object object) {
// set object with value fetched via onPostConstruct
}
#PostConstruct
private void onPostConstruct() {
// fetch some value using entityManager
}
#PersistenceContext
private EntityManager entityManager;
}
When I persist and instance via EJB, the entityManager is different instance from that of the EJB.
onPrePersist is executed (before or) regardless of postConstruct.
Is this normal?

How do I create a separate entity manager for bulk operations in a JTA environment?

In JPA, when doing bulk operations such as this
update LogEntry e set e.customer = null where e.customer.id = :cid
It is recommended to use a separate entity manager to avoid breaking synchronization, according to this: UPDATE SET Queries in JPA/JPQL
For example, the EntityManager may not be aware that a cached entity object in its persistence context has been modified by an UPDATE query. Therefore, it is a good practice to use a separate EntityManager for UPDATE queries.
How do I create a separate entity manager in a JTA environment such as Wildfly using hibernate? Do I need to create a separate persistence unit for bulk operations?
EDIT: Given I dont need a separate PU for bulk operations, is this a sufficient way of solving it using a new transaction?
#Transactional
public class JpaCustomerRepository implements CustomerRepository {
#Inject
private EntityManager em;
...
#Override
public Customer remove(long id) {
CustomerEntity entity = em.find(CustomerEntity.class, id);
if (entity != null) {
updateLogEntriesToNull(entity);
em.remove(entity);
return entity;
} else {
return null;
}
}
#Transactional(value=TxType.REQUIRES_NEW)
public void updateLogEntriesToNull(CustomerEntity entity) {
em.createNamedQuery(LogEntry.updateCustomerToNull)
.setParameter("cid", entity.getId())
.executeUpdate();
}
...
}
Where LogEntry.updateCustomerToNull is the bulk query.
Answer: This does not work because the interceptor is not invoked when called from inside the same class.
EDIT2: Following the suggestions from Andrei, this should work:
#Transactional
public class JpaCustomerRepository implements CustomerRepository {
public static class BulkUpdater {
#Inject
private EntityManager em;
#Transactional(value=TxType.REQUIRES_NEW)
public void updateLogEntriesToNull(CustomerEntity entity) {
em.createNamedQuery(LogEntry.updateCustomerToNull)
.setParameter("cid", entity.getId())
.executeUpdate();
}
}
#Inject
private EntityManager em;
#Inject
private BulkUpdater bulkUpdater;
...
#Override
public Customer remove(long id) {
CustomerEntity entity = em.find(CustomerEntity.class, id);
if (entity != null) {
bulkUpdater.updateLogEntriesToNull(entity);
em.remove(entity);
return entity;
} else {
return null;
}
}
...
}
Testing confirms that the interceptor gets called twice.
The recommendation is valid only if you also do other stuff with the EntityManager (when there is a risk of manipulating/reading the same entities as the BULK UPDATE). The easiest solution: make sure that this BULK UPDATE is executed in a separate service, within a new transaction. No need to create a separate PU (persistence unit) for bulk operations.

JPA using #PersistenceContext

How to get the Persistence Unit Name dynamically?
For example in below example,I am hard coding unitName to application_openjpa, but I want to give it dynamically as unitName=#{unitName} so if I have different project uses the entity jar, but can use its own persistence.xml file
#PersistenceContext(unitName="application_openjpa")
public void setEntityManager(EntityManager entityManager) {
this.entityManager = entityManager;
}
Since an application normally have limited number of persistence units, how about create two setter methods and annotate it with different unitName attribute, eg:
#PersistenceContext(unitName="testEM")
public void setTestEntityManager(EntityManager testEM) {
this.testEM = testEM;
}
#PersistenceContext(unitName="prodEM")
public void setProdEntityManager(EntityManager prodEM) {
this.prodEM = prodEM;
}
Then you can choose between testEM and prodEM dynamically ?

How to get entityManager by using an entity class

How to reach the entity manager which managed the entity. I mean; suppose that i have an entity reference in the sessionBean, how can i get entityManager of this entity belonged one?
I had already tried (plz see getEntityManagerOfEntity() method) contains method of em; but it does not work.
Thx
bgrds
#Stateless(name = "MainManager", mappedName = "MainManager")
#TransactionManagement(TransactionManagementType.CONTAINER)
#Interceptors(value = { PerformanceMonitor.class, ProfileInterceptor.class })
public class MainManagerBean implements MainManager, MainManagerLocal
{
private Logger logger = Logger.getLogger(this.getClass());
#PersistenceContext(unitName = "DSApp")
private EntityManager manager;
#PersistenceContext(unitName = "DSIX")
private EntityManager integrationManager;
#Resource
SessionContext ctx;
public EntityManager getEntityManagerOfEntity(SuperEntity superEntity)
{
if (manager.contains(superEntity))
return manager;
else if (integrationManager.contains(superEntity))
return integrationManager;
return null;
}
public SuperEntity findByPrimaryKey(SuperEntity superEntity)
{
getEntityManagerOfEntity(superEntity).setFlushMode(FlushModeType.COMMIT);
return dao.findByPrimaryKey(getEntityManagerOfEntity(superEntity), superEntity);
You cannot backtrack the EntityManager from an entity using the JPA API, even when it is still managed.
What you can do, if you have references to different EMs in your bean and the entity is managed, is to check the right EM by calling em.contains(entity).
In most cases it is not really important to know, which EM has fetched an entity originally, since you can merge the entity into any persistence context and continue working with it.