Hibernate data fetching - java

Here is how I fetch data:
public static List<SubjectSubjectUsersUsers> getAllSubjectUsers()
{
List<SubjectSubjectUsersUsers> theList = null;
Session session = ServiceLocator.getSessionFactory().openSession();
try{
theList = session.createSQLQuery("SELECT s.name as subject_name, u.name as user_name, u.surname as user_surname, su.id as id from subjects s "
+ "join subject_users su on s.id = su.subject_id join users u on su.user_id = u.id")
.addScalar("subject_name", StandardBasicTypes.STRING)
.addScalar("user_name", StandardBasicTypes.STRING)
.addScalar("user_surname", StandardBasicTypes.STRING)
.addScalar("id", StandardBasicTypes.LONG)
.setResultTransformer(Transformers.aliasToBean(SubjectSubjectUsersUsers.class)).list();
}catch(Exception e){
e.printStackTrace();
}finally{
session.flush();
session.close();
}
return theList;
}
The problem is when I update data manually in the DB, the fetched data is not changed. But when I reload the server and IDE it works. What could be the cause of the problem? I guess I used the session object in the incorrect way. Thank you.

you need to set CacheMode to refresh
query.setCacheMode(CacheMode.REFRESH);
Your query will look like -
theList = session.createSQLQuery("SELECT s.name as subject_name, u.name as user_name, u.surname as user_surname, su.id as id from subjects s "
+ "join subject_users su on s.id = su.subject_id join users u on su.user_id = u.id")
.setCacheMode(CacheMode.REFRESH)
.addScalar("subject_name", StandardBasicTypes.STRING)
.addScalar("user_name", StandardBasicTypes.STRING)
.addScalar("user_surname", StandardBasicTypes.STRING)
.addScalar("id", StandardBasicTypes.LONG)
.setResultTransformer(Transformers.aliasToBean(SubjectSubjectUsersUsers.class)).list();
However it's a bad idea to update DB from outsite while using hibernate, otherwise you won't get benefits of hibernate cache.

Related

Avoiding "HHH000104: firstResult/maxResults specified with collection fetch; applying in memory!" using Spring Data [duplicate]

