我创建了一个需要多租户(单表)的Web应用程序。 @Multitenant功能看起来非常有趣,但我不知道如何正确处理它。我使用以下组件:
对应用程序的每个请求都分配给用户和特定客户。我可以从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;
但是当我注释掉&#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)是容器管理的,但我不知道如何处理它。
对于任何帮助,我非常感激。 亲切的问候 洛伦茨