Return type of Query function in Android Room Database? - android-room

I have a simple question but I just can't find any documentation anywhere.
Suppose we have the below example, which can be found here.
#Dao
public interface UserDao {
#Query("SELECT * FROM User WHERE userId = :id")
public User getById(int id);
}
I notice the return type of the function is User, but what happens if there query doesn't return a result? (i.e. there is no user in the table with the specified id)
Will the function simply return null?
Also, if this were a Kotlin function, should the signature be the following with nullable type User?
#Query("SELECT * FROM User WHERE userId = :id")
fun getById(int id): User?

When documentation is not enough, reading sources could help.
Whatever you use (Java or Kotlin) describing Dao interface - Dao class implementation in Java (generated by Room under the hood) would be the same:
public User getById(int id) {
...
final Cursor _cursor = DBUtil.query(__db, "SELECT * FROM User ...", false, null);
.....
if(_cursor.moveToFirst()) {
// If there is at least one record
_result = new User(<...User's fields gotten from query...>);
} else {
// If there is no record
_result = null; // <---- here it is!
}
return _result;
...
}
Also, if this were a Kotlin function, should the signature be the following with nullable type User?
Technically you could omit ?, but it could mislead in reading code, so better use User? explicitly.

Related

QueryDSL Predicate for use with JPARepository where field is a JSON String converted using an AttributeConverter to a List<Object>

I have a JPA Entity (Terminal) which uses an AttributeConverter to convert a Database String into a list of Objects (ProgrmRegistration). The converter just uses a JSON ObjectMapper to turn the JSON String into POJO objects.
Entity Object
#Entity
#Data
public class Terminal {
#Id
private String terminalId;
#NotEmpty
#Convert(converter = ProgramRegistrationConverter.class)
private List<ProgramRegistration> programRegistrations;
#Data
public static class ProgramRegistration {
private String program;
private boolean online;
}
}
The Terminal uses the following JPA AttributeConverter to serialize the Objects from and to JSON
JPA AttributeConverter
public class ProgramRegistrationConverter implements AttributeConverter<List<Terminal.ProgramRegistration>, String> {
private final ObjectMapper objectMapper;
private final CollectionType programRegistrationCollectionType;
public ProgramRegistrationConverter() {
this.objectMapper = new ObjectMapper().setSerializationInclusion(JsonInclude.Include.NON_EMPTY);
this.programRegistrationCollectionType =
objectMapper.getTypeFactory().constructCollectionType(List.class, Terminal.ProgramRegistration.class);
}
#Override
public String convertToDatabaseColumn(List<Terminal.ProgramRegistration> attribute) {
if (attribute == null) {
return null;
}
String json = null;
try {
json = objectMapper.writeValueAsString(attribute);
} catch (final JsonProcessingException e) {
LOG.error("JSON writing error", e);
}
return json;
}
#Override
public List<Terminal.ProgramRegistration> convertToEntityAttribute(String dbData) {
if (dbData == null) {
return Collections.emptyList();
}
List<Terminal.ProgramRegistration> list = null;
try {
list = objectMapper.readValue(dbData, programRegistrationCollectionType);
} catch (final IOException e) {
LOG.error("JSON reading error", e);
}
return list;
}
}
I am using Spring Boot and a JPARepository to fetch a Page of Terminal results from the Database.
To filter the results I am using a BooleanExpression as the Predicate. For all the filter values on the Entity it works well, but the List of objects converted from the JSON string does not allow me to easily write an Expression that will filter the Objects in the list.
REST API that is trying to filter the Entity Objects using QueryDSL
#GetMapping(path = "/filtered/page", produces = MediaType.APPLICATION_JSON_VALUE)
public Page<Terminal> findFilteredWithPage(
#RequestParam(required = false) String terminalId,
#RequestParam(required = false) String programName,
#PageableDefault(size = 20) #SortDefault.SortDefaults({ #SortDefault(sort = "terminalId") }) Pageable p) {
BooleanBuilder builder = new BooleanBuilder();
if (StringUtils.isNotBlank(terminalId))
builder.and(QTerminal.terminal.terminalId.upper()
.contains(StringUtils.upperCase(terminalId)));
// TODO: Figure out how to use QueryDsl to get the converted List as a predicate
// The code below to find the programRegistrations does not allow a call to any(),
// expects a CollectionExpression or a SubqueryExpression for calls to eqAny() or in()
if (StringUtils.isNotBlank(program))
builder.and(QTerminal.terminal.programRegistrations.any().name()
.contains(StringUtils.upperCase(programName)));
return terminalRepository.findAll(builder.getValue(), p);
}
I am wanting to get any Terminals that have a ProgramRegistration object with the program name equal to the parameter passed into the REST service.
I have been trying to get CollectionExpression or SubQueryExpression working without success since they all seem to be wanting to perform a join between two Entity objects. I do not know how to create the path and query so that it can iterate over the programRegistrations checking the "program" field for a match. I do not have a QProgamRegistration object to join with, since it is just a list of POJOs.
How can I get the predicate to match only the Terminals that have programs with the name I am searching for?
This is the line that is not working:
builder.and(QTerminal.terminal.programRegistrations.any().name()
.contains(StringUtils.upperCase(programName)));
AttributeConverters have issues in Querydsl, because they have issues in JPQL - the query language of JPA - itself. It is unclear what actually the underlying query type of the attribute is, and whether the parameter should be a basic type of that query type, or should be converted using the conversion. Such conversion, whilst it appears logical, is not defined in the JPA specification. Thus a basic type of the query type needs to be used instead, which leads to new difficulties, because Querydsl can't know the type it needs to be. It only knows the Java type of the attribute.
A workaround can be to force the field to result into a StringPath by annotating the field with #QueryType(PropertyType.STRING). Whilst this fixes the issue for some queries, you will run into different issues in other scenarios. For more information, see this thread.
Although the following desired QueryDsl looks like it should work
QTerminal.terminal.programRegistrations.any().name().contains(programName);
In reality JPA would never be able to convert it into something that would make sense in terms of SQL. The only SQL that JPA could convert it into could be as follows:
SELECT t.terminal_id FROM terminal t where t.terminal_id LIKE '%00%' and t.program_registrations like '%"program":"MY_PROGRAM_NAME"%';
This would work in this use case, but be semantically wrong, and therefore it is correct that it should not work. Trying to select unstructured data using a structured query language makes no sense
The only solution is to treat the data as characters for the DB search criteria, and to treat it as a list of Objects after the query completes and then perform filtering of the rows in Java. Although This makes the paging feature rather useless.
One possible solution is to have a secondary read only String version of the column that is used for the DB search criteria, that is not converted to JSON by the AttributeConverter.
#JsonIgnore
#Column(name = "programRegistrations", insertable = false, updatable = false)
private String programRegistrationsStr;
The real solution is do not use unstructured data when you want structured queries on that data Therefore convert the data to either a database that supports the JSON natively for queries or model the data correctly in DDL.
To have a short answer: the parameter used in the predicate on attribute with #QueryType must be used in another predicate on attribute of type String.
It's a clearly known issue describe in this thread: https://github.com/querydsl/querydsl/issues/2652
I simply want to share my experience about this bug.
Model
I have an entity like
#Entity
public class JobLog {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private String id;
#QueryType(PropertyType.STRING)
private LocalizedString message;
}
Issue
I want to perform some predicate about message. Unfortunately, with this configuration, I can't do this:
predicates.and(jobLog.message.likeIgnoreCase(escapedTextFilter));
because I have the same issues that all people!
Solution
But I find a way to workaround :)
predicates.and(
(jobLog.id.likeIgnoreCase(escapedTextFilter).and(jobLog.id.isNull()))
.or(jobLog.message.likeIgnoreCase(escapedTextFilter)));
Why it workaround the bug?
It's important that escapedTextFilter is the same in both predicate!
Indeed, in this case, the constant is converter to SQL in the first predicate (which is of String type). And in the second predicate, we use the conterted value
Bad thing?
Add a performance overflow because we have OR in predicate
Hope this can help someone :)
I've found one way to solve this problem, my main idea is to use mysql function cast(xx as char) to cheat hibrenate. Below is my base info. My code is for work , so I've made an example.
// StudentRepo.java
public interface StudentRepo<Student, Long> extends JpaRepository<Student, Long>, QuerydslPredicateExecutor<Student>, JpaSpecificationExecutor<Student> {
}
// Student.java
#Data
#AllArgsConstructor
#NoArgsConstructor
#EqualsAndHashCode(of = "id")
#Entity
#Builder
#Table(name = "student")
public class Student {
#Convert(converter = ClassIdsConvert.class)
private List<String> classIds;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
}
// ClassIdsConvert.java
public class ClassIdsConvert implements AttributeConverter<List<String>, String> {
#Override
public String convertToDatabaseColumn(List<String> ips) {
// classid23,classid24,classid25
return String.join(",", ips);
}
#Override
public List<String> convertToEntityAttribute(String dbData) {
if (StringUtils.isEmpty(dbData)) {
return null;
} else {
return Stream.of(dbData.split(",")).collect(Collectors.toList());
}
}
}
my db is below
id
classIds
name
address
1
2,3,4,11
join
北京市
2
2,31,14,11
hell
福建省
3
2,12,22,33
work
福建省
4
1,4,5,6
ouy
广东省
5
11,31,34,22
yup
上海市
-- ----------------------------
-- Table structure for student
-- ----------------------------
DROP TABLE IF EXISTS `student`;
CREATE TABLE `student` (
`id` int(11) NOT NULL,
`classIds` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`address` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
SET FOREIGN_KEY_CHECKS = 1;
Use JpaSpecificationExecutor solve the problem
Specification<Student> specification = (root, query, criteriaBuilder) -> {
String classId = "classid24"
String classIdStr = StringUtils.wrap(classId, "%");
var predicate = criteriaBuilder.like(root.get("classIds").as(String.class), classIdStr);
return criteriaBuilder.or(predicate);
};
var students = studentRepo.findAll(specification);
log.info(new Gson().toJson(students))
attention the code root.get("classIds").as(String.class)
In my opinion, if I don't add .as(String.class) , hibernate will think the type of student.classIds is list and throw an Exception as below.
SQL will like below which runs correctly in mysql. But hibnerate can't work.
org.springframework.dao.InvalidDataAccessApiUsageException: Parameter value [%classid24%] did not match expected type [java.util.List (n/a)]; nested exception is java.lang.IllegalArgumentException: Parameter value [%classid24%] did not match expected type [java.util.List (n/a)]
SELECT
student0_.id AS id1_0_,
student0_.class_ids AS class_ids2_0_
FROM
student student0_
WHERE
student0_.class_ids LIKE '%classid24%' ESCAPE '!'
if you add .as(String.class) , hibnerate will think the type of student.classIds as string and won't check it at all.
SQL will be like below which can run correct in mysql. Also in JPA.
SELECT
student0_.id AS id1_0_,
student0_.class_ids AS class_ids2_0_
FROM
student student0_
WHERE
cast( student0_.class_ids AS CHAR ) LIKE '%classid24%' ESCAPE '!'
when the problem is solved by JpaSpecificationExecutor, so I think this can be solve also in querydsl. At last I find the template idea in querydsl.
String classId = "classid24";
StringTemplate st = Expressions.stringTemplate("cast({0} as string)", qStudent.classIds);
var students = Lists.newArrayList<studentRepo.findAll(st.like(StringUtils.wrap(classId, "%"))));
log.info(new Gson().toJson(students));
it's sql is like below.
SELECT
student0_.id AS id1_0_,
student0_.class_ids AS class_ids2_0_
FROM
student student0_
WHERE
cast( student0_.class_ids AS CHAR ) LIKE '%classid24%' ESCAPE '!'

