I have these annotations:
public class Account {
#Persistent(defaultFetchGroup = "true", dependent = "false")
#Column(name = "user_owner_id")
private User user;
}
public class User {
#Persistent(defaultFetchGroup = "true", mappedBy = "user")
#Element(column = "user_owner_id", dependent = "true")
private Set<Account> accounts;
}
When initiating the Account class, the query on the database use a SELECT * FROM accounts where exists (SELECT id from users where id=23)
I am trying to give datanucleus an annotation that tells it to run on the database SELECT a.* FROM accounts a JOIN users u on a.id = u.user_id where u.id = 23 as this is more optimal.
So which annotation should I use to make data nucleus change its query formation?
--- addition ----
This is a stripped down version of how we're retrieving the data:
PersistenceManager persistenceManager = persistenceManagerFactory.getPersistenceManager();
persistenceManager.getFetchPlan().setMaxFetchDepth(FetchPlan.FETCH_SIZE_GREEDY);
Query query = persistenceManager.newQuery("javax.jdo.query.JDOQL", null);
query.setClass(User.class);
query.setFilter("this.uuid==p1");
query.declareParameters("java.lang.String p1");
final List<E> entities = (List<E>) query.execute(uuid);
E entity = entities.iterator().next();
return persistenceManager.detachCopy(entity);
You are performing a Query just to get one object, which is very inefficient. Instead you could easily do
User u = pm.getObjectById(User.class, 1);
and this would likely issues 2 SQLs in total; 1 to get the basic User object, and 1 to get the Accounts connected to that User. There would be no EXISTS clause.
With regards to what you are actually doing. A Query is issued. A Query is general and in most use-cases will return multiple objects. The filter clause of the query can be complex. The Query is converted into an SQL to get the basic User fields. It can't get the related objects in a single call, so your log will likely say something about BULK FETCH (or something similar, whatever DataNucleus calls it). This will have an EXISTS clause with the EXISTS subquery restricting the second query to the objects the Query applies to). They do this to avoid the N+1 problem. Using EXISTS is, in general, the most appropriate for the general case of a query. In your specific situation it would have been nice to have an INNER JOIN, but I don't think that is supported as a BULK FETCH option currently. Their code is open source and I know they have asked people to contribute things in the past where they want alternative handling ... so you could contribute something if you want to use a query in this precise situation.
Related
I would like someone to explain me why Hibernate is making one extra SQL statement in my straight forward case. Basically i have this object:
#Entity
class ConfigurationTechLog (
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long?,
val configurationId: Long,
val type: String,
val value: String?
) {
#JsonIgnore
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "configurationId", insertable = false, updatable = false)
val configuration: Configuration? = null
}
So as you can see, nothing special there. And when i execute this query :
#Query(value = "SELECT c FROM ConfigurationTechLog c where c.id = 10")
fun findById10() : Set<ConfigurationTechLog>
In my console i see this:
Hibernate:
/* SELECT
c
FROM
ConfigurationTechLog c
where
c.id = 10 */ select
configurat0_.id as id1_2_,
configurat0_.configuration_id as configur2_2_,
configurat0_.type as type3_2_,
configurat0_.value as value4_2_
from
configuration_tech_log configurat0_
where
configurat0_.id=10
Hibernate:
select
configurat0_.id as id1_0_0_,
configurat0_.branch_code as branch_c2_0_0_,
configurat0_.country as country3_0_0_,
configurat0_.merchant_name as merchant4_0_0_,
configurat0_.merchant_number as merchant5_0_0_,
configurat0_.org as org6_0_0_,
configurat0_.outlet_id as outlet_i7_0_0_,
configurat0_.platform_merchant_account_name as platform8_0_0_,
configurat0_.store_type as store_ty9_0_0_,
configurat0_.terminal_count as termina10_0_0_
from
configuration configurat0_
where
configurat0_.id=?
Can someone please explain me, what is happening here ? From where this second query is coming from ?
I assume you are using Kotlin data class. The kotlin data class would generate toString, hashCode and equals methods utilizing all the member fields. So if you are using the returned values in your code in a way that results in calling of any of these method may cause this issue.
BTW, using Kotlin data claases is against the basic requirements for JPA Entity as data classes are final classes having final members.
In order to make an association lazy, Hibernate has to create a proxy instance instead of using the real object, i.e. it needs to create an instance of dynamically generated subclass of the association class.
Since in Kotlin all classes are final by default, Hibernate cannot subclass it so it has to create the real object and initialize the association right away. In order to verify this, try declaring the Configuration class as open.
To solve this without the need to explicitly declare all entities open, it is easier to do it via the kotlin-allopen compiler plugin.
This Link can be useful for understand what kind (common) problem is that N + 1 Problem
Let me give you an example:
I have three Courses and each of them have Students related.
I would like to perform a "SELECT * FROM Courses". This is the first query that i want (+ 1) but Hibernate in background, in order to get details about Students for each Course that select * given to us, will execute three more queries, one for each course (N, there are three Course coming from select *). In the end i will see 4 queries into Hibernate Logs
Considering the example before, probably this is what happen in your case: You execute the first query that you want, getting Configuration Id = 10 but after, Hibernate, will take the entity related to this Configuration, then a new query is executed to get this related entity.
This problem should be related in specific to Relationships (of course) and LAZY Fetch. This is not a problem that you have caused but is an Hibernate Performance Issue with LAZY Fetch, consider it a sort of bug or a default behaviour
To solve this kind of problem, i don't know if will be in your case but ... i know three ways:
EAGER Fetch Type (but not the most good option)
Query with JOIN FETCH between Courses and Students
Creating EntityGraph Object that rappresent the Course and SubGraph that rappresent Students and is added to EntityGraph
Looking at your question, it seems like an expected behavior.
Since you've set up configuration to fetch lazily with #ManyToOne(fetch = FetchType.LAZY), the first sql just queries the other variables. When you try to access the configuration object, hibernate queries the db again. That's what lazy fetching is. If you'd like Hibernate to use joins and fetch all values at once, try setting #ManyToOne(fetch = FetchType.EAGER).
I'm a bit confused about when use a join with Criteria. Example of what I'm talking about:
.createAlias("cars", "c", JoinType.LEFT_OUTER_JOIN)
Here are the different JoinType:
LEFT_OUTER_JOIN
INNER_JOIN
LEFT_OUTER_JOIN
NONE
RIGHT_OUTER_JOIN
When we map entities with Hibernate there is already a "JoinType" automatically (is that an INNER_JOIN ?).
Simple example with user - car:
User class (table name USERS)
class User {
#Id
#Column(name="ID_USER")
private int idUser;
#Column(name="NAME")
private String name;
#OneToMany
#JoinColumn(name="ID_USER")
private Set<Car> cars;
}
And Car class (table name CARS):
class Car{
#Id
#Column(name="ID_CAR")
private int idCar;
#Column(name="MODEL)
private String model;
#Column(name="ID_USER")
private int idUser;
}
If I have a user u and i type u.getCars() which Join is it mapped by default ?
If I want for example results of:
SELECT * FROM Users u LEFT JOIN Cars c on u.id_user = c.id_user
Then is that correct to use that :
Criteria c = getSession().createCriteria(User.class);
c.createAlias("cars", "c", JoinType.LEFT_OUTER_JOIN);
return c.list();
(And then I loop on User and Car to search what I'm looking for).
And if I'm looking for that:
SELECT * FROM Users u INNER JOIN Cars c on u.id_user = c.id_user
Am I right saying I have no need to create a JoinType.INNER_JOIN ?
If I'm, JoinType.INNER_JOIN is useless when I already have map entites ?
I have to map a lot of complex tables using a lot of join and I'm a little muddled !
When we map entities with Hibernate there is already a "JoinType" automatically (is that an INNER_JOIN ?).
Assuming you have marked the oneToMany association as Fetch.EAGER. In that case when you load User object via hibernate criteria, it uses LEFT OUTER JOIN by default, unless you override it in Criteria. If you have not marked it as FetchType.EAGER, it will be lazy and so no join by default, again, unless you override it in Criteria.
If I have a user u and i type u.getCars() which Join is it mapped by default ?
For EAGER loading refer above. If lazy and it is within the same session you are calling u.getCars, it will fire a new SELECT to load cars based on the userId and so no join.
Am I right saying I have no need to create a JoinType.INNER_JOIN ? If I'm, JoinType.INNER_JOIN is useless when I already have map entites ?
Refer above, by default it is LEFT OUTER JOIN if it is EAGER and unless overidden in Criteria.
On suggestion, - It would be good idea to view the SQLs that hibernate is actually firing in the background via enable show_sql property with different FetchTypes and Criteria JOIN Types, because based on your entiy mappings the count/complexity of SQL queries might be different.
Lets say I have an entity:
#Entity
public class Person {
#Id
#GeneratedValue
private Long id;
#ManyToMany(fetch = FetchType.LAZY)
private List<Role> roles;
#ManyToMany(fetch = FetchType.LAZY)
private List<Permission> permissions;
// etc
// other fields here
}
I want a to build a query using the Criteria API that filters these users and shows a list of people and among other info from the entity - how many roles does a person have.
CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<Person> query = builder.createQuery(Person.class);
Root<Person> personRoot = query.from(Person.class);
// predicates here
However, this limits me to returning only a list of Person entities. I can always add a #Transient field to the entity, but this seems ugly since I might have many different queries and might end up with many such fields.
On the other hand - I cant use HQL and write the query since I want complex filtering and I would have to deal with appending and removing things from the HQL query.
My question, besides the one in the title of this post is this: how do I query the database using the Criteria API and return a non-entity (in case I want to filter the Person table but return only the number of roles, permissions, etc) and how do I do it for something very close to the actual entity (like the example with the role counter instead of the roles collection)?
UPDATE
Using Hibernate's projections I came up with this. But still don't know that to write in TODO. Projections.count doesn't work since it excpects some kind of grouping, and I don't seem to be able to find any examples in the Hibernate documentation.
Criteria cr = session.createCriteria(Person.class);
if (id != null) {
cr.add(Restrictions.eq("id", id));
}
ProjectionList projectionList = Projections.projectionList();
projectionList.add(Projections.property("id"), "id");
projectionList.add(TODO, "rolesCount");
CriteriaQuery<Long> query = entityManager.getCriteriaBuilder().get().createQuery(Long.class);
query.select(builder.get().countDistinct(root));
works for me:)
how do I do it for something very close to the actual entity (like
the example with the role counter instead of the roles collection
You could make these values properties of your User entity by various means, for example using a Hibernate #Forumula property. This will issue an inline subquery on Entity load to get the count without touching the collection.
#Formula("select count(*) from roles where user_id = ?")
private int numberOfRoles;
Another (JPA compliant) option is to handle these calculated fields by creating a view at the database level and the mapping this to your User:
e.g.
#OneToOne
private UserData userData; //entity mapped to your view (works just like a table)
....
public int getNumberOfRoles(){
return userData.getRoleCOunt();
or
by using #SecondaryTable to join this User data.
I need to set a table name dynamically so that I use query.setText(tname,abc)
e.g: select a.name from :tname where a.id = '2'
I used setText() because when I use setString() it says "tname is a invalid parameter" because I assume that Hibernate adds '' when setting string parameters.
But even setText() does not help and gives the same exception.
How can I set the table name dynamically?
Reply to PSR:
So you mean replace table name as a java string replacement. But then we can not take support of sql injections prevention etc from hibernate right? Also How we bind parameters in hibernate in a situation where like statement,
Eg: name like "%:name%"
This also gives me Illegal argument exception: Parameter does not exist as a named parameter when i try to bind it using query.setString(name,"def");
Hibernate will not do this for you, because it works with PreparedStatements, and you can't prepare a statement where the table being queried isn't known yet.
I don't see why you would be exposing table names to end users, so preventing SQL injection doing a regular string substitution should be easy. You use some sort of business logic to determine the correct table from a list that only you know. The table name isn't coming from user input at all.
Depending on your choice of RDBMS, you may find a discriminator column, or table inheritance with partitioning to be a better way of handling a situation where identical queries are made against different tables.
It is not possible to set table name dynamically.You can set dynamically column names.it is not possible to set table name
try like this
select a.name from '+table name+'where a.id = '2'
In my opinion, There are 2 ways to resolve this issue:
1- If you are using Spring and Hibernate together, you could use SpEL and it would be like #{#entityName} as it is described here
#Entity
public class User {
#Id
#GeneratedValue
Long id;
String lastname;
}
public interface UserRepository extends JpaRepository<User,Long> {
#Query("select u from #{#entityName} u where u.lastname = ?1")
List<User> findByLastname(String lastname);
}
2-You could use CriteriaBuilder like
CriteriaQuery<YourEntity> cr = cb.createQuery(YourEntity.class);
Root<YourEntity> root = cr.from(YourEntity.class);
cr.select(root);
I copied the source codes from the provided links and they are described there much better
I got two classes User and Role which are mapped to each other using a many to many associations. Now I'm trying to query all users which doesn't have a certain role using the Hibernate Criteria API. But I'm a little bit stuck.
To query users with a certain role I use the following query, which works just fine.
Session session = getSessionFactory().getCurrentSession();
Criteria mainCrit = session.createCriteria(boClass);
return mainCrit.createAlias("roles", "r").add( Restrictions.eq("r.name", roleName)).list();
Now I'm a little bit confused how to reverse the query and get all user that don't have a certain role. If possible I want to explicit exclude a certain role and don't query for all roles chaining them with OR as there may be more roles added later dynamically.
UPDATE
To get a better understanding of my scenario you can see the association I'm trying to query in another question.
Furthermore I would also add that the name property of the Role class is an Enum, don't know if this is important or changes the way to query the database.
There's perhaps a more elegant way, but the only one I've found is to query for all the users for which there doesn't exist any role in the user's roles which has the given role name.
This is done like this:
Criteria c = session.createCriteria(User.class, "user");
DetachedCriteria roleOfUserWithName = DetachedCriteria.forClass(Role.class, "role");
roleOfUserWithName.createAlias("role.users", "userOfTheRole");
roleOfUserWithName.add(Restrictions.eqProperty("userOfTheRole.id", "user.id"));
roleOfUserWithName.add(Restrictions.eq("role.name", roleName);
roleOfUserWithName.setProjection(Projections.id());
c.add(Subqueries.notExists(roleOfUserWithName));
It's equivalent to the following HQL query:
select user from User user where not exists (
select role.id from Role role inner join role.users userOfTheRole
where userOfTheRole.id = user.id
and role.name = :roleName);