Android Room: How to use embedded with a custom query - java

I have a PostDAO, that looks like this.
#Dao
public interface PostDAO extends DAOTemplate<Post> {
#Query("SELECT * FROM posts order by time DESC")
LiveData<List<Post>> getPosts();
}
And the Post Pojo being.
#Keep
#Entity(tableName = "posts")
open class Post : Serializable, Cloneable {
#NonNull
#PrimaryKey
var id: String? = null
var text: String? = null
var time: Long = 0
var uid: String? = null
#Embedded
var user: User? = null
public override fun clone(): Post {
return super.clone() as Post
}
}
As you can see, the User object is #Embedded
And User's DAO
#Dao
public interface UserDAO extends DAOTemplate<User> {
#Query("SELECT *,(SELECT sound_time FROM sounds WHERE sound_uid =:id AND " +
"(sound_time < strftime('%s', 'now'))) AS hasNewMusic " +
"FROM users WHERE user_uid = :id")
LiveData<User> getUser(String id);
}
And User Pojo
#Keep
#IgnoreExtraProperties
#Entity(tableName = "users")
class User : ModelTemplate() {
#NonNull
#PrimaryKey
#ColumnInfo(name = "user_uid")
override var id: String? = null;
var name: String? = null
var icon: String? = null
var hasNewMusic: Boolean = false
}
Now, I want the Embedded user field on the Post Object, to have the field, hasNewMusic, populated with a subQuery.
Tried the above, not working and not sure about if this is how to go about this.

First of all there is no Boolean in SQLite and Room (as I've read) maps 1 and 0 INTEGER values to Boolean's true and false (see Hardcode Boolean Query In Room Database). By the name I can guess sound_time is not bounded in 1 and 0 values. So, try to convert sound_time to it.
Also, I think you misused #Embedded annotation. It's used to put columns of one table into classes that can be grouped by some reason. For example here (#Embedded) is written:
So if you have a query that returns street, latitude, longitude, Room
will properly construct an Address class.
So you should either modify PostDAO::getPosts() method to return columns of a User, through INNER JOIN plus your giant select. And I don't guarantee this will work as I haven't done that. Also It's bad for some reasons.
Or you can leave Post without User field and make an Entity called PostWithUser and use a #Relation annotation. Wich is better and recommended in documentation.
UPD:
To fetch two entities try this:
Remove User field from post.
Add ForeignKey to Post. I'll use userId, if uid is already for
that use it instead:
#Entity(tableName = "posts",
foreignKeys = [ForeignKey(
entity = Post::class,
parentColumns = ["user_id"], //from ColumnInfo of User class
childColumns = ["userId"],
onDelete = CASCADE)],
indices = [Index(value = ["userId"]]))
class Post {
#PrimaryKey
var id: String? = null
var userId: String? = null
//...else code....
}
Make PostWithUser class:
class PostWithUser {
#Embedded lateinit var post: Post
#Embedded lateinit var user: User
}
Make PostWithUserDao:
class PostWithUserDao {
#Query("select * from post, user where post.userId = user.user_id")
fun getPostsWithUsers(): List<PostWithUser>
}
And as I didn't manage to fit sound_time subquery here, I would do it in a second query, but if you figure out how to do it, I think it can work.
Also see this: Room database with one-to-one relation

Related

Using arbitrary query as projection in spring data rest project