Can a graphql mutation be wired inside a DataFetcher class along with query?

Error:FieldUndefined: Field 'createUser' in type 'Query' is undefined # 'createUser'"
#Service
public class GraphQlService {
#Value("classpath:schema.graphql")
Resource resource;
private GraphQL graphQL;
#Autowired
UserDataFetcher userFetcher;
#Autowired
PostDataFetcher postFetcher;
#PostConstruct
public void loadSchema() throws IOException {
File schemaFile = resource.getFile();
TypeDefinitionRegistry typeRegistry = new SchemaParser().parse(schemaFile);
RuntimeWiring wiring = buildRuntimeWiring();
GraphQLSchema schema = new SchemaGenerator().makeExecutableSchema(typeRegistry, wiring);
graphQL = GraphQL.newGraphQL(schema).build();
}
private RuntimeWiring buildRuntimeWiring() {
return RuntimeWiring.newRuntimeWiring()
.type("Query", typeWiring -> typeWiring
.dataFetcher("user", userFetcher)
.dataFetcher("post", postFetcher))
.type("Mutation", typeWiring -> typeWiring
.dataFetcher("createUser", userFetcher))
.build();
}
public GraphQL getGraphQL() {
return graphQL;
}
}
1. Cant I use common datafetcher/reslover for both Query and Mutation
as I have done below in a single class.It is not able to find
createUser?
#Component
public class UserDataFetcher implements DataFetcher<List<User>> {
#Autowired
UserRepository userRepository;
public User createUser(DataFetchingEnvironment environment) {
String username = environment.getArgument("username");
String location= environment.getArgument("location");
User[] follower = environment.getArgument("followers");
User[] following = environment.getArgument("following");
Post[] pos = environment.getArgument("posts");
User user = new User();
user.setUsername(username);
user.setFollowers(follower);
user.setFollowing(following);
user.setLocation(location);
user.setPosts(pos);
return userRepository.save(user);
}
#Override
public List<User> get(DataFetchingEnvironment environment) {
return userRepository.findAll();
}
}
//SDL below for schema
schema {
query: Query
mutation: Mutation
}
type User{
id:ID!
username:String!
followers:[User]
following:[User]
location:String!
posts:[Posts]
}
type Post{
id: ID
title: String!
author: User!
}
type Query{
user: [User]
post: [Post]
}
type Mutation{
createUser(username: String!, location: String!,followers: [User],following:[User],posts:[Post]):User
}
2. Is the schema correct because it would say User and Post are not mentioned as InputType. I tried InputType for User and Post but
couldnt get it working.How should correct schema for storing
followers and following look like ?
1) No, each DataFetcher implementation can do a single operation only - that's the get method the interface specifies. How would the framework know to call the createUser method?
2) No, the schema is not valid. You can not use User as an argument (input) type. You need to define a separate input type, e.g.
input UserInput {
username:String!
followers:[ID!]
following:[ID!]
location:String!
}
Just think, if you could make User the input, how would you provide the mandatory ID? What if User implements an interface, since interfaces are not defined for inputs? Imagine if User had a recursive field type. In output, that would be fine (as you control the nesting level directly), but would be impossible to satisfy for an input type.
The rules for inputs and output are quite different, so the types need to be defined separately.
Also, in this example, only the IDs are optionally expected for the followers or the followed, instead of the full objects, as it often makes more sense.

