Android Room: One database with multiple tables - java

I have two separate tables for my application, one is called users and the other is called passwords.
Users:
#Entity(tableName = "users")
public class Users {
// some setters and getters here
}
Passwords:
#Entity(tableName = "passwords")
public class Passwords {
// some setters and getters here
}
And this is how I'm accessing the database:
usersdb = Room.databaseBuilder(this, Users.class,"mymaindb")
.allowMainThreadQueries()
.build();
// Then later in some other activity when I need to use passwords table
passwords = Room.databaseBuilder(this, passwords.class,"mymaindb")
.allowMainThreadQueries()
.build();
The issue I'm having is that after a fresh install, when I access passwords then I can't access users or vice versa.
I get the following error:
Caused by: java.lang.IllegalStateException: Room cannot verify the
data integrity. Looks like you've changed schema but forgot to update
the version number. You can simply fix this by increasing the version
number.
But when I try to have two separate databases like passwords_db and users_db instead of mymaindb, then it works completely fine.
So is there a way I can have multiple tables under one database? If so, what am I doing wrong then? Thanks in advance!

I think you got this all wrong, Room.databaseBuilder should only be called once to setup the database and in that database class, you will construct multiple tables. For example:
Room.databaseBuilder(this, MyRoomDb.class, "mymaindb")
.allowMainThreadQueries()
.build()
And your MyRoomDb should look like this
#Database(
entities = {
Users.class,
Passwords.class
},
version = VERSION
)
public abstract class MyRoomDb extends RoomDatabase {
...
}

You have few variants how to solve this problem:
Add tables back but increase the version of database;
#Database(entities={Users.class, Passwords.class}, version = 2)
Clean the application settings and build the new database;
Just clean the application cache and try to recreate the database.

It's simple like adding one entity table. Just add another table name using ",". And another table will be added.
#Database(entities = [TableUser::class, TableRecord::class], version = 1)
abstract class MyDatabase : RoomDatabase() {
abstract fun myDao(): MyDao
}

Here's another way you can have what you need by using one RoomDB class for all your tables:
#Database(entities = {Users.class, Passwords.class}, version = 1, exportSchema = false)
public abstract class MyDatabase extends RoomDatabase {
private static MyDatabase INSTANCE;
public abstract UsersDAO usersDAO();
public abstract PasswordsDAO passwordsDAO();
public static MyDatabase getDatabase(Context context) {
if (INSTANCE == null) {
INSTANCE = Room.databaseBuilder(context.getApplicationContext(),
MyDatabase.class, "mydatabase")
.build();
}
return INSTANCE;
}
}
Your Entity will look like this:
#Entity(tableName = "user_table")
public class User {
#PrimaryKey(autoGenerate = true)
private long id;
private String username;
private String email;
.............//Constructor goes here
.............//Getters and Setters go here
}
The DAO looks like this:
#Dao
public interface UsersDAO {
#Query("SELECT * FROM user_table")
List<User> getAll();
}
Then you'll fetch data like this:
AsyncTask.execute(() -> {
MyDatabase.getDatabase(getApplicationContext()).usersDAO().getAll();
});

if you dont want to change the code you can simply uninstall and install the app again. But this will delete your data saved in database till now.

Related

How to get random record from one collection MongoDB

