问题产生背景:
在编写Spark任务时采用Spark SQL向Oracle存数据,对RDD与DateFrame进行了去空值(如下但不限于以下几种)处理后仍然会有ORA-01400: 无法将 NULL 插入 ,百思不得其解。
最后想到Spark框架采用Scala语言编写,虽然与Java一样都是JVM语言,但在语言类型上还是不同之处。
XXRDD.filter(xx.isEmpty)
XXRDD.filter(xx != null)
spark.sql("xxx IS NOT NULL AND xxx != ''")
XXXDateFrame.na.drop
Java的内存机制将内存分为两种:堆内存与栈内存,用通俗语言来描述:堆主要用来存放对象的,栈主要是用来执行程序
JVM是基于堆栈的虚拟机。JVM为每个新创建的线程都分配一个堆栈.也就是说,对于一个Java程序来说,它的运行就是通过对堆栈的操作来完成的。堆栈以帧为单位保存线程的状态。JVM对堆栈只进行两种操作:以帧为单位的压栈和出栈操作。
某个线程正在执行的方法称为此线程的当前方法。当前方法使用的帧称为当前帧。当线程激活一个Java方法,JVM就会在线程的 Java堆栈里新压入一个帧。这个帧自然成为了当前帧。在此方法执行期间,这个帧将用来保存参数、局部变量、中间计算过程和其他数据。这个帧在这里和编译原理中的活动纪录的概念是差不多的。
从Java的这种分配机制来看。堆栈又可以这样理解:堆栈(Stack)是操作系统在建立某个进程时或者线程(在支持多线程的操作系统中是线程)为这个线程建立的存储区域,该区域具有先进后出的特性。
每一个Java应用都唯一对应一个JVM实例,每一个实例唯一对应一个堆。应用程序在运行中所创建的所有类实例或数组都放在这个堆中,并由应用所有的线程共享。跟C/C++不同,Java中分配堆内存是自动初始化的。Java中所有对象的存储空间都是在堆中分配的,但是这个对象的引用却是在堆栈中分配,也就是说在建立一个对象时从两个地方都分配内存,在堆中分配的内存实际建立这个对象,而在堆栈中分配的内存只是一个指向这个堆对象的指针(引用)而已。
Java把内存划分成两种:一种是栈内存,一种是堆内存。
在函数中定义的一些基本类型的变量和对象的引用变量都在函数的栈内存中分配。
当在一段代码块定义一个变量时,Java就在栈中为这个变量分配内存空间,当超过变量的作用域后,Java会自动释放掉为该变量所分配的内存空间,该内存空间可以立即被另作他用。
堆内存用来存放由new创建的对象和数组。
在堆中分配的内存,由JVM的自动垃圾回收器(GC)来管理。
在堆中产生了一个数组或对象后,还可以在栈中定义一个特殊的变量,让栈中这个变量的取值等于数组或对象在堆内存中的首地址,栈中的这个变量就成了数组或对象的引用变量。
引用变量就相当于是为数组或对象起的一个名称,以后就可以在程序中使用栈中的引用变量来访问堆中的数组或对象。
栈与堆都是Java用来在内存中存放数据的地方。与C++不同,Java自动管理栈和堆,程序员不能直接地设置栈或堆。
Java的堆是一个运行时数据区,类的(对象从中分配空间。这些对象通过new、newarray、anewarray和multianewarray等指令建立,它们不需要程序代码来显式的释放。堆是由垃圾回收来负责的,堆的优势是可以动态地分配内存大小,生存期也不必事先告诉编译器,因为它是在运行时动态分配内存的,Java的垃圾收集器会自动收走这些不再使用的数据。但缺点是,由于要在运行时动态分配内存,存取速度较慢。
栈的优势是,存取速度比堆要快,仅次于寄存器,栈数据可以共享。但缺点是,存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。栈中主要存放一些基本类型的变量(,int, short, long, byte, float, double, boolean, char)和对象句柄。
栈有一个很重要的特殊性,就是存在栈中的数据可以共享。
摘自:https://www.cnblogs.com/whgw/archive/2011/09/29/2194997.html
JAVA的JVM的内存可分为3个区:堆(heap)、栈(stack)和方法区(method)
1. 栈区:
- 每个线程包含一个栈区,栈中只保存基础数据类型和自定义对象的引用(不是对象),对象都存放在堆区中。
- 每个栈中的数据(原始类型和对象引用)都是私有的,其他栈不能访问。
2. 堆区:
- 存储的全部是对象实例,每个对象都包含一个与之对应的class的信息(class信息存放在方法区)。
- jvm只有一个堆区(heap)被所有线程共享,堆中不存放基本类型和对象引用,只存放对象本身,几乎所有的对象实例和数组都在堆中分配。
3. 方法区:
又叫静态区,跟堆一样,被所有的线程共享。它用于存储已经被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
摘自:https://www.zhihu.com/question/29833675/answer/20726196
理解:可以认为null是指,程序在栈内存中存在该变量,但其不指向的堆内存或指向的堆内存找不到相应的地址
Scala的有即Any
Scala的无是Null、null、Nil、Nothing、None、Unit
1.Null 和 null
Null 是Scala的一个特征(Trait),我们不能创建他的实例,但从Scala语言层面上来说,null是Null的唯一对象。如果你写了一个带有Null作为参数的对象,那么你传入的参数只能是null,或者指向Null的引用。但无法获取null的类。
scala> val a = null
a: Null = null
scala> classOf[Null]
res3: Class[Null] = class scala.runtime.Null$
scala> a.getClass
java.lang.NullPointerException
... 29 elided
2.Nothing
Nothing也是一个特征,它继承自Any,而Any是整个Scala类型系统的根。Nothing是没有实例,但它时任何对象的子类,他是List的子类,是String的子类,是Int的子类,是任何用户自定义类型的子类。
3.Nil
Nil是一个空List 是一个继承List[Nothing]的对象。
4.None
写Java程序的时候,经常会碰到没有有意义的东西可以返回,我们返回null。但返回null有一些问题,调用方必须检查返回值,不然会有NullPointerException的异常。这逼迫我们去check函数的返回值。还有一种解决办法是使用异常,但增加try/catch块,并不是明智的选择。
Scala内置一种解决办法。如果你想返回一个String,但可能有的时候得不到有意义的返回值,我们可以让函数返回Option[String]。
scala> val b = None
b: None.type = None
scala> b.getClass
res5: Class[_ <: None.type] = class scala.None$
scala> val map1 = Map("key1" -> "value1")
map1: scala.collection.immutable.Map[String,String] = Map(key1 -> value1)
scala> val value1 = map1.get("key1")
value1: Option[String] = Some(value1)
scala> val value2 = map1.get("key2")
value2: Option[String] = None
5.Unit
Unit跟java的void一样,表示函数没有返回值。
使用Scala的Option类型
Scala鼓励在变量和函数返回值可能不会引用任何值的时候使用Option类型。在没有值的时候,使用None,这是Option的一个子类。如果有值可以引用,就使用Some来包含这个值。Some也是Option的子类。
None被声明为一个对象,而不是一个类,因为我们只需要它的一个实例。这样,它多少有点像null关键字,但它却是一个实实在在的,有方法的对象。
1、进行null和None的判断
有上述分析可见,往Oracle存数据报:ORA-01400: 无法将 NULL 插入 的原因是:
我在从scala map(scala.collection.immutable.Map[])中获取数据时,若有值,返回的是“Some(value)”;若没有相应的值,返回的是“Option[String] = None”。而没有null。
null只在栈内存中有数据。而None在堆内存和栈内存中都存在数据,Scala在转换成Java通过JDBC存储Oracle时None会转化成null,从而导致问题
因此,只去除null是没有用的,需要对返回的值进行None判断
2.将容错性交给框架处理
若上诉解决办法还是没有解决你的问题,可以将Spark将数据存到HBase,再将Hbase数据通过Sqoop导入Oracle中,将容错性交给框架。