One, foreword
Because of project requirements, xiaobian will migrate project from mysql to Oracle ~
Before have completed data migration (zhengqing.blog.csdn.net/article/det…).
You will now complete the conversion of all MySQL statements in the project to Oracle statements based on MyBatis-Plus
Approximate implementation steps:
- Change the project to support dual database configuration (because the project based on mysql database has been completed, and do not want to directly replace, so the new Oracle library, so that the whole project can support multiple databases, here is not multi-data source oh!)
- Create common functions in Oracle
- Walk through the project
xxxMapper.xml
File, find the difference between mysql and Oracle statements, and replace most of the SQL - Finally, manually change some special mysql statements to Oracle statements
MyBatis-Plus supports dual library configurationmysql
.oracle
】
1.application.yml
In the configurationmybatis-plus
thedatabase-id
# mybatis-plus: configuration: jdbc-type-for-null: 'null' # mybatis-plus: configuration: jdbc-type-for-null: 'null' Oracle # supports multiple library configurations for mysql, OracleCopy the code
2, MybatisPlus core configuration file -> Execute different SQL according to different database vendors
@Configuration @MapperScan("com.zhengqing.demo.modules.**.mapper*") public class MybatisPlusConfig { /** * The 'databaseId' in the 'xxxmapper. XML' file will automatically identify the type of database being used as corresponding to this * note: If you do not specify a ` databaseId ` then the SQL statement applies to all database oh ~ * * databaseIdProvider: support for multiple database vendors * VendorDatabaseIdProvider: GetDatabaseProductName (), myBatis can execute different SQL based on the database vendor id; * MySQL, Oracle, SQL Server,xxxx */ @Bean public DatabaseIdProvider getDatabaseIdProvider(){ DatabaseIdProvider databaseIdProvider = new VendorDatabaseIdProvider(); Properties properties = new Properties(); // alias properties.setProperty("MySQL"," MySQL") for different database vendors; properties.setProperty("Oracle","oracle"); databaseIdProvider.setProperties(properties); return databaseIdProvider; }}Copy the code
3,xxxMapper.xml
Through thedatabaseId
Specifying the database type
<select ID ="selectUserInfo" resultMap="UserVO" databaseId="mysql"> select * FROM table name LIMIT 1 </select> <select Id=" selectUserInfo" resultMap="UserVO" databaseId="oracle"> SELECT * FROM table WHERE ROWNUM <= 1 </ SELECT >Copy the code
Create common functions in Oracle
Here according to the personal project situation to the actual application can ~
1.ORACLE_TO_UNIX
The Oracle Date type is converted to a Unix timestamp, equivalent to UNIX_TIMESTAMP in mysql
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;Copy the code
2,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; STR varchar2(500); Piv_str varchar2(500) := piv_str2; -- assign piv_str2 to piv_str res number:=0; LoopIndex number:=0; BEGIN -- If there is no separator in piv_str, determine whether piv_str1 and PIv_str are equal. IF instr(piv_str, p_sep, 1) = 0 THEN IF piv_str = piv_str1 THEN res:= 1; END IF; Elsepiv_str LOOP l_idx := instr(piv_str,p_sep); loopIndex:=loopIndex+1; STR := substr(piv_str,1,l_idx-1); IF STR = piv_str1 THEN res:= loopIndex; EXIT; END IF; piv_str := substr(piv_str,l_idx+length(p_sep)); IF piv_str = PIv_str1 THEN res:= loopIndex; IF piv_str = piv_str1 THEN res:= loopIndex; END IF; -- EXIT the loop regardless of whether it is equal at the end; END IF; END LOOP; -- END IF; -- RETURN res RETURN res; END FIND_IN_SET;Copy the code
MySQL > convert MySQL to Oracle
Replacement steps:
- in
xxxMapper.xml
Add to all SQL statementsdatabaseId="mysql"
- Copy a copy of mysql SQL (i.e. the oracle statement to be replaced)
- Join on the replicated SQL
databaseId="oracle"
- Find out the difference between mysql and Oracle statements and replace SQL statements
Warm tips: this tool class is for reference only, the actual operation according to their own project to modify oh, before operation, it is recommended to back up their own project, in case of improper operation lost code oh!
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 {
// 1、加入 databaseId="mysql"
addMysql(filePath);
// 2、复制一份oracle的sql
if (!checkHasOracle(filePath)) {
copyMysqlToOracle(filePath);
}
// 3、加入 databaseId="oracle"
addOracle(filePath);
// 4、替换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);
}
}
}
}Copy the code
Five, the summary
Here is a brief description of the difference between MySQL and Oracle SQL, as well as MySQL statement conversion Oracle statement example
1, paging
- mysql:
LIMIT 0, 1
- oracle:
ROWNUM <= 1
Situation ① : Mysql does not containORDER BY
-- mysql SELECT * FROM table_name LIMIT 1 -- oracle SELECT * FROM table_name WHERE ROWNUM <= 1Copy the code
Scenario 2: Mysql containsORDER BY
-- mysql SELECT * FROM table_name ORDER BY table_name 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;Copy the code
Sweet tips:SQL
SELECT statement execution order
FROM
Clause assembles data from different data sourcesWHERE
Clause filters records based on specified criteriaGROUP BY
Clauses divide data into groupsAggregation function
To calculateHAVING
Clause filter groupingTo calculate
allexpression
ORDER BY
Sort the results
2, Oracle field names cannot include the symbol ‘ ‘
Mysql SELECT * FROM mysql; mysql SELECT * FROM mysqlCopy the code
3. String concatenation
Note: CONCAT function in Oracle only two parameters, thus to | | splicing
- mysql:
CONCAT('%', 'xxx' , '%')
- oracle:
'%' || 'xxx' || '%'
Mysql SELECT * FROM table_name WHERE table_name LIKE CONCAT('%','helloworld','%') || 'helloworld' || '%')Copy the code
4. Convert the date type to a 10-bit timestamp
- mysql:
UNIX_TIMESTAMP
- oracle:
ORACLE_TO_UNIX
(Note: this function was created manually in Step 3 and is not native to Oracle.)
5,The field name
forOracle keywords
Need to addDouble quotation marks
Tip: The field name must be uppercase. If the corresponding field of the Java entity class is lowercase, the alias of the mapping must be lowercase and correspond to ~
Such as:
level -> "LEVEL"
user -> "USER"
number -> "NUMBER"
desc -> "DESC"
date
->DATE
NULL: if x is NULL, return value, otherwise return x itself
- mysql:
IFNULL(x, value)
- oracle:
NVL(x, value)
7. Date and time swap
Mysql before oracle
- String type to time type:
STR_TO_DATE
->TO_DATE
- Time type to specified string type:
DATE_FORMAT
->TO_CHAR
- Obtain the current system time:
NOW()
->SELECT SYSDATE FROM DUAL
Turn - time type specified string type SELECT DATE_FORMAT (NOW (), '% % Y - m - H: % d % % I: % s'); -- mysql SELECT TO_CHAR( SYSDATE,'yyyy-MM-dd HH24:mi:ss') FROM DUAL; -- oracle -- string type to time type SELECT STR_TO_DATE(NOW(), '%Y-%m-%d %H'); -- mysql SELECT TO_DATE( '2020-01-09', 'yyyy-MM-dd') FROM DUAL; -- SELECT NOW(); -- SELECT NOW(); -- mysql SELECT SYSDATE + 8/24 FROM DUAL; -- mysql SELECT YEAR(NOW()); -- mysql SELECT YEAR(NOW()); SELECT QUARTER(NOW()); SELECT MONTH(NOW()); SELECT TO_CHAR(SYSDATE, 'Q') FROM DUAL; Quarter - oCopy the code
In addition, here is the format of the time identifier used by xiaobian
Mysql > select * from '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"Copy the code
8. When judgingOn the left
right
The field type
Must beThe same
Note here is a must, may be different in the case of oracle version, the old version of different types will also be queried, but it is recommended to change the same type association, to avoid future database version upgrade problems!!
You are advised to convert a small number to a large number, for example, to a string. And use CONCAT to change the type, because both mysql and Oracle support this function and there are no problems with special types
SELECT a.*,b.* FROM table 1 a LEFT JOIN table 2 b on a String type field = CONCAT(b. Numeric type field, "")Copy the code
9. Batch insert
-- mysql <insert id="insertBatch" databaseId="mysql"> `) VALUES <foreach collection="list" item="item" separator="," open="(" close=")"> #{item. Field 1},#{item. Field 2}, every field name in #{item... } </foreach> </insert> -- oracle <insert ID ="insertBatch" databaseId="oracle"> SELECT A.* FROM( <foreach collection="list" item="item" index="index" separator="UNION ALL" > SELECT #{item. Field 1},#{item. Field 2}, every field name in #{item... } FROM DUAL </foreach> ) A </insert>Copy the code
10, groupingGROUP BY
SQL > select * from GROUP BY; select * from GROUP BY;
Solution:
- Query fields are changed to aggregate functions
- Use the following
OVER (Partition BY...) And the windowing function
Mysql > SELECT * from mysqld; -- Oracle SELECT * FROM (SELECT TB.*, ROW_NUMBER () OVER (PARTITION BY TB). ORDER BY TB. DESC) AS result FROM (SELECT name, XXX... Select 'GROUP BY' FROM 'select * FROM' select * FROM 'select * FROM' select * FROM ' ) tb ) WHERE result = 1Copy the code
SQL > alter table alias not availableAS
, column aliases can be usedAS
why ? : to prevent conflicts with the Oracle stored procedure keyword AS
Note that Oracle is very strict about data types.
The difference between MySQL and Oracle is far more than the above mentioned points, more also need to be compared according to the actual project, and then to modify oh ~
MySQL to Oracle statement tool source code
Tips: If xiaobian is free later, the test replacement tool class provided above will be modified, and it will be stored in the following Github repository…
Github.com/zhengqingya…