How it is possible to use arbitrary sql query (I mean native sql query) in some repository? My actual problem is this:
#Data //lombok thing
#Entity
public class A extends AuditModel {
private long id;
private String name;
#OneToMany(mappedBy="a") //Comments.a is owning side of association, i.e. comments table does have column called a_id as foreign key
#ToString.Exclude
private Set<Comments> comments = new HashSet();
#OneToMany(mappedBy="a") //SimpleFile.a is owning side of association
private Set<SimpleFile> comments = new HashSet();
}
Than I have my repository, which exposes nice crud interface using HAL+json representation. I am trying to enrich it with some projection/view particularly due to web UI to load one page data in single request. I am aware of excerps and projections, but they seems not to be enough powerful.
#Repository
#RepositoryRestResource
#Transactional(readOnly = true)
public interface ARepository extends PagingAndSortingRepository<A, Long> {
Page<A> findByNameContaining(String namePart, Pageable pageable);
#Query(
value = "SELECT a.name,\n" +
"(SELECT CAST(count(ac.id) AS int) FROM COMMENTS ac WHERE ac.a_id = a.id),\n" +
"(SELECT listagg(asf.id) FROM SIMPLE_FILES asf WHERE asf.a_id = a.id)\n" +
"FROM AS a\n" +
"WHERE a.id = :id",
nativeQuery = true
)
Optional<ACustomPage42DTO> getByIdProjectedForScreen42(Long id);
}
I have also tried to use JPQL, but there I had problem with fetch join (as I am not familiar with JPQL). My last evaluation query was something like this:
#Query("SELECT new sk.qpp.qqq.documents.projections.ACustomPage42DTO(" +
"a " +
"(SELECT CAST(count(ac) AS int) FROM COMMENTS ac WHERE ac.a = a)" +
")\n" +
"FROM A a\n" +
"LEFT JOIN FETCH a.simpleFiles\n" +
"WHERE a.id = :id"
)
I would like to get some general advice about what approach is best to implement custom and complex query to be returned in DTO (ideally with some specific links to actions when needed).
PS: Implementing interface and returning simple (primitive) data works. Also using JPQL to create custom DAO instance works (with simple types and with single instance of type A for example). Method for using given query method does appear in search methods of given entity endpoint. I would like to have something more reasonable, so I would like to have projection as defined in spring data rest project.
I have my DTO object fully under my control. I prefer it to use #Value or #Data annotation from project lombok, but it is not a need. I have tried also these versions of DTO definition (using interface works for simple data and similarly class works for simple data).
interface ACustomPage42DTO {
String getName();
long getCommentsCount();
Object getAsdf();
}
Or using equivalent class with some bonus, like custom toString() method possible, or some custom getter for computed data:
#Value //lombok thing, imutable "POJO"
public class ACustomPage42DTO {
String name;
long commentsCount;
Set<SimpleFile> simpleFiles;
public ACustomPage42DTO(A a, long count) {
// constructor used by JPQL, if it works
name = a.getName();
this.commentsCount = count;
this.simpleFiles = a.getSimpleFiles(); // should be already fetched, due to fetch join in JPQL
}
}
Both working approaches can be called using "search" url, instead of projection. I see my method getByIdProjectedForScreen42 on url http://localhost:9091/api/a/search listing. I would like to use it like (I think that is the "right" way) http://localhost:8080/api/a?projection=ACustomPage42DTOProjection .
Question is quite broad and touches couple of aspects:
custom JPA repository method using #Query
selecting results in your #Query
mapping #Query results to an interface
exposing new repository method through #RepositoryRestResource
TLDR: wrote an example of what is talked about with couple of basic tests https://github.com/ivarprudnikov/test-spring-jpa-repository-query-exposed-through-http
custom JPA repository method using #Query
As you have mentioned it is quite straightforward, just annotate a method with #Query and make sure your return type corresponds to what is being returned from the query, eg:
public interface FooRepository extends JpaRepository<FooEntity, Long> {
#Query(nativeQuery = true, value = "select f from foo f where f.name = :myParam")
Optional<FooEntity> getInSomeAnotherWay(String myParam);
}
selecting results in your #Query
You have given an example already but I'll simplify to make it easier and shorter.
Given entities FooEntity.java and BarEntity.java:
#Entity
#Table(name = "foo")
public class FooEntity {
#Id
#Column(name = "id", unique = true, nullable = false)
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "name", nullable = false)
private String name;
#OneToMany(mappedBy = "foo")
private Set<BarEntity> bars = new HashSet<>();
// getter setters excluded for brevity
}
#Entity
#Table(name = "bar")
public class BarEntity {
#Id
#Column(name = "id", unique = true, nullable = false)
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "name", nullable = false)
private String name;
#ManyToOne(targetEntity = FooEntity.class)
#JoinColumn(name = "foo_id", nullable = false, foreignKey = #ForeignKey(name = "fk_bar_foo"))
private FooEntity foo;
// getter setters excluded for brevity
}
We want now to return custom result set which contains FooEntity.name and count of FooEntity.bars:
SELECT f.name as name, count(b.id) as barCount FROM foo f, bar b WHERE f.id = :id AND b.foo_id = :id
+-----------------+----------+
| name | barCount |
+-----------------+----------+
| Jonny tables | 1 |
+-----------------+----------+
mapping #Query results to an interface
To map above result set we need an interface where getters nicely reflect what is being selected:
public interface ProjectedFooResult {
String getName();
Long getBarCount();
}
Now we can rewrite our repository method to:
#Query(nativeQuery = true,
value = "SELECT f.name as name, count(b.id) as barCount FROM foo f, bar b WHERE f.id = :id AND b.foo_id = :id")
Optional<ProjectedFooResult> getByIdToProjected(Long id);
exposing new repository method through #RepositoryRestResource
I am not very familiar with this but after adding org.springframework.data:spring-data-rest-hal-browser dependency I got this nice interface that exposed available methods after repository was annotated with #RepositoryRestResource. For a given repository which contains above mentioned details:
#RepositoryRestResource(path = "foo")
public interface FooRepository extends JpaRepository<FooEntity, Long> {
#Query(nativeQuery = true, value = "SELECT f.name as name, count(b.id) as barCount FROM foo f, bar b WHERE f.id = :id AND b.foo_id = :id")
Optional<ProjectedFooResult> getByIdToProjected(Long id);
}
the method will be exposed through http://localhost:8080/foo/search/getByIdToProjected?id=1 when running locally.
As mentioned above the reference implementation is on Github https://github.com/ivarprudnikov/test-spring-jpa-repository-query-exposed-through-http
Additional helpful documentation for 'Custom Implementations for Spring Data Repositories'

