1  kettle.properties參数配置数据源连接和FTP连接

因为測试环境和生产环境中数据库连接FTP等配置会在部署过程中变更,所以预先定义成配置项,在配置文件里改动。这样測试和公布将会变得简单,以下以数据库为例说明这类配置的使用。

 

(1)      首先要找到配置文件,不同的操作系统路径也不一样。本人用win7进行开发,配置文件的路径为“C:\Users\chenpeng\.kettle\kettle.properties”。例如以下:


(2)      配置文件里的详细配置例如以下:


还能够可视化设置:


(3)      详细使用演示样例

l  下方是数据库连接配置:




l  下方是FTP连接配置:

 

1.1.2  kettle.properties參数设置和路径及与正则配合使用

(1)在kettle.properties中设置变量值

 

(2)kettle.properties设置例如以下(win7下的路径为:C:\Users\chenpeng\.kettle)


(4)      在输出文件时使用參数中指定的路径:




注:假设路径出现错误,如把某个目录删除则会报错。仅仅要是报错,即使把目录建好了也仍然会报目录错误(好像有记忆功能),这时必须又一次启动kettle才干正常执行。

l  在输出文件时使用參数中和正則表達式混合使用场景:


1.1.3  kettle.properties參数在java代码中的应用


1.1.4  作业中变量使用并用javascript设置变量值

上面的样例都是kettle.properties中声明的变量的应用。这些都是全局范围内通用的,但非常多时间。子作业须要有内部专用的变量參数。这时就不能使用kettle.properties中声明的变量了。须要在流程中声明变量,并把作用域设置为当前作业有效。下面应用场景业务例如以下:文件名称要命名成当前日期格式的。所以在作业级别定义了一个变量,但无法给它赋值。如是採用了javascript脚本方式给该变量赋值,然后在输出文件名称的位置应用该变量就可以,后面文件的删除上传都是公用部分都须要用到这个变量做为接口參数来做处理。

(1)      主流程例如以下:


(2)      定义变量:

 

(3)      子过程中调用javascript脚本改动值:



Date.prototype.Format = function (fmt) { //author: meizz   
   var o = {  
       "M+": this.getMonth() + 1, //月份   
       "d+": this.getDate(), //日   
       "h+": this.getHours(), //小时   
        "m+": this.getMinutes(), //分   
      "s+": this.getSeconds(), //秒   
       "q+": Math.floor((this.getMonth() + 3) / 3), //季度   
      "S": this.getMilliseconds() //毫秒   
    };  
    if (/(y+)/.test(fmt)) fmt = fmt.replace(RegExp.$1, (this.getFullYear() + "").substr(4 - RegExp.$1.length));  
   for (var k in o)  
    if (new RegExp("(" + k + ")").test(fmt)) fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr

(("" + o[k]).length)));  
    return fmt;  
}  
var dateTime = new Date().Format("yyyyMMdd"); // gives back today at yyyy/MM/dd HH:mm:ss.000
setVariable("curdate",dateTime,"s");


另外,假设是用上一节点的字段值,改动变量值则更为简单,例如以下:



(4)      使用变量(按常规使用):



1.1.5  Java代码訪问变量调用jar包并生成验证文件

这是一个较为综合性的演示样例。首先定义了一条记录(可能理解为非常多个变量),然后通过java代码来调用jar包,计算出记录数、文件大小、MD5值等,赋值给对应记录字段。并输出到文件,形成数据文件的校验文件。

(1)      主流程例如以下:



(2)      生成记录

 

(3)      Java处理



//导入在eclipse中编辑好的包。主要用于计算文件行数、MD5值
import cgb.tools.KettleHelper;
import java.io.File;
import java.io.IOException; 

