Consider a hypothetical situation where I have two models: Company and User, defined like so
case class Company(name: String) extends Model {
#Id
var id: Long = _
#OneToMany(fetch = FetchType.LAZY)
var admins: util.List[User] = new util.ArrayList[User]()
}
case class User(email: String) {
#Id
var id: Long = _
}
Next, I have a request coming in and I want to check if user_id 200 is an admin of a company_id 100. The obvious solution is to fetch the company with that id, and then check iteratively in the admins list if a user_id but that is quite inefficient. What's the best way to go about this?
I think that the easiest way to solve this problem is to simply add relation from user to company. If you need calling such query then such relation has sense:
Here is relation added to User class:
#ManyToOne
var company:Company = _
And here is example how to check if user(id=200) is admin of company(id=100):
val user = Ebean.find(classOf[User], 200L)
println(user.company.id==100L)
Second option here is using RawSql. We can do it in a way similar to this:
val sql="select u.company_id as id from user u where u.id=200"
val rawSql = RawSqlBuilder.parse(sql).create()
val query = Ebean.find(classOf[Company])
query.setRawSql(rawSql)
val list = query.findList()
println(list(0).id==100L)
Related
I can represent this query:
SELECT * FROM group g JOIN user u ON user.group_id = group.id
via the following in JPA:
#EntityGraph(attributePaths = {"users.posts.comments"})
Optional<Group> findEagerlyFetchedById(UUID id);
But how do I filter out some users based on a field? I want to get the group with the given groupId, with user rows (and children of those) but only for users that are authenticated. As in, how do I represent the following SQL query in JPA?
SELECT * FROM group g JOIN user u ON user.group_id = group.id WHERE user.isAuthenticated = true
I currently have the query below but it takes an all-or-nothing approach. If a single user has matching isAuthenticated field then it returns the group along with all users regardless of whether that field is true for that user. Also, if no users are authenticated, then the group isn't returned at all.
#EntityGraph(attributePaths = {"users.posts.comments"})
#Query("SELECT g FROM Group g JOIN g.users gu WHERE gu.isAuthenticated = :isAuthenticated AND g.id = :groupId")
Optional<Group> findEagerlyFetchedByUserAuthed(UUID groupId, boolean isAuthenticated);
For reference these are the entity definitions:
Group:
#Entity
public class Group {
private UUID id;
#OneToMany(
mappedBy = "groups",
fetch = FetchType.LAZY,
cascade = CascadeType.ALL,
orphanRemoval = true
)
private Set<User> users = Sets.newHashSet();
}
User:
#Entity
public class User {
private UUID id;
private Boolean isAuthenticated;
#ManyToOne( fetch = FetchType.LAZY )
private Group group;
}
From what i am understanding is that you want to select all groups that have authenticated users.
As I understand your problem with "then it returns the group along with all users regardless of whether" is that the database loads all the users, into the java/context even if this is not needed. The Problem for this is probably that even though the users are fetched lazy
#OneToMany(
mappedBy = "groups",
fetch = FetchType.LAZY,
cascade = CascadeType.ALL,
orphanRemoval = true
)
private Set<User> users = Sets.newHashSet();
java has to evaluate weather or not or not the entry in the set is unique. Depending on your implementation of equals for Group or User (not shown in your example) it might be possible that the value of all fields is called, therefore requiring the Set to be fully loaded. A solution for this could be replacing the Set with a List.
private Set<User> users = new ArrayList<>();
Depending on your toString() implementation of the classes it could also just be a problem with debugging since most debuggers call the toString() implementation when trying to display an Object inside the debugger.
The second problem I understand you are approaching is "Also, if no users are authenticated, then the group isn't returned at all." I dont know how to help with that since your SQL clearly states
" ....g.users gu WHERE gu.isAuthenticated = :isAuthenticated ..."
this will always just return groups with authenticated users. Here i cant understand what your problem is. That is what i thought was your goal.
A practical approach that might help you could be selecting the Users and then accessing the groups (in Java via streams).
#Query("SELECT u FROM Users u WHERE u.isAuthenticated = :isAuthenticated)
List<Users> findEagerlyFetchedByUserAuthed(boolean isAuthenticated);
or trying to do a sub select of users first and then joining with something like this:
#Query(
"SELECT group
FROM from group
where groupid IN (SELECT u.groupId
FROM Users u
WHERE u.isAuthenticated = :isAuthenticated))
Optional<Group> findEagerlyFetchedByUserAuthed(UUID groupId, boolean isAuthenticated);
My syntax here is probably not 100% correct but i hope you got the idea.
Lastly it might be better to use
List<Group> findEagerlyFetc...
instead of
Optional<Group> findEagerlyFetc....
#EntityGraph with #Query not working properly.
Use JPA method naming query with #EntityGraph
#EntityGraph(attributePaths = {"users.posts.comments"})
Optional<Group> findByIdAndUsers_IsAuthenticated(UUID groupId, boolean isAuthenticated);
Note: To resolve ambiguity we can use _ inside your method name to manually define traversal points.
I have a simple user data class that looks like:
#Serializable
data class User(
#SerialName("_id")
val _id: Id<User> = newId(),
val email: String,
var password: String,
var tokens: Array<String> = arrayOf()
)
And I'd like the email value to be unique, i've tried a unique annotation which seemed most appropiate, but with no success.
I've also tried google and the KMongo website but I could not find an answer.
You need to create an index on the field (or combination of fields) that you want to ensure uniqueness on.
db.getCollection<User>().createIndex(User::email,
indexOptions = IndexOptions().unique(true))
I am working in a Spring JPA/Hibernate application with Kotlin and I want to find all elements in an entity.
That entity has a foreign key with a #ManyToOne relationship. I want to get all elements with their associated values with a JOIN query avoiding the N+1 problem.
One thing is that the foreign keys are not related to the primary keys, but to another unique field in the entities (UUID).
I was able to make that query with a JOIN creating a custom Query with a JOIN FETCH, but my point is to avoid creating those queries and make those JOINS in all findAlls by default.
Is that possible or do I have to make a query in JPQL manually to force the JOIN FETCH?
Here is the example code:
#Entity
data class A {
#Id
val id: Long,
#Column
val uuid: UUID,
#Column
val name: String
}
#Entity
data class B {
#Id
val id: Long,
...
#Fetch(FetchMode.JOIN)
#ManyToOne
#JoinColumn(name = "a_uuid", referencedColumnName = "uuid", insertable = false, updatable = false)
val a: A
}
#Repository
interface Repo<B> : CrudRepository<B, Long>
...
repo.findAll() // <-- This triggers N+1 queries instead of making a JOIN
...
Another option for you is using EntityGraph. It allows defining a template by grouping the related persistence fields which we want to retrieve and lets us choose the graph type at runtime.
This is an example code that is made by modifying your code.
#Entity
data class A (
#Id
val id: Long,
#Column
val uuid: UUID,
#Column
val name: String
) : Serializable
#NamedEntityGraph(
name = "b_with_all_associations",
includeAllAttributes = true
)
#Entity
data class B (
#Id
val id: Long,
#ManyToOne
#JoinColumn(name = "a_uuid", referencedColumnName = "uuid")
val a: A
)
#Repository
interface ARepo: CrudRepository<A, Long>
#Repository
interface BRepo: CrudRepository<B, Long> {
#EntityGraph(value = "b_with_all_associations", type = EntityGraph.EntityGraphType.FETCH)
override fun findAll(): List<B>
}
#Service
class Main(
private val aRepo: ARepo,
private val bRepo: BRepo
) : CommandLineRunner {
override fun run(vararg args: String?) {
(1..3L).forEach {
val a = aRepo.save(A(id = it, uuid = UUID.randomUUID(), name = "Name-$it"))
bRepo.save(B(id = it + 100, a = a))
}
println("===============================================")
println("===============================================")
println("===============================================")
println("===============================================")
bRepo.findAll()
}
}
On B entity, an entity graph named "b_with_all_associations" is defined, and it is applied to the findAll method of the repository of B entity with LOAD type.
These things will prevent your N+1 problem by fetching with join.
Here is the SQL log for the bRepo.findAll().
select
b0_.id as id1_1_0_,
a1_.id as id1_0_1_,
b0_.a_uuid as a_uuid2_1_0_,
a1_.name as name2_0_1_,
a1_.uuid as uuid3_0_1_
from
b b0_
left outer join
a a1_
on b0_.a_uuid=a1_.uuid
ps1. due to this issue, I don't recommend using many to one relationship with non-pk. It forces us to use java.io.Serializable to 'One' entity.
ps2. EntityGraph can be a good answer to your question when you want to solve the N+1 problem with Join. But I would recommend the better solution: try to solve it with Lazy loading.
ps3. It's not a good idea that using non-pk associations for Hibernate. I truly agree on this comment. I think it's a bug that is not solved yet. It breaks the lazy loading mechanism of hibernate.
As far as I know, the fetch mode only applies to EntityManager.find related queries or when doing lazy loading but never when executing HQL queries, which is what is happening behind the scenes. If you want this to be join fetched, you will have to use an entity graph, which is IMO also better as you can define it per use-site, rather than globally.
I don't know how to configure exactly what you are asking, but the following suggestion might be worth considering...
Change
#Fetch(FetchMode.JOIN)
#ManyToOne
#JoinColumn(name = "a_uuid", referencedColumnName = "uuid", insertable = false, updatable = false)
val a: A
to
#ManyToOne(fetch = javax.persistence.FetchType.LAZY)
#JoinColumn(name = "a_uuid", referencedColumnName = "uuid", insertable = false, updatable = false)
val a: A
And then on your entity A, add the annotation to the class
#BatchSize(size = 1000)
Or whatever batch-size you feel to be appropriate.
This will generally give you the results in 2 queries if you have less than 1000 results. It will load a proxy for A rather than joining to A, but then the first time that A is accessed, it will populate the proxies for BATCH_SIZE number of entities.
It reduces the number of queries from
N + 1
to
1 + round_up(N / BATCH_SIZE)
The findAll implementation will always load b first and then resolve it's dependencies checking the annotations. If you want to avoid the N+1 problem you can add the #Query annotation with JPQL query:
...
#Query("select b from TableB b left join fetch b.a")
repo.findAll()
...
I'm wondering is there anyway to optimize an api when using foreign key and ManytoMany field, for example :
Serializer :
class SerializerA(serializers.ModelSerializer):
class Meta:
model = Model_A
fields = ('id', 'official_name', 'gender')
depth = 1
class SerializerB(serializers.ModelSerializer):
user = SerializerA(many=True)
class Meta:
model = Model_B
fields = ('id', 'project_name','project_type', 'project_start_date', 'user')
depth = 1
API :
class ReportAPI(APIView):
def get(self, request):
all_projects = Model_B.objects.all()
project_serializer = SerializerB(all_projects, many=True)
return Response(project_serializer.data)
now with this, if i go to the API url, and debug this page, it''l show that I'm querying 78 times from the SQL query. But if I remove one field from manytoMany seriealizer field which the 'gender', the page now will only query from the database 21 times, so my question is again, how can I optimize this?
You can use select_related (for ForeignKey) and/or prefetch_related (For ManyToMany or ManyToOne) so that it will not hit the database for each Model_B object.
If user is a FK in Model_B then you can do:
class ReportAPI(APIView):
def get(self, request):
all_projects = Model_B.objects.all().select_related('user')
project_serializer = SerializerB(all_projects, many=True)
return Response(project_serializer.data)
If the user model has other FK that you need in it's serializer, then you can also do select_related('user', 'user__other_field').
I'm new in Grails. I have a problem with generation association many to one and one to many between two tables. I'm using postgresql database.
Employee.groovy
class Employee {
String firstName
String lastName
int hoursLimit
Contact contact
Account account
Unit unit
char isBoss
static hasMany = [positionTypes:PositionType, employeePlans: EmployeePlan]
}
EmployeePlan.groovy
class EmployeePlan {
AcademicYear academicYear
HourType hourType
int hours
float weightOfSubject
Employee employee
static belongsTo = [SubjectPlan]
}
I'd like to have access from employee to list of employeePlans and access from EmployeePlan to Employee instance. Unfortunately GORM generates only two tables Employee and EmployeePlan with employee_id. I don't have third table which should have two columns employee_id and employee_plan_id. Could you help me ?
I think your setup is correct, as you write from Employee class you can access to a collection of EmployeePlan (take care, that if you don't explicitly define EmployeePlan like a List, it will be a Set by default) and from EmployeePlan you can access Employee.
If you need List, you can define it like that:
class Employee {
String firstName
String lastName
int hoursLimit
Contact contact
Account account
Unit unit
char isBoss
//explicitly define List
List<EmployeePlan> employeePlans
static hasMany = [positionTypes:PositionType, employeePlans: EmployeePlan]
}
But back to your question. You'd like to have join table between Employee and employeePlan, but why? Its not necessary, since you have bidirectional mapping with sets (unordered), grails will not create a join table. Can you explain why do you need it? In grails the references will be auto-populated, so I don't see any issue here.
If need to preserve order of employeePlans, then define it as List, shown above, and grails will create a join table with corresponding indexes.
have you read the ref-doc? it gives you the answer immediately:
class Person {
String firstName
static hasMany = [addresses: Address]
static mapping = {
table 'people'
firstName column: 'First_Name'
addresses joinTable: [name: 'Person_Addresses',
key: 'Person_Id',
column: 'Address_Id']
}
}