EclipseLink @Multitenant EntityManager /事务处理

时间:2015-06-07 17:02:43

标签: java eclipselink jta multi-tenant

我创建了一个需要多租户(单表)的Web应用程序。 @Multitenant功能看起来非常有趣,但我不知道如何正确处理它。我使用以下组件:

  • WildFly 8.2 Final
  • EclipseLink v2.6
  • Apache Shiro v1.2.3

对应用程序的每个请求都分配给用户和特定客户。我可以从Apache Shiro读取租户ID并设置tenant.id属性以创建EntityManager(应用程序管理)。

第一种方法是在ServletFilter中使用EntityTransaction,其中包含EntityManager-per-request模式和RESSOURCE_LOCAL事务(此处无异常处理):

public class EntityManagerServletFilter implements Filter {

private EntityManagerFactory entityManagerFactory;

@Override
public void init(FilterConfig filterConfig) throws ServletException {
    entityManagerFactory = Persistence.createEntityManagerFactory("MESaaS");
}

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    Session session = SecurityUtils.getSubject().getSession();
    Long tenantId = null;
    if(session != null) {
        tenantId = (Long) session.getAttribute("tenantId");
    }
    EntityManager em = entityManagerFactory.createEntityManager();
    em.setProperty("tenant.id", tenantId);
    EntityTransaction trn = em.getTransaction();
    trn.begin();

    EntityManagerProvider.setCurrentEntityManager(em);

    chain.doFilter(request, response);

    trn.commit();
}

@Override
public void destroy() {
    entityManagerFactory = null;
}

}

此方法无效并引发以下异常:



