I would like in HQL to use the result of a abstract method in my "where" clause. Can this be done?
Something like this:
#NamedQuery(name="getMailRelations", query="Select mr from MailRelation mr where mr.getOccurrences() > 5"
I have a mapped super class something like this
#Entity
#Table(name="Mail_Entity", schema="relations")
#Inheritance(strategy=InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name="relationType", discriminatorType=DiscriminatorType.STRING)
#PersistenceContext(name="domas")
public abstract class MailRelation {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private long id;
#ManyToOne(cascade = {CascadeType.MERGE, CascadeType.REFRESH, CascadeType.PERSIST})
#JoinColumn(name="mailId", referencedColumnName="mailId", nullable=false)
private Mail mail;
public void setMail(Mail mail) {
this.mail = mail;
if(!mail.getMailRelations().contains(this))
mail.addMailRelation(this);
}
public abstract int getOccurrences();
public Mail getMail() {
return mail;
// and more code......
No, that is impossible. The HQL code is translated into SQL and executed on the database. Generally Java methods can't be translated into SQL, and the database does not have any access to your Java code.
If you have a problem like this, there are for example these three possibilities to handle it. None of these possibilities is perfect.
1) You write the logic of the method in HQL (or SQL) using WHERE, GROUP BY and HAVING. In your example the getOccurrences() method seems to return a number of rows, which perhaps can be handled by `HAVING COUNT(...) > 5'.
2) You use database stored procedures. These are p. ex. procedures written in PL/SQL (in the case of Oracle). They can be accessed in select statements. But you loose the independency of the chosen database.
3) You load more rows than necessary and filter later in your Java code.
The solution is up to you, but I'm adding some additional options you can consider:
If you manage to precalculate the hash in all cases, use a parametrized named query:
#NamedQuery(name="getMailRelations", query="Select mr from MailRelation mr where :occurrences > 5"
then, you can call the query and add the parameter "occurrences":
String precalculatedHash = //your code here.
entityManager.createNamedQuery("getMailRelations",MailRelation.class).setParameter("occurrences", precalculatedHash).getResultList();
Another option is to go a little deeper with your hash logic, and determine what do you want to achieve with it. With that in mind you can use Criteria API to create a query and add all the restrictions represented by that hash. This can be a little tricky, so discard this option if the hash proves to be too context-depending (and I mean if it relies a lot on what do you have persisted, and the context of your application).
The third option is to bring all the results (or the smallest set of results possible, through either parameters or, again, Criteria API), and make your particular filtering logic.
Related
I have a database table where one of the columns contain an enum
Entity file:
#Entity
#Table(name = "error_log_entry")
public class ErrorLogEntryEntity extends AbstractPersistable<UUID> {
#Id
private UUID id;
#Column(name = "priority")
#Enumerated(EnumType.STRING)
private ErrorLogEntryPriorityType priority;
}
Enum file
public enum ErrorLogEntryPriorityType {
INFO,
WARN,
DANGER
}
Now I am trying to write a query that counts the number of rows where the enum is equal to "DANGER"
public interface ErrorLogEntryRepository extends JpaRepository<ErrorLogEntryEntity, UUID> {
List<ErrorLogEntryEntity> findByChangedUserId(UUID userId);
#Query(nativeQuery = true, value = "select count(*) from error_log_entry where priority = 'DANGER'")
ErrorLogEntryPriorityType getErrorCount();
}
However, this causes the following error
java.lang.ClassCastException: class java.math.BigInteger cannot be cast to class project.core.types.ErrorLogEntryPriorityType
I am a bit new to spring, but from the documentation, this problem seems to occur because I am casting on an incompatible type. I am not 100% sure.
Any suggestions on how to make this query work?
As mentioned in the comments, the error is because the return type of the query method you've defined is incorrect. You're not looking to get back an instance of ErrorLogEntryPriorityType, you need to get back a numerical value - the count of how may records match the query.
Having said that, there's an even easier, cleaner solution - use Spring Data's "derived query method" and you don't even need to write the #Query yourself. For example, this should work as you intend:
long countByPriority(ErrorLogEntryPriorityType priority);
Note there is no #Query annotation; Spring Data understands the mapping of your entity and generates the query for you based on the method name and parameter types.
This method is also more flexible; the application can use it to count all records with any of the values for priority, not just DANGER. Generally speaking, it's good to have the low-level repository methods be building blocks, like Lego. The application can then assemble a wide variety of "things" from those basic building blocks, such as a service method that counts how many DANGER log records there are.
I suggest you read all the reference docs for Spring Data repositories, starting here.
Problem
To make my code cleaner i want to introduce a generic Repository that each Repository could extend and therefore reduce the code i have to have in each of them. The problem is, that the Ids differ from Class to Class. On one (see example below) it would be id and in the other randomNumber and on the other may even be an #EmbeddedId. I want to have a derived (or non derived) query in the respository that gets One by id.
Preferred solution
I Imagine having something like:
public interface IUniversalRepository<T, K>{
#Query("select t from # {#entityName} where #id = ?1")
public T findById(K id);
}
Ecample Code
(that does not work because attribute id cannot be found on Settings)
public interface IUniversalRepository<T, K>{
//should return the object with the id, reagardless of the column name
public T findById(K id);
}
// two example classes with different #Id fields
public class TaxRate {
#Id
#Column()
private Integer id;
...
}
public class Settings{
#Id
#Column() //cannot rename this column because it has to be named exactly as it is for backup reason
private String randomNumber;
...
}
// the Repository would be used like this
public interface TaxRateRepository extends IUniversalRepository<TaxRate, Integer> {
}
public interface SettingsRepository extends IUniversalRepository<TaxRate, String> {
}
Happy for suggestions.
The idea of retrieving JPA entities via "id query" is not so good as you might think, the main problem is that is much slower, especially when you are hitting the same entity within transaction multiple times: if flush mode is set to AUTO (with is actually the reasonable default) Hibernate needs to perform dirty checking and flush changes into database before executing JPQL query, moreover, Hibernate doesn't guarantee that entities, retrieved via "id query" are not actually stale - if entity was already present in persistence context Hibernate basically ignores DB data.
The best way to retrieve entities by id is to call EntityManager#find(java.lang.Class<T>, java.lang.Object) method, which in turn backs up CrudRepository#findById method, so, yours findByIdAndType(K id, String type) should actually look like:
default Optional<T> findByIdAndType(K id, String type) {
return findById(id)
.filter(e -> Objects.equals(e.getType(), type));
}
However, the desire to place some kind of id placeholder in JQPL query is not so bad - one of it's applications could be preserving order stability in queries with pagination. I would suggest you to file corresponding CR to spring-data project.
I have following schema:
Projects (ID, NAME)
Projects_Users (PROJECT_ID, USERS_ID)
Users (NAME, ID)
Entity are as follows
public class Projects {
private String name;
private long id;
private List<User> users;
public Projects() {
}
}
public class User {
private String name;
private Long id;
}
so clearly one-to-many. Projects can have multiple users.
Now my goal is to write jooq query where I can fetch project objects already with corresponding users.
.select(PROJECT.NAME, PROJECT.ID, USERS)
.from(PROJECT)
.join(USERS_PROJECT).on(USERS_PROJECT.PROJECT_ID=PROJECT.ID)
.join(USERS).on(USERS.ID=USERS_PROJECT.USER_ID)
.fetchInto(Project.class);
but the query would resturn thousends results when expecting ~15
You're doing two things:
Run a jOOQ query:
.select(PROJECT.NAME, PROJECT.ID, USERS)
.from(PROJECT)
.join(USERS_PROJECT).on(USERS_PROJECT.PROJECT_ID.eq(PROJECT.ID))
.join(USERS).on(USERS.ID.eq(USERS_PROJECT.USER_ID))
.fetch();
This is straightforward. jOOQ transforms the above expression to a SQL string, sends it to JDBC, receives the result set and provides you with a jOOQ Result. Obviously, the result is denormalised (because you wrote joins), and thus you have many rows (about as many as there are rows in the USERS_PROJECT table).
Map the Result to your POJO
Now, this is what's confusing you. You called fetchInto(Project.class), which is just syntax sugar for calling fetch().into(Class). These are two separate steps, and in particular, the into(Class) step has no knowledge of your query nor of your intent. Thus, it doesn't "know" that you're expecting ~15 unique projects, and a nested collection of users.
But there are ways to map things more explicitly. E.g. by using intoGroups(Class, Class). This may not return nested types as you designed them, but something like a
Map<Person, List<User>> map = query.fetch().intoGroups(Person.class, User.class);
You can take it from there, manually. Or, you can write a RecordMapper and use that:
List<Person> list = query.fetch().map(record -> new Person(...));
Or, you could use any of the recommended third party mappers, e.g.:
http://modelmapper.org
http://simpleflatmapper.org
Consider the following setup.
Space.java
class Space {
Id id;
ParkingCampus campus;
}
class ParkingCampus {
Id id;
String country;
}
This is not the exact structure of my project but it is close enough for what I am trying to understand.
How would I be able to run a query on my 'Space' object which only returns instances where the child class 'ParkingCampus' has the String 'country' set to a specific value, eg: "UK".
I was thinking something like:
sessionFactory.getCurrentSession()
.createCriteria(String.class)
.add(Restrictions.eq("country", "UK"))
.list();
But i'm not sure if that would compile correctly. So does Hibernate by default do a 'deep' search in an attempt to map results to my restriction criteria or do I need to do something else to specify the query to work in this way?
Any help would be greatly appreciated!
Use Space as the base criteria, create an alias for the parking campus, and add a restriction on the alias' child country to UK.
However, keep in mind that your implementation seems a bit off, IMO. There should be a table with a compound key of parkingCampusId and spaceId, rather than the Space owning the id.
Criteria criteria = sessionFactory.getCurrentSession().createCriteria(Space.class, "space");
criteria.createAlias("space.parkingCampus", "campus");
criteria.add(Restrictions.eq("campus.country", "UK");
I have a mongodb with two model classes say User and UserInfo. The criteria is in User class I have to retrieve a multiple fields around 10 fields like "firstName","lastName", etc and in UserInfo Model class I like to retrieve only one field say "age".
At this moment I referenced the UserInfo class's object to the User class like stated below in the Structure and its stores in the DB as {"firstName","John"},{"lastName","Nash"},{userInfo: userInfoID} but if I make an Embedded Relation then it would store all the userInfo's fields and I think to retrieve one ("age") field it is Unwanted to Embed all the userInfo's fields which inturn will make the application slow I think.
Which scenario should I use whether #Reference or #Embedded, I think Embedded will slow down my response to DB but in the websites its given as reference annotation only slows down querying time and needs some sort of Lazy Loading an all, my structure is like below:
class User extends Model{
public String firstName;
public String lastName;
public String loginTime;
public String logoutTime;
public String emailId; etc,etc......
Some more 10 fields like this+userInfo reference object
#Reference
public UserInfo userInfo;
}
class UserInfo extends Model{
public String emailId;
public String age;
public String sex;
public String address;
public String bank; etc,etc......
Some more 10 fields like this
}
As I stated above I want only age field from UserInfo and all fields of User, so which Annotation is best and #Reference or #Embedded. It will be more helpful if I get a single query for User class in which I can retrieve all fields of User and only "age" field of UserInfo. In short I need a query like this when I go for #Reference relationship
field("userInfo.age") for userInfo.emailId = (MorphiaQuery q = User.createMorphiaQuery;
q.field("firstName").equal("John"); q.field("lastName").equal("Nash"); q.field("loginTime").greaterthan("sometime"))//the complex part where I need age of particular userInfo but I have only the ID of the userInfo since I am using Reference and that Id too got from a **subQuery**....
Please don't write two queries I need a single query or maybe a query with subquery. To be more clear I can tell in SQL language:
SELECT age FROM UserInfo where emailId = u.emailId
(SELECT * FROM User WHERE firstName='John' AND lastName='Nash' AND
logintime='someTime') AS u;
I need this exact same query without writing two morphia queries which consumes more time by referring two tables.
Mongo does not support query across tables / collections. And such page would satisfy you:
MongoDB and "joins"
As in sql, the join query is also build intermediate result set and make query again:
Understanding how JOIN works when 3 or more tables are involved. [SQL]
When you build your model, you should not consider a lot about what single query but structural modeling:
http://docs.mongodb.org/manual/core/data-modeling/
For your case, if you are using embeded, you can make this in one query and specify the fields you need by using queries like:
db.User.find({"some_field":"some_query"},{"firstName":1,....,"userInfo.age":1})
Check projections here:
http://docs.mongodb.org/manual/reference/method/db.collection.find/
If you are using reference or even soft link like using Morphia Key<> to lazy load the UserInfo, it requires two queries.
If it's not real-time application, you can also try mongo map-reduce to merge collection to handle big data, though the map-reduce is too bad for mongo though.
I'm reasonably sure you can't with just one query.