How to get data with #ManyToMany annotated tables? - sql

My related tables are below.
Person Table
id
name
surname
Location Table
id
title
point
Person_Location Table
person_id
location_id
I want to get person and location values like this..
id | name | surname | title | point
1 john | adas | my home | 44,45
1 | John | adas | brother's home | 55,33
How can I get the users and their locations in hibernate?

try this out :
you can get an array of object and you can manipulat them as you want
String stringQuery="select p.id,p.name,p.surname,l.title,l.point from person p, location l,person_location pl where "
+ "p.id=pl.person_id and l.id=pl.location_id";
Query query=entityManager.createNativeQuery(stringQuery);
List<Object[]> result=query.getResultList();
later you can get the person Id by result.get(i)[0]
or you can create a custom class which will not be a managed entity:
public class customPerson{
id | name | surname | title | point
private int id;
private String name;
private String surname;
private String title;
private String doube point;
//getters&setters
//constructors (required) one default ant one with all your attributes
public CustomPerson(){}
public customPerson(int id,...){
...
}
}
later in your Dao you can get the result you want through the custom object:
String stringQuery="select p.id,p.name,p.surname,l.title,l.point from person p, location l,person_location pl where "
+ "p.id=pl.person_id and l.id=pl.location_id";
Query query=entityManager.createNativeQuery(stringQuery,CustomPerson.class);
List<CustomPerson> result=query.getResultList();

// In Person class:
#ManyToMany
#JoinTable(name="Person_Location",
joinColumns=
#JoinColumn(name="person_id", referencedColumnName="ID"),
inverseJoinColumns=
#JoinColumn(name="location_id", referencedColumnName="ID")
)
public Set<Locations> getLocations() { return locations; }
// In Location:
#ManyToMany(mappedBy="locations")
public Set<Person> getPersons() { return persons; }

In Person class
#OneToMany(mappedBy="person")
public Set<personLocations> getPersonLocation { return personLocations; }
In Location class
#OneToMany(mappedBy="location")
public Set<personLocations> getPersonLocation { return personLocations; }
In personLocations
#ManyToOne
#JoinColumn(name="person_ID")
public Person getPerson() { return person; }
#ManyToOne
#JoinColumn(name="location_ID")
public Location getlocation() { return location; }

Related

Get data from database with one or multiple queries?

If you have a table like:
|--------|------------|
| City | CountryId |
|--------|------------|
Is it better to get data with one query, like:
public List<CityModel> selectAll() throws Exception {
List<CityModel> cities = new ArrayList<>();
String selectCities = "select * from cities inner join countries on countries.id = cities.countryId";
// ... create city and country model in the same query
return cities;
}
Or should it be separated like:
public List<CityModel> selectAll() throws Exception {
List<CityModel> cities = new ArrayList<>();
String selectCities = "select * from cities";
// ... create get cities model from table and add them to cities
foreach(City city in cities)
{
String selectCountry = "select * from countries where id = " + city.getCountryId();
// ... create country model
city.setCountry(country)
}
...
return cities;
}
What is the best approach?
Thanks.

RavenDB: How can I properly index a cartesian product in a map-reduce?