Merge multiple Spring JPA Page objects results

My initial requirement was to fetch Car details as:
List<String> carMake = getUserCarMake();
Page<Car> carDetails = carRepository.findAllCarsInfo(carMake, pageRequest);
CarRepository.java
#Query(value="SELECT A FROM Cars A WHERE A.model = ?1
public Page<Cars> findAllCarsInfo(List<String> carMake, Pageable pageRequest);
Now my requirement has changed to fetch car details based car models for each car make. So I have changed the code as shown
for (Cars car : userCars) {
String carMake = car.getCarMake();
List<String> carModelForMake = new ArrayList<>();
List <CarModels> carModelList = car.getCarModels();
for (CarModels carModel : carModelList) {
carModelForMake.add(carModel.getModelName());
Page<Car> carDetails = carRepository.findAllCarsInfo(carModelForMake, carMake, pageRequest)
}
}
CarRepository.java
#Query(value="SELECT A FROM Cars A WHERE A.model IN ?1 AND A.make = ?2”
public Page<Car> findAllCarsInfo(List<String> carModel, String carMake,Pageable pageRequest);
So for each car i have a carMake and corresponding carModels for that make which i then pass to the query to fetch carDetails which is a Page Object. As a result same query is called multiple times for different carMake.
The problem is how do I manage the Page object here. In the above scenario the Page object will contain only the details of last car from the carModelList, rest will be overwritten as I do not
have an option of carDetails.addAll() as in case of List.
Note: I cannot use the below query as the model can overlap across different makes.
SELECT A FROM Cars A WHERE A.model IN ?1 AND A.make IN ?2
Also my pageRequest has size as (0, 20 )
I have tried to modify the query to remove pageRequest and use findAll to fetch the results in List and then convert them to PageObject but that breaks the pagination because the page.getContent() has the entire result set and not just 20 records.
Page<Car> carDetails = new PageImpl <>(carDetailsList, pageRequest, carDetailsList.size());
How can I effectively get Page object or merge different page objects here so that my pagination works as it did in my previous requirement.
Sometimes it is a good idea to create a special "query entity" class that includes everything that is needed to respond to a certain kind of client request.
The general idea is like this:
Let's say you'd have two classes in you domain:
#Entity
#Table(name = "table_a")
public class A {
#Id int id;
String propertyA;
int bId;
}
#Entity
public class B {
#Id int id;
String propertyB;
}
And then you'd combine the two two the mentioned "query entity" (outside of the domain).
#Entity
#Table(name = "table_a")
public class QueryEntity {
private #Id private int aId;
private String propertyA;
private B b;
public String propertyA() {
return propertyA;
}
public String propertyB() {
return b.propertyB;
}
}
I'm not quite sure whether this approach is applicable in your case, but hopefully it makes the idea clear.

