diff --git a/core/lib/Drupal/Core/Field/EntityReferenceFieldItemList.php b/core/lib/Drupal/Core/Field/EntityReferenceFieldItemList.php
index 0a53d9894a..1403d8eb63 100644
--- a/core/lib/Drupal/Core/Field/EntityReferenceFieldItemList.php
+++ b/core/lib/Drupal/Core/Field/EntityReferenceFieldItemList.php
@@ -128,4 +128,21 @@ public function defaultValuesFormSubmit(array $element, array &$form, FormStateI
     return $default_value;
   }
 
+  /**
+   * Removes the items with a specific target ID.
+   *
+   * @param int $target_id
+   *   The target id of the item to be removed.
+   *
+   * @return $this
+   *
+   * @todo Add this to EntityReferenceFieldItemListInterface in Drupal 9.0.x.
+   *   https://www.drupal.org/node/2785673
+   */
+  public function removeItemsByTargetId($target_id) {
+    return $this->filter(function ($item) use ($target_id) {
+      return $item->target_id != $target_id;
+    });
+  }
+
 }
diff --git a/core/modules/forum/forum.views.inc b/core/modules/forum/forum.views.inc
index 6f2425912b..9a37c25a9f 100644
--- a/core/modules/forum/forum.views.inc
+++ b/core/modules/forum/forum.views.inc
@@ -74,7 +74,7 @@ function forum_views_data() {
     'filter' => [
       'title' => t('Has taxonomy term'),
       'id' => 'taxonomy_index_tid',
-      'hierarchy table' => 'taxonomy_term_hierarchy',
+      'hierarchy table' => 'taxonomy_term__parent',
       'numeric' => TRUE,
       'skip base' => 'taxonomy_term_data',
       'allow empty' => TRUE,
diff --git a/core/modules/forum/tests/src/Functional/ForumTest.php b/core/modules/forum/tests/src/Functional/ForumTest.php
index 9ba4be7b2a..2bc6a45d84 100644
--- a/core/modules/forum/tests/src/Functional/ForumTest.php
+++ b/core/modules/forum/tests/src/Functional/ForumTest.php
@@ -433,7 +433,7 @@ public function createForum($type, $parent = 0) {
 
     // Verify forum hierarchy.
     $tid = $term['tid'];
-    $parent_tid = db_query("SELECT t.parent FROM {taxonomy_term_hierarchy} t WHERE t.tid = :tid", [':tid' => $tid])->fetchField();
+    $parent_tid = db_query("SELECT t.parent_target_id FROM {taxonomy_term__parent} t WHERE t.entity_id = :tid", [':tid' => $tid])->fetchField();
     $this->assertTrue($parent == $parent_tid, 'The ' . $type . ' is linked to its container');
 
     $forum = $this->container->get('entity.manager')->getStorage('taxonomy_term')->load($tid);
diff --git a/core/modules/hal/hal.services.yml b/core/modules/hal/hal.services.yml
index b2c898fc56..c99f604691 100644
--- a/core/modules/hal/hal.services.yml
+++ b/core/modules/hal/hal.services.yml
@@ -1,7 +1,7 @@
 services:
   serializer.normalizer.entity_reference_item.hal:
     class: Drupal\hal\Normalizer\EntityReferenceItemNormalizer
-    arguments: ['@hal.link_manager', '@serializer.entity_resolver']
+    arguments: ['@hal.link_manager', '@serializer.entity_resolver', '@entity_type.manager']
     tags:
       - { name: normalizer, priority: 10 }
   serializer.normalizer.field_item.hal:
diff --git a/core/modules/hal/src/Normalizer/EntityReferenceItemNormalizer.php b/core/modules/hal/src/Normalizer/EntityReferenceItemNormalizer.php
index fbcae8c758..78afbe9949 100644
--- a/core/modules/hal/src/Normalizer/EntityReferenceItemNormalizer.php
+++ b/core/modules/hal/src/Normalizer/EntityReferenceItemNormalizer.php
@@ -2,7 +2,9 @@
 
 namespace Drupal\hal\Normalizer;
 
+use Drupal\Core\Entity\EntityTypeManagerInterface;
 use Drupal\Core\Entity\FieldableEntityInterface;
+use Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem;
 use Drupal\hal\LinkManager\LinkManagerInterface;
 use Drupal\serialization\EntityResolver\EntityResolverInterface;
 use Drupal\serialization\EntityResolver\UuidReferenceInterface;
@@ -33,6 +35,13 @@ class EntityReferenceItemNormalizer extends FieldItemNormalizer implements UuidR
    */
   protected $entityResolver;
 
+  /**
+   * The entity type manager.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
+   */
+  protected $entityTypeManager;
+
   /**
    * Constructs an EntityReferenceItemNormalizer object.
    *
@@ -40,25 +49,28 @@ class EntityReferenceItemNormalizer extends FieldItemNormalizer implements UuidR
    *   The hypermedia link manager.
    * @param \Drupal\serialization\EntityResolver\EntityResolverInterface $entity_Resolver
    *   The entity resolver.
+   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
+   *   The entity type manager.
    */
-  public function __construct(LinkManagerInterface $link_manager, EntityResolverInterface $entity_Resolver) {
+  public function __construct(LinkManagerInterface $link_manager, EntityResolverInterface $entity_Resolver, EntityTypeManagerInterface $entity_type_manager) {
     $this->linkManager = $link_manager;
     $this->entityResolver = $entity_Resolver;
+    $this->entityTypeManager = $entity_type_manager;
   }
 
   /**
    * {@inheritdoc}
    */
   public function normalize($field_item, $format = NULL, array $context = []) {
+    // If this is not a fieldable entity, let the parent implementation handle
+    // it, only fieldable entities are supported as embedded resources.
+    if (!$this->targetEntityIsFieldable($field_item)) {
+      return parent::normalize($field_item, $format, $context);
+    }
+
     /** @var $field_item \Drupal\Core\Field\FieldItemInterface */
     $target_entity = $field_item->get('entity')->getValue();
 
-    // If this is not a content entity, let the parent implementation handle it,
-    // only content entities are supported as embedded resources.
-    if (!($target_entity instanceof FieldableEntityInterface)) {
-      return parent::normalize($field_item, $format, $context);
-    }
-
     // If the parent entity passed in a langcode, unset it before normalizing
     // the target entity. Otherwise, untranslatable fields of the target entity
     // will include the langcode.
@@ -91,6 +103,39 @@ public function normalize($field_item, $format = NULL, array $context = []) {
     ];
   }
 
+  /**
+   * Checks whether the referenced entity is of a fieldable entity type.
+   *
+   * @param \Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem $ref
+   *   The reference field item whose target entity needs to be checked.
+   *
+   * @return bool
+   *   TRUE when the referenced entity is of a fieldable entity type.
+   */
+  protected function targetEntityIsFieldable(EntityReferenceItem $ref) {
+    $target_entity = $ref->get('entity')->getValue();
+
+    if ($target_entity !== NULL) {
+      return $target_entity instanceof FieldableEntityInterface;
+    }
+
+    $referencing_entity = $ref->getEntity();
+    $target_entity_type_id = $ref->getFieldDefinition()->getSetting('target_type');
+
+    // If the entity type is the same as the parent, we can check that. This is
+    // just a shortcut to avoid getting the entity type defintition and checking
+    // the class.
+    if ($target_entity_type_id === $referencing_entity->getEntityTypeId()) {
+      return $referencing_entity instanceof FieldableEntityInterface;
+    }
+
+    // Otherwise, we need to get the class for the type.
+    $target_entity_type = $this->entityTypeManager->getDefinition($target_entity_type_id);
+    $target_entity_type_class = $target_entity_type->getClass();
+
+    return in_array(FieldableEntityInterface::class, class_implements($target_entity_type_class), TRUE);
+  }
+
   /**
    * {@inheritdoc}
    */
diff --git a/core/modules/hal/tests/src/Functional/EntityResource/Term/TermHalJsonAnonTest.php b/core/modules/hal/tests/src/Functional/EntityResource/Term/TermHalJsonAnonTest.php
index 73a1549e0a..52efd3a24b 100644
--- a/core/modules/hal/tests/src/Functional/EntityResource/Term/TermHalJsonAnonTest.php
+++ b/core/modules/hal/tests/src/Functional/EntityResource/Term/TermHalJsonAnonTest.php
@@ -2,6 +2,8 @@
 
 namespace Drupal\Tests\hal\Functional\EntityResource\Term;
 
+use Drupal\taxonomy\Entity\Term;
+use Drupal\taxonomy\TermInterface;
 use Drupal\Tests\hal\Functional\EntityResource\HalEntityNormalizationTrait;
 use Drupal\Tests\rest\Functional\AnonResourceTestTrait;
 use Drupal\Tests\rest\Functional\EntityResource\Term\TermResourceTestBase;
@@ -37,6 +39,12 @@ protected function getExpectedNormalizedEntity() {
 
     $normalization = $this->applyHalFieldNormalization($default_normalization);
 
+    // The parent term is either 0 (<root>) or 2.
+    // @see ::createEntity()
+    // @see ::testGet()
+    // @see ::testGetTermWithParent()
+    $parent_term_id = (int) $this->entity->get('parent')->target_id;
+
     return $normalization + [
       '_links' => [
         'self' => [
@@ -45,6 +53,32 @@ protected function getExpectedNormalizedEntity() {
         'type' => [
           'href' => $this->baseUrl . '/rest/type/taxonomy_term/camelids',
         ],
+        $this->baseUrl . '/rest/relation/taxonomy_term/camelids/parent' => [
+          $parent_term_id === TermInterface::ROOT_ID
+            ? NULL
+            : [
+              'href' => $this->baseUrl . '/taxonomy/term/2?_format=hal_json',
+            ],
+        ],
+      ],
+      '_embedded' => [
+        $this->baseUrl . '/rest/relation/taxonomy_term/camelids/parent' => [
+          $parent_term_id === TermInterface::ROOT_ID
+            ? NULL
+            : [
+              '_links' => [
+                'self' => [
+                  'href' => $this->baseUrl . '/taxonomy/term/2?_format=hal_json',
+                ],
+                'type' => [
+                  'href' => $this->baseUrl . '/rest/type/taxonomy_term/camelids',
+                ],
+              ],
+              'uuid' => [
+                ['value' => Term::load($parent_term_id)->uuid()],
+              ],
+            ],
+        ],
       ],
     ];
   }
diff --git a/core/modules/rest/tests/src/Functional/EntityResource/Term/TermResourceTestBase.php b/core/modules/rest/tests/src/Functional/EntityResource/Term/TermResourceTestBase.php
index e0a15813a8..584c34374c 100644
--- a/core/modules/rest/tests/src/Functional/EntityResource/Term/TermResourceTestBase.php
+++ b/core/modules/rest/tests/src/Functional/EntityResource/Term/TermResourceTestBase.php
@@ -4,6 +4,7 @@
 
 use Drupal\taxonomy\Entity\Term;
 use Drupal\taxonomy\Entity\Vocabulary;
+use Drupal\taxonomy\TermInterface;
 use Drupal\Tests\rest\Functional\BcTimestampNormalizerUnixTestTrait;
 use Drupal\Tests\rest\Functional\EntityResource\EntityResourceTestBase;
 use GuzzleHttp\RequestOptions;
@@ -90,6 +91,12 @@ protected function createEntity() {
    * {@inheritdoc}
    */
   protected function getExpectedNormalizedEntity() {
+    // The parent term is either 0 (<root>) or 2.
+    // @see ::createEntity()
+    // @see ::testGet()
+    // @see ::testGetTermWithParent()
+    $parent_term_id = (int) $this->entity->get('parent')->target_id;
+
     return [
       'tid' => [
         ['value' => 1],
@@ -113,7 +120,18 @@ protected function getExpectedNormalizedEntity() {
           'format' => NULL,
         ],
       ],
-      'parent' => [],
+      'parent' => [
+        $parent_term_id === TermInterface::ROOT_ID
+          ? [
+            'target_id' => TermInterface::ROOT_ID,
+          ]
+          : [
+            'target_id' => $parent_term_id,
+            'target_type' => 'taxonomy_term',
+            'target_uuid' => Term::load($parent_term_id)->uuid(),
+            'url' => base_path() . 'taxonomy/term/' . $parent_term_id,
+          ]
+      ],
       'weight' => [
         ['value' => 0],
       ],
@@ -230,4 +248,30 @@ public function testPatchPath() {
     $this->assertSame($normalization['path'], $updated_normalization['path']);
   }
 
+  /**
+   * Tests GETting a term with a parent term other than the default <root> (0).
+   *
+   * @see ::getExpectedNormalizedEntity()
+   */
+  public function testGetTermWithParent() {
+    // Create parent term and update the entity under test to use it.
+    Term::create(['vid' => Vocabulary::load('camelids')->id()])
+      ->setName('Lamoids')
+      ->save();
+    $this->entity->set('parent', 2)->save();
+
+    $this->initAuthentication();
+    $url = $this->getEntityResourceUrl();
+    $url->setOption('query', ['_format' => static::$format]);
+    $request_options = $this->getAuthenticationRequestOptions('GET');
+    $this->provisionEntityResource();
+    $this->setUpAuthorization('GET');
+    $response = $this->request('GET', $url, $request_options);
+    $expected = $this->getExpectedNormalizedEntity();
+    static::recursiveKSort($expected);
+    $actual = $this->serializer->decode((string) $response->getBody(), static::$format);
+    static::recursiveKSort($actual);
+    $this->assertSame($expected, $actual);
+  }
+
 }
diff --git a/core/modules/system/tests/fixtures/update/drupal-8.views-taxonomy-parent-2543726.php b/core/modules/system/tests/fixtures/update/drupal-8.views-taxonomy-parent-2543726.php
new file mode 100644
index 0000000000..852261e436
--- /dev/null
+++ b/core/modules/system/tests/fixtures/update/drupal-8.views-taxonomy-parent-2543726.php
@@ -0,0 +1,62 @@
+<?php
+
+/**
+ * @file
+ * Contains database additions to drupal-8.bare.standard.php.gz for testing the
+ * upgrade path of https://www.drupal.org/node/2455125.
+ */
+
+use Drupal\Core\Database\Database;
+use Drupal\Core\Serialization\Yaml;
+
+$connection = Database::getConnection();
+
+$view_file = __DIR__ . '/drupal-8.views-taxonomy-parent-2543726.yml';
+$view_config = Yaml::decode(file_get_contents($view_file));
+
+$connection->insert('config')
+  ->fields(['collection', 'name', 'data'])
+  ->values([
+    'collection' => '',
+    'name' => "views.view.test_taxonomy_parent",
+    'data' => serialize($view_config),
+  ])
+  ->execute();
+
+$uuid = new \Drupal\Component\Uuid\Php();
+
+// The root tid.
+$tids = [0];
+
+for ($i = 0; $i < 4; $i++) {
+  $name = $this->randomString();
+
+  $tid = $connection->insert('taxonomy_term_data')
+    ->fields(['vid', 'uuid', 'langcode'])
+    ->values(['vid' => 'tags', 'uuid' => $uuid->generate(), 'langcode' => 'en'])
+    ->execute();
+
+  $connection->insert('taxonomy_term_field_data')
+    ->fields(['tid', 'vid', 'langcode', 'name', 'weight', 'changed', 'default_langcode'])
+    ->values(['tid' => $tid, 'vid' => 'tags', 'langcode' => 'en', 'name' => $name, 'weight' => 0, 'changed' => REQUEST_TIME, 'default_langcode' => 1])
+    ->execute();
+
+  $tids[] = $tid;
+}
+
+$hierarchy = [
+  // Term with tid 1 has terms with tids 2 and 3 as parents.
+  1 => [2, 3],
+  2 => [3, 0],
+  3 => [0],
+];
+
+$query = $connection->insert('taxonomy_term_hierarchy')->fields(['tid', 'parent']);
+
+foreach ($hierarchy as $tid => $parents) {
+  foreach ($parents as $parent) {
+    $query->values(['tid' => $tids[$tid], 'parent' => $tids[$parent]]);
+  }
+}
+
+$query->execute();
diff --git a/core/modules/taxonomy/tests/modules/taxonomy_test_views/test_views/views.view.test_taxonomy_parent.yml b/core/modules/system/tests/fixtures/update/drupal-8.views-taxonomy-parent-2543726.yml
similarity index 97%
copy from core/modules/taxonomy/tests/modules/taxonomy_test_views/test_views/views.view.test_taxonomy_parent.yml
copy to core/modules/system/tests/fixtures/update/drupal-8.views-taxonomy-parent-2543726.yml
index 256f618991..65df6a80e8 100644
--- a/core/modules/taxonomy/tests/modules/taxonomy_test_views/test_views/views.view.test_taxonomy_parent.yml
+++ b/core/modules/system/tests/fixtures/update/drupal-8.views-taxonomy-parent-2543726.yml
@@ -124,8 +124,8 @@ display:
       relationships:
         parent:
           id: parent
-          table: taxonomy_term_hierarchy
-          field: parent
+          table: taxonomy_term__parent
+          field: parent_target_id
           relationship: none
           group_type: group
           admin_label: Parent
diff --git a/core/modules/taxonomy/src/Entity/Term.php b/core/modules/taxonomy/src/Entity/Term.php
index 2e70a1cf7d..c86466775c 100644
--- a/core/modules/taxonomy/src/Entity/Term.php
+++ b/core/modules/taxonomy/src/Entity/Term.php
@@ -7,6 +7,7 @@
 use Drupal\Core\Entity\EntityStorageInterface;
 use Drupal\Core\Entity\EntityTypeInterface;
 use Drupal\Core\Field\BaseFieldDefinition;
+use Drupal\taxonomy\TermHierarchyInterface;
 use Drupal\taxonomy\TermInterface;
 
 /**
@@ -52,10 +53,17 @@
  *   permission_granularity = "bundle"
  * )
  */
-class Term extends ContentEntityBase implements TermInterface {
+class Term extends ContentEntityBase implements TermInterface, TermHierarchyInterface {
 
   use EntityChangedTrait;
 
+  /**
+   * Array of all loaded term ancestry keyed by ancestor term ID.
+   *
+   * @var \Drupal\taxonomy\TermInterface[]
+   */
+  protected $ancestors;
+
   /**
    * {@inheritdoc}
    */
@@ -64,22 +72,26 @@ public static function postDelete(EntityStorageInterface $storage, array $entiti
 
     // See if any of the term's children are about to be become orphans.
     $orphans = [];
-    foreach (array_keys($entities) as $tid) {
-      if ($children = $storage->loadChildren($tid)) {
+    /** @var \Drupal\taxonomy\TermInterface $term */
+    foreach ($entities as $tid => $term) {
+      if ($children = $term->getChildren()) {
+        /** @var \Drupal\taxonomy\TermInterface $child */
         foreach ($children as $child) {
+          $parent = $child->get('parent');
+          // Update child parents item list.
+          $parent->removeItemsByTargetId($tid);
+
           // If the term has multiple parents, we don't delete it.
-          $parents = $storage->loadParents($child->id());
-          if (empty($parents)) {
+          if ($parent->count()) {
+            $child->save();
+          }
+          else {
             $orphans[] = $child;
           }
         }
       }
     }
 
-    // Delete term hierarchy information after looking up orphans but before
-    // deleting them so that their children/parent information is consistent.
-    $storage->deleteTermHierarchy(array_keys($entities));
-
     if (!empty($orphans)) {
       $storage->delete($orphans);
     }
@@ -88,14 +100,11 @@ public static function postDelete(EntityStorageInterface $storage, array $entiti
   /**
    * {@inheritdoc}
    */
-  public function postSave(EntityStorageInterface $storage, $update = TRUE) {
-    parent::postSave($storage, $update);
-
-    // Only change the parents if a value is set, keep the existing values if
-    // not.
-    if (isset($this->parent->target_id)) {
-      $storage->deleteTermHierarchy([$this->id()]);
-      $storage->updateTermHierarchy($this);
+  public function preSave(EntityStorageInterface $storage) {
+    parent::preSave($storage);
+    // Terms with no parents are mandatory children of <root>.
+    if (!$this->get('parent')->count()) {
+      $this->parent->target_id = static::ROOT_ID;
     }
   }
 
@@ -156,8 +165,7 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
       ->setLabel(t('Term Parents'))
       ->setDescription(t('The parents of this term.'))
       ->setSetting('target_type', 'taxonomy_term')
-      ->setCardinality(BaseFieldDefinition::CARDINALITY_UNLIMITED)
-      ->setCustomStorage(TRUE);
+      ->setCardinality(BaseFieldDefinition::CARDINALITY_UNLIMITED);
 
     $fields['changed'] = BaseFieldDefinition::create('changed')
       ->setLabel(t('Changed'))
@@ -248,4 +256,64 @@ protected function getFieldsToSkipFromTranslationChangesCheck() {
     return $fields;
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function getParents() {
+    $parents = $ids = [];
+    // Cannot use $this->get('parent')->referencedEntities() here because that
+    // strips out the '0' reference.
+    foreach ($this->get('parent') as $item) {
+      if ($item->target_id == static::ROOT_ID) {
+        // The <root> parent.
+        $parents[0] = NULL;
+        continue;
+      }
+      $ids[] = $item->target_id;
+    }
+
+    // @todo Better way to do this? AND handle the NULL/0 parent?
+    // Querying the terms again so that the same access checks are run when
+    // getParents() is called as in Drupal version prior to 8.3.
+    $loaded_parents = [];
+
+    if ($ids) {
+      $query = \Drupal::entityQuery('taxonomy_term')
+        ->condition('tid', $ids, 'IN');
+
+      $loaded_parents = static::loadMultiple($query->execute());
+    }
+
+    return $parents + $loaded_parents;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getAncestors() {
+    if (!isset($this->ancestors)) {
+      $this->ancestors = [$this->id() => $this];
+      $search[] = $this->id();
+
+      while ($tid = array_shift($search)) {
+        foreach (static::load($tid)->getParents() as $id => $parent) {
+          if ($parent && !isset($this->ancestors[$id])) {
+            $this->ancestors[$id] = $parent;
+            $search[] = $id;
+          }
+        }
+      }
+    }
+    return $this->ancestors;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getChildren() {
+    $query = \Drupal::entityQuery('taxonomy_term')
+      ->condition('parent', $this->id());
+    return static::loadMultiple($query->execute());
+  }
+
 }
diff --git a/core/modules/taxonomy/src/Plugin/views/argument/IndexTidDepth.php b/core/modules/taxonomy/src/Plugin/views/argument/IndexTidDepth.php
index 37abdd565e..77da59b74e 100644
--- a/core/modules/taxonomy/src/Plugin/views/argument/IndexTidDepth.php
+++ b/core/modules/taxonomy/src/Plugin/views/argument/IndexTidDepth.php
@@ -111,18 +111,19 @@ public function query($group_by = FALSE) {
     $last = "tn";
 
     if ($this->options['depth'] > 0) {
-      $subquery->leftJoin('taxonomy_term_hierarchy', 'th', "th.tid = tn.tid");
+      $subquery->leftJoin('taxonomy_term__parent', 'th', "th.entity_id = tn.tid");
       $last = "th";
       foreach (range(1, abs($this->options['depth'])) as $count) {
-        $subquery->leftJoin('taxonomy_term_hierarchy', "th$count", "$last.parent = th$count.tid");
-        $where->condition("th$count.tid", $tids, $operator);
+        $subquery->leftJoin('taxonomy_term__parent', "th$count", "$last.parent_target_id = th$count.entity_id");
+        $where->condition("th$count.entity_id", $tids, $operator);
         $last = "th$count";
       }
     }
     elseif ($this->options['depth'] < 0) {
       foreach (range(1, abs($this->options['depth'])) as $count) {
-        $subquery->leftJoin('taxonomy_term_hierarchy', "th$count", "$last.tid = th$count.parent");
-        $where->condition("th$count.tid", $tids, $operator);
+        $field = $count == 1 ? 'tid' : 'entity_id';
+        $subquery->leftJoin('taxonomy_term__parent', "th$count", "$last.$field = th$count.parent_target_id");
+        $where->condition("th$count.entity_id", $tids, $operator);
         $last = "th$count";
       }
     }
diff --git a/core/modules/taxonomy/src/Plugin/views/filter/TaxonomyIndexTidDepth.php b/core/modules/taxonomy/src/Plugin/views/filter/TaxonomyIndexTidDepth.php
index 802786e467..ebb5b51f22 100644
--- a/core/modules/taxonomy/src/Plugin/views/filter/TaxonomyIndexTidDepth.php
+++ b/core/modules/taxonomy/src/Plugin/views/filter/TaxonomyIndexTidDepth.php
@@ -77,18 +77,19 @@ public function query() {
     $last = "tn";
 
     if ($this->options['depth'] > 0) {
-      $subquery->leftJoin('taxonomy_term_hierarchy', 'th', "th.tid = tn.tid");
+      $subquery->leftJoin('taxonomy_term__parent', 'th', "th.entity_id = tn.tid");
       $last = "th";
       foreach (range(1, abs($this->options['depth'])) as $count) {
-        $subquery->leftJoin('taxonomy_term_hierarchy', "th$count", "$last.parent = th$count.tid");
-        $where->condition("th$count.tid", $this->value, $operator);
+        $subquery->leftJoin('taxonomy_term__parent', "th$count", "$last.parent_target_id = th$count.entity_id");
+        $where->condition("th$count.entity_id", $this->value, $operator);
         $last = "th$count";
       }
     }
     elseif ($this->options['depth'] < 0) {
       foreach (range(1, abs($this->options['depth'])) as $count) {
-        $subquery->leftJoin('taxonomy_term_hierarchy', "th$count", "$last.tid = th$count.parent");
-        $where->condition("th$count.tid", $this->value, $operator);
+        $field = $count == 1 ? 'tid' : 'entity_id';
+        $subquery->leftJoin('taxonomy_term__parent', "th$count", "$last.$field = th$count.parent_target_id");
+        $where->condition("th$count.entity_id", $this->value, $operator);
         $last = "th$count";
       }
     }
diff --git a/core/modules/taxonomy/src/TermHierarchyInterface.php b/core/modules/taxonomy/src/TermHierarchyInterface.php
new file mode 100644
index 0000000000..9c465463a2
--- /dev/null
+++ b/core/modules/taxonomy/src/TermHierarchyInterface.php
@@ -0,0 +1,39 @@
+<?php
+
+namespace Drupal\taxonomy;
+
+/**
+ * Provides an interface for handling taxonomy term hierarchy.
+ *
+ * @todo Merge this into \Drupal\taxonomy\TermInterface in Drupal 9.0.x.
+ *   https://www.drupal.org/node/2785683
+ */
+interface TermHierarchyInterface {
+
+  /**
+   * Returns a list of parents of this term.
+   *
+   * @return \Drupal\taxonomy\TermInterface[]
+   *   The parent taxonomy term entities keyed by term ID. If this term has a
+   *   <root> parent, that item is keyed with ROOT_ID and will have NULL as
+   *   value.
+   */
+  public function getParents();
+
+  /**
+   * Returns all ancestors of this term.
+   *
+   * @return \Drupal\taxonomy\TermInterface[]
+   *   A list of ancestor taxonomy term entities keyed by term ID.
+   */
+  public function getAncestors();
+
+  /**
+   * Returns all children terms of this term.
+   *
+   * @return \Drupal\taxonomy\TermInterface[]
+   *   A list of children taxonomy term entities keyed by term ID.
+   */
+  public function getChildren();
+
+}
diff --git a/core/modules/taxonomy/src/TermInterface.php b/core/modules/taxonomy/src/TermInterface.php
index 3087a56ecb..9120a61906 100644
--- a/core/modules/taxonomy/src/TermInterface.php
+++ b/core/modules/taxonomy/src/TermInterface.php
@@ -7,9 +7,17 @@
 
 /**
  * Provides an interface defining a taxonomy term entity.
+ *
+ * @todo Merge here \Drupal\taxonomy\TermHierarchyInterface in Drupal 9.0.x.
+ *   https://www.drupal.org/node/2785685
  */
 interface TermInterface extends ContentEntityInterface, EntityChangedInterface {
 
+  /**
+   * Root ID for terms with no parent.
+   */
+  const ROOT_ID = 0;
+
   /**
    * Gets the term's description.
    *
diff --git a/core/modules/taxonomy/src/TermStorage.php b/core/modules/taxonomy/src/TermStorage.php
index 0a00c13de5..113286bc36 100644
--- a/core/modules/taxonomy/src/TermStorage.php
+++ b/core/modules/taxonomy/src/TermStorage.php
@@ -2,35 +2,14 @@
 
 namespace Drupal\taxonomy;
 
-use Drupal\Core\Entity\Sql\SqlContentEntityStorage;
 use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\Sql\SqlContentEntityStorage;
 
 /**
  * Defines a Controller class for taxonomy terms.
  */
 class TermStorage extends SqlContentEntityStorage implements TermStorageInterface {
 
-  /**
-   * Array of loaded parents keyed by child term ID.
-   *
-   * @var array
-   */
-  protected $parents = [];
-
-  /**
-   * Array of all loaded term ancestry keyed by ancestor term ID.
-   *
-   * @var array
-   */
-  protected $parentsAll = [];
-
-  /**
-   * Array of child terms keyed by parent term ID.
-   *
-   * @var array
-   */
-  protected $children = [];
-
   /**
    * Array of term parents keyed by vocabulary ID and child term ID.
    *
@@ -80,9 +59,6 @@ public function create(array $values = []) {
    */
   public function resetCache(array $ids = NULL) {
     drupal_static_reset('taxonomy_term_count_nodes');
-    $this->parents = [];
-    $this->parentsAll = [];
-    $this->children = [];
     $this->treeChildren = [];
     $this->treeParents = [];
     $this->treeTerms = [];
@@ -93,100 +69,46 @@ public function resetCache(array $ids = NULL) {
   /**
    * {@inheritdoc}
    */
-  public function deleteTermHierarchy($tids) {
-    $this->database->delete('taxonomy_term_hierarchy')
-      ->condition('tid', $tids, 'IN')
-      ->execute();
-  }
+  public function deleteTermHierarchy($tids) { }
 
   /**
    * {@inheritdoc}
    */
-  public function updateTermHierarchy(EntityInterface $term) {
-    $query = $this->database->insert('taxonomy_term_hierarchy')
-      ->fields(['tid', 'parent']);
-
-    foreach ($term->parent as $parent) {
-      $query->values([
-        'tid' => $term->id(),
-        'parent' => (int) $parent->target_id,
-      ]);
-    }
-    $query->execute();
-  }
+  public function updateTermHierarchy(EntityInterface $term) { }
 
   /**
    * {@inheritdoc}
    */
   public function loadParents($tid) {
-    if (!isset($this->parents[$tid])) {
-      $parents = [];
-      $query = $this->database->select('taxonomy_term_field_data', 't');
-      $query->join('taxonomy_term_hierarchy', 'h', 'h.parent = t.tid');
-      $query->addField('t', 'tid');
-      $query->condition('h.tid', $tid);
-      $query->condition('t.default_langcode', 1);
-      $query->addTag('taxonomy_term_access');
-      $query->orderBy('t.weight');
-      $query->orderBy('t.name');
-      if ($ids = $query->execute()->fetchCol()) {
-        $parents = $this->loadMultiple($ids);
+    $terms = [];
+    /** @var \Drupal\taxonomy\TermInterface $term */
+    if ($tid && $term = $this->load($tid)) {
+      foreach ($term->getParents() as $id => $parent) {
+        // This method currently doesn't return the <root> parent.
+        // @see https://www.drupal.org/node/2019905
+        if (!empty($id)) {
+          $terms[$id] = $parent;
+        }
       }
-      $this->parents[$tid] = $parents;
     }
-    return $this->parents[$tid];
+
+    return $terms;
   }
 
   /**
    * {@inheritdoc}
    */
   public function loadAllParents($tid) {
-    if (!isset($this->parentsAll[$tid])) {
-      $parents = [];
-      if ($term = $this->load($tid)) {
-        $parents[$term->id()] = $term;
-        $terms_to_search[] = $term->id();
-
-        while ($tid = array_shift($terms_to_search)) {
-          if ($new_parents = $this->loadParents($tid)) {
-            foreach ($new_parents as $new_parent) {
-              if (!isset($parents[$new_parent->id()])) {
-                $parents[$new_parent->id()] = $new_parent;
-                $terms_to_search[] = $new_parent->id();
-              }
-            }
-          }
-        }
-      }
-
-      $this->parentsAll[$tid] = $parents;
-    }
-    return $this->parentsAll[$tid];
+    /** @var \Drupal\taxonomy\TermInterface $term */
+    return (!empty($tid) && $term = $this->load($tid)) ? $term->getAncestors() : [];
   }
 
   /**
    * {@inheritdoc}
    */
   public function loadChildren($tid, $vid = NULL) {
-    if (!isset($this->children[$tid])) {
-      $children = [];
-      $query = $this->database->select('taxonomy_term_field_data', 't');
-      $query->join('taxonomy_term_hierarchy', 'h', 'h.tid = t.tid');
-      $query->addField('t', 'tid');
-      $query->condition('h.parent', $tid);
-      if ($vid) {
-        $query->condition('t.vid', $vid);
-      }
-      $query->condition('t.default_langcode', 1);
-      $query->addTag('taxonomy_term_access');
-      $query->orderBy('t.weight');
-      $query->orderBy('t.name');
-      if ($ids = $query->execute()->fetchCol()) {
-        $children = $this->loadMultiple($ids);
-      }
-      $this->children[$tid] = $children;
-    }
-    return $this->children[$tid];
+    /** @var \Drupal\taxonomy\TermInterface $term */
+    return (!empty($tid) && $term = $this->load($tid)) ? $term->getChildren() : [];
   }
 
   /**
@@ -202,11 +124,11 @@ public function loadTree($vid, $parent = 0, $max_depth = NULL, $load_entities =
         $this->treeParents[$vid] = [];
         $this->treeTerms[$vid] = [];
         $query = $this->database->select('taxonomy_term_field_data', 't');
-        $query->join('taxonomy_term_hierarchy', 'h', 'h.tid = t.tid');
+        $query->join('taxonomy_term__parent', 'p', 't.tid = p.entity_id');
+        $query->addExpression('parent_target_id', 'parent');
         $result = $query
           ->addTag('taxonomy_term_access')
           ->fields('t')
-          ->fields('h', ['parent'])
           ->condition('t.vid', $vid)
           ->condition('t.default_langcode', 1)
           ->orderBy('t.weight')
@@ -254,7 +176,9 @@ public function loadTree($vid, $parent = 0, $max_depth = NULL, $load_entities =
               $term = clone $term;
             }
             $term->depth = $depth;
-            unset($term->parent);
+            if (!$load_entities) {
+              unset($term->parent);
+            }
             $tid = $load_entities ? $term->id() : $term->tid;
             $term->parents = $this->treeParents[$vid][$tid];
             $tree[] = $term;
@@ -351,7 +275,7 @@ public function getNodeTerms(array $nids, array $vocabs = [], $langcode = NULL)
   public function __sleep() {
     $vars = parent::__sleep();
     // Do not serialize static cache.
-    unset($vars['parents'], $vars['parentsAll'], $vars['children'], $vars['treeChildren'], $vars['treeParents'], $vars['treeTerms'], $vars['trees']);
+    unset($vars['treeChildren'], $vars['treeParents'], $vars['treeTerms'], $vars['trees']);
     return $vars;
   }
 
@@ -361,9 +285,6 @@ public function __sleep() {
   public function __wakeup() {
     parent::__wakeup();
     // Initialize static caches.
-    $this->parents = [];
-    $this->parentsAll = [];
-    $this->children = [];
     $this->treeChildren = [];
     $this->treeParents = [];
     $this->treeTerms = [];
diff --git a/core/modules/taxonomy/src/TermStorageInterface.php b/core/modules/taxonomy/src/TermStorageInterface.php
index 4ab2d2deaf..227919d85b 100644
--- a/core/modules/taxonomy/src/TermStorageInterface.php
+++ b/core/modules/taxonomy/src/TermStorageInterface.php
@@ -15,6 +15,10 @@
    *
    * @param array $tids
    *   Array of terms that need to be removed from hierarchy.
+   *
+   * @todo Remove this method in Drupal 9.0.x. Now the parent references are
+   *   automatically cleared when deleting a taxonomy term.
+   *   https://www.drupal.org/node/2785693
    */
   public function deleteTermHierarchy($tids);
 
@@ -23,6 +27,10 @@ public function deleteTermHierarchy($tids);
    *
    * @param \Drupal\Core\Entity\EntityInterface $term
    *   Term entity that needs to be added to term hierarchy information.
+   *
+   * @todo remove this method Drupal 9.0.x. Now the parent references are
+   *   automatically updates when when a taxonomy term is added/updated.
+   *   https://www.drupal.org/node/2785693
    */
   public function updateTermHierarchy(EntityInterface $term);
 
@@ -34,6 +42,9 @@ public function updateTermHierarchy(EntityInterface $term);
    *
    * @return \Drupal\taxonomy\TermInterface[]
    *   An array of term objects which are the parents of the term $tid.
+   *
+   * @deprecated in Drupal 8.3.x, will be removed in Drupal 9.0.0. Use
+   *   \Drupal\taxonomy\Entity\Term::load($tid)->getParents() instead.
    */
   public function loadParents($tid);
 
@@ -45,6 +56,9 @@ public function loadParents($tid);
    *
    * @return \Drupal\taxonomy\TermInterface[]
    *   An array of term objects which are the ancestors of the term $tid.
+   *
+   * @deprecated in Drupal 8.3.x, will be removed in Drupal 9.0.0. Use
+   *   \Drupal\taxonomy\Entity\Term::load($tid)->getAncestors() instead.
    */
   public function loadAllParents($tid);
 
@@ -58,6 +72,9 @@ public function loadAllParents($tid);
    *
    * @return \Drupal\taxonomy\TermInterface[]
    *   An array of term objects that are the children of the term $tid.
+   *
+   * @deprecated in Drupal 8.3.x, will be removed in Drupal 9.0.0. Use
+   *   \Drupal\taxonomy\Entity\Term::load($tid)->getChildren($vid) instead.
    */
   public function loadChildren($tid, $vid = NULL);
 
diff --git a/core/modules/taxonomy/src/TermStorageSchema.php b/core/modules/taxonomy/src/TermStorageSchema.php
index 5bcb088db4..2b49ee247b 100644
--- a/core/modules/taxonomy/src/TermStorageSchema.php
+++ b/core/modules/taxonomy/src/TermStorageSchema.php
@@ -22,36 +22,6 @@ protected function getEntitySchema(ContentEntityTypeInterface $entity_type, $res
       'taxonomy_term__vid_name' => ['vid', 'name'],
     ];
 
-    $schema['taxonomy_term_hierarchy'] = [
-      'description' => 'Stores the hierarchical relationship between terms.',
-      'fields' => [
-        'tid' => [
-          'type' => 'int',
-          'unsigned' => TRUE,
-          'not null' => TRUE,
-          'default' => 0,
-          'description' => 'Primary Key: The {taxonomy_term_data}.tid of the term.',
-        ],
-        'parent' => [
-          'type' => 'int',
-          'unsigned' => TRUE,
-          'not null' => TRUE,
-          'default' => 0,
-          'description' => "Primary Key: The {taxonomy_term_data}.tid of the term's parent. 0 indicates no parent.",
-        ],
-      ],
-      'indexes' => [
-        'parent' => ['parent'],
-      ],
-      'foreign keys' => [
-        'taxonomy_term_data' => [
-          'table' => 'taxonomy_term_data',
-          'columns' => ['tid' => 'tid'],
-        ],
-      ],
-      'primary key' => ['tid', 'parent'],
-    ];
-
     $schema['taxonomy_index'] = [
       'description' => 'Maintains denormalized information about node/term relationships.',
       'fields' => [
diff --git a/core/modules/taxonomy/src/TermViewsData.php b/core/modules/taxonomy/src/TermViewsData.php
index 5e98bfe9b2..6d47f3ffb6 100644
--- a/core/modules/taxonomy/src/TermViewsData.php
+++ b/core/modules/taxonomy/src/TermViewsData.php
@@ -36,7 +36,7 @@ public function getViewsData() {
     $data['taxonomy_term_field_data']['tid']['filter']['id'] = 'taxonomy_index_tid';
     $data['taxonomy_term_field_data']['tid']['filter']['title'] = $this->t('Term');
     $data['taxonomy_term_field_data']['tid']['filter']['help'] = $this->t('Taxonomy term chosen from autocomplete or select widget.');
-    $data['taxonomy_term_field_data']['tid']['filter']['hierarchy table'] = 'taxonomy_term_hierarchy';
+    $data['taxonomy_term_field_data']['tid']['filter']['hierarchy table'] = 'taxonomy_term__parent';
     $data['taxonomy_term_field_data']['tid']['filter']['numeric'] = TRUE;
 
     $data['taxonomy_term_field_data']['tid_raw'] = [
@@ -146,8 +146,8 @@ public function getViewsData() {
         'left_field' => 'nid',
         'field' => 'nid',
       ],
-      'taxonomy_term_hierarchy' => [
-        'left_field' => 'tid',
+      'taxonomy_term__parent' => [
+        'left_field' => 'entity_id',
         'field' => 'tid',
       ],
     ];
@@ -181,7 +181,7 @@ public function getViewsData() {
       'filter' => [
         'title' => $this->t('Has taxonomy term'),
         'id' => 'taxonomy_index_tid',
-        'hierarchy table' => 'taxonomy_term_hierarchy',
+        'hierarchy table' => 'taxonomy_term__parent',
         'numeric' => TRUE,
         'skip base' => 'taxonomy_term_field_data',
         'allow empty' => TRUE,
@@ -223,40 +223,15 @@ public function getViewsData() {
       ],
     ];
 
-    $data['taxonomy_term_hierarchy']['table']['group']  = $this->t('Taxonomy term');
-    $data['taxonomy_term_hierarchy']['table']['provider']  = 'taxonomy';
-
-    $data['taxonomy_term_hierarchy']['table']['join'] = [
-      'taxonomy_term_hierarchy' => [
-        // Link to self through left.parent = right.tid (going down in depth).
-        'left_field' => 'tid',
-        'field' => 'parent',
-      ],
-      'taxonomy_term_field_data' => [
-        // Link directly to taxonomy_term_field_data via tid.
-        'left_field' => 'tid',
-        'field' => 'tid',
-      ],
+    // Link to self through left.parent = right.tid (going down in depth).
+    $data['taxonomy_term__parent']['table']['join']['taxonomy_term__parent'] = [
+      'left_field' => 'entity_id',
+      'field' => 'parent_target_id',
     ];
 
-    $data['taxonomy_term_hierarchy']['parent'] = [
-      'title' => $this->t('Parent term'),
-      'help' => $this->t('The parent term of the term. This can produce duplicate entries if you are using a vocabulary that allows multiple parents.'),
-      'relationship' => [
-        'base' => 'taxonomy_term_field_data',
-        'field' => 'parent',
-        'label' => $this->t('Parent'),
-        'id' => 'standard',
-      ],
-      'filter' => [
-        'help' => $this->t('Filter the results of "Taxonomy: Term" by the parent pid.'),
-        'id' => 'numeric',
-      ],
-      'argument' => [
-        'help' => $this->t('The parent term of the term.'),
-        'id' => 'taxonomy',
-      ],
-    ];
+    $data['taxonomy_term__parent']['parent_target_id']['help'] = $this->t('The parent term of the term. This can produce duplicate entries if you are using a vocabulary that allows multiple parents.');
+    $data['taxonomy_term__parent']['parent_target_id']['relationship']['label'] = $this->t('Parent');
+    $data['taxonomy_term__parent']['parent_target_id']['argument']['id'] = 'taxonomy';
 
     return $data;
   }
diff --git a/core/modules/taxonomy/src/VocabularyStorage.php b/core/modules/taxonomy/src/VocabularyStorage.php
index bdbf8938f7..0538e71b49 100644
--- a/core/modules/taxonomy/src/VocabularyStorage.php
+++ b/core/modules/taxonomy/src/VocabularyStorage.php
@@ -3,6 +3,7 @@
 namespace Drupal\taxonomy;
 
 use Drupal\Core\Config\Entity\ConfigEntityStorage;
+use Drupal\taxonomy\Entity\Vocabulary;
 
 /**
  * Defines a storage handler class for taxonomy vocabularies.
@@ -21,7 +22,12 @@ public function resetCache(array $ids = NULL) {
    * {@inheritdoc}
    */
   public function getToplevelTids($vids) {
-    return db_query('SELECT t.tid FROM {taxonomy_term_data} t INNER JOIN {taxonomy_term_hierarchy} th ON th.tid = t.tid WHERE t.vid IN ( :vids[] ) AND th.parent = 0', [':vids[]' => $vids])->fetchCol();
+    $tids = \Drupal::entityQuery('taxonomy_term')
+      ->condition('vid', $vids, 'IN')
+      ->condition('parent.target_id', TermInterface::ROOT_ID)
+      ->execute();
+
+    return array_values($tids);
   }
 
 }
diff --git a/core/modules/taxonomy/taxonomy.install b/core/modules/taxonomy/taxonomy.install
new file mode 100644
index 0000000000..e1a2c45dcf
--- /dev/null
+++ b/core/modules/taxonomy/taxonomy.install
@@ -0,0 +1,134 @@
+<?php
+
+/**
+ * @file
+ * Install, update and uninstall functions for the taxonomy module.
+ */
+
+/**
+ * Convert the custom taxonomy term hierarchy storage to a default storage.
+ */
+function taxonomy_update_8501() {
+  $manager = \Drupal::entityDefinitionUpdateManager();
+  /** @var \Drupal\Core\Field\BaseFieldDefinition $definition */
+  $definition = $manager->getFieldStorageDefinition('parent', 'taxonomy_term');
+  $definition->setCustomStorage(FALSE);
+  $manager->updateFieldStorageDefinition($definition);
+  // Update the entity type because a new interface was added to Term entity.
+  $manager->updateEntityType($manager->getEntityType('taxonomy_term'));
+}
+
+/**
+ * Copy hierarchy from {taxonomy_term_hierarchy} to {taxonomy_term__parent}.
+ */
+function taxonomy_update_8502(&$sandbox) {
+  $database = \Drupal::database();
+
+  if (!isset($sandbox['current'])) {
+    // Set batch ops sandbox.
+    $sandbox['current'] = 0;
+    $sandbox['max'] = $database->select('taxonomy_term_hierarchy')
+      ->countQuery()
+      ->execute()
+      ->fetchField();
+  }
+
+  // Save the hierarchy.
+  $select = $database->select('taxonomy_term_hierarchy', 'h');
+  $select->join('taxonomy_term_data', 'd', 'h.tid = d.tid');
+  $hierarchy = $select
+    ->fields('h', ['tid', 'parent'])
+    ->fields('d', ['vid', 'langcode'])
+    ->range($sandbox['current'], $sandbox['current'] + 100)
+    ->execute()
+    ->fetchAll();
+
+  // Restore data.
+  $insert = $database->insert('taxonomy_term__parent')
+    ->fields(['bundle', 'entity_id', 'revision_id', 'langcode', 'delta', 'parent_target_id']);
+  $tid = -1;
+
+  foreach ($hierarchy as $row) {
+    if ($row->tid !== $tid) {
+      $delta = 0;
+      $tid = $row->tid;
+    }
+
+    $insert->values([
+      'bundle' => $row->vid,
+      'entity_id' => $row->tid,
+      'revision_id' => $row->tid,
+      'langcode' => $row->langcode,
+      'delta' => $delta,
+      'parent_target_id' => $row->parent,
+    ]);
+
+    $delta++;
+    $sandbox['current']++;
+  }
+
+  $insert->execute();
+
+  $sandbox['#finished'] = empty($sandbox['max']) ? 1 : ($sandbox['current'] / $sandbox['max']);
+
+  if ($sandbox['#finished'] >= 1) {
+    return t('Taxonomy term hierarchy converted.');
+  }
+  return t('Taxonomy term hierarchy NOT FULLY converted.');
+}
+
+/**
+ * Update views to use {taxonomy_term__parent} in relationships.
+ */
+function taxonomy_update_8503() {
+  $config_factory = \Drupal::configFactory();
+  foreach ($config_factory->listAll('views.view.') as $id) {
+    $view = $config_factory->getEditable($id);
+    $changed = FALSE;
+
+    foreach (array_keys($view->get('display')) as $display_id) {
+      $base_path = "display.$display_id.display_options.relationships.parent";
+      $table_path = $base_path . '.table';
+      $field_path = $base_path . '.field';
+
+      if (!empty($table = $view->get($table_path)) && $table == 'taxonomy_term_hierarchy') {
+        $view->set($table_path, 'taxonomy_term__parent');
+        $view->set($field_path, 'parent_target_id');
+
+        $base_path = "display.{$display_id}.display_options.filters.parent";
+        $table_path = $base_path . '.table';
+        $field_path = $base_path . '.field';
+
+        if (!empty($table = $view->get($table_path)) && $table == 'taxonomy_term_hierarchy') {
+          $view->set($table_path, 'taxonomy_term__parent');
+          $view->set($field_path, 'parent_target_id');
+        }
+
+        $base_path = "display.{$display_id}.display_options.arguments.parent.table";
+        $table_path = $base_path . '.table';
+        $field_path = $base_path . '.field';
+
+        if (!empty($table = $view->get($table_path)) && $table == 'taxonomy_term_hierarchy') {
+          $view->set($table_path, 'taxonomy_term__parent');
+          $view->set($field_path, 'parent_target_id');
+        }
+
+        $changed = TRUE;
+      }
+    }
+
+    if ($changed) {
+      $view->save(TRUE);
+    }
+  }
+}
+
+/**
+ * Drop the legacy table {taxonomy_term_hierarchy}.
+ */
+function taxonomy_update_8504(&$sandbox) {
+  $database = \Drupal::database();
+  // Drop the legacy table.
+  $database->schema()->dropTable('taxonomy_term_hierarchy');
+  return t('Taxonomy term legacy tables dropped.');
+}
diff --git a/core/modules/taxonomy/tests/modules/taxonomy_test_views/test_views/views.view.test_taxonomy_parent.yml b/core/modules/taxonomy/tests/modules/taxonomy_test_views/test_views/views.view.test_taxonomy_parent.yml
index 256f618991..65df6a80e8 100644
--- a/core/modules/taxonomy/tests/modules/taxonomy_test_views/test_views/views.view.test_taxonomy_parent.yml
+++ b/core/modules/taxonomy/tests/modules/taxonomy_test_views/test_views/views.view.test_taxonomy_parent.yml
@@ -124,8 +124,8 @@ display:
       relationships:
         parent:
           id: parent
-          table: taxonomy_term_hierarchy
-          field: parent
+          table: taxonomy_term__parent
+          field: parent_target_id
           relationship: none
           group_type: group
           admin_label: Parent
diff --git a/core/modules/taxonomy/tests/modules/taxonomy_test_views/test_views/views.view.test_taxonomy_term_relationship.yml b/core/modules/taxonomy/tests/modules/taxonomy_test_views/test_views/views.view.test_taxonomy_term_relationship.yml
index 80295b6ea2..664ae27cf0 100644
--- a/core/modules/taxonomy/tests/modules/taxonomy_test_views/test_views/views.view.test_taxonomy_term_relationship.yml
+++ b/core/modules/taxonomy/tests/modules/taxonomy_test_views/test_views/views.view.test_taxonomy_term_relationship.yml
@@ -186,8 +186,8 @@ display:
           plugin_id: standard
         parent:
           id: parent
-          table: taxonomy_term_hierarchy
-          field: parent
+          table: taxonomy_term__parent
+          field: parent_target_id
           relationship: none
           group_type: group
           admin_label: Parent
diff --git a/core/modules/taxonomy/tests/src/Functional/TaxonomyQueryAlterTest.php b/core/modules/taxonomy/tests/src/Functional/TaxonomyQueryAlterTest.php
index 85310e9aa9..58ab9e825e 100644
--- a/core/modules/taxonomy/tests/src/Functional/TaxonomyQueryAlterTest.php
+++ b/core/modules/taxonomy/tests/src/Functional/TaxonomyQueryAlterTest.php
@@ -32,7 +32,7 @@ public function testTaxonomyQueryAlter() {
     }
 
     // Set up hierarchy. Term 2 is a child of 1.
-    $terms[2]->parent = $terms[1]->id();
+    $terms[2]->parent->setValue($terms[1]->id());
     $terms[2]->save();
 
     $term_storage = \Drupal::entityManager()->getStorage('taxonomy_term');
@@ -50,12 +50,12 @@ public function testTaxonomyQueryAlter() {
     $this->setupQueryTagTestHooks();
     $loaded_terms = $term_storage->loadParents($terms[2]->id());
     $this->assertEqual(count($loaded_terms), 1, 'All parent terms were loaded');
-    $this->assertQueryTagTestResult(2, 1, 'TermStorage::loadParents()');
+    $this->assertQueryTagTestResult(3, 1, 'TermStorage::loadParents()');
 
     $this->setupQueryTagTestHooks();
     $loaded_terms = $term_storage->loadChildren($terms[1]->id());
     $this->assertEqual(count($loaded_terms), 1, 'All child terms were loaded');
-    $this->assertQueryTagTestResult(2, 1, 'TermStorage::loadChildren()');
+    $this->assertQueryTagTestResult(3, 1, 'TermStorage::loadChildren()');
 
     $this->setupQueryTagTestHooks();
     $query = db_select('taxonomy_term_data', 't');
diff --git a/core/modules/taxonomy/tests/src/Functional/Update/TaxonomyParentUpdateTest.php b/core/modules/taxonomy/tests/src/Functional/Update/TaxonomyParentUpdateTest.php
new file mode 100644
index 0000000000..6733e7e447
--- /dev/null
+++ b/core/modules/taxonomy/tests/src/Functional/Update/TaxonomyParentUpdateTest.php
@@ -0,0 +1,82 @@
+<?php
+
+namespace Drupal\Tests\taxonomy\Functional\Update;
+
+use Drupal\Component\Serialization\Yaml;
+use Drupal\Component\Utility\Unicode;
+use Drupal\FunctionalTests\Update\UpdatePathTestBase;
+use Drupal\taxonomy\Entity\Term;
+
+/**
+ * Ensure that the taxonomy updates are running as expected.
+ *
+ * @group taxonomy
+ */
+class TaxonomyParentUpdateTest extends UpdatePathTestBase {
+
+  /**
+   * The database connection.
+   *
+   * @var \Drupal\Core\Database\Connection
+   */
+  protected $db;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+    $this->db = $this->container->get('database');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setDatabaseDumpFiles() {
+    $this->databaseDumpFiles = [
+      __DIR__ . '/../../../../../system/tests/fixtures/update/drupal-8-rc1.bare.standard.php.gz',
+      __DIR__ . '/../../../../../system/tests/fixtures/update/drupal-8.views-taxonomy-parent-2543726.php',
+    ];
+  }
+
+  /**
+   * Tests taxonomy term parents update.
+   *
+   * @see taxonomy_update_8501()
+   * @see taxonomy_update_8502()
+   * @see taxonomy_update_8503()
+   * @see taxonomy_update_8504()
+   */
+  public function testTaxonomyUpdateParents() {
+    // Run updates.
+    $this->runUpdates();
+
+    /** @var \Drupal\taxonomy\TermInterface $term */
+    $term = Term::load(1);
+    $parents = [2, 3];
+    $this->assertSame(count($term->parent), 2);
+    $this->assertTrue(in_array($term->parent[0]->entity->id(), $parents));
+    $this->assertTrue(in_array($term->parent[1]->entity->id(), $parents));
+
+    $term = Term::load(2);
+    $parents = [0, 3];
+    $this->assertSame(count($term->parent), 2);
+    $this->assertTrue(in_array($term->parent[0]->target_id, $parents));
+    $this->assertTrue(in_array($term->parent[1]->target_id, $parents));
+
+    $term = Term::load(3);
+    $this->assertSame(count($term->parent), 1);
+    // Target ID is returned as string.
+    $this->assertSame((int) $term->get('parent')[0]->target_id, 0);
+
+    // Test if the view has been converted to use {taxonomy_term__parent} table
+    // instead of {taxonomy_term_hierarchy}.
+    $view = $this->config("views.view.test_taxonomy_parent");
+    $path = 'display.default.display_options.relationships.parent.table';
+    $this->assertSame($view->get($path), 'taxonomy_term__parent');
+
+    // The {taxonomy_term_hierarchy} table has been removed.
+    $this->assertFalse($this->db->schema()->tableExists('taxonomy_term_hierarchy'));
+  }
+
+}
diff --git a/core/modules/taxonomy/tests/src/Functional/Views/TaxonomyRelationshipTest.php b/core/modules/taxonomy/tests/src/Functional/Views/TaxonomyRelationshipTest.php
index 03be3e15bb..3299964909 100644
--- a/core/modules/taxonomy/tests/src/Functional/Views/TaxonomyRelationshipTest.php
+++ b/core/modules/taxonomy/tests/src/Functional/Views/TaxonomyRelationshipTest.php
@@ -61,24 +61,24 @@ public function testTaxonomyRelationships() {
     $this->assertEqual($views_data['table']['join']['taxonomy_term_field_data']['field'], 'tid');
     $this->assertEqual($views_data['table']['join']['node_field_data']['left_field'], 'nid');
     $this->assertEqual($views_data['table']['join']['node_field_data']['field'], 'nid');
-    $this->assertEqual($views_data['table']['join']['taxonomy_term_hierarchy']['left_field'], 'tid');
-    $this->assertEqual($views_data['table']['join']['taxonomy_term_hierarchy']['field'], 'tid');
+    $this->assertEqual($views_data['table']['join']['taxonomy_term__parent']['left_field'], 'entity_id');
+    $this->assertEqual($views_data['table']['join']['taxonomy_term__parent']['field'], 'tid');
 
-    // Check the generated views data of taxonomy_term_hierarchy.
-    $views_data = Views::viewsData()->get('taxonomy_term_hierarchy');
+    // Check the generated views data of taxonomy_term__parent.
+    $views_data = Views::viewsData()->get('taxonomy_term__parent');
     // Check the table join data.
-    $this->assertEqual($views_data['table']['join']['taxonomy_term_hierarchy']['left_field'], 'tid');
-    $this->assertEqual($views_data['table']['join']['taxonomy_term_hierarchy']['field'], 'parent');
+    $this->assertEqual($views_data['table']['join']['taxonomy_term__parent']['left_field'], 'entity_id');
+    $this->assertEqual($views_data['table']['join']['taxonomy_term__parent']['field'], 'parent_target_id');
     $this->assertEqual($views_data['table']['join']['taxonomy_term_field_data']['left_field'], 'tid');
-    $this->assertEqual($views_data['table']['join']['taxonomy_term_field_data']['field'], 'tid');
+    $this->assertEqual($views_data['table']['join']['taxonomy_term_field_data']['field'], 'entity_id');
     // Check the parent relationship data.
-    $this->assertEqual($views_data['parent']['relationship']['base'], 'taxonomy_term_field_data');
-    $this->assertEqual($views_data['parent']['relationship']['field'], 'parent');
-    $this->assertEqual($views_data['parent']['relationship']['label'], t('Parent'));
-    $this->assertEqual($views_data['parent']['relationship']['id'], 'standard');
+    $this->assertEqual($views_data['parent_target_id']['relationship']['base'], 'taxonomy_term_field_data');
+    $this->assertEqual($views_data['parent_target_id']['relationship']['base field'], 'tid');
+    $this->assertEqual($views_data['parent_target_id']['relationship']['label'], t('Parent'));
+    $this->assertEqual($views_data['parent_target_id']['relationship']['id'], 'standard');
     // Check the parent filter and argument data.
-    $this->assertEqual($views_data['parent']['filter']['id'], 'numeric');
-    $this->assertEqual($views_data['parent']['argument']['id'], 'taxonomy');
+    $this->assertEqual($views_data['parent_target_id']['filter']['id'], 'numeric');
+    $this->assertEqual($views_data['parent_target_id']['argument']['id'], 'taxonomy');
 
     // Check an actual test view.
     $view = Views::getView('test_taxonomy_term_relationship');
@@ -95,7 +95,7 @@ public function testTaxonomyRelationships() {
       if (!$index) {
         $this->assertTrue($row->_relationship_entities['parent'] instanceof TermInterface);
         $this->assertEqual($row->_relationship_entities['parent']->id(), $this->term2->id());
-        $this->assertEqual($row->taxonomy_term_field_data_taxonomy_term_hierarchy_tid, $this->term2->id());
+        $this->assertEqual($row->taxonomy_term_field_data_taxonomy_term__parent_tid, $this->term2->id());
       }
       $this->assertTrue($row->_relationship_entities['nid'] instanceof NodeInterface);
       $this->assertEqual($row->_relationship_entities['nid']->id(), $this->nodes[$index]->id());
diff --git a/core/modules/taxonomy/tests/src/Kernel/Migrate/d6/MigrateTaxonomyTermTest.php b/core/modules/taxonomy/tests/src/Kernel/Migrate/d6/MigrateTaxonomyTermTest.php
index 6450e4d91e..66fa25f5d9 100644
--- a/core/modules/taxonomy/tests/src/Kernel/Migrate/d6/MigrateTaxonomyTermTest.php
+++ b/core/modules/taxonomy/tests/src/Kernel/Migrate/d6/MigrateTaxonomyTermTest.php
@@ -3,6 +3,7 @@
 namespace Drupal\Tests\taxonomy\Kernel\Migrate\d6;
 
 use Drupal\taxonomy\Entity\Term;
+use Drupal\taxonomy\TermInterface;
 use Drupal\Tests\migrate_drupal\Kernel\d6\MigrateDrupal6TestBase;
 
 /**
@@ -92,7 +93,7 @@ public function testTaxonomyTerms() {
       $this->assertSame($values['vid'], $term->vid->target_id);
       $this->assertSame((string) $values['weight'], $term->weight->value);
       if ($values['parent'] === [0]) {
-        $this->assertNull($term->parent->target_id);
+        $this->assertSame((int) $term->parent->target_id, TermInterface::ROOT_ID);
       }
       else {
         $parents = [];