Exception Description: An exception was thrown when trying to get a primary key class instance.
Internal Exception: java.lang.InstantiationException: java.lang.Long
Descriptor: RelationalDescriptor(ch.hauserag.mesaas.entities.Hierarchy --> [DatabaseTable(HIERARCHY)])
	at org.apache.shiro.web.servlet.AdviceFilter.cleanup(AdviceFilter.java:196) [shiro-web-1.2.3.jar:1.2.3]
	at org.apache.shiro.web.filter.authc.AuthenticatingFilter.cleanup(AuthenticatingFilter.java:155) [shiro-web-1.2.3.jar:1.2.3]
	at org.apache.shiro.web.servlet.AdviceFilter.doFilterInternal(AdviceFilter.java:148) [shiro-web-1.2.3.jar:1.2.3]
	at org.apache.shiro.web.servlet.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:125) [shiro-web-1.2.3.jar:1.2.3]
	at org.apache.shiro.web.servlet.ProxiedFilterChain.doFilter(ProxiedFilterChain.java:66) [shiro-web-1.2.3.jar:1.2.3]
	at org.apache.shiro.web.servlet.AdviceFilter.executeChain(AdviceFilter.java:108) [shiro-web-1.2.3.jar:1.2.3]
	at org.apache.shiro.web.servlet.AdviceFilter.doFilterInternal(AdviceFilter.java:137) [shiro-web-1.2.3.jar:1.2.3]
	at org.apache.shiro.web.servlet.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:125) [shiro-web-1.2.3.jar:1.2.3]
	at org.apache.shiro.web.servlet.ProxiedFilterChain.doFilter(ProxiedFilterChain.java:66) [shiro-web-1.2.3.jar:1.2.3]
	at org.apache.shiro.web.servlet.AbstractShiroFilter.executeChain(AbstractShiroFilter.java:449) [shiro-web-1.2.3.jar:1.2.3]
	at org.apache.shiro.web.servlet.AbstractShiroFilter$1.call(AbstractShiroFilter.java:365) [shiro-web-1.2.3.jar:1.2.3]
	at org.apache.shiro.subject.support.SubjectCallable.doCall(SubjectCallable.java:90) [shiro-core-1.2.3.jar:1.2.3]
	at org.apache.shiro.subject.support.SubjectCallable.call(SubjectCallable.java:83) [shiro-core-1.2.3.jar:1.2.3]
	at org.apache.shiro.subject.support.DelegatingSubject.execute(DelegatingSubject.java:383) [shiro-core-1.2.3.jar:1.2.3]
	at org.apache.shiro.web.servlet.AbstractShiroFilter.doFilterInternal(AbstractShiroFilter.java:362) [shiro-web-1.2.3.jar:1.2.3]
	at org.apache.shiro.web.servlet.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:125) [shiro-web-1.2.3.jar:1.2.3]
	at io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:60) [undertow-servlet-1.1.0.Final.jar:1.1.0.Final]
	at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:132) [undertow-servlet-1.1.0.Final.jar:1.1.0.Final]
	at io.undertow.servlet.handlers.FilterHandler.handleRequest(FilterHandler.java:85) [undertow-servlet-1.1.0.Final.jar:1.1.0.Final]
	at io.undertow.servlet.handlers.security.ServletSecurityRoleHandler.handleRequest(ServletSecurityRoleHandler.java:61) [undertow-servlet-1.1.0.Final.jar:1.1.0.Final]
	at io.undertow.servlet.handlers.ServletDispatchingHandler.handleRequest(ServletDispatchingHandler.java:36) [undertow-servlet-1.1.0.Final.jar:1.1.0.Final]
	at org.wildfly.extension.undertow.security.SecurityContextAssociationHandler.handleRequest(SecurityContextAssociationHandler.java:78)
	at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43) [undertow-core-1.1.0.Final.jar:1.1.0.Final]
	at io.undertow.servlet.handlers.security.SSLInformationAssociationHandler.handleRequest(SSLInformationAssociationHandler.java:131) [undertow-servlet-1.1.0.Final.jar:1.1.0.Final]
	at io.undertow.servlet.handlers.security.ServletAuthenticationCallHandler.handleRequest(ServletAuthenticationCallHandler.java:56) [undertow-servlet-1.1.0.Final.jar:1.1.0.Final]
	at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43) [undertow-core-1.1.0.Final.jar:1.1.0.Final]
	at io.undertow.security.handlers.AuthenticationConstraintHandler.handleRequest(AuthenticationConstraintHandler.java:51) [undertow-core-1.1.0.Final.jar:1.1.0.Final]
	at io.undertow.security.handlers.AbstractConfidentialityHandler.handleRequest(AbstractConfidentialityHandler.java:45) [undertow-core-1.1.0.Final.jar:1.1.0.Final]
	at io.undertow.servlet.handlers.security.ServletConfidentialityConstraintHandler.handleRequest(ServletConfidentialityConstraintHandler.java:63) [undertow-servlet-1.1.0.Final.jar:1.1.0.Final]
	at io.undertow.servlet.handlers.security.ServletSecurityConstraintHandler.handleRequest(ServletSecurityConstraintHandler.java:56) [undertow-servlet-1.1.0.Final.jar:1.1.0.Final]
	at io.undertow.security.handlers.AuthenticationMechanismsHandler.handleRequest(AuthenticationMechanismsHandler.java:58) [undertow-core-1.1.0.Final.jar:1.1.0.Final]
	at io.undertow.servlet.handlers.security.CachedAuthenticatedSessionHandler.handleRequest(CachedAuthenticatedSessionHandler.java:70) [undertow-servlet-1.1.0.Final.jar:1.1.0.Final]
	at io.undertow.security.handlers.SecurityInitialHandler.handleRequest(SecurityInitialHandler.java:76) [undertow-core-1.1.0.Final.jar:1.1.0.Final]
	at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43) [undertow-core-1.1.0.Final.jar:1.1.0.Final]
	at org.wildfly.extension.undertow.security.jacc.JACCContextIdHandler.handleRequest(JACCContextIdHandler.java:61)
	at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43) [undertow-core-1.1.0.Final.jar:1.1.0.Final]
	at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43) [undertow-core-1.1.0.Final.jar:1.1.0.Final]
	at io.undertow.servlet.handlers.ServletInitialHandler.handleFirstRequest(ServletInitialHandler.java:261) [undertow-servlet-1.1.0.Final.jar:1.1.0.Final]
	at io.undertow.servlet.handlers.ServletInitialHandler.dispatchRequest(ServletInitialHandler.java:247) [undertow-servlet-1.1.0.Final.jar:1.1.0.Final]
	at io.undertow.servlet.handlers.ServletInitialHandler.access$000(ServletInitialHandler.java:76) [undertow-servlet-1.1.0.Final.jar:1.1.0.Final]
	at io.undertow.servlet.handlers.ServletInitialHandler$1.handleRequest(ServletInitialHandler.java:166) [undertow-servlet-1.1.0.Final.jar:1.1.0.Final]
	at io.undertow.server.Connectors.executeRootHandler(Connectors.java:197) [undertow-core-1.1.0.Final.jar:1.1.0.Final]
	at io.undertow.server.HttpServerExchange$1.run(HttpServerExchange.java:759) [undertow-core-1.1.0.Final.jar:1.1.0.Final]
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) [rt.jar:1.8.0_40]
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) [rt.jar:1.8.0_40]
	at java.lang.Thread.run(Thread.java:745) [rt.jar:1.8.0_40]