This question is a spin-off of RavenDB: Why do I get null-values for fields in this multi-map/reduce index?, but I realized, the problem was another.
Consider my extremely simplified domain, rewritten to a movie rental store scenario for abstraction:
public class User
{
public string Id { get; set; }
}
public class Movie
{
public string Id { get; set; }
}
public class MovieRental
{
public string Id { get; set; }
public string MovieId { get; set; }
public string UserId { get; set; }
}
It's a text-book many-to-many example.
The index I want to create is this:
For a given user, give me a list of every movie in the database (filtering/search left out for the moment) along with an integer describing how many times (or zero) the user has rented this movie.
Basically like this:
Users:
| Id |
|--------|
| John |
| Lizzie |
| Albert |
Movies:
| Id |
|--------------|
| Robocop |
| Notting Hill |
| Inception |
MovieRentals:
| Id | UserId | MovieId |
|-----------|--------|--------------|
| rental-00 | John | Robocop |
| rental-01 | John | Notting Hill |
| rental-02 | John | Notting Hill |
| rental-03 | Lizzie | Robocop |
| rental-04 | Lizzie | Robocop |
| rental-05 | Lizzie | Inception |
Ideally, I want an index to query, that would look like this:
| UserId | MovieId | RentalCount |
|--------|--------------|-------------|
| John | Robocop | 1 |
| John | Notting Hill | 2 |
| John | Inception | 0 |
| Lizzie | Robocop | 2 |
| Lizzie | Notting Hill | 0 |
| Lizzie | Inception | 1 |
| Albert | Robocop | 0 |
| Albert | Notting Hill | 0 |
| Albert | Inception | 0 |
Or declaratively:
I always want a full list of all the movies (eventually I will add filtering/searching) - even when providing a user that has never rented a single movie yet
I want a count of the rentals for each user, just the integer
I want to be able to sort by the rental-count - i.e. show the most-rented movies for a given user at the top of the list
However, I can't find a way to make the "cross-join" above and save it in the index. Instead, I initially thought I got it right with this maneuver below, but it does not allow me to sort (see failing test):
{"Not supported computation: x.UserRentalCounts.SingleOrDefault(rentalCount => (rentalCount.UserId == value(UnitTestProject2.MovieRentalTests+<>c__DisplayClass0_0).user_john.Id)).Count. You cannot use computation in RavenDB queries (only simple member expressions are allowed)."}
My question is basically: how can I - or can I at all - index so, that my requirements are fulfilled?
Below is my mentioned example, that does not fulfill my requirements, but that's where I am right now. It uses the following packages (VS2015):
packages.config
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.Owin.Host.HttpListener" version="3.0.1" targetFramework="net461" />
<package id="NUnit" version="3.5.0" targetFramework="net461" />
<package id="RavenDB.Client" version="3.5.2" targetFramework="net461" />
<package id="RavenDB.Database" version="3.5.2" targetFramework="net461" />
<package id="RavenDB.Tests.Helpers" version="3.5.2" targetFramework="net461" />
</packages>
MovieRentalTests.cs
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using Raven.Client.Indexes;
using Raven.Client.Linq;
using Raven.Tests.Helpers;
namespace UnitTestProject2
{
[TestFixture]
public class MovieRentalTests : RavenTestBase
{
[Test]
public void DoSomeTests()
{
using (var server = GetNewServer())
using (var store = NewRemoteDocumentStore(ravenDbServer: server))
{
//Test-data
var user_john = new User { Id = "John" };
var user_lizzie = new User { Id = "Lizzie" };
var user_albert = new User { Id = "Albert" };
var movie_robocop = new Movie { Id = "Robocop" };
var movie_nottingHill = new Movie { Id = "Notting Hill" };
var movie_inception = new Movie { Id = "Inception" };
var rentals = new List<MovieRental>
{
new MovieRental {Id = "rental-00", UserId = user_john.Id, MovieId = movie_robocop.Id},
new MovieRental {Id = "rental-01", UserId = user_john.Id, MovieId = movie_nottingHill.Id},
new MovieRental {Id = "rental-02", UserId = user_john.Id, MovieId = movie_nottingHill.Id},
new MovieRental {Id = "rental-03", UserId = user_lizzie.Id, MovieId = movie_robocop.Id},
new MovieRental {Id = "rental-04", UserId = user_lizzie.Id, MovieId = movie_robocop.Id},
new MovieRental {Id = "rental-05", UserId = user_lizzie.Id, MovieId = movie_inception.Id}
};
//Init index
new Movies_WithRentalsByUsersCount().Execute(store);
//Insert test-data in db
using (var session = store.OpenSession())
{
session.Store(user_john);
session.Store(user_lizzie);
session.Store(user_albert);
session.Store(movie_robocop);
session.Store(movie_nottingHill);
session.Store(movie_inception);
foreach (var rental in rentals)
{
session.Store(rental);
}
session.SaveChanges();
WaitForAllRequestsToComplete(server);
WaitForIndexing(store);
}
//Test of correct rental-counts for users
using (var session = store.OpenSession())
{
var allMoviesWithRentalCounts =
session.Query<Movies_WithRentalsByUsersCount.ReducedResult, Movies_WithRentalsByUsersCount>()
.ToList();
var robocopWithRentalsCounts = allMoviesWithRentalCounts.Single(m => m.MovieId == movie_robocop.Id);
Assert.AreEqual(1, robocopWithRentalsCounts.UserRentalCounts.FirstOrDefault(x => x.UserId == user_john.Id)?.Count ?? 0);
Assert.AreEqual(2, robocopWithRentalsCounts.UserRentalCounts.FirstOrDefault(x => x.UserId == user_lizzie.Id)?.Count ?? 0);
Assert.AreEqual(0, robocopWithRentalsCounts.UserRentalCounts.FirstOrDefault(x => x.UserId == user_albert.Id)?.Count ?? 0);
var nottingHillWithRentalsCounts = allMoviesWithRentalCounts.Single(m => m.MovieId == movie_nottingHill.Id);
Assert.AreEqual(2, nottingHillWithRentalsCounts.UserRentalCounts.FirstOrDefault(x => x.UserId == user_john.Id)?.Count ?? 0);
Assert.AreEqual(0, nottingHillWithRentalsCounts.UserRentalCounts.FirstOrDefault(x => x.UserId == user_lizzie.Id)?.Count ?? 0);
Assert.AreEqual(0, nottingHillWithRentalsCounts.UserRentalCounts.FirstOrDefault(x => x.UserId == user_albert.Id)?.Count ?? 0);
}
// Test that you for a given user can sort the movies by view-count
using (var session = store.OpenSession())
{
var allMoviesWithRentalCounts =
session.Query<Movies_WithRentalsByUsersCount.ReducedResult, Movies_WithRentalsByUsersCount>()
.OrderByDescending(x => x.UserRentalCounts.SingleOrDefault(rentalCount => rentalCount.UserId == user_john.Id).Count)
.ToList();
Assert.AreEqual(movie_nottingHill.Id, allMoviesWithRentalCounts[0].MovieId);
Assert.AreEqual(movie_robocop.Id, allMoviesWithRentalCounts[1].MovieId);
Assert.AreEqual(movie_inception.Id, allMoviesWithRentalCounts[2].MovieId);
}
}
}
public class Movies_WithRentalsByUsersCount :
AbstractMultiMapIndexCreationTask<Movies_WithRentalsByUsersCount.ReducedResult>
{
public Movies_WithRentalsByUsersCount()
{
AddMap<MovieRental>(rentals =>
from r in rentals
select new ReducedResult
{
MovieId = r.MovieId,
UserRentalCounts = new[] { new UserRentalCount { UserId = r.UserId, Count = 1 } }
});
AddMap<Movie>(movies =>
from m in movies
select new ReducedResult
{
MovieId = m.Id,
UserRentalCounts = new[] { new UserRentalCount { UserId = null, Count = 0 } }
});
Reduce = results =>
from result in results
group result by result.MovieId
into g
select new
{
MovieId = g.Key,
UserRentalCounts = (
from userRentalCount in g.SelectMany(x => x.UserRentalCounts)
group userRentalCount by userRentalCount.UserId
into subGroup
select new UserRentalCount { UserId = subGroup.Key, Count = subGroup.Sum(b => b.Count) })
.ToArray()
};
}
public class ReducedResult
{
public string MovieId { get; set; }
public UserRentalCount[] UserRentalCounts { get; set; }
}
public class UserRentalCount
{
public string UserId { get; set; }
public int Count { get; set; }
}
}
public class User
{
public string Id { get; set; }
}
public class Movie
{
public string Id { get; set; }
}
public class MovieRental
{
public string Id { get; set; }
public string MovieId { get; set; }
public string UserId { get; set; }
}
}
}
Since your requirement says "for a given user", if you really are looking only for a single user, you can do this with a Multi-Map index. Use the Movies table itself to produce the baseline zero-count records and then map in the actual MovieRentals records for the user on top of that.
If you really need it for all users crossed with all movies, I don't believe there is a way to do this cleanly with RavenDB as this would be considered reporting which is noted as one of the sour spots for RavenDB.
Here are some options if you really want to try to do this with RavenDB:
1) Create dummy records in the DB for every user and every movie and use those in your index with a 0 count. Whenever a movie or user is added/updated/deleted, update the dummy records accordingly.
2) Generate the zero-count records yourself in memory on request and merge that data with the data that RavenDB gives you back for the non-zero counts. Query for all users, query for all movies, create the baseline zero-count records, then do the actual query for non-zero counts and layer that on top. Finally, apply paging/filtering/sorting logic.
3) Use the SQL replication bundle to replicate the Users, Movies, and MovieRental tables out to SQL and use SQL for this "reporting" query.

