公司内部推广DevOps,所有目前在维护阶段和开发阶段项目全部配置上了自动发布。采用Jenkins+SVN+ANT,之后批量执行SQL语句的实现提上日程
一、环境
- Linux环境
- 安装ANT工具,且下载ant扩展包---ant-contrib-1.0b3.jar,maven链接
- 下载oracle连接ojdbc5.jar包。
二、思路
步骤1:jenkins 从SVN下载所有SQL脚本
步骤2:编写shell脚本:获取今天的sql脚本,如果是今日已经执行过且执行成功的文件(即存在于bak文件夹中的),就不重复执行;
步骤3:编写ant脚本:对待执行脚本排序后,循环执行每条sql语句,执行成功的sql脚本备份到bak文件夹,方便后续执行时排除重复。
三、具体实现
3.1 执行shell
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
![](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
year=`date +%Y` today=`date +%Y%m%d` sqlfolder=${WORKSPACE}/svn/${year}/${today} bakfolder=${WORKSPACE}/sqlbak/${year}/${today} logfolder=${WORKSPACE}/sqllog if [ -d "runSqlInFolder" ];then echo '清空/runSqlInFolder文件夹' rm -rf runSqlInFolder/* else mkdir -p ${WORKSPACE}/runSqlInFolder fi if [ ! -d "${logfolder}" ];then mkdir ${logfolder} else echo dir ${logfolder} exist fi echo '提交到SVN的sql语句中,今日未执行过的拷贝到runSqlInFolder文件夹' cp -r ${sqlfolder}/* runSqlInFolder find runSqlInFolder/ -name '*.jar' |xargs rm -r find runSqlInFolder/ -type d | grep .svn$ | xargs rm -r if [ -d "${bakfolder}" ];then echo "备份文件夹已存在" cd ${bakfolder} find * -type f -name *.sql|xargs -i rm -f ../../../runSqlInFolder/{} else echo "备份文件夹不存在,创建备份文件夹: ${bakfolder}" mkdir -p ${bakfolder} fi
3.2 Invoke ant
3.2.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.2.2 定义execsql标签
通过sql标签执行sql文件,通过record标签记录这段执行的日志并且输出。
注意:如果执行procedure就需要设置delimiter,本例中通过SQL文件的命名来区分是不同SQL还是procedure。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
![](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
<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>
更新后
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
![](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
<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>
3.3 SVN文件夹设置
3.4 执行效果
四、遇到的问题处理
4.1【问题】如果执行的SQL语句中,有中文,执行完在数据库里是乱码
GBK、UTF-8是最常用的两种编码方式,是很多编码中的两种,也是两种风格(ANSI和UNICODE)中的代表
由于公司数据库采用GBK编码,所以将encoding改为GBK,乱码问题解决
<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;