Reputation: 10652
is there any chance to get the current sql statement somehow in MyBatis from a Spring DataSource or similar? Unfortunately, the MyBatisExceptionTranslator only puts the statement inside the message which makes it hard to use. Also it doesn't seem to put the actual parameters somewhere.
What I would like to do is write a PersistenceExceptionTranslator for MyBatis/Oracle that is able to use the current statement AND the parameters given to that statement to determine the final Exception, allowing for much better error messages. Any chance of doing that?
Is there any chance to do that?
Upvotes: 2
Views: 747
Reputation: 10652
So, while I haven't found a perfect answer yet (still hoping), I have at least found one possibility:
By using Spring AOP I can wrap an Aspect around each mapper method (using Mapper interfaces). This aspect can then determine, by the mapper class name and the called method name, the id of the statement, for example...
com.example.mappers.SomeMapper.findSomethingById
The Aspect can use the Configuration (via an injected SqlSessionFactory
) catch the PersistenceException thrown by MyBatis and get the MappedStatement and the paramter(s) given to the mapper to have all the details about the statement (sql + parameters):
@Around("execution(* com.example.mappers..*(..))")
public Object beforeTransactional(final ProceedingJoinPoint joinPoint) throws Throwable {
try {
return joinPoint.proceed();
} catch (final PersistenceException ex) {
final MethodSignature signature = (MethodSignature) joinPoint.getSignature();
final Class<?> mapperInterface = findMapper(joinPoint.getTarget());
// StatementInfo : simple bean to store sql statement and parameter object
final StatementInfo info = findMappedStatement(signature.getMethod(), mapperInterface, joinPoint.getArgs());
// a custom Exception extends PersistenceException
throw new CustomPersistenceException(info, ex);
}
protected SQLException findSqlException(Throwable ex) {
while (ex != null && !(ex instanceof SQLException)) {
ex = ex.getCause();
}
return (SQLException) ex;
}
protected StatementInfo findMappedStatement(final Method method, final Class<?> mapperInterface, final Object[] args) {
final String statementId = mapperInterface.getPackage().getName() + "." + mapperInterface.getSimpleName() + "." + method.getName();
final MappedStatement mappedStatement = this.configuration.getMappedStatement(statementId);
final Object arg = args == null || args.length == 0 ? null : args[0];
final BoundSql boundSql = mappedStatement.getBoundSql(arg);
final String sql = StringUtils.normalizeSpace(boundSql.getSql());
return new StatementInfo(sql, arg);
}
Still, that solution is far from perfect, since it does not allow me to get the sql code of the sql statement actually creating an error, just the "parent" sql statement called. For example:
If my (parent) statement calls various other (child) statements, for example associations or collections, and one of these creates an error, then my solution only gives information about the parent statement and not the child statement that actually leads to an error. So, another solution would be preferable.
Also I cannot use a clean PersistenceExceptionTranslator, since the translator would be called before the mapper method finishes, which is unfortunate, since it means that I would have to re-invent the whole exception translation via AOP.
Upvotes: 1