Hibernate Search (Lucene) filter on collections

I have a problem to implement a boolean logic with Hibernate Search Filter.
There are persons that can be part of groups. Every group has a status from status catalog.
I need to filter all the users that are in group 1 and have status 2. For that I'm using a boolean query with Occur.MUST for both clauses, but in the filtered result are included persons that has list of grops and one of them is 1 and one of the statuses of the group is 2, for example:
person | group | status
105 (1) 3
105 2 3
105 3 (2)
188 (1) 3
188 7 (2)
197 (1) 4
197 8 5
197 9 (2)
The users 105, 188 and 197 has not to be included in the filtered result. What is the correct way to accomplsh that?
Filter:
BooleanQuery bq = new BooleanQuery();
TermQuery tqGroup = new TermQuery(new Term("groupPersons.id.groupId", "1"));
TermQuery tqStatus = new TermQuery(new Term("groupPersons.status.id", "2"));
bq.add(tqGroup, BooleanClause.Occur.MUST);
bq.add(tqStatus, BooleanClause.Occur.MUST);
filter = new QueryWrapperFilter(bq);
Person entity:
...
private List<GroupPerson> groupPersons = new ArrayList<GroupPerson>(0);
#IndexedEmbedded
#OneToMany(fetch = FetchType.LAZY, mappedBy = "person")
public List<GroupPerson> getGroupPersons() {
return this.groupPersons;
}
GroupPerson entity:
...
#EmbeddedId
#AttributeOverrides({
#AttributeOverride(name = "groupId", column = #Column(name = "group_id", nullable = false)),
#AttributeOverride(name = "personId", column = #Column(name = "person_id", nullable = false)) })
#NotNull
#DocumentId
#FieldBridge(impl = GroupPersonIdBridge.class)
public GroupPersonId getId() {
return this.id;
}
...
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "status_id",nullable = false)
#IndexedEmbedded
#NotNull
public Status getStatus() {
return this.Status;
}
OrganizationPersonIdBridge:
public Object get(String name, Document document) {
GroupPersonId id = new GroupPersonId();
Field field = document.getField( name + ".groupId" );
id.setGroupId(Long.parseLong(field.stringValue()));
field = document.getField( name + ".personId" );
id.setPersonId(Long.parseLong(field.stringValue()));
return id;
}
public String objectToString(Object object) {
GroupPersonId id = (GroupPersonId) object;
StringBuilder sb = new StringBuilder();
sb.append( id.getGroupId() )
.append(" ")
.append(id.getPersonId());
return sb.toString();
}
public void set(String name,Object value,Document document,LuceneOptions luceneOptions) {
GroupPersonId id = (GroupPersonId)value;
Store store = luceneOptions.getStore();
Index index = luceneOptions.getIndex();
TermVector termVector = luceneOptions.getTermVector();
Float boost = luceneOptions.getBoost();
//store each property in a unique field
Field field = new Field(name + ".groupId", id.getGroupId() + "", store, index, termVector);
field.setBoost( boost );
document.add( field );
field = new Field(name + ".personId", id.getPersonId() + "", store, index, termVector);
field.setBoost( boost );
document.add( field );
//store the unique string representation in the named field
field = new Field( name,
objectToString( id ),
store, index, termVector );
field.setBoost( boost );
document.add( field );
}
The version of Hibernate search is 4.5.1.Final
The problem is that a Lucene Document does not have associations. When you are using #IndexedEmbedded you are effectively flattening all associations into a single Lucene Document (which is what's get added to a Lucene index and retrieved at search time). A Document can have the a field with the same name added multiple times. Taking your example, the Document for the Person with the id 105 will contain the following field name to field value pairs:
+-------------------------+-------------+
| field name | field value |
+-------------------------+-------------+
| groupPersons.id.groupId | 1 |
| groupPersons.id.groupId | 2 |
| groupPersons.id.groupId | 3 |
| groupPersons.status.id | 3 |
| groupPersons.status.id | 3 |
| groupPersons.status.id | 2 |
+-------------------------+-------------+
If you now look at your query, you understand why person 105 is a match. Both boolean queries match.
How can you solve the problem? You need to make sure to have something unique to search on. One way of doing this, is to index group and status into a single field - using a custom bridge. Then you can write a query which just targets that field.
For someone who haves the same uses case, here is the solution using classBridge:
public class CustomClassBridge implements FieldBridge, Serializable {
public final static String SEPARATOR = "-";
#Override
public void set(String name, Object value, Document document, LuceneOptions luceneOptions) {
GroupPerson gp = (GroupPerson)value;
String fieldValue = gp.getId().getGroupId() + SEPARATOR + gp.getStatus().getId();
Field field = new Field(name, fieldValue, luceneOptions.getStore(), luceneOptions.getIndex(), luceneOptions.getTermVector());
field.setBoost(luceneOptions.getBoost());
document.add(field);
}
}
Add annotation to GroupPerson entity on class level:
#ClassBridge(name="groupStatus",index=Index.YES, analyze=Analyze.NO, store=Store.YES, impl = CustomClassBridge.class)
And finally in the filter:
TermQuery tq = new TermQuery(new Term("groupPersons.groupStatus", 1 + CustomClassBridge.SEPARATOR + 2));

