Cast Hibernate result to a list of objects - java

I have a hibernate call in my DAO that looks like this
List<Associate> associate = (List<Associate>)session.createSQLQuery("SELECT * FROM associates WHERE fk_id = :id AND fk_associate_id = (SELECT id FROM users WHERE fk_user_type = 2)").setParameter("id", id).list();
I am getting an error saying that I cannot cast the resulting list to the model type Associate. I don't understand why this is happening. I am returning only the fields that are in the associates table.

You need to specify the class of entity the result should be converted to using addEntity(), because you are executing SQL query that doesn't know anything about entities:
List<Associate> associate = (List<Associate>) session.createSQLQuery(
"SELECT * FROM associates WHERE fk_id = :id AND fk_associate_id = (SELECT id FROM users WHERE fk_user_type = 2)")
.addEntity(Associate.class)
.setParameter("id", id).list();
See also:
18.1.2. Entity queries

It is possible to apply a ResultTransformer to native SQL queries, allowing it to return non-managed entities.
sess.createSQLQuery("SELECT NAME, BIRTHDATE FROM CATS")
.setResultTransformer(Transformers.aliasToBean(CatDTO.class))
The above query will return a list of CatDTO which has been instantiated and injected the values of NAME and BIRTHNAME into its corresponding properties or fields.
source : Link

Related

How can I get the same result in JPA hibernate as querying "select * from table where ..."?

