Spark SQL Java读取含压缩二进制数据的表时出现编译报错求助
toString编译错误 嘿,我碰到过类似的问题,这个报错本质上是Spark的Bean Encoder在生成序列化代码时出了问题,具体来说是它找不到某个类的toString()方法。咱们一步步来拆解和解决:
错误原因拆解
报错里的关键信息是:
Exception in thread "main" org.apache.spark.SparkException: Job aborted due to stage failure: Task 0 in stage 2.0 failed 1 times, most recent failure: Lost task 0.0 in stage 2.0 (TID 2, localhost, executor driver): java.util.concurrent.ExecutionException: java.lang.Exception: failed to compile: org.codehaus.commons.compiler.CompileException: File 'generated.java', Line 36, Column 31: A method named "toString" is not declared in any enclosing class nor any supertype, nor through a static import
这说明Spark在自动生成序列化相关的Java代码时,尝试调用某个类的toString(),但这个方法不存在。大概率是这两个地方出问题:
- 你的
ConfigObject或ConfigObjectResult实体类没有显式重写toString()方法(虽然Object类默认有,但Spark的Bean Encoder有时会依赖显式实现) - 解压后的
unCompressedDoc字段是自定义类型,这个类型既没实现toString(),也不是Spark能识别的标准序列化类型
具体修复方案
1. 给实体类添上toString()方法
先检查ConfigObject和ConfigObjectResult,给它们都加上toString()实现,哪怕只是简单的字段拼接。比如:
public class ConfigObject implements Serializable { // 你的字段、无参构造、getter/setter @Override public String toString() { return "ConfigObject{" + "configID=" + getConfigID() + ", agentVersion='" + getAgentVersion() + '\'' + ", compressedDoc=" + Arrays.toString(getCompressedDoc()) + ", creationDate=" + getCreationDate() + '}'; } } public class ConfigObjectResult implements Serializable { // 同样要有无参构造、getter/setter @Override public String toString() { return "ConfigObjectResult{" + "configID=" + getConfigID() + ", agentVersion='" + getAgentVersion() + '\'' + ", unCompressedDoc=" + getUncompressedDoc() + ", creationDate=" + getCreationDate() + '}'; } }
Spark的Bean Encoder在生成代码时经常会用到这个方法,显式实现能避免很多奇怪的编译错误。
2. 检查unCompressedDoc的类型
如果Compressor.unCompressData()返回的是自定义对象,那得确保这个对象是标准JavaBean(有getter/setter、无参构造),并且也实现了toString()。如果可以的话,尽量把它转成Spark原生支持的类型,比如:
- 如果解压后是JSON字符串,直接转成
String - 如果是二进制数据,用
byte[]存储 - 如果是结构化数据,拆成多个字段存在DataFrame里,而不是用自定义对象
3. 绕过Bean Encoder,直接操作Row
如果不想折腾Bean Encoder的问题,你可以直接从DataFrame的Row中提取字段处理,这样就避开了自动生成代码的环节:
Dataset<Row> df = spark.read().format("jdbc").options(options).load().limit(10); JavaRDD<ConfigObjectResult> configObjsResult = df.javaRDD().map(row -> { ConfigObjectResult confObjRes = new ConfigObjectResult(); // 根据你的字段类型调整get方法,比如getLong、getString等 confObjRes.setConfigID(row.getLong(row.fieldIndex("configID"))); confObjRes.setAgentVersion(row.getString(row.fieldIndex("agentVersion"))); byte[] compressedBytes = row.getBytes(row.fieldIndex("compressedDoc")); confObjRes.setUncompressedDoc(Compressor.getInstance().unCompressData(compressedBytes)); confObjRes.setCreationDate(row.getTimestamp(row.fieldIndex("creationDate"))); return confObjRes; });
这种方式更直接,也不容易触发Encoder的奇怪问题。
4. 确保实体类实现Serializable
别忘了,Spark在分布式环境下需要序列化对象,所以ConfigObject和ConfigObjectResult都要实现Serializable接口,这点我已经在上面的代码示例里加上了。
最后总结
这个问题的核心就是Spark的Bean Encoder生成代码时找不到toString()方法,最快速的修复就是给实体类加上这个方法,同时检查字段类型是否符合Spark的要求。如果还是不行,直接操作Row是个很稳妥的替代方案。
内容的提问来源于stack exchange,提问作者Suresh Kumar

