diff --git a/lib/private/server.php b/lib/private/server.php
index 7fa06298b29293bb0c51469b2bb802331887660f..ff34cfdccb6df283d1d8785a0f4dfc0bbcb53b64 100644
--- a/lib/private/server.php
+++ b/lib/private/server.php
@@ -14,6 +14,7 @@ use OC\Security\Crypto;
 use OC\Security\SecureRandom;
 use OCP\IServerContainer;
 use OCP\ISession;
+use OC\Tagging\TagMapper;
 
 /**
  * Class Server
@@ -68,9 +69,13 @@ class Server extends SimpleContainer implements IServerContainer {
 		$this->registerService('PreviewManager', function ($c) {
 			return new PreviewManager();
 		});
+		$this->registerService('TagMapper', function($c) {
+			return new TagMapper($c->getDb());
+		});
 		$this->registerService('TagManager', function ($c) {
+			$tagMapper = $c->query('TagMapper');
 			$user = \OC_User::getUser();
-			return new TagManager($user);
+			return new TagManager($tagMapper, $user);
 		});
 		$this->registerService('RootFolder', function ($c) {
 			// TODO: get user and user manager from container as well
diff --git a/lib/private/tagging/tag.php b/lib/private/tagging/tag.php
new file mode 100644
index 0000000000000000000000000000000000000000..d0cd6bbb966a50955a1145b9625ed50a1d9708ed
--- /dev/null
+++ b/lib/private/tagging/tag.php
@@ -0,0 +1,85 @@
+<?php
+/**
+* ownCloud - Tag class
+*
+* @author Bernhard Reiter
+* @copyright 2014 Bernhard Reiter <ockham@raz.or.at>
+*
+* This library is free software; you can redistribute it and/or
+* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+* License as published by the Free Software Foundation; either
+* version 3 of the License, or any later version.
+*
+* This library 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 library.  If not, see <http://www.gnu.org/licenses/>.
+*
+*/
+
+namespace OC\Tagging;
+
+use \OCP\AppFramework\Db\Entity;
+
+/**
+ * Class to represent a tag.
+ *
+ * @method string getOwner()
+ * @method void setOwner(string $owner)
+ * @method string getType()
+ * @method void setType(string $type)
+ * @method string getName()
+ * @method void setName(string $name)
+ */
+class Tag extends Entity {
+
+	protected $owner;
+	protected $type;
+	protected $name;
+
+	/**
+	* Constructor.
+	*
+	* @param string $owner The tag's owner
+	* @param string $type The type of item this tag is used for
+	* @param string $name The tag's name
+	*/
+	public function __construct($owner = null, $type = null, $name = null) {
+		$this->setOwner($owner);
+		$this->setType($type);
+		$this->setName($name);
+	}
+
+	/**
+	 * Transform a database columnname to a property
+	 * @param string $columnName the name of the column
+	 * @return string the property name
+	 */
+	public function columnToProperty($columnName){
+		if ($columnName === 'category') {
+		    return 'name';
+		} elseif ($columnName === 'uid') {
+		    return 'owner';
+		} else {
+		    return parent::columnToProperty($columnName);
+		}
+	}
+
+	/**
+	 * Transform a property to a database column name
+	 * @param string $property the name of the property
+	 * @return string the column name
+	 */
+	public function propertyToColumn($property){
+		if ($property === 'name') {
+		    return 'category';
+		} elseif ($property === 'owner') {
+		    return 'uid';
+		} else {
+		    return parent::propertyToColumn($property);
+		}
+	}
+}
diff --git a/lib/private/tagging/tagmapper.php b/lib/private/tagging/tagmapper.php
new file mode 100644
index 0000000000000000000000000000000000000000..b5929e2618c3b10bb0485fa7a840a8859a13af1d
--- /dev/null
+++ b/lib/private/tagging/tagmapper.php
@@ -0,0 +1,77 @@
+<?php
+/**
+* ownCloud - TagMapper class
+*
+* @author Bernhard Reiter
+* @copyright 2014 Bernhard Reiter <ockham@raz.or.at>
+*
+* This library is free software; you can redistribute it and/or
+* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+* License as published by the Free Software Foundation; either
+* version 3 of the License, or any later version.
+*
+* This library 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 library.  If not, see <http://www.gnu.org/licenses/>.
+*
+*/
+
+namespace OC\Tagging;
+
+use \OCP\AppFramework\Db\Mapper,
+    \OCP\AppFramework\Db\DoesNotExistException,
+    \OCP\IDb;
+
+/**
+ * Thin wrapper around \OCP\AppFramework\Db\Mapper.
+ */
+class TagMapper extends Mapper {
+
+	/**
+	* Constructor.
+	*
+	* @param IDb $db Instance of the Db abstraction layer.
+	*/
+	public function __construct(IDb $db) {
+		parent::__construct($db, 'vcategory', 'OC\Tagging\Tag');
+	}
+
+	/**
+	* Load tags from the database.
+	*
+	* @param array|string $owners The user(s) whose tags we are going to load.
+	* @param string $type The type of item for which we are loading tags.
+	* @return array An array of Tag objects.
+	*/
+	public function loadTags($owners, $type) {
+		if(!is_array($owners)) {
+			$owners = array($owners);
+		}
+
+		$sql = 'SELECT `id`, `uid`, `type`, `category` FROM `' . $this->getTableName() . '` '
+			. 'WHERE `uid` IN (' . str_repeat('?,', count($owners)-1) . '?) AND `type` = ? ORDER BY `category`';
+		return $this->findEntities($sql, array_merge($owners, array($type)));
+	}
+
+	/**
+	* Check if a given Tag object already exists in the database.
+	*
+	* @param Tag $tag The tag to look for in the database.
+	* @return bool
+	*/
+	public function tagExists($tag) {
+		$sql = 'SELECT `id`, `uid`, `type`, `category` FROM `' . $this->getTableName() . '` '
+			. 'WHERE `uid` = ? AND `type` = ? AND `category` = ?';
+		try {
+			$this->findEntity($sql, array($tag->getOwner(), $tag->getType(), $tag->getName()));
+		} catch (DoesNotExistException $e) {
+			return false;
+		}
+		return true;
+	}
+}
+
diff --git a/lib/private/tagmanager.php b/lib/private/tagmanager.php
index 72648e9b932ac4799cd8d7e21712fa5bf3e99719..7a3216de032fd2b6c5435bc2654ee3106ee350ff 100644
--- a/lib/private/tagmanager.php
+++ b/lib/private/tagmanager.php
@@ -33,6 +33,8 @@
 
 namespace OC;
 