Caused by: javax.persistence.RollbackException: Exception [EclipseLink-222] (Eclipse Persistence Services - 2.6.0.v20150309-bf26070): org.eclipse.persistence.exceptions.DescriptorException
Exception Description: An exception was thrown when trying to get a primary key class instance.
Internal Exception: java.lang.InstantiationException: java.lang.Long
Descriptor: RelationalDescriptor(ch.hauserag.mesaas.entities.Hierarchy --> [DatabaseTable(HIERARCHY)])
	at org.eclipse.persistence.internal.jpa.transaction.EntityTransactionImpl.commit(EntityTransactionImpl.java:159) [eclipselink-2.6.0.jar:2.6.0.v20150309-bf26070]
	at ch.hauserag.mesaas.daos.EntityManagerServletFilter.doFilter(EntityManagerServletFilter.java:61) [classes:]
	at io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:60) [undertow-servlet-1.1.0.Final.jar:1.1.0.Final]
	at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:132) [undertow-servlet-1.1.0.Final.jar:1.1.0.Final]
	at org.apache.shiro.web.servlet.ProxiedFilterChain.doFilter(ProxiedFilterChain.java:61) [shiro-web-1.2.3.jar:1.2.3]
	at org.apache.shiro.web.servlet.AdviceFilter.executeChain(AdviceFilter.java:108) [shiro-web-1.2.3.jar:1.2.3]
	at org.apache.shiro.web.servlet.AdviceFilter.doFilterInternal(AdviceFilter.java:137) [shiro-web-1.2.3.jar:1.2.3]
	... 43 more
Caused by: Exception [EclipseLink-222] (Eclipse Persistence Services - 2.6.0.v20150309-bf26070): org.eclipse.persistence.exceptions.DescriptorException
Exception Description: An exception was thrown when trying to get a primary key class instance.
Internal Exception: java.lang.InstantiationException: java.lang.Long
Descriptor: RelationalDescriptor(ch.hauserag.mesaas.entities.Hierarchy --> [DatabaseTable(HIERARCHY)])
	at org.eclipse.persistence.exceptions.DescriptorException.exceptionAccessingPrimaryKeyInstance(DescriptorException.java:2084) [eclipselink-2.6.0.jar:2.6.0.v20150309-bf26070]
	at org.eclipse.persistence.internal.jpa.CMP3Policy.getPKClassInstance(CMP3Policy.java:205) [eclipselink-2.6.0.jar:2.6.0.v20150309-bf26070]
	at org.eclipse.persistence.descriptors.CMPPolicy.createPrimaryKeyInstance(CMPPolicy.java:441) [eclipselink-2.6.0.jar:2.6.0.v20150309-bf26070]
	at org.eclipse.persistence.internal.sessions.UnitOfWorkImpl.updateDerivedIds(UnitOfWorkImpl.java:5445) [eclipselink-2.6.0.jar:2.6.0.v20150309-bf26070]
	at org.eclipse.persistence.internal.sessions.UnitOfWorkImpl.calculateChanges(UnitOfWorkImpl.java:653) [eclipselink-2.6.0.jar:2.6.0.v20150309-bf26070]
	at org.eclipse.persistence.internal.sessions.UnitOfWorkImpl.commitToDatabaseWithChangeSet(UnitOfWorkImpl.java:1516) [eclipselink-2.6.0.jar:2.6.0.v20150309-bf26070]
	at org.eclipse.persistence.internal.sessions.RepeatableWriteUnitOfWork.commitRootUnitOfWork(RepeatableWriteUnitOfWork.java:278) [eclipselink-2.6.0.jar:2.6.0.v20150309-bf26070]
	at org.eclipse.persistence.internal.sessions.UnitOfWorkImpl.commitAndResume(UnitOfWorkImpl.java:1169) [eclipselink-2.6.0.jar:2.6.0.v20150309-bf26070]
	at org.eclipse.persistence.internal.jpa.transaction.EntityTransactionImpl.commit(EntityTransactionImpl.java:134) [eclipselink-2.6.0.jar:2.6.0.v20150309-bf26070]
	... 49 more
Caused by: java.lang.InstantiationException: java.lang.Long
	at java.lang.Class.newInstance(Class.java:427) [rt.jar:1.8.0_40]
	at org.eclipse.persistence.internal.jpa.CMP3Policy.getPKClassInstance(CMP3Policy.java:201) [eclipselink-2.6.0.jar:2.6.0.v20150309-bf26070]
	... 56 more
Caused by: java.lang.NoSuchMethodException: java.lang.Long.<init>()
	at java.lang.Class.getConstructor0(Class.java:3082) [rt.jar:1.8.0_40]
	at java.lang.Class.newInstance(Class.java:412) [rt.jar:1.8.0_40]
	... 57 more
&#13;
&#13;
&#13;

但是当我注释掉&#34; trn.begin();&#34;和&#34; trn.commit();&#34;一切正常,除了数据库上的操作不会持久化(如预期的那样)。请求异步,我认为这是问题(在begin()和commit()之间,EntityManager不再相同)。

另一种方法是使用请求范围的EntityManger并使用JTA:

