{"id":27966486,"url":"https://github.com/techery/snapper","last_synced_at":"2025-05-07T20:19:05.553Z","repository":{"id":28217845,"uuid":"31722030","full_name":"techery/snapper","owner":"techery","description":"NoSQL fast-serializable storage with DataViews, Sorting, Filtering and more","archived":false,"fork":false,"pushed_at":"2016-04-18T12:28:28.000Z","size":659,"stargazers_count":40,"open_issues_count":8,"forks_count":1,"subscribers_count":40,"default_branch":"master","last_synced_at":"2025-05-07T20:18:58.773Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/techery.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2015-03-05T15:56:54.000Z","updated_at":"2023-09-08T16:55:16.000Z","dependencies_parsed_at":"2022-09-14T18:40:59.819Z","dependency_job_id":null,"html_url":"https://github.com/techery/snapper","commit_stats":null,"previous_names":[],"tags_count":11,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/techery%2Fsnapper","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/techery%2Fsnapper/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/techery%2Fsnapper/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/techery%2Fsnapper/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/techery","download_url":"https://codeload.github.com/techery/snapper/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252949258,"owners_count":21830160,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-05-07T20:19:04.866Z","updated_at":"2025-05-07T20:19:05.540Z","avatar_url":"https://github.com/techery.png","language":"Java","funding_links":[],"categories":["数据库"],"sub_categories":["微服务框架"],"readme":"## Snapper\nNoSQL fast-serializable storage with Projections, Sorting, Filtering and more.\n\n## Overview\nNoSQL db is great in simplicity.\n`Snapper` is simple but yet functional:\nit's based on idea that every `POJO` could be addressed as typed `DataCollection` with\nany kind of `Projection` (aka sub-set) for filtering, sorting, mapping or joining.\nSuch `DataCollection`\\\\`Projection` is observable and reacts to changes from parent `DataSet`.\n\n## Performance\nTiming is very close to fastest `SQLite`-based DBs.\nSee [Benchmark test results](BENCHMARK.md) for details.\n\n## Getting started\n### Core objects\n`DroidSnapper` is main controller for `DataCollection`(s) - data provider objects.\nIt's thread safe Singleton:\n\n```java\nDataCollection\u003cUser\u003e userCollection = DroidSnapper.with(context).collection(User.class);\n```\n`DataCollection` itself is used for:\n- data manipulation\n\n   ```java\n   userCollection.insert(new User(1, \"Jim\")); // add user with id=1\n   userCollection.insert(new User(1, \"Jim Defoe\")); // replace user by id \n   ```\n   _Note_: all data-related work is done on background `Executor` so `MainThread`-friendly.\n- data observation\n\n  ```java\n  userCollection.addDataListener(new IDataSet.DataListener\u003cUser\u003e() {\n      @Override\n      public void onDataUpdated(List\u003cUser\u003e items, StorageChange\u003cUser\u003e change) {\n          //\n      }\n  });\n  ```\n  _Note_: `DataCollection` holds memory cache from it's storage for better performance;\n  \n  _Note_: listener is called right away upon addition. It's guaranteed that `DataCollection` is in costistent state (read initialized) when callback is called.\n  \n  _Caution_: listener is called from `Executor`'s thread, \n  it's up to `DataListener` to proxy calls to another thread, e.g. [MainThreadDataListener](droidsnapper/src/main/java/io/techery/snapper/droidsnapper/helper/MainThreadDataListener.java).\n- `Projection` creation:\n\n  ```java\n  Projection\u003cUser\u003e jimProjection = userCollection.projection()\n          .where(new Predicate\u003cUser\u003e() {\n              @Override\n              public boolean apply(User element) {\n                  return element.name.contains(\"Jim\");\n              }\n          }).build();\n  jimProjection.addDataListener(...);\n  ```\n`Projection` is a subset of parent `DataSet` which `DataCollection` or another `Projection` is.\nIt's used for:\n    - sub-projection with conditional filtering/sorting;\n    - data observation.\n\n### Data Model\nEvery model must implement `Indexable` interface, e.g.:\n```java\npublic class User implements Indexable {\n\n    public final int id;\n    \n    public User(int id) {\n        this.id = id;\n    }\n\n    @Override\n    public byte[] index() {\n        return ByteBuffer.allocate(4).putInt(id).array();\n    }\n}\n```\n- `index()` is used as unique key for each object;\n- insertion will replace (update) object with same index.\n  \n## Advanced usage\n### DataCollection naming\nEvery POJO storage is named with POJO's `class#getSimpleName()`, but could be labeled for uniqueness:\n```java\nDataCollection\u003cUser\u003e userCollection = DroidSnapper.with(context).collection(User.class);\nDataCollection\u003cUser\u003e friendsCollection = DroidSnapper.with(context).collection(User.class, \"friends\");\n```\n### Advanced DataSet(s)\n#### Joining\nConnects two `DataSet`s to listen for their changes:\n```java\nDataSetJoin\u003cCompany, User, Pair\u003cCompany, User\u003e\u003e companyUserJoin =\n    new JoinBuilder\u003c\u003e(companyStorage, userStorage)\n            .setJoinFunction(new Function2\u003cCompany, User, Boolean\u003e() {\n                @Override public Boolean apply(Company company, User user) {\n                    return company.getId() == user.getCompanyId();\n                }\n            })\n            .setMapFunction(new Function2\u003cCompany, List\u003cUser\u003e, Pair\u003cCompany, User\u003e\u003e() {\n                @Override public Pair\u003cCompany, User\u003e apply(Company company, List\u003cUser\u003e users) {\n                    User user = users.isEmpty() ? null : users.get(0);\n                    return new Pair\u003c\u003e(company, user);\n                }\n            }).create();\n```\n#### Mapping\nCreates virtual relation between `DataSet` objects and some type:\n```java\nDataSetMap\u003cUser, Integer\u003e userIdMap = new DataSetMap\u003cUser, Integer\u003e(dataCollection, new Function1\u003cUser, Integer\u003e() {\n    @Override\n    public Integer apply(User user) {\n        return user.id;\n    }\n});\nuserIdMap.addDataListener(new IDataSet.DataListener\u003cInteger\u003e() {\n    @Override\n    public void onDataUpdated(List\u003cInteger\u003e items, StorageChange\u003cInteger\u003e change) {\n        //\n    }\n});\n```\n#### Rx support\n`DataListener` is wrappable with Rx goodies, see [Rx Sample](droidsnapper-sample-rx/src/main/java/com/example/snapper/rx) for details.\n\n### Resources Cleanup\n- Every collection could be closed with `DataCollection#close()`\n- All collections are closed via `Snapper#close()`\n\n## Under the hood\n`DroidSnapper` is a `Snapper` instance with default impl. of `Snapper`'s components, those are:\n- `DataCollectionNamingFactory` - creates names for collections depending on it's model's class and custom label;\n- `DataCollectionFactory` creates a `DataCollection` per POJO's model, every collection involves;\n - `StorageFactory` provides storage to put/get collection's items;\n - `ExecutorFactory` provides `ExecutorService` to perform collection actions;\n \n `Storage` impl. could differ and up to developer. \n Anyway `Snapper` provides `CachingStorage` with in-mem cache and forwarding calls to it's disk persister.\n  `CachingStorage` uses `ObjectConverter` to convert POJO to bytes and vice versa.\n\nIt means one can easily create own no-sql storage with own components of choice.\n\n`DroidSnapper` uses\n- `CachingStorage` to hold collection's items in memory for best performance;\n- [SnappyDB](https://github.com/nhachicha/SnappyDB) as `StoragePersister`\n- [Kryo](https://github.com/EsotericSoftware/kryo) as `ObjectConverter`\n\n## Dev. status\nTested in production.\n\n[![Build Status](https://travis-ci.org/techery/snapper.svg?branch=master)](https://travis-ci.org/techery/snapper)\n![JitPack release](https://img.shields.io/github/tag/techery/snapper.svg?label=JitPack)\n\n## Installation\n```groovy\nrepositories {\n    maven { url \"https://jitpack.io\" }\n}\ndependencies {\n    compile 'com.github.techery.snapper:droidsnapper:{latestVersion}'\n}\n```\n\n## License\n\n    Copyright (c) 2015 Techery\n\n    Licensed under the Apache License, Version 2.0 (the \"License\");\n    you may not use this file except in compliance with the License.\n    You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n    Unless required by applicable law or agreed to in writing, software\n    distributed under the License is distributed on an \"AS IS\" BASIS,\n    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n    See the License for the specific language governing permissions and\n    limitations under the License.\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftechery%2Fsnapper","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftechery%2Fsnapper","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftechery%2Fsnapper/lists"}