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

浙公网安备 33010602011771号