因項目需求,小編要將項目從mysql遷移到oracle中 ~java
以前已經完成 數據遷移
(https://zhengqing.blog.csdn.net/article/details/103694901)mysql
如今將完成 基於MyBatis-Plus
將項目中的MySQL
語句所有轉換
成Oracle
語句git
xxxMapper.xml
文件,找到mysql與oracle語句的區別,而後替換絕大部分SQLmysql
,oracle
】application.yml
中配置mybatis-plus
的database-id
# mybatis-plus配置 mybatis-plus: configuration: jdbc-type-for-null: 'null' # 解決oracle更新數據爲null時沒法轉換報錯 database-id: oracle # 支持多庫配置 mysql,oracle
@Configuration @MapperScan("com.zhengqing.demo.modules.**.mapper*") public class MybatisPlusConfig { /** * `xxxMapper.xml`文件中的`databaseId`會自動識別使用的數據庫類型與這裏相對應 * 注: 若是沒有指定`databaseId`則該SQL語句適用於全部數據庫哦~ * * databaseIdProvider:支持多數據庫廠商 * VendorDatabaseIdProvider: 獲得數據庫廠商的標識(驅動getDatabaseProductName()),mybatis就能根據數據庫廠商標識來執行不一樣的sql; * MySQL,Oracle,SQL Server,xxxx */ @Bean public DatabaseIdProvider getDatabaseIdProvider(){ DatabaseIdProvider databaseIdProvider = new VendorDatabaseIdProvider(); Properties properties = new Properties(); // 爲不一樣的數據庫廠商起別名 properties.setProperty("MySQL","mysql"); properties.setProperty("Oracle","oracle"); databaseIdProvider.setProperties(properties); return databaseIdProvider; } }
xxxMapper.xml
中經過databaseId
指定數據庫類型<select id="selectUserInfo" resultMap="UserVO" databaseId="mysql"> SELECT * FROM 表名 LIMIT 1 </select> <select id="selectUserInfo" resultMap="UserVO" databaseId="oracle"> SELECT * FROM 表名 WHERE ROWNUM <= 1 </select>
這裏根據我的項目狀況去實際應用便可~
ORACLE_TO_UNIX
Oracle時間 Date類型轉換爲Unix時間戳,等同於mysql中的UNIX_TIMESTAMP
github
create or replace function ORACLE_TO_UNIX(in_date IN DATE) return number is begin return( ROUND( (in_date -TO_DATE('19700101','yyyymmdd'))*86400 - TO_NUMBER(SUBSTR(TZ_OFFSET(sessiontimezone),1,3))*3600, 0) ); end ORACLE_TO_UNIX;
FIND_IN_SET
CREATE OR REPLACE FUNCTION FIND_IN_SET(piv_str1 varchar2, piv_str2 varchar2, p_sep varchar2 := ',') RETURN NUMBER IS l_idx number:=0; -- 用於計算piv_str2中分隔符的位置 str varchar2(500); -- 根據分隔符截取的子字符串 piv_str varchar2(500) := piv_str2; -- 將piv_str2賦值給piv_str res number:=0; -- 返回結果 loopIndex number:=0; BEGIN -- 若是piv_str中沒有分割符,直接判斷piv_str1和piv_str是否相等,相等 res=1 IF instr(piv_str, p_sep, 1) = 0 THEN IF piv_str = piv_str1 THEN res:= 1; END IF; ELSE -- 循環按分隔符截取piv_str LOOP l_idx := instr(piv_str,p_sep); loopIndex:=loopIndex+1; -- 當piv_str中還有分隔符時 IF l_idx > 0 THEN -- 截取第一個分隔符前的字段str str:= substr(piv_str,1,l_idx-1); -- 判斷 str 和piv_str1 是否相等,相等 res=1 並結束循環判斷 IF str = piv_str1 THEN res:= loopIndex; EXIT; END IF; piv_str := substr(piv_str,l_idx+length(p_sep)); ELSE -- 當截取後的piv_str 中不存在分割符時,判斷piv_str和piv_str1是否相等,相等 res=1 IF piv_str = piv_str1 THEN res:= loopIndex; END IF; -- 不管最後是否相等,都跳出循環 EXIT; END IF; END LOOP; -- 結束循環 END IF; -- 返回res RETURN res; END FIND_IN_SET;
xxxMapper.xml
中將全部sql語句上加入 databaseId="mysql"
databaseId="oracle"
舒適小提示: 這裏工具類只供參考,實際操做根據本身的項目作修改哦,操做前建議先備份本身的項目,以防操做不當丟失代碼哦!
import org.apache.commons.lang3.StringUtils; import org.junit.Test; import java.io.*; import java.util.*; /** * <p> mysql遷移oracle 測試工具類 </p> * * @description : * @author : zhengqing * @date : 2020/1/08 10:10 */ public class MySQLToOracleTest { private final static String ORACLE_SQL = " <!-- ====================================== ↓↓↓↓↓↓ oracle ↓↓↓↓↓↓ ====================================== -->"; @Test // 替換項目中的sql語句 public void testSQL() throws Exception { String path = System.getProperty("user.dir") + "\\src\\main\\java\\com\\zhengqing\\xxx"; // TODO 這裏替換爲本身的項目路徑 File file = new File(path); HashMap<Object, Object> fileMap = new HashMap<>(); getAllFileByRecursion(fileMap, file); fileMap.forEach((key, value) -> { String fileXmlName = (String) key; File fileXml = (File) value; String filePath = fileXml.getPath(); if (fileXmlName.equals("Test.xml")) { System.out.println(filePath); try { // 一、加入 databaseId="mysql" addMysql(filePath); // 二、複製一份oracle的sql if (!checkHasOracle(filePath)) { copyMysqlToOracle(filePath); } // 三、加入 databaseId="oracle" addOracle(filePath); // 四、替換mybatis `xxxMapper.xml` 中的sql語句 repalceSQL(filePath); } catch (IOException e) { e.printStackTrace(); } } }); System.out.println(fileMap); } /** * 替換mybatis `xxxMapper.xml` 中的sql語句 */ private static void repalceSQL(String path) throws IOException { File file = new File(path); FileReader in = new FileReader(file); BufferedReader bufIn = new BufferedReader(in); // 內存流, 做爲臨時流 CharArrayWriter tempStream = new CharArrayWriter(); // 替換 String line = null; int row = 0; int rowOracle = 0; while ((line = bufIn.readLine()) != null) { row++; if (line.contains(ORACLE_SQL)) { rowOracle = row; } if (rowOracle != 0 && row > rowOracle) { // ① 替換 `LIMIT` -> `AND ROWNUM <= 1` TODO 【注: 部分包含`ORDER BY` 關鍵字,需單獨處理】 if (line.contains("limit") || line.contains("LIMIT")) { System.out.println(); System.out.println(" ==============================↓↓↓↓↓↓ copy分頁所需 (" + row + ") ↓↓↓↓↓↓===================================== "); System.out.println("SELECT * FROM ( SELECT TMP.*, ROWNUM ROW_ID FROM ("); System.out.println(); System.out.println(") TMP WHERE ROWNUM <=1) WHERE ROW_ID > 0"); System.out.println(); } line = StringUtils.replace(line, "limit 1", "AND ROWNUM <= 1"); line = StringUtils.replace(line, "LIMIT 1", "AND ROWNUM <= 1"); line = StringUtils.replace(line, "limit 0,1", "AND ROWNUM <= 1"); line = StringUtils.replace(line, "LIMIT 0,1", "AND ROWNUM <= 1"); // ② oracle中不能使用「 ` 」符號 line = StringUtils.replace(line, "`", ""); // ③ CONCAT('%', #{name}, '%') -> '%'||#{name}||'%' (Oracle中concat函數只能放兩個參數) if (line.contains("concat")) { // String newLine = line.substring(line.indexOf("concat(") + 7, line.lastIndexOf("'%')") + 3); line = line.replaceAll(",", " || "); line = line.replaceAll("concat", ""); } if (line.contains("CONCAT")) { // String newLine = line.substring(line.indexOf("CONCAT(") + 7, line.lastIndexOf("'%')") + 3); line = line.replaceAll(",", " || "); line = line.replaceAll("CONCAT", ""); } // ④ `UNIX_TIMESTAMP` -> `ORACLE_TO_UNIX` date類型時間轉10位時間戳 line = line.replaceAll("UNIX_TIMESTAMP", "ORACLE_TO_UNIX"); // ⑤ 部分關鍵字需加上雙引號 TODO 【注: 字段名大寫,映射的別名需保存本來小寫!】 `level -> "LEVEL"` `user -> "USER"` `number -> "NUMBER"` `desc -> "DESC"` List<String> keywordList = new ArrayList<>(Arrays.asList("level", "user", "number")); if (!line.contains("test=")) { for (String e : keywordList) { // StringUtils.swapCase(e) : 大小寫互換 line = line.replaceAll(" " + e + " ", " \"" + StringUtils.swapCase(e) + "\" "); line = line.replaceAll("." + e + " ", "\\.\"" + StringUtils.swapCase(e) + "\" "); if (line.endsWith(e) || line.endsWith(e + ",")) { line = line.replaceAll(e, "\"" + StringUtils.swapCase(e) + "\""); } } } if (line.endsWith(" date") || line.endsWith(" date,") || line.endsWith(" 'date'") || line.endsWith(" 'DATE'") || line.endsWith("DATE")) { line = line.replaceAll(" date", " \"date\""); line = line.replaceAll(" date,", " \"date,\""); line = line.replaceAll(" 'date'", " \"date\""); line = line.replaceAll(" 'DATE'", " \"date\""); line = line.replaceAll(" DATE", " \"date\""); } line = line.replaceAll(" date ", " \"date\" "); line = line.replaceAll(" DATE ", " \"date\" "); // ⑥ `IFNULL` -> `NVL` line = line.replaceAll("IFNULL", "NVL"); line = line.replaceAll("ifnull", "NVL"); // ⑦ 時間 `str_to_date` -> `to_date` `date_format` -> `to_char` // `%Y-%m-%d` -> `yyyy-MM-dd` `%Y-%m` -> `yyyy-MM` line = line.replaceAll("str_to_date", "TO_DATE"); line = line.replaceAll("STR_TO_DATE", "TO_DATE"); line = line.replaceAll("date_format", "TO_CHAR"); line = line.replaceAll("DATE_FORMAT", "TO_CHAR"); // 這裏注意替換順序問題,最長的應該放最前面!!! line = line.replaceAll("%Y-%m-%d %H:%i:%S", "yyyy-MM-dd HH24:mi:ss"); line = line.replaceAll("%Y-%m-%d %H:%i:%s", "yyyy-MM-dd HH24:mi:ss"); line = line.replaceAll("%Y-%m-%d %H:%i", "yyyy-MM-dd HH24:mi"); line = line.replaceAll("%Y-%m-%d %H", "yyyy-MM-dd HH24"); line = line.replaceAll("%Y-%m-%d %h", "yyyy-MM-dd HH"); line = line.replaceAll("%Y-%m-%d", "yyyy-MM-dd"); line = line.replaceAll("%Y-%m", "yyyy-MM"); line = line.replaceAll("%Y", "yyyy"); line = line.replaceAll("%H", "HH24"); line = line.replaceAll("%k", "HH24"); line = line.replaceAll("now\\(\\)", "(SELECT SYSDATE + 8/24 FROM DUAL)"); line = line.replaceAll("NOW\\(\\)", "(SELECT SYSDATE + 8/24 FROM DUAL)"); // ⑧ ... // 需手動處理的SQL 【 group by | 批量插入 | ... 】 } // 將該行寫入內存 tempStream.write(line); // 添加換行符 tempStream.append(System.getProperty("line.separator")); } // 關閉 輸入流 bufIn.close(); // 將內存中的流 寫入 文件 FileWriter out = new FileWriter(file); tempStream.writeTo(out); out.close(); } /** * 加入 databaseId="mysql" */ private static void addMysql(String path) throws IOException { File file = new File(path); FileReader in = new FileReader(file); BufferedReader bufIn = new BufferedReader(in); // 內存流, 做爲臨時流 CharArrayWriter tempStream = new CharArrayWriter(); // 替換 String line = null; while ((line = bufIn.readLine()) != null) { if ((line.contains("<select") || line.contains("<update") || line.contains("<insert") || line.contains("<delete")) && !line.contains("databaseId")) { if (line.endsWith(">")) { line = line.replaceAll(">", " databaseId=\"mysql\">"); } else { line = line + " databaseId=\"mysql\""; } } // 將該行寫入內存 tempStream.write(line); // 添加換行符 tempStream.append(System.getProperty("line.separator")); } // 關閉 輸入流 bufIn.close(); // 將內存中的流 寫入 文件 FileWriter out = new FileWriter(file); tempStream.writeTo(out); out.close(); } /** * 加入 databaseId="oracle" */ private static void addOracle(String path) throws IOException { File file = new File(path); FileReader in = new FileReader(file); BufferedReader bufIn = new BufferedReader(in); // 內存流, 做爲臨時流 CharArrayWriter tempStream = new CharArrayWriter(); HashSet<String> lineSet = new HashSet<>(); // 替換 String line = null; while ((line = bufIn.readLine()) != null) { if (line.contains("databaseId=\"mysql\"")) { if (lineSet.contains(line)) { line = line.replaceAll("databaseId=\"mysql\"", "databaseId=\"oracle\""); } lineSet.add(line); } // 將該行寫入內存 tempStream.write(line); // 添加換行符 tempStream.append(System.getProperty("line.separator")); } // 關閉 輸入流 bufIn.close(); // 將內存中的流 寫入 文件 FileWriter out = new FileWriter(file); tempStream.writeTo(out); out.close(); } /** * 複製一份oracle的sql */ private static void copyMysqlToOracle(String path) throws IOException { File file = new File(path); FileReader in = new FileReader(file); BufferedReader bufIn = new BufferedReader(in); // 內存流, 做爲臨時流 CharArrayWriter tempStream = new CharArrayWriter(); // 替換 String line = null; // 須要替換的行 List<String> lineList = new LinkedList<>(); int row = 0; int firstRow = 0; while ((line = bufIn.readLine()) != null) { row++; if (line.contains("<select") || line.contains("<update") || line.contains("<insert") || line.contains("<delete")) { firstRow = row; } // 添加替換內容 if (firstRow != 0 && row >= firstRow && !line.contains("</mapper>")) { lineList.add(line); } // 查詢結束位置 if (line.contains("</mapper>")) { tempStream.append(System.getProperty("line.separator")); tempStream.write(ORACLE_SQL); tempStream.append(System.getProperty("line.separator")); tempStream.append(System.getProperty("line.separator")); lineList.forEach(lineValue -> { // copy mysql 語句 轉爲oracle try { tempStream.write(lineValue); tempStream.append(System.getProperty("line.separator")); } catch (IOException e) { e.printStackTrace(); } }); tempStream.append(System.getProperty("line.separator")); } // 將該行寫入內存 tempStream.write(line); // 添加換行符 tempStream.append(System.getProperty("line.separator")); } // 關閉 輸入流 bufIn.close(); // 將內存中的流 寫入 文件 FileWriter out = new FileWriter(file); tempStream.writeTo(out); out.close(); } /** * 檢查是否已經複製SQL */ private static boolean checkHasOracle(String path) throws IOException { File file = new File(path); FileReader in = new FileReader(file); BufferedReader bufIn = new BufferedReader(in); // 內存流, 做爲臨時流 CharArrayWriter tempStream = new CharArrayWriter(); // 替換 String line = null; boolean result = false; while ((line = bufIn.readLine()) != null) { if (line.contains(ORACLE_SQL)) { result = true; } // 將該行寫入內存 tempStream.write(line); // 添加換行符 tempStream.append(System.getProperty("line.separator")); } // 關閉 輸入流 bufIn.close(); // 將內存中的流 寫入 文件 FileWriter out = new FileWriter(file); tempStream.writeTo(out); out.close(); return result; } /** * 遞歸文件夾 -> 找到全部xml文件 */ private static void getAllFileByRecursion(HashMap<Object, Object> fileMap, File file) { File[] fs = file.listFiles(); for (File f : fs) { String fileName = f.getName(); if (f.isDirectory()) { // 如果目錄則遞歸,不然打印該目錄下的文件 getAllFileByRecursion(fileMap, f); } if (f.isFile() && fileName.endsWith(".xml")) { fileMap.put(fileName, f); } } } }
這裏簡單說下MySQL和Oracle中的SQL區別,以及mysql語句轉換oracle語句示例sql
LIMIT 0,1
ROWNUM <= 1
ORDER BY
-- mysql SELECT * FROM 表名 LIMIT 1 -- oracle SELECT * FROM 表名 WHERE ROWNUM <= 1
ORDER BY
-- mysql SELECT * FROM 表名 ORDER BY 字段名 DESC LIMIT 1 -- oracle SELECT * FROM ( SELECT TMP.*, ROWNUM ROW_ID FROM ( SELECT * FROM 表名 ORDER BY 字段名 DESC ) TMP WHERE ROWNUM <= 1 ) WHERE ROW_ID > 0;
SQL
SELECT語句執行順序FROM
子句組裝來自不一樣數據源的數據WHERE
子句基於指定的條件對記錄進行篩選GROUP BY
子句將數據劃分爲多個分組彙集函數
進行計算HAVING
子句篩選分組計算
全部表達式
ORDER BY
對結果進行排序-- mysql SELECT `字段名` FROM 表名 -- oracle SELECT 字段名 FROM 表名
注: Oracle中CONCAT
函數只能放兩個參數,所以改成||
拼接
CONCAT('%', 'xxx' , '%')
'%' || 'xxx' || '%'
-- mysql SELECT 字段名 FROM 表名 WHERE 字段名 LIKE CONCAT('%','helloworld','%') -- oracle SELECT 字段名 FROM 表名 WHERE 字段名 LIKE ('%' || 'helloworld' || '%')
UNIX_TIMESTAMP
ORACLE_TO_UNIX
(注:此函數爲步驟三中手動建立的,並不是oracle自帶哦!)字段名
爲Oracle關鍵字
需加上雙引號
舒適小提示: 字段名需大寫,若是Java實體類對應字段爲小寫,映射的別名注意需保持本來小寫與之對應 ~
例如:數據庫
level -> "LEVEL"
user -> "USER"
number -> "NUMBER"
desc -> "DESC"
date
-> DATE
IFNULL(x, value)
NVL(x, value)
前mysql,後oracle
STR_TO_DATE
-> TO_DATE
DATE_FORMAT
-> TO_CHAR
NOW()
-> SELECT SYSDATE FROM DUAL
-- 時間類型轉指定字符串類型 SELECT DATE_FORMAT( NOW(),'%Y-%m-%d %H:%i:%s'); -- mysql SELECT TO_CHAR( SYSDATE,'yyyy-MM-dd HH24:mi:ss') FROM DUAL; -- oracle -- 字符串類型轉時間類型 SELECT STR_TO_DATE( NOW(), '%Y-%m-%d %H'); -- mysql SELECT TO_DATE( '2020-01-09', 'yyyy-MM-dd') FROM DUAL; -- oracle 【 注:oracle中前者字符串時間的格式需與後者轉換格式相同哦~ 】 -- 獲取系統當前時間 SELECT NOW(); -- mysql SELECT SYSDATE + 8/24 FROM DUAL; -- oralce 【注:若是服務器時間沒有相差8小時則無需加上`8/24`】 -- mysql SELECT YEAR( NOW() ); -- 求年份 SELECT QUARTER( NOW() ); -- 求季度 SELECT MONTH( NOW() ); -- 求月份 -- oracle SELECT TO_CHAR(SYSDATE, 'Q') FROM DUAL; -- 求季度
另外這裏給出小編所用到的時間標識符格式apache
-- 前:mysql 後:oracle "%Y-%m-%d %H:%i:%S" "yyyy-MM-dd HH24:mi:ss" "%Y-%m-%d %H:%i:%s" "yyyy-MM-dd HH24:mi:ss" "%Y-%m-%d %H:%i" "yyyy-MM-dd HH24:mi" "%Y-%m-%d %H" "yyyy-MM-dd HH24" "%Y-%m-%d %h" "yyyy-MM-dd HH" "%Y-%m-%d" "yyyy-MM-dd" "%Y-%m" "yyyy-MM" "%Y" "yyyy" "%H" "HH24" "%k" "HH24"
左
右
字段類型
必須相同
這裏注意是必須,可能在oracle版本不一樣的狀況下,老版本不一樣類型也會查詢出來,但建議仍是改成相同類型關聯,避免之後數據庫版本升級出現問題!!!
建議小轉大,好比:數字轉字符串;並使用CONCAT
去修改類型,由於mysql和oracle都支持此函數,而且不會在特殊類型上出現問題 ~服務器
-- ex: `JOIN` 關聯表時 兩張表的關聯`字段類型`必須`相同` SELECT a.*,b.* FROM 表1 a LEFT JOIN 表2 b on a.字符串類型字段 = CONCAT(b.數字類型字段, '')
-- mysql <insert id="insertBatch" databaseId="mysql"> INSERT INTO 表名( `字段名1`, `字段名2`, `字段...`) VALUES <foreach collection="list" item="item" separator="," open="(" close=")"> #{item.字段1},#{item.字段2},#{item中的每個字段名...} </foreach> </insert> -- oracle <insert id="insertBatch" databaseId="oracle"> INSERT INTO 表名(字段名1,字段名2,xxx...) SELECT A.* FROM( <foreach collection="list" item="item" index="index" separator="UNION ALL" > SELECT #{item.字段1},#{item.字段2},#{item中的每個字段名...} FROM DUAL </foreach> ) A </insert>
GROUP BY
oracle中GROUP BY
分組後,查詢出來的全部字段(除分組字段)必須爲聚合函數的字段,不然會報錯!session
解決:mybatis
分析函數 OVER (Partition BY ...) 及開窗函數
-- mysql SELECT 字段名,xxx... FROM 表名 GROUP BY 分組字段 -- oracle SELECT * FROM ( SELECT tb.*, ROW_NUMBER ( ) OVER ( PARTITION BY tb.分組字段 ORDER BY tb.排序字段 DESC ) AS result FROM ( SELECT 字段名,xxx... FROM 表名 -- 此處爲查詢sql,去掉`GROUP BY`分組條件,將分組字段加到上面 【 注:此sql的查詢字段中要麼全是聚合函數字段,要麼都不是! 】 ) tb ) WHERE result = 1
AS
, 列的別名能夠用AS
why ?:爲了防止和Oracle存儲過程當中的關鍵字AS衝突的問題
MySQL與Oracle不一樣之處遠不止小編說起的如上幾點,更多的還須要你們根據在實際項目中作對比,而後去修改哦 ~
舒適小提示:若是後期小編空閒會將上文中提供的測試替換工具類再加以修改,到時候會存放在以下github倉庫中...