Return properties from subclass left joins when querying superclass in NHibernate

I have an ICriteria that returns properties from a superclass Animal. Now I want to include in the results several properties from a subclass, Bird. For other subclasses, these properties should return null. I am using table-per-subclass inheritance. Is there any way to do this without adding lots of DetachedCriteria? NHibernate already left joins on the subclass tables; is there a way to project values from those joins?
Update: I need to be able to sort and filter on the subclass properties, and the entire query needs to support paging.
Here is my model:
public abstract class Animal
{
public long Id { get; set; }
public string Name { get; set; }
}
public class Cat : Animal
{
public int WhiskerCount { get; set; }
}
public class Bird : Animal
{
public long WingSpan { get; set; }
}
Given the following tables:
Animal:
Id | Name
----+--------------
1 | Sylvester
2 | Tweety
3 | Maru
4 | Big Bird
Cat:
Id | WhiskerCount
----+--------------
1 | 6
3 | 12
Bird:
Id | Wingspan
----+--------------
2 | 0.5
4 | 10
I want the following result set:
Id | Name | Wingspan
----+------------+-------------
1 | Sylvester | <null>
2 | Tweety | 0.5
3 | Maru | <null>
4 | Big Bird | 10
var results = session.CreateCriteria<Animal>()
.List<Animal>()
.Select(a => new
{
Id = a.Id,
Name = a.Name,
Wingspan = (a is Bird) ? ((Bird)a).Wingspan : (int)null
});

