diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php
index 48aa9533e810ee7431a9a3c202533a18c9bab1bb..cd5090cc93ce0c8a3e3a98030f5055642a77ef32 100644
--- a/lib/composer/composer/autoload_classmap.php
+++ b/lib/composer/composer/autoload_classmap.php
@@ -23,6 +23,7 @@ return array(
     'OCP\\AppFramework\\Db\\Entity' => $baseDir . '/lib/public/AppFramework/Db/Entity.php',
     'OCP\\AppFramework\\Db\\Mapper' => $baseDir . '/lib/public/AppFramework/Db/Mapper.php',
     'OCP\\AppFramework\\Db\\MultipleObjectsReturnedException' => $baseDir . '/lib/public/AppFramework/Db/MultipleObjectsReturnedException.php',
+    'OCP\\AppFramework\\Db\\QBMapper' => $baseDir . '/lib/public/AppFramework/Db/QBMapper.php',
     'OCP\\AppFramework\\Http' => $baseDir . '/lib/public/AppFramework/Http.php',
     'OCP\\AppFramework\\Http\\ContentSecurityPolicy' => $baseDir . '/lib/public/AppFramework/Http/ContentSecurityPolicy.php',
     'OCP\\AppFramework\\Http\\DataDisplayResponse' => $baseDir . '/lib/public/AppFramework/Http/DataDisplayResponse.php',
diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php
index 0ed2d0a763236dee29141aef7b8fc127c7b1dee1..4e47536eacc5288fdf40225d553459b496a75a43 100644
--- a/lib/composer/composer/autoload_static.php
+++ b/lib/composer/composer/autoload_static.php
@@ -53,6 +53,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
         'OCP\\AppFramework\\Db\\Entity' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Db/Entity.php',
         'OCP\\AppFramework\\Db\\Mapper' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Db/Mapper.php',
         'OCP\\AppFramework\\Db\\MultipleObjectsReturnedException' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Db/MultipleObjectsReturnedException.php',
+        'OCP\\AppFramework\\Db\\QBMapper' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Db/QBMapper.php',
         'OCP\\AppFramework\\Http' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Http.php',
         'OCP\\AppFramework\\Http\\ContentSecurityPolicy' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Http/ContentSecurityPolicy.php',
         'OCP\\AppFramework\\Http\\DataDisplayResponse' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Http/DataDisplayResponse.php',
diff --git a/lib/private/Authentication/Token/DefaultTokenMapper.php b/lib/private/Authentication/Token/DefaultTokenMapper.php
index 41d1b9f203db9ceebe7641288c9e70a36c41490d..55494d7237057e9d14e1f07fff1698418ac7b3ad 100644
--- a/lib/private/Authentication/Token/DefaultTokenMapper.php
+++ b/lib/private/Authentication/Token/DefaultTokenMapper.php
@@ -30,11 +30,12 @@ namespace OC\Authentication\Token;
 
 use OCP\AppFramework\Db\DoesNotExistException;
 use OCP\AppFramework\Db\Mapper;
+use OCP\AppFramework\Db\QBMapper;
 use OCP\DB\QueryBuilder\IQueryBuilder;
 use OCP\IDBConnection;
 use OCP\IUser;
 
-class DefaultTokenMapper extends Mapper {
+class DefaultTokenMapper extends QBMapper {
 
 	public function __construct(IDBConnection $db) {
 		parent::__construct($db, 'authtoken');
diff --git a/lib/public/AppFramework/Db/Mapper.php b/lib/public/AppFramework/Db/Mapper.php
index b008702ba542989896467e1217d5bb3080ba3b69..6910757add0daf271df74f8e04343cc192d05bf5 100644
--- a/lib/public/AppFramework/Db/Mapper.php
+++ b/lib/public/AppFramework/Db/Mapper.php
@@ -34,6 +34,7 @@ use OCP\IDBConnection;
  * Simple parent class for inheriting your data access layer from. This class
  * may be subject to change in the future
  * @since 7.0.0
+ * @deprecated 14.0.0 Move over to QBMapper
  */
 abstract class Mapper {
 
@@ -47,6 +48,7 @@ abstract class Mapper {
 	 * @param string $entityClass the name of the entity that the sql should be
 	 * mapped to queries without using sql
 	 * @since 7.0.0
+	 * @deprecated 14.0.0 Move over to QBMapper
 	 */
 	public function __construct(IDBConnection $db, $tableName, $entityClass=null){
 		$this->db = $db;
@@ -65,6 +67,7 @@ abstract class Mapper {
 	/**
 	 * @return string the table name
 	 * @since 7.0.0
+	 * @deprecated 14.0.0 Move over to QBMapper
 	 */
 	public function getTableName(){
 		return $this->tableName;
@@ -76,6 +79,7 @@ abstract class Mapper {
 	 * @param Entity $entity the entity that should be deleted
 	 * @return Entity the deleted entity
 	 * @since 7.0.0 - return value added in 8.1.0
+	 * @deprecated 14.0.0 Move over to QBMapper
 	 */
 	public function delete(Entity $entity){
 		$sql = 'DELETE FROM `' . $this->tableName . '` WHERE `id` = ?';
@@ -90,6 +94,7 @@ abstract class Mapper {
 	 * @param Entity $entity the entity that should be created
 	 * @return Entity the saved entity with the set id
 	 * @since 7.0.0
+	 * @deprecated 14.0.0 Move over to QBMapper
 	 */
 	public function insert(Entity $entity){
 		// get updated fields to save, fields have to be set using a setter to
@@ -139,6 +144,7 @@ abstract class Mapper {
 	 * @param Entity $entity the entity that should be created
 	 * @return Entity the saved entity with the set id
 	 * @since 7.0.0 - return value was added in 8.0.0
+	 * @deprecated 14.0.0 Move over to QBMapper
 	 */
 	public function update(Entity $entity){
 		// if entity wasn't changed it makes no sense to run a db query
@@ -195,6 +201,7 @@ abstract class Mapper {
 	 * @param array $array
 	 * @return bool true if associative
 	 * @since 8.1.0
+	 * @deprecated 14.0.0 Move over to QBMapper
 	 */
 	private function isAssocArray(array $array) {
 		return array_values($array) !== $array;
@@ -205,6 +212,7 @@ abstract class Mapper {
 	 * @param $value
 	 * @return int PDO constant
 	 * @since 8.1.0
+	 * @deprecated 14.0.0 Move over to QBMapper
 	 */
 	private function getPDOType($value) {
 		switch (gettype($value)) {
@@ -226,6 +234,7 @@ abstract class Mapper {
 	 * @param int $offset from which row we want to start
 	 * @return \PDOStatement the database query result
 	 * @since 7.0.0
+	 * @deprecated 14.0.0 Move over to QBMapper
 	 */
 	protected function execute($sql, array $params=[], $limit=null, $offset=null){
 		$query = $this->db->prepare($sql, $limit, $offset);
@@ -249,7 +258,6 @@ abstract class Mapper {
 		return $query;
 	}
 
-
 	/**
 	 * Returns an db result and throws exceptions when there are more or less
 	 * results
@@ -262,6 +270,7 @@ abstract class Mapper {
 	 * @throws MultipleObjectsReturnedException if more than one item exist
 	 * @return array the result as row
 	 * @since 7.0.0
+	 * @deprecated 14.0.0 Move over to QBMapper
 	 */
 	protected function findOneQuery($sql, array $params=[], $limit=null, $offset=null){
 		$stmt = $this->execute($sql, $params, $limit, $offset);
@@ -297,6 +306,7 @@ abstract class Mapper {
 	 * @param int $offset from which row we want to start
 	 * @return string formatted error message string
 	 * @since 9.1.0
+	 * @deprecated 14.0.0 Move over to QBMapper
 	 */
 	private function buildDebugMessage($msg, $sql, array $params=[], $limit=null, $offset=null) {
 		return $msg .
@@ -313,6 +323,7 @@ abstract class Mapper {
 	 * @param array $row the row which should be converted to an entity
 	 * @return Entity the entity
 	 * @since 7.0.0
+	 * @deprecated 14.0.0 Move over to QBMapper
 	 */
 	protected function mapRowToEntity($row) {
 		return call_user_func($this->entityClass .'::fromRow', $row);
@@ -327,6 +338,7 @@ abstract class Mapper {
 	 * @param int $offset from which row we want to start
 	 * @return array all fetched entities
 	 * @since 7.0.0
+	 * @deprecated 14.0.0 Move over to QBMapper
 	 */
 	protected function findEntities($sql, array $params=[], $limit=null, $offset=null) {
 		$stmt = $this->execute($sql, $params, $limit, $offset);
@@ -354,6 +366,7 @@ abstract class Mapper {
 	 * @throws MultipleObjectsReturnedException if more than one item exist
 	 * @return Entity the entity
 	 * @since 7.0.0
+	 * @deprecated 14.0.0 Move over to QBMapper
 	 */
 	protected function findEntity($sql, array $params=[], $limit=null, $offset=null){
 		return $this->mapRowToEntity($this->findOneQuery($sql, $params, $limit, $offset));
diff --git a/lib/public/AppFramework/Db/QBMapper.php b/lib/public/AppFramework/Db/QBMapper.php
new file mode 100644
index 0000000000000000000000000000000000000000..a9b38732a3033219be5ee0ecd5247675edf73d96
--- /dev/null
+++ b/lib/public/AppFramework/Db/QBMapper.php
@@ -0,0 +1,272 @@
+<?php
+declare(strict_types=1);
+/**
+ * @copyright 2018, Roeland Jago Douma <roeland@famdouma.nl>
+ *
+ * @author Roeland Jago Douma <roeland@famdouma.nl>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OCP\AppFramework\Db;
+
+use OCP\DB\QueryBuilder\IQueryBuilder;
+use OCP\IDBConnection;
+
+/**
+ * Simple parent class for inheriting your data access layer from. This class
+ * may be subject to change in the future
+ *
+ * @since 14.0.0
+ */
+abstract class QBMapper {
+
+	/** @var string */
+	protected $tableName;
+
+	/** @var string */
+	protected $entityClass;
+
+	/** @var IDBConnection */
+	protected $db;
+
+	/**
+	 * @param IDBConnection $db Instance of the Db abstraction layer
+	 * @param string $tableName the name of the table. set this to allow entity
+	 * @param string $entityClass the name of the entity that the sql should be
+	 * mapped to queries without using sql
+	 * @since 14.0.0
+	 */
+	public function __construct(IDBConnection $db, string $tableName, string $entityClass=null){
+		$this->db = $db;
+		$this->tableName = $tableName;
+
+		// if not given set the entity name to the class without the mapper part
+		// cache it here for later use since reflection is slow
+		if($entityClass === null) {
+			$this->entityClass = str_replace('Mapper', '', \get_class($this));
+		} else {
+			$this->entityClass = $entityClass;
+		}
+	}
+
+
+	/**
+	 * @return string the table name
+	 * @since 14.0.0
+	 */
+	public function getTableName(): string {
+		return $this->tableName;
+	}
+
+
+	/**
+	 * Deletes an entity from the table
+	 * @param Entity $entity the entity that should be deleted
+	 * @return Entity the deleted entity
+	 * @since 14.0.0
+	 */
+	public function delete(Entity $entity): Entity {
+		$qb = $this->db->getQueryBuilder();
+
+		$qb->delete($this->tableName)
+			->where(
+				$qb->expr()->eq('id', $qb->createNamedParameter($entity->getId()))
+			);
+		$qb->execute();
+		return $entity;
+	}
+
+
+	/**
+	 * Creates a new entry in the db from an entity
+	 * @param Entity $entity the entity that should be created
+	 * @return Entity the saved entity with the set id
+	 * @since 14.0.0
+	 * @suppress SqlInjectionChecker
+	 */
+	public function insert(Entity $entity): Entity {
+		// get updated fields to save, fields have to be set using a setter to
+		// be saved
+		$properties = $entity->getUpdatedFields();
+
+		$qb = $this->db->getQueryBuilder();
+		$qb->insert($this->tableName);
+
+		// build the fields
+		foreach($properties as $property => $updated) {
+			$column = $entity->propertyToColumn($property);
+			$getter = 'get' . ucfirst($property);
+			$value = $entity->$getter();
+
+			$qb->setValue($column, $qb->createNamedParameter($value));
+		}
+
+		$qb->execute();
+
+		$entity->setId((int) $qb->getLastInsertId());
+
+		return $entity;
+	}
+
+
+
+	/**
+	 * Updates an entry in the db from an entity
+	 * @throws \InvalidArgumentException if entity has no id
+	 * @param Entity $entity the entity that should be created
+	 * @return Entity the saved entity with the set id
+	 * @since 14.0.0
+	 * @suppress SqlInjectionChecker
+	 */
+	public function update(Entity $entity): Entity {
+		// if entity wasn't changed it makes no sense to run a db query
+		$properties = $entity->getUpdatedFields();
+		if(\count($properties) === 0) {
+			return $entity;
+		}
+
+		// entity needs an id
+		$id = $entity->getId();
+		if($id === null){
+			throw new \InvalidArgumentException(
+				'Entity which should be updated has no id');
+		}
+
+		// get updated fields to save, fields have to be set using a setter to
+		// be saved
+		// do not update the id field
+		unset($properties['id']);
+
+		$qb = $this->db->getQueryBuilder();
+		$qb->update($this->tableName);
+
+		// build the fields
+		foreach($properties as $property => $updated) {
+			$column = $entity->propertyToColumn($property);
+			$getter = 'get' . ucfirst($property);
+			$value = $entity->$getter();
+
+			$qb->set($column, $qb->createNamedParameter($value));
+		}
+
+		$qb->where(
+			$qb->expr()->eq('id', $qb->createNamedParameter($id))
+		);
+		$qb->execute();
+
+		return $entity;
+	}
+
+	/**
+	 * Returns an db result and throws exceptions when there are more or less
+	 * results
+	 *
+	 * @see findEntity
+	 *
+	 * @param IQueryBuilder $query
+	 * @throws DoesNotExistException if the item does not exist
+	 * @throws MultipleObjectsReturnedException if more than one item exist
+	 * @return array the result as row
+	 * @since 14.0.0
+	 */
+	protected function findOneQuery(IQueryBuilder $query): array {
+		$cursor = $query->execute();
+
+		$row = $cursor->fetch();
+		if($row === false) {
+			$cursor->closeCursor();
+			$msg = $this->buildDebugMessage(
+				'Did expect one result but found none when executing', $query
+			);
+			throw new DoesNotExistException($msg);
+		}
+
+		$row2 = $cursor->fetch();
+		$cursor->closeCursor();
+		if($row2 !== false ) {
+			$msg = $this->buildDebugMessage(
+				'Did not expect more than one result when executing', $query
+			);
+			throw new MultipleObjectsReturnedException($msg);
+		}
+
+		return $row;
+	}
+
+	/**
+	 * @param string $msg
+	 * @param IQueryBuilder $sql
+	 * @return string
+	 * @since 14.0.0
+	 */
+	private function buildDebugMessage(string $msg, IQueryBuilder $sql): string {
+		return $msg .
+			': query "' . $sql->getSQL() . '"; ';
+	}
+
+
+	/**
+	 * Creates an entity from a row. Automatically determines the entity class
+	 * from the current mapper name (MyEntityMapper -> MyEntity)
+	 *
+	 * @param array $row the row which should be converted to an entity
+	 * @return Entity the entity
+	 * @since 14.0.0
+	 */
+	protected function mapRowToEntity(array $row): Entity {
+		return \call_user_func($this->entityClass .'::fromRow', $row);
+	}
+
+
+	/**
+	 * Runs a sql query and returns an array of entities
+	 *
+	 * @param IQueryBuilder $query
+	 * @return Entity[] all fetched entities
+	 * @since 14.0.0
+	 */
+	protected function findEntities(IQueryBuilder $query): array {
+		$cursor = $query->execute();
+
+		$entities = [];
+
+		while($row = $cursor->fetch()){
+			$entities[] = $this->mapRowToEntity($row);
+		}
+
+		$cursor->closeCursor();
+
+		return $entities;
+	}
+
+
+	/**
+	 * Returns an db result and throws exceptions when there are more or less
+	 * results
+	 *
+	 * @param IQueryBuilder $query
+	 * @throws DoesNotExistException if the item does not exist
+	 * @throws MultipleObjectsReturnedException if more than one item exist
+	 * @return Entity the entity
+	 * @since 14.0.0
+	 */
+	protected function findEntity(IQueryBuilder $query): Entity {
+		return $this->mapRowToEntity($this->findOneQuery($query));
+	}
+
+}