Hibernate calculated collection #Formula - java

is it possible to have and Entity with a field calculated using Formula when the field is a Collection (let's say it's a Set)?
Here's the dummy example of what I'm trying to achive:
#Formula(value =
"SELECT NEW com.example.entity.Person(p.name, p.age) FROM Person p")
lateinit var people :Set<Person>

From the JavaDoc for #Formula:
Defines a formula (derived value) which is a SQL fragment ...
You have to think of the fragment you write as an replacement in the select statement:
SELECT (formulaValue) AS propertyName FROM ....
Everything you can write into formulaValue can be used in #Formula.
Your example is not a valid SQL fragment and as you can see, it is not possible to return more than one value from a #Formula.
But you could use #Subselect and a wrapper object instead:
#Entity
#Subselect("SELECT name, age FROM Person p")
public class PersonWrapper {
#Id
private String name;
private int age;
}
(in Java, as I'm not aware of the correct syntax of Kotlin)
If you really need the collection of these values in another entity, you need something to join on (which is missing in your example) and use that in an #OneToMany or #ManyToMany. Otherwise there is no use to have all these values in your entity, as all entities would have the same collection.

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);
}
...
}

JPA: dynamically mapping an Entity to a table based on an instance variable

Here is the problem i have:
class CurrencyPrice {
#Transient
String pair;
float spotPrice;
Date timeStamp;
}
And I have 3 table the names of which stand for "usd value of euro/gbp/yen respectively": usd_euro, usd_gbp & usd_yen. They all have the same 3 columns: id, spotprice, timestamp.
For some reason i cannot have a single table. The transient instance variable 'pair' will have the following values depending on what it represents: "usd_euro", "usd_gbp" & "usd_yen"
And depending on the value in 'pair' I want to insert a row in one of the tables, eg: if I have the value "usd_yen" in 'pair' then the object should be persisted in usd_yen table.
And when I want to fetch data, I want JPA to decide which table to SELECT from based on the value in 'pair'
This is simple in JDBC but is there a way to do this in JPA?
Thank you.
If I understand your requirements correctly, this might actually be feasible in JPA now (those threads you cite are quite old), if you can use inheritance on your entity and an additional join table and if it's acceptable that the ID for each type is not contiguous.
You could basically define your classes like this then:
#Entity
#Table(name="curr_base") // choose a suitable name
#Inheritance(strategy=InheritanceType.JOINED)
#DiscriminatorColumn(name="currency", discriminatorType=DiscriminatorType.STRING) // your join table needs this column in addition to the id
public abstract class CurrencyPrice {
#Id
private int id;
}
#Entity
#Table(name="usd_euro")
#DiscriminatorValue("usd_euro")
public class UsdEuroPrice extends CurrencyPrice {
float spotPrice;
Date timeStamp;
}
#Entity
#Table(name="usd_gbp")
#DiscriminatorValue("usd_euro")
public class UsdGbpPrice extends CurrencyPrice {
float spotPrice;
Date timeStamp;
}
#Entity
#Table(name="usd_yen")
#DiscriminatorValue("usd_euro")
public class UsdYenPrice extends CurrencyPrice {
float spotPrice;
Date timeStamp;
}
I replicated spotPrice and timeStamp on each subclass so that you don't have to modify your existing table definitions - of course it would be much cleaner to only have them on the superclass/join table.
This mapping allows it for example to perform a EntityManager.persist(new UsdGbpPrice(...)) and have JPA insert a row into the right table. For more information, look here.

Join tables in Hibernate