unexpected JPA results

Im learning JPA and having problems. My main problem is when i join my entitys and dont quite understand why im getting the results i am .. and its realy slowing my progress if someone could help i would be very greatful.
Entitys
College
#Entity
#NamedQueries({
#NamedQuery(name=College.allCollegeQuery,query="SELECT col FROM College col"),
#NamedQuery(name=College.collegeStudentJoinQuery,query="SELECT DISTINCT col FROM College col JOIN FETCH col.students"),
#NamedQuery(name=College.collegeStudentBasicJoinQuery,query="SELECT col FROM College col JOIN col.students s")
})
public class College {
public static final String allCollegeQuery = "allCollegeQuery";
public static final String collegeStudentJoinQuery = "collegeStudentJoinQuery";
public static final String collegeStudentBasicJoinQuery = "collegeStudentBasicJoinQuery";
#Id
#GeneratedValue
private long id;
private String name;
#OneToMany(mappedBy="college")
private List<Student> students;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<Student> getStudents() {
return students;
}
}
Student
#Entity
#NamedQueries({
#NamedQuery(name=Student.allStudentsQuery, query="SELECT stu FROM Student stu"),
#NamedQuery(name=Student.studentsCollageQuery, query="SELECT stu.college FROM Student stu WHERE stu.id = :id"),
#NamedQuery(name=Student.studentsWithCollageIDQuery, query="SELECT stu FROM Student stu WHERE stu.college.id = :id")
})
public class Student {
public static final String allStudentsQuery = "allStudentsquery";
public static final String studentsCollageQuery = "studentsCollageQuery";
public static final String studentsWithCollageIDQuery = "studentsCollagewithIDQuery";
#Id
#GeneratedValue
private long id;
private String name;
#ManyToOne
private College college;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public College getCollege() {
return college;
}
#XmlTransient
public void setCollege(College college) {
this.college = college;
}
}
Ok so my goal is to query all college's and to return all the students related to them.
Which i was expecting my named query to do
SELECT col FROM College col JOIN col.students s
My responce was (invoked via glassfish Tester interface)
<return>
<id>1</id>
<name>"Bournemouth</name>
</return>
<return>
<id>1</id>
<name>"Bournemouth</name>
</return>
<return>
<id>1</id>
<name>"Bournemouth</name>
</return>
<return>
<id>1</id>
<name>"Bournemouth</name>
</return>
As you can imagen this college only exists once in the database, but i know there are four students attached to this college entity (which i guess why it has repeated).
The solution i and kind of hoping for would be somthing like (in a sudo xml kind way)
<return>
<id>1</id>
<name>Bournemouth</name>
<students>
<student>
</student>
</students>
</return>
any help tips or pointers would be greatfuly recived
Thanks
--------------------------- Additional Info ------------------------------
So i performed the SQL logging as Pascal kindly suggested which upon calling the method genorates
SELECT DISTINCT t1.ID, t1.NAME, t0.ID, t0.NAME, t0.college_Id FROM STUDENT t0, COLLEGE t1 WHERE (t0.college_Id = t1.ID)
I also tested the above script in my DB and its results are as i would expect JPA to return.
ID name id name col_id
1 "Bournemouth 2 James 1
1 "Bournemouth 3 Rich 1
1 "Bournemouth 1 Jon 1
1 "Bournemouth 4 tom 1
so i guess this means there must be a problem in my JPA setup?
as its obviously not repacking the results correctly.
Thanks Again
----------- Update ---------------------------
Further from Pascals suggestions i printed the data to the server log to cut out the
Tester interface.
public List<College> getCollageWithStudents(){
List<College> s = null;
try{
Query query = em.createNamedQuery(College.collegeStudentBasicJoinQuery);
s = query.getResultList();
for (int i = 0; i < s.size(); i++) {
College c = (College) s.get(i);
System.out.println("College "+c.getId() + " "+c.getName());
System.out.println("Amt Students: "+c.getStudents().size());
for (int j = 0; j < c.getStudents().size(); j++) {
Student stu = (Student) c.getStudents().get(j);
System.out.println(stu.getId() + " "+stu.getName());
}
}
}catch (Exception e) {
System.out.println(e.toString());
}
return s;
}
The result are as the should be but this is still not reflected in the Glassfish interface.
INFO: College 1 "Bournemouth
INFO: Amt Students: 4
INFO: 2 James
INFO: 3 Rich
INFO: 1 Jon
INFO: 4 tom
Your current query is doing an INNER JOIN and thus returns rows when there is at least one match in both tables. In other words, you'll get the College of each Student that has a College (and this explains why you are getting 4 times the the Bournemouth here).
What about:
SELECT DISTINCT c
FROM College c JOIN FETCH c.students
That would return distinct College having Student (and fetch the association).