Jenkins基礎篇 系列之-—07 實現SQL腳本批量執行


 公司內部推廣DevOps,全部項目配置CI/CD流水線(Jenkins+SVN+ANT),在代碼實現持續集成后,SQL語句自動執行的實現提上日程

一、環境

  • Linux環境
  • 安裝ANT工具,包括ant擴展包---ant-contrib-1.0b3.jar,maven鏈接
  • 下載oracle連接ojdbc5.jar包。

二、思路

步驟1:  從SVN的QL腳本提交路徑上,下載所有SQL腳本

步驟2:【shell腳本】:獲取日期文件夾名為今天的文件夾下的sql腳本,拷貝到待執行文件夾中。將其中已執行成功的腳本(即存在於bak文件夾中)刪掉;

步驟3:【ant腳本】:對待執行腳本排序后,循環執行每條sql語句,執行成功的sql腳本備份到bak文件夾,方便后續執行時排除重復。

三、具體實現

3.1 源碼管理

3.2 執行shell

if [ $# -ne 1 ]
then
        echo "Usage: $0 foldername"
        echo "  $0 Exec SQL"
        exit
fi

Workspace=$1
year=`date +%Y`
today=`date +%Y%m%d`
sqlfolder=${Workspace}/svn/${year}/${today}
bakfolder=${Workspace}/sqlbak/${year}/${today}
logfolder=${Workspace}/sqllog

#如果待執行sql文件夾已存在就清空該文件夾,否則創建
if [ -d "runSqlInFolder" ];then
  echo "-----Clean the executed sql folder:/runSqlInFolder-----"
  rm -rf runSqlInFolder/*
else
  mkdir -p ${Workspace}/runSqlInFolder
fi

if [ ! -d "${logfolder}" ];then
  mkdir ${logfolder}
else
  echo "-----Dir ${logfolder} is exist"
fi

#將今天提交到SVN的sql腳本中還未執行過的拷貝到runSqlInFolder文件夾
if [ -d "${sqlfolder}" ];then
  echo '-----Copy the sql file into folder'
  cp -r ${sqlfolder}/* runSqlInFolder
  find runSqlInFolder/ -name '*.jar' |xargs rm -rf
  find runSqlInFolder/ -type d | grep .svn$ | xargs rm -rf
  if [ -d "${bakfolder}" ];then
    cd ${bakfolder}
    find * -type f -name *.sql|xargs -i rm -f ../../../runSqlInFolder/{}
  else
    echo "-----create back folder: ${bakfolder}"
    mkdir -p ${bakfolder}
  fi
else
  echo dir ${sqlfolder} not exist
fi

3.3 Invoke ANT

3.3.1 定義一個target:runSqlInFolder

使用try catch包裹for 循環,for循環中調用execSQL標簽(自定義公共方法塊,即宏),順序逐條執行SQL腳本。

    <taskdef resource="net/sf/antcontrib/antlib.xml" classpath="${lib}/ant-contrib-1.0b3.jar"/>
    <target name="runSqlInFolder">
        <echo>Run the SQL at Folder: ${sqlfolder}</echo>
        <echo>DB Host: ${v7uatdb.host}</echo>
        <echo>DB Name: ${v7uatdb.name}</echo>
        <echo>DB User: ${v7uatdb.user}</echo>
        <trycatch property="errMsg">
            <try>            
                <for param="folder">
                    <path>
                        <sort xmlns:rcmp="antlib:org.apache.tools.ant.types.resources.comparators">
                            <dirset dir="${sqlfolder}" includes="*" />                        
                        </sort>
                    </path>
                    <sequential>
                    <echo>SQL Folder: @{folder}</echo>    
                    <for param="file">
                        <path>
                            <sort xmlns:rcmp="antlib:org.apache.tools.ant.types.resources.comparators">
                                <fileset dir="@{folder}" includes="*/*.sql" casesensitive="false"/>                                            
                            </sort>
                        </path>
                        <sequential>
                        <echo>SQL: @{file}</echo>                             
                        <execsql
                            dbhost="${v7uatdb.host}"    
                            dbport="${v7uatdb.port}"    
                            dbname="${v7uatdb.name}" 
                            dbuser="${v7uatdb.user}" 
                            dbpwd="${v7uatdb.pwd}"
                            sqlfile="@{file}"
                            logfile="${Sqllogfile}"/>
                        </sequential>
                        <!--<move file="@{file}" todir="${sqlbakdir}/@{folder}"/>
                        folder 包含路徑和文件名,所以直接復制file還有點問題,需要截取文件名--目前待研究 -->
                    </for>
                    <move file="@{folder}" todir="${sqlbakdir}"/> 
                    </sequential>    
                </for>
                <echo>Finished running all SQL</echo>
                <echo>File moved to backup folder:</echo>
                <echo>${sqlbakdir}</echo>
            </try>
            <catch>
                <echo>Error found when running SQL</echo>
                <echo>Log file can be found in:</echo>
                <echo>${sqlbakdir}/err</echo>
                <move file="${Sqllogfile}" todir="${sqlbakdir}/err"/>
                <fail>Error Occur</fail>
            </catch>
            <finally>
            </finally>
        </trycatch>
    </target>

3.3.2 定義execsql標簽

通過sql標簽執行sql文件,通過record標簽記錄對應的執行日志並輸出。

注意:如果執行procedure就需要設置delimiter,本例中通過SQL文件的命名來區分是不同的SQL類型(procedure,declare等)。

    <macrodef name="execsql" description="Run single SQL file.">
        <attribute name="dbhost" description="Host Name/ IP of the DB"/>
        <attribute name="dbport" description="DB Port"/>
        <attribute name="dbname" description="DB name"/>
        <attribute name="dbuser" description="DB User name"/>
        <attribute name="dbpwd" description="DB Password"/>
        <attribute name="sqlfile" description="SQL file to be run"/>
        <attribute name="logfile" default="sql.log" description="Log file"/>
        <sequential>
            <echo>Log file @{logfile}</echo>                    
            <record name="@{logfile}" action="start"/>    
            <if>
                <contains string="@{sqlfile}" substring="PROCEDURE"/>
                <then>
                    <sql driver="${oracleDriver}"
                        url="jdbc:oracle:thin:@@@{dbhost}:@{dbport}:@{dbname}"
                        userid="@{dbuser}"
                        password="@{dbpwd}"
                        classpathref="classpath"
                        encoding="${encoding}"
                        print="true"
                        autocommit="true"
            delimiter="/"
            delimitertype="row">
                        <transaction src="@{sqlfile}"/>
                    </sql>
                </then>
                <else>
                    <sql driver="${oracleDriver}"
                        url="jdbc:oracle:thin:@@@{dbhost}:@{dbport}:@{dbname}"
                        userid="@{dbuser}"
                        password="@{dbpwd}"
                        encoding="${encoding}"
                        classpathref="classpath"
                        autocommit="true"
                        print="true">
                        <transaction src="@{sqlfile}"/>
                    </sql>
                </else>
            </if>
            <record name="@{logfile}" action="stop"/> 
        </sequential>
    </macrodef>
 
View Code

更新后

    <macrodef name="execsql" description="Run single SQL file.">
        <attribute name="dbhost" description="Host Name/ IP of the DB"/>
        <attribute name="dbport" description="DB Port"/>
        <attribute name="dbname" description="DB name"/>
        <attribute name="dbuser" description="DB User name"/>
        <attribute name="dbpwd" description="DB Password"/>
        <attribute name="sqlfile" description="SQL file to be run"/>
        <attribute name="logfile" default="sql.log" description="Log file"/>
        <sequential>
            <echo>Log file @{logfile}</echo>    
            <record name="@{logfile}" action="start"/>    
            <if>
                <contains string="@{sqlfile}" casesensitive="no" substring="PROCEDURE"/>
                <then>
                    <sql driver="${oracleDriver}"
                        url="jdbc:oracle:thin:@@@{dbhost}:@{dbport}:@{dbname}"
                        userid="@{dbuser}"
                        password="@{dbpwd}"
                        classpathref="classpath"
                        encoding="${encoding}"
                        print="true"
                        autocommit="true"
                        delimiter="/"
                        delimitertype="row">
                        <transaction src="@{sqlfile}"/>
                    </sql>
                </then>
            <elseif>
                <contains string="@{sqlfile}" casesensitive="no" substring="DECLARE"/>
                <then>
                    <sql driver="${oracleDriver}"
                        url="jdbc:oracle:thin:@@@{dbhost}:@{dbport}:@{dbname}"
                        userid="@{dbuser}"
                        password="@{dbpwd}"
                        classpathref="classpath"
                        encoding="${encoding}"
                        print="true"
                        autocommit="true"
                        delimiter=";;">
                        <transaction src="@{sqlfile}"/>
                    </sql>
                </then>
             </elseif>    
            <else>
                    <sql driver="${oracleDriver}"
                        url="jdbc:oracle:thin:@@@{dbhost}:@{dbport}:@{dbname}"
                        userid="@{dbuser}"
                        password="@{dbpwd}"
                        encoding="${encoding}"
                        classpathref="classpath"
                        autocommit="true"
                        print="true">
                        <transaction src="@{sqlfile}"/>
                    </sql>
            </else>
            </if>
            <record name="@{logfile}" action="stop"/> 
        </sequential>
    </macrodef>
View Code

3.4  SVN中存放SQL的文件夾設置

3.5 成功執行效果

3.5 注意

在這個過程中,需要DBA 介入人工審核:

第一類:是在提交腳本之后。審核的內容主要是變更內容是否合法、方式是否得當、是否影響業務等等。

第二類:是在提交生產變更后。審核的主要的內容是,判斷變更是否會對當時的生產系統產生影響。比如,訂單表的更新、大表的變化等,就不允許在業務高峰期進行。 

四、遇到的問題處理

4.1【問題】如果執行的SQL語句中存在中文,執行完在數據庫中保存的數據是亂碼

目前常用的兩種數據庫編碼格式為:GBK、UTF-8,也是兩種編碼風格(ANSI和UNICODE)中的代表

經確認公司數據庫采用GBK編碼,所以將encoding改為GBK,亂碼問題解決,ant文件頭設置

<property name="encoding" value="GBK" /><!--UTF-8-->

4.2【問題】如果執行的SQL語句中,有declare,聲明變量的,執行declare過程中會報錯

delete zssys.WEB_APP_TEMPLATE where C_INTERFACE_ID = '0000-GDCARVA';;
declare
  response clob;
begin
  response := 'aaa';

insert into zssys.WEB_APP_TEMPLATE (C_PK_ID, C_INTERFACE_ID, C_SYSCODE, C_APP_NAME, C_TEMPLATE_REQUEST, C_TEMPLATE_RESPONSE)
values (SYS_GUID(), '0000-GDCARVA', '*', 'test', null, response);
end;

4.3【問題】目前備份腳本是按照模塊整體備份的,沒有做到單個sql腳本備份,后期優化

五、 參考資料

使用ANT腳本批量執行SQL,並且結合Jenkins自動化構建

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM