I need to have a mapping like below in Spring JPA
Teacher -> Class -> Subject
Teacher has Classes and Classes should be having its Subjects.
I am developing a School Management System.
I have the below tables
MariaDB [school-mgmt-sys]> select * from subject;
+----+---------+----------------------------+----------------------------+---------+
| id | code | date_created | last_updated | name |
+----+---------+----------------------------+----------------------------+---------+
| 1 | MATHS | 2022-07-20 14:21:20.000000 | 2022-07-20 14:21:20.000000 | Maths |
| 2 | SCIENCE | 2022-07-20 14:21:34.000000 | 2022-07-20 14:21:34.000000 | Science |
| 3 | HINDI | 2022-07-20 14:21:59.000000 | 2022-07-20 14:21:59.000000 | Hindi |
| 4 | SOCIAL | 2022-07-20 14:22:10.000000 | 2022-07-20 14:22:10.000000 | Social |
| 5 | ENGLISH | 2022-07-20 14:22:16.000000 | 2022-07-20 14:22:16.000000 | English |
+----+---------+----------------------------+----------------------------+---------+
5 rows in set (0.00 sec)
MariaDB [school-mgmt-sys]> select * from class;
+----+---------+----------------------------+----------------------------+---------+
| id | code | date_created | last_updated | name |
+----+---------+----------------------------+----------------------------+---------+
| 1 | GRADE_1 | 2022-07-20 14:23:49.000000 | 2022-07-20 14:23:49.000000 | Grade 1 |
| 2 | GRADE_2 | 2022-07-20 14:23:56.000000 | 2022-07-20 14:23:56.000000 | Grade 2 |
+----+---------+----------------------------+----------------------------+---------+
2 rows in set (0.00 sec)
MariaDB [school-mgmt-sys]> select * from teacher;
+----+----------------------------+--------------+------------+-----------+----------------------------+------------+
| id | date_created | email | first_name | last_name | last_updated | phone_no |
+----+----------------------------+--------------+------------+-----------+----------------------------+------------+
| 1 | 2022-07-20 14:41:18.000000 | abdm#g.com | Abdul | Mannan | 2022-07-20 14:41:18.000000 | 9911223344 |
| 2 | 2022-07-20 14:41:39.000000 | anjgk#g.com | Anji | G Konda | 2022-07-20 14:41:39.000000 | 9911223355 |
| 3 | 2022-07-20 14:42:04.000000 | nvdShk#g.com | Naveed | Shaik | 2022-07-20 14:42:04.000000 | 9911223366 |
+----+----------------------------+--------------+------------+-----------+----------------------------+------------+
3 rows in set (0.00 sec)
I want to create a mapping like below
Teacher -> Set<Class> -> Set<Subject>
Teacher will be teaching multiple classes. And In each class he will be teaching different subjects.
Ex:
Teacher1 - Class1 -> Sub1, Sub2
- Class2 -> Sub1, Sub4
So I want to have a mapping when I fetch a teacher, it has to have all classes with respective subject he teachers. Output expected is
teacher {
"name" : "Abdul",
"classes" [
{
"id": "Class1",
"name": "Class 1"
"subjects": [
{
"id" : "SUB1",
"name" "MAths"
},
{
"id" : "SUB2",
"name" "HIndi"
}
]
},
{
"id": "Class2",
"name": "Class 2",
"subjects": [
{
"id" : "SUB1",
"name" "MAths"
},
{
"id" : "SUB4",
"name" "English"
}
]
}
]
}
I tried below.
#Entity
#Table(name = "teacher")
public class SMSTeacher {
...
#OneToMany(mappedBy = "teacher")
private Set<SMSClass> classes;
}
#Entity
#Table(name = "class")
public class SMSClass {
...
#OneToMany(cascade = CascadeType.ALL, mappedBy = "smsClass")
private Set<SMSSubject> subjects;
#ManyToOne
private SMSTeacher teacher;
}
#Entity
#Table(name = "subject")
public class SMSSubject {
...
#ManyToOne
private SMSClass smsClass;
}
It has created below table structure and I inserted data
MariaDB [school-mgmt-sys]> select * from teacher;
+----+----------------------------+--------------+------------+-----------+----------------------------+------------+
| id | date_created | email | first_name | last_name | last_updated | phone_no |
+----+----------------------------+--------------+------------+-----------+----------------------------+------------+
| 1 | 2022-07-20 17:40:57.000000 | nvdshk#g.com | Navid | Shaik | 2022-07-20 17:40:57.000000 | 9911223366 |
| 2 | 2022-07-20 17:41:21.000000 | abdMn#g.com | Abdul | Mannna | 2022-07-20 17:41:21.000000 | 9911223355 |
| 4 | 2022-07-20 17:41:50.000000 | anjgkn#g.com | Anji | GK | 2022-07-20 17:41:50.000000 | 9911223377 |
+----+----------------------------+--------------+------------+-----------+----------------------------+------------+
3 rows in set (0.01 sec)
MariaDB [school-mgmt-sys]> select * from class;
+----+---------+----------------------------+----------------------------+---------+------------+
| id | code | date_created | last_updated | name | teacher_id |
+----+---------+----------------------------+----------------------------+---------+------------+
| 1 | GRADE_1 | 2022-07-20 17:42:46.000000 | 2022-07-20 17:42:46.000000 | Grade 1 | 1 |
| 2 | GRADE_1 | 2022-07-20 17:43:28.000000 | 2022-07-20 17:43:28.000000 | Grade 1 | 2 |
| 3 | GRADE_2 | 2022-07-20 17:44:20.000000 | 2022-07-20 17:44:20.000000 | Grade 2 | 2 |
| 4 | GRADE_2 | 2022-07-20 17:49:21.000000 | 2022-07-20 17:49:21.000000 | Grade 2 | 2 |
+----+---------+----------------------------+----------------------------+---------+------------+
4 rows in set (0.01 sec)
MariaDB [school-mgmt-sys]> select * from subject;
+----+------+----------------------------+----------------------------+---------+--------------+
| id | code | date_created | last_updated | name | sms_class_id |
+----+------+----------------------------+----------------------------+---------+--------------+
| 1 | ENG | 2022-07-20 17:46:32.000000 | 2022-07-20 17:46:32.000000 | English | 3 |
| 2 | HIN | 2022-07-20 17:46:40.000000 | 2022-07-20 17:46:40.000000 | Hindi | 4 |
| 3 | MATH | 2022-07-20 17:46:52.000000 | 2022-07-20 17:46:52.000000 | Maths | 2 |
| 4 | MATH | 2022-07-20 17:46:59.000000 | 2022-07-20 17:46:59.000000 | Maths | 1 |
+----+------+----------------------------+----------------------------+---------+--------------+
4 rows in set (0.01 sec)
to fetch the above data, am using Spring Data Rest API when I fetch teacher it will have a link to navigate to class and from there i can go to subject.
But not able to do what am looking for.
Please help.
I tried a couple of solutions. Finally I found the below solution.
I have created one to many mappings between the entities with join tables, to maintain unique data in tables.
Entities:
#Entity
#Table(name = "teacher")
public class SMSTeacher {
...
#OneToMany
#JoinTable(name = "teacher_class", joinColumns = #JoinColumn(name = "teacher_id"), inverseJoinColumns = #JoinColumn(name = "class_id"))
private Set<SMSClass> classes = new HashSet<>();
}
#Entity
#Table(name = "class")
public class SMSClass {
...
#OneToMany(cascade = CascadeType.ALL)
#JoinTable(name = "class_subject", joinColumns = #JoinColumn(name = "class_id"), inverseJoinColumns = #JoinColumn(name = "subject_id"))
private Set<SMSSubject> subjects = new HashSet<>();;
}
#Entity
#Table(name = "subject")
public class SMSSubject {
...
//No mapping here, Configured only uni directional mapping.
}
A third table to maintain teacher-> class -> subject relation ship data.
+----+----------+------------+------------+
| id | class_id | subject_id | teacher_id |
+----+----------+------------+------------+
| 1 | 1 | 3 | 1 |
| 2 | 1 | 5 | 1 |
| 3 | 2 | 2 | 1 |
| 4 | 2 | 4 | 1 |
| 5 | 1 | 3 | 2 |
| 6 | 2 | 3 | 3 |
+----+----------+------------+------------+
Created a JPA native SQL to fetch the result as required.
public interface TeacherClassSubjectRepo extends JpaRepository<TeacherClassSubject, Long> {
#Query(value = "select t.id T_ID, t.first_name fName, t.last_name lName, t.phone_no phone, t.email email, c.id C_ID, c.name C_NAME, c.code C_CODE, s.id S_ID, s.name S_NAME, s.code S_CODE from teacher t, class c,subject s, teacher_class_subject tcs where tcs.teacher_id=t.id and tcs.class_id = c.id and tcs.subject_id = s.id and t.id=?1 order by t.id, c.id", nativeQuery = true)
List<Tuple> findAllByTeacherId(Long teacherId);
}
Here is my service Impl method to generate the output as required.
#Override
public ResponseEntity<Object> getTeacher(Long teacherId) {
List<Tuple> teacherClassSubjects = teacherClassSubjectRepo
.findAllByTeacherId(teacherId);
if (teacherClassSubjects.size() == 0)
throw new ResponseStatusException(HttpStatus.NOT_FOUND);
SMSTeacherDTO smsTeacherDTO = null;
String fName = "", lName = "", email = "", currCName = "", currCCode = "";
BigInteger phone = null;
Set<SMSSubjectDTO> subjectDTOs = new HashSet<>();
Set<SMSClassDTO> classDTOs = new HashSet<>();
for (Tuple tuple : teacherClassSubjects) {
...
//Logic to fetch SMSTeacherDTO fields and Logic to construct classDTOs
}
smsTeacherDTO = new SMSTeacherDTO(fName, lName, email, phone, classDTOs);
return new ResponseEntity<>(smsTeacherDTO, HttpStatus.OK);
}
Output:
{
"firstName": "Anji",
"lastName": "GK",
"email": "anjgkn#g.com",
"phoneNo": 9911223311,
"classes": [
{
"name": "Grade 2",
"code": "GRADE_2",
"subjects": [
{
"name": "Science",
"code": "SCIENCE"
},
{
"name": "Social",
"code": "SOCIAL"
}
]
},
{
"name": "Grade 1",
"code": "GRADE_1",
"subjects": [
{
"name": "Hindi",
"code": "HINDI"
},
{
"name": "English",
"code": "ENGLISH"
}
]
}
]
}
Please comment if this solution is good or if there is any better solution available for this problem.
Thanks.
I have the following tables where 1 page has many containables, and each containable has either lines or questions:
// Page model
class Page extends Model
{
protected $fillable = ['title'];
public function containables() {
return $this->morphMany(Containable::class, 'containable');
}
}
Example pages table data:
| id | title |
|----|--------|
| 1 | Page 1 |
// Containable model
class Containable extends Model
{
protected $table = 'containables';
protected $fillable = ['containable_id', 'containable_type'];
public function containable() {
return $this->morphTo('containable');
}
public function sequences() {
return $this->hasMany(ContainableSequence::class, 'containable_id', 'id');
}
Example containables table data:
| id | containable_id | containable_type |
|----|----------------|------------------|
| 2 | 1 | App\Page |
| 3 | 1 | App\Page |
// ContainableSequence model
class ContainableSequence extends Model
{
protected $table = 'containable_sequence';
protected $fillable = ['containable_id', 'sequencable_id',
'sequencable_type', 'order'];
public function sequencable() {
return $this->morphTo('sequencable');
}
}
Example containable_sequence table data:
| id | containable_id | sequencable_type | sequencable_id | order |
|----|----------------|------------------|----------------|-------|
| 4 | 2 | App\Line | 6 | 1 |
| 5 | 2 | App\Question | 8 | 2 |
| 6 | 3 | App\Question | 9 | 1 |
| 7 | 3 | App\Line | 7 | 2 |
// Line model
class Line extends Model
{
protected $table = 'lines';
protected $fillable = ['name'];
}
Example Lines table data:
| id | name |
|----|--------|
| 6 | line 1 |
| 7 | line 2 |
// Question model
class Question extends Model
{
protected $table = 'questions';
protected $fillable = ['name', 'marks'];
}
Example Questions table data:
| id | name | marks |
|----|------------|-------|
| 8 | Question 1 | 10 |
| 9 | Question 2 | 15 |
I am trying to perform a join the tables with polymorphic associations to get the count of line(s) and question(s) linked to a page.
Starting from a page, how can I get the counts of related lines and questions using eloquent? Thanks
I have implemented a tagging system in my application, using Postgres 9.6. There are three tables.
Projects
Table "public.project"
Column | Type | Collation | Nullable | Default
-------------+-----------------------------+-----------+----------+---------------------------------
id | integer | | not null | nextval('tag_id_seq'::regclass)
name | character varying(255) | | not null |
user_id | integer | | |
Tags
Table "public.tag"
Column | Type | Collation | Nullable | Default
-------------+-----------------------------+-----------+----------+---------------------------------
id | integer | | not null | nextval('tag_id_seq'::regclass)
tag | character varying(255) | | not null |
user_id | integer | | |
is_internal | boolean | | not null | false
Project tags
Column | Type | Collation | Nullable | Default
------------------+-----------------------------+-----------+----------+-----------------------------------------
id | integer | | not null | nextval('project_tag_id_seq'::regclass)
tag_id | integer | | not null |
project_id | integer | | | |
user_id | integer | | not null |
Now I want to get a list of all the projects, annotated with a column that indicates (for a particular tag) whether it has that tag.
So I'd like the results to look like this:
id name has_favorite_tag
1 foo true
2 bar false
3 baz false
This is my query so far:
select project.*, CASE(XXXX) as has_project_tag
from project p
join (select * from project_tag where tag_id=1) pt on p.id=pt.project_id
I know that I want to use CASE to be true when the length of project_tag matches is greater than 0 - but how do I do this?
(In reality the project table has many more fields, of course.)
Here's a possibility (unfiltered for tag_id; add to inner select if necessary):
select project.*, exists(select * from project_tag where id=project.id) as has_project_tag from project;
I'm new to JPA and I am having some difficulty creating some entities.
In the application I am building it is possible to classify some entities according to some area and subarea defined in a database.
The relevant tables are these four ones:
1) classification
+-------------+-----------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+-----------------------+------+-----+---------+----------------+
| ID | int(11) unsigned | NO | PRI | NULL | auto_increment |
| pID | int(11) unsigned | NO | MUL | NULL | |
| reference | varchar(300) | NO | | NULL | |
| link | varchar(255) | YES | | NULL | |
+-------------+-----------------------+------+-----+---------+----------------+
2) cls_area_map
+---------+------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+---------+------------------+------+-----+---------+----------------+
| class | int(11) unsigned | NO | MUL | NULL | |
| idarea | int(11) unsigned | NO | MUL | NULL | |
| subarea | int(11) unsigned | YES | MUL | NULL | |
| id | int(11) unsigned | NO | PRI | NULL | auto_increment |
+---------+------------------+------+-----+---------+----------------+
3) area
+--------+------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+--------+------------------+------+-----+---------+----------------+
| idarea | int(11) unsigned | NO | PRI | NULL | auto_increment |
| label | varchar(255) | NO | UNI | NULL | |
+--------+------------------+------+-----+---------+----------------+
4) subarea
+-------------+------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+------------------+------+-----+---------+----------------+
| area_idarea | int(11) unsigned | NO | MUL | NULL | |
| label | varchar(255) | NO | UNI | NULL | |
| ID | int(11) unsigned | NO | PRI | NULL | auto_increment |
+-------------+------------------+------+-----+---------+----------------+
In classification I store general classification information, and in cls_area_map I try to connect the general information to the areas of classification (defined in area and subarea).
When I try to add the classification-area mapping information to my Classification and my Cls_area_map entity classes I run into trouble.
I get the error:
An incompatible mapping has been encountered between [class Entity.Classification] and [class Entity.Cls_area_map]. This usually occurs when the cardinality of a mapping does not correspond with the cardinality of its backpointer..
I'm not sure what I'm doing wrong about the cardinality. This is what I added to Classification to create the relationship:
#OneToMany(mappedBy = "id")
private List<Cls_area_map> cls_area;
and in Cls_area_map:
#JoinColumn(name = "class",referencedColumnName = "ID")
#ManyToOne(optional=false)
private Classification classy;
Any explanations/hints?
(and what is meant by backpointer?)
mappedBy indicates that the entity in this side is the inverse of the relationship. So entity name should be used instead of foreign key.
The doc says
mappedBy refers to the property name of the association on the owner
side.
It is classy in your case, so use
#OneToMany(mappedBy = "classy")
private List<Cls_area_map> cls_area;
See also:
mappedBy
I am creating a form which uses an Entity type.
The entity form type displays Roles as a list of checkboxes.
But I dont know how to set default values. I need to get default values from DB then dynamically check some of those options.
According to the documentation, It seems like 'preferred_choices' option won't do this job.
Can anyone please help me out there?
Sorry about my English if some sentences don't make sense.
3 Tables:
UserRole
+---------+---------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+---------+---------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| user_id | int(11) | YES | MUL | NULL | |
| role_id | int(11) | YES | MUL | NULL | |
+---------+---------+------+-----+---------+----------------+
AdminUser
+-----------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-----------+-------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| username | varchar(25) | NO | UNI | NULL | |
| salt | varchar(32) | NO | | NULL | |
| password | varchar(40) | NO | | NULL | |
| email | varchar(60) | NO | UNI | NULL | |
| is_active | tinyint(1) | NO | | NULL | |
+-----------+-------------+------+-----+---------+----------------+
Role
+-------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| name | varchar(50) | NO | UNI | NULL | |
+-------+-------------+------+-----+---------+----------------+
Form builder:
$builder->add('role', 'entity', array(
'class' => 'AcmeAdminBundle:Role',
'property' => 'name',
'multiple' => TRUE,
'expanded' => TRUE,
));
You must have defined a ManyToMany relation between User and Role, with some traditional methods on User entity : setRoles, getRoles, addRole...
Data that will be loaded in your form are data from a User instance, for example $user.
$user = new User; // or $user is existing User, same logic
$rolesYouWantToSetToUser = array('ROLE_1', 'ROLE_2', 'ROLE_3');
foreach ($rolesYouWantToSetToUser as $roleId) {
// $em must previsouly be set as EntityManager in your code
$role = $em->getReference('YourBundle:Role', $roleId);
$user->addRole($role);
}
// From a controller
$form = $this->createFormBuilder($user)
->add('roles', 'entity', array(
'class' => 'AcmeAdminBundle:Role',
'multiple' => true,
'expanded' => true,
'property' => 'name',
))
->getForm();