I'm getting a warning in the Server log "firstResult/maxResults specified with collection fetch; applying in memory!". However everything working fine. But I don't want this warning.
My code is
public employee find(int id) {
return (employee) getEntityManager().createQuery(QUERY).setParameter("id", id).getSingleResult();
}
My query is
QUERY = "from employee as emp left join fetch emp.salary left join fetch emp.department where emp.id = :id"
Although you are getting valid results, the SQL query fetches all data and it's not as efficient as it should.
So, you have two options.
Fixing the issue with two SQL queries that can fetch entities in read-write mode
The easiest way to fix this issue is to execute two queries:
. The first query will fetch the root entity identifiers matching the provided filtering criteria.
. The second query will use the previously extracted root entity identifiers to fetch the parent and the child entities.
This approach is very easy to implement and looks as follows:
List<Long> postIds = entityManager
.createQuery(
"select p.id " +
"from Post p " +
"where p.title like :titlePattern " +
"order by p.createdOn", Long.class)
.setParameter(
"titlePattern",
"High-Performance Java Persistence %"
)
.setMaxResults(5)
.getResultList();
List<Post> posts = entityManager
.createQuery(
"select distinct p " +
"from Post p " +
"left join fetch p.comments " +
"where p.id in (:postIds) " +
"order by p.createdOn", Post.class)
.setParameter("postIds", postIds)
.setHint(
"hibernate.query.passDistinctThrough",
false
)
.getResultList();
Fixing the issue with one SQL query that can only fetch entities in read-only mode
The second approach is to use SDENSE_RANK over the result set of parent and child entities that match our filtering criteria and restrict the output for the first N post entries only.
The SQL query can look as follows:
#NamedNativeQuery(
name = "PostWithCommentByRank",
query =
"SELECT * " +
"FROM ( " +
" SELECT *, dense_rank() OVER (ORDER BY \"p.created_on\", \"p.id\") rank " +
" FROM ( " +
" SELECT p.id AS \"p.id\", " +
" p.created_on AS \"p.created_on\", " +
" p.title AS \"p.title\", " +
" pc.id as \"pc.id\", " +
" pc.created_on AS \"pc.created_on\", " +
" pc.review AS \"pc.review\", " +
" pc.post_id AS \"pc.post_id\" " +
" FROM post p " +
" LEFT JOIN post_comment pc ON p.id = pc.post_id " +
" WHERE p.title LIKE :titlePattern " +
" ORDER BY p.created_on " +
" ) p_pc " +
") p_pc_r " +
"WHERE p_pc_r.rank <= :rank ",
resultSetMapping = "PostWithCommentByRankMapping"
)
#SqlResultSetMapping(
name = "PostWithCommentByRankMapping",
entities = {
#EntityResult(
entityClass = Post.class,
fields = {
#FieldResult(name = "id", column = "p.id"),
#FieldResult(name = "createdOn", column = "p.created_on"),
#FieldResult(name = "title", column = "p.title"),
}
),
#EntityResult(
entityClass = PostComment.class,
fields = {
#FieldResult(name = "id", column = "pc.id"),
#FieldResult(name = "createdOn", column = "pc.created_on"),
#FieldResult(name = "review", column = "pc.review"),
#FieldResult(name = "post", column = "pc.post_id"),
}
)
}
)
The #NamedNativeQuery fetches all Post entities matching the provided title along with their associated PostComment child entities. The DENSE_RANK Window Function is used to assign the rank for each Post and PostComment joined record so that we can later filter just the amount of Post records we are interested in fetching.
The SqlResultSetMapping provides the mapping between the SQL-level column aliases and the JPA entity properties that need to be populated.
Now, we can execute the PostWithCommentByRank #NamedNativeQuery like this:
List<Post> posts = entityManager
.createNamedQuery("PostWithCommentByRank")
.setParameter(
"titlePattern",
"High-Performance Java Persistence %"
)
.setParameter(
"rank",
5
)
.unwrap(NativeQuery.class)
.setResultTransformer(
new DistinctPostResultTransformer(entityManager)
)
.getResultList();
Now, by default, a native SQL query like the PostWithCommentByRank one would fetch the Post and the PostComment in the same JDBC row, so we will end up with an Object[] containing both entities.
However, we want to transform the tabular Object[] array into a tree of parent-child entities, and for this reason, we need to use the Hibernate ResultTransformer.
The DistinctPostResultTransformer looks as follows:
public class DistinctPostResultTransformer
extends BasicTransformerAdapter {
private final EntityManager entityManager;
public DistinctPostResultTransformer(
EntityManager entityManager) {
this.entityManager = entityManager;
}
#Override
public List transformList(
List list) {
Map<Serializable, Identifiable> identifiableMap =
new LinkedHashMap<>(list.size());
for (Object entityArray : list) {
if (Object[].class.isAssignableFrom(entityArray.getClass())) {
Post post = null;
PostComment comment = null;
Object[] tuples = (Object[]) entityArray;
for (Object tuple : tuples) {
if(tuple instanceof Identifiable) {
entityManager.detach(tuple);
if (tuple instanceof Post) {
post = (Post) tuple;
}
else if (tuple instanceof PostComment) {
comment = (PostComment) tuple;
}
else {
throw new UnsupportedOperationException(
"Tuple " + tuple.getClass() + " is not supported!"
);
}
}
}
if (post != null) {
if (!identifiableMap.containsKey(post.getId())) {
identifiableMap.put(post.getId(), post);
post.setComments(new ArrayList<>());
}
if (comment != null) {
post.addComment(comment);
}
}
}
}
return new ArrayList<>(identifiableMap.values());
}
}
The DistinctPostResultTransformer must detach the entities being fetched because we are overwriting the child collection and we don’t want that to be propagated as an entity state transition:
post.setComments(new ArrayList<>());
Reason for this warning is that when fetch join is used, order in result sets is defined only by ID of selected entity (and not by join fetched).
If this sorting in memory is causing problems, do not use firsResult/maxResults with JOIN FETCH.
To avoid this WARNING you have to change the call getSingleResult to
getResultList().get(0)
This warning tells you Hibernate is performing in memory java pagination. This can cause high JVM memory consumption.
Since a developer can miss this warning, I contributed to Hibernate by adding a flag allowing to throw an exception instead of logging the warning (https://hibernate.atlassian.net/browse/HHH-9965).
The flag is hibernate.query.fail_on_pagination_over_collection_fetch.
I recommend everyone to enable it.
The flag is defined in org.hibernate.cfg.AvailableSettings :
/**
* Raises an exception when in-memory pagination over collection fetch is about to be performed.
* Disabled by default. Set to true to enable.
*
* #since 5.2.13
*/
String FAIL_ON_PAGINATION_OVER_COLLECTION_FETCH = "hibernate.query.fail_on_pagination_over_collection_fetch";
the problem is you will get cartesian product doing JOIN. The offset will cut your recordset without looking if you are still on same root identity class
I guess the emp has many departments which is a One to Many relationship. Hibernate will fetch many rows for this query with fetched department records. So the order of result set can not be decided until it has really fetch the results to the memory. So the pagination will be done in memory.
If you do not want to fetch the departments with emp, but still want to do some query based on the department, you can achieve the result with out warning (without doing ordering in the memory). For that simply you have to remove the "fetch" clause. So something like as follows:
QUERY = "from employee as emp left join emp.salary sal left join emp.department dep where emp.id = :id and dep.name = 'testing' and sal.salary > 5000 "
As others pointed out, you should generally avoid using "JOIN FETCH" and firstResult/maxResults together.
If your query requires it, you can use .stream() to eliminate warning and avoid potential OOM exception.
try (Stream<ENTITY> stream = em.createQuery(QUERY).stream()) {
ENTITY first = stream.findFirst().orElse(null); // equivalents .getSingleResult()
}
// Stream returned is an IO stream that needs to be closed manually.

How to select from multi tables (many to many)?

Database:
I want to get a list User, who have the same Monhoc (it has maMH= MH1).
My code :
private final SessionFactory sf = HibernateUtil.getSessionFactory();
Public List<User> listUserMonHoc() {
try {
sf.getCurrentSession().beginTransaction();
Query query = sf.getCurrentSession().createSQLQuery("select a.username, a.name from Monhoc b join b.User a where b.mamh = :id");
query.setString("id", "MH1");
List<User> list = query.list();
sf.getCurrentSession().getTransaction().commit();
return list;
} catch (Exception e) {
System.out.println("sai");
System.out.println(e.toString());
//return null;
e.printStackTrace();
return null;
}
}
ERROR message: ERROR: Table 'b.user' doesn't exist.
Thank you!
As i can see you have problems with your query. First you must join your third table and then search by id from the monhoc table.
Second there is a problem here
Query query = sf.getCurrentSession().createSQLQuery("select a.username, a.name from Monhoc b join b.User a where b.mamh = :id");
you must change b.User to User, cause b is allias of your Monhoc table.
So as a native query in MySql will look something like this :
SELECT u.username, u.name
FROM user AS u
INNER JOIN user_monhoc AS um
ON u.username = um.user_id
INNER JOIN monhoc AS m
ON um.mh_id = m.mamh
WHERE ....
I am using inner join (it can be different depents on your needs).

SQL query to do INNER JOIN from two tables of a data base

I have two tables:
usuario (user in english): nombre, apellido, usuario, contrasena, id_perfil
perfil (profile in english): id_perfil, nombre
I have a login on my program using Java and MySQL and I want to know if the username and password entered by the user is correct, to send him to another Jframe.
I don't know much about SQL query but, I did my best here (I'm passing the username and password directly to the function):
public boolean login(String usuario, String contrasena) {
Dato_login d_lgn = new Dato_login();
boolean resultado = false;
sSQL = "SELECT u.nombre, u.apellido, u.usuario, u.contrasena, u.id_perfil, p.nombre AS perfil_nombre FROM "
+ "usuario U INNER JOIN perfil P u.id_perfil = p.id "
+ "WHERE u.usuario='" + usuario + "' AND u.contrasena='" + contrasena + "'";
// Java 7 try-with-resources
try (PreparedStatement pstm = con.prepareStatement(sSQL);
ResultSet rs = pstm.executeQuery(sSQL)) {
while (rs.next()) {
if (d_lgn.getContrasena().equals(contrasena)) {
resultado = true;
} else {
resultado = false;
}
d_lgn.setPerfil(rs.getString("perfil_nombre"));
d_lgn.setUsuario(rs.getString("usuario"));
d_lgn.setNombre(rs.getString("nombre"));
d_lgn.setApellido(rs.getString("apellido"));
d_lgn.setContrasena(rs.getString("contrasena"));
}
} catch (SQLException e) {
JOptionPane.showMessageDialog(null, "SQLException:\n" + e, "Error: login(String usuario, String contrasena)", JOptionPane.ERROR_MESSAGE);
}
return resultado;
}
Is not working, it keeps telling me that there is not any user to log in.
What I need to change?
Also I would like to receive the name of the profile instead of the id of the user, I tried with INNER JOIN but I don't understand how it works correctly yet.
Table:
Error received:
Error in SQL Syntax
In this part:
sSQL = "SELECT u.nombre, u.apellido, u.usuario, u.contrasena, u.id_perfil FROM usuario U INNER JOIN perfil P ON p.nombre=u.nombre WHERE u.usuario='"
+ usuario + "' AND u.contrasena='" + contrasena + "'";
I don't see where a variables contrasena or usario is defined. Should that be ...AND u.usario='" + username + "' AND u.contrasena='" + password + "'"; instead? (putting aside a moment that this exposes a SQL Injection vulnerability). Also, it seems suspect that you're joining your usario and perfil tables on nombre. Is it the case that a User's name would be the same as their Profile name? Without understanding your domain and data model better, I can't really say.
If you also wanted to retrieve the profile name as well, your query could be this:
SELECT u.nombre, u.apellido, u.usuario, u.contrasena, p.nombre as perfil_nombre
FROM usario u
JOIN perfil p ON u.id_perfil = p.id_perfil
WHERE u.usuario = ? and u.contrasena = ?
Notice I'm joining usario and perfil on the id columns instead of nombre. I think you want the usario.perfil_id to match the perfil.id_perfil column.
Instead of con.createStatement() use con.createPreparedStatement(). See Using Prepared Statements for more information on that.
Lastly, to access the perfil.nombre from the ResultSet do this: rs.getString("perfil_nombre");
Also I am returning perfil.nombre instead of usario.nombre because you mentioned your want the profile name instead of the user name.