//kettle中已定义好的行处理方法,每行记录都会运行一次
public boolean processRow(StepMetaInterface smi, StepDataInterface sdi) throws KettleException
{
	//(1)获取到上一个步骤的输入行
	Object[] r = getRow();
	if (r == null) {
	  setOutputDone();
	  return false;
	}
	r = createOutputRow(r, data.outputRowMeta.size());

	//(2)读取出參数变量值
	String kettleoutputdir = getVariable("kettleoutputdir", "");
	String hbprovince_code = getVariable("hbprovince_code", "");
	String item_code = getVariable("item_code", "");
	String curdate = getVariable("curdate", "");

	String filename = "";
	String onlyfilename = "";
	String recordcount = "";
	String bytecount = "";
	String md5code = "";

	//(3)调用jar包计算出MD5值、行数、字节数
	try {
		filename = kettleoutputdir + "\\" + hbprovince_code + "_" + item_code + "_day_" + curdate + ".csv";
		onlyfilename = hbprovince_code + "_" + item_code + "_day_" + curdate + ".csv";
		md5code = KettleHelper.getFileMD5String(filename);
		recordcount = String.valueOf(KettleHelper.getFileRecordCount(filename, true));
		bytecount = String.valueOf(KettleHelper.getFileByteCount(filename));
	} catch (IOException e) {
		e.printStackTrace();
	}

	//(4)把计算好的值放入到输出记录中
	get(Fields.Out, "filename").setValue(r, onlyfilename);
	get(Fields.Out, "recordcount").setValue(r, recordcount);
	get(Fields.Out, "byteCount").setValue(r, bytecount);
	get(Fields.Out, "md5code").setValue(r, md5code);

	//(5)输出到下一个节点做处理
    putRow(data.outputRowMeta, r);
	return true;
}

(4)      Jar包的开发和导出

当中调用的jar包,能够选择用eclipse来生成。例如以下:


package XXX.tools;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
  
