JPA - is it possible to get result as an instance of #MappedSuperclass? - java

Regarding the following example, is it possible to retrieve list of AnsweredQuestion instances as objects of Question?
#MappedSuperclass
public abstract class Question{
#Column(name="TITLE")
private String title;
...
}
#Entity
#Table(name="ANSWEREDQUESTION")
public class AnsweredQuestion extends Question
{
#Column(name="ANSWER")
private String answer;
...
}
It is very important for me to retrieve only a few columns since the descendant class has many.
I tried something as follows, but it still returns list of AnsweredQuestion:
queryStr = " select q from AnsweredQuestion q where ..."
TypedQuery<Question> query = entityManager.createQuery(queryStr, Question.class);
return query.setParameter( ... ).getResultList();

If you need to return a few fields, you can also select them and use the new operator:
TypedQuery<Sample> query = entityManager.createQuery("select new com.acme.sample.Sample(e.fieldA, e.fieldB) from AnsweredQuestion e", Sample.class);
return query.setParameter( ... ).getResultList();
JPA implementation will look for a Sample constructor (the path must be complete) and invoke it while transforming the result. It is pretty handy (at the cost of creating new classes to represent the result) to avoid returning everything the database has to return :)

Related

Select only specific columns from joined tables (Many-to-Many) in Spring Data JPA

The purpose is to select columns from joined tables (Many-to-Many).
The problem i have is to select two columns from a joined Many-to-Many table.
I'am using Springboot 2.3 and Spring data Jpa.
I have this data model, and what i want to fetch are the blue boxed fields
So the native query could look like this (if i am right ...)
SELECT bg.id, bg.name, p.name, c.name, c.short_desc FROM boardgame as bg
JOIN boardgame_category bgc on bg.id = bgc.fk_game
JOIN publisher p on bg.fk_publisher = p.id
JOIN category c on bgc.fk_category = c.id
WHERE bg.id = :id
I first tried to work with dto in JPQL statment
public class BoardgameDto {
private long id;
private String name;
private String publisherName;
private Set<CatregoryDto> categoryDto;
// setter, getter etc...
}
public class CategoryDto {
private String name;
private String shortDesc;
// setter, getter etc...
}
The JQPL query could look like this , but it doesn't work (IDE shows errors on CategoryDto)
/* THIS DOESN'T WORK */
SELECT new org.moto.tryingstuff.dto.BoardgameDto(bg.id, bg.name, p.name,
new org.moto.tryingstuff.dto.CategoryDto(c.name, c.short_desc)) FROM Boardgame as bg, Publisher as p, Category as c
Well, I think the problem I have with this way of doing is that the dto's contructor can't receive a collection as written here, and i think neither another contructor in parameter.
Then i started looking at Criteria Queries, especialy multiselect, Tuple, Dto, but it look like i had same kind of problems so i didn't dive deeper into it.
Finally i used a JpaRepository and it's findById() method like this
public interface BoardgameRepository extends JpaRepository<Boardgame, Long> {
}
// In a test or service method
Boardgame game = repository.findById(long id);
Then i filter the fields i need to keep through mappings in Service or Controller layer. So the front only received need datas.
But it feel a bit overkill,
Am I missing something, any part of the framework that would allow me to select only specific columns?
As you wrote, you can't use a collection as the parameter of a constructor expression. That's because the expression gets applied to each record in the result set. These records are a flat data structure. They don't contain any collections. Your database returns a new record for each element in that collection instead.
But your constructor expression fails for a different reason. You're trying to combine 2 constructor expressions, and that's not supported. You need to remove the 2nd expression and perform that operation within the constructor of your DTO.
So, your query should look like this:
SELECT new org.moto.tryingstuff.dto.BoardgameDto(bg.id, bg.name, p.name, c.name, c.short_desc) FROM Boardgame as bg <Your JOIN CLAUSES HERE>
And the constructor of your BoardgameDto like this:
public class BoardgameDto {
public BoardgameDto(Long id, String gameName, String publisherName, String categoryName, String description) {
this.id = id;
this.name = gameName;
this.publisherName = publisherName;
this.category = new Category(categoryName, description);
}
...
}

Save a query result on a custom class

