MyBatis Spring Bootの別名のBugをスキャンできません.

11791 ワード

この問題が発生した原因は複雑で、主な条件は4つあります.
  • Spring Bootを使用して、Spring BootのMavenプラグインを使用して
  • を包装します.
  • はMyBatisを使用しています.(現在の最新の3.3.1バージョンはまだこの問題があります.)
  • は、Domainを個別のJarパケット(例えば、Maven多モジュール)に配置する
  • .
  • は、SqlSessionFactoryBean.setTypeAliasesPackageを使用して、パケットスキャンDomain
  • を指定する.
    そして、開発時にIDEAを使って直接マイコンを実行するのは正常ですが、Jarパッケージにしてjava-jarを使って起動すると、Domainの別名が無効になります.
    例えば私はSpring Bootプロジェクトを持っています.この中に三つのMavenモジュールがあります.
    scienjus--scienjus-domain-–comp.scienjuse.domail.User--scienjus-mapper-–comp.scienjuse.mapper.UserMapper——–UserMapper.xml—scienjuse-web——–Sql Session Factfiory
    Sql Session FactoryConfigにSql Session Factoryを配置する:
    @Bean
    public SqlSessionFactory sqlSessionFactory() throws Exception {
        final SqlSessionFactoryBean sqlSessionFactory = new SqlSessionFactoryBean();
        sqlSessionFactory.setDataSource(dataSource());
        //    
        sqlSessionFactory.setTypeAliasesPackage("com.scienjus.domain");
        return sqlSessionFactory.getObject();
    }
    UserMapper.xmlに別名を使用します.
    <select id="get" resultType="User">
        select  * from user u where id = #{id}
    select>
    開発時にIDEAを使って起動すると正常に動作しますが、実行時にコマンドラインで起動すると以下のエラーメッセージが発生します.
    org.apache.ibatis.builder.BuilderException: Error resolving class. Cause:
    org.apache.ibatis.type.TypeException: Could not resolve type alias 'User'.  Cause:
    java.lang.ClassNotFoundException: Cannot find class: User
    このエラーの大体の意味はMapperを生成する時にエラーが発生しました.原因はUserという別名を識別できないし、Userというクラスも見つけられないからです.以前に配されたパケットスキャンは、com.scienjus.domain.Userというクラスに全くスキャンされていないことが分かります.
    これを証明するために、MyBatisのソースコードを翻訳してorg.apache.ibatis.type.TypeAliasRegistryregisterAliases(String packageName, Class superType)方法でMyBatisがどのようにパッケージ名を通して別名類をスキャンしているかを発見しました.この部分の論理を直接main方法で実行してみます.
    public static void main(String[] args) {
        ResolverUtil resolverUtil = new ResolverUtil();
        resolverUtil.find(new IsA(Object.class), "com.scienjus.domain");
        Set typeSet = resolverUtil.getClasses();
        Iterator i$ = typeSet.iterator();
    
        while(i$.hasNext()) {
            Class type = (Class)i$.next();
            if(!type.isAnonymousClass() && !type.isInterface() && !type.isMemberClass()) {
                System.out.println(type.getName());
            }
        }
    }
    それぞれIDEAとJarで実行すると、前者はcom.scienjus.domain.Userをプリントしているが、後者は出力結果がないので、問題はここにあると説明している.
    問題が発生したところをロックした以上、どうやって起こったのかよく見てください.ResolverUtil.find方法を参照すると、VFS.getInstance().list(path)方法でクラスファイルを取得し、VFS.getInstance()がデフォルトで返したのはDefaultVFSです.つまり、このクラスのlist方法では、Spring BootがJarパケットに依存するクラスをスキャンできないからです.
    もう少し細かくして論理を呼び出せば、ブレークポイントを準備してデバッグできます.
    public static void main(String[] args) throws IOException {
        DefaultVFS defaultVFS = new DefaultVFS();
        List children = defaultVFS.list("com/scienjus/domain");
        for (String child : children) {
            System.out.println(child);
        }
    }
    ブレークポイント調整はjava -jarで起動したプログラムを使っても、思ったより難しくないです.IDEAとEclipseは非常に優秀なデバッグツールを内蔵しています.IDEAの使い方を紹介します.
    DebugモードをオンにしてJarパケットを実行し、特定のポートを待ち受けます.
    java -Xdebug -Xrunjdwp:transport=dt_socket,address=5005,server=y,suspend=y -jar scienjus-web.jar
    IDEA端はRun->Edit ConfigrationsでRemoteアプリケーションを作成し、IPと傍受のポート番号を記入して起動すればいいです.
    ブレークポイント調整によって、私はfindJarForResourceで面白いコードを発見しました.
    // If the file part of the URL is itself a URL, then that URL probably points to the JAR
    try {
      for (;;) {
        url = new URL(url.getFile());
        if (log.isDebugEnabled()) {
          log.debug("Inner URL: " + url);
        }
      }
    } catch (MalformedURLException e) {
      // This will happen at some point and serves as a break in the loop
    }
    これは死の循環であり、MalformedURLExceptionの異常を投げ出した時だけがサイクルから飛び出すことができます.上記の注釈によれば、このことは必然的に発生し、urlを所望の結果に向けることが分かります.2つの方式の運行時のurlの最後の結果を比較します.
    IDEAで直接運転:
    scienjus-domain/target/classes/com/scienjus/domain
    コマンドライン運転:
    scienjus-web/target/scienjus-web.jar!/lib/scienjus-domain.jar!/com/scienjus/domain
    その後、変数jarUrlの値はscienjus-web/target/scienjus-web.jar!/lib/scienjus-domain.jarに割り当てられるが、最後のlistResources方法はnullに戻る.
    この方法を呼び出した時のコメントはこうです.
    // First, try to find the URL of a JAR file containing the requested resource. If a JAR
    // file is found, then we'll list child resources by reading the JAR.
    つまり、スキャンされたファイルがJarパッケージの中にある場合、この方法はこのJarパッケージのURLに戻るべきであり、乱暴な改善を試みる.
    public static void main(String[] args) throws IOException {
        DefaultVFS defaultVFS = new DefaultVFS() {
            @Override
            protected URL findJarForResource(URL url) throws MalformedURLException {
                String urlStr = url.toString();
                if (urlStr.contains("jar!")) {
                    return new URL(urlStr.substring(0, urlStr.lastIndexOf("jar") + "jar".length()));
                }
                return super.findJarForResource(url);
            }
        };
        List children = defaultVFS.list("com/scienjus/domain");
        for (String child : children) {
            System.out.println(child);
        }
    }
    このURLにjar!の識別情報が含まれている場合、このJarパケットのアドレスに直接戻ります.私はこのようにして隠れている危険があるかどうかはよく分かりませんが、Domainの別名をスキャンする時だけこの類を使います.そしてこの時は正常に働きます.
    このクラスが正常に動作するようになるなら、それをデフォルトのVFSに設定する必要があります.MyBatisのドキュメントには、プロファイル変更vfsImplによってVFSを変更することができるクラスが書いてありますが、ここではこの構成では効果がありません.なぜならば、Springの構成はMyBatisプロファイルの前に実行されるので、この構成を読み込む前にVFS.getInstall()が既に実用化されています.そしてMyBatisにIssueを提出しました.ついでに、このスキャンができない種類のBugは去年の10月に提出されました.解決方法もとっくにありました.
    MyBatis公式の解決策は、まずmybatis-spring-bootの3.4.1バージョンを推奨しており、デフォルトではSpring Bootに対応するVFS実装クラスがすでに構成されています.またはこの実装クラスをあなたのプロジェクトに追加し、手動で設定します.
    これらの無駄な思いをさせないために、この過程を発表することにしました.ソースプロジェクトを使ってバグに遭遇した時、どのように順位を決めるかを教えてください.これも本文の残りの価値かもしれません.
    以上の紙面はMyBatis Spring Bootの別名のBugをスキャンできません.
    自分で使うのはmybatis-spring-boot-starterの1.2.0バージョンで、mybatisバージョンは3.4.2です.
    そして、次のようにSession Factoryを宣言しました.
    @Configuration
    @AutoConfigureAfter(DataSourceConfig.class)
    @MapperScan(basePackages = {"org.rainbow.persistence.mysql"}, sqlSessionFactoryRef = "mysqlSqlSessionFactory")
    public class MyBatisConfigForMysql {
        @Bean("mysqlSqlSessionFactory")
        @Autowired
        public SqlSessionFactory mysqlSqlSessionFactory(@Qualifier("mysqlDataSource") DataSource dataSource) throws Exception {
            SqlSessionFactoryBean sqlSessionFactory = new SqlSessionFactoryBean();
            sqlSessionFactory.setDataSource(dataSource);
            // IMPORTANT: we MUST set the 'VFS',
            // otherwise if you run this project as a 'executable jar', then mybatis will throw an exception saying that it can not find java POJO
            sqlSessionFactory.setVfs(SpringBootVFS.class);
            sqlSessionFactory.setTypeAliasesPackage("org.rainbow.model.mysql");
    
            return sqlSessionFactory.getObject();
        }
    }
    上記のような実装クラスに従って、コードの行を追加したことが分かる.
    sqlSessionFactory.setVfs(SpringBootVFS.class);
    これで問題も解決できます.
    後の話
    mybatis atoconfigureで生成されたSession Factoryを使えばこの問題はないかもしれません.具体的には、Mybatis AutoConfigratin.javaを見ることができます.