mysql rdd_JdbcRDD连接MySQL

本文详细介绍了如何使用 Spark 的 JdbcRDD 连接并操作 MySQL 数据库,包括添加依赖、从 MySQL 读取数据、写入数据,以及处理数据库连接和性能优化,特别提到了分区策略和序列化问题的解决方法。
摘要由CSDN通过智能技术生成

(1)添加依赖

org.apache.spark

spark-core_2.11

2.1.1

mysql

mysql-connector-java

8.0.16

(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的构造函数参数如下:

classJdbcRDD[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= newSparkContext(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";否则会报错:“无法确定时区”。

以上就是JdbcRDD连接MySQL的全部内容。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值