Delete all objects from database in hibernate Spring java

I want to delete all those rows from xyz table where id = 1 using hibernate spring.
I have tried following code but its not giving error but not deleting rows -
Session session = (Session) getEm().getDelegate();
String sql ="Delete from xyz where id=:id" ;
SQLQuery query = session.createSQLQuery(sql);
query.setParameter("id", "1");
int flg = query.executeUpdate();
Can you please help me to delete all rows using hibernate query.
Try wrapping your code within a transaction like this:
Session session = (Session) getEm().getDelegate();
Transaction tx = session.beginTransaction();
String sql ="Delete from xyz where id=:id" ;
SQLQuery query = session.createSQLQuery(sql);
query.setParameter("id", "1");
int flg = query.executeUpdate();
tx.commit();
Try
query.setParameter("id", Long.valueOf(1));
if your entity is of type Long (which ideally should be).
Reference: http://www.codejava.net/frameworks/hibernate/hibernate-basics-3-ways-to-delete-an-entity-from-the-datastore
Note: The link is just for your reference.
public void deleteById(Class clazz,Integer id) {
String hql = "delete " + clazz.getName() + " where id = :id";
Query q = session.createQuery(hql).setParameter("id", id);
q.executeUpdate();
}

Is there any spring jpa equivalent to following query