public class KettleHelper {  
    /** 
     * 默认的密码字符串组合。用来将字节转换成 16 进制表示的字符,apache校验下载的文件的正确性用的就是默认的这个组合 
     */  
    private static char hexDigits[] = { '0', '1', '2', '3', '4', '5', '6',  
            '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };  
  
    private static MessageDigest messagedigest = null;  
    static {  
        try {  
            messagedigest = MessageDigest.getInstance("MD5");  
        } catch (NoSuchAlgorithmException nsaex) {  
            System.err.println("MD5Util.class.getName()" + "初始化失败,MessageDigest不支持MD5Util。

"); nsaex.printStackTrace(); } } /** * 生成字符串的md5校验值 * * @param s * @return */ private static String getMD5String(String s) { return getMD5String(s.getBytes()); } /** * 推断字符串的md5校验码是否与一个已知的md5码相匹配 * * @param password 要校验的字符串 * @param md5PwdStr 已知的md5校验码 * @return */ public static boolean checkPassword(String password, String md5PwdStr) { String s = getMD5String(password); return s.equals(md5PwdStr); } /** * 生成文件的md5校验值 * * @param file * @return * @throws IOException */ public static String getFileMD5String(String fileName) throws IOException { File file = new File(fileName); InputStream fis; fis = new FileInputStream(file); byte[] buffer = new byte[1024]; int numRead = 0; while ((numRead = fis.read(buffer)) > 0) { messagedigest.update(buffer, 0, numRead); } fis.close(); return bufferToHex(messagedigest.digest()); } /** * 获取文件的记录条数 * * @param file * @return * @throws IOException */ public static int getFileRecordCount(String fileName,boolean hasHeadRow) throws IOException { File inFile = new File(fileName); // 读取的CSV文件 @SuppressWarnings("unused") String inString = ""; int count = 0; try { BufferedReader reader = new BufferedReader(new FileReader(inFile)); while((inString = reader.readLine())!= null){ count++; } reader.close(); } catch (FileNotFoundException ex) { System.out.println("没找到文件!"); } catch (IOException ex) { System.out.println("读写文件出错!

"); } if(hasHeadRow) { count--; } return count; } /** * 生成文件的md5校验值 * * @param file * @return * @throws IOException */ public static String getFileMD5String(File file) throws IOException { InputStream fis; fis = new FileInputStream(file); byte[] buffer = new byte[1024]; int numRead = 0; while ((numRead = fis.read(buffer)) > 0) { messagedigest.update(buffer, 0, numRead); } fis.close(); return bufferToHex(messagedigest.digest()); } private static String getMD5String(byte[] bytes) { messagedigest.update(bytes); return bufferToHex(messagedigest.digest()); } private static String bufferToHex(byte bytes[]) { return bufferToHex(bytes, 0, bytes.length); } private static String bufferToHex(byte bytes[], int m, int n) { StringBuffer stringbuffer = new StringBuffer(2 * n); int k = m + n; for (int l = m; l < k; l++) { appendHexPair(bytes[l], stringbuffer); } return stringbuffer.toString(); } private static void appendHexPair(byte bt, StringBuffer stringbuffer) { char c0 = hexDigits[(bt & 0xf0) >> 4];// 取字节中高 4 位的数字转换, >>> 为逻辑右移,将符号位一起右移,此处未发现两种符号有何不同 char c1 = hexDigits[bt & 0xf];// 取字节中低 4 位的数字转换 stringbuffer.append(c0); stringbuffer.append(c1); } /** * 获取文个把的字节数 * @param fileName * @return * @throws IOException */ public static long getFileByteCount(String fileName) throws IOException { File file = new File(fileName); return file.length(); } }


导出jar包


放入到kettle的jar包文件夹,它会自载入。操作系统不同,文件夹也会不同。本人使用的是win7,文件夹例如以下:

 

 

(5)      文件输出


(6)   终于生成文件的效果例如以下:


1.1.6     SQL中使用变量

以下的查询语句用问号占位符,当開始日期(第一个?号)和结束日期(第二个?号)绑定到SQL的问号占位符,在查询入职日期在一定期间的总统信息:

SELECTname,took_office FROMpresidents WHEREtook_officeBETWEEN? AND?


演示样例中。首先使用生成行步骤(“Generdate Rows”)生成一行带有两个字段的记录,分别按顺序取代表输入SQL语句中的占位符。

实际场景中,通常使用动态处理结果产生期望值取代生成行步骤。


接下来是表输入步骤,当中配置SQL查询语句。包括问号占位符,通过在“Insert Data Step”的下拉框中选择前一步骤,来替换问号的值。

通过传输不同的值多次运行查询

假设你想循环运行查询。使用不同值替换占位符。就须要占位符生产步骤生成多行数据,并把表输入的选项“Execute for each row”选中。本演示样例文件名为placeholders_in_loop.ktr


占位符的局限性

尽管通过给占位符绑定值查询很有效,但也有一些场景不能使用,以下一些SQL不能使用占位符。

这些演示样例都很通用。可是不能使用占位符。

不能用占位符取代表名词,否则查询将不运行。

SELECT some_fieldFROM ?

不能使用占位符取代查询的字段名称,以下的查询能够成功绑定參数,但仅仅是作为一个常量,而不是字段的名称。

SELECT ? asmy_field FROM table

不能使用占位符绑定逗号分隔的多个列表项值;假设你绑定1,2,3给以下的查询语句,将得到意外的结果。

SELECT * FROM testWHERE id IN(?

)

你期望得到的结果是:

SELECT * FROM testWHERE id IN("1,2,3")

可是执行的结果却是这样,传输一个字符串,却得到三个值,而实际情况全然不确定有几个值传输进来。

SELECT * FROM testWHERE id IN(1,2,3)

为了解决这些场景的问题,须要使用kettle的变量动态构造查询文本。以下具体说明。

SQL查询中使用kettle变量

表输入步骤支持替换查询中的变量或參数。如果有一系列结构全然相关的表,各自是: mammals, birds, insects(动物、鸟、昆虫),能够使用kettle变量作为表的名称。

如果我们有一个变量。名称为:ANIMALS_TABLE,赋值为birds,我们设置“Replace Vaiables”选项选中。假设我们写以下的查询:

SELECT name,population FROM${ANIMALS_TABLE}

在运行一定被成功的替换成:

SELECT name,population FROM birds

假设设置变量的值为“mammals”或“insects”。则将动态查询不同的表。当占位符不能胜任是,使用变量技术能够帮助我们解决。演示样例的名称为variables.ktr。执行时不要忘了给parameter(命名參数)赋值进行測试。


变量和占位符一起使用

假设有必要。我们能够混合这两种技术;本演示样例中使用变量作为表名词,同一时候使用占位符作为前面步骤的输入值。

演示样例文件variables_and_placeholders.ktr

SELECT name, population FROM${ANIMALS_TABLE}WHERE population > ?