开发过程中代码会随着开发进度进行变更,解决bug和进行优化、功能新增等
最新的代码可以再Gitee中获取:SparkStreamingDemand: 完整的sparkStreaming项目开发代码记录
1 PropertiesUtil
用来加载配置数据资源包
1.1 PropertiesUitl编写
package SparkStreamingProject.util
import java.io.InputStreamReader
import java.util.Properties
object PropertiesUtil {
def load(propertiesName: String): Properties = {
val prop = new Properties()
prop.load(
new InputStreamReader(
Thread.currentThread().getContextClassLoader.getResourceAsStream(propertiesName), "UTF-8"
)
)
prop
}
}
1.2生成资源包的方法
1.3 使用工具类加载资源包的方式
不带路径直接填文件名(在JDBCUtil类中有使用)
val config: Properties = PropertiesUtil.load("config.properties")
1.4 加载config.properties时出现空指针异常
1.4.1 加载资源包代码:
Thread.currentThread().getContextClassLoader().getResourceAsStream("config.properties")
1.4.2 出现的报错:
Exception in thread "main" java.lang.NullPointerException
原因:config.properties需要放在资源目录下,且如果是maven项目,资源包的路径其实是
target/classes/config.properties
工程目录src/main/resources下的文件一般在编译后是会自动复制到target/classes目录下的,这时候需要检查一下资源包路径和target/classes路径下是否有config.properties
1.4.3 解决办法
1.4.3.1 step1 在maven的pom依赖中增加以下配置
<resources>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
</resources>
1.4.3.2 step2 在src下建立resource资源包文件夹,并标记为资源根目录
(增加了上面的pom依赖后这一步就不用做,可以检查一下是否标记成功,如果未设置成功就手动设置下)
1.4.3 step3 资源包不要带任何路径,因为已经设置好资源根路径
1.4.4 idea中生成properties文件的方式
2 JDBCUtil
获取mysql返回值,使用方式见本文第四章节Dao层对kafka和mysql的操作
package SparkStreamingProject.util
import com.alibaba.druid.pool.DruidDataSourceFactory
import java.sql.Connection
import java.util.Properties
object jdbcUtil {
def mysqlConnect(): Connection = {
val properties = new Properties()
val config: Properties = PropertiesUtil.load("config.properties")
properties.setProperty("driverClassName", "com.mysql.jdbc.Driver")
properties.setProperty("url", config.getProperty("jdbc.url"))
properties.setProperty("username", config.getProperty("jdbc.user"))
properties.setProperty("password", config.getProperty("jdbc.password"))
properties.setProperty("maxActive",
config.getProperty("jdbc.datasource.size")
)
//获取 MySQL 连接
DruidDataSourceFactory.createDataSource(properties).getConnection
}
}
3 KafkaUtil
createKafkaProducer()获取kafkaProductor--生产者
getKafkaPara() --获取消费者的kafka链接
使用方式见第四章节Dao层对kafka和mysql的操作
package SparkStreamingProject.util
import org.apache.kafka.clients.producer.{KafkaProducer, ProducerConfig}
import org.apache.kafka.common.serialization.StringDeserializer
import java.util.Properties
object KafkaUtil {
a
def getKafkaPara(topicName: String): Map[String, Object] = {
val config: Properties = PropertiesUtil.load("config.properties")
val broker_list: String = config.getProperty("kafka.broker.list")
val kafkaPara = Map(
"bootstrap.servers" -> broker_list,
"key.deserializer" -> classOf[StringDeserializer],
"value.deserializer" -> classOf[StringDeserializer],
//消费者组
"group.id" -> topicName,
//如果没有初始化偏移量或者当前的偏移量不存在任何服务器上,可以使用这个配置属性
//可以使用这个配置,latest 自动重置偏移量为最新的偏移量
"auto.offset.reset" -> "latest",
//如果是 true,则这个消费者的偏移量会在后台自动提交,但是 kafka 宕机容易丢失数据
//如果是 false,会需要手动维护 kafka 偏移量
"enable.auto.commit" -> (true: java.lang.Boolean)
)
kafkaPara
}
//获取kafkaProductor
def createKafkaProducer(): KafkaProducer[String, Any] = {
val config: Properties = PropertiesUtil.load("config.properties")
val prop = new Properties()
// 添加配置
prop.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, config.getProperty("kafka.broker.list"))
prop.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer")
prop.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer")
// 根据配置创建 Kafka 生产者
new KafkaProducer[String, Any](prop)
}
}
4 更新Dao层对kafka和mysql的操作
4.1 kafka
4.1.1 获取指定主题的kafka数据流,返回字符数据流--消费者
def getKafkaData(topicName:String): InputDStream[ConsumerRecord[String, String]] = {
//定义 Kafka 参数
val kafkaPara: Map[String, Object] = getKafkaPara(topicName)
val inputDStream: InputDStream[ConsumerRecord[String, String]] =
KafkaUtils.createDirectStream[String, String](
sscGet(),
//ssc,
LocationStrategies.PreferConsistent,
ConsumerStrategies.Subscribe[String, String](Set(topicName), kafkaPara)
)
inputDStream
}
4.1.2 生产数据到kafka --生产者
def setDataToKafka(topicName:String,data:Any): Unit = {
val record: ProducerRecord[String, Any] = new ProducerRecord[String, Any](topicName, data)
val producer: KafkaProducer[String, Any] = createKafkaProducer()
producer.send(record)
}
4.2 mysql
4.2.1 执行 SQL 语句,单条数据插入和更新
返回布尔值成功与否
//执行 SQL 语句,单条数据插入和更新,返回布尔值成功与否
/*
* 举例 insert into mytable(?,?)
* 数据:array(1,2)
* 插入一条
* */
def executeOneData(mysqlConn:Connection,sql:String,data:Array[Any],debugFlag:Boolean=false): Int = {
var resultCnt=0
try {
//设置不自动 Commit参数
mysqlConn.setAutoCommit(false)
//预编译SQL语句,设置参数
val statement: PreparedStatement = mysqlConn.prepareStatement(sql)
//设置第一个参数
if (data != null && !data.isEmpty) {
for (i <- data.indices) {
statement.setObject(i + 1, data(i))
}
}
statement.addBatch()
if (debugFlag) {
print(s"\n$sql: => (${data.mkString(",")})")
}
//批量执行,返回每个执行的条数
val counts = statement.executeBatch()
//提交刚才的更新
mysqlConn.commit
statement.close()
resultCnt=counts.sum
}catch {
case e: Exception => e.printStackTrace()
}
resultCnt
}
4.2.2 执行 SQL 语句,批量数据插入
第三个参数为debug开关,打开后会打印
//执行 SQL 语句,批量数据插入,第三个参数为debug开关,打开后会打印
/*举例 insert into mytable(?,?)
数据:Array(Array(1,0),Array(2,1))
插入两条
* 举例 update table
* set col1=?
* where col2=?
* 数据:Array(Array(1,0),Array(2,1));
* 更新col1=1的数据的col2=0
* 更新col1=2的数据的col2=1
* */
def executeBatch(mysqlConn:Connection,sql: String, dataList:Iterator[Array[Any]] ,debugFlag:Boolean=false): Int = {
var resultCnt=0
try{
//设置不自动 Commit参数
mysqlConn.setAutoCommit(false)
//预编译SQL语句,设置参数
val statement: PreparedStatement = mysqlConn.prepareStatement(sql)
//设置第一个参数
for(data<-dataList) {
if (data != null && !data.isEmpty) {
//debug//
if (debugFlag) {
data.mkString(",")
print(s"\n$sql: => (${data.mkString(",")})")
}
for (i <- data.indices) {
statement.setObject(i+1, data(i))
}
statement.addBatch()
}
}
if (debugFlag){
println()
}
//批量执行,返回每个执行的条数//批量执行,返回执行的总条数
val counts: Array[Int] = statement.executeBatch()
//提交刚才的更新
mysqlConn.commit
statement.close()
if (dataList.length!=0) {
resultCnt=counts.sum/dataList.length
}else {
resultCnt=counts.sum
}
statement.close()
} catch
{
case e: Exception => e.printStackTrace()
}
//debug//
if(debugFlag) {
println(s"插入条数:$resultCnt")
}
resultCnt
}
4.2.3 判断一条数据是否存在
def dataIsExist(sql: String, data:Array[T]): Boolean = {
var result = false
try {
//设置不自动 Commit参数
mysqlConn.setAutoCommit(false)
//预编译SQL语句,设置参数
val statement: PreparedStatement = mysqlConn.prepareStatement(sql)
if (data != null && !data.isEmpty) {
for (i <- data.indices) {
statement.setObject(i + 1, data(i))
}
}
result = statement.executeQuery().next()
statement.close()
} catch {
case e: Exception => e.printStackTrace()
}
result
}
4.2.4 查询数据操作返回条数
def dataCnt(sql: String, data: Array[T]): Long = {
var resultCnt:Long=0
try {
//设置不自动 Commit参数
mysqlConn.setAutoCommit(false)
//预编译SQL语句,设置参数
val staatement: PreparedStatement = mysqlConn.prepareStatement(sql)
if (data != null && !data.isEmpty) {
for (i <- data.indices) {
statement.setObject(i + 1, data(i))
}
}
val result: ResultSet = statement.executeQuery()
while (result.next()) {
resultCnt = result.getLong(1)
}
} catch {
case e: Exception => e.printStackTrace()
}
resultCnt
}
4.2.5 获取 MySQL 数据
传入limit参数,返回limit条数据,数据形式为二维数组,如果limit=-1则返回所有的数据
//获取 MySQL 数据,传入limit参数,返回limit条数据,数据形式为二维数组,如果limit=-1则返回所有的数据
def getData(mysqlConn:Connection,sql: String, data: Array[Any],limit:Int=1,debugFlag:Boolean=false): Array[Array[Any]] = {
var resultList: List[Array[Any]] = List()
if (debugFlag){
println(s"查询sql=$sql\t[${data.mkString(",")}]\tlimit ${limit}")
}
try {
//设置不自动 Commit参数
mysqlConn.setAutoCommit(false)
//预编译SQL语句,设置参数
val statement: PreparedStatement = mysqlConn.prepareStatement(sql)
if (data != null && !data.isEmpty) {
for (i <- data.indices) {
statement.setObject(i + 1, data(i))
}
}
val result: ResultSet = statement.executeQuery()
if (limit == -1){
while (result.next()) {
var list: Array[Any] =Array.empty
for (i <- 1 to result.getMetaData().getColumnCount) {
list :+= result.getObject(result.getMetaData().getColumnName(i)).toString
}
resultList=resultList :+ list
}
}else {
while (result.next()) {
for (j <- 1 to limit) {
var list: Array[Any] =Array.empty
for (i <- 1 to result.getMetaData().getColumnCount) {
list :+= result.getObject(result.getMetaData().getColumnName(i)).toString
}
resultList=resultList :+ list
}
}
}
if (debugFlag) {
val result = resultList.map { arr =>
if (arr.length == 1) arr.mkString
else arr.mkString(s"查询的数据:[", ", ", "]")
}.mkString("\n")
println(result)
}
result.close()
statement.close()
} catch {
case e: Exception => e.printStackTrace()
}
resultList.toArray
}