I want to save the result of a query on a List. But when I debug to see the list, it's full of Object[], not a Conta class.
Here is my query and the Conta class.
Query query = em.createQuery("SELECT p.data data, c.nome nomePedido, c.valor, g.nome nomeGarcom FROM Pedidos p\n"
+ " JOIN Cardapio c ON p.idItem = c.id\n"
+ " JOIN Garcons g ON p.idGarcon = g.id", Conta.class);
contaList = query.getResultList();
public class Conta {
private Date data;
private String nomeGarcom;
private String nomePedido;
private float valor;
public Conta(Date data, String nomePedido, float valor, String nomeGarcom) {
this.data = data;
this.nomeGarcom = nomeGarcom;
this.nomePedido = nomePedido;
this.valor = valor;
} ... getters and setters
AFAIK, you can use a constructor expression in JPA, especially since already have a matching constructor:
Query query = em.createQuery("SELECT new com.mine.Conta(p.data, g.nome, c.nome, c.valor) FROM Pedidos p\n" ...
Your Conta class is no Jpa #Entity - that's why it doesn't know how to map the result to your Java Class. Annotate Conta properly, give it a primary key, that's the most straight-forward way.
#Entity
public class Conta {
#Id
private long someID;
#Column
private float valor;
#Column
... more columns (watch out for your Timestamp, probably needs #Temporal)
....getters and setters
}
As is, you can't use Jpa to save instances of your Conta into the database which is probably one thing you're looking to do later.
Consider this Answer too: JPA : How to convert a native query result set to POJO class collection
Leandro, can you also please attach the code of getResultList()? From my experience, there are two potential causes. One, your getResultList method has the wrong return type. Two, your query is not working at all.

How to test if Enum value is in EnumSet using JPQL

I have a property as follows:
#Entity
class Project implements Serializable {
#Convert(converter = TypeFlattener.class)
#Column(name = "assignable_types")
private EnumSet<Type> assignableTypes;
The point of the TypeFlattener is that I want to avoid normalization for a simple list, so basically it converts the EnumSet to/from a simple String with comma separated values matching the Enum.name()
Now the question is how can I filter only Projects that have the a specific Type in assignableTypes?
My best approach would be something like that:
#NamedQuery(
name="Project.findByType",
query="SELECT p FROM Project p WHERE :t IN p.assignableTypes"
)
What do I pass in for :t? The String version as follows? Can I even do this?
q.setParameter("id", "MAJOR");
The converter does not matter here as he only affects the database representation. The below query using MEMBER OF should work.
TypedQuery<Project> query = em.createQuery(
"SELECT p FROM Project p WHERE :type MEMBER OF p.assignableTypes", Project.class);
query.setParameter("type", Type.ONE);
List<Project> resultMemberOf = query.getResultList();

Map hibernate projections result to java POJO model

I've been using spring and hibernate for this past few weeks and I've always been learning something new there.
Right now I've got a problem that I want to solve with Projections in Hibernate.
Suppose there is a model Person and that model has many Car. The following are how the class definitions roughly gonna look like:
public class Person implements java.io.Serializable {
private Integer id;
private String name;
private List<Car> cars;
private Integer minYear; // Transient
private Integer maxYear; // Transient
}
public class Car implements java.io.Serializable {
private Integer id;
private Integer year;
}
The problem here is I want to get the minYear (maxYear) of each Person to be filled by the earliest year (latest year) of the cars they have.
Later I found a solution to use Projections but I stumbled upon org.hibernate.QueryException: could not resolve property: minYear of: model.Person and here is the code of the db operation:
Criteria criteria = sessionFactory.getCurrentSession().createCriteria("model.Person");
criteria.add(create(personInstance));
criteria.createAlias("minYear", "minYear");
criteria.setProjection(Projections.min("cars.year").as("minYear"));
Is there anyway to store the aggregation value in transient method using Projections because I just want to avoid using plain SQL and HQL as much as possible.
Never mind, I've found the solution.
First we need to create alias of the associated object like so
Criteria criteria = sessionFactory.getCurrentSession().createCriteria("model.Person");
criteria.createAlias("cars", "cars");
Select the needed using Hibernate Projections
ProjectionList projections = Projections.projectionList();
projections.add(Projections.property("id").as("id"));
projections.add(Projections.property("name").as("name"));
projections.add(Projections.property("cars").as("cars"));
Group the result based on the root entity (in this case using its id, Person.id), this is needed especially when used with aggregation to group the aggregation
projections.add(Projections.groupProperty("id"));
Use the aggregate function
projections.add(Projections.min("cars.year").as("minYear"));
projections.add(Projections.max("cars.year").as("maxYear"));
Set the projection
criteria.setProjection(projections);
Use result transformer AliasToBeanResultTransformer to map the result fields (as specified in step 2 & 4) to the POJO
criteria.setResultTransformer(new AliasToBeanResultTransformer(Person.class));
Get the result
List<Person> results = (List<Person>) criteria.list();

Hibernate Criteria API - adding a criterion: string should be in collection

I have to following entity object
#Entity
public class Foobar {
...
private List<String> uuids;
...
}
Now I'd like to make a criteria query which would fetch all Foobar pojos whose uuids list contains the string "abc123", I'm just not sure how to make the appropriate criterion.
I assume you are using a version of Hibernate that implements JPA 2.0. Here's a JPA 2.0 solution that should work with any compliant implementation.
Please annotate uuids with JPA's #ElementCollection annotation. Don't use Hibernate's #CollectionOfElements as mentioned in some of the other answer comments. The latter has equivalent functionality but is being deprecated.
Foobar.java will look approximately like this:
#Entity
public class Foobar implements Serializable {
// You might have some other id
#Id
private Long id;
#ElementCollection
private List<String> uuids;
// Getters/Setters, serialVersionUID, ...
}
Here's how you can build a CriteriaQuery to select all Foobars whose uuids contain "abc123".
public void getFoobars() {
{
EntityManager em = ... // EM by injection, EntityManagerFactory, whatever
CriteriaBuilder b = em.getCriteriaBuilder();
CriteriaQuery<Foobar> cq = b.createQuery(Foobar.class);
Root<Foobar> foobar = cq.from(Foobar.class);
TypedQuery<Foobar> q = em.createQuery(
cq.select(foobar)
.where(b.isMember("abc123", foobar.<List<String>>get("uuids"))));
for (Foobar f : q.getResultList()) {
// Do stuff with f, which will have "abc123" in uuids
}
}
I made a self-contained proof-of-concept program while playing with this. I can't push it out right now. Please comment if you want the POC pushed to github.
I know this is old question, but I have just encountered this issue and found solution.
If you want to use Hibernate Criteria you can join your uuids collection and use its property elements to match elements. Just like that:
session.createCriteria(Foobar.class)
.createAlias("uuids", "uuids")
.add(Restrictions.eq("uuids.elements", "MyUUID"))
.list()
You could use a Query as in the example below or you could convert this to a NamedQuery. Unfortunately there doesn't seem to be a way to do this with Criteria.
List<Foobar> result = session
.createQuery("from Foobar f join f.uuids u where u =: mytest")
.setString("mytest", "acb123")
.list();
I've found this post from one year ago, and I've made this method, if it can help anybody with the same problem I had a few hours ago.
Public List<EntityObject> getHasString(String string) {
return getSession().createCriteria(EntityObject.class)
.add(Restriction.like("property-name", string, MatchMode.ANYWHERE).ignoreCase();
.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY)
.list();
Made the same with a group of strings too.
public List<EntityObject> getByStringList(String[] tab) {
Criterion c = Restrictions.like("property-name", tab[tab.length-1], MatchMode.ANYWHERE).ignoreCase();
if(tab.length > 1) {
for(int i=tab.length-2; i >= 0 ; i--) {
c = Restrictions.or(Restrictions.like("property-name",tab[i], MatchMode.ANYWHERE).ignoreCase(), c);
}
}
return getSession().createCriteria(EntityObject.class)
.add(c)
.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY)
.list();
}
It works with "or" statements, but can easily be replaced by "and" statements.
What you are asking is not supported out of the box by hibernate. See http://opensource.atlassian.com/projects/hibernate/browse/HHH-869
Here is a workaround available in the jira ticket :
entityCriteria.add(Restrictions.sqlRestriction(
"fooAlias.id in (select e.id from foobar_table e, values_table v" +
" where e.id = v.entity_id and v.field = ?)", "abc123"), Hibernate.String)) ;
The solution with the sqlRestriction from jira
http://opensource.atlassian.com/projects/hibernate/browse/HHH-869
seemed the best way to go for me since i heavily use criteria api. I had to edit Thierry's code so it worked in my case
Model:
#Entity
public class PlatformData
{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long iID;
private List<String> iPlatformAbilities = new ArrayList<String>();
}
Criteria call:
tCriteria.add(Restrictions.sqlRestriction(
"{alias}.id in (select e.id from platformData e, platformdata_platformabilities v"
+ " where e.id = v.platformdata_id and v.element = ? )", aPlatformAbility.toString(),
Hibernate.STRING));
For starters, I don't think Hibernate can map a List<String>. However, it can map a list of other entities.
So if your code was something like this:
#Entity
public class Foobar {
private List<EntityObject> uuids;
...
}
And the EntityObject has a String-property called str, the criteria could look like this:
List<Foobar> returns = (List<Foobar>) session
.createCriteria.(Foobar.class, "foobars")
.createAlias("foobars.uuids", "uuids")
.add(Restrictions.like("uuids.str", "%abc123%"))
.list();

Categories

Resources