diff --git a/core/lib/Drupal/Core/Entity/Sql/DefaultTableMapping.php b/core/lib/Drupal/Core/Entity/Sql/DefaultTableMapping.php
index 14374a5e3f1cf00eba98c8d93edec65d36ca9c0e..879762de81b472ff7bd98743b1f148777e6274c7 100644
--- a/core/lib/Drupal/Core/Entity/Sql/DefaultTableMapping.php
+++ b/core/lib/Drupal/Core/Entity/Sql/DefaultTableMapping.php
@@ -24,6 +24,35 @@ class DefaultTableMapping implements TableMappingInterface {
    */
   protected $fieldStorageDefinitions = [];
 
+  /**
+   * The base table of the entity.
+   *
+   * @var string
+   */
+  protected $baseTable;
+
+  /**
+   * The table that stores revisions, if the entity supports revisions.
+   *
+   * @var string
+   */
+  protected $revisionTable;
+
+  /**
+   * The table that stores field data, if the entity has multilingual support.
+   *
+   * @var string
+   */
+  protected $dataTable;
+
+  /**
+   * The table that stores revision field data if the entity supports revisions
+   * and has multilingual support.
+   *
+   * @var string
+   */
+  protected $revisionDataTable;
+
   /**
    * A list of field names per table.
    *
@@ -87,6 +116,132 @@ class DefaultTableMapping implements TableMappingInterface {
   public function __construct(ContentEntityTypeInterface $entity_type, array $storage_definitions) {
     $this->entityType = $entity_type;
     $this->fieldStorageDefinitions = $storage_definitions;
+
+    // @todo Remove table names from the entity type definition in
+    //   https://www.drupal.org/node/2232465.
+    $this->baseTable = $entity_type->getBaseTable() ?: $entity_type->id();
+    if ($entity_type->isRevisionable()) {
+      $this->revisionTable = $entity_type->getRevisionTable() ?: $entity_type->id() . '_revision';
+    }
+    if ($entity_type->isTranslatable()) {
+      $this->dataTable = $entity_type->getDataTable() ?: $entity_type->id() . '_field_data';
+    }
+    if ($entity_type->isRevisionable() && $entity_type->isTranslatable()) {
+      $this->revisionDataTable = $entity_type->getRevisionDataTable() ?: $entity_type->id() . '_field_revision';
+    }
+  }
+
+  /**
+   * Initializes the table mapping.
+   *
+   * @param \Drupal\Core\Entity\ContentEntityTypeInterface $entity_type
+   *   The entity type definition.
+   * @param \Drupal\Core\Field\FieldStorageDefinitionInterface[] $storage_definitions
+   *   A list of field storage definitions that should be available for the
+   *   field columns of this table mapping.
+   *
+   * @return static
+   *
+   * @internal
+   */
+  public static function create(ContentEntityTypeInterface $entity_type, array $storage_definitions) {
+    $table_mapping = new static($entity_type, $storage_definitions);
+
+    $revisionable = $entity_type->isRevisionable();
+    $translatable = $entity_type->isTranslatable();
+
+    $id_key = $entity_type->getKey('id');
+    $revision_key = $entity_type->getKey('revision');
+    $bundle_key = $entity_type->getKey('bundle');
+    $uuid_key = $entity_type->getKey('uuid');
+    $langcode_key = $entity_type->getKey('langcode');
+
+    $shared_table_definitions = array_filter($storage_definitions, function (FieldStorageDefinitionInterface $definition) use ($table_mapping) {
+      return $table_mapping->allowsSharedTableStorage($definition);
+    });
+
+    $key_fields = array_values(array_filter([$id_key, $revision_key, $bundle_key, $uuid_key, $langcode_key]));
+    $all_fields = array_keys($shared_table_definitions);
+    $revisionable_fields = array_keys(array_filter($shared_table_definitions, function (FieldStorageDefinitionInterface $definition) {
+      return $definition->isRevisionable();
+    }));
+    // Make sure the key fields come first in the list of fields.
+    $all_fields = array_merge($key_fields, array_diff($all_fields, $key_fields));
+
+    $revision_metadata_fields = $revisionable ? array_values($entity_type->getRevisionMetadataKeys()) : [];
+
+    if (!$revisionable && !$translatable) {
+      // The base layout stores all the base field values in the base table.
+      $table_mapping->setFieldNames($table_mapping->baseTable, $all_fields);
+    }
+    elseif ($revisionable && !$translatable) {
+      // The revisionable layout stores all the base field values in the base
+      // table, except for revision metadata fields. Revisionable fields
+      // denormalized in the base table but also stored in the revision table
+      // together with the entity ID and the revision ID as identifiers.
+      $table_mapping->setFieldNames($table_mapping->baseTable, array_diff($all_fields, $revision_metadata_fields));
+      $revision_key_fields = [$id_key, $revision_key];
+      $table_mapping->setFieldNames($table_mapping->revisionTable, array_merge($revision_key_fields, $revisionable_fields));
+    }
+    elseif (!$revisionable && $translatable) {
+      // Multilingual layouts store key field values in the base table. The
+      // other base field values are stored in the data table, no matter
+      // whether they are translatable or not. The data table holds also a
+      // denormalized copy of the bundle field value to allow for more
+      // performant queries. This means that only the UUID is not stored on
+      // the data table.
+      $table_mapping
+        ->setFieldNames($table_mapping->baseTable, $key_fields)
+        ->setFieldNames($table_mapping->dataTable, array_values(array_diff($all_fields, [$uuid_key])));
+    }
+    elseif ($revisionable && $translatable) {
+      // The revisionable multilingual layout stores key field values in the
+      // base table and the revision table holds the entity ID, revision ID and
+      // langcode ID along with revision metadata. The revision data table holds
+      // data field values for all the revisionable fields and the data table
+      // holds the data field values for all non-revisionable fields. The data
+      // field values of revisionable fields are denormalized in the data
+      // table, as well.
+      $table_mapping->setFieldNames($table_mapping->baseTable, $key_fields);
+
+      // Like in the multilingual, non-revisionable case the UUID is not
+      // in the data table. Additionally, do not store revision metadata
+      // fields in the data table.
+      $data_fields = array_values(array_diff($all_fields, [$uuid_key], $revision_metadata_fields));
+      $table_mapping->setFieldNames($table_mapping->dataTable, $data_fields);
+
+      $revision_base_fields = array_merge([$id_key, $revision_key, $langcode_key], $revision_metadata_fields);
+      $table_mapping->setFieldNames($table_mapping->revisionTable, $revision_base_fields);
+
+      $revision_data_key_fields = [$id_key, $revision_key, $langcode_key];
+      $revision_data_fields = array_diff($revisionable_fields, $revision_metadata_fields, [$langcode_key]);
+      $table_mapping->setFieldNames($table_mapping->revisionDataTable, array_merge($revision_data_key_fields, $revision_data_fields));
+    }
+
+    // Add dedicated tables.
+    $dedicated_table_definitions = array_filter($table_mapping->fieldStorageDefinitions, function (FieldStorageDefinitionInterface $definition) use ($table_mapping) {
+      return $table_mapping->requiresDedicatedTableStorage($definition);
+    });
+    $extra_columns = [
+      'bundle',
+      'deleted',
+      'entity_id',
+      'revision_id',
+      'langcode',
+      'delta',
+    ];
+    foreach ($dedicated_table_definitions as $field_name => $definition) {
+      $tables = [$table_mapping->getDedicatedDataTableName($definition)];
+      if ($revisionable && $definition->isRevisionable()) {
+        $tables[] = $table_mapping->getDedicatedRevisionTableName($definition);
+      }
+      foreach ($tables as $table_name) {
+        $table_mapping->setFieldNames($table_name, [$field_name]);
+        $table_mapping->setExtraColumns($table_name, $extra_columns);
+      }
+    }
+
+    return $table_mapping;
   }
 
   /**
@@ -143,17 +298,13 @@ public function getFieldTableName($field_name) {
       // where field data is stored, otherwise the base table is responsible for
       // storing field data. Revision metadata is an exception as it's stored
       // only in the revision table.
-      // @todo The table mapping itself should know about entity tables. See
-      //   https://www.drupal.org/node/2274017.
-      /** @var \Drupal\Core\Entity\Sql\SqlContentEntityStorage $storage */
-      $storage = \Drupal::entityManager()->getStorage($this->entityType->id());
       $storage_definition = $this->fieldStorageDefinitions[$field_name];
-      $table_names = [
-        $storage->getDataTable(),
-        $storage->getBaseTable(),
-        $storage->getRevisionTable(),
+      $table_names = array_filter([
+        $this->dataTable,
+        $this->baseTable,
+        $this->revisionTable,
         $this->getDedicatedDataTableName($storage_definition),
-      ];
+      ]);
 
       // Collect field columns.
       $field_columns = [];
@@ -161,7 +312,7 @@ public function getFieldTableName($field_name) {
         $field_columns[] = $this->getFieldColumnName($storage_definition, $property_name);
       }
 
-      foreach (array_filter($table_names) as $table_name) {
+      foreach ($table_names as $table_name) {
         $columns = $this->getAllColumns($table_name);
         // We assume finding one field column belonging to the mapping is enough
         // to identify the field table.
@@ -227,6 +378,10 @@ public function getFieldColumnName(FieldStorageDefinitionInterface $storage_defi
    *   A list of field names to add the columns for.
    *
    * @return $this
+   *
+   * @deprecated in Drupal 8.6.0 and will be changed to a protected method
+   *   before Drupal 9.0.0. There will be no replacement for it because the
+   *   default table mapping is now able to be initialized on its own.
    */
   public function setFieldNames($table_name, array $field_names) {
     $this->fieldNames[$table_name] = $field_names;
@@ -254,6 +409,10 @@ public function getExtraColumns($table_name) {
    *   The list of column names.
    *
    * @return $this
+   *
+   * @deprecated in Drupal 8.6.0 and will be changed to a protected method
+   *   before Drupal 9.0.0. There will be no replacement for it because the
+   *   default table mapping is now able to be initialized on its own.
    */
   public function setExtraColumns($table_name, array $column_names) {
     $this->extraColumns[$table_name] = $column_names;
diff --git a/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php
index f235ecdad0865afe185ec187470b300d173688a5..3fefd5e763fd713235ea50b73748c70297ff91cf 100644
--- a/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php
+++ b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php
@@ -307,102 +307,12 @@ public function getTableMapping(array $storage_definitions = NULL) {
     // case, we can statically cache the computed table mapping. If a new set
     // of field storage definitions is passed, for instance when comparing old
     // and new storage schema, we compute the table mapping without caching.
-    // @todo Clean-up this in https://www.drupal.org/node/2274017 so we can
-    //   easily instantiate a new table mapping whenever needed.
     if (!isset($this->tableMapping) || $storage_definitions) {
       $table_mapping_class = $this->temporary ? TemporaryTableMapping::class : DefaultTableMapping::class;
       $definitions = $storage_definitions ?: $this->entityManager->getFieldStorageDefinitions($this->entityTypeId);
-      /** @var \Drupal\Core\Entity\Sql\DefaultTableMapping|\Drupal\Core\Entity\Sql\TemporaryTableMapping $table_mapping */
-      $table_mapping = new $table_mapping_class($this->entityType, $definitions);
-
-      $shared_table_definitions = array_filter($definitions, function (FieldStorageDefinitionInterface $definition) use ($table_mapping) {
-        return $table_mapping->allowsSharedTableStorage($definition);
-      });
-
-      $key_fields = array_values(array_filter([$this->idKey, $this->revisionKey, $this->bundleKey, $this->uuidKey, $this->langcodeKey]));
-      $all_fields = array_keys($shared_table_definitions);
-      $revisionable_fields = array_keys(array_filter($shared_table_definitions, function (FieldStorageDefinitionInterface $definition) {
-        return $definition->isRevisionable();
-      }));
-      // Make sure the key fields come first in the list of fields.
-      $all_fields = array_merge($key_fields, array_diff($all_fields, $key_fields));
-
-      // If the entity is revisionable, gather the fields that need to be put
-      // in the revision table.
-      $revisionable = $this->entityType->isRevisionable();
-      $revision_metadata_fields = $revisionable ? array_values($this->entityType->getRevisionMetadataKeys()) : [];
-
-      $translatable = $this->entityType->isTranslatable();
-      if (!$revisionable && !$translatable) {
-        // The base layout stores all the base field values in the base table.
-        $table_mapping->setFieldNames($this->baseTable, $all_fields);
-      }
-      elseif ($revisionable && !$translatable) {
-        // The revisionable layout stores all the base field values in the base
-        // table, except for revision metadata fields. Revisionable fields
-        // denormalized in the base table but also stored in the revision table
-        // together with the entity ID and the revision ID as identifiers.
-        $table_mapping->setFieldNames($this->baseTable, array_diff($all_fields, $revision_metadata_fields));
-        $revision_key_fields = [$this->idKey, $this->revisionKey];
-        $table_mapping->setFieldNames($this->revisionTable, array_merge($revision_key_fields, $revisionable_fields));
-      }
-      elseif (!$revisionable && $translatable) {
-        // Multilingual layouts store key field values in the base table. The
-        // other base field values are stored in the data table, no matter
-        // whether they are translatable or not. The data table holds also a
-        // denormalized copy of the bundle field value to allow for more
-        // performant queries. This means that only the UUID is not stored on
-        // the data table.
-        $table_mapping
-          ->setFieldNames($this->baseTable, $key_fields)
-          ->setFieldNames($this->dataTable, array_values(array_diff($all_fields, [$this->uuidKey])));
-      }
-      elseif ($revisionable && $translatable) {
-        // The revisionable multilingual layout stores key field values in the
-        // base table, except for language, which is stored in the revision
-        // table along with revision metadata. The revision data table holds
-        // data field values for all the revisionable fields and the data table
-        // holds the data field values for all non-revisionable fields. The data
-        // field values of revisionable fields are denormalized in the data
-        // table, as well.
-        $table_mapping->setFieldNames($this->baseTable, array_values($key_fields));
-
-        // Like in the multilingual, non-revisionable case the UUID is not
-        // in the data table. Additionally, do not store revision metadata
-        // fields in the data table.
-        $data_fields = array_values(array_diff($all_fields, [$this->uuidKey], $revision_metadata_fields));
-        $table_mapping->setFieldNames($this->dataTable, $data_fields);
-
-        $revision_base_fields = array_merge([$this->idKey, $this->revisionKey, $this->langcodeKey], $revision_metadata_fields);
-        $table_mapping->setFieldNames($this->revisionTable, $revision_base_fields);
-
-        $revision_data_key_fields = [$this->idKey, $this->revisionKey, $this->langcodeKey];
-        $revision_data_fields = array_diff($revisionable_fields, $revision_metadata_fields, [$this->langcodeKey]);
-        $table_mapping->setFieldNames($this->revisionDataTable, array_merge($revision_data_key_fields, $revision_data_fields));
-      }
 
-      // Add dedicated tables.
-      $dedicated_table_definitions = array_filter($definitions, function (FieldStorageDefinitionInterface $definition) use ($table_mapping) {
-        return $table_mapping->requiresDedicatedTableStorage($definition);
-      });
-      $extra_columns = [
-        'bundle',
-        'deleted',
-        'entity_id',
-        'revision_id',
-        'langcode',
-        'delta',
-      ];
-      foreach ($dedicated_table_definitions as $field_name => $definition) {
-        $tables = [$table_mapping->getDedicatedDataTableName($definition)];
-        if ($revisionable && $definition->isRevisionable()) {
-          $tables[] = $table_mapping->getDedicatedRevisionTableName($definition);
-        }
-        foreach ($tables as $table_name) {
-          $table_mapping->setFieldNames($table_name, [$field_name]);
-          $table_mapping->setExtraColumns($table_name, $extra_columns);
-        }
-      }
+      /** @var \Drupal\Core\Entity\Sql\DefaultTableMapping|\Drupal\Core\Entity\Sql\TemporaryTableMapping $table_mapping */
+      $table_mapping = $table_mapping_class::create($this->entityType, $definitions);
 
       // Cache the computed table mapping only if we are using our internal
       // storage definitions.
diff --git a/core/tests/Drupal/Tests/Core/Entity/Sql/DefaultTableMappingTest.php b/core/tests/Drupal/Tests/Core/Entity/Sql/DefaultTableMappingTest.php
index 39c9a053c845e884dd8bbd728fb2deed3f28a9d2..e9a306dd4cdc639a869f7e9ccd257409296b2453 100644
--- a/core/tests/Drupal/Tests/Core/Entity/Sql/DefaultTableMappingTest.php
+++ b/core/tests/Drupal/Tests/Core/Entity/Sql/DefaultTableMappingTest.php
@@ -362,38 +362,35 @@ public function testGetFieldTableName($table_names, $expected) {
       ->method('getColumns')
       ->willReturn($columns);
 
-    $storage = $this->getMockBuilder('\Drupal\Core\Entity\Sql\SqlContentEntityStorage')
-      ->disableOriginalConstructor()
-      ->getMock();
-
-    $storage
+    $this->entityType
       ->expects($this->any())
       ->method('getBaseTable')
-      ->willReturn(isset($table_names['base']) ? $table_names['base'] : 'base_table');
+      ->willReturn(isset($table_names['base']) ? $table_names['base'] : 'entity_test');
 
-    $storage
+    $this->entityType
       ->expects($this->any())
       ->method('getDataTable')
-      ->willReturn(isset($table_names['data']) ? $table_names['data'] : NULL);
+      ->willReturn(isset($table_names['data']) ? $table_names['data'] : FALSE);
 
-    $storage
+    $this->entityType
       ->expects($this->any())
       ->method('getRevisionTable')
-      ->willReturn(isset($table_names['revision']) ? $table_names['revision'] : NULL);
+      ->willReturn(isset($table_names['revision']) ? $table_names['revision'] : FALSE);
 
-    $entity_manager = $this->getMock('\Drupal\Core\Entity\EntityManagerInterface');
-    $entity_manager
+    $this->entityType
       ->expects($this->any())
-      ->method('getStorage')
-      ->willReturn($storage);
+      ->method('isTranslatable')
+      ->willReturn(isset($table_names['data']));
 
-    $container = $this->getMock('\Symfony\Component\DependencyInjection\ContainerInterface');
-    $container
+    $this->entityType
       ->expects($this->any())
-      ->method('get')
-      ->willReturn($entity_manager);
+      ->method('isRevisionable')
+      ->willReturn(isset($table_names['revision']));
 
-    \Drupal::setContainer($container);
+    $this->entityType
+      ->expects($this->any())
+      ->method('getRevisionMetadataKeys')
+      ->willReturn([]);
 
     $table_mapping = new DefaultTableMapping($this->entityType, [$field_name => $definition]);
 
diff --git a/core/tests/Drupal/Tests/Core/Entity/Sql/SqlContentEntityStorageSchemaTest.php b/core/tests/Drupal/Tests/Core/Entity/Sql/SqlContentEntityStorageSchemaTest.php
index d16ece2097dd72e784fcf054f645e7eb53f97c10..5643b15ca3e6465308b1fa4f53cbc2c56c483176 100644
--- a/core/tests/Drupal/Tests/Core/Entity/Sql/SqlContentEntityStorageSchemaTest.php
+++ b/core/tests/Drupal/Tests/Core/Entity/Sql/SqlContentEntityStorageSchemaTest.php
@@ -389,13 +389,22 @@ public function testGetSchemaBase() {
    * @covers ::processIdentifierSchema
    */
   public function testGetSchemaRevisionable() {
-    $this->entityType = new ContentEntityType([
-      'id' => 'entity_test',
-      'entity_keys' => [
-        'id' => 'id',
-        'revision' => 'revision_id',
-      ],
-    ]);
+    $this->entityType = $this->getMockBuilder('Drupal\Core\Entity\ContentEntityType')
+      ->setConstructorArgs([
+        [
+          'id' => 'entity_test',
+          'entity_keys' => [
+            'id' => 'id',
+            'revision' => 'revision_id',
+          ],
+        ]
+      ])
+      ->setMethods(['getRevisionMetadataKeys'])
+      ->getMock();
+
+    $this->entityType->expects($this->any())
+      ->method('getRevisionMetadataKeys')
+      ->will($this->returnValue([]));
 
     $this->storage->expects($this->exactly(2))
       ->method('getRevisionTable')
@@ -595,14 +604,23 @@ public function testGetSchemaTranslatable() {
    * @covers ::processRevisionDataTable
    */
   public function testGetSchemaRevisionableTranslatable() {
-    $this->entityType = new ContentEntityType([
-      'id' => 'entity_test',
-      'entity_keys' => [
-        'id' => 'id',
-        'revision' => 'revision_id',
-        'langcode' => 'langcode',
-      ],
-    ]);
+    $this->entityType = $this->getMockBuilder('Drupal\Core\Entity\ContentEntityType')
+      ->setConstructorArgs([
+        [
+          'id' => 'entity_test',
+          'entity_keys' => [
+            'id' => 'id',
+            'revision' => 'revision_id',
+            'langcode' => 'langcode',
+          ],
+        ]
+      ])
+      ->setMethods(['getRevisionMetadataKeys'])
+      ->getMock();
+
+    $this->entityType->expects($this->any())
+      ->method('getRevisionMetadataKeys')
+      ->will($this->returnValue([]));
 
     $this->storage->expects($this->exactly(3))
       ->method('getRevisionTable')
diff --git a/core/tests/Drupal/Tests/Core/Entity/Sql/SqlContentEntityStorageTest.php b/core/tests/Drupal/Tests/Core/Entity/Sql/SqlContentEntityStorageTest.php
index bf3fd46eb6a331eba92983524c95fc809eee4103..964020cf5ca8a53c50d652c134eaf3e6b7ead4b4 100644
--- a/core/tests/Drupal/Tests/Core/Entity/Sql/SqlContentEntityStorageTest.php
+++ b/core/tests/Drupal/Tests/Core/Entity/Sql/SqlContentEntityStorageTest.php
@@ -205,6 +205,9 @@ public function testGetRevisionTable($revision_table, $expected) {
     $this->entityType->expects($this->once())
       ->method('getRevisionTable')
       ->will($this->returnValue($revision_table));
+    $this->entityType->expects($this->any())
+      ->method('getRevisionMetadataKeys')
+      ->willReturn([]);
 
     $this->setUpEntityStorage();
 
@@ -243,6 +246,9 @@ public function testGetDataTable() {
     $this->entityType->expects($this->exactly(1))
       ->method('getDataTable')
       ->will($this->returnValue('entity_test_field_data'));
+    $this->entityType->expects($this->any())
+      ->method('getRevisionMetadataKeys')
+      ->willReturn([]);
 
     $this->setUpEntityStorage();
 
@@ -276,6 +282,9 @@ public function testGetRevisionDataTable($revision_data_table, $expected) {
     $this->entityType->expects($this->once())
       ->method('getRevisionDataTable')
       ->will($this->returnValue($revision_data_table));
+    $this->entityType->expects($this->any())
+      ->method('getRevisionMetadataKeys')
+      ->willReturn([]);
 
     $this->setUpEntityStorage();
 
@@ -533,7 +542,7 @@ public function testGetTableMappingRevisionable(array $entity_keys) {
       'uuid' => $entity_keys['uuid'],
     ];
 
-    $this->entityType->expects($this->exactly(2))
+    $this->entityType->expects($this->exactly(4))
       ->method('isRevisionable')
       ->will($this->returnValue(TRUE));
     $this->entityType->expects($this->any())
@@ -606,7 +615,7 @@ public function testGetTableMappingRevisionableWithFields(array $entity_keys) {
       $field_names = array_merge($field_names, $revisionable_field_names);
       $this->fieldDefinitions += $this->mockFieldDefinitions(array_merge($revisionable_field_names, array_values($revision_metadata_field_names)), ['isRevisionable' => TRUE]);
 
-      $this->entityType->expects($this->exactly(2))
+      $this->entityType->expects($this->exactly(4))
         ->method('isRevisionable')
         ->will($this->returnValue(TRUE));
       $this->entityType->expects($this->any())