JPA nested collection - java

I'm working on saving schedule for shop into the db.
public class Shop {
#Id
private long id;
}
public class Schedule {
#Id
private long id;
#Column
#Enumerated(EnumType.STRING)
private DayOfWeek weekday;
#Column(name="time_from")
private Time from;
#Column(name="time_to")
private Time to;
#ManyToOne
#JoinColumn(name="shop_id")
private Shop shop;
}
What I want, is to have schedule available within Shop instance (one shop can have multiple to and from times, for example 09:00-13:00, 14:00-18:00), like this
public class Shop {
#Id
private long id;
private Map<DayOfWeek, List<Schedule>> scheduleList = new HashMap<>();
}
but I don't know how to map that. I was looking through the JPA 2.1 wiki page (https://en.wikibooks.org/wiki/Java_Persistence/Relationships#Nested_Collections.2C_Maps_and_Matrices), but the the criteria field is ENUM, and I think that there is no reason to create an extra table with 7 rows (for each weekday). Does anyone work with such features?
EDIT: the possible solution is to divide weekday to separate table:
Table shop
id
Table week_day
id
name
shop_id
Table hours
id
time_from
time_to
week_day_id
In this case the example from wiki would work, but is there any solution to use instead of intermediate table just enums?

If you used Java 8, then you could go with accessing the schedules on the shops with a simple inverse OneToMany relationship in your Shop object
#OneToMany
private List<Schedule> scheduleList;
and then creating a method like
public Map<DayOfWeek, List<Schedule>> getScheduleListByDaysOfWeek() {
return scheduleList.stream()
.collect(Collectors.groupingBy(Schedule::getWeekday);
}

Related

OOPS DataBase Design help for Bug tracking application

lets say there are two tables TICKET and USER
table USER(username, password, roles)
Table TICKET(ticketname,users_assigned)
the problem in the TICKET table is how can I have attribute List in table TICKET. can someone guide me on how to make the TICKET table. coz I'm planning to implement the Ticket table with List as a property using java Spring Data JPA ORM.but I don't know how to create tables that go with it
I was thinking maybe have another table TicketUser(username,ticketname). I just want to know if there's a better way to design this. thanks in advance.
Your thinking is correct.
First Step : add an id column to all you tables
second step : create a table TicketUser (userId, ticketId) referencing the respective foreign keys.
Or you can also let jpa create the table for you you just create classes in you code like
#Entity
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id; // or long
private String name, password, roles;
#OneToMany
private List<Ticket> ticketList;
// constructor, getters, setters, etc.
}
For Ticket class
#Entity
public class Ticket {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id; // or long
private name;
#ManyToOne
private User user;
//Constructor, Getter and Setters, ...
}

Separate Table vs Extra Columns in JPA/Hibernate

