Goal
I am trying to define a generic query that allows me to list the possible (distinct) values of a property, possibly nested, of an entity. The goal is to have a drop down selection for the end user to choose from when filtering down the list of entities.
Setup
#Entity
public class Customer {
#Id #GeneratedValue Long id;
#NotNull String name;
#Embedded #NotNull Address address;
...
}
#Embeddable
public class Address {
String country;
String city;
String postalCode;
String street;
String number;
...
}
public interface CustomRepository {
#Query("select distinct ?1 from #{#entityName}")
List<String> findAllValues(String value);
#Query("select distinct ?1.?2 from #{#entityName} where ?1 IS NOT NULL")
List<String> findAllSubValues(String path, String value);
}
public interface RepositoryCustomer extends
CrudRepository<Customer, Long>,
JpaSpecificationExecutor<Customer>,
CustomRepository {}
Usage
The query could then be used as follows to show a selection box for filtering down the customers list based on their address country:
public class SelectionComponent {
#Autowired RepositoryCustomer repo;
ComboBox<String> select = new ComboBox<String>();
#PostConstruct
void onPostConstruct() {
select.setItems(repo.findAllSubValues("address", "country"));
}
}
Problem
Compiling the above setup results in follow exception:
org.hibernate.QueryException: Parameters are only supported in SELECT clauses when used as part of a INSERT INTO DML statement
Question
It seems this is not supported. Any alternative suggestions?
To not leave this unanswered. My solution in general has been to avoid trying to express relational or complex queries when it comes to JPA and Spring data.
I have come to prefer creating specific (single purpose) database views for such needs and have a very simple query in my "business layer". In some sense, this creates duplication or denormalization in the database layer, but greatly reduces the complexity required from overlay frameworks such as JPA and Spring data.
In this particular case, I would have a customer country database view that I would map to a JPA entity.
Related
My goal is to create my Contact object directly from query and hibernate, the peculiarities are mainly three:
The contact object has another custom object inside that is the Company.
The contact object contains many more fields and I just want to retrieve a few
To retrieve the values I have to use a complex query that I cannot generate via a simple createCriteria using hibernate.
Contact
#Entity
#Table(name = "contacts")
public class Contact
{
private String firstName;
private String lastName;
private Company company;
...
}
Company
#Entity
#Table(name = "companies")
public class Contact
{
private Integer id;
private String name;
...
}
SQL Query
As I explained before the query is very complex but for convenience both in writing and in answering the question I have reduced it to its minimum terms:
SELECT a.first_name as firstName,
a.last_name as lastName,
a.company_id as companyId,
b.company_name as companyName
FROM contacts a
INNER JOIN companies b ON a.company_id = b.company_id
UserType (CompanyType)
To create the Company object I use a UserType and it is the following
public class CompanyType implements UserType
{
#Override
public int[] sqlTypes()
{
return new int[] { Types.INTEGER, Types.VARCHAR };
}
#SuppressWarnings("rawtypes")
#Override
public Class returnedClass()
{
return Company.class;
}
...
}
Java
I am currently building my object in the following way and is it working.. currently thanks to my UserType I can create a new Company object and set the id.
Type companyType = session.getTypeHelper().custom(CompanyType.class);
results = session.createSQLQuery(SQL_QUERY)
.addScalar("firstName", StandardBasicTypes.STRING)
.addScalar("lastName", StandardBasicTypes.STRING)
.addScalar("companyId", companyType)
.setResultTransformer(Transformers.aliasToBean(Contact.class))
.list();
My goal is to set the name of the Company in the same object created before (by the id).. I tried to add the following line of code but I get an error because it is trying to allocate a new object instead of setting the current one:
.addScalar("companyName", companyType)
Not sure if I completely understand the problem, but do you want to set that the fields of the nested entity(Company) inside the Contact entity object?
If that's the goal, then the simple AliasToBeanResultTransformer won't help for single-level entity transformation. For transformation involving nested entities, you will need to use an AliasToBeanNestedResultTransformer - like what samiandoni has contributed here - https://github.com/samiandoni/AliasToBeanNestedResultTransformer/blob/master/AliasToBeanNestedResultTransformer.java.
You can use it like so:-
results = session.createSQLQuery(SQL_QUERY)
.addScalar("firstName", StandardBasicTypes.STRING)
.addScalar("lastName", StandardBasicTypes.STRING)
.addScalar("companyId", companyType)
.setResultTransformer(new AliasToBeanNestedResultTransformer(Contact.class))
.list();
You can create a DTO object of selected fields and Transformed the query result to DTO.
Transformers.aliasToBean(ContactCompanyDTO.class)
public class ContactCompanyDTO {
private String firstName;
private String lastName;
private String companyName;
private Integer id;
}
If you want to use an actual Domain object then you can easily convert the DTO to a Domain object.
Quarkus simplifies Hibernate ORM mappings with Panache.
Here is an example of my entity and PanacheRepository:
#Entity
public class Person {
#Id #GeneratedValue private Long id;
private String firstName;
private String lastName;
private LocalDate birth;
private Status status;
}
#ApplicationScoped
public class PersonRepository implements PanacheRepository<Person> {
// example
public Person findByName(String name){
return find("name", name).firstResult();
}
// ! and this is what I tried, but it's not possible to do it this way
// all the methods return Person or something of type Person like List<Person>
// so basically this won't even compile
public List<String> findAllLastNames() {
return this.find("select p.lastName from Person p").list();
}
}
All the guides explain how to write different queries, but is not clear how to select only certain attributes.
If I don't need the whole Person object, but rather the lastName of all persons in my DB?
Is it possible to select only certain attributes with Quarkus Panache?
This is currently not possible, you can subscribe to this issue regarding projection for Hibernate with Panache: https://github.com/quarkusio/quarkus/issues/6261
Don't hesistate to vote for it (+1 reaction) and provides feedback.
As #aksappy said, this feature was added and it's documentation is available here.
I have an entity like:
#Entity
class Blog{
#Id
private Long id;
// ...
#ManyToOne
#JoinColumn(name = "author_id")
private User author;
}
And I want to perform an "in" query on the author column, so I wrote my BlogRepository like:
public interface BlogRepository extends JpaRepository<Blog, Long>, CustomizedBlogRepository {
Page<Blog> findByUserIn(Collection<User> users, Pageable pageable);
}
This works, however, I need to perform two queries for one request, that is to query the User entity from UserRepository to get Collection<User> users.
Because in many situation, all I want is semantic like:
select * from blog where author_id in (some_id_list);
So is there anyway in jpa to let me perform query like below without querying the User entity?
The Order part of your method gets in the way. Since you don't want the results ordered, you can use this:
public interface BlogRepository extends JpaRepository<Blog, Long>, CustomizedBlogRepository {
Page<Blog> findByUser_IdIn(Collection<Long> userId, Pageable pageable);
}
Yes you can also write custom JPQL
public interface BlogRepository extends JpaRepository, CustomizedBlogRepository {
#Query("select b from Blog b LEFT JOIN b.author.authorId in :authorIds")
Page<Blog> getByUsersByIds(List<Integer> authorIds, Pageable pageable);
}
Here you can change authorId to any Id of User table, which you have created.And you can also try JOIN instead of LEFT JOIN
How can I select only specific fields from the following class hierarchy?
#Entity
public class MyEntity {
#Id private Long id;
#ManyToOne
#JoinColumn(name="fk_person_id", foreignKey = #ForeignKey(name="fk_person"))
private Person person; //unidirectional
private String fieldA, fieldB, ..field M;
//many more fields and some clobs
}
#Entity
public class Person {
#Id private Long id;
private String firstname;
private String lastname;
}
interface MyEntityRepository extends CrudRepository<MyEntity, Long> {
List<MyEntity> findByIdAndPersonFirstnameAndPersonLastname(long id, String firstname, String lastname);
}
This works perfectly, just the performance is very poor as MyEntity and also Person have some fields and associations that I would like to prevent to be fetched in this specific case (eg clob/texts).
Question: how can I write this query to find the result set, and just fetch the fields that are absolutely required (let's assume id, fieldA, fieldB from MyEntity?
Use lazy initialization for the non necessary fields:
FetchType.LAZY = Doesn’t load the relationships unless explicitly “asked for” via getter
FetchType.EAGER = Loads ALL relationships
For example, in Person:
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name="fk_person_id", foreignKey = #ForeignKey(name="fk_person"))
private Person person; //unidirectional
Mark the unwanted fields as lazy (beware of this documented warning though), or create a dedicated class with the properties you want, and use projections in your query:
select new com.foo.bar.MyEntityWithFirstNameAndLastName(m.id, person.firstname, person.lastname)
from MyEntity m
join m.person person
where ...
If Person is an often-used entity, and contains large blobs that should rarely be fetched, you should consider storing the blobs in a separate entity, and use a OneToOne lazy association.
I have a generic Database structure which can store several user-defined records. For example, the main table is RECORD and the columns are STRING01, STRING02, [...], NUM01, NUM02 etc.
I know this is a bit weird, but it has advantages as well as disadvantages. However, this structure exists and can't be changed. Now I want to create some JPA classes.
First, I created an abstract class RECORD as follows (the Annotations are placed on the gettersthe example is just simplified):
#Entity
#Inheritance(strategy=InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name="TYPE", discriminatorType=DiscriminatorType.STRING)
public abstract class Record {
#Id
private long id;
#Column(name="STRING01")
private String string01;
#Column(name="STRING02")
private String string02;
#Column(name="NUM01")
private BigDecimal num01;
}
Then, I created specific classes inherited from RECORD:
#Entity
#DiscriminatorValue("Person")
public class Person extends Record {
#Transient
public String getFirstName() {
return getString01();
}
public void setFirstName(String name) {
setString01(name);
}
#Transient
public BigDecimal getWeight() {
return getNum01();
}
public void setWeight(BigDecimal weight) {
setNum01(weight);
}
}
This works fine, as I can query RECORD for a PERSON's primary key (via EntityManager.find()) and get a Result as instance of PERSON. I can query for FirstName and Weight without having to know the generic column names.
However, if I write my own JPA Query like SELECT p FROM Person p WHERE p.firstName = 'Michael', it fails. firstName is transient, and here I have to use the generic name string01.
Is there some way of overriding the base class' attribute name in JPA? Maybe there's a vendor-specific solution (I'm using EclipseLink)?
You can try and map multiple attributes to the same column.
The additional attribute would then be annotated with #Column( name = "column name", insertable = false, updatable = false, nullable = false ).
Alternatively, you might be able to replace/enhance the JPQL resolver in order to internally map p.firstName to p.string01, but that would be EclipseLink specific, and I don't really know if that's even possible. Take this as just a hint what to look for.