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.
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.
I have this entity model class (Book) where an author can have written multiple books.
#Entity
#Table(name="book")
public class Book {
#Id
#GeneratedValue
private Long id;
#Column(name="book_name")
private String bookName;
#Column(name="author_id")
private Long authorId;
//Setters and getters
}
In my Spring Boot project, I don't want to have an author table since there is a third part service that defines authors and their ids, how could I make a paginated repository call for all authorIds and their books?
I would want to have an endpoint that takes in (page, size) and returns a paginated list of a AuthorDTO like so:
public abstract class AuthorDTO implements Serializable {
public abstract Long authorId();
public abstract List<Book> books();
}
[
{
"authorId": 123,
"books": [...]
},
...
]
My first thought is to create a repository call not sure how we can get a page of a custom object. This is not valid below, but I would like to do something like the following.
Page<AuthorDTO> findAllBooksGroupedByAuthorId(Pageable pageable);
Your code seems to suggest you are trying to show the foreign key relationship in the class as an id.
JPA doesn't really do that.
JPA = "Java Persistence Language" i.e. you represent the relations between Java classes that mirror the database.
So in the database you may have a foreign key like 'author_id' in the book table, but in JPA/Java side it will be an "Author" class and not just a long/int.
I hope the below helps. Iv'e just slapped it on the main() of my code so it may not be perfect but I have left some comments as well.
Once you have a Page<Book> you may then want to map it to the DTO in java.
As the query is "get books by author id" we can assume that they all have the same author ID...so there is no real need to try get this projection in the database.
EDIT: Is it not at all possible to have a reference to the author from the 3rd party?
I.e. I don't know how you are populating "Book"...but could you not as you get "Book" from the 3rd party see if you have an Author entity with the books 'author_id' and not persist a new "Author" with that ID if it doesn't already exist?
In this case you can then do an AuthorRepo and simply query like:
Page<Author> findAllBy(Pageable page)
==========================================================================
Seeming as you are fetching a Page of Books by an author Id...you should really have a JPA relationship to show that:
#Entity
private class Book{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#Column(name = "book_name")
private String name;
//Technically this could be Many:Many as a book could have 2 authors? If so....#ManyToMany
//For simplicity (and what you seem to want) Many Books have ONE author.
#ManyToOne(fetch = FetchType.LAZY)
private Author author;
}
#Entity
private class Author{
//ID here - omitted for clarity
#Column(name = "authors_name")
String name;
//The Author has many books.
// Mapped by shows the bi-direction relationship. You can then do 'Author.getAuthorsBooks()'
//Lazy means it wont fetch all the books from database/(hibernate wont) when you do AuthorRepo.get()
//and will only do the `JOIN ON Books where` if you do Author.getAuthorsBooks()
#OneToMany(fetch = FetchType.LAZY,mappedBy = "author")
private Set<Book> authorsBooks = new HashSet<>();
}
private interface AuthorRepo extends JpaRepository<Author,Long>{
//Note the JPA syntax.
Page<Book> findAll(Pageable pageable);
}
EDIT:
I have only written this in an empty file...so it may need tweaking or has typos etc
If you can NOT have a separate entity for some reason for Author, having to keep your entity as it currently is...I'd do 2 queries.
I feel you can either do this in various ways.
If you MUST stick with spring's Pageable:
Get the page request in the controller and make it in to a new PageRequest.of(pagenum,size)
and feed it in to do the Page query below
List<Long> getPageOfUniqueAuthorIds(Pageable pageable);
This will give a page of author Ids.
Then you want to use that List of Longs (aithorIds) to do the second query.
List<AuthorDTOProjection> getBooksAndAuthorIdsWithAuthorsIdsIn(List<Long> authorIds);
#Entity
#Table(name="book")
public class Book {
#Id
#GeneratedValue
private Long id;
#Column(name="book_name")
private String bookName;
#Column(name="author_id")
private Long authorId;
//Setters and getters
}
private interface BookRepo extends JpaRepository<Book,Long> {
//The countQuery is required by Spring Paging.
//Hibernate will need to use the count query when doing paging on a native query.
#Query(nativeQuery = true,
value = "SELECT DISTINCT(author_id) FROM book b ",
countQuery = "SELECT count(*) \n" +
"FROM (SELECT DISTINCT(author_id) FROM book b) authorIds ")
List<Long> getPageOfUniqueAuthorIds(Pageable pageable);
//This is not paged. You want all books with the author IDs from the page query above.
List<Book> findAllByAuthorIdIn(List<Long> authorIds);
}
You will then have to map the Entity to the DTO in your service layer.
#Autowired
BookRepo bookRepo;
//This would be from the controller method...not declared here...
Pageable pageableFromController = PageRequest.of(0,10);
List<Long> pageOfUniqueAuthorIds = bookRepo.getPageOfUniqueAuthorIds(pageableFromController);
//Get All the books with Author Ids.
List<Book> books = bookRepo.findAllByAuthorIdIn(pageOfUniqueAuthorIds);
//Your abstract AuthorDTO.
abstract class AuthorDTO implements Serializable {
public abstract Long authorId();
public abstract List<Book> books();
}
//Your Author DTO needs to be implemented so I made a "View".
#AllArgsConstructor
class AuthorView extends AuthorDTO{
private long authorId;
private List<Book> books;
#Override
public Long authorId() {
return authorId;
}
#Override
public List<Book> books() {
return books;
}
}
//Get a List of the authorIds in the List<Books>. Could also use the original Page<Long> authorIds...
//As an author without a book is not possible in your database.
final List<Long> authorIdsInBooks = books.stream().map(it -> it.authorId).distinct().collect(Collectors.toList());
//Map the Ids of authors to an Impl of your abstract DTO. Personally I don't see why the AuthorDTO is abstract.
//I'd have expected just an abstract DTO class called "DTO" or something and then AuthorDTO impl that.
//But as the way you have it this will work. I guess you may want more impl of the AuthorDTO so maybe leave the AuthorDTO as abstract.
//This can be returned to client.
final List<AuthorView> authorViews = authorIdsInBooks.stream()
.map(authorId -> new AuthorView(
authorId,
books.stream().filter(it -> it.authorId.equals(authorId)).collect(Collectors.toList()))
)
.collect(Collectors.toList());
Suppose i have two morphia entities: Person and Team which are look like this
#Entity
public class Person {
private String name;
private String login;
private String mail;
private List<Team> teams;
}
#Entity
public class Team {
private String name;
private String description;
private List<Person> members;
//some more fields
}
I want map this model into Mongodb database like this
Users collection
{
name:"someName",
login:"somelogin",
mail:"some#mail.com",
teams: [
{id:"teamId", name:"TeamName"} //only specific fields fron Team Entity
{id:"anotherTeamId", name:"AnotherTeamName"}
]
}
Teams collection
{
id:"teamId",
name:"TeamName",
description:"Very strong team",
members: [id:"aaa", name: "someName"] //only specific fields fron User Entity
//some other fields
}
{
id:"anotherTeamId",
name:"AnotherTeamName",
description:"Brave new team",
members: [id:"aaa", name: "someName"] //only specific fields fron User Entity
//some other fields
}
So, I want denormolize only specific fields (only name for example) from Team document into User's teams field.
I don't understand Can I use morphia (or some other odm) for this case? Which annotations I should use in my Entities?
It seems that #Reference annotation is not allowed with List<> fields.
I think, i should create inner class PersonTeam, which will contain Team's name and id, and use it in Person class
#Entity
public class Person {
private String name;
private String login;
private String mail;
private List<PersonTeam> teams;
}
public class PersonTeam {
private String teamId;
private String teamName;
}
Is this a good way to solve my problem? thank you!
That's about the only way to do it with morphia at least. You might consider using #Reference on those fields and only storing the IDs. That'd help if your concern is saving space.
Here are my two classes :
#Document(collection="people")
public class Person {
// Data.
#Id
private ObjectId Id;
#Field("email")
private String email;
#DBRef
private List<Entity> entities;
/* Getters and setters removed to keep this question simple. */
}
And, here is my entity class.
#Document(collection = "entities")
public class Entity {
#Id
private ObjectId Id;
#DBRef
private List<Person> people;
/* Getters and setters removed to keep question simple. */
}
And here is my mongoRepository :
public interface EntityRepository extends MongoRepository<Entity, String>{
List<Entity> findByPeople_email(String email);
}
I think you can see what I want to do here - I want to query the mongo database, and get back all entities attached to a user where the email address matches the one I provide. I keep getting stuck in an SQL mindset here, and thinking I can just do something like :
SELECT * FROM people WHERE email="<email_here>"
But obviously that is not the case! Anyone know what I am missing?
I am completely new in Hibernate ORM world, recently reading mapping specially one-to-many relation. But i am facing some problem to understand.
I am using this link to understand hibernate relation.
https://dzone.com/tutorials/java/hibernate/hibernate-example/hibernate-mapping-one-to-many-using-annotations-1.html
please have a look the code.
public class Student {
private long studentId;
private String studentName;
private Set<Phone> studentPhoneNumbers
//setter getter
}
public class Phone {
private long phoneId;
private String phoneType;
private String phoneNumber;
//setter getter
}
i understand it.
confusion is here.
Set<Phone> phoneNumbers = new HashSet<Phone>();
phoneNumbers.add(new Phone("house","32354353"));
phoneNumbers.add(new Phone("mobile","9889343423"));
Student student = new Student("Eswar", phoneNumbers);
session.save(student);
Does this only one code session.save(student), store value in both table ? if no then why we didn't write code to save phoneNumbers like session.save(phoneNumbers).
Once this above code is executed then the value will be stored in both table or object(STUDENT and PHONE) right ?. I think each time we execute this code, this code insert value in both table. i don't like it. i want value should be store on only second table(PHONE).
So how will i store value in only PHONE object ?
or how will we implement this relation where user will select STUDENT through combobox and fill value in PHONE object through simple textboxes. then finally save it.like product/category.
3.please help or suggest some good tutorial where hibernate is implemented (specially one-to-many and many to many relation) in web application. i have seen many tutorial but all these are implemented through main method.
sorry for the poor English
thanks.
Yes it should save both student and phone number in their own table. If you annotate the right way. Student should be something like this:
#Entity
#Table(name = "student")
public class Student {
#id
#GeneratedValue
private long studentId;
#column(name = "studentName")
private String studentName;
#OneToMany
private Set<Phone> studentPhoneNumbers
//setter getter
}
public class Phone {
private long phoneId;
private String phoneType;
private String phoneNumber;
//setter getter
}
Then there is also some configuration to do. I'd advice you to read this tutorial: https://www.tutorialspoint.com/hibernate/index.htm
In the case of your code it doesn't much. But if you configure hibernate the right way you can decide in which table/column a value should be saved
Hibernate tutorial - Tutorials point