【发布时间】:2014-09-08 13:22:56
【问题描述】:
我有一个使用 JBoss AS 7、Hibernate 和 HornetQ 的应用程序。我们使用 HornetQ 来处理进入应用程序的所有请求的日志记录。当一个请求进来时,我们发送一个“开始日志条目”消息,当使用该消息时,记录请求的 UUID、它的开始时间以及与请求相关的其他各种数据。当请求完成时,我们发送一条“结束日志条目”消息,当被消费时从数据库中查找起始日志条目(带有 UUID),并将结束时间添加到请求中。
经常发生的问题是在“结束日志条目”中完成的数据库查找找不到匹配的起始条目,因为实体管理器实际上还没有将起始日志条目提交/刷新到数据库。在结束日志条目尝试查找它之前,我需要保证开始日志条目在数据库中。有什么想法可以确保发生这种情况吗?
以下是我们拥有的相关类:
LogFilter.java - 这是 web.xml 中定义的过滤器,用于拦截所有请求并记录它们
public class LogFilter implements Filter {
@Inject
LoggingService ls;
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
// do stuff to create the log entry
try {
ls.startLogEntry(logEntry);
filterChain.doFilter(servletRequest, servletResponse);
} finally {
try {
ls.endLogEntry(logEntry);
} catch (Exception e2) {
// log stacktrace to the server logs
}
}
}
}
LoggingService.java - 注入 LogFilter 的服务
public class LoggingService {
private static Log logger = LogFactory.getLog(LoggingService.class);
@PersistenceContext(unitName="p1")
private EntityManager em;
// find a log entry by its generated uuid when the request started
public LogEntry getLogEntry(String uuid) {
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<LogEntry> c = cb.createQuery(LogEntry.class);
Root<LogEntry> root = c.from(LogEntry.class);
c.where(cb.equal(root.get(LogEntry_.uuid), uuid));
TypedQuery<LogEntry> tq = em.createQuery(c);
return JpaUtil.getSingleResultOrNull(tq);
}
// send message to write the log entry without the end time
public void startLogEntry(LogEntry logEntry) {
AuditLogProducer.sendLogEntryToQueue(logEntry);
}
public void endLogEntry(LogEntry logEntry) {
if (logEntry != null) {
logEntry.setRequestEnd(new Date());
AuditLogProducer.sendLogEntryToQueue(logEntry);
}
}
}
AuditLogProducer.java - 负责将消息发送到队列的类
public class AuditLogProducer {
private static final String QUEUE_LOOKUP = "jboss/queue/AuditLogQueue";
private static final String CONNECTION_FACTORY = "ConnectionFactory";
private static Log log = LogFactory.getLog(AuditLogProducer.class);
public static void sendLogEntryToQueue(LogEntry logEntry) {
QueueSession session = null;
QueueConnection connection = null;
try {
Context context = new InitialContext();
QueueConnectionFactory factory = (QueueConnectionFactory) context.lookup(CONNECTION_FACTORY);
connection = factory.createQueueConnection();
session = connection.createQueueSession(false, QueueSession.AUTO_ACKNOWLEDGE);
Queue queue = (Queue) context.lookup(QUEUE_LOOKUP);
QueueSender sender = session.createSender(queue);
ObjectMessage message = session.createObjectMessage();
message.setObject(logEntry);
sender.send(message);
session.close();
} catch (Exception e) {
log.error("problem sending message to queue: " + e.getMessage());
} finally {
if (session != null) {
try {
session.close();
connection.close();
} catch (Exception e) { }
}
}
}
}
AuditLogConsumer.java - 负责从队列中消费消息的类
@MessageDriven(
activationConfig={
@ActivationConfigProperty(
propertyName="destinationType",
propertyValue="javax.jms.Queue"
), @ActivationConfigProperty(
propertyName="destination",
propertyValue="queue/AuditLogQueue"
)
}
)
public class AuditLogConsumer implements MessageListener {
private static Log log = LogFactory.getLog(AuditLogConsumer.class);
@Inject
LogBuilder logBuilder;
@Inject
LogPolisher logPolisher;
@Override
public void onMessage(Message message) {
if (message instanceof ObjectMessage) {
ObjectMessage msg = (ObjectMessage) message;
LogEntry logEntry = null;
try {
logEntry = (LogEntry) msg.getObject();
} catch (JMSException e) {
log.error("Problem retrieving JMS message from the AuditLogQueue: " + e.getMessage(), e);
return;
}
if (logEntry.getRequestEnd() == null) {
logBuilder.insertLogEntry(logEntry);
} else {
logPolisher.updateLogEntry(logEntry);
}
}
}
}
LogBuilder.java - 单独负责将日志条目合并到数据库中的类 - 曾经属于一个类,但希望将创建和更新分为 2 个类,以确保将开始写入数据库准时(最终没有工作)
@Singleton
public class LogBuilder {
@PersistenceContext(unitName="p1")
private EntityManager em;
@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public void insertLogEntry(LogEntry log) {
em.merge(log);
em.flush();
}
}
LogPolisher.java - 单独负责查找开始日志条目、设置结束时间以及将新日志合并到数据库的类 - newLog.setRequestEnd(requestEnd) 有时会在上一行返回 null 时失败,因为它没有找到开始日志条目(因为它还没有被写入)。
@Singleton
public class LogPolisher {
private static Log log = LogFactory.getLog(LogPolisher.class);
@Inject
LoggingService loggingService;
@PersistenceContext(unitName="p1")
private EntityManager em;
@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public void updateLogEntry(LogEntry logEntry) {
Date requestEnd = logEntry.getRequestEnd();
try {
LogEntry newLog = loggingService.getLogEntry(logEntry.getUuid());
newLog.setRequestEnd(requestEnd);
em.merge(newLog);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
LogEntry.java 只是一个普通的休眠 bean,我不会在这里浪费更多的空间来展示它的代码。
【问题讨论】:
-
这是一个问题还是架构审查?我正在尝试确定您的问题在哪里
-
再次查看第二段。我在问如何确保实体管理器在第二个事务发生之前刷新第一个事务
-
但要了解您必须知道 EntityManager 在图片上的位置。无论如何..这里的表现会很糟糕。您每次都在创建消费者和生产者......如果您要使其同步,为什么还需要消息传递?这里有一个数据库会更好。如果您想要真正的消息传递,请让消费者或生产者在某处保持开放。您实际上依赖于队列顺序的事实告诉我,您将消息系统混淆为数据库。 Database=store/retrieve Messaging=deliver
标签: hibernate jboss jboss7.x messaging hornetq