I have two tables in my PostgreSQL database:
CREATE TABLE tableOne (id int, name varchar(10), address varchar(20))
CREATE TABLE tableTwo (id int, info text, addresses varchar(20)[])
now I want to create a join as follows:
SELECT * FROM tableOne JOIN tableTwo ON address = ANY(addresses)
I tried to achieve this using Hibernate - class TableOne:
#Entity
#Table(name = "tableOne")
class TableOne {
private int id;
private TableTwo tableTwo;
private String address;
#Id
#Column(name = "id")
public getId() { return id; }
#ManyToOne
#JoinFormula(value = "address = any(tableTwo.addresses)",
referencedColumnName = "addresses")
public TableTwo getTableTwo(){
return tableTwo;
}
// Setters follow here
}
But Hibernate keeps generating queries with non-sense JOIN clauses, like:
... JOIN tableTwo ON _this.address = any(tableTwo.addresses) = tableTwo.addresses
How do I tell Hibernate using annotations to format my join query correctly? Unfortunately, our project must be restricted only to the Criteria API.
EDIT:
After suggestion from ashokhein in the comments below, I annotated the method getTableTwo() with just #ManyToOne - and now I would like to do the join using Criteria API, presumably with createAlias(associationPath,alias,joinType,withClause) method where withClause would be my ON clause in the join.
But Im not sure what to put as associationPath and alias parameters.
Any hints?
To support PostgreSQL array you need a custom Hibernate Type. Having a dedicated user type will allow you to run native SQL queries to make use of the type:
String[] values = ...
Type arrayType = new CustomType(new ArrayUserType());
query.setParameter("value", values, arrayType);
HQL supports ANY/SOME syntax but only for sub-queries. In your case you'll need a native query to use the PostgreSQL specific ANY clause against array values.
You can try Named Query.
#NamedQuery(name="JOINQUERY", query="SELECT one FROM tableOne one JOIN tableTwo two ON one.address = :address" )
#Entity
class TableOne{......
Retrieving part is:
TypedQuery<TableOne> q = em.createNamedQuery("query", TableOne.class);
q.setParameter("address", "Mumbai");
for (TableOne t : q.getResultList())
System.out.println(t.address);
You might need to do some permutations on the query
So after a lot of time searching for the right answer, the only real solution that works for us is creating a view:
CREATE VIEW TableA_view AS SELECT TableOne.*,TableTwo.id FROM TableA JOIN TableTwo ON TableOne.address = ANY(TableTwo.addresses)
and mapping it to an entity TableOne instead of the original table.
This was the only solution for us besides, of course, using a named query, which was a no-go as we needed to stick to the Criteria API.
As #ericbn has mentioned in the comments this is really an example where ORM gets really annoying. I would never expect that custom join clause like this is not possible to do in Hibernate.
#JoinFormula should contain SQL instead of HQL.
https://docs.jboss.org/hibernate/orm/4.2/javadocs/org/hibernate/annotations/JoinFormula.html

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();

JPA : Is there any way to run a simple SELECT statement that only access a few columns?

I'm new to JPA so forgive me if my question seems silly.
We have used JPA in our project. I see that every entity object has a direct mapping with a table and each row in the table is an object of that entity type.
But, suppose I only want to access one or two columns of a table, how do i go about doing it ? The reason I'm asking is because of the task i have in hand.
There are two tables. The first table has everything set up with JPA so that each row can be cast into an object type. The first table has a column that is referenced in the second table i.e. say, table A has column CLOTH_ID and Table B has columns CLOTH_ID and CLOTH_DESCRIPTION. CLOTH_ID is used in both Table A and B; But B has the CLOTH_DESCRIPTION columns which corresponds to CLOTH_ID.
I'm displaying Table A in my webpage but I also need to display : CLOTH_DESCRIPTION in my webpage. Is there a JPA oriented way to do this or Am i better off using regular JDBC to extract the CLOTH DESCRIPTION values ?
I assume you have the following setup:
#Entity
#Table(name="A")
class A {
#ManyToOne
#JoinColumn(name="CLOTH_ID")
private B cloth;
//...
}
#Entity
#Table(name="B")
class B {
#Id
#Column(name="CLOTH_ID")
private int id;
#Column(name="CLOTH_DESCRIPTION")
private String description;
//...
}
If you don't... you're doing it wrong (i.e. it is not idiomatic JPA usage). You have the following options:
Simply fetch A
In this case #ManyToOne relationship will be fetched eagerly by default as well. Then simply call in Java:
a.getCloth().getDescription()
Prefer this approach as it is the simplest and most idiomatic unless the number of columns in B is huge.
Use JPA query with custom columns:
SELECT a, a.b.description
FROM A a
WHERE a.id = :id
In this case the query returns List<Object[]>, where Object[] actually contains two elements: A and String.
Same as above but with custom DTO:
class Adto {
private final A a;
private final String description;
public Adto(A a, String description) {
this.a = a;
this.description = description;
}
}
And slightly modified query:
SELECT new Adto(a, a.b.description)
FROM A a
WHERE a.id = :id

Categories

Resources