what to use instead of fetch.EAGER in spring data?

I'm working with spring-boot and angular5 , i have this entity in spring :
#Entity
#Data
#AllArgsConstructor
#NoArgsConstructor
public class Contrat implements Serializable{
#Id #GeneratedValue
private Long id;
private Date dateDebut ;
private Date dateFin ;
#ManyToOne
#JoinColumn(name = "Id_Project")
#JsonBackReference(value="projet-contrat")
private Project project;
#ManyToOne
#JoinColumn(name = "Id_AppUser")
#JsonBackReference(value="appuser-contrat")
private AppUser appUser;
}
A repository :
public interface ContratRepo extends JpaRepository<Contrat,Long> {
public Page<Contrat> findByAppUser(#Param("userApp") AppUser userApp, Pageable pageable);
}
As the fetch.lazy is the default one , when i try to call the method findByAppUser i get as result :
{id: 1, dateDebut: 1526083200000, dateFin: 1526083200000}
Which is normal , what i want for my case is to load also the object 'project' that exists in the entity , but i don't wan't to use the fetch.EAGER , any solution for this goal ?
Your entity is one-many relationship object. If you don't use EAGER, spring data will get the object without related member object. And if you get that with contract.getProject().getName(), then another query will be sent to get that member.
If you log the SQL, you can see that, there will be 2 queries. But if you set the field as EAGER, there will be only 1 query. You can get improvement obviously.
But you should not use EAGER always. If in 90% of time, you just need the Contract object, but no need the project data of it. It is a waste of time to get that. Because in SQL, it will relate 2 tables and get all columns of data.
SO, you should make this decision based on your usage of this entity.
[Updated based on comment]
You can use Query to write your sql expression. for example, I have a method to get the entity with detail:
#Query("select s from Contract s left join fetch s.project pr where s.id = ?1 ")
Contract findOneWithDetail(Long id);
If I need to get the detail in ONE sql, I can use this method. If I don't need the project detail, I just use findOne(Long id), which is provided interface.
And, if you just want to get some columns, you need to define a DTO, with a constructor, and write your method like this:
#Query("SELECT NEW com.mypackage.dto.ContractDTO(s.id, s.name, s.status) FROM Contract AS s WHERE s.status = ?1")
List<ContractDTO> findDTOAllByStatus(String status);
Provide the query in your repo method, e.g. (syntax may be wrong, just show you the idea)
public interface ContratRepo extends JpaRepository<Contrat,Long> {
#Query(query="from Contrat c left join fetch c.project " +
"where c.userApp = :userApp")
public Page<Contrat> findByAppUser(#Param("userApp") AppUser userApp, Pageable pageable);
}

Is it possible using String as PrimaryKey in Android Room

I use uuid in my java backend server. So I need to use that uuid in room android to make sure entities are sync'd properly.
I am aware of Is it possible to apply primary key on the text fields in android database and text as primarykey in android. I want to create something like this
#Entity(tableName = CrewColumns.TABLE_NAME)
#TypeConverters(BigDecimalConverter::class)
#JsonIgnoreProperties(ignoreUnknown = true)
class Crew() {
constructor (uuid: String) : this() {
this.uuid = uuid;
}
/**
* The unique ID of the item.
*/
#PrimaryKey
#ColumnInfo(name = CrewColumns.UUID)
var uuid: String = ""
#ColumnInfo(name = CrewColumns.NAME)
var name: String = ""
}
Will it be a problem with Room (DAO etc)? Thank you.
Yes, you can use a String as a #PrimaryKey.
Additionally, I also recommend making use of Kotlin's data class to simplify your entities. For example:
#Entity
data class Crew(#PrimaryKey val uuid: String, val name: String) {
// Put any functions or other members not initialized by the constructor here
}

ORMLite - OneToOne relation

I got next database structure with OneToOne relation:
[company]
company_id (PK)
company_name
[company_configuration]
company_configuration_id (Autoincrement, PK)
company_id (UNIQUE KEY,FK)
company_configuration_v
I have been using ORMlite and I have next classes for this two tables:
#DatabaseTable(tableName = "company")
public class Company {
public static final String ID_COMPANY = "company_id";
public static final String COMPANY_NAME = "company_name";
#DatabaseField(generatedId = true, columnName = ID_COMPANY)
private int idCompany;
#DatabaseField(columnName = COMPANY_NAME)
private String companyName;
#DatabaseTable(tableName = "company_configuration")
public class CompanyConfiguration {
public static final String COMPANY_CONFIGURATION_ID = "company_configuration_id";
public static final String COMPANY_ID = "company_id";
public static final String COMPANY_CONFIGURATION_V = "company_configuration_v";
#DatabaseField(generatedId = true, columnName = COMPANY_CONFIGURATION_ID)
private int idCompanyConfiguration;
#DatabaseField(foreign = true,foreignAutoRefresh = true, columnName = COMPANY_ID)
private Company companyId;
#DatabaseField(columnName = COMPANY_CONFIGURATION_V)
private String companyConfigurationV;
Here is OneToOne relation because I want to divide a table with many columns.
As you can see in the example above, there is not relation from Company class to CompanyConfiguration class.
I know that I can add this snippet of code(examle below) into Company class, but I don't need a #ForeignCollectionField becaues the collection will contain only one CompanyConfiguration object:
#ForeignCollectionField()
private ForeignCollection<CompanyConfiguration> companyConfigurations;
I need to add something like this (examle below) into Company class and will get the reference from Company class to CompanyConfiguration class:
#OneToOne(targetEntity = CompanyDbConfig.class)
#JoinTable(name = "company_configuration")
#JoinColumn(name = "id_company")
CompanyConfiguration companyConfiguration;
Shortly, I want to get Company object using ORMlite. See the example below. After fetching company from the database, I want to have and CompanyConfiguration object within company object.
Company company = daoCompany.queryForId(id); //daoCompany is an instance of ORMlite Dao class
Is it possible and how to do that using ORMlite?
I posted an OrmLite question myself so I looked through the unanswered questions to see if there was anything I could answer. Even though this is an old topic, I wanted to take a stab at it in case it could help someone.
I've read your post a few times and I think you're asking how to load the information from two tables into one model. You're separating a rather large table into two in the database but you want it to come back as one model. If that is correct, here's my take on the code. This assumes you want to use objects to build the query instead of passing in a query string.
public class CompanyResult
{
public long CompanyId { get; set; }
public long ConfigurationId { get; set; }
public string Name { get; set; }
public string ConfigurationV { get; set; }
}
var query = _db.From<Company>
.Join<CompanyConfiguration>((c, cc) => c.idCompany == cc.idCompany)
.Where(c => c.idCompany == companyId)
.Select<CompanyConfiguration>((c, cc) = new {
CompanyId = c.idCompany,
ConfigurationId = cc.idCompanyConfiguration,
Name = c.companyName,
ConfigurationV - cc.companyConfigurationV
});
var results = _db.Single<CompanyResult>(query);
You'd keep your existing models so they could be used as DTOs. You'd just be using the new model model above to pass back the exact properties you want.
*I wrote this in Notepad++, forgive any typos.

Categories