Query :
#Query("Select p.name,t.points from Player p,Tournament t where t.id=?1 And p.id=t.player_id")
I have my player and tournament entity and their corresponding JPA repositories. But the problem is we can get only entities from our query, but i want to do above query, please help me with this i am new to it.
this is my sql query i want to add but where to add i am not getting:
Select p.name, t.points_rewarded from player p, participant t where t.tournament_id="1" and t.player_id=p.id;
This is how you can do it with JPQL for JPA:
String queryString = "select p.name, t.points from Tournament t," +
" Player p where t.player_id=p.id " +
"and t.id= :id_tournament";
Query query = this.entityManager.createQuery(queryString);
query.setParameter("id_tournament", 1);
List results = query.getResultList();
You can take a look at this JPA Query Structure (JPQL / Criteria) for further information about JPQL queries.
And this is ho you can do it using HQL for Hibernate, these are two ways of doing it:
String hql = "SELECT p.name, t.points from Player p,Tournament t WHERE t.id= '1' And p.id=t.player_id";
Query query = session.createQuery(hql);
List results = query.list();
Or using query.setParameter() method like this:
String hql = "SELECT p.name, t.points from Player p,Tournament t WHERE t.id= :tournament_id And p.id=t.player_id";
Query query = session.createQuery(hql);
query.setParameter("tournament_id",1);
List results = query.list();
You can take a look at this HQL Tutorial for further information about HQL queries.
Note:
In both cases you will get a list of Object's array List<Object[]> where element one array[0] is the p.name and the second one is t.points.
TypedQuery instead of normal Query in JPA
this is what i was looking for, thanks chsdk for help, i have to create pojos class, and in above link answer is working fine foe me,
Here is my code sample
String querystring = "SELECT new example.restDTO.ResultDTO(p.name,t.pointsRewarded) FROM Player p, Participant t where t.tournamentId=?1 AND t.playerId = p.id ORDER by t.pointsRewarded DESC";
EntityManager em = this.emf.createEntityManager();
try {
Query queryresults = em.createQuery(querystring).setParameter(1, tournamentId);
List<ResultDTO> result =queryresults.getResultList();
return new ResponseEntity<>(result, HttpStatus.OK);
} catch (Exception e) {
e.printStackTrace();
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
} finally {
if (em != null) {
em.close();
}}

Categories