jpa pass reserved words in #Param String

My goal is to write a query like:
select * from Book where author = any(select author from book)
and(genre='comedy') order by ( '' )ASC, ( pages )DESC;
Where 'any(select author from book)' loses the single quotes so I can pass it thus
#Query("select b from Book b where b.author =:author and b.genre =:genre")
List<Book> findAllBySearch(#Param("author") String author,
#Param("genre") String genre);
The reason for doing this is because I have a form with 5 input criteria that may or may not be present and I don't want to write a separate query for each permutation. I know that one query with either the 'stringInput' or 'any(select criteria from book)' in case or null or empty string inserted before running the query.
I suppose using criteria API or something like that would allow building dynamic Query or inserting reserved sql words but I don't know how to implement it easily since I'm extending Spring data CrudRepository not using entitymanager...yet.... I suppose I will have to.
Does anyone know how to escape the '' imposed by string input #Param or what approach would easily work... such as Criteria API, Stored Procedure, Function ?
Please excuse my inexperience here...
Thanks !
It looks like the only way to do this with 5 possible inputs is to have the runtime strategy choose from 14 different queries depending on what inputs are present but I was trying to avoid this !!
So... here's the solution !
/**
* A JPA-based implementation of the Book Service. Delegates to a JPA entity
* manager to issue data access calls against the backing repository. The
* EntityManager reference is provided by the managing container (Spring)
* automatically.
*/
#Service("bookService")
#Repository
public class JpaBookService implements BookService {
private EntityManager em;
#PersistenceContext
public void setEntityManager(EntityManager em) {
this.em = em;
}
public List<Book> searchBooks(String author, String genre, String pages, String year, String rating){
List<Predicate> predList = new LinkedList<Predicate>();
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Book> c =cb.createQuery(Book.class);
Root<Book> b = c.from(Book.class);
if(author!="")
predList.add(cb.equal(b.get("author"), author));
}
if(genre!=""){
predList.add(cb.equal(b.get("genre"), genre));
}
if(pages!=""){
predList.add(cb.equal(b.get("pages"), pages));
}
if(year!=""){
predList.add(cb.equal(b.get("year"), year));
}
if(rating!=""){
predList.add(cb.equal(b.get("rating"), rating));
}
Predicate[] predArray = new Predicate[predList.size()];
predList.toArray(predArray);
c.where(predArray);
TypedQuery<Book> q = em.createQuery(c);
List<Book> result = q.getResultList();
return result;
}
}
Works good !
Johnny O.