public class EntityManagerRequestScoped {

@PersistenceUnit(unitName="MESaaS")
EntityManagerFactory emf;

@Produces
@RequestScoped
public EntityManager getEntityManager() {
    Session session = SecurityUtils.getSubject().getSession();
    Long tenantId = null;
    if(session != null) {
        tenantId = (Long) session.getAttribute("tenantId");
    }
    EntityManager em = emf.createEntityManager();
    em.setProperty("tenant.id", tenantId);
    return em;      
}

}

我的抽象DAO看起来像:

public abstract class AbstractDAO<T> {

@Inject
EntityManager em;

abstract public Class<T> getEntityClass();

protected EntityManager getEntityManager() {
    return em;
}

public T createOrUpdate(T obj) {
    T result = getEntityManager().merge(obj);
    return result;
}

public T getById(long id) {
    T result = getEntityManager().find(getEntityClass(), id);
    return result;
}

public List<T> getAll() {
    TypedQuery<T> q = getEntityManager().createQuery("SELECT o FROM "
            + getTableName() + " o", getEntityClass());
    List<T> result = q.getResultList();
    return result;
}

public void delete(long id) {
    getEntityManager().remove(getById(id));
}

}

具体的DAO看起来像:

public class UserDAO extends AbstractDAO<User> {

@Override
public Class<User> getEntityClass() {
    return User.class;
}

public User getUserByEmail(String email) {
    String query =  "SELECT u FROM " + getTableName() + " u " +
                    "WHERE u.email =:email";
    TypedQuery<User> q = getEntityManager().createQuery(query, User.class);
    q.setParameter("email", email);
    User user = q.getResultList().isEmpty() ? null : q.getSingleResult();
    return user;
}

}

然后将此DAO注入服务层:

public class AdministrationService {

@Inject
UserDAO userDao;

@Inject
TenantDAO tenantDao;

@Inject
RoleDAO roleDao;

@Inject
Subject subject;

@Inject
TenantResolver<Long> currentTenant;

public List<User> getAllUsers() {
    return userDao.getAll();
}

public User getUserById(long id) {
    return userDao.getById(id);
}

public User getUserByEmailForLogin(String email, long tenantId) {
    return userDao.getUserByEmail(email, tenantId);
}

public User getUserByEmail(String email) {
    return userDao.getUserByEmail(email);
}

public Tenant getTenantByEmail(String email) {
    return tenantDao.getTenantByUserName(email);
}

public List<Tenant> getAllTenants() {
    return tenantDao.getAll();
}

@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public void insertUser(User user) {
    RandomNumberGenerator rng = new SecureRandomNumberGenerator();
    ByteSource salt = rng.nextBytes();
    String hashedPasswordBase64 = new Sha256Hash(user.getPassword(), salt, 1024).toBase64();

    user.setActive(true);
    user.setPassword(hashedPasswordBase64);
    user.setSalt(salt.toString());

    Tenant t = tenantDao.getById(currentTenant.getCurrentTenantId());
    user.setTenant(t);

    userDao.createOrUpdate(user);
}

public void deleteUser(long id) {
    userDao.delete(id);
}

}

然后将此服务注入REST外观:

@Path("/administration/")
public class AdministrationRESTService {

private static final Logger LOG = LoggerFactory.getLogger(AdministrationRESTService.class);

@Inject
private Subject subject;

@Inject
private AdministrationService administrationService;

@Inject
private TenantResolver<Long> tenantResolver;

@GET
@Path("user")
@Produces("application/json")
public Response getUsers() {
    List<User> users = administrationService.getAllUsers();
    return Response.ok(DTOFactory.getUserDTOs(users)).build();
}

@GET
@Path("user/{id}")
@Produces("application/json")
public Response getUser(@PathParam("id") long id) {
    User user = administrationService.getUserById(id);
    return Response.ok(DTOFactory.getUserDTO(user)).build();
}

@POST
@Path("user")
@Consumes("application/json")
@Produces("application/json")
public Response createUser(UserDTO user) {
    if(Validator.validateUser(user)) {
        User u = DTOFactory.getUserEntity(user);
        administrationService.insertUser(u);
        return Response.status(Status.OK).build();
    }
    return Response.status(Status.BAD_REQUEST).build();
}

@DELETE
@Path("user/{id}")
@Produces("application/json")
public Response deleteUser(@PathParam("id") long id) {
    administrationService.deleteUser(id);
    return Response.status(Status.OK).build();
}

}

我已经读过EntityManager可以使用em.joinTransaction()加入JTA事务。但是我如何以及在哪里可以处理应用程序管理的EntityManager的连接和生命周期? 据我所知,注释@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)是容器管理的,但我不知道如何处理它。

对于任何帮助,我非常感激。 亲切的问候 洛伦茨

0 个答案:

没有答案