I'm new to ORM interface, and I'm trying to connect to my databases with Hibernate.
What I've figured out so far is:
With a serializable object, I can get a persistent object with
Person p = session.get(Person.class, serializable);
I can get all the objects by a list with
List people = session.createQuery("FROM Person").list();
What I need is to find a row that meets a certain condition, such as SELECT * FROM person WHERE name="Kim" AND age=30;
However, the above two aren't the ways to achieve this.
#Entity
#Table(name = "person")
public class Person {
#Id
private Integer id; // I can use this variable when using session.get(Person.class, serializable) , but I cannot know the id of my target row.
private String name;
private Integer age;
...
Should I iterate all the objects in people, and check whether all the member variables match what I want?
Is there any simple way to achieve this?
First and most importantly, never put user input in a query like this
SELECT * FROM person WHERE name="Kim" AND age=30;
You have to use Prepared Statements. Learn why from Bobby Tables.
Secondly, you should use the JPA interface EntityManager instead of Hibernate's Session as the second one anchors you to a specific implementation, rather than the wider standard.
With the EntityManager you get an object by id like this:
Person p = em.find(Person.class, id);
To get a list of People you can create a JPQL query like this:
TypedQuery<Person> query = em.createQuery("SELECT p FROM Person p WHERE p.name = :name AND p.age = :age", Person.class);
query.setParameter("name", "Kim"); // :param1 defines a parameter named "param1" in the query
query.setParameter("age", 30);
List<Person> results = query.getResultList();
You could also do this in one chain if you don't need to reuse the query with different parameters on a loop.
List<Person> results = em.createQuery(..., Person.class)
.setParameter("name", "Kim")
.setParameter("age", 30)
.getResultList();
The reason to put every call on a new row is in case an exception occurs it will give you the proper row to look for. If they're all in one row, then that's not very useful.
If your query is a SELECT, and it needs to return exactly one result every time, you can use getSingleResult() instead of getResultList(). If you do that and the query did return more than one result, it will throw a NonUniqueResultException. If the query did not return any results it will throw a NoResultException instead of returning null.
If your query is NOT a SELECT, then you have to use executeUpdate() to invoke it after setting the parameters.
There are many resources to get you started, but generally if its for a Hibernate version before 5.2 you should consider it outdated, and it will likely be more difficult.

How do you specify a column by it's native name in an SQL Selection?

I am writing an application with Spring 4 and Hibernate 5.1. I need to be able to interoperate with a legacy system that saves SQL queries using the native table and column names. I need to be able to add a Selection object to my Tuple query which uses the original column name rather then the entity field name.
I tried doing this using Hibernate with Projections.sqlProjection(column_name... and that sort of worked, but other issues are preventing me from continuing in this direction.
#Transactional(propagation = Propagation.MANDATORY)
public List<Object[]> testQuery() {
Class<?> rootClass = getEntityClassByTable("pm_project");
List<String> columnList = new ArrayList<>();
columnList.add("pm_project.pm_project_id");
columnList.add("pm_project.pm_project_name");
columnList.add("pm_project.pm_project_title");
columnList.add("pm_project.parent_id");
columnList.add("pm_project.pm_project_from_date");
CriteriaQuery<Tuple> query = criteriaBuilder.createTupleQuery();
Root<?> root = query.from(rootClass);
List<Selection> selectionList = new ArrayList<>();
for (String column : columnList) {
Selection s = nativeSelection(column);
selectionList.add(s);
}
query.multiselect(selectionList.toArray());
List<Tuple> resultList = em.createQuery(query).getResultList();
return resultList;
}
I need 'NativeSelection', something that produces a Selection for a native column name, not an entity attribute name. It would be really good if it took any sql that could be a field, because some JSON_VALUE fields might pop up.
The values of the native sql are dynamic, and I am not receiving a full SQL, only column and table names, with possible JSON_VALUE calls. I would very much like not to have to generate native sql, as I want to leverage other JPA features in my query.
If you are using native query then no need to specify entity attribute names. All you have to do is to set the value of the nativeQuery attribute to true and define the native SQL query in the value attribute of the annotation
#Query(
value = "SELECT * FROM pm_project p WHERE p.pm_project_id= 1",
nativeQuery = true)
Collection<Project> findProjectById();
You can also do it without using native query by simple JPA
List<Project> findAllByProjectId(Integer id);

How to delete/getList using JPA in Spring Boot Application

I have an entity:
#Entity
#Table(name ="cats")
public class Cat {
#Id
#Column(name="name")
private String name;
#Column(name="age")
private int age;
#Column(name="color")
private String color;
#Column(name="weight")
private int weigth;
..
}
1. I need to delete it from database using EntityManager:
#Override
public void delete(Cat cat) {
entityManager.remove(cat);
}
Problem: I have a Map<String, Cat> which contains all this elements. I get it by name from map IllegalArgumentException -> "Removing a detached instance com.entities.Cat#cats".
Question: How can i do it without getting from database by key?
2. I need to getList with limit and offset.
To get all the elements i can just use:
entityManager.createNativeQuery("SELECT name, age, color, weight FROM cats");
Without entityManager i used prepatedStatement with:
"SELECT name, age, color, weight FROM cats LIMIT ?,?"
Question:
How can i do it using entityManager?
Do entityManager have something like preparedStatement?
With EntityManager you can use Query objects. It provides you with several different methods to build your queries, which you can see in the Docs.
From there, you can use a Query to perform a select or execute an update into the db.
Update example:
//:id is a parameter you can set
Query query = entityManager.createQuery("delete from Entity e where e.id = :id");
query = query.setParameter("id", id);
query.executeUpdate();
Select example (using TypedQuery which implements Query:
String sql = "select e from Entity e";
TypedQuery<Entity> query = entityManager.createQuery(sql, Entity.class);
System.out.println(query.getResultList());
You can determine limit and offset like this:
query = query.setFirstResult(offset);
query = query.setMaxResults(limit);
If you have an entity at hand you can (and should) delete it using your EntityManager with remove(). You're getting that error because your entity is detached - that is, your EntityManager isn't aware of its existence.
To "attach" entities to your manager you can use merge(). However, if said entity doesn't exist in the database it will be inserted, and if it exists but has different fields from your object it will be updated.
public void delete(Cat cat) {
if(!entityManager.contains(cat)) {
entityManager.merge(cat);
}
entityManager.remove(cat);
}
To insert entities for the first time you can also use persist(). For the differences between merge() and persist(), see this.
If you need to use EntityManager, then simply use reference:
entityManager.remove(entityManager.getReference(Cat.class, id));
This way the entity won't be fetched from db, but will be deleted.
Using query is also an option:
Query query = entityManager.createQuery("delete from Entity e where e = :entity");
query = query.setParameter("entity", entity);
query.executeUpdate();
You can create Query using EntityManager#createQuery. Then set parameters: firstResult and maxResults:
query.setFirstResult(10).setMaxResults(20);
This will take 20 entities starting from 10th.

Problems mapping Hibernate entities - native query containing left join with condition

This should be straight-forward though can't get my Hibernate entities to play nice for the following scenario with a simple two table structure:
I'm attempting to get all config names and matching config values for a given currency code (and null's where not matching).. so have written a native query to retrieve the following like so:
SELECT * FROM CONFIG_NAME LEFT JOIN CONFIG_VALUE ON CONFIG_NAME.ID =
CONFIG_VALUE.CONFIG_ID AND CONFIG_VALUE.CURRENCY_CODE = '<CURRENCY_CODE>'
ORDER BY CONFIG_NAME.ID
This query doesn't seem to play nice with my Hibernate mapping as it appears to be essentially ignoring the CURRENCY_CODE clause in the join.
Essentially, for the following subset of data:
CONFIG_NAME:
CONFIG_VALUE:
There is no value defined for 'FREE_SHIPPING_ENABLED' for 'USD' so running the query above for both currency code returns as expected:
QUERY RESULTS FOR 'CAD':
QUERY RESULTS FOR 'USD':
I'm running the above query as a native query in a JpaRepository for the ConfigName entity. But what I appear to be getting is that it seems to ignore the currency_code clause in the JOIN condition. As the list of config values defined has both values for USD and CAD where they're populated. Is there an Hibernate annotation to factor this in that I'm unaware of?
It's worth bearing in mind there will only ever be ONE value defined for each config for a given currency - there's a unique constraint across CONFIG_VALUE.CONFIG_ID/CONFIG_VALUE.CURRENCY_CODE so potentially ConfigValue on the ConfigName entity would not need to be a map.
Mappings as are follows:
ConfigName - Entity
#OneToMany(mappedBy = "config")
private Set<ConfigValue> configValue;
ConfigValue - Entity
#ManyToOne(optional = false)
#JoinColumn(name="CONFIG_ID")
#Property(policy=PojomaticPolicy.NONE)
private ConfigName config;
Doesn't need to be strictly unidirectional either.. as I'm only concerned with the values from the ConfigName entity either being populated or null.
Think I'm missing something simple, so hope someone can help.
EDIT: Am querying using JpaRepository:
Am using JpaRepository to query:
#Repository
public interface ConfigNameRepository extends JpaRepository<ConfigName, Long>
{
static final String SQL_QUERY = "SELECT * FROM CONFIG_NAME "
+ "LEFT JOIN CONFIG_VALUE ON CONFIG_NAME.ID = CONFIG_VALUE.CONFIG_ID "
+ "AND CONFIG_VALUE.CURRENCY_CODE = ?1 ORDER BY CONFIG_NAME.ID";
#Query(value = SQL_QUERY, nativeQuery = true)
List<ConfigName> findConfigValuesByCurrencyCode(final String currencyCode);
}
As mentioned by #Ouney, your JPA relations are not taken in account if you use a native query.
You declared a SELECT * and List<ConfigName> (the real sql result contains ConfigName+ConfigValue). So with this query, Hibernate fetchs all the ConfigName. Then, when you try to access to the set of configValue, it fetchs all the related ConfigValue.
I think this should be better/easier to use a JPQL query instead (but you need Hibernate 5.1+) :
SELECT n, v
FROM ConfigName n
LEFT JOIN ConfigValue v
ON v.config = n AND v.currencyCode = :currencyCode
ORDER BY n.id
With this method signature :
List<Object[]> findConfigValuesByCurrencyCode(#Param("currencyCode") String currencyCode);
Where the result will be :
o[0] // ConfigName
o[1] // ConfigValue (nullable)
You may want to do this prettier with a wrapper :
SELECT new my.package.MyWrapper(n, v)
...
MyWrapper constructor :
public MyWrapper(ConfigName configName, ConfigValue configValue) {
...
}
Method signature with the wrapper :
List<MyWrapper> findConfigValuesByCurrencyCode(#Param("currencyCode") String currencyCode);
(update)
I think in this case, your query can be :
SELECT n, v // or new my.package.MyWrapper(n, v)
FROM ConfigName n
LEFT JOIN n.configValue v
WITH v.currencyCode = :currencyCode
ORDER BY n.id

Java Hibernate createSQLQuery using addEntity

I need to apply a SQL Query similiar to this.
SELECT
id as id,
c03 as c03,
c34 as c34
FROM
(SELECT
id,
c03,
c34
FROM
students
where
c34 in(
?, ?,?,?
)
order by
id desc) o
group by
c34;
And My Java code.
private final void retrieveStudents(){
final List result = currentSession()
.createSQLQuery("SELECT id as id,c03 as c03,c34 as c34 FROM (SELECT id,c34,c03 FROM students where c34 in(:filters) order by id desc) o group by c34;")
.setParameterList("filters",Arrays.asList(74,1812))
.list();
result.forEach(this::consumer1);
}
The query is Just OK. Returns a array of objets which i can iterate but i would like to return a Student object so i add.
.addEntity(Student.class)
But a error is throw which says
Column 'ACTIVE' not found.
Also i have try with .addScalar but the same thing happens.
.addScalar("id",org.hibernate.type.IntegerType.INSTANCE)
.addScalar("c03",org.hibernate.type.DateType.INSTANCE)
Which is a column from Student but is not on the projection my question how can i do that i just thought that applying in some way the alias would Hibernate populate the student entity.
All i want is a Student object with id,c03,c34 values populate.
What i am doing wrong is that possible?
For this use case, you don't want hibernate to treat Student as an entity, but as a DTO.
For this, do not use the addEntity method, but the setResultTransfomer :
final List result = currentSession()
.createSQLQuery("SELECT id as id,c03 as c03,c34 as c34 " +
"FROM (SELECT id,c34,c03 FROM students " +
"where c34 in(:filters) " +
"order by id desc) o group by c34")
.setParameterList("filters",Arrays.asList(74,1812))
.addScalar("id",org.hibernate.type.IntegerType.INSTANCE)
.addScalar("c03",org.hibernate.type.DateType.INSTANCE)
.addScalar("c34",org.hibernate.type.DateType.INSTANCE)
.setResultTransformer(Transformers.aliasToBean(Student.class))
.list();
This is working for non entity classes, provided the class has setters that match the names of the projected columns, and there is a no-arg constructor. I never tested this on an entity class.
If you do not have setters, but a constructor for those 3 fields, you can use :
// choose here the right constructor
java.lang.reflect.Constructor constructor = Student.class.getConstructors()...
// ...
.setResultTransformer(new AliasToBeanConstructorResultTransformer(constructor));
instead.
EDIT :
I would not use an entity as a DTO (but create a specific DTO for this use case) : what if one of your service think the entity was loaded the regular way (so is fully initialized), do some modifications on it (update a field for example), and save the entity ?
In the best case, a not-nullable field (on db side) will not have been initialized, and you'll get a ConstraintViolationException and trigger a rollback, keeping your data safe.
In the worst case, you'll be corrupting your data by setting to null all the fields of the entity but the three loaded ones.

Categories

Resources