Я хочу добиться следующего:
Внутри транзакции я хочу создать несколько сообщений журнала. Эти сообщения журнала должны быть записаны только в том случае, если транзакция успешно зафиксирована. Если транзакция откатывается, сообщения журнала не должны регистрироваться.
Я не смог найти ничего, чтобы добиться этого (используя spring, hibernate, atomikos), поэтому я написал эту маленькую оболочку (я упустил пару удобных методов):
public class TransactionLogger {
private Logger logger;
private Map<Long, LinkedList<LogRecord>> threadBuffers =
new HashMap<Long, LinkedList<LogRecord>>();
public TransactionLogger(Logger logger) {
this.logger = logger;
}
private void addRecord(LogRecord rec) {
LinkedList<LogRecord> list =
threadBuffers.get(Thread.currentThread().getId());
if (list == null) {
list = new LinkedList<LogRecord>();
threadBuffers.put(Thread.currentThread().getId(), list);
}
list.add(rec);
}
private LinkedList<LogRecord> getRecords() {
if (threadBuffers.containsKey(Thread.currentThread().getId())) {
return threadBuffers.remove(Thread.currentThread().getId());
} else {
return new LinkedList<LogRecord>();
}
}
public void commit() {
for (LogRecord rec : getRecords()) {
rec.setLoggerName(logger.getName());
logger.log(rec);
}
}
public void rollback() {
getRecords();
}
/**
* If the resulting log entry should contain the sourceMethodName
* you should use logM(Level,String,String) instead,
* otherwise TransactionLogger.commit() will get
* inferred as sourceMethodName.
*/
public void log(Level l, String sourceClassName, String msg) {
LogRecord rec = new LogRecord(l, msg);
rec.setSourceClassName(sourceClassName);
addRecord(rec);
}
/**
* Same as log(Level,String,String), but the sourceMethodName gets set.
*/
public void logM(Level l, String sourceClassName, String msg) {
StackTraceElement[] trace = Thread.currentThread().getStackTrace();
LogRecord rec = new LogRecord(l, msg);
rec.setSourceClassName(sourceClassName);
if (trace != null && trace.length > 1) {
rec.setSourceMethodName(trace[2].getMethodName());
}
addRecord(rec);
}
}
Что вы думаете об этом подходе? Есть ли какие-либо серьезные или незначительные недостатки или проблемы с ним? Или еще лучше, есть ли готовые решения для этого?
ОБНОВЛЕНИЕ:
Поскольку я также использую JTA, у меня появилась новая идея.Решит ли реализация TransactionLogger в качестве получателя очереди сообщений с поддержкой транзакций проблему времени или она просто усложнит ситуацию?
ОБНОВЛЕНИЕ:
Я думаю, что запись в базу данных, а затем периодическая запись записей журнала из этой базы данных в файл в периодической задаче, как это предлагается в комментариях, является очень хорошим решением этой проблемы:
PROs:
Минусы:
Вот плюсы и минусы, которые я вижу в опубликованной оболочке:
PROs :
Недостатки:
rollb ack()
и commit()
должны вызываться "вручную", что может привести к ошибкам программирования (и возможному OutOfMemoryError, если вызов этих методов забыт)Я думаю, что комбинация этих два, такие как буферизация записей журнала в подходе «обертка», хуже, чем использование одного из двух упомянутых подходов, из-за возможных несогласованных файлов журнала (забытые записи журнала из-за сбоя приложения).
Теперь я решил оставить свою "обертку". Следующие причины имеют решающее значение для этого решения (в порядке важности):
rollback()
и commit()
до нескольких методов.Кстати: Я хотел бы улучшить свой английский. Так что, если вы заметили какие-то ошибки в моей статье, буду рад, если вы на них укажете.
ОБНОВЛЕНИЕ:
Simplefied Я использую это так:
/*
* This objects contains one or more TransactionLogger(s) and exposes rollback()
* and commit() through rollbackLogs() and commitLogs().
*/
@Autowired
private ITransactionalServer server;
public void someMethod(String someParam) {
boolean rollback = false;
try {
/*
* This method is for example annotated with @Transactional.
*/
return server.someTransactionalMethod(someParam);
} catch (Exception ex) {
logError(ex);
rollback = true;
} finally {
if (rollback) {
server.rollbackLogs();
} else {
server.commitLogs();
}
}
}
Тем не менее, это не идеально, но прямо сейчас мне это кажется "достаточно хорошим решением". Следующим шагом будет использование аспектов для украшения моих транзакционных методов.
ОБНОВЛЕНИЕ:
Я добавляю это в свой вопрос, потому что мне было неловко принимать свой собственный ответ, хотя кто-то другой помог мне в пути.
Сейчас я использую АОП-подход в основном со следующим регистратором. (В моем реальном приложении у меня есть несколько таких регистраторов, и все эти регистраторы управляются настраиваемым менеджером синглтона.):
public class AopLogger extends Logger {
public static AopLogger getLogger(String name) {
LogManager manager = LogManager.getLogManager();
Object l = manager.getLogger(name);
if (l == null) {
manager.addLogger(new AopLogger(name));
}
l = manager.getLogger(name);
return (AopLogger)l;
}
private Map<Long, LinkedList<LogRecord>> threadBuffers = new HashMap<Long, LinkedList<LogRecord>>();
public AopLogger(String name) {
super(name, null);
}
public void beginTransaction() {
LinkedList<LogRecord> list = threadBuffers.get(Thread.currentThread().getId());
if (list == null) {
list = new LinkedList<LogRecord>();
threadBuffers.put(Thread.currentThread().getId(), list);
}
}
private void addRecord(LogRecord rec) {
LinkedList<LogRecord> list = threadBuffers.get(Thread.currentThread().getId());
if (list != null) {
list.add(rec);
} else {
super.log(record);
}
}
private LinkedList<LogRecord> getRecords() {
if (threadBuffers.containsKey(Thread.currentThread().getId())) {
return threadBuffers.remove(Thread.currentThread().getId());
} else {
return new LinkedList<LogRecord>();
}
}
public void commit() {
for (LogRecord rec : getRecords()) {
rec.setMillis(System.currentTimeMillis());
super.log(rec);
}
}
public void rollback() {
getRecords();
}
public void log(LogRecord record) {
addRecord(record);
}
}
и этот аспект:
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Service;
@Service
@Aspect
@Order(10)
public class AopLogManager implements Ordered {
@Autowired
private AopLogger logger;
private Logger errorLogger = Logger.getLogger("ExceptionLogger");
@Around("@annotation(org.springframework.transaction.annotation.Transactional)")
public Object logTransaction(ProceedingJoinPoint pjp) throws Throwable {
logger.beginTransaction();
Exception ex = null;
try {
return pjp.proceed();
} catch (Exception e) {
ex = e;
throw e;
} finally {
if (ex != null) {
logger.rollback();
errorLogger.severe(ex.getMessage());
} else {
logger.commit();
}
}
}
private int mOrder;
@Override
public int getOrder() {
return mOrder;
}
public void setOrder(int order) {
mOrder = order;
}
}
В моем applicationContext.xml у меня есть следующие строки:
<aop:aspectj-autoproxy />
<tx:annotation-driven transaction-manager="springTransactionManager" order="5"/>
Это работает нормально до сих пор.
PRO:
rollback()
и commit()
вызываются автоматически после каждая транзакцияПРОТИВ: