Crystal's Record

電腦,生活

20231219 Spring Boot 2.7.11加入Mybatis Starter XML讀取不到踩坑實錄🫠

TL:DR

且這篇內容超腦霧,就是個八八坑道(??),我也不知道為什麼會失常成這樣,搏君一笑,單純做個紀錄.....


前情提要

我按照一個教學(不過這邊有個巨大的過失,就是我看到文末才發現作者寫了"本教學內容已更新到Spring Boot 3.0",我知道八成會出問題了=-=...),大概做了這些事情:

  1. 加入mybatis-spring-boot-starter依賴(一開始是加3.0.0,一直出錯,按照官方建議先降到2.3.0)
  2. 寫了一個XML config並在application.yaml指定路徑
  3. 開好目錄結構放入相應的內容物,並在application.yaml指定mapper XML路徑(⚠️注意:這邊路徑是包版後jar/war檔內的路徑)

UserMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" ><!--記得帶上這個約束文件-->
<mapper namespace="org.example.mybatis.UserMapper" >
    <resultMap id="BaseResultMap" type="org.example.mybatis.vo.User" >
        <id column="username" property="username" jdbcType="VARCHAR" />
        <result column="password" property="password" jdbcType="VARCHAR" />
        <result column="name" property="name" jdbcType="VARCHAR" />
    </resultMap>
    
    <sql id="Base_Column_List" >
        username, password, name
    </sql>

    <select id="getAll" resultMap="BaseResultMap"  >
       SELECT 
       <include refid="Base_Column_List" />
       FROM "user"
    </select>
</mapper>

vo

用途等價於entity的物件,Mybatis生成工具預設值產生的放置目錄即叫做vo

package org.example.mybatis.vo;

import lombok.Data;
import org.apache.ibatis.type.Alias;

@Data
@Alias("User") // 注意這邊,為了避免造成更多路徑問題,我沒有在application.yaml寫type-aliases-package: org.example.mybatis.vo讓他自己去掃描,而是用註釋手動註冊,可以依個人習慣調整
public class User{
    String username;
    String password;
    String name;
}

dao

如果用生成器預設會放在名為dao的目錄內,因為我是手寫,在不會影響讀取運作,就放得隨意一點了

package org.example.mybatis;
import org.apache.ibatis.annotations.Mapper;
import org.example.mybatis.vo.User;

import java.util.List;

@Mapper // 注意這邊,也是初期為了避免造成更多路徑問題,選擇手動註釋,而非在Spring應用程式的Main方法上方加入@MapperScan自動掃描,可以依個人習慣調整
public interface UserMapper {

    List<User> getAll();
    
        // 以下是之後預計加入的query
    // User getOne(Long id);

    // void insert(User user);

    // void update(User user);

    // void delete(Long id);
}

4.然後很簡略、沒有把業務邏輯放在Service地,直接在Controller弄個API(幾乎是搬運之前JPA練習的),再以Postman訪問

package org.example.controller;

import java.util.ArrayList;
import java.util.List;

import org.example.model.response.UserListModel;
import org.example.mybatis.UserMapper;
import org.example.mybatis.vo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

@RestController
public class MybatisTestController {
    @Autowired
    UserMapper userMapper;

    @GetMapping(value = "getall", produces = MediaType.APPLICATION_JSON_VALUE)
    public String userList() throws JsonProcessingException {
        ObjectMapper objectMapper = new ObjectMapper(); //TODO這東西賊耗能的應該做成一個單例:(

        UserListModel userListModel = new UserListModel(); // 這是之前給API用的傳輸物件(response model),跟Mybatis無關

        ArrayList<UserListModel.user> userList = new ArrayList<>(); // 那個傳輸物件有inner class

        List<User> userEntities = userMapper.getAll(); // 這邊才是呼叫Mybatis DAO查詢結果
        userEntities.forEach(node -> { // 查詢結果裝起來送給client
            UserListModel.user user = new UserListModel.user();
            user.setUsername(node.getUsername());
            user.setName(node.getName());
            userList.add(user);
        });
        userListModel.setUserList(userList);
        return objectMapper.writeValueAsString(userListModel); // 其實不用這樣也能序列化出JSON物件,只是JPA那次寫法比較舊又是照搬沒改
    }
}

說好的ObjectMapper的單例ㄋ


以上都做完後,其實首先我在包版時就有遇到

mybatis:
  config-location: classpath:/org/example/mybatis/mybatis-config.xml # 我這裡面只放一些基本型態的對應

application.yaml這段造成的錯誤

