I am wondering if there is any way to retrieve a record and specific number of its members of collection.
I have a class called MyGroup which keeps a list of Students, each student can be in one group at a time. I need to retrieve list of available students of each group. If I retrieve all the students of each group and find available ones it needs a big amount of memory to run a loop through the collection and check the available field of each student.
On the other hand, (Bidirectional) if I execute a select clause on students to retrieve those students that are available, in this way I am retrieving the group class record as well (since it is a member of student). So for each of students I am retrieving its fields + group's fields.
Sample data
Group 1
Name available
Jack true
Luke false
Nou true
...
Group 2
Name available
Mike false
George false
Alex true
...
Expected Result
Name Available Group GroupName
Jack true 1 Wolfs
Nou true 1 Wolfs
Alex true 2 Tigers
Classes
#Entity
public class Student {
#Id
#GeneratedValue
private long id;
private String name;
private boolean available;
#OneToOne
private MyGroup group;
getters and setters
}
#Entity
public class MyGroup {
#Id
#GeneratedValue
private long id;
private String name;
#OneToMany
private List<Student> student = new ArrayList();
getters and setters
}
In the hibernate by default lazy loading is true so if you are executing a select clause on the students table and fetching students so by default with one student object u will get the group id as the foreign key and i think not all the records of the associated group object will fetch atonce .If u want to fetch the list of students associated with the group object later u can do that but due to the lazy loading list of students associated with a group object will not fetch atonce .I think this is your major problem.You do not want to fetch the list of students associated with a group atonce.I think your current student table structure will be like this .
id name group_id
1 jack 1
2 joe 2
3 Mack 1
if you can add one more field boolean available then your query can be more specific like the student which belong to a group id and avialble true.
Boolean available
now your student table structure will be like this.
id name group_id available
1 jack 1 true
2 Joe 2 true
3 Mack 1 false
so now u can query specific set of student based on available boolean field
Related
How to select query filter in one-to-one relationship with Spring Data JDBC ?
Schema looks like this, basically 2 tables where Rental references Movie
drop table if exists rental;
drop table if exists movie;
create table movie
(
id serial primary key,
title text,
description text
);
create table rental
(
movie integer primary key references movie (id),
duration text,
price integer
)
And my code looks like this
#Query("select * from movie where title = :title ")
fun findByTitle(#Param("title") title: String): List<Movie>
But got an exception org.springframework.data.mapping.MappingException: Could not read value rental_movie from result set!
The example project on GitHub.
P.S I am quite new to this and followed this video to learn basics, please help me to do it in proper way
Solution # 1
Use the #Query like this, but still not so good since there can be a lot of columns inside second table
SELECT movie.*,
rental.price AS rental_price,
rental.duration AS rental_duration,
rental.movie AS rental_movie
FROM movie
LEFT OUTER JOIN rental ON rental.movie = movie.id
where movie.title = 'Matrix'
Your solution #1 is currently the correct way to do this.
Query must return columns for all simple properties of the aggregate root, and for all embedded or referenced entities.
If you don't want to do that you can alway specify your own RowMapper or ResultSetExtractor
Let's assume the following classes (similar to those you probably have):
class Movie {
#Id Long id;
String title;
#Embedded
Actor with;
Rental rental;
}
class Actor {
String name;
}
class Rental {
Integer price;
}
Your select needs to return the following columns:
id for the id property of Movie
title for the title property of Movie
rental_price for the price property of Rental. Note the prefix rental comes from the property namerentalnot from the class nameRental`.
rental_movie this column is an artificial id for Rental used to determine if there is a Rental at all or if Movie.rental is null.
The value is irrelevant except for the fact if it is null or not.
This column is not required if Rental has an id column.
name for the property name of the class Actor.
Note: There is no prefix here. If you want a prefix, e.g. because a class is embedded multiple times you have to put that in the #Embedded annotation.
_Note #1: There is no artificial id here.
For embedded classes there is the onEmpty attribute of the #Embedded annotation to control if when all properties are null, if the whole embedded class is null or if the embedded class gets instantiated with all properties set to null.
With Spring Data JDBC 1.x a missing column causes an exception as you have seen.
From Version 2.0 a missing column will be silently ignored and the property not set.
There is an issue to provide an easier way to define by just defining the where clause: https://jira.spring.io/browse/DATAJDBC-225
data class Movie(
#Id
val id: Long?,
val title: String,
val description: String,
val rental: Rental
)
I don't use Kotlin, but I think if you want to query Movie and Rental together, you have to use #OneToOne annotation. Something like(java):
public class Movie {
#OneToOne(mappedBy = "moive", cascade = CascadeType.ALL,
fetch = FetchType.LAZY, optional = false)
private Rental rental;
}
And your Rental class is not right, need movieId.
public class Rental {
...
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "movie_id")
private Movie movie;
}
you could check some code example.
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;
}
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.
Here are the tables that I'm working with. There is a User table that has some predefined columns ('name' in the example below) and an Extra Attributes table that used for making schema extension possible. The Extra Attributes table stores values for any new extended attributes as key-value pairs('title' and 'mobile' are the extra attributes in the example below) and the 'user' column is used for joining with the main User table. This arrangement generally works well for most operations, but I am not able to think of a way to sort on any of the extra attributes for the Users.
User Table:
---------------
id name
==============
1 jon
2 harry
Extra attributes table:
--------------------------------
user attr_key attr_value
================================
1 title engineer
1 mobile 111-2222-4444
2 title manager
2 mobile 111-0000-5555
Result of joining the two tables above(few columns intentionally skipped):
--------------------------------------------
id name attr_key attr_value
============================================
1 jon title engineer
1 jon mobile 111-2222-4444
2 harry title manager
2 harry mobile 111-0000-5555
I'm using hibernate for ORM. Here are the entity classes:
#Entity
public class User {
private Long id;
private String name;
private Set<ExtraAttributes> extraAttributes;
public String getName() {}
#OneToMany(mappedBy="user",fetch=FetchType.EAGER)
public Set<ExtraAttributes> getExtraAttributes() {}
...
}
#Entity
public class ExtraAttribute {
private Long id;
private String attrKey;
private String attrValue;
private User user;
...
#ManyToOne(optional = false)
public User getUser() {}
...
}
I want to be able to sort the results by attr_value corresponding to a specific attr_key, say 'title'. The problem is that if I order the results by attr_value then all the attr_values will be considered, while I want to order by only the attr_values that correspond to a specific attr_key - 'title'. I thought of ordering both by attr_key as well as attr_value but this doesn't seem to solve the problem either.
Let's say I have the following two classes; User and Location. I want to create a DetachedCriteria to query the user table, and return all users who do not have a location with the name "xyz".
#Entity
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
Long id;
#CollectionOfElements
Set<Location> locations;
}
#Entity
public class Location{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
Long id;
#Column
String name;
}
The following code will return all users who DO have a location with name "xyz" set:
DetachedCriteria dc = DetachedCriteria.forClass(User.class);
dc.createCriteria("locations")
dc.add(Restrictions.eq("name", "xyz"));
If I change to Restrictions.ne(), that doesn't work, because it will only return users who actually have locations set. Also, if there are a bunch of locations set for a user, it will duplicate that user over and over.
Any ideas?
Using the entities User and Location as described in your original post:
1) Check if the associated locations is empty.
Junction or = Restrictions.disjunction();
or.add(Restrictions.isEmpty("locations"));
2) Create an associated criteria for locations using a LEFT_JOIN and an alias to be used in the "ne" restriction. The LEFT_JOIN is required so we still get User records back even if the locations relationship is empty.
userCriteria.createCriteria("locations", "loc", Criteria.LEFT_JOIN);
or.add(Restrictions.ne("loc.name", "xyz"));
3) Add the disjunction to the the original User criteria.
userCriteria.add(or);
You'll probably need to use some combination of Restrictions.and(), Restrictions.not(), Restrictions.or() and Restrictions.in() to get your inverse-logic to work right. Check http://docs.jboss.org/hibernate/core/3.5/api/org/hibernate/criterion/Restrictions.html - it can definitely be done!
To ensure you only get at-most-one User, use a Distinct projection (http://docs.jboss.org/hibernate/core/3.5/api/org/hibernate/criterion/Distinct.html) via the Projections.distinct() factory method (http://docs.jboss.org/hibernate/core/3.5/api/org/hibernate/criterion/Projections.html)- i.e.:
dc.setProjection(Projections.distinct(Projections.property("id")));
Something like that should do what you need.