Hibernate: Query entities which contain a specified element in a CollectionOfElements? - java

Let's say I have this entity (for Hibernate):
#Entity
public class Person {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
Long id;
#CollectionOfElements
#IndexColumn("phones_index")
Set<String> phones;
}
For example, I want to get instances of Person where their phones contain "555-1234".
How can I do a query on this? I am looking for something similar to:
session.createCriteria(Person.class)./*something*/.add(Restrictions./*something*/"555-1234").list();

Hi you can try this one
String phone = "555-1234";
Person person= (Person) session.createQuery("from Person p join p.phones pl where pl = :phone").setString("phone", phone).uniqueResult();

I think you want Hibernate's Restrictions.in() method, which takes a property name as the first argument, and either an array or Collection of objects as the second.
See also: The Javadoc
Edit: Upon re-reading your question, I think you can use any of the relevant Restrictions methods, in particular, eq:
session.createCriteria(Person.class).add(Restrictions.eq("phones", "555-1234")).list();

Related

Hibernate calculated collection #Formula

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.

Hibernate many to many fetching associated objects

#Entity
#Table(name = "MATCHES")
public class Match implements Serializable{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "MATCH_ID")
private Long id;
#ManyToMany(mappedBy = "matches", cascade = CascadeType.ALL)
private Set<Team> teams = new HashSet<Team>();
}
#Entity
#Table(name = "Teams")
public class Team implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "TEAM_ID")
private long id;
#ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinTable(name = "TEAM_MATCH", joinColumns = { #JoinColumn(name = "TEAM_ID") }, inverseJoinColumns = {
#JoinColumn(name = "MATCH_ID") })
private Set<Match> matches = new HashSet<Match>();
}
I got those classes, now I want to get all the matches and let's say, print names of both teams.
public List getAllMatches() {
Session session = HibernateUtil.getSession();
Transaction t = session.beginTransaction();
Criteria criteria = session.createCriteria(Match.class, "match");
criteria.createAlias("match.teams", "mt", JoinType.LEFT_OUTER_JOIN);
List result = criteria.list();
t.commit();
session.close();
return result;
}
But when I invoke that method, result has size 2 when I got only 1 match in my table. Both of those matches in result have 2 teams, which is correct. I have no idea why this happends. What I want is to have one Match object with two Team objects in 'teams' set, but I have two of those Match objects. They are fine, but there are two of them. I'm completely new to this and have no idea how to fix those criterias. I tried deleting 'FetchType.LAZY' from #ManyToMany in Team but it doesn't work. Team also has properties like Players/Trainer etc. which are in their own tables, but I don't want to dig that deep yet, baby steps. I wonder tho if doing such queries is a good idea, should I just return Matches and then if I want to get Teams, get them in another session?
Edit: I added criteria.setResultTransformer(DistinctRootEntityResultTransformer.INSTANCE); and it works, is that how I was suppose to fix that or this is for something completely different and I just got lucky?
I think the duplication is a result of your createAlias call, which besides having this side effect is redundant in the first place.
By calling createAlias with those arguments, you are telling Hibernate to not just return all matches, but to first cross index the MATCHES table with the TEAM_MATCH table and return a result for each matching pair of rows. You get one result for a row in the matches table paired with the many-to-many mapping to the first team, and another result for the same row in the matches table paired with the many-to-many mapping to the second team.
I'm guessing your intent with that line was to tell Hibernate to fetch the association. This is not necessary, Hibernate will fetch associated objects on its own automatically when needed.
Simply delete the criteria.createAlias call, and you should get the result you expected - with one caveat. Because the association is using lazy fetching, Hibernate won't load it until you access it, and if that comes after the session is closed you will get a LazyInitializationException. In general I would suggest you prefer solving this by having the session opened and closed at a higher level of abstraction - getting all matches is presumably part of some larger task, and in most cases you should really use one session for the duration of the entire task unless there are substantial delays (such as waiting for user input) involved. Changing that would likely require significant redesign of your code, however; the quick solution is to simply loop over the result list and call Hibernate.initialize() on the teams collection in each Match. Or you could just change the fetch type to eager, if the performance cost of always loading the association whether or not you need it is acceptable.

Hibernate: fetching only the list of associated entities

