Generating a query at runtime using Room Persistance - java

I want to run queries on my SQLite database that have been generated at runtime (instead of the standard compiletime queries in the #Dao). For example I might want to search a TEXT column in the SQLite db, to see if it contains all words in a list of N length. In raw SQLITE, a query where N is 3 would look like this :
SELECT * FROM table
WHERE textValue LIKE %queryTerm1%
AND textValue LIKE %queryTerm2%"
AND textValue LIKE %queryTerm3%"
I have tried generating, and passing the end of the query, instead of just passing variables. For example :
String generatedQuery = "textValue LIKE %queryTerm1% AND textValue LIKE %queryTerm2% AND textValue LIKE %queryTerm3%";
tableDao.find(generatedQuery);
and in the #Dao:
#Query("SELECT * FROM tableName WHERE :endQuery")
List<POJO> find(String endQuery);
This doesn't seem to work for me. Do you have any idea how to get runtime generated queries working with Room?
PS:
I have debugged the Dao implementation and looked at the statement it is running. This confirms that the generated query information, and the query are being passed correctly. I assume this is an issue with SQL injection prevention (aka more of an SQLITE problem, than a Room problem)

Update: latest release 1.1.1 of Room now uses SupportSQLiteQuery instead of String.
A query with typed bindings. It is better to use this API instead of
rawQuery(String, String[]) because it allows binding type safe
parameters.
New Answer:
#Dao
interface RawDao {
#RawQuery(observedEntities = User.class)
LiveData<List<User>> getUsers(SupportSQLiteQuery query);
}
Usage:
LiveData<List<User>> liveUsers = rawDao.getUsers( new
SimpleSQLiteQuery("SELECT * FROM User ORDER BY name DESC"));
Update your gradle to 1.1.1 (or whatever the current version is)
implementation 'android.arch.persistence.room:runtime:1.1.1'
implementation 'android.arch.lifecycle:extensions:1.1.1'
annotationProcessor "android.arch.persistence.room:compiler:1.1.1"

The problem is you want to pass a part of SQL statement, but Room treats it like a query parameter.
If you want you can try to use Kripton Persistence Library, an open source library written (by me :) ) that drastically simplify SQLite's management code for Android platform and support situations like this.
Kripton works with DAO pattern too, so concept are quite similar. Just to write an example that fit your needs:
Given a model class:
#BindType
public class User {
public long id;
public String name;
public String username;
public String email;
public Address address;
public String phone;
public String website;
public Company company;
}
a DAO definition:
#BindDao(User.class)
public interface UserDao {
#BindSqlInsert
void insert(User bean);
#BindSqlSelect
List<User> selectDynamic(#BindSqlDynamicWhere String where, #BindSqlDynamicWhereParams String[] args);
}
and a data source definition:
#BindDataSource(daoSet={UserDao.class}, fileName = "kripton.quickstart.db", generateAsyncTask = true)
public interface QuickStartDataSource {
}
Kripton will generate at compile time all code is need to work with database. So to accomplish your task with Kripton you have to write a code similar to:
BindQuickStartDataSource ds = BindQuickStartDataSource.instance();
// execute operation in a transaction
ds.execute(new BindQuickStartDataSource.SimpleTransaction() {
#Override
public boolean onExecute(BindQuickStartDaoFactory daoFactory) throws Throwable
{
UserDaoImpl dao = daoFactory.getUserDao();
String[] p={"hello"};
dao.selectDynamic("name=?",p);
return true;
}
});
In logcat when code above is executed you will see the generated log:
database OPEN READ_AND_WRITE_OPENED (connections: 1)
UserDaoImpl, selectDynamic (line 352): SELECT id, name, username, email, address, phone, website, company FROM user WHERE name=?
selectDynamic (line 357): ==> param0: 'hello'
Rows found: 0
database CLOSED (READ_AND_WRITE_OPENED) (connections: 0)
Kripton obviously supports static where conditions too and many other features (i start to develop it in 2015).
For more information about Kripton Persistence Library:
https://github.com/xcesco/kripton
http://abubusoft.com/
https://github.com/xcesco/kripton/wiki

Related

ORMLite Java, casing of sequences

I am trying to connect to postgresql with ORMlite from a Java client.
the DB gets generated perfectly, but when I try to insert something into a table that is using an autoincrement id I get the following error:
org.postgresql.util.PSQLException: ERROR: relation "commandusage_id_seq" does not exist
When I check the DB I can see a "commandUsage_id_seq" sequence has been created. With a Capital U.
How can I configure ORMLite to use all the same casing for both creating and interacting with the DB ? I couldn't find this in the documentation
Thanks in advace.
update:
When explicitly setting the sequenceId I can circumvent the issue
generatedIdSequence = "commandusage_id_seq"
but still I would like to know if this is possible by setting some config for ORMLite instead of setting this per DBObject class
update2:
URL of the package to prevent confusion: ORMLite
update3:
Below a snippet of the code and how it works. Again I would like to know if ORMLite is capable of doing the to lowercase conversion automatically instead of me doing it explicitly.
#DatabaseTable(tableName = "commandusage", daoClass = CommandUsageDaoImpl.class)
public class CommandUsage {
#DatabaseField(columnName = "id", generatedIdSequence = "commandusage_id_seq")
private transient int identifier;
When I check the DB I can see a "commandUsage_id_seq" sequence has been created. With a Capital U.
Interesting. After some initial confusion on my part, this seems like a bug a in ORMLite. The pattern is if you force a table name with mixed case (typical is to downcase the name) and then ask for a sequence-id on it in Postgresql:
#DatabaseTable(tableName = "TableNameCaseWithSeqeuence")
private static class TableNameCaseWithSeqeuence {
#DatabaseField(generatedId = true)
public int id;
...
}
I've got a fix in trunk but it's going to take a bit to spin a release.
The workaround right now is to extend the PostgresDatabaseType and inject it into your ConnectionSource. It should do something like:
public OurPostgresDatabaseType extends PostgresDatabaseType {
// constructors ...
#Override
public String generateIdSequenceName(String tableName, FieldType idType) {
String name = tableName + DEFAULT_SEQUENCE_SUFFIX;
return downCaseString(name, true);
}
}

Spring Boot JPA: Mapping one entity to multiple (a lot) tables with same columns

I have a lot of (like 60+) tables which have the same schema and similar names:
log_2020_07_01
log_2020_07_02
... and so on.
They have the same columns: id, site, size. Each of the table contains around 2 million rows.
I've read Hibernate and tables with same data/columns but with different table names which suggests to use hibernate alone. I hope after seven years maybe there's something new we could do with JPA.
In JPA, is it possible to just write one entity class and let the code to handle which table to use?
E.G.,
for(int i=0;i<60;i++) {
// read from the i-th table.
}
First of all, we could use a middleware to make the sharding transparent to users for middle/large projects.
Here is a quick work around for my small project (and I'm the only developer working it):
Step 1, create an inteceptor:
public class MySqlInterceptor extends EmptyInterceptor {
private String entityName;
#Setter
private int tableId;
protected MySqlInterceptor() {
super();
}
public MySqlInterceptor(String entityName) {
this.entityName = entityName;
}
#Override
public String onPrepareStatement(String sql) {
// Here is the trick.
String modifiedSql = sql.replaceAll(entityName, entityName + tableId);
log.debug("{}", modifiedSql);
return modifiedSql;
}
}
Step 2, hock up the interceptor:
MySqlInterceptor mySqlInterceptor = new MySqlInterceptor("temp");
mySqlInterceptor.setTableId(tableId);
session = entityManagerFactory.unwrap(SessionFactory.class).withOptions().interceptor(mySqlInterceptor).openSession();
Explanation:
Hibernate is using JDBC to communicate with the database. The interceptor will change the table name in the sql from an entity's name (in my case it's temp) to a real table name (temp1, temp2, ...) at runtime.
P.S., be careful when you use multi-thread.

Can I derive ARRAY_CONTAINS from query method name in Spring Data Couchbase Repository?

I have a Couchbase-Document "Group" with a list of group-members-names. I want to query for all groups of one person. I managed to do it with N1QL ARRAY_CONTAINS - see in code example - but i hoped that i could generate the query from the method name as it is usual in Spring Data.
Any help is appreciated :)
I tried
public List<MyGroup> findAllByMembers(String member); and public List<MyGroup> findByMembers(String member); but they just return an empty list - i guess they try to match the whole "members" value and don't recognize it as a list -, no errors.
Code
My Document with a List field
#Data
#Document
public class MyGroup {
private String name;
private List<String> members = new ArrayList<>();
}
My Repository
#RepositoryDefinition(domainClass = MyGroup.class, idClass = String.class)
public interface MyGroupRepository extends CouchbaseRepository<MyGroup, String> {
//#Query("#{#n1ql.selectEntity} WHERE ARRAY_CONTAINS(members,$1) AND #{#n1ql.filter}")
public List<MyGroup> findAllByMembers(String member);
}
Expected
Given a "group1" with "member1" in members.
repository.findAllByMembers("member1"); should return ["group1"].
Couchbase is limited by the Spring Data specification. Unfortunately, we can't simply add new behaviors to it (if you switch to a relational database, it has to work with no breaking points). So, whenever you need to query something that has a N1QL specific function/keyword, you have to write a query via #Query

What's the proper way of generating a unique field using Spring Boot / JPA?

First of all, I'm not taking about the primary id of the record. I'm talking about an field that is used by users to identify the record that's automatically generated but changeable by the user, not sequential and not a UUID. For example, starting with an account entity:
#Entity
#Data
class Account {
#Id
#GeneratedValue
private int id;
#Column(unique=true)
#NotNull
private String slug;
#Column
private String name;
}
and then I simply create a record:
#Autowired
private AccountRepository accountRepository;
Account account = new Account();
account.setName("ACME");
accountRepository.saveAndFlush(account);
At that point, the slug should have been generated, either completely randomly, or by doing something based on the name. How should that be done?
I know without locking the whole table it's impossible to ensure that the insertion won't result in an exception due to the uniqueness constrain being violated. I'm actually OK blocking the whole table or even letting the exception happen (you need a lot of requests per second fora conflict to happen between the check for availability and the insert).
If you separate the slug from the Account table and put it in a (id, slug) table by itself, you can generate the slug first (retrying until you succeed) and then persist the Account with a link to the just generated slug id.
You can't achieve this in a #PrePersist method, so your service needs to create the slug whenever you're creating an new Account. However it does simplify things on the application side (e.g. you don't need to wonder which constraint was violated when persisting an Account).
Depending on your other code, you can also get around locking the Account table and even the Slug table if you go for the optimistic approach.
A pseudo-code example of a service method that creates a new account (providing new Slug() creates the random slug):
#Autowired SlugRepository slugRepository;
#Autowired AccountRepository accountRepository;
public void createAccount(Account a) {
Slug s = null;
while(s == null) {
try {
s = slugRepository.save(new Slug());
} catch(Exception e) {
}
}
a.setSlug(s);
accountRepository.save(a);
}
I can think of JPA callbacks to generate the slug. In your case #PrePersist could be useful.
That said, why you need to make sure the value is available with a select before inserting the record, so the window for a collision to occur is tiny? You do have unique constraint on the column, right?
Update
Personally I would prefer to address it like this:
Use JPA callback #PrePersist when generating the the slug. Use to random UUID or timestamp to minimise the possibility of collision. No checking for collision as chances are minimal.
When updating the Account for user generated slug, always check first using query for collision. This check will offcourse happen in service update method itself.
This way I can be DB agnostic and also don't have to use repository/service in entity or listener classes.
I will do something like a separate Bean, helper or service class like this.
public class SlugService {
public String generateSlug(String slug)
{
if (accountRepo.getBySlug(slug) != null){ //check if it is already
return slug
} else {
slug.append("-"); //whatever the syntax
generateSlug();
}
}
public String makeSlug()
{
String slug = split by " ", replace by "_"(accountObject.getName);
generateSlug(slug)
}
}
Call the makeSlug(); method.

How to work with SQL Views in Spring JPA?

I'm currently working on a project with Spring and HANA DB.
I have already an Webservice which is working properly, but I need to select a View from my HANA DB(created outside of the Spring Webservice).
My view is made from 2 tables, 4 of its fields are SUM of one column from one of the tables(which counts how much of each of each 4 possible values and shows that as a number in each of the 4 fields).
I read about in some Spring JPA posts that it should work properly with a simple select, my questions are:
I usually make selects like this:
TypedQuery<name_of_the_model_class> query = em.createQuery("SELECT c FROM NameOfTheTable ORDER BY c.some_field DESC WHERE c.name =?1 ", name_of_the_model_class.class);
query.setParameter(1, name_to_search);
And then after that I just return the list to the Controller and the Controller shows that in a JSON.
The problem is, I have created this View in Eclipse, using directly SQL code in HANA DB, without creating it at the Webservice.
I would like to know, is there a way to select like I showed above, returning it without a model class? It would just need a class with exactly the same fields like a model class but without #Entity annotation on it?
OR is there a way to create a view like a model class in Spring JPA? If there is a way, what are the annotations to make it work?
Spring JPA would not accept me to return it without a class would it?
I read plenty posts but didn't found it, even in the Manual it doesn't give clear specification or examples.
Thanks a lot for your time, if I didn't explained it well pls tell me, I'm really stuck on this.
More info(my latest try):
My entire search function at the DAO:
public List<IncidentStatusResultsUser> getIncidentStatusByUser(String userID) throws ParseException {
List<IncidentStatusResultsUser> resultList = em.createQuery("SELECT c FROM USERSTATUS ORDER BY c.uploadDateTime DESC WHERE c.userID=?1 ").setMaxResults(48).getResultList();
Collections.reverse(resultList);
return resultList;
}
I have created a class to just receive the data with the same fields of the view:
public class IncidentStatusResultsUser {
private Date uploadDateTime ;
private int status1;
private int status2;
private int status3;
private int status4;
//All gets and sets ommited
public IncidentStatusResultsUser (Date uploadDateTime, long status1, long status2, long status3, long status4) {
this.uploadDateTime = uploadDateTime;
this.status1= status1;
this.status2= status2;
this.status3= status3;
this.status4= status4;
}
}
EDIT-2: Added public constructor inside the class.
Best Regards,
Famibica
You should be able to map your view to a JPA object as long as it has a primary key (or composite key). You can then map to a Spring JPA Reposiry so your query would go from -
TypedQuery<name_of_the_model_class> query = em.createQuery("SELECT c FROM NameOfTheTable ORDER BY c.some_field DESC WHERE c.name =?1 ", name_of_the_model_class.class);
query.setParameter(1, name_to_search);
To
findBySomeFieldOrderBySomeFieldDesc(String name_to_search)
If you want JPA returning a list of object directly, you need a DTO just like the IncidentStatusResultsUser you created. make sure you have a constructor taking all fields, something like:
public IncidentStatusResultsUser(Date uploadDateTime, int status1, int status2, int status3, int status4)
Then in your query String dtoQuery, you can do:
SELECT new IncidentStatusResultsUser(c.uploadDateTime, c.status1,c.status2,c.status3,c.status4)
FROM NameOfTheTable
ORDER BY c.some_field DESC
WHERE c.name =?1
Then you should be able to call your stuff:
List<IncidentStatusResultsUser> resultList = em.createQuery(dtoQuery).setMaxResults(48).getResultList();
Hope it works for you:)

Categories

Resources