View Javadoc
1   /*
2    * Copyright 2010-2023 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  
18  import java.sql.SQLException;
19  import java.util.function.Supplier;
20  
21  import javax.sql.DataSource;
22  
23  import org.apache.ibatis.exceptions.PersistenceException;
24  import org.springframework.dao.DataAccessException;
25  import org.springframework.dao.support.PersistenceExceptionTranslator;
26  import org.springframework.jdbc.UncategorizedSQLException;
27  import org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator;
28  import org.springframework.jdbc.support.SQLExceptionTranslator;
29  import org.springframework.transaction.TransactionException;
30  
31  /**
32   * Default exception translator.
33   * <p>
34   * Translates MyBatis SqlSession returned exception into a Spring {@code DataAccessException} using Spring's
35   * {@code SQLExceptionTranslator} Can load {@code SQLExceptionTranslator} eagerly or when the first exception is
36   * translated.
37   *
38   * @author Eduardo Macarron
39   */
40  public class MyBatisExceptionTranslator implements PersistenceExceptionTranslator {
41  
42    private final Supplier<SQLExceptionTranslator> exceptionTranslatorSupplier;
43    private SQLExceptionTranslator exceptionTranslator;
44  
45    /**
46     * Creates a new {@code PersistenceExceptionTranslator} instance with {@code SQLErrorCodeSQLExceptionTranslator}.
47     *
48     * @param dataSource
49     *          DataSource to use to find metadata and establish which error codes are usable.
50     * @param exceptionTranslatorLazyInit
51     *          if true, the translator instantiates internal stuff only the first time will have the need to translate
52     *          exceptions.
53     */
54    public MyBatisExceptionTranslator(DataSource dataSource, boolean exceptionTranslatorLazyInit) {
55      this(() -> new SQLErrorCodeSQLExceptionTranslator(dataSource), exceptionTranslatorLazyInit);
56    }
57  
58    /**
59     * Creates a new {@code PersistenceExceptionTranslator} instance with specified {@code SQLExceptionTranslator}.
60     *
61     * @param exceptionTranslatorSupplier
62     *          Supplier for creating a {@code SQLExceptionTranslator} instance
63     * @param exceptionTranslatorLazyInit
64     *          if true, the translator instantiates internal stuff only the first time will have the need to translate
65     *          exceptions.
66     *
67     * @since 2.0.3
68     */
69    public MyBatisExceptionTranslator(Supplier<SQLExceptionTranslator> exceptionTranslatorSupplier,
70        boolean exceptionTranslatorLazyInit) {
71      this.exceptionTranslatorSupplier = exceptionTranslatorSupplier;
72      if (!exceptionTranslatorLazyInit) {
73        this.initExceptionTranslator();
74      }
75    }
76  
77    /**
78     * {@inheritDoc}
79     */
80    @Override
81    public DataAccessException translateExceptionIfPossible(RuntimeException e) {
82      if (e instanceof PersistenceException) {
83        // Batch exceptions come inside another PersistenceException
84        // recursion has a risk of infinite loop so better make another if
85        String msg = e.getMessage();
86        if (e.getCause() instanceof PersistenceException) {
87          e = (PersistenceException) e.getCause();
88          if (msg == null) {
89            msg = e.getMessage();
90          }
91        }
92        if (e.getCause() instanceof SQLException) {
93          this.initExceptionTranslator();
94          String task = e.getMessage() + "\n";
95          SQLException se = (SQLException) e.getCause();
96          DataAccessException dae = this.exceptionTranslator.translate(task, null, se);
97          return dae != null ? dae : new UncategorizedSQLException(task, null, se);
98        } else if (e.getCause() instanceof TransactionException) {
99          throw (TransactionException) e.getCause();
100       }
101       return new MyBatisSystemException(msg, e);
102     }
103     return null;
104   }
105 
106   /**
107    * Initializes the internal translator reference.
108    */
109   private synchronized void initExceptionTranslator() {
110     if (this.exceptionTranslator == null) {
111       this.exceptionTranslator = exceptionTranslatorSupplier.get();
112     }
113   }
114 
115 }