Skip to main content


Eclipse Community Forums
Forum Search:

Search      Help    Register    Login    Home
Home » Eclipse Projects » Eclipse Scout » Scout + JPA. Where is good place to get EntityManager?
Scout + JPA. Where is good place to get EntityManager? [message #1828473] Wed, 10 June 2020 11:49 Go to next message
Darth Bolek is currently offline Darth BolekFriend
Messages: 25
Registered: August 2019
Junior Member
I plan to use at least two different EntityManagers (two different DBs):
1. first to use for technical data: roles, permissions, user specific properties (not bussiness related)
2. second for bussiness data

The first Em is required during initial stage of preparing authorization process (during ServerAccessControlService.execLoadPermissions() ).
Authentication is LDAP only. Authorization is LDAP/DB.

My idea is to have two EMs per each session - to be thread safe and efficient. Creating EM per view or per DB interaction seems excessive.

What I am thinking right now is:
org.eclipse.scout.apps.helloworld.server.ServerSession.execLoadSession()

Or maybe using JakarteEE compatible server is a viable option - that actually would be quite OK.
I am interested in any opinions.
Re: Scout + JPA. Where is good place to get EntityManager? [message #1828477 is a reply to message #1828473] Wed, 10 June 2020 12:27 Go to previous messageGo to next message
Ivan Motsch is currently offline Ivan MotschFriend
Messages: 154
Registered: March 2010
Senior Member
You can take a look at the (very basic) org.eclipse.scout.rt.server.jdbc.AbstractSqlService.SqlTransactionMember

The idea is that you couple the db transaction to a more general scout transaction as a scout transaction member.

That way both of your db transactions are attached to the same scout transaction as a member and get committed/rollback at the same time.

For the EM you would create one or two @ApplicationScope beans that represent resp. contain a reference to the EM over the application lifecycle.
For example Database1.java and Database2.java.

For each transaction take a look at org.eclipse.scout.rt.server.jdbc.AbstractSqlService#getTransaction.

This method could go to your new Database1.java and Database2.java as public members.

So a very simplified service implementation in your code looks like:
      ServerRunContexts.empty().withSession(new ServerSession()).withSubject(new Subject()).run(() -> {

      //...
      BEANS.get(Database1.class).getTransaction().createStatement().execute(...);
      //...

      });



Does that help?
Re: Scout + JPA. Where is good place to get EntityManager? [message #1828649 is a reply to message #1828477] Mon, 15 June 2020 18:57 Go to previous message
Darth Bolek is currently offline Darth BolekFriend
Messages: 25
Registered: August 2019
Junior Member
Thank you for input, but
I do not think this would work well in JPA world.

In JDBC world, to make it usable, the Database1.class would have to include connection pool. java.sql.Connection in theory should be thread safe, but in practice it depends on driver implementation. Even if it is thread safe, it may be through use of "synchronize" - and that implies performance bottleneck.
Instead of relying on the quality of the JDBC driver, I'd rather avoid the issue at all by using connection pool.

XA transactions are out of scope for me right now, but thank you for pointing me to scout jdbc package - I wasn't aware of its existence, and I am going to use it at some point.

I still think unique EM per each session is good idea. After some testing and session juggling (there is client, server and UiSession), it looks like staying at server side in ServerSession class is the pragmatic solution. I really like the concept of IGlobalSessionListener interface, but that was working only at the client (scout client) side. And since EM is internal object to server side, behind JPA, behind entity repository, creating this at the client side does not feel right.

So, what I have right now:
* all at server side
* EM created, cached and removed per each session
* entity repository/DAO using the session EM
* ScoutRole is the JPA entity

What I don't like about it, is instantiating repository every time it is used. Since each session has to have its own, it cannot really be @ApplicationScoped. Cache it? - it is quickly becoming application of caches... cache of user caches from another my thread...

For the client part I do not want it to be aware of any technical details from the server side, so connections, transactions, statements, entity managers are out of question. It is likely I will use another layer of business level repository, one per each client UI use case - but I am not there yet...

Here is the code:
package org.eclipse.scout.apps.helloworld.server;
...
public class ServerSession extends AbstractServerSession {

	private static final long serialVersionUID = 1L;
	private static final XLogger LOG = XLoggerFactory.getXLogger(ServerSession.class);
	
	
	public static String JPA_EM_CACHE_ID = ServerSession.class.getName() + ".JPA_EM_CACHE_ID";
	
			
	public ServerSession() {
		super(true);
	}

	/**
	 * @return The {@link ServerSession} which is associated with the current
	 *         thread, or {@code null} if not found.
	 */
	public static ServerSession get() {
		return ServerSessionProvider.currentSession(ServerSession.class);
	}

	@Override
	protected void execLoadSession() {
		LOG.entry();
		LOG.info("created a new session for {}", getUserId());
		
//		SERVICES.getService(IAccessControlService.class).clearCacheOfUserIds(Collections.singleton(getUserId()));
		
		// add EM to cache per session
		String sessionId = getId();
		LOG.debug("sessionId: {}", sessionId);
		
		ISession s = ISession.CURRENT.get();
		LOG.debug("session: {}", s);
		LOG.debug("sessionId from ISession: {}", (s!=null) ? s.getId() : s);
		
		getEMCache().get( new KeyContext(sessionId) );
		
		LOG.exit();
	}
	
	@Override
	public void stop() {
		LOG.entry();
		
		// remove EM from cache
		String sessionId = getId();
		LOG.debug("sessionId: {}", sessionId);
		
		KeyContext key = new KeyContext(sessionId);
		getEMCache().get(key).close();
		KeyCacheEntryFilter<KeyContext, EntityManager> filter = new KeyCacheEntryFilter<KeyContext, EntityManager>(Collections.singletonList(key));
		getEMCache().invalidate(filter, true);
		
		super.stop();
		
		LOG.exit();
	}
	
	private ICache<KeyContext, EntityManager> getEMCache() {
		LOG.entry();
		ICache<KeyContext, EntityManager> result = null;
		
		CacheRegistryService crs = BEANS.get(CacheRegistryService.class);
		result = crs.opt(JPA_EM_CACHE_ID);
		
		if (result == null) {
			result = createEMCache();
		}
		
		return LOG.exit(result);
	}
	
	private ICache<KeyContext, EntityManager> createEMCache() {
		LOG.entry();
		ICache<KeyContext, EntityManager> result = null;
		
		@SuppressWarnings("unchecked")
		ICacheBuilder<KeyContext, EntityManager> cacheBuilder = BEANS.get(ICacheBuilder.class);
		result = cacheBuilder
				.withCacheId(JPA_EM_CACHE_ID)
				.withValueResolver(new EMCacheValueResolver())
				.withThreadSafe(true)
				.build();
		
		return LOG.exit(result);
	}
		
}


package org.eclipse.scout.apps.helloworld.server;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;

import org.eclipse.scout.rt.platform.cache.ICacheValueResolver;
import org.slf4j.ext.XLogger;
import org.slf4j.ext.XLoggerFactory;

import io.poc.scout.ldap.security.cache.KeyContext;

public class EMCacheValueResolver implements ICacheValueResolver<KeyContext, EntityManager> {
	
	private static final XLogger LOG = XLoggerFactory.getXLogger(EMCacheValueResolver.class);
	
	private static final String PERSISTENCE_UNIT_NAME = "scout-roles";
	private static EntityManagerFactory factory = null;
	
	
	public EMCacheValueResolver() {
		LOG.entry();
		
		if (EMCacheValueResolver.factory == null) {
			factory = Persistence.createEntityManagerFactory(PERSISTENCE_UNIT_NAME);
		}
		
		LOG.exit();
	}

	@Override
	public EntityManager resolve(KeyContext key) {
		LOG.entry(key);
		EntityManager result = null;
		
		result = factory.createEntityManager();
		
		return LOG.exit(result);
	}
	
}


import org.eclipse.scout.rt.shared.session.IGlobalSessionListener;
import org.eclipse.scout.rt.shared.session.SessionEvent;
import org.slf4j.ext.XLogger;
import org.slf4j.ext.XLoggerFactory;

public class MySessionListener implements IGlobalSessionListener {
	
	private static final XLogger LOG = XLoggerFactory.getXLogger(MySessionListener.class);
	
	
	public MySessionListener() {
		LOG.entry();
		LOG.exit();
	}

	@Override
	public void sessionChanged(SessionEvent event) {
		LOG.entry(event);
		
		LOG.debug("event type: {} id: {}", event.getType(), event.getSource().getId());
		
		switch ( event.getType() ) {
		
		case SessionEvent.TYPE_STARTED : {
			LOG.debug("session started");
			break;
		}
		case SessionEvent.TYPE_STOPPING : {
			LOG.debug("session is stopping");
			break;
		}
		
		case SessionEvent.TYPE_STOPPED :
		default :
		// do nothing
			
		} // switch
		
		LOG.exit();
	}
	
}


public interface IScoutRepository<E> {
	
	E persist(E entity);
	
	void remove(E entity);
	
	<K> E findById(K id);
	
	Set<E> findAll();

	E findByName(String name);
	
}


public abstract class JPARepository<E> implements IScoutRepository<E> {
	
	private static final XLogger LOG = XLoggerFactory.getXLogger(JPARepository.class);
	
	
	protected Class<?> entityClass;
	protected EntityManager em = null;
	
	
	public JPARepository() {
		LOG.entry();
		
		final ParameterizedType genericSuperclass = (ParameterizedType) this.getClass().getGenericSuperclass();
		LOG.debug("genericSuperclass: {}", genericSuperclass);
		
		this.entityClass = (Class<?>) genericSuperclass.getActualTypeArguments()[0];
		LOG.debug("entityClass: {}", entityClass);
		
		ServerSession s = ServerSessionProvider.currentSession(ServerSession.class);
		LOG.debug("sessionId: {}", s.getId());
		String userId = s.getUserId();
		LOG.debug("userId from session: {}", userId);
		
		// get EM from cache
		em = getEMCache().get(new KeyContext(s.getId() ) );
		
		LOG.exit();
	}

	@Override
	public E persist(E entity) {
		LOG.entry(entity);
		E result = null;
		
		em.getTransaction().begin();
		em.persist(entity);
		em.getTransaction().commit();
		result = entity;
		
		return LOG.exit(result);
	}

	@Override
	public void remove(E entity) {
		LOG.entry(entity);
		
		em.getTransaction().begin();
		em.persist(entity);
		em.getTransaction().commit();
		
		LOG.exit();
	}

	@SuppressWarnings("unchecked")
	@Override
	public <K> E findById(K id) {
		LOG.entry(id);
		E result = null;
		
		em.getTransaction().begin();
		result = (E) em.find(entityClass, id);
		em.getTransaction().commit();
		
		return LOG.exit(result);
	}

	@SuppressWarnings("unchecked")
	@Override
	public Set<E> findAll() {
		LOG.entry();
		Set<E> result = null;
		
		em.getTransaction().begin();
		Query q = em.createQuery("SELECT e FROM " + entityClass.getName() + " e").setHint("org.hibernate.readOnly", true);
		result = (Set<E>) q.getResultStream().collect(Collectors.toSet());
		em.getTransaction().commit();
		
		return LOG.exit(result);
	}
	
	@Override
	public abstract E findByName(String name);
	
	
	protected ICache<KeyContext, EntityManager> getEMCache() {
		LOG.entry();
		ICache<KeyContext, EntityManager> result = null;
		
		CacheRegistryService crs = BEANS.get(CacheRegistryService.class);
		result = crs.get(ServerSession.JPA_EM_CACHE_ID);
				
		return LOG.exit(result);
	}
	
}


public interface IScoutRoleRepository<ScoutRole> extends IScoutRepository<ScoutRole> {
	
	Collection<ScoutRole> findByName(Collection<String> roles);
	
}


public class ScoutRoleRepository extends JPARepository<ScoutRole> implements IScoutRoleRepository<ScoutRole> {
	
	private static final XLogger LOG = XLoggerFactory.getXLogger(ScoutRoleRepository.class);
	
	
	public ScoutRoleRepository() {
		super();
		ServerSession s = ServerSessionProvider.currentSession(ServerSession.class);
		LOG.debug("sessionId: {}", s.getId());
		String userId = s.getUserId();
		LOG.debug("userId from session: {}", userId);
		
		LOG.exit();
	}
	
	@Override
	public ScoutRole findByName(String name) {
		LOG.entry(name);
		ScoutRole result = null;
		
		em.getTransaction().begin();
		Query q = em.createNativeQuery("SELECT A.ID, A.NAME, A.VER FROM SC_ROLE A WHERE A.NAME = ?", ScoutRole.class);
		q.setParameter(1, name);
		result = (ScoutRole) q.getSingleResult();
		em.getTransaction().commit();
		
		return LOG.exit(result);
	}

	@Override
	public Collection<ScoutRole> findByName(Collection<String> roles) {
		LOG.entry(roles);
		Collection<ScoutRole> result = null;
		
		// TODO
		
		return LOG.exit(result);
	}
	
}


How to use it:
		IScoutRepository<ScoutRole> repo = new ScoutRoleRepository();
		ScoutRole tmpRole = repo.findByName(name);
		
		// with cache
		for (String role : roles) {
			KeyContext key = new KeyContext(role);
			key.setCtx(repo);
			ScoutRole sr = cacheRoles.get(key);
			result.put(role, sr);
		}

Previous Topic:Cache - how to dispose it?
Next Topic:ICacheRegistryService and CacheBuilder possible issue
Goto Forum:
  


Current Time: Sat Dec 07 08:49:36 GMT 2024

Powered by FUDForum. Page generated in 0.03118 seconds
.:: Contact :: Home ::.

Powered by: FUDforum 3.0.2.
Copyright ©2001-2010 FUDforum Bulletin Board Software

Back to the top