title: How to iterate through a spark dataset and update a column value in Java?
body: <p>I'm working on POC where I have to update the account number in DB with tokens. I read the data into a dataset dsRecords(about 2M records). I have another routine that had captured distinct account numbers and got the tokens, the mapping is stored in HashMap. </p>
  
  <pre><code>Dataset&lt;Row&gt; applySwappedTokens(Dataset&lt;Row&gt; dsRecords, Map&lt;String, String&gt; mappedTokens){
  }
  </code></pre>
  
  <p>Now, I must iterate through the dataset to do the following -
  1. Read account number column (accountNumber) value and update (I know dataset is immutable. So, updating the dataset means creating copy of dataset with updated rows) it with the token value from mappedTokens. This can be achieved through JOIN or other operations but I am not spending effort on this because of 2nd task.
  2. Read another XML blob column and find the account number and update it.</p>
  
  <p>All the options I have tried so far are resulting into compile-time error or test compilation error due to non-serializable code. Most online resources are in Scala and not Java. Please help.</p>
  
  <p>Spark 2.1
  Java 8</p>
  
  <p><strong>Approach1 - Couldn't test due to serialization error.</strong></p>
  
  <pre><code>Dataset&lt;Row&gt; output = sparkSession.sqlContext().createDataFrame(dsRecords.javaRDD().map(row -&gt;  {
              return RowFactory.create(row.get(0), row.get(1), row.get(2), swapToken(row.get(3)),row.get(4));
          }), dsRecords.schema());
  
          return output;
  
  String swapToken(Object inputToken) {
          return mappedTokens.get(inputToken);//mappedToken will have to be instance field.
      }
  </code></pre>
  
  <p><strong>Approach2- Incomplete.</strong></p>
  
  <pre><code>dsRecords.foreach((ForeachFunction&lt;Row&gt;) row -&gt; {
              Integer index = row.fieldIndex("accountNumber");
              String pan = row.getString(index);
              String swap = this.swapToken(pan);
              //TODO: create a dataset with rows from dsRecords but swap value.
  
          });
  </code></pre>
  
  <p><strong>Approach 3 - Use UDF with map function</strong></p>
  
  <p>Create a UDF2 (that takes 2 input parameters viz. accountNumber and mappedToken and returns token). It seems UDF can only take column values</p>
  
  <p><strong>update 1 - UDF</strong>
  So, I implemented UDF (AFK, will post the code later):
  1. Defined UDF1 ‘updateToken’ to pass the xml column value and return the updated xml value. 
  2. The HashMap instance ‘mappedTokens’ that has the account-token pair mapping is made static. It is accessed inside my UDF function to find the account in xml string and update with the token. </p>
  
  <p>I could test my applySwappedTokens function which calls the above UDF on dataset ‘withColumn’.  However when I run the spark program, I see that ‘mappedToken’ has ‘null’ data and hence, xml column gets updated with empty data. I think the static ‘mappedTokens’ is either in another JVM or driver (even in local the spark creates isolated driver, executor). It’s frustrating that there is no simple solution to iterate and update rows in spark.</p>
  
  <pre><code>Dataset&lt;Row&gt; processByRow(Dataset&lt;Row&gt; dsRecords, SparkSession sparkSession) {
          sparkSession.udf().register("updateToken", updateToken, DataTypes.StringType);          
          return ds = dsRecords.withColumn("eventRecordTokenText", callUDF("updateToken", dsRecords.col("eventRecordTokenText")));
              }
  
  static UDF1 updateToken = new UDF1&lt;String, String&gt;() {
          public String call(final String tokenText) throws Exception {
                  // xml operations here..
                  for (int nodeIndex = 0; nodeIndex &lt; nList.getLength(); nodeIndex++) {
                      Node thisNode = nList.item(nodeIndex);
                      if (thisNode.getAttributes().getNamedItem("ProcessTokenValue") != null &amp;&amp; thisNode.getAttributes()
                              .getNamedItem("ProcessTokenValue").getNodeValue().equalsIgnoreCase("true")) {
                          Node valueNode = thisNode.getAttributes().getNamedItem("Value");
                          String thisToken = valueNode.getNodeValue();
                          String newToken = mappedTokens.get(thisToken); // *returns null values from the map*
                          if(newToken != null &amp;&amp; !newToken.isEmpty())
                          valueNode.setNodeValue(newToken);
                      }
                  }
                  // more xml operations here..
                  return output;
          }
      };
  </code></pre>
  
  <p><strong>update 2 - iterate &amp; update</strong>
  Now, I am trying row by row traversing ..</p>
  
  <pre><code>Dataset&lt;Row&gt; processByRow1(Dataset&lt;Row&gt; dsRecords, SparkSession sparkSession) {
          List&lt;MongoRecordSmall&gt; newRows = new ArrayList&lt;MongoRecordSmall&gt;();
              dsRecords.foreach((ForeachFunction&lt;Row&gt;) record -&gt; {
              String currentToken = record.getAs(AppConstants.TokenCol);
              String newToken = mappedTokens.get(currentToken);
              newRows.add(new MongoRecordSmall(record.getString(0), record.getString(1), newToken, record.getString(3)));
              logger.error(“Size plus=“+newRows.size());
          });
         return sparkSession.createDataFrame(newRows, MongoRecordSmall.class);
      }
  </code></pre>
  
  <p>This is throwing serliazation error. It seems (<a href="https://databricks.gitbooks.io/databricks-spark-knowledge-base/content/troubleshooting/javaionotserializableexception.html" rel="nofollow noreferrer">https://databricks.gitbooks.io/databricks-spark-knowledge-base/content/troubleshooting/javaionotserializableexception.html</a>) my class where the above logic exists, is being serlalized and sent to worker nodes and failing in doing so.</p>