Spark系列05,SparkSQL概念及相关操作

1. SparkSQL简介

	SparkSQL,可以简单的理解为Spark生态体系中用于处理结构化数据的模块。

1.1. 特点

  • 可集成

  • 统一的访问数据方式

  • 集成Hive操作

  • 提供标准的jdbc/odbc的数据库连接方式

1.2. 参考网址

https://www.cnblogs.com/BYRans/p/5057110.html

官网:http://spark.apache.org/sql

1.3. 发展

在spark生态体系中,最早并不叫sparksql,最早叫shark,shark底层的任务的解析器、优化器、执行器等等都是基于hive的,所以说hive的发展制约了shark。shark的负责人意识到了这个问题之后,大概在2015年左右终止了shark的开发,从新开始搭建一套框架sparksql,同时在hive的基础之上发展出了另一套hive集成spark的框架hive-on-spark(https://cwiki.apache.org/confluence/display/Hive/Hive+on+Spark%3A+Getting+Started)
虽然shark的源代码被废弃了,但是一些非常优秀的设计思想还是被保留下来了,比如基于内存的列存储,动态字节码技术等等。

1.4. DataFrame and Datasets and RDD

类似于sparkCore中的RDD,在sparksql中的编程模型被称为之DataFrame和Datasets。DataFrame是spark1.6以前的唯一的编程模型,理解为一张二维表,或者更加简单的理解为RDD+列;Dataset是1.6之后出现的新的编程模型,集成RDD和DataFrame的优点(RDD强类型推断,强大的lambda表达式进行函数式编程;DataFrame的二维表结构,以及传统的sparksql的优化器),逐渐成为当前sparksql编程的主流。

有时候我们把RDD称之为Spark的第一代编程模型,官方预计在Spark3.0之后不再维护RDD;DataFrame被称之为Spark的第二代编程模型;Dataset被称为Spark的第三代编程模型。

这三者的比较,参见Spark-Dataset.docx

2. SparkSession的创建和初始化

2.1. SparkSQL项目的创建

在这里插入图片描述
选择maven的archetype骨架

在这里插入图片描述

指定artifactId

添加maven骨架加载方式,基于local,也就是本地仓库

![1561532661932]在这里插入图片描述

模块存储路径

在这里插入图片描述

删除选中的几个类

在这里插入图片描述

添加maven依赖操作:

在父模块中加入如下依赖:

<dependency>
    <groupId>org.apache.spark</groupId>
    <artifactId>spark-sql_2.11</artifactId>
    <version>${spark.version}</version>
</dependency>
<dependency>
    <groupId>org.apache.spark</groupId>
    <artifactId>spark-hive_2.11</artifactId>
    <version>${spark.version}</version>
</dependency>

然后在spark-sql的模块设置为如下依赖:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <parent>
        <artifactId>spark-parent</artifactId>
        <groupId>com.desheng.bigdata</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.desheng.bigdata</groupId>
    <artifactId>spark-sql</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>org.scala-lang</groupId>
            <artifactId>scala-library</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.spark</groupId>
            <artifactId>spark-sql_2.11</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.spark</groupId>
            <artifactId>spark-core_2.11</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.spark</groupId>
            <artifactId>spark-hive_2.11</artifactId>
        </dependency>
    </dependencies>

    <build>
        <sourceDirectory>src/main/scala</sourceDirectory>
        <testSourceDirectory>src/test/scala</testSourceDirectory>
        <plugins>
            <plugin>
                <groupId>org.scala-tools</groupId>
                <artifactId>maven-scala-plugin</artifactId>
                <executions>
                    <execution>
                        <goals>
                            <goal>compile</goal>
                            <goal>testCompile</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <scalaVersion>${scala.version}</scalaVersion>
                    <args>
                        <arg>-target:jvm-1.5</arg>
                    </args>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-eclipse-plugin</artifactId>
                <configuration>
                    <downloadSources>true</downloadSources>
                    <buildcommands>
                        <buildcommand>ch.epfl.lamp.sdt.core.scalabuilder</buildcommand>
                    </buildcommands>
                    <additionalProjectnatures>
                        <projectnature>ch.epfl.lamp.sdt.core.scalanature</projectnature>
                    </additionalProjectnatures>
                    <classpathContainers>
                        <classpathContainer>org.eclipse.jdt.launching.JRE_CONTAINER</classpathContainer>
                        <classpathContainer>ch.epfl.lamp.sdt.launching.SCALA_CONTAINER</classpathContainer>
                    </classpathContainers>
                </configuration>
            </plugin>
        </plugins>
    </build>
    <reporting>
        <plugins>
            <plugin>
                <groupId>org.scala-tools</groupId>
                <artifactId>maven-scala-plugin</artifactId>
                <configuration>
                    <scalaVersion>${scala.version}</scalaVersion>
                </configuration>
            </plugin>
        </plugins>
    </reporting>
</project>

2.2. SparkSession及其入门案例操作

在老版本中的SparkSQL的编程入口称之为SQLContext(通用)/HiveContext(只能操作Hive),在spark2.0以后对这两个Context做了统一,这个统一就是今天学习SparkSession。SparkSession的构建依赖SparkConf,我们可以基于SparkSession来获得SparkContext,或者SQLContext或者HiveContext。

通用的SQLContext支持通用的SQL操作,但是Hive中的一些特殊方言,SQLContext无法支持,比如row_number开窗函数,此时用方言的就是用HiveContext。

val spark = SparkSession.builder().build()

object _01SparkSQLFirstExperienceOps {
    def main(args: Array[String]): Unit = {
        Logger.getLogger("org.apache.spark").setLevel(Level.WARN)
        Logger.getLogger("org.apache.hadoop").setLevel(Level.WARN)
        Logger.getLogger("org.spark_project").setLevel(Level.WARN)
        val spark:SparkSession = SparkSession.builder()
                    .appName("SparkSQLFirstExperience")
                    .master("local[2]")
                    .getOrCreate()

        //加载外部数据
        val people:DataFrame = spark.read.json("file:///E:/data/spark/sql/people.json")

        //元数据信息 show create table people;
        println("=================查看该二维表的元数据信息=====================")
        people.printSchema()
        println("=================查看该二维表的数据=====================")
//        people.foreach(row => println(row))
        people.show(8)//show()打印dataframe中的前20条记录,show(num)
        //查询某一列或者某几列的字段数据select name, age from xxx
        println("=================查看该二维表某几列的数据=====================")
        people.select("name", "age").show()
        people.select(new Column("height")).show()
        println("=================简单的查询操作=====================")
        //比如对所有人的年龄+1 select name, age+10 as age from xxx
        people.select(new Column("name"), new Column("age").+(10).as("age")).show()
        /*
            比如查询身高超过168的信息
                gt(great than) >
                lt(low than) <
                gte(great or equal than) >=
                lte(low or equal than) <=
                eq (equal) =
                neq (not equal) !=
                select name, age, height > 168 from xxx
         */
        people.select(new Column("name"), new Column("age"), new Column("height").>(168).as("great_than_168")).show()
        // select name, age, height from xxx where height > 168
        people.select(new Column("name"), new Column("age"), new Column("height"))
//                .where(new Column("height").gt(168))
                .where("height > 168")
            .show()
        //查询姓刘的同志 select * from xxx where name like '刘%'、'刘_'
        people.select("name", "age", "height").where("name like '刘%'").show()
        //按照年龄进行分组,计算出不同年龄的人数 select age, count(1) from xxx group by age
        people.select(new Column("age")).groupBy("age").count().show()
        println("=====================SQL操作方式========================")
        //注册一张临时表
        people.createOrReplaceTempView("people")
        val sql =
            """
              |select
              | age,
              | count(*) countz
              |from people
              |where height < 178.8
              |group by age
            """.stripMargin
        spark.sql(sql).show()
        spark.stop()
    }
}

3. SparkSQL的编程模型的创建

? SparkSQL中的编程模型主要有DataFrame和DataSet。

3.1. DataFrame的构建

? 构建的方式分为了两类操作,一类是基于JavaBean的反射操作来构建;另一类是动态编程的方式构建。这两种构建方式主要区别就在元数据的识别方式.

3.1.1. 基于反射的方式构建DataFrame

import scala.collection.JavaConversions._
//DataFrame的构建方式
object _01SparkSQLDataFrameOps {
    def main(args: Array[String]): Unit = {
        val conf = new SparkConf()
            .setMaster("local[*]")
            .set("spark.serializer", "org.apache.spark.serializer.KryoSerializer")
            .registerKryoClasses(Array(classOf[Person]))
            .setAppName("SparkSQLDataFrame")
        val spark = SparkSession.builder()
                    .config(conf)
                    .getOrCreate()
        val scalaList = List(
            new Person("赵阳", 19, 178.8),
            new Person("孙望", 20, 180.0),
            new Person("刘梦男", 21, 165.5),
            new Person("马静", 18, 168.5)
        )

//        reflection2DataFrame1(scalaList, spark)
        reflection2DataFrame2(scalaList, spark)

        spark.stop()
    }

    def reflection2DataFrame2(scalaList:List[Person], spark:SparkSession): Unit = {
        val rdd = spark.sparkContext.parallelize(scalaList)

        val pdf = spark.createDataFrame(rdd, classOf[Person])

        pdf.printSchema()

        pdf.show()
    }

    def reflection2DataFrame1(scalaList:List[Person], spark:SparkSession): Unit = {
        /*
          两种方式,进行scala和java的普通集合之间的互相转换
              第一种,引入隐式转换import JavaConversions._
              第二种,直接调用相关api
                  eg.JavaConversions.seqAsJavaList(scalaList)
       */
        //创建DataFrame
        //        val list = JavaConversions.seqAsJavaList(scalaList)

        val pdf = spark.createDataFrame(scalaList, classOf[Person])

        pdf.printSchema()

        pdf.show()
    }

3.1.2. 基于动态编程的方式构建DataFrame

? 动态编程,主要使用到了两个新的API,一个Row,一个StructType,Row代表的就是二维表中的一行记录,StructType代表的是这张二维表的元数据,主要包含,列名、类型、是否为空。

    def dynamic2DataFrame2(scalaList:List[Person], spark:SparkSession): Unit = {

        val rowRDD = spark.sparkContext.parallelize(scalaList).map(person => {
            Row(person.getName, person.getAge, person.getHeight)
        })
        val schema = StructType(List(
            StructField("name", DataTypes.StringType, false),
            StructField("age", DataTypes.IntegerType, false),
            StructField("height", DataTypes.DoubleType, true)
        ))
        val pdf = spark.createDataFrame(rowRDD, schema)

        pdf.printSchema()

        pdf.show()
    }

    def dynamic2DataFrame1(scalaList:List[Person], spark:SparkSession): Unit = {
        val rowList:List[Row] = scalaList.map(person => {
            Row(person.getName, person.getAge, person.getHeight)
        })

        val schema = StructType(List(
            StructField("name", DataTypes.StringType, false),
            StructField("age", DataTypes.IntegerType, false),
            StructField("height", DataTypes.DoubleType, true)
        ))
        val pdf = spark.createDataFrame(rowList, schema)

        pdf.printSchema()

        pdf.show()
    }
    def reflection2DataFrame2(scalaList:List[Person], spark:SparkSession): Unit = {
        val rdd = spark.sparkContext.parallelize(scalaList)

        val pdf = spark.createDataFrame(rdd, classOf[Person])

        pdf.printSchema()

        pdf.show()
    }

总结:

? 在开发过程中使用这两都可以,但是动态编程的方式要比反射的方式更加的灵活,所以建议使用动态编程。

3.2. DataSet的构建

? 使用sparksession.createDataset()构建,示例如下:

val scalaList = List(
    new Person("赵阳", 19, 178.8),
    new Person("孙望", 20, 180.0),
    new Person("刘梦男", 21, 165.5),
    new Person("马静", 18, 168.5)
)
val pds:Dataset[Person] = spark.createDataset(scalaList)

? 但是编译没有通过,报错如下:构建Dataset需要一个encoder来进行类型的推断,但是目前仅支持两种类型:primitive type(Int, String, Float等等基本数据类型) 和 case class,其它类型暂时不支持。同时使用上述两种类型的时候,还必须要引入sparksession中的隐式转换,import spark.implicits._,二者缺一不可

在这里插入图片描述

object _02SparkSQLDataSetOps {
    def main(args: Array[String]): Unit = {
        val conf = new SparkConf()
            .setMaster("local[*]")
            .setAppName("SparkSQLDataSet")
        val spark = SparkSession.builder()
            .config(conf)
            .getOrCreate()

        val scalaList = List(
            new Person("赵阳", 19, 178.8),
            new Person("孙望", 20, 180.0),
            new Person("刘梦男", 21, 165.5),
            new Person("马静", 18, 168.5)
        )
        import spark.implicits._
        val stuList = scalaList.map(person => {
            Student(person.getName, person.getAge, person.getHeight)
        })
        val pds:Dataset[Student] = spark.createDataset(stuList)
        pds.printSchema()
        
        pds.show()
        spark.stop()
    }
}

case class Student(name:String, age:Int, height:Double)

3.3. DataFrame、DataSet以及RDD之间的相互转换

/**
  * RDD DataFrame Dataset三者之间的互相转化
  *     基于上述的操作,我们可以非常灵活的进行SparkCore和SparkSQL之间的编程切换
  */
object _03ApiConvertOps {
    def main(args: Array[String]): Unit = {
        val conf = new SparkConf()
            .setMaster("local[*]")
            .setAppName("ApiConvertOps")
        val spark = SparkSession.builder()
            .config(conf)
            .getOrCreate()

        val scalaList = List(
            Student("赵阳", 19, 178.8),
            Student("孙望", 20, 180.0),
            Student("刘梦男", 21, 165.5),
            Student("马静", 18, 168.5)
        )
        import spark.implicits._
        val stuRDD = spark.sparkContext.parallelize(scalaList)
        /*
            rdd --> dataframe
            rdd转化为dataframe,一种方式就是通过动态编程或者反射,
            还有一种更为简单的方式进行转换,使用rdd.toDF,但前提是需要引入spark.implicits._
         */
        val sdf = stuRDD.toDF("aaa", "bbb", "ccc")
        sdf.show()
        /*
            rdd --> dataset
            一种方式就是通过spark.createDataset(rdd)
            还有一种更为简单的方式进行转换,使用rdd.toDS,但前提是需要引入spark.implicits._
            另外一点需要说明的是rdd中的类型需要是case class
         */
        val sds = stuRDD.toDS()
        sds.show()
        //dataframe --->rdd ==> dataframe.rdd | dataframe.toJavaRDD
        val sdf2RDD = sdf.rdd
        sdf2RDD.foreach(row => {
            //获取Row 一行中的信息 --->ResultSet
            val name = row.getAs[String]("aaa")
            val age = row.getInt(1)//row的索引是0开始
            val height = row.getAs[Double]("ccc")//建议使用getAs(fieldName)
            println(s"name:$name\tage:$age\theight:$height")
        })

        //dataframe --->dataset(没有办法直接转换)
        //dataset ---> rdd ==> dataset.rdd | dataset.javaRDD
        sds.rdd.foreach(println)
        //dataset ---> dataframe
          sds.toDF("bbb", "aha", "heh").show()

        spark.stop()
    }
}

4. SparkSQL的各种操作

4.1. 创建临时表操作

        val scalaList = List(
            Student("蔡金廷", 48, 148.8),
            Student("范腾文", 19, 180.0),
            Student("陈燕瑾", 32, 155.5),
            Student("曹煜", 19, 170.5)
        )
        import spark.implicits._
        val sds:Dataset[Student] = spark.createDataset(scalaList)

        /**
          * 如果一个dataframe或者dataset要想我们进行sql的操作,
          *     必须需要将该dataframe或者dataset注册成为一张临时表
          *
          * createTempView
          * createOrReplaceTempView
          * createGlobalTempView
          * createOrReplaceGlobalTempView
          *     是否有replace
          *         如果内存中已经存在一张表名相同的表,再去create,会报错
          *     是否有global
          *         主要就在于当前的表的生命周期周期,
          *       没有这global指的是只在当前sparksession中有效,
          *       如果有global说明是全局的,可以在当前Application中有效
          *   建议使用createOrReplaceTempView
          */
        sds.createTempView("student")
//        sds.createTempView("student")//TempTableAlreadyExistsException
        sds.createOrReplaceGlobalTempView("student")

4.2. sparksql的缓存操作

见如下示意图:

在这里插入图片描述

? 和这个内存列存储相关的两个配置参数:

在这里插入图片描述

5. SparkSQL数据的加载和落地

5.1. 数据加载

? sparksql中的数据的加载的方式spark.read.load,这都是已经不建议是的了,我们直接使用具体的文件格式的加载方式即可。

/**
  * SparkSQL加载数据的各种方式
  * sparksql.read.load方式加载的默认文件格式parquet
  *     parquet是twitter公司为自己的海量数据的存储设计的一种列式文件存储格式,已经贡献给apache基金会成为顶级项目
  *     如果要想加载其他的文件格式,只需要指定一个format即可
  */
object _05SparkSQLReadOps {
    def main(args: Array[String]): Unit = {
        val spark = SparkSession.builder()
                    .master("local[*]")
                    .appName("SparkSQLRead")
                    .getOrCreate()

        var stuDF = spark.read.format("csv")
            .load("file:///E:/data/spark/sql/student.csv")
            //toDF 重新构建列名
            .toDF("id", "name", "age", "gender", "course", "score")

        stuDF.show()

//        stuDF = spark.read.csv("file:///E:/data/spark/sql/student.csv")
//        stuDF.show()
        spark.read.json("file:///E:/data/spark/sql/people.json").show()
        spark.read.parquet("file:///E:/data/spark/sql/users.parquet").show()
        spark.read.text("file:///E:/data/spark/sql/dailykey.txt").show()
        spark.read.orc("file:///E:/data/spark/sql/student.orc").show()

        spark.stop()
    }
}

5.2. 数据落地

? sparksql中的数据的落地的方式spark.write.save,这都是已经不建议是的了,我们直接使用具体的文件格式的加载方式即可。

/**
  * SparkSQL落地数据的各种方式
  *     spark.write.save(path)
  *         如果像一个已经存在的路径中执行保存操作,会报错AnalysisException: already exists.;
  *         默认的存储文件格式和读取的默认的文件格式一致,都是parquet。
  *
  *     sparksql中的文件保存的模式SaveMode
  *         ErrorIfExists:存在即报错,默认的存储格式
  *         Append:在原有的基础之上追加新的数据
  *         Overwrite:覆盖(删除原有,重新创建)
  *         Ignore:忽略(如果有,不执行相关操作,如果没有就执行save操作)
  *
  *
    CREATE TABLE `student` (
      `id` INT,
      `name` VARCHAR(20),
      `age` INT,
      `gender` TINYINT,
      `course` VARCHAR(20),
      `score` FLOAT(3, 1)
    ) ENGINE=INNODB DEFAULT CHARSET=utf8

  */
object _06SparkSQLSaveOps {
    def main(args: Array[String]): Unit = {
        val spark = SparkSession.builder()
                    .master("local[*]")
                    .appName("SparkSQLSave")
                    .getOrCreate()

        var stuDF = spark.read.format("csv")
            .load("file:///E:/data/spark/sql/student.csv")
            //toDF 重新构建列名
            .toDF("id", "name", "age", "gender", "course", "score")
        import spark.implicits._
        val retDF = stuDF.map(row => {
            val id = row.getAs[String]("id").toInt
            val name = row.getAs[String]("name")
            val age = row.getAs[String]("age").toInt
            val gender = row.getAs[String]("gender").toShort
            val course = row.getAs[String]("course")
            val score = row.getAs[String]("score").toFloat
            BigDataStudent(id, name, age, gender, course, score)
        })

        retDF.printSchema()
        //保存数据
//        stuDF.write.mode(SaveMode.Ignore)//指定存储模式
//            .save("file:///E:/data/out/spark-sql/stu")
//        stuDF.write.orc("file:///E:/data/out/spark-sql/stu-orc")
        val url = "jdbc:mysql:///test?useUnicode=true&characterEncoding=UTF-8"
        val table = "student"
        val properties = new Properties()
        properties.put("user", "bigdata")
        properties.put("password", "sorry")
        retDF.write.mode(SaveMode.Append).jdbc(url, table, properties)
        spark.stop()
    }
}

case class BigDataStudent(
       id:Int,
       name:String,
       age:Int,
       gender:Short,
       course:String,
       score:Float)

6. SparkSQL和Hive的整合

? 这也是sparksql最常见的一种处理方式,也就是加载hive表中的数据,进行业务处理,最后将结果存储到hive或者其他地方。

6.1. 需求:

? 两张基础表数据:teacher_basic和teacher_info,

? 数据结构:

? teacher_basic

列名解释
name老师姓名
age老师年龄
married是否已婚
courses正在带的科目数量

? teacher_info

列名类型解释
namestring老师姓名
heightdouble身高

? 要求,基于上述两张表,进行hive的hql操作,首先在hive相应数据库中构建相关的表,并加载数据,其次进行关联操作,查询老师的所有信息,将结果存储到teacher表中。

create table teacher as 
select
    b.name,
    b.age,
    b.married,
    b.courses,
    i.height
from teacher_basic b
join teacher_info i on b.name = i.name

? 现在需要使用sparksql的方式进行处理。

6.2. 业务实现

/**
  * SparkSql和hive的整合操作
  *
  * teacher_basic -->name老师姓名 age老师年龄 married是否已婚 courses正在带的科目数量
  * teacher_info --->namestring老师姓名 heightdouble身高
  *
  * teacher
  *
  * 注意:SparkSession.sql操作每次只能执行一条sql语句,不可以执行多条sql操作,因为只有一个返回值DataFrame。
  */
object _01SparkSqlIntergationWithHiveOps {
    def main(args: Array[String]): Unit = {
        if(args == null || args.length <2) {
            println(
                """
                  |Parameter Errors! Usage: <teacher_basic> <teacher_info>
                """.stripMargin)
            System.exit(-1)
        }
        val Array(basicPath, infoPath) = args
        val spark = SparkSession.builder()
                    .appName("SparkSqlIntergationWithHive")
//                    .master("local[*]")
                    .enableHiveSupport()//支持hive中特定的方言操作
                    .getOrCreate()

        //首先创建数据库
        var sql =
            """
              |create database if not exists `test_1901`
            """.stripMargin
        spark.sql(sql)

        //创建teacher_basic
        sql =
            """
              |create table if not exists `test_1901`.`teacher_basic` (
              |   name string,
              |   age int,
              |   married boolean,
              |   courses int
              |) row format delimited
              |fields terminated by ','
            """.stripMargin
        spark.sql(sql)
        //创建teacher_info
        sql =
            """
              |create table if not exists `test_1901`.`teacher_info` (
              |   name string,
              |   height double
              |) row format delimited
              |fields terminated by ','
            """.stripMargin
        spark.sql(sql)
        //加载数据teacher_basic.txt和teacher_info.txt
        val loadBasic = s"load data inpath '${basicPath}' into table `test_1901`.`teacher_basic`"
        spark.sql(loadBasic)
        val loadInfo = s"load data inpath '${infoPath}' into table `test_1901`.`teacher_info`"
        spark.sql(loadInfo)

        //执行关联查询
        val joinSQL =
            """
              |select
              |   b.name,
              |   b.age,
              |   b.married,
              |   b.courses,
              |   i.height
              |from `test_1901`.`teacher_basic` b
              |join `test_1901`.`teacher_info` i on b.name = i.name
              |where b.courses > 0
            """.stripMargin
        val teacher = spark.sql(joinSQL)
        //存储结果
        teacher.write.saveAsTable("`test_1901`.`teacher`")

        spark.stop()
    }
}

6.3. 整合注意的地方

  1. 需要将hive的配置文件hive-site.xml添加进行spark程序的classpath中

    通常我们将hive-site.xml放入$SPARK_HOME/conf

  2. 需要将mysql的驱动程序也要添加进spark的classpath中

    通常将mysql.jar放入$SPARK_HOME/jars中,或者将jar打到spark程序中

  3. 为了保险起见,大家可以将hive-site.xml打入程序的classpath中,放入resources目录下即可。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值