Cannot implicitly convert type 'string' to Model

I keep getting these errors:
Error 1 Cannot implicitly convert type 'string' to 'NSWebSite.Models.Role'
public Role GetRoleForUser (User user)
{
if (!UserExists(user))
throw new ArgumentException(MissingUser);
return user.Roles.TargetRoleName;
}
Error 2 'NSWebSite.Models.User' does not contain a definition for 'RoleID'
User newUser = new User()
{
Name = name,
Password = FormsAuthentication.HashPasswordForStoringInConfigFile(
password.Trim(), "md5"),
Email = email,
RoleID = role.Id
};
Error 1 - I have no idea how to fix, so any advice is welcome.
Error 2 - I am getting it because my user model does not contain the definition for RoleID. If I want to set the role when I create a new user, what should I put here instead?
Below is my repository file (modified from here: brianleggDOTcom/post/2011/05/09/Implementing-your-own-RoleProvider-and-MembershipProvider-in-MVC-3.aspx) - DOT=. (dam antispam measures :-) )
http://www.mediafire.com/?gey4y9ub0v2u9nh
and my Model.Designer.cs file
http://www.mediafire.com/?qa3p9we8uqwfj09
Your Error #2 simply means that your User class has no definition for a RoleID property. Look at the class definition and you can define it there.
Your Error #1 looks like you have a method with a return type of Role, and you're trying to do:
return user.Roles.TargetRoleName; // this is a string???
If that string is what you do want to return, you'll need to modify your method like so:
string YourMethodName(...any parameters you have...)
{
// ...your code here
return SomeRole;
}
Or you can change your TargetRoleName property from type string to Role.
My first question would be: Why is this all in the same class?
I would start by sorting the classes: Role, User, etc.
You need to define what a Role is and what a User is, separately.
public class User {
private String name;,
private String password;
private String email;
private RoleID role;
public User(String name, String password, String email, RoleID role) {
this.name = name;
this.password = password;
this.email = email;
this.role = role;
}
public void someOtherMethod() {
//some code here, etc
}
}
I am kinda new to this part of MVC and C#, from the original example at: https://www.brianlegg.com/post/2011/05/09/Implementing-your-own-RoleProvider-and-MembershipProvider-in-MVC-3.aspx
I had changed the DB schema from a 1 to many for user and roles to a many to many relationship. Changing the DB scheme back and updating the entity schema made everything work again like from the original blog post.
Why the original poster did not separate the two classes I am not sure, but once I get things working like I want I will start looking at cleaning up the code. If some one has any suggestions about how to make the example from the above mentioned webpage work with a many to many relationship for the User and Roles, they will be greatly appreciated :-)

