MyBatisExceptionTranslator.java

  1. /*
  2.  * Copyright 2010-2024 the original author or authors.
  3.  *
  4.  * Licensed under the Apache License, Version 2.0 (the "License");
  5.  * you may not use this file except in compliance with the License.
  6.  * You may obtain a copy of the License at
  7.  *
  8.  *    https://www.apache.org/licenses/LICENSE-2.0
  9.  *
  10.  * Unless required by applicable law or agreed to in writing, software
  11.  * distributed under the License is distributed on an "AS IS" BASIS,
  12.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13.  * See the License for the specific language governing permissions and
  14.  * limitations under the License.
  15.  */
  16. package org.mybatis.spring;

  17. import java.sql.SQLException;
  18. import java.util.function.Supplier;

  19. import javax.sql.DataSource;

  20. import org.apache.ibatis.exceptions.PersistenceException;
  21. import org.springframework.dao.DataAccessException;
  22. import org.springframework.dao.support.PersistenceExceptionTranslator;
  23. import org.springframework.jdbc.UncategorizedSQLException;
  24. import org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator;
  25. import org.springframework.jdbc.support.SQLExceptionTranslator;
  26. import org.springframework.transaction.TransactionException;

  27. /**
  28.  * Default exception translator.
  29.  * <p>
  30.  * Translates MyBatis SqlSession returned exception into a Spring {@code DataAccessException} using Spring's
  31.  * {@code SQLExceptionTranslator} Can load {@code SQLExceptionTranslator} eagerly or when the first exception is
  32.  * translated.
  33.  *
  34.  * @author Eduardo Macarron
  35.  */
  36. public class MyBatisExceptionTranslator implements PersistenceExceptionTranslator {

  37.   private final Supplier<SQLExceptionTranslator> exceptionTranslatorSupplier;
  38.   private SQLExceptionTranslator exceptionTranslator;

  39.   /**
  40.    * Creates a new {@code PersistenceExceptionTranslator} instance with {@code SQLErrorCodeSQLExceptionTranslator}.
  41.    *
  42.    * @param dataSource
  43.    *          DataSource to use to find metadata and establish which error codes are usable.
  44.    * @param exceptionTranslatorLazyInit
  45.    *          if true, the translator instantiates internal stuff only the first time will have the need to translate
  46.    *          exceptions.
  47.    */
  48.   public MyBatisExceptionTranslator(DataSource dataSource, boolean exceptionTranslatorLazyInit) {
  49.     this(() -> new SQLErrorCodeSQLExceptionTranslator(dataSource), exceptionTranslatorLazyInit);
  50.   }

  51.   /**
  52.    * Creates a new {@code PersistenceExceptionTranslator} instance with specified {@code SQLExceptionTranslator}.
  53.    *
  54.    * @param exceptionTranslatorSupplier
  55.    *          Supplier for creating a {@code SQLExceptionTranslator} instance
  56.    * @param exceptionTranslatorLazyInit
  57.    *          if true, the translator instantiates internal stuff only the first time will have the need to translate
  58.    *          exceptions.
  59.    *
  60.    * @since 2.0.3
  61.    */
  62.   public MyBatisExceptionTranslator(Supplier<SQLExceptionTranslator> exceptionTranslatorSupplier,
  63.       boolean exceptionTranslatorLazyInit) {
  64.     this.exceptionTranslatorSupplier = exceptionTranslatorSupplier;
  65.     if (!exceptionTranslatorLazyInit) {
  66.       this.initExceptionTranslator();
  67.     }
  68.   }

  69.   @Override
  70.   public DataAccessException translateExceptionIfPossible(RuntimeException e) {
  71.     if (e instanceof PersistenceException) {
  72.       // Batch exceptions come inside another PersistenceException
  73.       // recursion has a risk of infinite loop so better make another if
  74.       var msg = e.getMessage();
  75.       if (e.getCause() instanceof PersistenceException) {
  76.         e = (PersistenceException) e.getCause();
  77.         if (msg == null) {
  78.           msg = e.getMessage();
  79.         }
  80.       }
  81.       if (e.getCause() instanceof SQLException) {
  82.         this.initExceptionTranslator();
  83.         var task = e.getMessage() + "\n";
  84.         var se = (SQLException) e.getCause();
  85.         var dae = this.exceptionTranslator.translate(task, null, se);
  86.         return dae != null ? dae : new UncategorizedSQLException(task, null, se);
  87.       }
  88.       if (e.getCause() instanceof TransactionException) {
  89.         throw (TransactionException) e.getCause();
  90.       }
  91.       return new MyBatisSystemException(msg, e);
  92.     }
  93.     return null;
  94.   }

  95.   /**
  96.    * Initializes the internal translator reference.
  97.    */
  98.   private synchronized void initExceptionTranslator() {
  99.     if (this.exceptionTranslator == null) {
  100.       this.exceptionTranslator = exceptionTranslatorSupplier.get();
  101.     }
  102.   }

  103. }