Spring框架 底层架构核心概念解析-四万字你值得一看
Posted lingering fear
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring框架 底层架构核心概念解析-四万字你值得一看相关的知识,希望对你有一定的参考价值。
本篇文章将对Spring底层的一些概念做一些简单的分析 , 也是为了方便后续在阅读源码的时候更加的方便
BeanDefintion
BeanDefintion是一个接口 , 它表示一个Bean的定义 , BeanDefintion存在很多属性来描述一个Bean的特点 , Spring在扫描完需要注册的Bean之后会进行解析 , 而解析的数据就会存入到BeanDefintion
我们在定义Bean的时候可以分为两种方式 :
申明式
申明式定义一个Bean就比如我们用的@Bean , @Component等注解 , 或者是xml标签的形式来定义一个Bean
编程式
编程式就是通过写代码的方式来定义 , 如下
首先定义一个MenberService
public class MemberService
public void test()
System.out.println("test方法被调用...");
接着我们从Spring获取这个’Bean’ , 看是否能获取到
public class TestSpring
public static void main(String[] args)
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
MemberService memberService = (MemberService) applicationContext.getBean("memberService");
memberService.test();
运行之后我们回发现报错了 , 报错内容如下 :
org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named ‘memberService’ available
没有找到一个名为memberService的Bean来使用
我们再用编程式的方式定义
public static void main(String[] args)
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
// 得到一个BeanDefinition对象
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
// 设置Bean的类型
beanDefinition.setBeanClass(MemberService.class);
// 设置Bean的作用域
beanDefinition.setScope("prototype");
// 当然光这样申明还是不够的 , 我们需要调用一个函数来把这个beanDefinition注册到容器中 , 在注册的时候我们可以指定一个Bean的名称
applicationContext.registerBeanDefinition("memberService" , beanDefinition);
MemberService memberService = (MemberService) applicationContext.getBean("memberService");
memberService.test();
接着运行进行测试 , 会发现这次并没有报错 , 并且也打印出来了 “test方法被调用…”
其实不难理解 , 我们通过注解的方式去定义 , 当Spring扫描完解析的时候 , 他底层也会把解析的结果封装到BeanDefinition, 我们现在不通过注解申明的方式 , 而是直接编程式的把值封装到BeanDefinition中 , 然后注册到Spring容器 , 这样我们就可以使用了
BeanDefinitionReader
现在我们介绍几种BeanDefintion的几种读取器 , 虽然在工作中用的很少 , 但是 , 它也是Spring基础设施中比较重要的一部分
AnnotatedBeanDefinitionReader
可以直接把某个类转换为BeanDefinition,并且会解析该类上的注解,如下
public class TestSpring
public static void main(String[] args)
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
// 把某一个类转换为BeanDefinition之后 , 我们也需要将它注册到Spring容器中 , 所以需要传一个ApplicationContext对象
AnnotatedBeanDefinitionReader annotatedBeanDefinitionReader = new AnnotatedBeanDefinitionReader(applicationContext);
// 注册到Spring容器中
annotatedBeanDefinitionReader.register(MemberService.class);
MemberService memberService = (MemberService) applicationContext.getBean("memberService");
memberService.test();
运行测试发现控制台打印 " test方法被调用…" , 我们可以简单看一下register()方法
public void register(Class<?>... componentClasses)
for (Class<?> componentClass : componentClasses)
registerBean(componentClass);
public void registerBean(Class<?> beanClass)
doRegisterBean(beanClass, null, null, null, null);
private <T> void doRegisterBean(Class<T> beanClass, @Nullable String name,
@Nullable Class<? extends Annotation>[] qualifiers, @Nullable Supplier<T> supplier,
@Nullable BeanDefinitionCustomizer[] customizers)
AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(beanClass);
if (this.conditionEvaluator.shouldSkip(abd.getMetadata()))
return;
abd.setInstanceSupplier(supplier);
// 解析@Scope注解的结果为ScopeMetadata
ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(abd);
// 将类的作用域添加到数据结构中
abd.setScope(scopeMetadata.getScopeName());
String beanName = (name != null ? name : this.beanNameGenerator.generateBeanName(abd, this.registry));
AnnotationConfigUtils.processCommonDefinitionAnnotations(abd);
if (qualifiers != null)
for (Class<? extends Annotation> qualifier : qualifiers)
if (Primary.class == qualifier)
abd.setPrimary(true);
else if (Lazy.class == qualifier)
abd.setLazyInit(true);
else
abd.addQualifier(new AutowireCandidateQualifier(qualifier));
if (customizers != null)
for (BeanDefinitionCustomizer customizer : customizers)
customizer.customize(abd);
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(abd, beanName);
definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, this.registry);
我们看一下processCommonDefinitionAnnotations()方法
public static void processCommonDefinitionAnnotations(AnnotatedBeanDefinition abd)
processCommonDefinitionAnnotations(abd, abd.getMetadata());
// 处理@Lazy、@Primary、@DependsOn、@Role、@Description
static void processCommonDefinitionAnnotations(AnnotatedBeanDefinition abd, AnnotatedTypeMetadata metadata)
AnnotationAttributes lazy = attributesFor(metadata, Lazy.class);
if (lazy != null)
abd.setLazyInit(lazy.getBoolean("value"));
else if (abd.getMetadata() != metadata)
lazy = attributesFor(abd.getMetadata(), Lazy.class);
if (lazy != null)
abd.setLazyInit(lazy.getBoolean("value"));
if (metadata.isAnnotated(Primary.class.getName()))
abd.setPrimary(true);
AnnotationAttributes dependsOn = attributesFor(metadata, DependsOn.class);
if (dependsOn != null)
abd.setDependsOn(dependsOn.getStringArray("value"));
AnnotationAttributes role = attributesFor(metadata, Role.class);
if (role != null)
abd.setRole(role.getNumber("value").intValue());
AnnotationAttributes description = attributesFor(metadata, Description.class);
if (description != null)
abd.setDescription(description.getString("value"));
可以看到 , 他底层的逻辑代码和我们刚刚用编程式定义的方式差不多 , 也是创建一个BeanDefintion对象 , 然后解析注解并设置BeanDefintion的值 , 那么这个AnnotatedBeanDefinitionReader读取器在什么时候用的呢?其实是在创建AnnotationConfigApplicationContext容器对象时候用的 , 我们点进去看一下它的构造方法
public AnnotationConfigApplicationContext(Class<?>... componentClasses)
// 构造DefaultListableBeanFactory、AnnotatedBeanDefinitionReader、ClassPathBeanDefinitionScanner
this();
// 注册bean配置类
register(componentClasses);
// 刷新上下文
refresh();
然后看this()方法
public AnnotationConfigApplicationContext()
StartupStep createAnnotatedBeanDefReader = this.getApplicationStartup().start("spring.context.annotated-bean-reader.create");
// 额外会创建StandardEnvironment
this.reader = new AnnotatedBeanDefinitionReader(this);
createAnnotatedBeanDefReader.end();
this.scanner = new ClassPathBeanDefinitionScanner(this);
我们就可以看到它构造了一个AnnotatedBeanDefinitionReader对象 , 其实Spring容器本身也有register()方法
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
applicationContext.register();
其实它们使用的是同一个方法 , 看一看applicationContext.register()方法的源码
@Override
public void register(Class<?>... componentClasses)
Assert.notEmpty(componentClasses, "At least one component class must be specified");
StartupStep registerComponentClass = this.getApplicationStartup().start("spring.context.component-classes.register")
.tag("classes", () -> Arrays.toString(componentClasses));
this.reader.register(componentClasses);
registerComponentClass.end();
public void register(Class<?>... componentClasses)
for (Class<?> componentClass : componentClasses)
registerBean(componentClass);
public void registerBean(Class<?> beanClass)
doRegisterBean(beanClass, null, null, null, null);
可以看到, 它们最终调用的都是doRegisterBean()方法 , 都是会把传入的类转换为一个BeanDefintion
XmlBeanDefinitionReader
既然有解析类的读取器的 , 那么也就应该有解析xml标签的 , 它就是XmlBeanDefinitionReader , 如下
首先定义一个xml文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd"
>
<bean id="member" class="com.lyh.service.MemberService"/>
</beans>
然后我们通过如下的方式来获取
public class TestSpring
public static void main(String[] args)
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
XmlBeanDefinitionReader xmlBeanDefinitionReader = new XmlBeanDefinitionReader(applicationContext);
xmlBeanDefinitionReader.loadBeanDefinitions("spring.xml");
MemberService memberService = (MemberService) applicationContext.getBean("memberService");
memberService.test();
ClassPathBeanDefinitionScanner
前面介绍的两个是读取器 , 现在来介绍一个BeanDefinition扫描器 , 读取器 , 读取类以及xml文件然后解析成为一个BeanDefintion , 那么扫描器, 那么就是读取某个包路径下的类 , 然后进行解析 ,比如,扫描到的类上如果存在@Component 注解,那么就会把这个类解析为一个BeanDefinition , 如下
申明一个ScannerService来测试
public class ScannerService
public void test()
System.out.println("执行了ScannerService.test()方法");
取消AppConfig.class这个入参
public class TestSpring
public static void main(String[] args)
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
ScannerService scannerService = (ScannerService) applicationContext.getBean("scannerService");
scannerService.test();
这样运行肯定是报错的 , 即使在ScannerService类加上了@Component注解 , 因为这里没有传配置文件进去 , 所以构造的是一个空的Spring容器
报错信息如下:
Exception in thread “main” java.lang.IllegalStateException: org.springframework.context.annotation.AnnotationConfigApplicationContext@433c675d has not been refreshed yet
在这里插入代码片
它提示我们这个容器还没有刷新一下 , 加一行代码来刷新一下
public class TestSpring
public static void main(String[] args)
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
applicationContext.refresh();
ScannerService scannerService = (ScannerService) applicationContext.getBean("scannerService");
scannerService.test();
发现刷新之后还是报错 , 报错信息如下 :
Exception in thread “main” org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named ‘scannerService’ available
没有一个名为scannerService的Bean用来使用
我们现在用这个扫描器试一下
public class TestSpring
public static void main(String[] args)
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(applicationContext);
scanner.scan("com.lyh");
ScannerService scannerService = (ScannerService) applicationContext.getBean("scannerService");
scannerService.test();
发现控制台打印了 “执行了ScannerService.test()方法” , 也就是说这个扫描器生效了 , 它扫描到了ScannerService并且这个类加了@Component , 就表示这个类需要注册为一个Bean , 就会把这个类解析并放入Spring容器 , 其实比较细心的话就可以发现ClassPathBeanDefinitionScanner这个类是在之前申明AnnotatedBeanDefinitionReader读取器的时候也申明了
public AnnotationConfigApplicationContext()
StartupStep createAnnotatedBeanDefReader = this.getApplicationStartup().start("spring.context.annotated-bean-reader.create");
// 额外会创建StandardEnvironment
this.reader = new AnnotatedBeanDefinitionReader(this);
createAnnotatedBeanDefReader.end();
this.scanner = new ClassPathBeanDefinitionScanner(this);
所以这里我们就知道了 , 我们的Spring容器它既可以去注册某一个类成为Bean ,也可以去扫描 , 当然它的内部会进行扫描 , 我们自己触发也是可以的 , 所以AnnotationConfigApplicationContext 也是有这两个功能的 , 一个是直接注册 , 一个是扫描
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
// 需要传入一个包路径
applicationContext.scan();
BeanDefinition子类
上面我们说到 , BeanDefinition是一个接口 , 那么重要的实现类有这么三个 :
1.GenericBeanDefinition
2.AnnotatedGenericBeanDefinition
3.ScannedGenericBeanDefinition
它们之前的父子关系是这样的
AnnotatedGenericBeanDefinition和ScannedGenericBeanDefinition的区别
先来看ScannedGenericBeanDefinition , 它表示是扫描出来的BeanDefinition的类型
我们来看源码 , 从刚刚scan()的源码进去
scanner.scan(“com.lyh”);
public int scan(String... basePackages)
int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
doScan(basePackages);
// Register annotation config processors, if necessary.
if (this.includeAnnotationConfig)
AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
return (this.registry.getBeanDefinitionCount(Hive从入门到精通,HQL硬核整理四万字,全面总结,附详细解析,赶紧收藏吧!!
往期好文推荐:
🔶🔷Hadoop深入浅出 ——三大组件HDFS、MapReduce、Yarn框架结构的深入解析式地详细学习【建议收藏!!!】
🔶🔷Redis从青铜到王者,从环境搭建到熟练使用,看这一篇就够了,超全整理详细解析,赶紧收藏吧!!!
🔶🔷硬核整理四万字,学会数据库只要一篇就够了,盘它!MySQL基本操作以及常用的内置函数汇总整理
🔶🔷Redis主从复制 以及 集群搭建 详细步骤解析,赶快收藏练手吧!
🔶🔷Hadoop集群HDFS、YARN高可用HA详细配置步骤说明,附Zookeeper搭建详细步骤【建议收藏!!!】
🔶🔷SQL进阶-深入理解MySQL,JDBC连接MySQL实现增删改查,赶快收藏吧!
🔶🔷【小白学Java】D25 》》》Java中的各种集合大汇总,学习整理
💜🧡💛制作不易,各位大佬们给点鼓励!
🧡💛💚点赞👍 ➕ 收藏⭐ ➕ 关注✅
💛💚💙欢迎各位大佬指教,一键三连走起!
》》》本篇文章主要是与大家分享,Hive的一些常见操作,分区,分桶,窗口函数等等,以及Hive的HQL的使用练习,如有错误,烦请大佬指教。希望大家能够喜欢!
目录
🧡一、了解Hive
💜1、Hive的概念及架构
💜2、Hive与传统数据库比较
💜3、Hive的数据存储格式
💜4、Hive操作客户端
🧡二、Hive的基本语法
💜1、Hive建表语法
💜2、Hive加载数据
💜3、Hive 内部表(Managed tables)vs 外部表(External tables)
💜4、Hive 分区
💜5、Hive动态分区
💜6、Hive分桶
💜7、Hive连接JDBC
🧡三、Hive的数据类型
💜1、基本数据类型
💜2、日期类型
💜3、复杂数据类型
🧡四、Hive HQL使用语法
💜1、HQL语法-DDL
💜2、HQL语法-DML
🧡五、Hive HQL使用注意
🧡六、Hive 的函数使用
💜1、Hive-常用函数
💚(1)关系运算
💚(2)数值计算
💚(3) 条件函数
💚(4)日期函数
💚(5) 字符串函数
💜2、Hive-高级函数
💚(1)窗口函数(开窗函数):用户分组中开窗
💚(2)Hive 行转列
💚(3)Hive 列转行
💚(4)Hive自定义函数UserDefineFunction
⭕ UDF:一进一出
⭕UDTF:一进多出
⭕UDAF:多进一出
💜3、Hive 中的wordCount
🧡七、Hive 的Shell使用
一、了解Hive
1、Hive的概念及架构
Hive 是建立在 Hadoop 上的数据仓库
基础构架。它提供了一系列的工具,可以用来进行数据提取转化加载(ETL ),这是一种可以存储、查询和分析存储在 Hadoop 中的大规模数据的机制。Hive 定义了简单的类 SQL 查询语言,称为 HQL ,它允许熟悉 SQL 的用户查询数据。同时,这个语言也允许熟悉 MapReduce 的开发者开发自定义的 mapper 和 reducer 来处理内建的 mapper 和 reducer 无法完成的复杂的分析工作。Hive是SQL解析引擎,它将SQL语句转译成Map/Reduce Job然后在Hadoop执行。Hive的表其实就是HDFS的目录,按表名把文件夹分开。如果是分区表,则分区值是子文件夹,可以直接在Map/Reduce Job里使用这些数据。Hive相当于hadoop的客户端工具,部署时不一定放在集群管理节点中,也可以放在某个节点上。
数据仓库
,英文名称为Data Warehouse
,可简写为DW或DWH。数据仓库,是为企业所有级别的决策制定过程,提供所有类型数据支持的战略集合。它出于分析性报告和决策支持目的而创建。为需要业务智能的企业,提供指导业务流程改进、监视时间、成本、质量以及控制。
Hive的版本介绍:
0.13和.14版本,稳定版本,但是不支持更新删除操作。
1.2.1和1.2.2 版本,稳定版本,为Hive2版本(是主流版本)
1.2.1的程序只能连接hive1.2.1 的hiveserver2
2、Hive与传统数据库比较
查询语言 HiveQL SQL 数据存储位置 HDFS Raw Device or 本地FS 数据格式 用户定义 系统决定 数据更新 不支持(1.x以后版本支持) 支持 索引 新版本有,但弱 有 执行 MapReduce Executor 执行延迟 高 低 可扩展性 高 低 数据规模 大 小
- 查询语言。类 SQL 的查询语言 HQL。熟悉 SQL 开发的开发者可以很方便的使用 Hive 进行开发。
- 数据存储位置。所有 Hive 的数据都是存储在 HDFS 中的。而数据库则可以将数据保存在块设备或者本地文件系统中。
- 数据格式。Hive 中没有定义专门的数据格式。而在数据库中,所有数据都会按照一定的组织存储,因此,数据库加载数据的过程会比较耗时。
- 数据更新。Hive 对数据的改写和添加比较弱化,0.14版本之后支持,需要启动配置项。而数据库中的数据通常是需要经常进行修改的。
- 索引。Hive 在加载数据的过程中不会对数据进行任何处理。因此访问延迟较高。数据库可以有很高的效率,较低的延迟。由于数据的访问延迟较高,决定了 Hive 不适合在线数据查询。
- 执行计算。Hive 中执行是通过 MapReduce 来实现的而数据库通常有自己的执行引擎。
- 数据规模。由于 Hive 建立在集群上并可以利用 MapReduce 进行并行计算,因此可以支持很大规模的数据;对应的,数据库可以支持的数据规模较小。
3、Hive的数据存储格式
-
Hive的数据存储基于Hadoop HDFS。
-
Hive没有专门的数据文件格式,常见的有以下几种:TEXTFILE、SEQUENCEFILE、AVRO、RCFILE、ORCFILE、PARQUET。
下面我们详细的看一下Hive的常见数据格式:
-
TextFile:
TEXTFILE 即正常的文本格式,是Hive默认文件存储格式,因为大多数情况下源数据文件都是以text文件格式保存(便于查看验数和防止乱码)。此种格式的表文件在HDFS上是明文,可用hadoop fs -cat命令查看,从HDFS上get下来后也可以直接读取。
TEXTFILE 存储文件默认每一行就是一条记录,可以指定任意的分隔符进行字段间的分割。但这个格式无压缩,需要的存储空间很大。 虽然可以结合Gzip、Bzip2、Snappy等使用,使用这种方式,Hive不会对数据进行切分,从而无法对数据进行并行操作。一般只有与其他系统由数据交互的接口表采用TEXTFILE 格式,其他事实表和维度表都不建议使用。
-
RCFile:
Record Columnar的缩写。是Hadoop中第一个列文件格式。 能够很好的压缩和快速的查询性能。通常写操作比较慢,比非列形式的文件格式需要更多的内存空间和计算量。 RCFile是一种行列存储相结合的存储方式。 首先,其将数据按行分块,保证同一个record在一个块上,避免读一个记录需要读取多个block。其次,块数据列式存储,有利于数据压缩和快速的列存取。
-
ORCFile:
Hive从0.11版本开始提供了ORC的文件格式,ORC文件不仅仅是一种列式文件存储格式,最重要的是有着很高的压缩比,并且对于MapReduce来说是可切分(Split)的。因此,在Hive中使用ORC作为表的文件存储格式,不仅可以很大程度的节省HDFS存储资源,而且对数据的查询和处理性能有着非常大的提升,因为ORC较其他文件格式压缩比高,查询任务的输入数据量减少,使用的Task也就减少了。ORC能很大程度的节省存储和计算资源,但它在读写时候需要消耗额外的CPU资源来压缩和解压缩,当然这部分的CPU消耗是非常少的。
-
Parquet:
通常我们使用关系数据库存储结构化数据,而关系数据库中使用数据模型都是扁平式的,遇到诸如List、Map和自定义Struct的时候就需要用户在应用层解析。但是在大数据环境下,通常数据的来源是服务端的埋点数据
,很可能需要把程序中的某些对象内容作为输出的一部分,而每一个对象都可能是嵌套的,所以如果能够原生的支持这种数据,这样在查询的时候就不需要额外的解析便能获得想要的结果。Parquet的灵感来自于2010年Google发表的Dremel论文,文中介绍了一种支持嵌套结构的存储格式,并且使用了列式存储的方式提升查询性能。
Parquet仅仅是一种存储格式,它是语言、平台无关的,并且不需要和任何一种数据处理框架绑定。这也是parquet相较于orc的仅有优势:支持嵌套结构。Parquet 没有太多其他可圈可点的地方,比如他不支持update操作(数据写成后不可修改),不支持ACID等.
-
SEQUENCEFILE:
SequenceFile是Hadoop API 提供的一种二进制文件,它将数据以<key,value>的形式序列化到文件中。 这种二进制文件内部使用Hadoop 的标准的Writable 接口实现序列化和反序列化。它与Hadoop API中的MapFile 是互相兼容的。Hive 中的SequenceFile 继承自Hadoop API 的SequenceFile,不过它的key为空,使用value 存放实际的值, 这样是为了避免MR 在运行map 阶段的排序过程。 SequenceFile支持三种压缩选择:NONE, RECORD, BLOCK。 Record压缩率低,一般建议使用BLOCK压缩。 SequenceFile最重要的优点就是Hadoop原生支持较好,有API,但除此之外平平无奇,实际生产中不会使用。
-
AVRO:
Avro是一种用于支持数据密集型的二进制文件格式。它的文件格式更为紧凑,若要读取大量数据时,Avro能够提供更好的序列化和反序列化性能。并且Avro数据文件天生是带Schema定义的,所以它不需要开发者在API 级别实现自己的Writable对象。Avro提供的机制使动态语言可以方便地处理Avro数据。最近多个Hadoop 子项目都支持Avro 数据格式,如Pig 、Hive、Flume、Sqoop和Hcatalog。
其中的TextFile、RCFile、ORC、Parquet为Hive最常用的四大存储格式
它们的 存储效率及执行速度比较如下:
ORCFile存储文件读操作效率最高,耗时比较(ORC<Parquet<RCFile<TextFile)
ORCFile存储文件占用空间少,压缩效率高(ORC<Parquet<RCFile<TextFile)
4、Hive操作客户端
常用的客户端有两个:CLI,JDBC/ODBC
-
CLI,即Shell命令行
-
JDBC/ODBC 是 Hive 的Java,与使用传统数据库JDBC的方式类似。
-
Hive 将元数据存储在数据库中(metastore),目前只支持 mysql、derby。 Hive 中的元数据包括表的名字,表的列和分区及其属性,表的属性(是否为外部表等),表的数据所在目录等;由解释器、编译器、优化器完成 HQL 查询语句从词法分析、语法分析、编译、优化以及查询计划(plan)的生成。生成的查询计划存储在 HDFS 中,并在随后由 MapReduce 调用执行。
-
Hive 的数据存储在 HDFS 中,大部分的查询由 MapReduce 完成(包含 * 的查询,比如 select * from table 不会生成 MapRedcue 任务)
Hive的metastore
metastore是hive元数据的集中存放地。
metastore默认使用内嵌的derby数据库作为存储引擎
Derby引擎的缺点:一次只能打开一个会话
使用MySQL作为外置存储引擎,可以多用户同时访问`
元数据库详解见:查看mysql SDS表和TBLS表
连接地址:https://blog.csdn.net/haozhugogo/article/details/73274832
二、Hive的基本语法
1、Hive建表语法
CREATE [EXTERNAL] TABLE [IF NOT EXISTS] table_name
// 定义字段名,字段类型
[(col_name data_type [COMMENT col_comment], ...)]
// 给表加上注解
[COMMENT table_comment]
// 分区
[PARTITIONED BY (col_name data_type [COMMENT col_comment], ...)]
// 分桶
[CLUSTERED BY (col_name, col_name, ...)
// 设置排序字段 升序、降序
[SORTED BY (col_name [ASC|DESC], ...)] INTO num_buckets BUCKETS]
[
// 指定设置行、列分隔符
[ROW FORMAT row_format]
// 指定Hive储存格式:textFile、rcFile、SequenceFile 默认为:textFile
[STORED AS file_format]
| STORED BY 'storage.handler.class.name' [ WITH SERDEPROPERTIES (...) ] (Note: only available starting with 0.6.0)
]
// 指定储存位置
[LOCATION hdfs_path]
// 跟外部表配合使用,比如:映射HBase表,然后可以使用HQL对hbase数据进行查询,当然速度比较慢
[TBLPROPERTIES (property_name=property_value, ...)] (Note: only available starting with 0.6.0)
[AS select_statement] (Note: this feature is only available starting with 0.5.0.)
建表格式1:全部使用默认建表方式
create table students
(
id bigint,
name string,
age int,
gender string,
clazz string
)
ROW FORMAT DELIMITED FIELDS TERMINATED BY ',';
// 必选,指定列分隔符
建表格式2:指定location (这种方式也比较常用)
create table students2
(
id bigint,
name string,
age int,
gender string,
clazz string
)
ROW FORMAT DELIMITED FIELDS TERMINATED BY ','
LOCATION '/input1';
// 指定Hive表的数据的存储位置,一般在数据已经上传到HDFS,想要直接使用,会指定Location,
//通常Locaion会跟外部表一起使用,内部表一般使用默认的location
建表格式3:指定存储格式
create table students3
(
id bigint,
name string,
age int,
gender string,
clazz string
)
ROW FORMAT DELIMITED FIELDS TERMINATED BY ','
STORED AS rcfile;
// 指定储存格式为rcfile,inputFormat:RCFileInputFormat,outputFormat:RCFileOutputFormat,
//如果不指定,默认为textfile,
//注意:除textfile以外,其他的存储格式的数据都不能直接加载,需要使用从表加载的方式。
建表格式4:create table xxxx as select_statement(SQL语句) (这种方式比较常用)
注意:
- 新建表不允许是
外部表
。 - select后面表需要是已经存在的表,建表同时会加载数据。
- 会启动mapreduce任务去读取源表数据写入新表
create table students4 as select * from students2;
建表格式5:create table xxxx like table_name 只想建表,不需要加载数据
create table students5 like students;
2、Hive加载数据
1)、使用hdfs dfs -put '本地数据' 'hive表对应的HDFS目录下'
2)、使用 load data inpath
从hdfs导入数据,路径可以是目录,会将目录下所有文件导入,但是文件格式必须一致
// 将HDFS上的/input1目录下面的数据 移动至 students表对应的HDFS目录下
// 注意是 移动!移动!移动!
load data inpath '/input1/students.txt' into table students;
// 清空表
truncate table students;
从本地文件系统导入
// 加上 local 关键字 可以将Linux本地目录下的文件 上传到 hive表对应HDFS 目录下 原文件不会被删除
load data local inpath '/usr/local/soft/data/students.txt' into table students;
// overwrite 覆盖加载
load data local inpath '/usr/local/soft/data/students.txt' overwrite into table students;
3)、create table xxx as SQL语句,表对表加载
4)、insert into table xxxx SQL语句 (没有as),表对表加载:
// 将 students表的数据插入到students2
//这是复制 不是移动 students表中的表中的数据不会丢失
insert into table students2 select * from students;
// 覆盖插入 把into 换成 overwrite
insert overwrite table students2 select * from students;
注意:
1,如果建表语句没有指定存储路径,不管是外部表还是内部表,存储路径都是会默认在hive/warehouse/xx.db/表名的目录下。
加载的数据如果在HDFS上会移动到该表的存储目录下。注意是移动,不是复制
2,删除外部表,文件不会删除,对应目录也不会删除
3、Hive 内部表(Managed tables)vs 外部表(External tables)
外部表和普通表的区别
- 外部表的路径可以自定义,内部表的路径需要在 hive/warehouse/目录下
- 删除表后,普通表数据文件和表信息都删除。外部表仅删除表信息
1)、建表语句:
// 内部表
create table students_internal
(
id bigint,
name string,
age int,
gender string,
clazz string
)
ROW FORMAT DELIMITED FIELDS TERMINATED BY ','
LOCATION '/input2';
// 外部表
create external table students_external
(
id bigint,
name string,
age int,
gender string,
clazz string
)
ROW FORMAT DELIMITED FIELDS TERMINATED BY ','
LOCATION '/input3';
2)、加载数据:
hive> dfs -put /usr/local/soft/data/students.txt /input2/;
hive> dfs -put /usr/local/soft/data/students.txt /input3/;
3)、删除表:
hive> drop table students_internal;
Moved: 'hdfs://master:9000/input2' to trash at: hdfs://master:9000/user/root/.Trash/Current
OK
Time taken: 0.474 seconds
hive> drop table students_external;
OK
Time taken: 0.09 seconds
1、可以看出,删除内部表的时候,表中的数据(HDFS上的文件)会被同表的元数据一起删除;删除外部表的时候,只会删除表的元数据,而不会删除表中的数据(HDFS上的文件)
2、一般在公司中,使用外部表多一点,因为数据可以需要被多个程序使用,避免误删,通常外部表会结合location一起使用
3、外部表还可以将其他数据源中的数据 映射到 hive中,比如说:hbase,ElasticSearch…
4、设计外部表的初衷就是 让 表的元数据 与 数据 解耦
4、Hive 分区
分区表实际上是在表的目录下在以分区命名,建子目录;作用:进行分区裁剪,避免全表扫描,减少MapReduce处理的数据量,提高效率
一般在公司的hive中,所有的表基本上都是分区表,通常按日期分区、地域分区;分区表在使用的时候记得加上分区字段;分区也不是越多越好,一般不超过3级,根据实际业务衡量
分区的概念和分区表:
分区表指的是在创建表时指定分区空间,实际上就是在hdfs上表的目录下再创建子目录。
在使用数据时如果指定了需要访问的分区名称,则只会读取相应的分区,避免全表扫描,提高查询效率。
1)、建立分区表:
create external table students_pt1
(
id bigint,
name string,
age int,
gender string,
clazz string
)
PARTITIONED BY(pt string)
ROW FORMAT DELIMITED FIELDS TERMINATED BY ',';
2)、增加一个分区:
alter table students_pt1 add partition(pt='20210904');
3)、删除一个分区:
alter table students_pt drop partition(pt='20210904');
4)、查看某个表的所有分区
// 推荐这种方式(直接从元数据中获取分区信息)
show partitions students_pt;
// 不推荐
select distinct pt from students_pt;
5)、往分区中插入数据:
insert into table students_pt partition(pt='20210902') select * from students;
load data local inpath '/usr/local/soft/data/students.txt' into table students_pt partition(pt='20210902');
6)、查询某个分区的数据:
// 全表扫描,不推荐,效率低
select count(*) from students_pt;
// 使用where条件进行分区裁剪,避免了全表扫描,效率高
select count(*) from students_pt where pt='20210101';
// 也可以在where条件中使用非等值判断
select count(*) from students_pt where pt<='20210112' and pt>='20210110';
5、Hive动态分区
有的时候我们原始表中的数据里面包含了 ‘‘日期字段 dt’’,我们需要根据dt中不同的日期,分为不同的分区,将原始表改造成分区表。
hive默认不开启动态分区
动态分区
:根据数据中某几列的不同的取值 划分 不同的分区
# 表示开启动态分区
hive> set hive.exec.dynamic.partition=true;
# 表示动态分区模式:strict(需要配合静态分区一起使用)、nostrict
# strict: insert into table students_pt partition(dt='anhui',pt) select ......,pt from students;
hive> set hive.exec.dynamic.partition.mode=nostrict;
# 表示支持的最大的分区数量为1000,可以根据业务自己调整
hive> set hive.exec.max.dynamic.partitions.pernode=1000;
1)、建立原始表并加载数据
create table students_dt
(
id bigint,
name string,
age int,
gender string,
clazz string,
dt string
)
ROW FORMAT DELIMITED FIELDS TERMINATED BY ',';
2)、建立分区表并加载数据
create table students_dt_p
(
id bigint,
name string,
age int,
gender string,
clazz string
)
PARTITIONED BY(dt string)
ROW FORMAT DELIMITED FIELDS TERMINATED BY ',';
3)、使用动态分区插入数据
// 分区字段需要放在 select 的最后,如果有多个分区字段 同理,
//它是按位置匹配,不是按名字匹配
insert into table students_dt_p partition(dt) select id,name,age,gender,clazz,dt from students_dt;
// 比如下面这条语句会使用age作为分区字段,而不会使用student_dt中的dt作为分区字段
insert into table students_dt_p partition(dt) select id,name,age,gender,dt,age from students_dt;
4)、多级分区
create table students_year_month
(
id bigint,
name string,
age int,
gender string,
clazz string,
year string,
month string
)
ROW FORMAT DELIMITED FIELDS TERMINATED BY ',';
create table students_year_month_pt
(
id bigint,
name string,
age int,
gender string,
clazz string
)
PARTITIONED BY(year string,month string)
ROW FORMAT DELIMITED FIELDS TERMINATED BY ',';
insert into table students_year_month_pt partition(year,month) select id,name,age,gender,clazz,year,month from students_year_month;
6、Hive分桶
分桶实际上是对文件(数据)的进一步切分;Hive默认关闭分桶;分桶的作用:在往分桶表中插入数据的时候,会根据 clustered by 指定的字段 进行hash分组 对指定的buckets个数 进行取余,进而可以将数据分割成buckets个数个文件,以达到数据均匀分布,可以解决Map端的“数据倾斜”问题,方便我们取抽样数据,提高Map join效率;分桶字段 需要根据业务进行设定
1)、开启分桶开关
hive> set hive.enforce.bucketing=true;
2)、建立分桶表
create table students_buks
(
id bigint,
name string,
age int,
gender string,
clazz string
)
CLUSTERED BY (clazz) into 12 BUCKETS
ROW FORMAT DELIMITED FIELDS TERMINATED BY ',';
3)、往分桶表中插入数据
// 直接使用load data 并不能将数据打散
load data local inpath '/usr/local/soft/data/students.txt' into table students_buks;
// 需要使用下面这种方式插入数据,才能使分桶表真正发挥作用
insert into students_buks select * from students;
Hive关于分桶好文分享, Hive分桶表的使用场景以及优缺点分析:https://zhuanlan.zhihu.com/p/93728864
7、Hive连接JDBC
1)、启动hiveserver2的服务
hive --service hiveserver2 &
2)、 新建maven项目并添加两个依赖
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-common</artifactId>
<version>2.7.6</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.hive/hive-jdbc -->
<dependency>
<groupId>org.apache.hive</groupId>
<artifactId>hive-jdbc</artifactId>
<version>1.2.1</version>
</dependency>
3)、 编写JDBC代码
import java.sql.*;
public class HiveJDBC {
public static void main(String[] args) throws ClassNotFoundException, SQLException {
Class.forName("org.apache.hive.jdbc.HiveDriver");
Connection conn = DriverManager.getConnection("jdbc:hive2://master:10000/test3");
Statement stat = conn.createStatement();
ResultSet rs = stat.executeQuery("select * from students limit 10");
while (rs.next以上是关于Spring框架 底层架构核心概念解析-四万字你值得一看的主要内容,如果未能解决你的问题,请参考以下文章
Hive从入门到精通,HQL硬核整理四万字,全面总结,附详细解析,赶紧收藏吧!!
Spring IoC容器初始化源码—populateBeaninitializeBean填充Bean字段反射和setter方法依赖注入以及IoC容器初始化总结四万字