Hibernate Criteria API Equivalent to Oracle's Decode

What would the equivalent of Oracle's DECODE() function be in the Hibernate Criteria API?
An SQL example of what I need to do:
SELECT DECODE(FIRST_NAME, NULL, LAST_NAME, FIRST_NAME) as NAME ORDER BY NAME;
Which returns LAST_NAME to NAME in the event that FIRST_NAME is NULL.
I would prefer to use the Criteria API but could use HQL if there's no other way.
Check out org.hibernate.criterion.Projections.sqlProjection(...).
Similar to this answer.
For the example you give, you could use COALESCE().
How to simulate NVL in HQL
You can use sqlRestriction to call the native decode function.
session.createCriteria(Table.class).add(Restrictions.sqlRestriction("decode({alias}.firstName,null,
{alias}.lastName,
{alias}.firstName)"))
With HQL, the Oracle dialect already has coalesce and nvl functions, or if you really need decode, you could subclass the dialect and add it as a custom function. I don't know if Hibernate supports a variable length number of arguments like decode does, but worst-case, you could create decode1, decode2, etc to support different numbers of arguments.
Or, if you aren't using the column in a where or group by, you could just bring both attributes back and do the check in Java.
Ended up adding a formula for it:
<property name="name" formula="coalesce(first_name, last_name)"/>
I'm concerned about cross-database problems and possibly efficiency problems with this approach so I'm willing to change the accepted answer.
You can Use Hibernate #Type attribute,Based on your requirement you can customize the annotation and apply on top of the fied. like :
public class PhoneNumberType implements UserType {
#Override
public int[] sqlTypes() {
return new int[]{Types.INTEGER, Types.INTEGER, Types.INTEGER};
}
#Override
public Class returnedClass() {
return PhoneNumber.class;
}
// other methods
}
First, the null SafeGet method:
#Override
public Object nullSafeGet(ResultSet rs, String[] names,
SharedSessionContractImplementor session, Object owner) throws HibernateException,
SQLException {
int countryCode = rs.getInt(names[0]);
if (rs.wasNull())
return null;
int cityCode = rs.getInt(names[1]);
int number = rs.getInt(names[2]);
PhoneNumber employeeNumber = new PhoneNumber(countryCode, cityCode, number);
return employeeNumber;
}
Next, the null SafeSet method:
#Override
public void nullSafeSet(PreparedStatement st, Object value,
int index, SharedSessionContractImplementor session)
throws HibernateException, SQLException {
if (Objects.isNull(value)) {
st.setNull(index, Types.INTEGER);
} else {
PhoneNumber employeeNumber = (PhoneNumber) value;
st.setInt(index,employeeNumber.getCountryCode());
st.setInt(index+1,employeeNumber.getCityCode());
st.setInt(index+2,employeeNumber.getNumber());
}
}
Finally, we can declare our custom PhoneNumberType in our OfficeEmployee entity class:
#Entity
#Table(name = "OfficeEmployee")
public class OfficeEmployee {
#Columns(columns = { #Column(name = "country_code"),
#Column(name = "city_code"), #Column(name = "number") })
#Type(type = "com.baeldung.hibernate.customtypes.PhoneNumberType")
private PhoneNumber employeeNumber;
// other fields and methods
}
This might solve your problem, This will work for all database. if you want more info refer :: https://www.baeldung.com/hibernate-custom-types
If you can use HQL the you can replace DECODE with CASE.
You can update your query from,
SELECT DECODE(FIRST_NAME, NULL, LAST_NAME, FIRST_NAME) as NAME ORDER BY NAME;
to,
SELECT CASE WHEN FIRST_NAME = NULL then LAST_NAME ELSE FIRST_NAME END as NAME ORDER BY NAME;

Resources