JdbcRDD连接MySQL

 

1)添加依赖

<dependencies>
        <dependency>
            <groupId>org.apache.spark</groupId>
            <artifactId>spark-core_2.11</artifactId>
            <version>2.1.1</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.16</version>
        </dependency>

    </dependencies>

2)Mysql读取

package com.atguigu

import java.sql.{DriverManager, SQLException}

import org.apache.spark.rdd.JdbcRDD
import org.apache.spark.{SparkConf, SparkContext}

object MysqlRDD {

  def main(args: Array[String]): Unit = {

    //1.创建spark配置信息
    val sparkConf: SparkConf = new SparkConf()
      .setMaster("local[*]").setAppName("JdbcRDD")

    //2.创建SparkContext
    val sc = new SparkContext(sparkConf)

    //3.定义连接mysql的参数
    val driver = "com.mysql.cj.jdbc.Driver"
    val url = "jdbc:mysql://localhost:3306/sensor?serverTimezone=UTC"
    val userName = "root"
    val passWd = "******"

    try {
      //创建JdbcRDD
      val rdd = new JdbcRDD(
        sc,
        () => {
          Class.forName(driver)
          DriverManager.getConnection(url, userName, passWd)
        },
        "select * from `user` where `userid` between ? and ?;",
        1,
        1,
        1,
        r => (r.getInt(1), r.getString(2))
      )

      //打印最后结果
      println(rdd.count())
      rdd.foreach(println)
    }catch{
      case ex:SQLException => println("sql exception occur!")
      case ex:Exception => println("other exception occur")
    }finally {
      sc.stop()
    }
  }
}

JdbcRDD的构造函数参数如下:

class JdbcRDD[T: ClassTag](
    sc: SparkContext,
    getConnection: () => Connection,
    sql: String,
    lowerBound: Long,//第一个占位符的值
    upperBound: Long,//第二个占位符的值
    numPartitions: Int,
    mapRow: (ResultSet) => T = JdbcRDD.resultSetToObjectArray _)//这个函数用于从resultset中获取特定的列,返回作为RDD分区中的元素
  extends RDD[T](sc, Nil) with Logging {

注意:一个select语句必须带两个占位符,第一个占位符的值为lowerbound,第二个占位符的值为upperbound。

那么:如果我指向查询一条记录呢,sql语句该怎么书写?

"select * from `user` where `userid` between ? and ?;"将lowerbound与upperbound设置为相同的值即可。

 

msyql写入

def main(args: Array[String]) {
    val sparkConf = new SparkConf().setMaster("local[2]").setAppName("HBaseApp")
    val sc = new SparkContext(sparkConf)
    val data = sc.parallelize(List((1,"javok"),(2,"holuwo")))

    try {
      data.foreachPartition(insertData)//对每个分区,调用一次insertData函数,将分区中的元素插入数据库
    }catch{
      case ex:SQLException => println("sql exception occur!")
      case ex:Exception => println("other exception occur")
    }finally {
      sc.stop()
    }
  }

  def insertData(iterator: Iterator[(Int,String)]): Unit = {
    Class.forName ("com.mysql.cj.jdbc.Driver").newInstance()
    val conn = java.sql.DriverManager
      .getConnection("jdbc:mysql://localhost:3306/sensor?serverTimezone=UTC", "root", "******")
    iterator.foreach(data => {
      val ps = conn.prepareStatement("insert into rddtable('id','name') values (?,?)")
      ps.setInt(1,data._1)
      ps.setString(2, data._2)
      ps.executeUpdate()
      conn.close()
    })
  }

注意:这里采用了foreachPartition算子而不是foreach算子,分析如下:

如果有100条数据需要插入,它们分布在4个分区,如果采用foreach算子,就需要调用insertdata方法100次,创建和释放数据库连接100次;而如果采用foreachPartition算子,创建和释放连接只需要4次 。

所以,对于数据库连接这种敏感性强的资源,使用foreachPartittion算子具有重大意义

 

 

之前一直错误地认为foreach方法是在driver端执行的,因此传入insertdata方法不会导致序列化问题。后来又发现foreach执行在executor端,那么问题又来了:这里要传递一个insertdata方法给各个executor端执行,为啥没有出现像《RDD(九)——序列化问题》中的问题。

原因在于,insertdata方法是定义在一个object中的。下面的代码演示了,将该方法定义在另一个object中,以对象名引用该方法:

object InsertDataUtil {

  def insertData(iterator: Iterator[(Int,String)]) = {
    Class.forName("com.mysql.cj.jdbc.Driver").newInstance()
    val conn = java.sql.DriverManager
      .getConnection("jdbc:mysql://localhost:3306/sensor?serverTimezone=UTC", "root", "613692")
    iterator.foreach(data => {
      val ps = conn.prepareStatement("INSERT INTO rddtable VALUES (?,?)")
      ps.setInt(1, data._1)
      ps.setString(2, data._2)
      ps.executeUpdate()
      conn.close()
      1;
    })
  }
}

... data.foreachPartition(InsertDataUtil.insertData)

 

并没有出现序列化问题。原因在于object的引用方式类似于一个静态类引用静态方法,没有对象被创建,因此不会导致序列化问题。

如果将其定义在一个class里,会导致同样的序列化问题。

 

遇到的坑:MySQL数据库版本与MySQL驱动版本的不兼容导致的问题

我的MySQL数据库版本是8.0以上,如果采用5.0的驱动,会抛出异常:java.sql.SQLException: Unable to load authentication plugin 'caching_sha2_password'.更新到8.0的驱动即可。

同时,8.0版本的驱动名称变为:com.mysql.cj.jdbc.Driver;否则会报错:“无法找到driver”;url也要加上时区信息:  url = "jdbc:mysql://localhost:3306/sensor?serverTimezone=UTC";否则会报错:“无法确定时区”。

 

posted @ 2020-02-27 13:07  盛夏群岛  阅读(548)  评论(0)    收藏  举报