I am building Demo application in which we have three tables and i am using spring-boot data Jpa with mysql. I have following requirement
Table1 name->Student
sid sname srole //columns name
Table2 name->Courses
cid cname ctime //columns name
Table3 name-> Tutioncenter
id Name sid(student table sid) cid(Courses table cid)
#Entity
public class Student{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long sid;
private String sname;
private String srole;
//getter and setter
}
#Entity
public class Courses{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long cid;
private String cname;
private String ctimme;
//getter and setter
}
#Entity
public class Tutioncenter{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "sid", nullable = false)
#OnDelete(action = OnDeleteAction.CASCADE)
#JsonIgnore
private Student student;
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "cid", nullable = false)
#OnDelete(action = OnDeleteAction.CASCADE)
#JsonIgnore
private Courses courses;
//getter ans setter
}
#Repository
public interface CoursesRepository extends JpaRepository<Courses,Long>{}
#Repository
public interface StudentRepository extends JpaRepostiroy<Student,Long>{}
#Repository
public interface TutionRepository extends JpaRepository<Tutioncenter,Long>{
Page<SecretManager> findBySIdAndCId(Long sId, Pageable pageable);
Optional<SecretManager> findBySecretIdAndSIdAndCId(Long sid, Long cid);
}
Now I can design controller for Student and Courses but how should i design controller of Tutioncenter for crud operations??
The create, read, update operations of both student and courses table should be no relationship with tutioncenter table. Then we only need to consider how to deal with delete operation. Considering data in tutioncenter table, there would be 2 opotions:
Delete all related data in tutioncenter table at the same time.
Do not delete related data, or do not delete imediaterly.
The following are the SQL of delete both table together:
delete c, t from `courses` c join `tutioncenter` t on c.cid = t.cid where id = 1;
For tutioncenter table, I think there may be no update operation according to your business. Therefore we only need to focus on create, read, delete operations.
read operation is as following:
select s.sname, c.cname, c.ctime from `tutioncenter` t join `student` s on s.sid = t.sid join `courses` c on c.cid = t.cid;
delete operation is as following:
delete t from `tutioncenter` t join `student` s on s.sid = t.sid join `courses` c on c.cid = t.cid where t.sid = 1 and t.cid = 2;
insert operation as following:
insert into `tutioncenter` (name, sid, cid)
select 'test3', sid, cid from `student` s, `courses` c where s.sid = 1 and c.cid = 1;
Back to how to design your controller for TutionRepository, I think that should base on how to design your business.
For example, If this demo system is designed only for student and administrator, then you need to consider both situations.
The following are what I could give till now how a student use your system:
Log in. With that said, all operation will have a sid now. Query student.
List all chozen courses. Query all courses have been choozen by this student.
If add new course, get all valid courses, then add chozen course. Query all courses operation. Add courses for this student.
Or delete chozen courses. Delete chozen courses.
To sum up, choosing courses system for student need the following interface:
Query student by name or id. url may be GET /students/<sid> or GET /students/<sname>. If you have login interface, you could use it instead.
Query all valid courses have been choozen by this student's id. GET /students/<sid>/courses
Query all valid courses. GET /courses
Add courses for this student (courses ids and student id). POST /students/<sid>/courses body is cid list like [cid1, cid2]
Delete courses for this student (courses ids and student id). DELETE /students/<sid>/courses/<cid>
The same to analysis what interfaces needed if you want to add a administrator role, who can manage courses and students.
Anyway, all should begin from business requirements.
Related
I got following tables. Lets ignore the fact that the relation is done wrong here. I cannot change that.
Each company can have multiple employes and each employe belongs to only one company.
Table: Company
ID
EMPLOYE_ID
10
100
Table: Employe
ID
NAME
100 (Same as EMPLOYE_ID)
John
Now i want to create a relation #OneToMany between Company -> Employe . My entities look as follow
class Company {
#Id
#Column(name = "id", unique = true, nullable = false)
private String id;
#OneToMany(fetch = FetchType.LAZY)
#JoinColumn(name = "EMPLOYE_ID", referencedColumnName = "ID")
private Set<Employe> employees;
}
No matter if i try to create a uniderectional, or biderection relationship by adding also #ManyToOne on my Employe class, when using Criteria api to select all Company entities and their Employes i always end up with a wrong generated SQL query at the point where it joines the tables. The above relation for example creates following:
FROM company company0
INNER JOIN employe employe0 ON company0.id = employe0.employe_id
I tried several approaches, but i end up almost with the same error. It tries either to access a column which does not exist on the table, or joins wrong columns (e.g. id = id). Or by the following exception
Caused by: org.hibernate.MappingException: Repeated column in mapping
for entity: com.Employe column: id (should be mapped with
insert="false" update="false")"}}
What is a simple approach to create a bidrectional relation with the above table structure?
Note: I finally ended up changing the DB schema. Still, it would be interesting if someone could provide an answer for such a case, even if it is based on a not well formed
The central problem is that the described table structures do not allow a 1:n relationship from Company to Employee. According to the table design (especially the design of PKs) above, a company can only have one employee.
However, if the DB design cannot be changed, the following approach using the JoinColumnOrFormula annotation may lead to partial success.
The #JoinColumnOrFormula annotation is used to customize the join between a child Foreign Key and a parent row Primary Key when we need to take into consideration a column value as well as a #JoinFormula.
See https://docs.jboss.org/hibernate/stable/orm/userguide/html_single/Hibernate_User_Guide.html#associations-JoinColumnOrFormula for details.
More concretely with these Entities
#Entity
#Table(name="t_company")
public class Company {
#Id
#Column(name="id")
private Integer id;
#Column(name="employee_id")
private Integer employeeId;
#OneToMany(mappedBy = "company")
private List<Employee> employees;
// ..
}
#Entity
#Table(name = "t_employee")
public class Employee {
#Id
#Column(name = "id")
private Integer id;
#Column(name = "name")
private String name;
#ManyToOne
#JoinColumnOrFormula( column =
#JoinColumn(
name = "id",
referencedColumnName = "employee_id",
insertable = false,
updatable = false
)
)
private Company company;
// ..
}
and this custom repository
#Repository
public class EmployeeRepository {
#Autowired
EntityManager entityManager;
List<Employee> findAll() {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Employee> cq = cb.createQuery(Employee.class);
Root<Employee> root = cq.from(Employee.class);
Join<Employee, Company> joinCompany = root.join("company");
TypedQuery<Employee> query = entityManager.createQuery(cq);
return query.getResultList();
}
}
you get the following query:
select
employee0_.id as id1_1_,
employee0_.name as name2_1_
from t_employee employee0_
inner join t_company company1_ on employee0_.id=company1_.employee
My application under Spring Boot v1.5.7
I have 3 entities (schematically):
#Entity
public class Word {
#Id
#GeneratedValue
private Integer id
...
}
#Entity
public class UserWordList {
#Id
#GeneratedValue
private Integer id
#ManyToOne
#JoinColumn(name = "user_id")
private User user;
#ManyToOne
#JoinColumn(name = "word_id")
private Word word;
}
#Entity
public class UserAnotherWordList {
#Id
#GeneratedValue
private Integer id
#ManyToOne
#JoinColumn(name = "user_id")
private User user;
#ManyToOne
#JoinColumn(name = "word_id")
private Word word;
}
And now I need to select all Words for User, but exclude Words placed in user's lists
Native SQL for user_id=1 is
select *
from Word w
left join UserWordList uwl
on w.id = uwl.word_id and uwl.user_id = 1
left join UserAnotherWordList uawl
on w.id = uawl.word_id and uawl.user_id = 1
where uwl.word_id is NULL
and uawl.word_id is NULL
What is a best way to do it? Ideally I would like to use Spring Data features or HQL, but I don't understand how...
UPD
I solve my problem with native query:
#Entity
#NamedNativeQuery(
name = "User.getWordsToProcess",
resultClass = Word.class,
query = "<...native query to select Words...>"
)
public class User {...}
...
public interface UserRepository extends CrudRepository<User, Integer> {
List<Word> getWordsToProcess(Integer userId);
}
Fastest answer is Criteria api (but that is deprecated in hibernate 5.2 and above.)
So you can use Hql :
getSession().createQuery(" select * from UserWordList u left join fetch u.word
left join fetch u.user").list()
And you can use union or create another query to fetch UserAnotherWordList.
Also you can set any restrictions in Hql like below:
Query query = getSession().createQuery(" select * from UserWordList u left join fetch u.word left join fetch u.user us where us.user = :sample").list();
query.setParameter("sample",value);
query.list();
I'm writing a service with JPA and Postgres db. I have a class named Student:
public class Student {
#id
private String id;
private String firstName;
private String lastName;
#OneToMany(targetEntity = Phone.class, fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "student_id", referencedColumnName = "id")
private Set<Phone> phones;
// Setter and Getter
}
And a Phone class:
public class Phone {
#id
private String id;
private String number;
// Setter and Getter
}
Now there will be two tables in my db, following are columns in them:
student: id, first_name, last_name
phone: id, number, student_id (generated by #JoinColumn)
Now every time when I query the student, JPA will join the phone table into student table and the student result contains the phone info of this student. This is exactly what I want.
But now I met a problem. When I query the list of the students, the phone info is not useful in this case, only the id, firstName and lastName are necessary. But JPA does the same "join" operation for me too. I know this consumes lots of time. In such case how could I just return the info in the student table? Without joining the phone table?
I tried something in the repository like
#Query(SELECT s.id, s.firstName, s.lastName FROM student s)
public List<Student> findAllStudentWithoutPhone();
but it returns me the list of the values, but not converted into a Student object. How could I achieve this feature?
In the one-to-many mapping (refer below) you have set the fetch type to be lazy fetch = FetchType.LAZY hence hibernate won't fetch set of phones corresponding to the student until you access the set of phones via getter method, so no need to worry about it.
#OneToMany(targetEntity = Phone.class, fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "student_id", referencedColumnName = "id")
private Set<Phone>;
However in case you need to verify whether things are working fine or not you can set the property show_sql to true and check the sql generated via hibernate.
fetch = FetchType.LAZY provides you possibility don't query Phone table until of the first call from the code. Please see: http://docs.oracle.com/javaee/7/api/javax/persistence/FetchType.html#LAZY
In case if you want to retrieve the list of the students without phones query should be:
#Query(SELECT * FROM student s where phones IS NULL)
For automatically converting results into Student object please don't use s.id, s.firstName, s.lastName in your query.
Answer given by #Sahil is absolutely correct but to add that.
#Cong
You do not need to add FetchType.LAZY as by default its already LAZY.
& as a note the Student class property for phone missing variable name.
I have a Person class that has a collection of Contacts. Everything works ok, I get the list of persons with their contacts. However, in log I see that a separate query is made to read collection of every person. That is too bad.
How to make hibernate make a join to read all the data in one query? I use JPA.
This is the person class:
#Entity
#Table(name = "tbl1")
public class PersonItem implements Serializable{
#Id
#Column(name="col1")
private String guid;
.....
#ElementCollection(targetClass = ContactItem.class,fetch=FetchType.EAGER)
#CollectionTable(name="tbl2",joinColumns=#JoinColumn(name="col2"))
private List<ContactItem> contacts;
....
}
This is the contact class
#Embeddable
#Table(name = "tbl2")
public class ContactItem implements Serializable {
#Column(name="col1")
private String guid;
#Column(name="col3")
private String info;
}
This is the way I get the list of persons:
Query query = em.createQuery("Select p from PersonItem p WHERE p.guid IN (:guids)");
query.setParameter("guids", guids);
List<PersonItem> list=query.getResultList();
And this what I see in log (I have three persons in DB):
Hibernate: select personitem0_.col1 as col1_0_, personitem0_.col4 as col2_0_, personitem0_.col2 as col3_0_, personitem0_.col3 as col4_0_ from tbl1 personitem0_ where personitem0_.col1 in (? , ? , ?)
Hibernate: select contacts0_.col2 as col1_1_0_, contacts0_.col1 as col2_1_0_, contacts0_.col3 as col3_1_0_ from tbl2 contacts0_ where contacts0_.col2=?
Hibernate: select contacts0_.col2 as col1_1_0_, contacts0_.col1 as col2_1_0_, contacts0_.col3 as col3_1_0_ from tbl2 contacts0_ where contacts0_.col2=?
Hibernate: select contacts0_.col2 as col1_1_0_, contacts0_.col1 as col2_1_0_, contacts0_.col3 as col3_1_0_ from tbl2 contacts0_ where contacts0_.col2=?
Please, begin from a more simple mapping. Use plural names, and column prefixes.
#Entity
#Table(name = "persons")
public class Person {
#Id
#Column(name = "f_guid")
private String guid;
#OneToMany(mappedBy = "person", fetch = FetchType.EAGER)
private List<Contact> contacts;
}
#Entity
#Table(name = "contacts")
public class Contact {
#Id
#Column(name = "f_guid")
private String guid;
#Column(name = "f_info")
private String info;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "fk_person")
private Person person;
}
Person is associated to contacts by a foreign key fk_person in the contacts table.
Update
Looks like JPQL overrides a default fetching strategy. You need to specify a fetch explicitly
select p from PersonItem p left join fetch p.contacts WHERE p.guid IN (:guids)
If you have duplicates, cause of joins, you can use distinct
select distinct p from PersonItem p left join fetch p.contacts WHERE p.guid IN (:guids)
Try #Fetch on your relation.
Also i would suggest to use #OneToMany relation int this case
#OneToMany(mappedBy = "person", fetch = FetchType.EAGER)
#Fetch(FetchMode.JOIN) //You can use SUBSELECT as well
private List<ContactItem> contacts;
You can read more about fetching strategies here
fetch-“join” = Disable the lazy loading, always load all the collections and entities.
fetch-“select” (default) = Lazy load all the collections and entities.
batch-size=”N” = Fetching up to ‘N’ collections or entities, Not record.
fetch-“subselect” = Group its collection into a sub select statement.
I have three tables with entities in hibernate. DB - MySQL. I need to get fields from entity "Item" where ModelsMm.id has some value. At first I tried to do separate queries, it was huge amount of requests in sum. So, i tried to do complex query, but it became a very long run.
I think there is a simpler way, but I do not know what.
My query and entities.
List<Item> itemIds = session.createQuery("select it from Item it where :id in elements(it.mmPrice.modelsMm)");
#Entity (name = "MODELS_MM")
public class ModelsMm {
#Id
private int Id;
#ManyToMany(cascade=CascadeType.ALL, fetch = FetchType.LAZY)
#JoinTable(name="parth_mm", joinColumns={#JoinColumn(name="MODEL_ID", referencedColumnName="ID")}, inverseJoinColumns={#JoinColumn(name="PART_ID", referencedColumnName="ID")})
private List<MmPrice> mmPrices;
#Entity (name = "MM_PRICE")
public class MmPrice {
#Id
private int id;
private String article;
#OneToOne(optional = true, fetch = FetchType.LAZY)
#JoinColumn(name = "article", referencedColumnName = "article",insertable = false, updatable = false)
private Item item;
#ManyToMany
#JoinTable(name="parth_mm", joinColumns={#JoinColumn(name="PART_ID", referencedColumnName="ID")}, inverseJoinColumns={#JoinColumn(name="MODEL_ID", referencedColumnName="ID")})
private List<ModelsMm> modelsMm;
#Entity
#Table(name="SHOP_ITEMS")
public class Item implements Serializable {
#Id
private int id;
private String article;
#OneToOne(optional = true, fetch = FetchType.LAZY)
#JoinColumn(name = "article", referencedColumnName = "article",insertable = false, updatable = false)
private MmPrice mmPrice;
In console i have that query
Hibernate: select item0_.ID as ID0_, item0_.ARTICLE as ARTICLE0_, item0_.article as article0_ from SHOP_ITEMS item0_ cross join MM_PRICE mmprice1_ where item0_.article=mmprice1_.article and (? in (select modelsmm2_.MODEL_ID from parth_mm modelsmm2_ where mmprice1_.ID=modelsmm2_.PART_ID))
Thanks.
First, you'll have to fix your mapping. In a bidirectional association, one side MUST be the inverse side, and thus use the mappedBy attribute. For example, if you choose ModelsMm to be the inverse side, then its mmPrices attribute should be declared as
#ManyToMany(mappedBy = "modelsMm")
private List<MmPrice> mmPrices;
You should also forget about CascadeType.ALL on ManyToMany associations: it makes no sense. You don't want to delete all the courses of a student when you delete a student, since the course is also followed by several other students.
Now, regarding your query, it's not very clear what you want to do. If you want to select all the items which have a price which have at least one model whose ID is in a collection of IDs, then you simply need the following query:
select distinct i from Item i
join i.mmPrice p
join p.modelsMm m
where m.id in :modelIds
Side note: please fix your naming. This inconsistent and unnecessary usage of mm as a prefix or suffix makes the code unreadable. Name your class Price, the fields of type Price price, and the collections of prices prices. Just as you would do in English: an Item has a price, and a price has models.