I have entity Article I'm trying to retrieve one random record from database collection.
This is entity Article:
#Data
#Document(value = "article")
public class Article {
#Id
private String articleId;
private String title;
private String description;
private String fullArticle;
This is service to save it:
#Override
public Article save(Article article) {
return articleRepository.save(article);
}
And repository:
#Repository
public interface ArticleRepository extends MongoRepository<Article, String> {
}
So, now I'm trying to create a method that will get me one random record from my collection Article also, I want to create a controller so when I go to some endpoint and submit some get method to retrieve one record from the collection and so I can check it in postman or with Swagger. I find some answers to similar question to mine but no one solved my problem, I want to have API for something like that.
You can use $sample in an aggregation query to get a random document:
db.collection.aggregate([
{
"$sample": {
"size": 1
}
}
])
Example here
I've tested the code and it works as expected.
You can create add this method into repository:
#Aggregation(pipeline={"{$sample:{size:1}}"})
AggregationResults<Article> random();
And call from service like this:
#Override
public Article random(){
return articleRepository.random().getMappedResults().stream().findFirst().orElse(null);
// also you can use .orElseThrow() or whatever you want
}

Javers not recognizing insert as an initial change

Working on a SpringBoot application using MongoDB as a persistent store.
Using spring data and MongoRepository to access MongoDB.
Using Javers to provide auditting.
If I use mongoRepository.insert(document) followed later by a mongoRepository.save(document) and then use javers to query the changes to that document, javers does not detect the differences between the object inserted and the object saved. It reports only a single change as if the save call was the original object persisted.
If I replace the insert call with a save and let spring data handle whether or not to insert or update, javers reports the expected change.
Example:
Consider the following:
#JaversSpringDataAuditable
public interface SomeDocumentRepository extends MongoRepository<SomeDocument, String> {
}
#Builder
#Data
#Document(collection = "someDocuments")
public class SomeDocument {
#Id
private String id;
private String status;
}
#Service
public class SomeDocumentService {
#Autowired
private SomeDocumentRepository someDocumentRepository;
public SomeDocument insert(SomeDocument doc) {
return someDocumentRepository.insert(doc);
}
public SomeDocument save(SomeDocument doc) {
return someDocumentRepository.save(doc);
}
}
#Service
public class AuditService {
#Autowired
private Javers javers;
public List<Change> getStatusChangesById(String documentId) {
JqlQuery query = QueryBuilder
.byInstanceId(documentId, SomeDocument.class)
.withChangedProperty("status")
.build();
return javers.findChanges(query);
}
}
If I call my service as follows:
var doc = SomeDocument.builder().status("new").build();
doc = someDocumentService.insert(doc);
doc.setStatus("change1");
doc = someDocumentService.save(doc);
and then call the audit service to get the changes:
auditService.getStatusChangesById(doc.getId());
I get a single change with "left" set to a blank and "right" set to "change1".
If I call "save" instead of "insert" like:
var doc = SomeDocument.builder().status("new").build();
doc = someDocumentService.save(doc);
doc.setStatus("change1");
doc = someDocumentService.save(doc);
and then call the audit service to get the changes I get 2 changes, the first being the most recent change with "left" set to "new", and "right" set to "change1" and a second change with "left" set to "" and "right" set to "new".
Is this a bug?
That's a good point. In case of Mongo, Javers covers only the methods from the CrudRepository interface. See https://github.com/javers/javers/blob/master/javers-spring/src/main/java/org/javers/spring/auditable/aspect/springdata/JaversSpringDataAuditableRepositoryAspect.java
Looks like MongoRepository#insert() should be also covered by the aspect.
Feel free to contribute a PR to javers, I will merge it. If you want to discuss the design first - please create a discussion here https://github.com/javers/javers/discussions

Room doesn't store data

My aim is to store some data into a SQLite database using Room.
So I made a lot of #Entities in POJO.
For each #Entity I made a #Dao with at least these queries:
#Dao
public interface RouteDao {
#Query("SELECT * FROM route")
LiveData<List<Route>> getAll();
#Insert
void insertAll(List<Route> routes);
#Query("DELETE FROM route")
void deleteAll();
}
My Singleton Room #Database is:
#Database(entities = {Agency.class, Calendar.class, CalendarDate.class, FeedInfo.class, Route.class, Stop.class, StopTime.class, Transfer.class, Trip.class}, version = 1)
#TypeConverters(MyConverters.class)
public abstract class GtfsDatabase extends RoomDatabase {
private static final String DATABASE_NAME = "gtfs-db";
private static GtfsDatabase INSTANCE;
public abstract AgencyDao agencyDao();
public abstract CalendarDao calendarDao();
public abstract CalendarDateDao calendarDateDao();
public abstract FeedInfoDao feedInfoDao();
public abstract RouteDao routeDao();
public abstract StopDao stopDao();
public abstract StopTimeDao stopTimeDao();
public abstract TransferDao transferDao();
public abstract TripDao tripDao();
public static synchronized GtfsDatabase getDatabase(Context context) {
return INSTANCE == null ? INSTANCE = Room.databaseBuilder(
context.getApplicationContext(),
GtfsDatabase.class,
DATABASE_NAME
).build() : INSTANCE;
}
}
When I open the app for the first time, I fill the database with data in a background IntentService:
public static void importData(Context context, Map<String, String> data) {
GtfsDatabase db = GtfsDatabase.getDatabase(context);
db.beginTransaction();
try {
db.agencyDao().deleteAll();
db.calendarDao().deleteAll();
db.calendarDateDao().deleteAll();
db.feedInfoDao().deleteAll();
db.routeDao().deleteAll();
db.stopDao().deleteAll();
db.stopTimeDao().deleteAll();
db.transferDao().deleteAll();
db.tripDao().deleteAll();
db.agencyDao().insertAll(rawToAgencies(data.get(AGENCY_FILE)));
db.calendarDao().insertAll(rawToCalendars(data.get(CALENDAR_FILE)));
db.calendarDateDao().insertAll(rawToCalendarDates(data.get(CALENDAR_DATES_FILE)));
db.feedInfoDao().insertAll(rawToFeedInfos(data.get(FEED_INFO_FILE)));
db.routeDao().insertAll(rawToRoutes(data.get(ROUTES_FILE)));
db.tripDao().insertAll(rawToTrips(data.get(TRIPS_FILE)));
db.stopDao().insertAll(rawToStops(data.get(STOPS_FILE)));
db.stopTimeDao().insertAll(rawToStopsTimes(data.get(STOP_TIMES_FILE)));
db.transferDao().insertAll(rawToTransfers(data.get(TRANSFERS_FILE)));
PreferenceManager.getDefaultSharedPreferences(context).edit().putBoolean(context.getString(R.string.empty), false).apply();
} finally {
db.endTransaction();
}
}
I am absolutely sure that the data is correct. I debugged each line and I can say 100% sure that the list of objects I pass to these functions is correct. No error at all.
When this service is finished (if(!sharedPreferences.getBoolean(getString(R.string.empty), true))) I try to access to the database in another activity and this show me that is empty.
I checked with this library debugCompile 'com.amitshekhar.android:debug-db:1.0.0' and every table is really empty.
What I'm doing wrong?
I know you cannot see all my code, and maybe there's something wrong, so my actual question is: is the above code correct?
Solved.
Android Room is handling transactions automatically.
#Query are asynchronous, while #Insert and #Delete are synchronous.
My error was to try including all those operations in a single transaction.
The solution is: let that Room handles them automatically.
public static void importData(Context context, Map<String, String> data) {
GtfsDatabase db = GtfsDatabase.getDatabase(context);
db.agencyDao().deleteAll();
db.calendarDao().deleteAll();
db.calendarDateDao().deleteAll();
db.feedInfoDao().deleteAll();
db.routeDao().deleteAll();
db.stopDao().deleteAll();
db.stopTimeDao().deleteAll();
db.transferDao().deleteAll();
db.tripDao().deleteAll();
db.agencyDao().insertAll(rawToAgencies(data.get(AGENCY_FILE)));
db.calendarDao().insertAll(rawToCalendars(data.get(CALENDAR_FILE)));
db.calendarDateDao().insertAll(rawToCalendarDates(data.get(CALENDAR_DATES_FILE)));
db.feedInfoDao().insertAll(rawToFeedInfos(data.get(FEED_INFO_FILE)));
db.routeDao().insertAll(rawToRoutes(data.get(ROUTES_FILE)));
db.tripDao().insertAll(rawToTrips(data.get(TRIPS_FILE)));
db.stopDao().insertAll(rawToStops(data.get(STOPS_FILE)));
db.stopTimeDao().insertAll(rawToStopsTimes(data.get(STOP_TIMES_FILE)));
db.transferDao().insertAll(rawToTransfers(data.get(TRANSFERS_FILE)));
PreferenceManager.getDefaultSharedPreferences(context).edit().putBoolean(context.getString(R.string.empty), false).apply();
}

accessing child constant in parent class in java

OK, so I have an interesting problem. I am using java/maven/spring-boot/cassandra... and I am trying to create a dynamic instantiation of the Mapper setup they use.
I.E.
//Users.java
import com.datastax.driver.mapping.annotations.Table;
#Table(keyspace="mykeyspace", name="users")
public class Users {
#PartitionKey
public UUID id;
//...
}
Now, in order to use this I would have to explicitly say ...
Users user = (DB).mapper(Users.class);
obviously replacing (DB) with my db class.
Which is a great model, but I am running into the problem of code repetition. My Cassandra database has 2 keyspaces, both keyspaces have the exact same tables with the exact same columns in the tables, (this is not my choice, this is an absolute must have according to my company). So when I need to access one or the other based on a form submission it becomes a mess of duplicated code, example:
//myWebController.java
import ...;
#RestController
public class MyRestController {
#RequestMapping(value="/orders", method=RequestMethod.POST)
public string getOrders(...) {
if(Objects.equals(client, "first_client_name") {
//do all the things to get first keyspace objects like....
FirstClientUsers users = (db).Mapper(FirstClientUsers.class);
//...
} else if(Objects.equals(client, "second_client_name") {
SecondClientUsers users = (db).Mapper(SecondClientUsers.class);
//....
}
return "";
}
I have been trying to use methods like...
Class cls = Class.forName(STRING_INPUT_VARIABLE_HERE);
and that works ok for base classes but when trying to use the Accessor stuff it no longer works because Accessors have to be interfaces, so when you do Class cls, it is no longer an interface.
I am trying to find any other solution on how to dynamically have this work and not have to have duplicate code for every possible client. Each client will have it's own namespace in Cassandra, with the exact same tables as all other ones.
I cannot change the database model, this is a must according to the company.
With PHP this is extremely simple since it doesn't care about typecasting as much, I can easily do...
function getData($name) {
$className = $name . 'Accessor';
$class = new $className();
}
and poof I have a dynamic class, but the problem I am running into is the Type specification where I have to explicitly say...
FirstClientUsers users = new FirstClientUsers();
//or even
FirstClientUsers users = Class.forName("FirstClientUsers");
I hope this is making sense, I can't imagine that I am the first person to have this problem, but I can't find any solutions online. So I am really hoping that someone knows how I can get this accomplished without duplicating the exact same logic for every single keyspace we have. It makes the code not maintainable and unnecessarily long.
Thank you in advance for any help you can offer.
Do not specify the keyspace in your model classes, and instead, use the so-called "session per keyspace" pattern.
Your model class would look like this (note that the keyspace is left undefined):
#Table(name = "users")
public class Users {
#PartitionKey
public UUID id;
//...
}
Your initialization code would have something like this:
Map<String, Mapper<Users>> mappers = new ConcurrentHashMap<String, Mapper<Users>>();
Cluster cluster = ...;
Session firstClientSession = cluster.connect("keyspace_first_client");
Session secondClientSession = cluster.connect("keyspace_second_client");
MappingManager firstClientManager = new MappingManager(firstClientSession);
MappingManager secondClientManager = new MappingManager(secondClientSession);
mappers.put("first_client", firstClientManager.mapper(Users.class));
mappers.put("second_client", secondClientManager.mapper(Users.class));
// etc. for all clients
You would then store the mappers object and make it available through dependency injection to other components in your application.
Finally, your REST service would look like this:
import ...
#RestController
public class MyRestController {
#javax.inject.Inject
private Map<String, Mapper<Users>> mappers;
#RequestMapping(value = "/orders", method = RequestMethod.POST)
public string getOrders(...) {
Mapper<Users> usersMapper = getUsersMapperForClient(client);
// process the request with the right client's mapper
}
private Mapper<Users> getUsersMapperForClient(String client) {
if (mappers.containsKey(client))
return mappers.get(client);
throw new RuntimeException("Unknown client: " + client);
}
}
Note how the mappers object is injected.
Small nit: I would name your class User in the singular instead of Users (in the plural).

Spring JPA - Best way to update multiple fields

I'm new to using JPA and trying to transition my code from JdbcTemplate to JPA. Originally I updated a subset of my columns by taking in a map of the columns with their values and created the SQL Update string myself and executed it using a DAO. I was wondering what would be the best way to do something similar using JPA?
EDIT:
How would I transform this code from my DAO to something equivalent in JPA?
public void updateFields(String userId, Map<String, String> fields) {
StringBuilder sb = new StringBuilder();
for (Entry<String, String> entry : fields.entrySet()) {
sb.append(entry.getKey());
sb.append("='");
sb.append(StringEscapeUtils.escapeEcmaScript(entry.getValue()));
sb.append("', ");
}
String str = sb.toString();
if (str.length() > 2) {
str = str.substring(0, str.length() - 2); // remove ", "
String sql = "UPDATE users_table SET " + str + " WHERE user_id=?";
jdbcTemplate.update(sql, new Object[] { userId },
new int[] { Types.VARCHAR });
}
}
You have to read more about JPA for sure :)
Once entity is in Persistence Context it is tracked by JPA provider till the end of persistence context life or until EntityManager#detach() method is called. When transaction finishes (commit) - the state of managed entities in persistence context is synchronized with database and all changes are made.
If your entity is new, you can simply put it in the persistece context by invoking EntityManager#persist() method.
In your case (update of existing entity), you have to get a row from database and somehow change it to entity. It can be done in many ways, but the simpliest is to call EntityManager#find() method which will return managed entity. Returned object will be also put to current persistence context, so if there is an active transaction, you can change whatever property you like (not the primary key) and just finish transaction by invoking commit (or if this is container managed transaction just finish method).
update
After your comment I can see your point. I think you should redesign your app to fit JPA standards and capabilities. Anyway - if you already have a map of pairs <Attribute_name, Attrbute_value>, you can make use of something called Metamodel. Simple usage is shown below. This is naive implementation and works good only with basic attributes, you should take care of relationships etc. (access to more informations about attributes can be done via methods attr.getJavaType() or attr.getPersistentAttributeType())
Metamodel meta = entityManager.getMetamodel();
EntityType<User> user_ = meta.entity(User.class);
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaUpdate<User> update = cb.createCriteriaUpdate(User.class);
Root e = update.from(User.class);
for( Attribute<? super User, ?> attr : user_.getAttributes() ) {
if (map.containsKey(attr.getName())) {
update.set(attr, map.get(attr));
}
}
update.where(cb.equal(e.get("id"), idOfUser));
entityManager.createQuery(update).executeUpdate();
Please note that Update Criteria Queries are available in JPA since 2.1 version.
Here you can find more informations about metamodel generation.
Alternatively to metamodel you can just use java reflection mechanisms.
JPA handles the update. Retrieve a dataset as entity using the entitymanager, change the value and call persist. This will store the changed data in your db.
In case you are using Hibernate(as JPA provider), here's an example
Entity
#Entity
#Table(name="PERSON")
public class Person {
#Id #GeneratedValue(strategy=GenerationType.IDENTITY)
private int id;
#Column(name="NAME", nullable=false)
private String name;
other fields....
}
DAO
public interface PersonDao {
Person findById(int id);
void persist(Person person);
...
}
DaoImpl
#Repository("personDao")
public class PersonDaoImpl extends AnAbstractClassWithSessionFactory implements PersonDao {
public Person findById(int id) {
return (Person) getSession().get(Person.class, id);
}
public void persist(Person person){
getSession().persist(person);
}
}
Service
#Service("personService")
#Transactional
public class PersonServiceImpl implements PersonService {
#Autowired
PersonDao personDao;
#Override
public void createAndPersist(SomeSourceObject object) {
//create Person object and populates with the source object
Person person = new Person();
person.name = object.name;
...
personDao.persist(person);
}
#Override
public Person findById(int id) {
return personDao.findById(id);
}
public void doSomethingWithPerson(Person person) {
person.setName(person.getName()+" HELLO ");
//here since we are in transaction, no need to explicitly call update/merge
//it will be updated in db as soon as the methods completed successfully
//OR
//changes will be undone if transaction failed/rolledback
}
}
JPA documentation are indeed good resource for details.
From design point of view, if you have web interfacing, i tends to say include one more service delegate layer(PersonDelegateService e.g.) which maps the actual data received from UI to person entity (and viceversa, for display, to populate the view object from person entity) and delegate to service for actual person entity processing.

Categories