Hi I have the following JPA entity class. It has two #CollectionTable mapping for Groups and Contact Id's. I am interesting in getting back a list of unique contact id's from the contact_details table and having a reference to them in my Users class variable contacts below:
#Entity
#Table(name = "users")
public class User {
#Id
#GeneratedValue(generator="uuid")
#GenericGenerator(name = "uuid", strategy = "uuid2")
private UUID id;
#NotBlank
private String username;
#NotBlank
#Column(name = "name")
private String name;
#ElementCollection(fetch = FetchType.EAGER)
#CollectionTable(name = "users_groups", joinColumns = #JoinColumn(name = "user_id"))
#Column(name = "[group]")
private List<String> groups = new ArrayList<>();
#ElementCollection(fetch = FetchType.EAGER)
#CollectionTable(name = "contact_detail", joinColumns = #JoinColumn(name = "user_id"))
#Column(name = "[id]")
private List<String> contacts = new ArrayList<String>();
}
When I get a user from the database it produces the following query in the hibernate logs
select user0_.id, user0_.password, user0_.username, contacts1_.user_id as user_id8_4_1_, contacts1_."id" as id1_2_1_, groups2_.user_id as user_id1_4_2_, groups2_."group" as group2_5_2_
from users user0_
left outer join contact_detail contacts1_ on user0_.id=contacts1_.user_id
left outer join users_groups groups2_ on user0_.id=groups2_.user_id
where user0_.id=?
Because of the Left Outer Join on contact_detail and users_groups, it actually retrieves back the same contact_id multiple times. This is a JSON representation of the entity with multiple "47e5b98a-2116-4ad9-b773-3acc99e2c83c" contact id's
{
user: {
id: "d3b3be2a-8a2a-48ac-94dd-fd85faf1a8ff",
username: "shiv",
firstName: "Shivam",
lastName: "Sinha",
groups: [
"ADMIN",
"USER",
"ROOT"
],
expired: false,
locked: false,
credentialsExpired: false,
enable: true,
birthday: "2015-05-18",
joined: "2015-05-18",
gender: "M",
contactDetails: null,
contacts: [
"47e5b98a-2116-4ad9-b773-3acc99e2c83c",
"47e5b98a-2116-4ad9-b773-3acc99e2c83c",
"47e5b98a-2116-4ad9-b773-3acc99e2c83c"
]
}
}
However when I completely remove the following groups class variable:
#ElementCollection(fetch = FetchType.EAGER)
#CollectionTable(name = "users_groups", joinColumns = #JoinColumn(name = "user_id"))
#Column(name = "[group]")
private List<String> groups = new ArrayList<>();
And attempt to retrieve the the user from the database. Hibernate generates the following sql:
select user0_.id, user0_.password, user0_.username, contacts1_.user_id as user_id8_4_1_, contacts1_."id" as id1_2_1_
from users user0_ left outer join contact_detail contacts1_ on user0_.id=contacts1_.user_id
where user0_.id=?
This is the JSON representation of the entity:
{
user: {
id: "d3b3be2a-8a2a-48ac-94dd-fd85faf1a8ff",
username: "shiv",
firstName: "Shivam",
lastName: "Sinha",
expired: false,
locked: false,
credentialsExpired: false,
enable: true,
birthday: "2015-05-18",
joined: "2015-05-18",
gender: "M",
contacts: [
"47e5b98a-2116-4ad9-b773-3acc99e2c83c"
]
}
}
It only contains unique contact id "47e5b98a-2116-4ad9-b773-3acc99e2c83c". Which is what is expected.
So my question is how can i achieve the same thing without having to remove groups class variable OR changing the datatype from List<String> contacts to Set<String> contacts
The behaviour that is happening now is as expected as can be seen from the hibernate FAQ.The solution is mentioned in the FAQ here as well
If you are looking to do this via JPA refer here.
Following is the implementation in Spring data using the following Specification
public class UserSpecifications {
public static Specification<User> findUniqueUser(Integer userId){
return new Specification<User>() {
#Override
public Predicate toPredicate(Root<User> userRoot,
CriteriaQuery<?> criteriaQ, CriteriaBuilder critieriaB) {
criteriaQ.distinct(true);
return critieriaB.equal(userRoot.get(User_.id), 1);
}
};
}
}
To find the record
User user = userRepository.findOne(UserSpecifications.findUniqueUser(1));
Also have a look at this
https://developer.jboss.org/wiki/AShortPrimerOnFetchingStrategies .
If you use fetch="join" for more than one collection role for a
particular entity instance (in "parallel"), you create a Cartesian
product (also called cross join) and two (lazy or non-lazy) SELECT
would probably be faster.
This Cartesian product is what you are describing (multiple values). Try attempting to change it to use select as suggested:
#Fetch(FetchMode.JOIN) or Alternatively also try the batch Mode.
This is a further explanation of the fetching strategies:
http://www.mkyong.com/hibernate/hibernate-fetching-strategies-examples/
Related
I am trying to select multiple columns from 2 tables with OneToOne relationship, where the users will send the columns they want to search and the sever returns the result list contains only those columns. I have two entities like these:
#Table(name = "user")
public class User implements Serializable{
private static final long serialVersionUID = 1L;
#Column(name = "id" )
#Id
private String id;
private String name;
#OneToOne(mappedBy = "user", cascade = CascadeType.ALL)
#JsonManagedReference
private Address address;
//Getters and Setters
}
public class Address implements Serializable{
private static final long serialVersionUID = 1L;
#Column(name = "user_id" )
#Id
private String id;
private String houseNr;
#OneToOne
#JoinColumn(name = "user_id")
#JsonBackReference
private Networkdata networkdata;
//Getters and Setters
With search functions using EntityManager with Tuple
public List<?> find(String[] neededFields){
if(neededFields.length > 0){
String queryStr = this.createQueryString(neededFields);
TypedQuery<Tuple> query = em.createQuery(queryStr, Tuple.class);
List<Map<String, Object>> resultList = new ArrayList<>();
query.getResultList().forEach(tuple -> {
Map<String, Object> map = new HashMap<>();
List<TupleElement<?>> elements = tuple.getElements();
for (TupleElement<?> element : elements ) {
String alias = element.getAlias();
map.put(alias, tuple.get(alias));
}
resultList.add(map);
});
return resultList;
}
else{
return em.createQuery(this.FIND_ALL_STR, User.class).getResultList();
}
}
When i search using SELECT u FROM User u, Address a WHERE u.id = a.id. It returns result like this:
[
{
"id": "5e4e3c95cd8b290008db6f3c",
"name": "name",
"address": {
"id": "5e4e3c95cd8b290008db6f3c",
"houseNr": "123ABC"
}
}
]
Another case is SELECT u.address as address FROM User u, Address a WHERE u.id = a.id which returns
[
{
"address": {
"id": "5e4e3c95cd8b290008db6f3c",
"houseNr": "123ABC"
}
}
]
But when i add a little spicy ingredient like for example, to select the id and the whole address entity like this SELECT u.id as id, u.address as address FROM User u, Address a WHERE u.id = a.id it returns
[
{
"id": "5e4e3c95cd8b290008db6f3c",
"address": null
}
]
Why the first two queries are perfectly fine but the last just returns a null address?
How can i solve this?
Other ideas are also welcome.
If you searching from two column, you can actually use INNER JOIN key words with FOREIGN KEY on the Adress. If you still want to trim down your search you can always use WHERE clause.
SELECT u.id as Id, a.address as Adress from User u INNER JOIN Address a ON u.id=a.user.id .
to trim down your search you can add “WHERE u LIKE %?1% AND a LIKE % ?1% ORDER BY u.id “
Better still to save yourself from headache you can use RESPONSE CLASS to map out the column you want to search from something like SELECT new UserAddress(u.id, a.address) ......
I hope I’m able to help.
I am trying to get familiar with spring and jpa. For start there is a table anime_details containing details of an anime. As it can have many genres, db has another table named genre. The intermediate table to contain their many to many relationship entries is also there. When I query for any anime by id, it should return the details of the anime along with the genres.
It does return an object with details of the anime and list of Genre objects (which is as expected). But what I want is to restrict the columns that will be fetched from Genre objects. For example only id or just id and name (In case there are more columns other than these).
AnimeDetails
#Getter
#Setter
#Entity
#Table(name = "anime_details")
public class AnimeDetails {
#Id
#SequenceGenerator(name = "animeDetailsSeq", sequenceName =
"anime_details_id_seq",
allocationSize = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator =
"animeDetailsSeq")
private Integer id;
private String name;
private Integer episodes;
private Date started;
private Date ended;
private String status;
#ManyToMany
#JoinTable(
name = "anime_genre",
joinColumns = #JoinColumn(name = "details_id", referencedColumnName = "id"),
inverseJoinColumns = #JoinColumn(name = "genre_id", referencedColumnName = "id"))
#JsonManagedReference
private List<Genre> genres;
protected AnimeDetails() {
}
}
Genre
#Data
#Entity
#Table(name = "genre")
public class Genre {
#Id
#SequenceGenerator(name = "genreSeq", sequenceName = "genre_id_seq",
allocationSize = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "genreSeq")
private Integer id;
private String name;
#ManyToMany(mappedBy = "genres")
List<AnimeDetails> animes;
protected Genre() {
}
}
Expected payload
{
"id": 2,
"name": "Your Name",
"episodes": 1,
"started": "2016-08-25T18:00:00.000+0000",
"ended": "2016-08-25T18:00:00.000+0000",
"status": "Completed",
"genres": [
{
"id": 5,
"name": "Drama"
},
{
"id": 10,
"name": "Supernatural"
}
]
}
Right now, I get the result and manually get columns one by one and set those in a DTO. But that is not efficient as the database query is already fetching more data than needed.
Is there any specific annotation/property/jpql to reduce it?
Indeed was looking for a proper solution regarding the same issue , cause as you pointed out it is creating performance issues as there are huge useless data loads between the APP and the DB. Imagine that there could be, not only one query but much more and you need a global optimization solution...
From the first place Spring DATA is not supporting this operation so its leading you at the manual configuration and set up on a DTO reference. The same applies if you were using a custom Object and returning that inside the JPQL with the constructor trick , or else write a native query , get back a List<Object> and again manually map the data back to your actual object , which is the most efficient but not elegant solution
More info in this link , but try to check both answers for the details.
The other thing is that as you are using hibernate underneath , which is providing custom mappers , you could always write up your custom HQL(not jpql) , set up a proper DAO , wire up the EntityManager or directly the SessionFactory (which is breaking the abstract JPA contract , but you can utilize the full goodies that hibernates offers) and then return the same object, but only with the columns you need.
Example for the second point:
import javax.persistence.EntityManager;
import org.hibernate.query.Query;
import org.hibernate.transform.Transformers;
public CustomEntity getEntity(){
Query<CustomEntity> q = (Query<CustomEntity>) entityManager.createQuery("select
e.id,e.name from CustomEntity e where e.name = 'parameter'");
q.setResultTransformer(Transformers.aliasToBean(CustomEntity.class));
CustomEntity entity = (CustomEntity) q.getSingleResult();
return name;
}
Note CustomEntity is a managed Entity Bean / Table in the database , just placing this example to be close on what you might need to achieve.
Tried with
Spring boot 2.0.5.RELEASE
Spring Data 2.0.5.RELEASE
Hibernate-core 5.2.17.Final
I tried a different solution today. Lets look at the code first.
Genre
#Data
#Entity
#Table(name = "genre")
public class Genre {
#Id
#SequenceGenerator(name = "genreSeq", sequenceName = "genre_id_seq",
allocationSize = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "genreSeq")
private Integer id;
private String name;
#ManyToMany
#JoinTable(
name = "anime_genre",
joinColumns = #JoinColumn(name = "genre_id", referencedColumnName = "id"),
inverseJoinColumns = #JoinColumn(name = "details_id", referencedColumnName = "id"))
List<AnimeIdentity> animes;
protected Genre() {
}
}
AnimeIdentity
#Getter
#Setter
#Entity
#Table(name = "anime_details")
public class AnimeIdentity {
#Id
#SequenceGenerator(name = "animeDetailsSeq", sequenceName =
"anime_details_id_seq",
allocationSize = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator =
"animeDetailsSeq")
private Integer id;
private String name;
protected AnimeIdentity() {};
}
Queries Hibernate made
Hibernate: select genre0_.id as id1_2_0_, genre0_.name as name2_2_0_ from genre genre0_ where genre0_.id=?<br>
Hibernate: select animes0_.genre_id as genre_id2_1_0_, animes0_.details_id as details_1_1_0_, animeident1_.id as id1_0_1_, animeident1_.name as name2_0_1_ from anime_genre animes0_ inner join anime_details animeident1_ on animes0_.details_id=animeident1_.id where animes0_.genre_id=?
Feel free to show me the pros and cons of this solution. To me its a good solution if my necessity is limited to only this. But in case of different type of queries making more and more entity pojos will be a tiresome task.
According to this post Difference between #OneToMany and #ElementCollection? I should prefer #ElementCollection for embeddable types and #OneToMany for entities. But using #OneToMany I can additionaly set option orphanRemoval=true. How can I do this with #ElementCollection? It it implied?
It is implied. Removing the owning entity would also remove all data on the #ElementCollection. Setting the Collection to null or changing elements in the Collection would cause an update if Session isn't already closed.
The official documentation here says this:
2.8.1. Collections as a value type
Value and embeddable type collections have a similar behavior as
simple value types because they are automatically persisted when
referenced by a persistent object and automatically deleted when
unreferenced. If a collection is passed from one persistent object to
another, its elements might be moved from one table to another.
...
For collections of value types, JPA 2.0 defines the #ElementCollection
annotation. The lifecycle of the value-type collection is entirely
controlled by its owning entity.
I ran these three tests to test it out:
#Test
public void selectStudentAndSetBooksCollectionToNull() {
Student student = studentDao.getById(3L);
List<String> books = student.getBooks();
books.forEach(System.out::println);
student.setBooks(null);
em.flush(); // delete from student_book where student_id = ?
}
#Test
public void selectStudentAndAddBookInCollection() {
Student student = studentDao.getById(3L);
List<String> books = student.getBooks();
books.add("PHP Book");
books.forEach(System.out::println);
em.flush(); // insert into student_book(student_id, book) values(?, ?)
}
#Test
public void selectStudentAndChangeCollection() {
Student student = studentDao.getById(3L);
List<String> newBooks = new ArrayList<>();
newBooks.add("Rocket Engineering");
newBooks.forEach(System.out::println);
student.setBooks(newBooks);
em.flush(); // delete from student_book where student_id = ?
// insert into student_book(student_id, book) values(?, ?)
}
This is the Student class:
#Entity
#Table(name = "student")
public class Student {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "student_id", nullable = false, insertable = false, updatable = false)
private Long id;
#Column(name = "name", nullable = false)
private String name;
#ElementCollection
#CollectionTable(
name = "student_books",
joinColumns = #JoinColumn(name = "student_id", referencedColumnName = "student_id"))
#Column(name = "book")
private List<String> books = new ArrayList<>();
// Getters & Setters
}
This is my Entity configuration
#Entity
#NamedQuery(name = "Payment.findByEmail", query = "SELECT p FROM Payment p JOIN p.additionalAuthData a " +
"WHERE KEY(a) = 'email' AND VALUE(a) = ?1 AND (p.paymentType = 4 OR p.paymentType = 10)")
public class Payment {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
#Column(name = "payment_type")
private Integer paymentType;
/** other properties, getters and setters */
#ElementCollection
#CollectionTable(name = "additional_auth_data")
#MapKeyJoinColumn(name = "id", referencedColumnName = "id")
#MapKeyColumn(name = "field")
#Column(name = "data_value")
private Map<String, String> additionalAuthData;
}
The NamedQuery findByEmail("test#example.com") generates the following SQL
select -- all fields ...
from payment payment0_ inner join additional_auth_data additional1_ on payment0_.id=additional1_.id
where
additional1_.field='email' and (select additional1_.data_value from additional_auth_data additional1_ where payment0_.id=additional1_.id)='test#example.com' and (payment0_.payment_type=4 or payment0_.payment_type=10)
which is wrong: it may work if you have only one row but it blows up otherwise. H2 complains Scalar subquery contains more than one row and PostgreSQL more than one row returned by a subquery used as an expression. In fact, query's where condition compares a scalar value ('test#example.com') with a subquery.
The correct SQL should be:
select -- all fields
from payment payment0_ inner join additional_auth_data additional1_ on payment0_.id=additional1_.id
where additional1_.field='payerEmail' and additional1_.data_value='test#example.com' and (payment0_.payment_type=4 or payment0_.payment_type=10)
Is the HSQL correct? Is there a way to instruct Hibernate to generates a clever, better SQL? Is this a Hibernate bug?
Note: Hibernate shipped with Spring Boot Starter 1.3.7.RELEASE
Edit:
Using an #Embeddable class
#ElementCollection
#JoinTable(name = "additional_auth_data", joinColumns = #JoinColumn(name = "id"))
#MapKeyColumn(name = "field")
#Column(name = "data_value")
private Set<AdditionalData> additionalAuthData;
#Embeddable
public static class AdditionalData {
#Column(name = "field", nullable = false)
private String field;
#Column(name = "data_value")
private String dataValue;
protected AdditionalData() {
}
public AdditionalData(String field, String dataValue) {
this.field = field;
this.dataValue = dataValue;
}
/** Getters, setters; equals and hashCode on "field" */
}
#NamedQuery(name = "Payment.findByEmail", query = "SELECT p FROM Payment p JOIN p.additionalAuthData a " +
"WHERE a.field = 'email' AND a.dataValue = ?1 AND (p.paymentType = 4 OR p.paymentType = 10)")
solves the problem, and the SQL is correct, but it looks just plain wrong, like shooting a fly with a bazooka...
It generates correct SQL without value().
Use just a=?1
But I would expect is should generate it simple also with it.
I'm having a big problem for which I can't find an answer.
I have a OneToOne bidirectional Lazy fetched relation between two entities, but when I query one of them, it eagerly fetch the other, even though I'm explicitly saying that it's Lazy.
According to what I've found, almost all from 2010 and back, says that that is the way hibernate behaves on OneToOne relations. This is my first time using Hibernate ever but I highly doubt that is true, and if it was that it still is true. But i can't find any recent information about it. Post1, Post2, Post3 as you can see, they are not really recent. So I wanted to know if that's still the case or maybe guide me to where I can find the answer, examples, anything would help really.
Here's what I'm doing, just in case I'm doing something wrong.
Note the classes has fewer attributes than the original, but all of them are columns of the entity, no FK, PK, or something like that.
User Entity
#Entity
#Table(name = "\"User\"")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "Id", unique = true, nullable = false)
private long id;
#Column(name = "Username", nullable = false, unique = true, length = 100)
private String username;
#Valid
#OneToOne(fetch = FetchType.LAZY, mappedBy = "user", cascade = CascadeType.ALL)
#JsonBackReference
private UserProfile userProfile;
//Getters and Setters
}
UserProfile Entity
#Entity
#Table(name = "UserProfile")
public class UserProfile {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#NotBlank
#Size(max = 100)
#Column(name = "FirstName", nullable = false, unique = false, length = 100)
private String firstName;
#NotBlank
#Size(max = 100)
#Column(name = "LastName", nullable = false, unique = false, length = 100)
private String lastName;
#Valid
#OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name = "UserId")
#JsonManagedReference
private User user;
//Getters and Setters
}
Test controller method
#RequestMapping(value = "/test", method = RequestMethod.GET)
public ResponseEntity<Void> test(){
User user = userService.findUserByUsername("admin");
if(user.getUserProfile() == null){
return new ResponseEntity<Void>(HttpStatus.OK);
}
return new ResponseEntity<Void>(HttpStatus.CONFLICT);
}
And the UserService simply calls the UserDao method, which is
#Override
public User findByUsername(String username) {
Criteria criteria = createEntityCriteria();
criteria.add(Restrictions.eq("username", username));
return (User) criteria.uniqueResult();
}
The result of that service is always Conflict. Even though I'm calling the UserService (Which is Transactional) and the direct relation is in UserProfile. The controller is not transactional.
The log:
2016-02-29 18:50:58 DEBUG SQL::logStatement:92 -
select
this_.Id as Id1_2_0_,
this_.Username as Username5_2_0_
from
Public.[User] this_
where
this_.Username=?
Hibernate:
select
this_.Id as Id1_2_0_,
this_.Username as Username5_2_0_
from
Public.[User] this_
where
this_.Username=?
2016-02-29 18:50:58 DEBUG Loader::debugf:384 - Result set row: 0
2016-02-29 18:50:58 DEBUG Loader::getRow:1514 - Result row: EntityKey[com.portal.model.user.User#1]
2016-02-29 18:50:58 DEBUG TwoPhaseLoad::doInitializeEntity:144 - Resolving associations for [com.portal.model.user.User#1]
2016-02-29 18:50:58 DEBUG Loader::loadEntity:2186 - Loading entity: [com.portal.model.user.UserProfile#1]
2016-02-29 18:50:58 DEBUG SQL::logStatement:92 -
select
userprofil0_.id as id1_18_0_,
userprofil0_.FirstName as FirstNam5_18_0_,
userprofil0_.LastName as LastName7_18_0_,
userprofil0_.UserId as UserId10_18_0_
from
Public.UserProfile userprofil0_
where
userprofil0_.UserId=?
Hibernate:
select
userprofil0_.id as id1_18_0_,
userprofil0_.FirstName as FirstNam5_18_0_,
userprofil0_.LastName as LastName7_18_0_,
userprofil0_.UserId as UserId10_18_0_
from
Public.UserProfile userprofil0_
where
userprofil0_.UserId=?
Is there something wrong? Am I doing something wrong?
Thanks for any help you can provide! Let me know if you need more info!