I'm kind of lost in the woods at this point trying to optimize how Hibernate gets its data from the database. Here's the case:
I have a Person class that has a OneToMany association with the Address class. At the time of querying it is known from the application point of view that we're not going to need the Person instance but we'd like to have the list of that Person's Addresses.
The classes look more/less like this (getters/setters omitted):
#Entity
public class Person {
#Id
#GeneratedValue
private Long id;
#Column
private String firstName;
#Column
private String lastName;
#OneToMany(cascade = CascadeType.ALL)
#JoinColumn(name="address_id")
private Set<Address> addresses = new HashSet<Address>();
//...
}
#Entity
public class Address {
#Id
#GeneratedValue
private Long id;
#Column
private String city;
#Column
private String street;
//...
}
Now what I'd like to achieve is a criteria query (I have all the other parts of the system already using Criteria API and I'd very much like to keep it consistent) that'll return addresses belonging to a given person.
Criteria is the limiting factor here: it only allows to select the root entity, or scalars. And since you don't have the reverse association (from address to person), it's not possible to do it in a simple way with Criteria.
I find HQL much more flexible, and much more readable as well. Criteria is useful when a query must be dynamically composed, but in this case, using a HQL query is straightforward:
select a from Person p inner join p.addresses a where p.id = :personId
It's actually doable in Criteria, but it would need a query which is less efficient and straightforward: something like
select a from Address a where a.id in (select a2.id from Person p inner join p.addresses a2 where p.id = :personId)
translated in Criteria.
That would be:
Criteria criteria = session.createCriteria(Address.class, "a");
DetachedCriteria dc = DetachedCriteria.forClass(Person.class, "p");
dc.createAlias("p.addresses", "a2");
dc.add(Restrictions.eq("p.id", personId);
dc.setProjection(Projections.property("a2.id"));
criteria.add(Subqueries.propertyIn("a.id", dc));
As you see: less readable, much longer, and less efficient.

Hibernate Criteria Join problem

I have a 2 classes that share a UUID and are uni-directionally mapped. I use the UUID to group related rows, and this group shares many details (this is just an example):
#Entity #Table
class Something {
#Id #Column("something_id")
private Long id;
private String uuid = UUID.randomUUID().toString();
#OneToMany
#JoinColumn("uuid")
private List<Detail> details = new LinkedList<Detail>();
}
#Entity #Table
class Detail {
#Id #Column("detail_id")
private Long id;
private String value;
private String uuid;
}
I'm attempting to use Criteria:
Criteria c = getSession().createCriteria(Something.class).createAlias("details", "detail").add(Restrictions.eq("detail.value", someValue));
This is all fine and dandy, but I'm not getting results because of the join:
inner join DETAIL d1_ on this_.SOMETHING_ID=d1_.UUID
Is it possible to specify:
inner join DETAIL d1 on this_.UUID=d1.UUID
I would have expected the join to use the #JoinColumn annotaiton to find the column to join on. I see that I can specify a join type, but I don't see a way to specify the actual column.
I would have expected the join to use the #JoinColumn annotation to find the column to join on. I see that I can specify a join type, but I don't see a way to specify the actual column.
The join is using the JoinColumn annotation since it's joining on d1_.UUID. However, because you didn't specify the referencedColumnName element, the foreign key is assumed to refer to the primary key of the referenced table (this_.SOMETHING_ID), hence the obtained result.
In other words, try this:
#OneToMany
#JoinColumn(name="uuid", referencedColumnName="uuid")
private List<Detail> details = new LinkedList<Detail>();
I'm not sure to understand the benefit but let's say it's another story.

Use fewer columns on SQL query through Hibernate Projections on Entity with ManyToOne relation

I'm trying to build a smaller SQL, to avoid the "select * from A" that is being build by default for hibernate Criteria.
If I use simple fields (no relation), through "Transformers", I have can manage to have this SQL:
select description, weight from Dog;
Hi, I have this Entity:
#Entity
public class Dog
{
Long id;
String description;
Double weight;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "person_id", nullable = false)
Person owner;
}
#Entity
public class Person
{
Long id;
String name;
Double height;
Date birthDate;
}
My goal is to have this:
select description, weight, owner.name from Dog
I tried this with with Criteria (and subcriteria):
Criteria dogCriteria = sess.createCriteria(Dog.class);
ProjectionList proList = Projections.projectionList();
proList.add(Projections.property("description"), description);
proList.add(Projections.property("weight"), weigth);
dogCriteria.setProjection(proList);
Criteria personCriteria = dogCriteria.createCriteria("owner");
ProjectionList ownerProList = Projections.projectionList();
ownerProList.add(Projections.property("name"), description);
dogCriteria.setProjection(ownerProList); //After this line, debugger shows that the
//projection on dogCriteria gets overriden
//and the query fails, because "name" is
//not a field of Dog entity.
How should I use Projections, to get a smaller SQL, less columns ?
Thanks in advance.
First of all,
select description, weight, owner.name from Dog
is not valid SQL. It would have to be something like
select description, weight, Person.name
from Dog join Person on Dog.person_id = Person.id
instead. Secondly, why? While it's possible to do what you want (see below), it's extremely verbose to do so via Criteria API and you gain nothing to show for it. Savings on data transfer for a couple of columns are negligible unless said columns are huge blobs or you're selecting hundreds of thousands of records. In either case there are better ways to deal with this issue.
Anywho, to do what you want for criteria, you need to join linked table (Person) via alias and specify projection on main criteria using said alias:
Criteria criteria = session.createCriteria(Dog.class, "dog")
.createAlias("owner", "own")
.setProjection( Projections.projectionList()
.add(Projections.property("dog.description"))
.add(Projections.property("dog.weight"))
.add(Projections.property("own.name"))
);
There's a description and an example of the above in Criteria Projections documentation. Keep in mind that, when executed, the above criteria would return a list of object arrays. You'll need to specify a ResultTransformer in order to have results converted into actual objects.
I didn't tried it yet by myself, but I think you can also use another constructor in your Entity (Pojo) and pass the columns there.
See https://www.thoughts-on-java.org/hibernate-best-practices/ chapter "1.2 Pojo" for a detailed instruction.
Altough for me it's not yet clear if this also works for ManyToOne relationships too. I will have a try.

Categories

Resources