I'm a beginner on JPA and I don't know pass result of a query to attribute entity.
Let me explain in one example:
I have an entity called Team, and another called Players:
The player is children of Team.
Class Team:
public class Team{
...
(relationship with Player has been hidden)
...
#Column(name = "AMOUNT_PLAYERS")
private Short amountPlayers;
#Column(name = "AMOUNT_FIRSTSTRING_PLAYERS")
private Short amountFirstStringPlayers;
#Column(name = "AMOUNT_SECONDSTRING_PLAYERS")
private Short amountSecondStringPlayers;
...
}
Class Player:
Public class Player{
...
#Column("STATUS_PLAYER")
private Short statusPlayer;
...
}
I have two questions about this.
1) In the amountPlayers, I want number total of player from this team;
In the amountFirstStringPlayers, I want number total of players (first-string) with statusPlayer of Player equals 1;
And in the amountSecondStringPlayers, I want number total of players (second-string) with statusPlayer of Player equals 2;
How I can get this values when I Find entity with JPA.
Example:
SELECT team FROM Team team
When I execute this, I want amountPlayers, amountFirstStringPlayers and amountSecondStringPlayers.
2) Put the resultQuery in an attribute of an entity, on an entity is a good practice?
You can use hibernate formula which are very convenient for these cases where you dont really need to persist these computed datas.
ex :
#Formula("(select count(*) from Player p where p.team_id = id)")
private Short amountPlayers;
#Formula("(select count(*) from Player p where p.team_id = id and p.statusPLayer=1)")
private Short amountFirstStringPlayers;
#Formula("(select count(*) from Player p where p.team_id = id and p.statusPLayer=2)")
private Short amountSecondStringPlayers;
Formula accepts sql string as parameter and here in the example id is the id of the current entity. You must replace with your entity id name and column.
If you don't want to use Formula you can also compute these values in memory based on players relation in your team entity.
ex:
#OneToMany(mappedBy="team")
private List<Player> players; // Here your onetomany association
public short getAmountPlayers(){
return players!= null ? players.size() : 0;
}
Related
I am quite a beginner with Spring Data, and I have to code my first difficult query :)
I am making a reservation engine for an event. My data model is composed by:
a RoomType entity defining a possible configuration for a room (e.g. double, triple, quadruple)
a Room entity representing the actual Room
a RoomArrangement entity defining all the possible RoomTypes for a Room (e.g. the room 7 can be configured as Triple or Double room)
a RoomAssignment entity representing the actual configuration chosen for a room after having been reserved
FKs are configured this way
RoomType <--* RoomArrangement *--> Room <-- RoomAssignment
(see code below for Entity definition)
I need to find the Room without an Assignment that has the highest Priority (= nearest to 1) for a given RoomType.
I have configured Spring as below.
RoomType.java
#OneToMany(fetch = FetchType.LAZY)
#JoinColumn(name = "room_type_id")
private List<RoomArrangement> roomArrangements;
RoomArrangement.java
#ManyToOne(targetEntity = RoomType.class)
#JoinColumn(name = "room_type_id", nullable = false)
private RoomType roomType;
#ManyToOne(targetEntity = Room.class)
#JoinColumn(name = "room_id", nullable = false)
private Room room;
#Column(name = "priority")
private Integer priority;
Room.java
#OneToMany(fetch = FetchType.LAZY)
#JoinColumn(name = "room_id")
private List<RoomArrangement> roomArrangements;
#OneToOne(fetch = FetchType.LAZY, mappedBy = "room", cascade = CascadeType.ALL)
private RoomAssignment assignment;
RoomArrangementRepository.java
RoomArrangement findFirstByRoomTypeAndRoom_AssignmentIsNullOrderByPriorityAsc(RoomType rt);
The query above is translated to
select
roomarrang0_.id as id1_3_,
roomarrang0_.priority as priority2_3_,
roomarrang0_.room_id as room_id3_3_,
roomarrang0_.room_type_id as room_typ4_3_
from
room_arrangements roomarrang0_
left outer join
rooms room1_ on roomarrang0_.room_id=room1_.id
where
roomarrang0_.room_type_id=9
and
(room1_.id is null)
order by
roomarrang0_.priority asc
limit 1;
The issues are two:
I do not know where the where clause
(room1_.id is null)
comes from
I do not know where the "AndRoom_AssignmentIsNull" clause has gone
Should I "invert" the OneToOne relationship and put the FK on the Room class?
Thanks for your help!
Lorenzo
I've tried to apply some of the suggestions, and "turned" the query on the RoomRepository.
The query came like this:
Room findFirstByRoomArrangements_RoomTypeAndAssignmentIsNullOrderByRoomArrangements_PriorityAsc(RoomType rt);
We come to the same problem:
select
room0_.id as id1_6_,
room0_.room_name as room_nam2_6_
from
rooms room0_
left outer join
room_arrangements roomarrang1_ on room0_.id=roomarrang1_.room_id
where
roomarrang1_.room_type_id=?
and
(room0_.id is null)
order by
roomarrang1_.priority asc
limit ?;
I think the problem lies in the fact that the one-to-one relationship between Room and RoomAssignment is represented on the database with a FK on the room_assignments table to the rooms table.
I will try to put the FK on the rooms table instead of on the room_assignments table and see if something changes.
If you need to find a room without an without an Assignment, shouldn't you be looking that in the Room repository?
You said that you have to find a Room but you are returning a RoomArrangement in a RoomAssignmentRepository. This is quite confusing.
Let's assume you are in right place, that means RoomRepository and as you said:
I need to find the Room without an Assignment that has the highest Priority (= nearest to 1) for a given RoomType.
try to use the following method name
Room findByRoomArrangementRoomTypeAndRoomAssignmentIsNullOrderByPriorityAsc(RoomType rt)
Made it!
In the end, the problem lied in the fact that the one-to-one relationship between Room and RoomAssignment was mapped by an FK from RoomAssignment to Room. Apparently, Spring Data didn't manage this configuration properly: the Assignment is null was translated to room_assignment.room_id = null and, since room_id was an FK to room.id, to room.id = null.
Inverting the FK mapping, the query is translated to
select
room0_.id as id1_6_,
room0_.assignment_id as assignme3_6_,
room0_.room_name as room_nam2_6_
from
rooms room0_
left outer join room_arrangements roomarrang1_ on room0_.id=roomarrang1_.room_id
where
roomarrang1_.room_type_id=?
and (room0_.assignment_id is null)
order by
roomarrang1_.priority asc
limit ?
which correctly returns what I needed.
Say I want to get all rows of the MyEntity that have an id lower than 10. This entity contains a list of Another entity. I would like this list to be fetched only by a subset of the listAnother. This subset containing only Another where the user contained in it is a specific one.
Basically in SQL it would be like this :
SELECT * FROM myentity m
LEFT JOIN another a
ON m.idTable=a.my_entity
AND a.user = "test1"
WHERE m.idTable < 10;
I didn't manage however to translate this query to jpql.
My entities being like this :
#Entity
public class MyEntity implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int idTable;
#OneToMany(mappedBy = "myEntity")
private List<Another> listAnother;
}
#Entity
public class Another implements Serializable {
#Id
private int idAnother;
// bi-directional many-to-one association to Thethread
#ManyToOne(fetch = FetchType.LAZY)
private MyEntity myEntity;
#ManyToOne(fetch = FetchType.LAZY)
private User user;
}
#Entity
public class User implements Serializable {
#Id
private String username;
}
In jpa I could do this :
SELECT m FROM MyEntity where m.idTable < 10;
And then for each entity I get from this list call this:
SELECT a FROM Another Where a.user.username=:'test' AND a.myEntity=:entity;
However I would like to do it all at once in one query. Can I do this with criteria ? I didn't take the time to learn it but if it's possible then I will.
JPQL and Critaria API are equal in terms of what you can express with them. What is possible with JPQL is possible with Criteria and vice versa.
With JPQL, you can simply combine your 2 queries into one in this way:
SELECT a FROM Another a Where a.user.username=:test AND a.myEntity.idTable < 10
You can use dot notation (.) to join multiple entities in the query, provided that the relationship is X-to-one. If you have X-to-many relationship, you need to use JPQL JOIN, which is not very complicated. Example with (LEFT) JOIN:
SELECT m FROM MyEntity m LEFT JOIN m.listAnother a Where a.user.username=:test AND m.idTable < 10
The result is of course not equal - in first case you will get list of Another entities and you can get MyEntity by a.myEntity, in the second case you will get list of MyEntity, which all have at least one Another entity with given user
In hibernate you can use Filters and FilterJoinTable. Here you can read how to do that. Similar problem was solved here.
You need to extend the logic which you applied to check the username (a.user.username=:'test') which was for many-to-one relation between anything and user by taking it one level up to myEntity and then using it for one-to-many relation as well -
SELECT m FROM MyEntity where m.idTable < 10 and (m.listAnother.user.username=:'test')
The join condition "m.listAnother.myEntity=:entity" wouldn't be needed now as in our query we have started from the specific myEntity and then moved down to listAnother.user.username .
I don't have the table definitions to try this query myself, exact SQL may require some tweaks - but logically it should work like the way I showed above, i.e. just the way you joined Another with User, the same way you can join MyEntity with Another by just traversing down the child listAnother.
I'm having a hard time trying to figure this out.
The problem is, i have a table containing the score of users in a specific game.
So, i have 2 entities called User and Game.
Users have no direct relationship to a Game, but a i have a View in the database that has the following field:
public UserScore implements Serializable {
#Id
#ManyToOne
private Game game;
#Id
#ManyToOne
private User user;
private Long score;
}
So, what a i need is, given a User and a Game, i need the classification of this user in that game based o his score.
The SQL query that do this is the following:
select user.name as name,
scores.score as myScore,
count(distinct countScores.score) as classification
from user INNER JOIN userscore as scores ON user.id = scores.user_id,
userscore as countScores
where user.id = 66 and scores.game_id = 666
and countScores.game_id = 666 and countScores.score > scores.score
group by user.id;
Assuming that 66 is the ID of the user you want the classification position and 666 the ID of the game that you are considering.
So, this works, but how to translate it to the Hibernate criteria API?
Since i can't create a Criteria with multiple root entities, i tryed to do the following:
DetachedCriteria countCriteria = DetachedCriteria.forClass(UserScore.class, "countScores")
.add(Restrictions.eq("game.id", 666))
.add(Restrictions.gtProperty("score", "scores.score"))
.setProjection(Projections.countDistinct("score"));
this.session.createCriteria(User.class)
.add(Restrictions.eq("id", 66))
.createAlias("userScore", "scores", JoinType.INNER_JOIN, Restrictions.eq("game", 666))
.setProjection(Projections.projectionsList()
.add(Projections.property("name"), "name")
.add(Projections.property("scores.score"), "myScore")
.add(Projections.groupProperty("id"))
// Here is where this is killing me:
// .add(Projections.SOMETHING(countCriteria), "classification")
);
...
So, this is the problem, how i can select as a field in the return set the result of a subquery?
There must be a way to do that, otherwise i have no ideas to solve this dilema.
Thank you in advance.
You can create another DetachedCriteria and then to join them like:
someNewDetachedCriteria.add(Subqueries.propertyEq("classification", countCriteria));
I'm trying to follow the JPA tutorial and using ElementCollection to record employee phone numbers:
PHONE (table)
OWNER_ID TYPE NUMBER
1 home 792-0001
1 work 494-1234
2 work 892-0005
Short version
What I need is a class like this:
#Entity
#Table(name="Phones")
public class PhoneId {
#Id
#Column(name="owner_id")
long owner_id;
#Embedded
List<Phone> phones;
}
that stores each person's phone numbers in a collection.
Long version
I follow the tutorial code:
#Entity
#Table(name="Phones")
public class PhoneId {
#Id
#Column(name="owner_id")
long owner_id;
#ElementCollection
#CollectionTable(
name="Phones",
joinColumns=#JoinColumn(name="owner_id")
)
List<Phone> phones = new ArrayList<Phone>();
}
#Embeddable
class Phone {
#Column(name="type")
String type = "";
#Column(name="number")
String number = "";
public Phone () {}
public Phone (String type, String number)
{ this.type = type; this.number = number; }
}
with a slight difference that I only keep one table. I tried to use the following code to add records to this table:
public static void main (String[] args) {
EntityManagerFactory entityFactory =
Persistence.createEntityManagerFactory("Tutorial");
EntityManager entityManager = entityFactory.createEntityManager();
// Create new entity
entityManager.getTransaction().begin();
Phone ph = new Phone("home", "001-010-0100");
PhoneId phid = new PhoneId();
phid.phones.add(ph);
entityManager.persist(phid);
entityManager.getTransaction().commit();
entityManager.close();
}
but it keeps throwing exceptions
Internal Exception: org.postgresql.util.PSQLException: ERROR: null
value in column "type" violates not-null constraint Detail: Failing
row contains (0, null, null). Error Code: 0 Call: INSERT INTO Phones
(owner_id) VALUES (?) bind => [1 parameter bound] Query:
InsertObjectQuery(tutorial.Phone1#162e295)
What did I do wrong?
Sadly, i think the slight difference that you only keep one table is the problem here.
Look at the declaration of the PhoneId class (which i would suggest is better called PhoneOwner or something like that):
#Entity
#Table(name="Phones")
public class PhoneId {
When you declare that a class is an entity mapped to a certain table, you are making a set of assertions, of which two are particularly important here. Firstly, that there is one row in the table for each instance of the entity, and vice versa. Secondly, that there is one column in the table for each scalar field of the entity, and vice versa. Both of these are at the heart of the idea of object-relational mapping.
However, in your schema, neither of these assertions hold. In the data you gave:
OWNER_ID TYPE NUMBER
1 home 792-0001
1 work 494-1234
2 work 892-0005
There are two rows corresponding to the entity with owner_id 1, violating the first assertion. There are columns TYPE and NUMBER which are not mapped to fields in the entity, violating the second assertion.
(To be clear, there is nothing wrong with your declaration of the Phone class or the phones field - just the PhoneId entity)
As a result, when your JPA provider tries to insert an instance of PhoneId into the database, it runs into trouble. Because there are no mappings for the TYPE and NUMBER columns in PhoneId, when it generates the SQL for the insert, it does not include values for them. This is why you get the error you see - the provider writes INSERT INTO Phones (owner_id) VALUES (?), which PostgreSQL treats as INSERT INTO Phones (owner_id, type, number) VALUES (?, null, null), which is rejected.
Even if you did manage to insert a row into this table, you would then run into trouble on retrieving an object from it. Say you asked for the instance of PhoneId with owner_id 1. The provider would write SQL amounting to select * from Phones where owner_id = 1, and it would expect that to find exactly one row, which it can map to an object. But it will find two rows!
The solution, i'm afraid, is to use two tables, one for PhoneId, and one for Phone. The table for PhoneId will be trivially simple, but it is necessary for the correct operation of the JPA machinery.
Assuming you rename PhoneId to PhoneOwner, the tables need to look like:
create table PhoneOwner (
owner_id integer primary key
)
create table Phone (
owner_id integer not null references PhoneOwner,
type varchar(255) not null,
number varchar(255) not null,
primary key (owner_id, number)
)
(I've made (owner_id, number) the primary key for Phone, on the assumption that one owner might have more than one number of a given type, but will never have one number recorded under two types. You might prefer (owner_id, type) if that better reflects your domain.)
The entities are then:
#Entity
#Table(name="PhoneOwner")
public class PhoneOwner {
#Id
#Column(name="owner_id")
long id;
#ElementCollection
#CollectionTable(name = "Phone", joinColumns = #JoinColumn(name = "owner_id"))
List<Phone> phones = new ArrayList<Phone>();
}
#Embeddable
class Phone {
#Column(name="type", nullable = false)
String type;
#Column(name="number", nullable = false)
String number;
}
Now, if you really don't want to introduce a table for the PhoneOwner, then you might be able to get out of it using a view. Like this:
create view PhoneOwner as select distinct owner_id from Phone;
As far as the JPA provider can tell, this is a table, and it will support the queries it needs to do to read data.
However, it won't support inserts. If you ever needed to add a phone for an owner who is not currently in the database, you would need to go round the back and insert a row directly into Phone. Not very nice.
I could not make a better title, if anyone can do it please, help me out! Same for tags.
I've made a JPQL to bring me one Object with a List of other Objects.
The thing that's happening is.
I've got 2 items in Novidade(DB).
I've got 2 items in ComentarioNovidade(DB).
1 of the items from Novidade, connects to all 2 items from ComentarioNovidade. The other has no ComentarioNovidade related.
JPQL returns a List of Novidade (it's supposed to be)
I'm trying to make it return one Novidade with all ComentarioNovidade in it if it has any.
It's returning 3 Objects containing Novidade and ComentarioNovidade separated.
My JPQL is like this:
from Novidade as n left outer join n.comentariosNovidade
The class Novidade:
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name="CodNovidade")
private Integer codNovidade;
#Column(name="Mensagem")
private String mensagem;
#Column(name="CodigoCidade")
private int codCidade;
#Column(name="CodigoBairro")
private int codBairro;
#Column(name="MesmoBairro")
private String mesmoBairro;
#OneToMany
#JoinColumn(name="CodNovidade")
private List<ComentarioNovidade> comentariosNovidade;
The class ComentarioNovidade:
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name="CodComentarioNovidade")
private Integer codComentarioNovidade;
#Column(name="Comentario")
private String comentario;
#ManyToOne
#JoinColumn(name="CodNovidade")
private Novidade novidade;
#ManyToOne
#JoinColumn(name="CodUsuario")
private Usuario usuario;
A friend helped me out with that.
My JPQL ended up like this:
select distinct n from Novidade as n left outer join fetch n.comentariosNovidade