在 Jenkins Pipeline 中,我们需要连接 MySQL 数据库,以存储某些状态数据,用于在多个 Job 之间共享。这些数据不属于制品,通过制品管理的方式进行传递是件繁琐的事情,而且存在问题。然而,通过数据库进行传递是最好的方式。因此,我们需要解决在 Jenkins Pipline 中连接数据库的问题。
但是,事情往往没有看起来那样简单。我们难以像「Groovy 连接数据库」那样使用 mysql-connector-java 类库,原因在于 JDBC 驱动的加载方式以及 Jenkins Pipeline 类加载器的设计,这两点导致我们无法直接使用。但是,我们终究是找到解决方案。
该笔记将记录:在 Jenkins Pipeline 中,如何连接 MySQL 数据库,以及常见问题处理。
针对大多数构建任务,没有必要使用 Jenkins Pipeline 访问数据库,这也许是种小众需求。如果在设计中出现这种需求,也许应该重新考虑。我们之所以需要连接数据库,是因为我们要用 Jenkins 做些复杂的事情;
方案一、通过系统类加载器(废弃)
java – Where to put external jars in jdk10 – Stack Overflow
connecting to mysql db from jenkins pipeline not wroking – Stack Overflow
How to add a JDBC driver to Jenkins’s jobs & load the parameter values from SQL Database
mysql – How to add a JDBC driver to a Jenkins pipeline? – Stack Overflow
好在还有其他的方法:通过 Extension 机制,能加载自定义 Jar 包;
首先,在 Jenkins Pipeline 中,执行如下语句,以获取扩展加载位置:
println System.getProperty("java.ext.dirs"),
在我们的环境中,如上语句输出:/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.302.b08-0.el7_9.x86_64/jre/lib/ext:/usr/java/packages/lib/ext
然后,下载 mysql-connector-java.jar 类库,并放入 /usr/java/packages/lib/ext/ 中,此时 mysql-connector-java 将由系统类加载器进行加载;
最后,访问数据库的代码如下(这与「Groovy 连接数据库」基本类似,但无需 Grab 引入驱动):
import java.sql.*; import groovy.sql.Sql def connectionString = "jdbc:mysql://<ip-address>:<port-number>/<database>" def username = "<username>" def password = "<password>" def driver = "com.mysql.jdbc.Driver" def sqlInstance = Sql.newInstance(connectionString, username, password, driver) // 查询第一条数据 def firstRow = sqlInstance.firstRow("select * from <database>.<table>") print firstRow
如果采用该方法,(1)建议不再使用 YUM 提供的 OpenJDK 包,(2)而应该到 OpenJDK 官网下载,然后放入自定义的 Jar 包,并单独维护;
# 08/14/2021 在 Java 10 中,移除 Extension 机制,因此不能再使用该方法,只能使用 databse 插件的方式;
方案二、通过 database 插件(推荐)
database | Jenkins plugin ⇒ database-h2 | Jenkins plugin ⇒ MySQL Database | Jenkins plugin
# 09/24/2020 官方提供 Database ⇒ MySQL API Plugin ⇒ MySQL Database Plugin 扩展,但是没有演示程序,文档也少的可怜(近乎没有),所以我们也没有办法使用官方插件(经过反复的实验,并没有成功,也许是我们的操作方式不对);
# 03/24/2021 但是,我们现在找到相关的文档说明,并通过测试。该部分将记录这些内容,介绍连接 MySQL 数据库的方法;
https://plugins.jenkins.io/database/
第一步、安装 database 与 MySQL Database Plugin 插件
其中,插件 database 是抽象层,对“底层驱动”进行封装。而这里的“底层驱动”便是“MySQL Database Plugin”插件。除了连接 MySQL 数据库的插件,还有 PostgreSQL Database plugin、H2 Database plugin 等等(这里不再赘述插件的安装方法);
官网:https://plugins.jenkins.io/database-mysql/
文档:https://www.jenkins.io/doc/pipeline/steps/database/
仓库:https://github.com/jenkinsci/database-mysql-plugin
第二步、添加 MySQL 数据库配置
在 Manage Jenkins ⇒ Configure System ⇒ Global Database 中,填写数据库信息:
1)选择 MySQL 数据库;
2)填写 Username、Password 等等信息;
3)填写 Validation Query 字段,例如 select version(); 语句;
4)最后,点击按钮 Test Connection 测试连接,最后 Save 保存;
第三步、在 Pipeline 中,连接数据库
pipeline { agent any stages { stage('# 构建开始') { steps { script{ getDatabaseConnection(type: 'GLOBAL') { def result = sql(sql: "SHOW SESSION VARIABLES LIKE '%char%'") println "Class of result: ${result.getClass().toString()}" println "Value of result: ${result}" } } } } } }
下面是执行的输出结果(省略无关内容),正如我们所见,返回 ArrayList 对象:
... Class of result: class java.util.ArrayList Value of result: [[SCHEMA_NAME:information_schema], [SCHEMA_NAME:test_db]] ...
关于连接数据库的相关参数,参考 Pipeline Steps Reference/database 文档;
中文乱码
hibernate – JDBC MySQL character encoding: Why is useUnicode required? – Stack Overflow
但是,数据库写入的中文是乱码,需要指定 useUnicode=true&characterEncoding=UTF-8 属性,解决方法如下:
1)在 $JENKINS_HOME/jdbc-drivers/ 中,保存 JDBC 驱动,比如 mysql-connector-java.jar 文件,然后重启 Jenkins 服务;
2)在 Manage Jenkins ⇒ Configure System ⇒ Global Database 中,Database: Generic
3)JDBC Driver Class: com.mysql.jdbc.Driver
4)JDBC Connection URL: jdbc:mysql://hostname:3306/db?useUnicode=true&characterEncoding=UTF-8
5)User Name、Passowrd
6)最后,点击按钮 Test Connection 测试连接,最后 Save 保存;
java.sql.SQLException: Incorrect string value: ‘\xAC\xED\x00\x05sr…’ for column…
mysql – java.sql.SQLException: Incorrect string value: ‘\xAC\xED\x00\x05sr…’ – Stack Overflow
在 Jenkins Pipeline 中,我们使用 PreparedStatement 执行 SQL 语句,产生如下错误:
java.sql.SQLException: Incorrect string value: '\xAC\xED\x00\x05sr...' for column 'content' at row 1
问题原因
传入 PreparedStatement 的 Sql 参数并非 String 类型,而是 org.codehaus.groovy.runtime.GStringImpl 类型。通过如下代码验证:
pipeline { agent { node { label 'NODE03-UBUNTU-20.04' } } stages { stage ("# 构建开始") { steps { script { def str1 = "1" def str2 = "2" println "${str1}/${str2}".getClass().toString() } } } } }
解决办法
解决办法很简单,转换成 String 类型即可:
// 解决办法 println "${str1}/${str2}".toString() // 验证为 String 类型 println "${str1}/${str2}".toString().getClass().toString()