文章目录
要定义一个外部数据源可以参考
JDBCrelation和JDBCrelationprovider。
JDBCrelation相当于用户可以使用里面的方法实现数据select,过滤和插入。
JDBCrelationprovider相当于用户和JDBCrelation的中转站,解析用户传入的参数。
JDBCrelationprovider
createrelation可以接受用户参数,调用JDBCrelation
模仿JDBCrelationprovider
package sql04.text
import org.apache.spark.sql.SQLContext
import org.apache.spark.sql.sources.{BaseRelation, RelationProvider, SchemaRelationProvider}
import org.apache.spark.sql.types.StructType
//jdbcrelationprovider
//必须叫DefaultSource,不然调用报错
//schemarelationprovider可以让用户传入schema
class DefaultSource extends RelationProvider with SchemaRelationProvider{
def createRelation(
sqlContext: SQLContext,
parameters: Map[String, String],
schema: StructType): BaseRelation = {
//读文件就要path
val path = parameters.get("path")
path match {
case Some(x) => new TextDatasourceRelation(sqlContext, x, schema)
case _ => throw new IllegalArgumentException("path is required for custom-text-datasource-api")
}
}
override def createRelation(sqlContext: SQLContext, parameters: Map[String, String]): BaseRelation = {
createRelation(sqlContext,parameters,null)
}
}
这里继承的RelationProvider和SchemaRelationProvider区别在于,schemarelationprovider可以接受schema码。
注意,这里的类名要是DefaultSource。
JDBCrelation
可以看出JDBCRelation继承自Baserelation,PrunedFilteredScan,InsertableRelation
Baserelation
定义了schema信息,相当于StructType
PrunedFilteredScan
用户可以做行过滤和列裁剪
select a,b,c from student where id>10;
类似的还有
TableScan
只能显示全部数据
select a,b,c,d,e from student;
PrunedScan
可以做列裁剪
select a,b,c from student;
InsertableRelation
可以写数据
实现这些的是需要用户自定义的buildScan函数。
模仿JDBCrelation
package sql04.text
import org.apache.spark.internal.Logging
import org.apache.spark.rdd.RDD
import org.apache.spark.sql.sources._
import org.apache.spark.sql.types.{LongType, StringType, StructField, StructType}
import org.apache.spark.sql.{DataFrame, Row, SQLContext, SaveMode}
//jdbcrelation
class TextDatasourceRelation(override val sqlContext: SQLContext,
path:String,
userSchema:StructType)
//日志
extends BaseRelation
with TableScan
with PrunedScan
with PrunedFilteredScan
with InsertableRelation
with Logging {
//可以用外面传进来StructType,也可以默认
override def schema: StructType = {
if (userSchema != null) {
userSchema
} else {
StructType(
//::和,皆可
StructField("id",LongType,false)::
StructField("name",StringType,false)::
StructField("gender",StringType,false)::
StructField("salary",LongType,false)::
StructField("comm",LongType,false)::Nil
)
}
}
override def buildScan(): RDD[Row] = {
logWarning("this is ruozedata custom buildScan().")
//wholeTextFiles,返回前面文件名后面内容
val rdd = sqlContext.sparkContext.wholeTextFiles(path).map(x=>x._2)
//返回的是schema的数组
val schemaFields = schema.fields
/*
把field作用到rdd上面去
如何根据schema的field的数据类型以及字段顺序整合到rdd
*/
val rows = rdd.map(fileContent => {
//注意fileContent是一段内容
val lines = fileContent.split("\n")
//转换成sequencefile
val data = lines.map(x => x.split(",").map(x=>x.trim).toSeq)
//按照字段field整合到rdd
//按照index压在一起
/**
* 10000 long
* ruoze string
*/
val typedValues = data.map(x => x.zipWithIndex.map {
case (value, index) => {
val colName = schemaFields(index).name
Utils.castTo(if (colName.equalsIgnoreCase("gender")) {
if (value == "0") {
"男"
} else if (value == "1") {
"女"
} else {
"未知"
}
} else {
value
}, schemaFields(index).dataType)
}
})
typedValues.map(x=>Row.fromSeq(x))
})
rows.flatMap(x=>x)
}
override def buildScan(requiredColumns: Array[String]): RDD[Row] = {
logWarning("this is ruozedata custom buildScan(requiredColumns).")
//wholeTextFiles,返回前面文件名后面内容
val rdd = sqlContext.sparkContext.wholeTextFiles(path).map(x=>x._2)
//返回的是schema的数组
val schemaFields = schema.fields
/*
把field作用到rdd上面去
如何根据schema的field的数据类型以及字段顺序整合到rdd
*/
val rows = rdd.map(fileContent => {
//注意fileContent是一段内容
val lines = fileContent.split("\n")
val data = lines.map(x => x.split(",").map(x=>x.trim).toSeq)
//按照字段field整合到rdd
//按照index压在一起
/**
* 10000 long
* ruoze string
*/
val typedValues = data.map(x => x.zipWithIndex.map {
case (value, index) => {
val colName = schemaFields(index).name
val castedValue = Utils.castTo(if (colName.equalsIgnoreCase("gender")) {
if (value == "0") {
"男"
} else if (value == "1") {
"女"
} else {
"未知"
}
} else {
value
}, schemaFields(index).dataType)
if (requiredColumns.contains(colName)){
Some(castedValue)
} else {
None
}
}
})
typedValues.map(x=>Row.fromSeq(x.filter(_.isDefined).map(x=>x.get)))
})
rows.flatMap(x=>x)
}
override def buildScan(requiredColumns: Array[String], filters: Array[Filter]): RDD[Row] = {
logWarning("this is ruozedata custom buildScan(requiredColumns,filters).")
logWarning("Fileter:")
filters.foreach(x => println(x.toString))
//wholeTextFiles,返回前面文件名后面内容
val rdd = sqlContext.sparkContext.wholeTextFiles(path).map(x=>x._2)
//返回的是schema的数组
val schemaFields = schema.fields
/*
把field作用到rdd上面去
如何根据schema的field的数据类型以及字段顺序整合到rdd
*/
val rows = rdd.map(fileContent => {
//注意fileContent是一段内容
val lines = fileContent.split("\n")
val data = lines.map(x => x.split(",").map(x=>x.trim).toSeq)
//按照字段field整合到rdd
//按照index压在一起
/**
* 10000 long
* ruoze string
*/
//见下图
val typedValues = data.map(x => x.zipWithIndex.map {
//第一列的索引都是0,第二列都是1.。。
case (value, index) => {
val colName = schemaFields(index).name
/**
* public boolean equalsIgnoreCase(String anotherString)将此 String 与另一个 String 进行比较,不考虑大小写。如果两个字符串的长度相等,并且两个字符串中的相应字符都相等(忽略大小写),则认为这两个字符串是相等的。
*/
//(value,dataType)
val castedValue = Utils.castTo(if (colName.equalsIgnoreCase("gender")) {
if (value == "0") {
"男"
} else if (value == "1") {
"女"
} else {
"未知"
}
} else {
value
}, schemaFields(index).dataType)
if (requiredColumns.contains(colName)){
Some(castedValue)
} else {
None
}
}
})
//这个row.fromseq可以通过sequencefile构建一个row
//isDefined,如果是some类型返回true
//x把some里的值取出来
val asd = typedValues.map(x=>Row.fromSeq(x))
println(asd)
typedValues.map(x=>Row.fromSeq(x.filter(_.isDefined).map(x=>x.get)))
})
rows.flatMap(x=>x)
}
override def insert(data: DataFrame, overwrite: Boolean): Unit = {
data.write
.mode(if (overwrite) SaveMode.Overwrite else SaveMode.Append)
.save(path)
}
}
filecontent的值
lines的值
data的值
zipwithindex
typedValues
typedvalues转换为row的值
代码里的Utils.castTo
功能是根据schema的数据类型对相应数据进行类型转换
package sql04.text
import org.apache.spark.sql.types.{DataType, LongType, StringType}
object Utils {
def castTo(value:String,dataType: DataType)={
dataType match {
// case LongType => value.toLong 一样
case _:LongType => value.toLong
case _:StringType => value
}
}
}
提交到spark执行
只要–jars即可实现插入式调用。
format里指定外部数据源文件夹在jar包的位置即可
val people = sparkSession.read.format("sql04.text").option("path"," ").load()
people.select("id","name").filter('id>2).filter('salary >2000).show()