我昨天快下班前已經幾乎是腦霧狀態了,還以為是那篇文章寫錯屬性...我竟然給location尾巴加了個s,包版是過了,但到底在幹嘛啦,根本沒這個屬性@@,總之就是多埋一個bug,所以今天看到這篇發現就算有需要也可以全寫在application.yaml啊(而且starter好像不用額外對應...?),就把它及yaml那條設定移除了,但要說的問題還是存在

昨天更顯著的問題是:訪問API他總是說我Invalid bound statement,對應不到我mapper.xml內的查詢方法實作,查詢網路上的討論,通常他們會要我們確定包版後的jar/war解壓縮後,xml檔案有沒有也被一併加入,沒有的話要運用一些奇巧(例如放在resource目錄或在pom.xml內寫build規則要包含*.xml),所以我的問題應該是跟這種方法沒關係的,但我昨天秉持著死馬當活馬醫的態度(?),我還是花時間照做了,但幾乎還是不影響=-=

昨天下班回家滿頭問號,只是離開公司後有清醒點,想起來版本對應蠻重要的,找了一篇文章存著今天來讀,今天早上打開才發現他是搬運MybatisPlus的還沒寫清楚(...

所以找到這兩篇適用Spring Boot 2.7的教學,寫得都挺好: 1 2

本來想整個砍掉重練跟著做,我就一步一步順著步驟,多得拔、少的拆,想說連目錄結構都一樣好了(謎之音:就結果論沒關係啊很迷信欸),版本也跟他們一樣好了

首先是在把Mybatis Starter降到2.2.2 pom.xml

        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.2.2</version>
        </dependency>

(記得Maven reload,不放心可以先reload->clean->package)(謎之音:這什麼邪教儀式)

然後看到掘金那篇大概1/3?突然💡亮了,我應該不用整個照搬,我趕快去檢查application.yaml,應該只是這個地方設定出問題了,那就是指定xml位置時路徑應該要是jar/war內的路徑,而非在專案內的路徑,我的情境的正確配置:

mybatis:
  mapper-locations: classpath:/org/example/mybatis/mapper/*.xml

然後敲API就能看到會員資料了=-=... (我有嘗試過把版本換回官方說可用的2.3.0,但又出現Invalid bound statement了)


好亂的心路歷程,稍微濃縮一下可以提供的方向

此情境故障排除的幾個方向

  • 確認Spring Boot跟Mybatis starter真正對應的版本(最好用parent寫,問題就不會這麼多)
  • application.yaml內指定xml檔案的路徑是否正確

要把頭埋起來了Orz|||


後日談

話說要不是我懶一直用Spring Boot 2.7那個遊樂場(?)一直試東西 直上Spring Boot三代大概不會遇到這種問題吧-.-

另外why XML?因為是老專案要升級,不過話說回來是要升到Spring Boot 3啊啊啊,但Spring Boot 3的教學資源蠻多的,應該不會翻車成這兩天這樣

20231208 Java Interface Q&A

  • Q:負責實作的class可以有自己的method嗎? A:可以

但像下面這樣會找不到method

    BeverageFactory bevergeFactory = new TestFactory();
    bevergeFactory.strike();

這樣才可以

    TestFactory testFactory = new TestFactory();
    testFactory.strike();
  • Q:一定要實作interface的所有方法嗎? A:除了default一定要,可以看下面的例子
package factory;

public interface BeverageFactory {
  public Beverage createBeverage();
}
package factory;

public class TestFactory implements BeverageFactory {
  // @Override
  // public Beverage createBeverage() {
  // System.out.println("橘子汁");
  // return new OrangeJuice();
  // }
  public void strike() {
    System.out.println("發起罷工工廠是不做飲料的");
  }

}
    bevergeFactory = new TestFactory();
    bevergeFactory.strike();

Output:

error: TestFactory is not abstract and does not override abstract method createBeverage() in BeverageFactory

  • 如果你想在介面裡有個預設方法得用關鍵字default,因為介面中的其他抽象方法都是得實作的 貝爾東的文章
  • 一則破碎的記憶:為啥我們平常寫Spring Boot DAO(或儲藏庫)可以不實作Interface JpaRepository抽象方法啊->因為是繼承

20231122列出目錄及下方資料夾所有檔案清單批次檔v1

⚠️已知問題:

  1. web.config這種檔案不知道為甚麼顯示不出來

  2. 如果有個要從專案根目錄排除的目錄名稱,跟想要保留子目錄下的目錄名稱雷同時,其政則寫法要再考究

1.製作prodFileList.bat

@echo off
dir /s /b /a-d /on | findstr /v /i /c:".git" /c:"vendor" /c:"storage" > fileList.txt
  • /c:"目錄名稱"目錄名稱替換成你要排除的目錄

2.把這個bat放到要產清單的資料夾下方點兩下,取得你的檔案清單