SpringAOPを使用してDBテーブルへのログ記録を実装しようとしています。「ログインテーブル」とは、ドメインオブジェクトの通常のテーブルでCREATED / UPDATED/DELETEDされたレコードに関する特別なログテーブル情報を書き込むことを意味します。
コードの一部を記述しましたが、1つを除いてすべて正常に機能しています。トランザクションがロールバックされても、ログテーブルの変更は正常にコミットされます。私のAOPアドバイスでは、私のビジネスおよびDAOレイヤーと同じトランザクションが使用されているため、私にとっては奇妙です。(AOPアドバイスから、トランザクション伝播MANDATORYを使用して特別なマネージャークラスのメソッドを呼び出し、ビジネスレイヤー、daoレイヤー、およびAOPアドバイスでトランザクション名TransactionSynchronizationManager.getCurrentTransactionName()を確認しました。これは同じです)。
誰かが実際に同様のことを実装しようとしましたか?AOPアドバイスでビジネスレイヤーと同じトランザクションを使用し、ビジネスレイヤーでエラーが発生した場合にAOPアドバイスで行われた変更をロールバックすることは可能ですか?
よろしくお願いします。
編集
ロールバックの問題は、AOPアドバイスから行われた変更に対してのみ発生することを明確にしたいと思います。DAOレイヤーで行われたすべての変更は、正常にロールバックされます。つまり、たとえば、何らかの例外がスローされた場合、DAOレイヤーで行われた変更は正常にロールバックされますが、ログテーブルの情報は保存(コミット)されます。しかし、上記のAOPアドバイスで書いたように、同じトランザクションが使用しているため、なぜそのようなのか理解できません。
編集2
AOPアドバイスでログテーブルに書き込んでいるコードの一部をデバッガーで確認しましたが、ステートメントの実行直後とトランザクションメソッドの前に変更がDBにコミットされたため、JdbcTemplateの更新メソッドはトランザクション外で実行されているようです。終了した。
編集3
私はこの問題を解決しました。実際、それは私の愚かな過ちでした。MySQLを使用しています。ログテーブルの作成後、DBエンジンを変更せず、HeidySQLはデフォルトでMyIsamを設定しました。しかし、MyIsamはトランザクションをサポートしていないため、DBエンジンをInnoDBに変更し(他のすべてのテーブルと同様)、すべてが完全に機能しています。
助けてくれてありがとう、そして邪魔してすみません。
誰かが興味を持っているなら、ここに私のアプローチを説明する簡単な例があります。
saveメソッドを持つDAOクラスについて考えてみます。
@Repository(value="jdbcUserDAO")
@Transactional(propagation=Propagation.SUPPORTS, readOnly=true, rollbackFor=Exception.class)
public class JdbcUserDAO implements UserDAO {
@Autowired
private JdbcTemplate jdbcTemplate;
@LoggedOperation(affectedRows = AffectedRows.ONE, loggedEntityClass = User.class, operationName = OperationName.CREATE)
@Transactional(propagation=Propagation.REQUIRED, readOnly=false, rollbackFor=Exception.class)
@Override
public User save(final User user) {
if (user == null || user.getRole() == null) {
throw new IllegalArgumentException("Input User object or nested Role object should not be null");
}
KeyHolder keyHolder = new GeneratedKeyHolder();
jdbcTemplate.update(new PreparedStatementCreator() {
@Override
public PreparedStatement createPreparedStatement(Connection connection)
throws SQLException {
PreparedStatement ps = connection.prepareStatement(SQL_INSERT_USER, new String[]{"ID"});
ps.setString(1, user.getUsername());
ps.setString(2, user.getPassword());
ps.setString(3, user.getFullName());
ps.setLong(4, user.getRole().getId());
ps.setString(5, user.geteMail());
return ps;
}
}, keyHolder);
user.setId((Long) keyHolder.getKey());
VacationDays vacationDays = user.getVacationDays();
vacationDays.setId(user.getId());
// Create related vacation days record.
vacationDaysDAO.save(vacationDays);
user.setVacationDays(vacationDays);
return user;
}
}
アスペクトは次のようになります。
@Component
@Aspect
@Order(2)
public class DBLoggingAspect {
@Autowired
private DBLogManager dbLogManager;
@Around(value = "execution(* com.crediteuropebank.vacationsmanager.server.dao..*.*(..)) " +
"&& @annotation(loggedOperation)", argNames="loggedOperation")
public Object doOperation(final ProceedingJoinPoint joinPoint,
final LoggedOperation loggedOperation) throws Throwable {
Object[] arguments = joinPoint.getArgs();
/*
* This should be called before logging operation.
*/
Object retVal = joinPoint.proceed();
// Execute logging action
dbLogManager.logOperation(arguments,
loggedOperation);
return retVal;
}
}
そして、これが私のdbログマネージャークラスLooksLikeのやり方です:
@Component("dbLogManager")
public class DBLogManager {
@Autowired
private JdbcTemplate jdbcTemplate;
@InjectLogger
private Logger logger;
@Transactional(rollbackFor={Exception.class}, propagation=Propagation.MANDATORY, readOnly=false)
public void logOperation(final Object[] inputArguments, final LoggedOperation loggedOperation) {
try {
/*
* Prepare query and array of the arguments
*/
jdbcTemplate.update(insertQuery.toString(),
insertedValues);
} catch (Exception e) {
StringBuilder sb = new StringBuilder();
// Prepare log string
logger.error(sb.toString(), e);
}
}