I am wondering about best practices in database design with Hibernate.
I have a User entity that is going to have a lot of different settings. For each set of settings, I have to either add them as extra columns in the User table or create a separate entity and connect them with a #OneToOne relationship. It is my understanding that #OneToMany and #ManyToOne relationships should generally take place in separate tables because you should not have columns that are optional.
But it is kind of unclear for #OneToOne relationships. I think there is a case for using #OneToOne because ORMs will select all single attributes by default and having a lot of columns will slow down that process.
An example of what I am talking about can be illustrated by
#Entity
public class User{
#OneToOne
private ForumSettings forumSettings;
#OneToOne
private AccountSettings accountSettings;
#OneToOne
private SecuritySettings securitySettings;
}
vs
#Entity
public class User{
#Column
private boolean showNSFWContent; //Forum Setting
#Column
private int numberOfCommentsPerPage; //Forum Setting
#Column
private boolean subscribedToNewsLetter; //Account Setting
#Column
private boolean isAccountBanned; //Account Setting
#Column
private boolean isTwoFactorAuthenticationEnabled; //Security Setting
#Column
private boolean alertForSuspiciousLogin; //Security Setting
}
The above is a simple example to show the concept, but in practice there would be many more columns in the 2nd portion.
I know that this might be opinion based, but I am hoping someone could share the pros/cons of both choices.
Thank you very much
Your question is in general about Data normalization. Normalization is itself extensive field of study and basically is a way of structuring database tables avoiding redundancy and making sure that updates don’t introduce anomalies.
And first rule of normalization says a table shall contain no repeating groups. In your case it does.
SOLUTION 1 : Store UserSettings as Entity as map as OneToMany relationship
#Entity
public class User
#OneToMany
private List<UserSettings> userSettings;
And then you can query for particular setting type by joining User and UserSettings entities.
For example (JPQL)
SELECT user u
JOIN u.settings us
WHERE us.settings_type = 'account_settings'
and us.settings_value = 'secure' // or any other logic
Advantage of this approach is that UserSettings will have it is own persistence identity and can be queried by it's own. It it is not dependent on parent.
For example :
SELECT q from Query q where ...
Solution 2 : Store settings in a collection of basic elements
You can store User Settings in the collection (Each user will have it's own set of settings)
#Entity
public class User {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private long id;
private String name;
...
#ElementCollection
#CollectionTable(name="USER_SETTINGS")
#MapKeyColumn(name="SETTINGS_TYPE")
#Column(name="SETTINGS_VALUE")
Map<String, Boolean> userSettings = new HashMap<>();
UserSettings collection will be stored in a separate table with foreign key to User table. UserSettings does not have it is own persistence ID, is dependent on User entity and can be queried only through it is parent ('User')
Solution 3: Store User Settings as Embedded type
Embedded type is not an entity, it does not have it is own persistence ID and is depends on parent type, stored as part of parent record in database (in User table)
#Entity
public class User {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private long id;
private String name;
...
#Embedded
private UserSettings userSettings;
UserSettings is in separate class, but stored in User table.
#Embeddable
public class UserSettings {
private List<String> securitySettings; // or any other collection type
private List<Boolean> forumSettings;

Querying 2 Entities With Objectify

I have 2 diferent Entities:
USER:
#Entity
public class UserMW {
#Id
private Long id;
private String name;
private Long score;
...
}
USER_CHALLENGE
#Entity
public class UserChallengeMW {
#Id
private Long id;
#Index
private Ref<UserMW> user;
#Index
#Load
private Ref<ChallengeMW> challenge;
}
I want to be able to get one ChallengeMW object and query all users which have done this ChallengeMW. So far it's pretty simple. I just need to query USER_CHALLENGE filtering by "Ref challenge". Something like this:
ofy().load().type(UserChallengeMW.class).filter("challenge", challengeRef).list();
The problem is, I want to order it according to the property "score" into USER entity. Any idea about what is the correct way to go?
Unfortunately, there are no joins in the datastore. Either denormalize the 'score' into the UserChallengeMW object or query for all the data and sort it in memory.

JPA ManyToMany join table query

Assuming theses Entities
#Entity
public class EntityNote implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#SequenceGenerator(name="SeqEntityNote", sequenceName="SeqEntityNote", allocationSize = 1)
#GeneratedValue(strategy=GenerationType.SEQUENCE, generator="SeqEntityNote")
private long id;
private Date date;
private String subject;
private String content;
#ManyToMany
private List<EntityTopic> listEntityTopic;
//setters/getters
#Entity
public class EntityTopic implements Serializable {
#Id
#SequenceGenerator(name="SeqEntityTopic", sequenceName="SeqEntityTopic", allocationSize = 1)
#GeneratedValue(strategy=GenerationType.SEQUENCE, generator="SeqEntityTopic")
private long id;
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
In my DB, a join table named "entity_note_list_entity_topic" records the ManyToMany relation.
This works correctly so far.
But I'd like to perform a count query like 'how many EntityNotes per EntitityTopic'
Unfortunatly I'm quite lost in this situation.
How this query can be written ?
Do I need other elements in my two entities ?
(In many examples I see a reverse relation using mappedBy attribute on ManyToMany.. Do I need this ?)
It will be the easiest if you make the many to many relation bidirectional. There are no serious extra costs involved, as it uses the same db structure, and the list are lazy loaded so if the relation is not being used the lists are not populated (you can hide the second direction by making accessors private).
Simply change:
#Entity
public class EntityTopic implements Serializable {
...
#ManyToMany(mappedBy="listEntityTopic")
private List<EntityNote> notes;
}
You can issue normal count jpql queries, for example:
SELECT count(n) from EntityTopic t INNER JOIN t.notes n where t.name =:name
so you don't neet to retrieve the notes and topics if don't need to.
But I also believe that your original mapping can also be queries with:
SELECT COUNT(n) FROM EntityNote n INNER JOIN n.listEntityTopic t WHERE t.name = :name
If you have the following code:
#Entity
public class EntityNote implements Serializable {
#ManyToMany(fetch = FetchType.LAZY)
private List<EntityTopic> topics;
}
#Entity
public class EntityTopic implements Serializable {
#ManyToMany(fetch = FetchType.LAZY)
private List<EntityNote> notes;
}
Then, topic.getNotes().size() will give you the number of notes associated with a topic. When using Hibernate as the JPA provider, a SELECT COUNT(...) query is issued for this instead of loading all the associated notes. If this does not work for you out-of-the-box, mark the collections as extra lazy using the instructions in this post.

How should I map these 2 simple entities in JPA?

What I currently have:
#Entity
public class Payment {
#Id #GeneratedValue
private long id;
#Column(unique = true)
private Date period; // Only used for year and month
...
}
#Entity
public class Department {
#Id #GeneratedValue
private long id;
...
}
The Payment entity just holds default payments that need to be paid by all departments only once pear year and month. There is no relationship needed for between them as all Departments pay all Payments.
What I want to achieve:
I want to distinguish between the currently shared payments and some other Department specific payments. So a Department will be able to choose to use all the shared payments (as it is currently designed) or define its own payments and not use any of the other ones.
The company Payments should keep working in the same way and I have to make sure that the Department payments are unique for each department too.
In OOP terms, I think I need to model any of the following options:
Probably the first one would be more appropriate.
Note I can't change the way any entity is currently identified. However, I can add uniqueness on any other fields.
Questions:
What would be the appropriate way to do this in JPA2?
Is a Payment hierarchy the way to go? How should it be mapped to make sure the unique
fields don't collide?
Is there any way to avoid the hierarchy?
I think the scenario does require a relationship:
#Entity
public class Payment {
#Id #GeneratedValue
private long id;
#Column(unique = true)
private Date period; // Only used for year and month
#ManyToOne
private Department department;
}
This would allow any type of payment to be created for any department. As far as default payments for a department, I think that is outside the responsibility of the ORM and should be handled within the business logic.
If I understood you correctly, you need to achieve uniqueness per department. It's possible using compound id.
Some points:
if you want to use compound keys(period+department_id) you have to set them both and your default payments should have 1 common fake Department to which all default payments will belong to.
In general case I would follow Kevin's approach. It's easy and less error-prone. Anyway you decide.
#Entity
public class Payment implements Serializable {
#EmbeddedId
private Period period;
}
#Embeddable
public class Period implements Serializable {
#ManyToOne
private Department department;
private Date period;
}
#Entity
public class Department implements Serializable {
#Id#GeneratedValue
private long id;
#OneToMany
private List<Payment> payments = new ArrayList<Payment>();
}
I haven't been able to keep the PK in my Payment entity and also maintain a unique index on both Period and Department. In DB terms I'm looking for these:
Payment(ID, Period, FK_Department)
And this table should be added a unique index in Period and FK_Department that would allow nulls in FK_Department (as you can see a compound PK is not an option here for that reason and because I need to keep the same PK structure used). With that table, all Payments with null FK_Department value will be the generic/default/company payments while the ones with a non-null FK_Department will be the ones that a particular department has assigned, so it will use those instead of the company ones.
Due to my lack of knowledge of JPA I couldn't manage to replicate this schema. However, I could create a similarly functional schema. This is the best I came up with so far:
With its obviously awful period duplication I can manage to create two unique index for each table: one for the Period of the CompanyPayment entity and one for the Period and Department pair of the DepartmentPayment entity:
#Entity
#Inheritance(strategy = InheritanceType.JOINED)
public abstract class Payment {
#Id #GeneratedValue
private long id;
...
}
#Entity
public class CompanyPayment extends Payment {
#Column(unique = true)
public Date period;
...
}
#Entity
#Table(uniqueConstraints =
#UniqueConstraint(columnNames = { "period", "department_id" })
)
public class DepartmentPayment extends Payment {
public Date period;
#ManyToOne(optional = false)
#JoinColumn(name = "department_id")
private Department department;
...
}
I will be using this solution for now but I'm open to any other better solution.

Categories