Я хочу добиться следующего:
Внутри транзакции я хочу создать несколько сообщений журнала.Эти сообщения журнала должны быть записаны только в том случае, если транзакция успешно зафиксирована.Если транзакция откатывается, сообщения журнала не должны регистрироваться.
Я не мог найти ничего, чтобы достичь этого (используя 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 в качестве получателя очереди сообщений с поддержкой транзакций проблему времени или это просто усложнит ситуацию?
ОБНОВЛЕНИЕ:
Я думаю, что ведение журналав базу данных, а затем периодически записывать записи журнала из этой базы данных в файл в периодической задаче, как предлагается в комментариях, является очень хорошим решением этой проблемы:
PRO:
- Нормальная реализация позволяет
- интегрируется с TransactionManager
- Записи журнала в файлах журнала можно упорядочить по метке времени
CON:
- Файлы журналов не обновлены (в зависимости от интервала периодических задач)
- зависит от структуры базы данных
- регистрация простых нетранзакционных событий становится зависимой от dbconnection
- вероятнобольшие общие затраты на ведение журнала
Вот плюсы и минусы, которые я вижу с размещенной мной оболочкой:
Плюсы:
- база данных и фреймработа независимая
- простая реализация
- файлы журнала всегда актуальны
CONs:
- Записи журнала в файлах журналане упорядочено по метке-события-времени, а по методу-транзакции-времени (длинные транзакции приводят к очень смешанным файлам журналов.
rollback()
и commit()
должны вызываться «вручную», что может привести кошибки программирования (и возможный OutOfMemoryError, если вызов этих методов забыт) * 1063 *
Я думаю, что комбинация этих двух, например буферизация записей журнала в подходе "обертка", хуже, чем использование одного из двух упомянутыхподходы, из-за возможных несовместимых файлов журнала (забытых записей журнала из-за сбоя приложения).
Мое решение сейчас - сохранить мою "оболочку".Следующие причины имеют решающее значение для этого решения (в порядке важности):
- Я предпочитаю файлы журналов, которые всегда обновляются по сравнению с идеально упорядоченными записями журнала
- Длинные транзакции очень редкив моем случае
- Я могу сократить использование
rollback()
и commit()
до нескольких методов. - Это решение уже существует.
Кстати: я бы хотел улучшить свой английский.Поэтому, если вы заметите какие-либо ошибки в моей статье, я буду рад, если вы укажете на них.
ОБНОВЛЕНИЕ:
Проще говоря, я использую это какэто:
/*
* 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();
}
}
}
Все же это не идеально, но сейчас это кажется мне "достаточно хорошим решением".Следующим шагом будет использование аспектов для украшения моих транзакционных методов.
ОБНОВЛЕНИЕ:
Я добавляю это в своем вопросе, потому что я чувствовал себя плохо из-за принятия своего собственногоответ, хотя кто-то другой помог мне.
Я сейчас использую AOP-подход в основном со следующим Logger.(В моем реальном приложении у меня есть несколько подобных регистраторов, и все эти регистраторы управляются специальным одноэлементным менеджером.):
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()
вызываются автоматически после каждой транзакции
CON:
- (Записи журнала в файлах журнала упорядочены не по метке времени-события, а по метке-транзакции-завершения. Я думаю, что это не является большим недостатком, поскольку действия DB действительно имеют место во время совершения транзакции и записи LogRecordsодна транзакция все еще упорядочена правильно.)