As part of the training project, I create a database of passenger rail traffic. A user registers, buys a train ticket. The train goes on a route with several stations.
Help, please, deal with relationships.
There are several entities: user, ticket, train, station, route.
I create each entity as a:
#Entity
#Table(name = "...")
public class ... implements Serializable { ... }
User:
id (primary key),
email,
password,
firstName,
lastName,
birthDate,
role (user/admin for example).
With this, I understand everything. Further questions arise. Ticket:
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column("TICKET_ID")
private int id;
// One user can have multiple tickets?
#ManyToOne
#JoinColumn(name = "USER_ID")
private User user;
#Column(name = "SALE_TIME")
private Date saleTime;
// One train can have multiple tickets?
#ManyToOne
#JoinColumn(name = "TRAIN_ID")
private Train train;
#Column(name = "CARRIAGE")
private int carriage;
#Column(name = "PLACE")
private int place;
Is everything right here? Next...
#Entity
#Table(name = "TRAINS")
public class Train implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "TRAIN_ID")
private int id;
#Column(name = "TRAIN_NUMBER")
private int number;
#Column(name = "SEATS")
private int seats;
// on the way for example
#Column(name = "STATUS")
private String status;
How to set a train a fixed number of seats and how to reduce them when buying a ticket?
With the Station is not difficult: id, name, status.
And finally, Route:
#Entity
#Table(name = "ROUTE_POINT")
public class RoutePoint implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ROUTE_ID")
private int id;
// ?????????
#ManyToOne
#JoinColumn(name = "TRAIN_NUMBER")
private Train train;
// ?????????
#ManyToOne
#JoinColumn(name = "STATION_NAME")
private Station station;
#Column(name = "DATE_ARRIVAL")
private Date dateArrival;
#Column(name = "DATE_DEPARTURE")
private Date dateDeparture;
Please help me deal with annotations and relationships, and most importantly, understand this. Google is already purple, the best understanding comes through practice. Thank.
First of all - don't use primitive types for iDs. Use Integer or better Long.
According to the Hibernate documentation:
We recommend that you declare consistently-named identifier properties
on persistent classes and that you use a nullable (i.e.,
non-primitive) type.
About seats. There is two ways. First way:
- You don't need to reduce seats in trains. It can be fixed. Just when your customer trying to buy tickets, your application doing query to database for all tickets on that train. And if number of existing tickets are more or equals than train capacity customer just can't buy ticket. But that way is good, when all your seats are equals (don't have numbers, like in bus, for example). But if your seats are not equal (train with different type of seats) you go with second way:
- You must have another entity SEATS, which has id, number, price etc., etc. and has ManyToOne relationship with TRAIN. In that time TRAIN must have next field
#OneToMany(mappedBy = "train", cascade = CascadeType.ALL)
private Set<Seat> seats;
pros:
- If you properly set getters and setters (and it's not just
public setSeats (Set<Seat> seats) {
this.seats = seats
}
, you must google for right setters, cuz if you setters works only by eager fetch - it's not right setters), you'll have advantage of cascading (for example - when you delete train from DB, all seats will be deleted automatically)
cons:
- bidirectional relationship can be the pain in the performance when you use JPQL (N+1 and so on, and you feel it when your table will be more than 1000 rows). So there is third way:
You still have SEAT entity. Train can know about seats anything, but in Seat you have field Train like this
#OneToOne
public Train getTrain() {
return train;
}
public void setTrain(Train train) {
this.train = train;
}
cons:
- When you want to delete train, you should at first delete all seats from that train
pros:
- It's much faster than bidirectional relation for JPQL
Related
I have a user that can have a wallet. Now, when user create a wallet I want to give him a option to create a transaction on that wallet. So, on that form I would like to have next fields, so prompt user to insert:
Amount of transaction, Date, note of transaction, category of transaction
So far I have this in Spring:
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "transaction_id")
private Integer id;
private double amount;
private String note;
#DateTimeFormat(pattern = "dd-MM-yyyy")
#Column(name = "date")
private Date date;
But I have problem with field category. I want to prompt user to pick from dropdown menu one of category from the list. But how to create a field categories that will be filled with names of categories?
I tried with:
#Column(name = "categories")
private List categories;
But I'm getting:
Could not determine type for: java.util.List, at table: transaction, for columns: [org.hibernate.mapping.Column(categories)]
Spring JPA lets you handle this a couple of different ways. Since you didn't specify what type of thing a category is, I am assuming you want it to be a String.
#ElementCollection
The first, easiest, and generally recommended way is to use the #ElementCollection. If your categories are fairly well fixed, you can even use enums for this.
#ElementCollection
private List<String> categories=new ArrayList<>();
With this, JPA will generate a second table in the database called transaction_categories You don't have to create an Entity for this table or anything. Everything is handled under the covers.
JsonB
If you are using postgres 10+ (I think) you can make a column into a JSON object. You will need the following dependency in your gradle.
implementation 'com.vladmihalcea:hibernate-types-52:2.15.1'
And you will need to change your model thus:
#TypeDefs({
#TypeDef(name = "json", typeClass = JsonType.class)
})
#Entity
public class Transaction {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "transaction_id")
private Integer id;
private double amount;
private String note;
#DateTimeFormat(pattern = "dd-MM-yyyy")
#Column(name = "date")
private Date date;
#Type(type = "json")
#Column(columnDefinition = "jsonb")
private List<String> categories=new ArrayList<>();
}
So long as this list does not become gigantic, it is a pretty decent solution.
I have created an Employee entity and a corresponding ProfilePicture entity:
#Entity
#Table(name = "employee")
public class Employee{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "first_name", nullable = false)
private String firstName;
#Column(name = "last_name", nullable = false)
private String lastName;
}
#Entity
#Table(name = "profile_picture")
public class ProfilePicture {
#Id
private Long id;
#OneToOne
#MapsId
private Employee employee;
#Column(name = "image")
private byte[] image;
}
Now, I want to add a default image to my profile_picture table. I am using a MySQL database and introducing a default image seems to be a problem, since my profile_picture database entries require an association with an employee. The issue is that MapsId is trying to map the id of the associated employee to the id of my profile-picture. When there is no associated employee, this is impossible.
Does anyone have an idea how to solve this problem? I know I could create a dummy employee, but I don't want to do this. I would like to have an entry in my profile_picture table for which the employee_id column is simply null. Right now, however, this is violating some constraints. I know I will have to think of a different solution, I am just asking for ideas.
I would like to map an simple excel table to my javaEE application using hibernate. I'm really new to Databases and ORM so i would like to know if the following relations make sense and in how many Entities would make sense to split the Table.
This is the attributes contained in the Excel spreadsheet:
(Office Room Number|ComputerName|ComputerIP|Computer OS|UserFirstName|UserLastName)
Relations:
OfficeRoomNumber -- 1 : N -- Users
N users working in 1 Office?
OfficeRoomNumber -- 1 : N -- Computer
N Computers are in 1 Office ?
User -- 1:1 -- Computer
1 User got 1 Computer?
Thanks for any help and sorry for my horrible English.
Here are my 50c for modelling your domain. First, one can use an abstract base class for generic aspects, such as primary key generation:
#MappedSuperClass
public abstract class AbstractEntity {
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "pk-sequence")
#SequenceGenerator(name = "pk-sequence", sequenceName = "ID_GEN", allocationSize = 1)
protected Long objectID = -1;
#Version
private int version;
public int getVersion() {
return version;
}
public long getObjectID() {
return objectID;
}
}
Note well, this can be enhanced to include other generic aspects, e.g. creation/modification date/timestamps.
Next, we introduce three domain classes/entities as follows:
#Entity
public class OfficeRoom extends AbstractEntity {
private String name;
private String roomNumer;
#ManyToMany // Maybe an employee is associated with 2 or more office places she/he might work at?
private Collection<Employee> staff;
#OneToMany(mappedBy="location")
private Collection<Computer> equipment;
// getters and setters
}
I added a comment as you can see above on the field staff. Potentially, one would like to associated two different office rooms to certain VIP staff, so you should consider this case in modelling your domain by using #ManyToMany here already.
Moving on with:
#Entity
public class Computer extends AbstractEntity {
private String name;
private String model;
private String vendor;
private String installedOS;
private String ipAddress;
#ManyToOne
private OfficeRoom location;
#OneToMany(mappedBy="machine") // Maybe a computer is associated with 2 or more employees?
private Collection<Employee> user;
// getters and setters
}
Again, consider my comment carefully. Finally,...
#Entity
public class Employee extends AbstractEntity {
private String firstName;
private String lastName;
// other staff related attributes here ...
#ManyToOne
private Computer machine;
// getters and setters
}
Note well: Use only annotations originating from the javax.persistence package, in your import statements to stay compliant with the JPA 2.x specification and remain JPA-provider neutral with your application.
Hope this helps.
I am using Spring MVC, Hibernate to store 2 entities - FacilityMember and User. Business requirement is such that once entry is inserted into 'FacilityMember' then 'User' entry should get inserted with his email id as user name.
I have unidirectional mapping from FacilityMember to User. I need User->id for some further action inside my service layer. After hibernate persists I do get ID for FacilityMember but I get 0 for FacilityMember->User->Id.
Code snippet as follows:
My FacilityMember
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private int id;
//other attributes
#OneToOne(fetch = FetchType.EAGER)
#Fetch(FetchMode.SELECT)
#Cascade(CascadeType.SAVE_UPDATE)
#JoinColumn(name = "userId")
private User user;
//getters setters
My User entity
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NotNull
#Size(min = 4, max = 30)
private String username;
//getters setters
My service layer is having method level transaction which calls below Dao layer code.
User user = new User();
user.setUsername(userName);
facilityMember.setUser(user);
persist(facilityMember);
return facilityMember;
Inside service layer my facilityMember->id is having proper value but
facilityMember->User->id is 0. Data gets stored properly into mysql tables.
Hibernate queries are executed in following manner
Insert FacilityMember ...
Insert User ..
Update FacilityMember ..
What's going wrong - how to get newly inserted id of my mapped entity ?
Thanks in advance
Manisha
For the relationship of many-to-one, one-to-many or even many-to-many, how to get an object without the objects included on the other side?
Say a group of address and a group of people, the relationship would be many-to-many, what if i just wanne get a "people" without the concerned address?
Classroom.java
#Entity
public class Classroom {
public Classroom(){
}
public Classroom(long id, String name) {
this.id = id;
this.name = name;
}
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private long id;
#Column(name = "name")
private String name;
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#JoinTable(name = "classroom_people",
joinColumns = {#JoinColumn(name = "classroom_id", referencedColumnName = "id") },
inverseJoinColumns = {#JoinColumn(name = "people_id", referencedColumnName = "id") })
private List<People> peoples = new ArrayList<>();
...
// getter() and setter()
}
People.java
#Entity
public class People{
public People(){
}
public People(long id, String name) {
this.id = id;
this.name = name;
}
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name="id")
private long id;
#Column(name = "name")
private String name;
#ManyToMany(mappedBy = "peoples", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<Classroom> classrooms = new ArrayList<>();
...
// getter() and setter()
}
By using hibernate, the table of Classroom, People and the join table classroom_people have been created automatically. Until now, everything is fine.
Then by using spring repository, I tried to fetch a people by passing the name, and return a JSON object.
What I hope to get is just a JSON including people.id and people.name but what I get is a JSON including one people and all the classroom in the Set, which includes all the concerning peoples.
SO I guess the problem is the List and List and what I should do is to create three tables: classroom, people and classroom_people, then store the relationship manually to join table. Then I can fetch a simple object without problem.
AM I right?
I use Spring Repository and findAllByName(), so the SQL is generated by spring. Just wonder whether this would be the source of problem.
It's easily done with HQL if you keep the relationships bidirectional.
Something like:
String hql = "select addr.people from " + Address.class.getName() + " addr where addr.name(or some other attribute) = :addressName"
//set parameter "addressName"
EDIT:
Now you took a different example, but the above query applies the same, for example getting people from a classroom.
About your last note, getting a People by name will fetch only People instance, because the #ManyToMany List referencing classrooms is LAZY which means is not populated until you call people.getClassRooms()(if you have the session open it will load the empty list with another query)
You don't need to create the junction table yourselves, not for this purpose.
How do you know that List<ClassRoom> is populated when you fetch a People? Because from your mappings it looks OK, unless you're calling people.getClassRooms()