+use OC\Tagging\TagMapper;
+
 class TagManager implements \OCP\ITagManager {
 
 	/**
@@ -42,13 +44,22 @@ class TagManager implements \OCP\ITagManager {
 	 */
 	private $user;
 
+	/**
+	 * TagMapper
+	 *
+	 * @var TagMapper
+	 */
+	private $mapper;
+
 	/**
 	* Constructor.
 	*
-	* @param string $user The user whos data the object will operate on.
+	* @param TagMapper $mapper Instance of the TagMapper abstraction layer.
+	* @param string $user The user whose data the object will operate on.
 	*/
-	public function __construct($user) {
+	public function __construct(TagMapper $mapper, $user) {
 
+		$this->mapper = $mapper;
 		$this->user = $user;
 
 	}
@@ -62,7 +73,7 @@ class TagManager implements \OCP\ITagManager {
 	* @return \OCP\ITags
 	*/
 	public function load($type, $defaultTags=array()) {
-		return new Tags($this->user, $type, $defaultTags);
+		return new Tags($this->mapper, $this->user, $type, $defaultTags);
 	}
 
 }
diff --git a/lib/private/tags.php b/lib/private/tags.php
index b1bd3b13d45b1e3f3741abd570880ef9db721e17..5a962c4891dd4d57403d64cc0203eb24d093564a 100644
--- a/lib/private/tags.php
+++ b/lib/private/tags.php
@@ -34,6 +34,9 @@
 
 namespace OC;
 
+use \OC\Tagging\Tag,
+    \OC\Tagging\TagMapper;
+
 class Tags implements \OCP\ITags {
 
 	/**
@@ -64,6 +67,13 @@ class Tags implements \OCP\ITags {
 	 */
 	private $user;
 
+	/**
+	 * The Mapper we're using to communicate our Tag objects to the database.
+	 *
+	 * @var TagMapper
+	 */
+	private $mapper;
+
 	const TAG_TABLE = '*PREFIX*vcategory';
 	const RELATION_TABLE = '*PREFIX*vcategory_to_object';
 
@@ -72,10 +82,13 @@ class Tags implements \OCP\ITags {
 	/**
 	* Constructor.
 	*
-	* @param string $user The user whos data the object will operate on.
-	* @param string $type
+	* @param TagMapper $mapper Instance of the TagMapper abstraction layer.
+	* @param string $user The user whose data the object will operate on.
+	* @param string $type The type of items for which tags will be loaded.
+	* @param array $defaultTags Tags that should be created at construction.
 	*/
-	public function __construct($user, $type, $defaultTags = array()) {
+	public function __construct(TagMapper $mapper, $user, $type, $defaultTags = array()) {
+		$this->mapper = $mapper;
 		$this->user = $user;
 		$this->type = $type;
 		$this->loadTags($defaultTags);
@@ -86,27 +99,13 @@ class Tags implements \OCP\ITags {
 	*
 	*/
 	protected function loadTags($defaultTags=array()) {
-		$this->tags = array();
-		$result = null;
-		$sql = 'SELECT `id`, `category` FROM `' . self::TAG_TABLE . '` '
-			. 'WHERE `uid` = ? AND `type` = ? ORDER BY `category`';
 		try {
-			$stmt = \OCP\DB::prepare($sql);
-			$result = $stmt->execute(array($this->user, $this->type));
-			if (\OCP\DB::isError($result)) {
-				\OCP\Util::writeLog('core', __METHOD__. ', DB error: ' . \OCP\DB::getErrorMessage($result), \OCP\Util::ERROR);
-			}
+			$this->tags = $this->mapper->loadTags(array($this->user), $this->type);
 		} catch(\Exception $e) {
 			\OCP\Util::writeLog('core', __METHOD__.', exception: '.$e->getMessage(),
 				\OCP\Util::ERROR);
 		}
 
-		if(!is_null($result)) {
-			while( $row = $result->fetchRow()) {
-				$this->tags[$row['id']] = $row['category'];
-			}
-		}
-
 		if(count($defaultTags) > 0 && count($this->tags) === 0) {
 			$this->addMultiple($defaultTags, true);
 		}
@@ -127,10 +126,10 @@ class Tags implements \OCP\ITags {
 	/**
 	* Get the tags for a specific user.
 	*
-	* This returns an array with id/name maps:
+	* This returns an array with maps containing each tag's properties:
 	* [
-	* 	['id' => 0, 'name' = 'First tag'],
-	* 	['id' => 1, 'name' = 'Second tag'],
+	* 	['id' => 0, 'name' = 'First tag', 'owner' = 'User', 'type' => 'tagtype'],
+	* 	['id' => 1, 'name' = 'Shared tag', 'owner' = 'Other user', 'type' => 'tagtype'],
 	* ]
 	*
 	* @return array
@@ -140,16 +139,19 @@ class Tags implements \OCP\ITags {
 			return array();
 		}
 
-		$tags = array_values($this->tags);
-		uasort($tags, 'strnatcasecmp');
+		usort($this->tags, function($a, $b) {
+			return strnatcasecmp($a->getName(), $b->getName());
+		});
 		$tagMap = array();
 
-		foreach($tags as $tag) {
-			if($tag !== self::TAG_FAVORITE) {
+		foreach($this->tags as $tag) {
+			if($tag->getName() !== self::TAG_FAVORITE) {
 				$tagMap[] = array(
-					'id' => $this->array_searchi($tag, $this->tags),
-					'name' => $tag
-					);
+					'id'    => $tag->getId(),
+					'name'  => $tag->getName(),
+					'owner' => $tag->getOwner(),
+					'type'  => $tag->getType()
+				);
 			}
 		}
 		return $tagMap;
@@ -174,7 +176,7 @@ class Tags implements \OCP\ITags {
 				\OCP\Util::writeLog('core', __METHOD__.', Cannot use empty tag names', \OCP\Util::DEBUG);
 				return false;
 			}
-			$tagId = $this->array_searchi($tag, $this->tags);
+			$tagId = $this->getTagId($tag);
 		}
 
 		if($tagId === false) {
@@ -217,7 +219,7 @@ class Tags implements \OCP\ITags {
 	* @return bool
 	*/
 	public function hasTag($name) {
-		return $this->in_arrayi($name, $this->tags);
+		return $this->getTagId($name) !== false;
 	}
 
 	/**
@@ -233,35 +235,21 @@ class Tags implements \OCP\ITags {
 			\OCP\Util::writeLog('core', __METHOD__.', Cannot add an empty tag', \OCP\Util::DEBUG);
 			return false;
 		}
-		if($this->hasTag($name)) {
+		if($this->hasTag($name)) { // FIXME
 			\OCP\Util::writeLog('core', __METHOD__.', name: ' . $name. ' exists already', \OCP\Util::DEBUG);
 			return false;
 		}
 		try {
-			$result = \OCP\DB::insertIfNotExist(
-				self::TAG_TABLE,
-				array(
-					'uid' => $this->user,
-					'type' => $this->type,
-					'category' => $name,
-				)
-			);
-			if (\OCP\DB::isError($result)) {
-				\OCP\Util::writeLog('core', __METHOD__. 'DB error: ' . \OCP\DB::getErrorMessage($result), \OCP\Util::ERROR);
-				return false;
-			} elseif((int)$result === 0) {
-				\OCP\Util::writeLog('core', __METHOD__.', Tag already exists: ' . $name, \OCP\Util::DEBUG);
-				return false;
-			}
+			$tag = new Tag($this->user, $this->type, $name);
+			$tag = $this->mapper->insert($tag);
+			$this->tags[] = $tag;
 		} catch(\Exception $e) {
 			\OCP\Util::writeLog('core', __METHOD__.', exception: '.$e->getMessage(),
 				\OCP\Util::ERROR);
 			return false;
 		}
-		$id = \OCP\DB::insertid(self::TAG_TABLE);
-		\OCP\Util::writeLog('core', __METHOD__.', id: ' . $id, \OCP\Util::DEBUG);
-		$this->tags[$id] = $name;
-		return $id;
+		\OCP\Util::writeLog('core', __METHOD__.', id: ' . $tag->getId(), \OCP\Util::DEBUG);
+		return $tag->getId();
 	}
 
 	/**
@@ -280,27 +268,21 @@ class Tags implements \OCP\ITags {
 			return false;
 		}
 
-		$id = $this->array_searchi($from, $this->tags);
-		if($id === false) {
+		$key = $this->array_searchi($from, $this->tags); // FIXME: owner. or renameById() ?
+		if($key === false) {
 			\OCP\Util::writeLog('core', __METHOD__.', tag: ' . $from. ' does not exist', \OCP\Util::DEBUG);
 			return false;
 		}
 
-		$sql = 'UPDATE `' . self::TAG_TABLE . '` SET `category` = ? '
-			. 'WHERE `uid` = ? AND `type` = ? AND `id` = ?';
 		try {
-			$stmt = \OCP\DB::prepare($sql);
-			$result = $stmt->execute(array($to, $this->user, $this->type, $id));
-			if (\OCP\DB::isError($result)) {
-				\OCP\Util::writeLog('core', __METHOD__. 'DB error: ' . \OCP\DB::getErrorMessage($result), \OCP\Util::ERROR);
-				return false;
-			}
+			$tag = $this->tags[$key];
+			$tag->setName($to);
+			$this->tags[$key] = $this->mapper->update($tag);
 		} catch(\Exception $e) {
 			\OCP\Util::writeLog('core', __METHOD__.', exception: '.$e->getMessage(),
 				\OCP\Util::ERROR);
 			return false;
 		}
-		$this->tags[$id] = $to;
 		return true;
 	}
 
@@ -322,9 +304,8 @@ class Tags implements \OCP\ITags {
 
 		$newones = array();
 		foreach($names as $name) {
-			if(($this->in_arrayi(
-				$name, $this->tags) == false) && $name !== '') {
-				$newones[] = $name;
+			if(!$this->hasTag($name) && $name !== '') {
+				$newones[] = new Tag($this->user, $this->type, $name);
 			}
 			if(!is_null($id) ) {
 				// Insert $objectid, $categoryid  pairs if not exist.
@@ -346,12 +327,9 @@ class Tags implements \OCP\ITags {
 		if(is_array($this->tags)) {
 			foreach($this->tags as $tag) {
 				try {
-					\OCP\DB::insertIfNotExist(self::TAG_TABLE,
-						array(
-							'uid' => $this->user,
-							'type' => $this->type,
-							'category' => $tag,
-						));
+					if (!$this->mapper->tagExists($tag)) {
+						$this->mapper->insert($tag);
+					}
 				} catch(\Exception $e) {
 					\OCP\Util::writeLog('core', __METHOD__.', exception: '.$e->getMessage(),
 						\OCP\Util::ERROR);
@@ -366,7 +344,7 @@ class Tags implements \OCP\ITags {
 			// For some reason this is needed or array_search(i) will return 0..?
 			ksort($tags);
 			foreach(self::$relations as $relation) {
-				$tagId = $this->array_searchi($relation['tag'], $tags);
+				$tagId = $this->getTagId($relation['tag']);
 				\OCP\Util::writeLog('core', __METHOD__ . 'catid, ' . $relation['tag'] . ' ' . $tagId, \OCP\Util::DEBUG);
 				if($tagId) {
 					try {
@@ -527,7 +505,7 @@ class Tags implements \OCP\ITags {
 			if(!$this->hasTag($tag)) {
 				$this->add($tag);
 			}
-			$tagId =  $this->array_searchi($tag, $this->tags);
+			$tagId =  $this->getTagId($tag);
 		} else {
 			$tagId = $tag;
 		}
@@ -560,7 +538,7 @@ class Tags implements \OCP\ITags {
 				\OCP\Util::writeLog('core', __METHOD__.', Tag name is empty', \OCP\Util::DEBUG);
 				return false;
 			}
-			$tagId =  $this->array_searchi($tag, $this->tags);
+			$tagId =  $this->getTagId($tag);
 		} else {
 			$tagId = $tag;
 		}
@@ -579,7 +557,7 @@ class Tags implements \OCP\ITags {
 	}
 
 	/**
-	* Delete tags from the
+	* Delete tags from the database.
 	*
 	* @param string[] $names An array of tags to delete
 	* @return bool Returns false on error
@@ -598,20 +576,17 @@ class Tags implements \OCP\ITags {
 			$id = null;
 
 			if($this->hasTag($name)) {
-				$id = $this->array_searchi($name, $this->tags);
-				unset($this->tags[$id]);
-			}
-			try {
-				$stmt = \OCP\DB::prepare('DELETE FROM `' . self::TAG_TABLE . '` WHERE '
-					. '`uid` = ? AND `type` = ? AND `category` = ?');
-				$result = $stmt->execute(array($this->user, $this->type, $name));
-				if (\OCP\DB::isError($result)) {
-					\OCP\Util::writeLog('core', __METHOD__. 'DB error: ' . \OCP\DB::getErrorMessage($result), \OCP\Util::ERROR);
+				$key = $this->array_searchi($name, $this->tags);
+				$tag = $this->tags[$key];
+				$id = $tag->getId();
+				unset($this->tags[$key]);
+				try {
+					$this->mapper->delete($tag);
+				} catch(\Exception $e) {
+					\OCP\Util::writeLog('core', __METHOD__ . ', exception: '
+						. $e->getMessage(), \OCP\Util::ERROR);
+					return false;
 				}
-			} catch(\Exception $e) {
-				\OCP\Util::writeLog('core', __METHOD__ . ', exception: '
-					. $e->getMessage(), \OCP\Util::ERROR);
-				return false;
 			}
 			if(!is_null($id) && $id !== false) {
 				try {
@@ -635,19 +610,28 @@ class Tags implements \OCP\ITags {
 		return true;
 	}
 
-	// case-insensitive in_array
-	private function in_arrayi($needle, $haystack) {
+	// case-insensitive array_search
+	protected function array_searchi($needle, $haystack, $mem='getName') {
 		if(!is_array($haystack)) {
 			return false;
 		}
-		return in_array(strtolower($needle), array_map('strtolower', $haystack));
+		return array_search(strtolower($needle), array_map(
+			function($tag) use($mem) {
+				return strtolower(call_user_func(array($tag, $mem)));
+			}, $haystack)
+		);
 	}
 
-	// case-insensitive array_search
-	private function array_searchi($needle, $haystack) {
-		if(!is_array($haystack)) {
+	/**
+	* Get a tag's ID.
+	*
+	* @param string $name The tag name to look for.
+	* @return string The tag's id or false if it hasn't been saved yet.
+	*/
+	private function getTagId($name) {
+		if (($key = $this->array_searchi($name, $this->tags)) === false) {
 			return false;
 		}
-		return array_search(strtolower($needle), array_map('strtolower', $haystack));
+		return $this->tags[$key]->getId();
 	}
 }
diff --git a/tests/lib/tags.php b/tests/lib/tags.php
index 9195587f1ddc0905dac0e10c59b71389e5ee8ad4..4d9b8558fd3e214b2a358fc378d9fd581cc68400 100644
--- a/tests/lib/tags.php
+++ b/tests/lib/tags.php
@@ -34,7 +34,8 @@ class Test_Tags extends PHPUnit_Framework_TestCase {
 		$this->objectType = uniqid('type_');
 		OC_User::createUser($this->user, 'pass');
 		OC_User::setUserId($this->user);
-		$this->tagMgr = new OC\TagManager($this->user);
+		$this->tagMapper = new OC\Tagging\TagMapper(new OC\AppFramework\Db\Db());
+		$this->tagMgr = new OC\TagManager($this->tagMapper, $this->user);
 
 	}