Hibernate mapping of similar columns - java

if I create a table like:
CREATE TABLE IF NOT EXISTS `user`(
`id` INT NOT NULL AUTO_INCREMENT,
`email_1_type` INT NULL,
`email_1` VARCHAR(255) NULL,
`email_2_type` INT NULL,
`email_2` VARCHAR(255) NULL,
`email_3_type` INT NULL,
`email_3` VARCHAR(255) NULL,
PRIMARY KEY (`id`)
)
can I map email columns as list? Like:
public class Email{
private int type;
private String email;
// getter & setter
}
#Entity
#Table(name = "info_req_chat_time_detail")
public class User{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
private List<Email> emails;
// getter & setter
}

Don't do that. Java collections types are used to map relationships to other tables that need foreign keys.
https://docs.jboss.org/hibernate/orm/3.6/reference/en-US/html/collections.html
As a requirement persistent collection-valued fields must be declared
as an interface type (see Example 7.2, “Collection mapping using
#OneToMany and #JoinColumn”). The actual interface might be
java.util.Set, java.util.Collection, java.util.List, java.util.Map,
java.util.SortedSet, java.util.SortedMap or anything you like
("anything you like" means you will have to write an implementation of
org.hibernate.usertype.UserCollectionType).
It actually would be good design to have another table of just Phones containing of id(FK), emailType, emailValue. Then you could do what you're listing.
But if you're trying to keep it simple and know for sure about 3 phones, don't do that. You never know when you'll want to modify or augment just one of the SQL columns. If you need to use a List in the Java class, build a private List<Email> getAllEmailsAsList() method collecting all the individual email fields and packaging them into a list for you.
There's nothing wrong with having the 6 extra member fields in your #Entity class to handle this table's creation.

Related

Persist non-primitive data in JPA

I am creating a program that needs to interact with a database. It's a bare-bones inventory management system, so the entities are 'Item' and 'Patron'.
Edit: This is a Vaadin application using Spring boot and spring data JPA
First I will start with my 2 classes and omit getters/setters for brevity.
#Table(name="item")
#Entity
public class Item implements Serializable, Cloneable {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private long barcode;
#NotNull
private String name, type;
#NotNull
private boolean isAvailable;
#Nullable
private boolean isLate;
#Nullable
private String notes;
#Nullable
private Patron currentPatron;
#Nullable
private Patron[] history;
#Nullable
private Date checkOutDate, dueDate;
public Item() {}
public Item(long barcode, String name, String type, boolean isAvailable) {
this.barcode = barcode;
this.name = name;
this.type = type;
this.isAvailable = isAvailable;
}
public Item(long barcode, String name, String type, String notes, boolean isAvailable) {
this.barcode = barcode;
this.name = name;
this.type = type;
this.notes = notes;
this.isAvailable = isAvailable;
}
public Item(long barcode, String name, String type, String notes, boolean isAvailable, Date checkOutDate, Date dueDate, boolean isLate, Patron currentPatron, Patron[] history) {
this.barcode = barcode;
this.name = name;
this.type = type;
this.notes = notes;
this.isAvailable = isAvailable;
this.checkOutDate = checkOutDate;
this.dueDate = dueDate;
this.isLate = isLate;
this.currentPatron = currentPatron;
this.history = history;
}
}
#Entity
#Table(name="patron")
public class Patron {
#Id
private long id;
#NotNull
private String name, email;
#Nullable
private Item[] checkedOutItems;
#Nullable
private List<Item> itemHistory;
#Nullable
private boolean owesFines;
#Nullable
private int finesOwed;
public Patron() {}
public Patron(long id, String name, String email, boolean owesFines) {
this.id = id;
this.name = name;
this.email = email;
this.owesFines = owesFines;
}
public Patron(long id, String name, String email, Item[] checkedOutItems, List<Item> itemHistory, boolean owesFines, int finesOwed) {
this.id = id;
this.name = name;
this.email = email;
this.checkedOutItems = checkedOutItems;
this.itemHistory = itemHistory;
this.owesFines = owesFines;
this.finesOwed = finesOwed;
}
In practice the Patron Object is instantiated by scanning their campus ID with a MSR. Then that data populates the name, email and ID fields of the patron class.
When checking out an item, the patron would first swipe their card with the MSR (the system would confirm they are in the DB, add them if not).
After their magnetic strip is scanned, the QR code for the item they want is scanned so we can tie that item to them.
When an item is checked out to a patron, we need to get their id, name and email from the Patron table and then populate the rest of its variables: check_out_date, due_date, etc.
A patron can check out many items, but only one item can be checked out to a patron. Does This establish a OneToMany relationship? Patron -> Item(
My thought process was as follows:
For Patron Objects
have an array of Items to store the barcode of the items they currently have.
have an arraylist of items to store info about what patron had it and when List<Item> history, that way the code is as simple as history.addToFront(something)
For Item Objects
have a Patron object to see who has it
have an arraylist of patrons to see all the times it was checked out
Q1: Is it redundant to have an array and a list as instance data for both classes?
Q1.2: Are an array of objects and a list of objects even appropriate data structures for a scenario like this?
Q1.3: Is there a difference in using javax.persistence.*; and org.springframework.data.annotation.*; for something like ID and is there a difference between import javax.validation.constraints.NotNull; and import org.springframework.lang.NonNull;
Q2: Does this produce a OneToMany relationship between Patron and Items?
Q3: In order to achieve this, I believe I need some additional tables in my database. I was thinking something like this: (And I realize I will need to include the appropriate spring annotations when implementing the new schema)
Item table
create table item(barcode int(10) primary key, name varchar(64) not null, type varchar(64) not null, availability boolean, is_late boolean, note varchar(255), check_out_date Datetime, due_date Datetime); #foreign keys for currentPatron and Patron History
Patron table
create table patron(id int(10) primary key, name varchar(64) not null, email varchar(64) not null, owes_fines boolean, fines_owed int); #foreign key to item table?
Patron_Item_History table
: This would pull id, name, email from the patron table, and then id, check_out_date, due_date from the item table?
Item_Patron_History table: Similar structure to the above table?
Thank you in advance.
OK here goes,
I am assuming you're building you application with Spring Boot, Hibernate as your ORM and probably some kind or relational database (MySQL).
Regarding db design:
Yes the Patreon object here is the owning entity with a OneToMany relation to the Item entity (since one Patreon may have N objects).
Your Patreon entity could do with the following redesing:
1) Try to use non-primitive types especially for table keys (long id -> Long id).
2) Lose the array of checkedOutItems as well as the itemHistory list. First of all relations should be modelled using collections and not arrays. Secondly you don't need those two.
You'll never store the checkedOutItems nor the itemHistory this way. Instead create a List<Item> items that will store the Patreon items while describing the relation (here are some examples: http://www.baeldung.com/hibernate-one-to-many)
3) Again with the Item entity you need to lose the array of history. The only thing you need there is a reference to the owning entity (Patreon in this case) thus completing the ManyToOne side of the relation.
4) Note that Date fields should be annotated with #Temporal also providing the correct type (you can read up for more).
5) Item class in general should do with a redesign.
5) After all the above are in place and assuming you're using Spring, you can create a Repository with which you can query a Patreon object thus retrieving an object along with it's related entities (Items).
Regarding your questions:
Q1: Yes it see. See above for more.
Q1.2: No arrays are not. Lists or better yet Sets are more suited.
Q1.3: Yes there is. The first one a JPA annotation used in relational
databases while the second one is Spring Data specific annotation
used by databases and frameworks that are not of this type
(relational) or do not have a standard persistence API defined (like
JPA). For the NonNull and NotNull are roughly the same with the first
one actually supersetting the latter one (something that is done
often). The only difference I see is the target. You can read for
more here:
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/lang/NonNull.html
https://docs.oracle.com/javaee/7/api/javax/validation/constraints/NotNull.html
Q2: Yes there is. See above.
Q3: With a bit of clever desing I do not see the need for more, but
hey If you feel it'll help you, why not. Just don't overkill the
desingn and it's complexity
I spent a long time yesterday just thinking about a solution in my head.
I made the changes you mentioned to both classes. Long is an object and long is a primitive, you can serialize Long, is that why you recommended I use it instead?
I went on fiddle to test out my thoughts and here's what I came up with. It works as I want it to, but I would then need to implement it in my repository.. Something like repo.checkout(item, patron) should suffice? As for everything else, like populating a list for the client to view, is mainly java logic from here on out?
Anyway, Here's my solution!
create table item (
barcode bigint not null auto_increment primary key,
name varchar(20) not null,
type varchar(20) not null,
is_available boolean not null,
is_late boolean null,
notes varchar(255) null,
check_out_date datetime null,
due_date datetime null
#create index idx_barcode (barcode));
create table patron (
trinity_id bigint not null primary key,
name varchar(30) not null,
email varchar(20) not null,
owes_fines boolean not null,
fines_owed int null
#create index idx_trinity_id (trinity_id));
create table checked_out_items (
ref_id bigint primary key auto_increment not null,
patron_id bigint not null,
item_id bigint not null,
item_available boolean not null,
item_check_out_date datetime null,
item_due_date datetime null);
alter table checked_out_items
add constraint fk_patron_id
foreign key (patron_id) references patron(trinity_id),
add constraint fk_item_id
foreign key (item_id) references item(barcode)
#add constraint fk_item_available
#add constraint fk_check_out_date
#add constraint fk_due_date
#foreign key (item_available references item(is_available)
#foreign key (item_check_out_date) references item(check_out_date)
#foreign key (item_due_date) references item(due_date)
on update cascade
on delete cascade;
insert into patron values(0000000,'Test Erino','test#erino.edu',0,null);
insert into item values(1,'Chromebook','Laptop',0,null,null,null,null);
insert into checked_out_items(patron_id,item_id,item_available,item_check_out_date,item_due_date)
select patron.trinity_id,item.barcode,item.is_available,item.check_out_date,item.due_date
from patron
inner join item;
and lastly:
select * from item;
select * from patron;
select * from checked_out_items;

How to handle multiple parent types with JPA?

I've inherited a system which has several tables of a form like this:
CREATE TABLE Notes (
id INT PRIMARY KEY,
note TEXT,
parent_id INT,
parent_type VARCHAR
);
Basically, the idea is that you could have several other types, say "Tickets" and "Widgets", and if you want to add a note for ticket 123, you'd do:
INSERT INTO Notes (note, parent_id, parent_type)
VALUES ('blah blah', 123, 'ticket');
Is there any sensible way to have JPA create #OneToMany relationships from, say, a Ticket to a Note with this schema?
Or would I need to split my Notes table out into separate Ticket_Notes, Widgets_notes, etc tables?
Would it be possible to create separate TicketNotes, WidgetNotes, etc entities in Java using #DiscriminatorColumn, perhaps?
It seems that taking advantage of discriminators and inheritance gets me what I want.
For example:
#Entity
class Ticket {
#Id
private Integer id;
// ...
#OneToMany(mappedBy="ticket", fetch=FetchType.LAZY)
private List<TicketNotes> notes;
}
#Entity
#Inheritence
#DiscriminatorColumn(name="PARENT_TYPE")
public abstract class Note {
#Id
private Integer id;
// ...
}
#Entity
#DiscriminatorValue("ticket")
public class TicketNote extends Note {
#ManyToOne
#JoinColumn(name="PARENT_ID")
private Ticket ticket;
}
Helpful reference: http://en.wikibooks.org/wiki/Java_Persistence/Inheritance

JPA. Many to Many creates duplicated rows

please help me understand where I was wrong.
I have three table:
Table Wishes
CREATE TABLE WISHES(
wish_id bigint default nextval('wish_id_inc'::regclass),
target text not null,
PRIMARY KEY(wish_id)
)
Table TAGS
CREATE TABLE TAGS(
tag character varying(255) not null,
PRIMARY KEY(tag)
)
Table Wish_tags
CREATE TABLE wish_tags(
wish_tags bigint default nextval('wish_tags_id_inc'::regclass),
wish_id bigint references wishes(wish_id),
tag_id character varying(255) references tags(tag),
PRIMARY KEY(wish_tags)
)
I created two classes for these tables:
#Entity
#Table(name="wishes")
public class Wish implements Serializable{
...
#ManyToMany
#JoinTable(
name="wish_tags",
joinColumns={#JoinColumn(name="wish_id", referencedColumnName="wish_id")},
inverseJoinColumns={#JoinColumn(name="tag_id", referencedColumnName="tag")})
private List<Tag> tags;
...
}
#Entity
#Table(name="tags")
public class Tag implements Serializable{
...
#ManyToMany(mappedBy="tags")
private List<Wish> whishes;
...
}
When i try to create wish with tags, i get duplicate into wish_tags table.
#Transactional
public Wish createWish(List<String> tags){
//em is EntityManager
ArrayList<Tag> ObTags = new ArraList<Tag>();
for(String tagId: tags){
Tag tag = new Tag(tagId);
ObTags.add(em.merge(tag));
}
Wish wish = new Wish(args1,..., ObTags);
em.persist(wish);
}
What i do wrong? Why duplicate is created? Please, help me.
Two tags are saved in DB because one is saved on EntityManager.merge(tag) and the other on cascading EntityManager.persist(wish) to ObTags collection.
Sorry guys, it was my mistake in code. I put the same tags twice into wish.

How do I make an abstract JPA entity that has an ordered column in a shared object?

I am creating JPA entities to store information about customers. I have an abstract class called customer. It has two child classes called shoppers and users. Both shoppers and users have metadata about them in the form of key value pairs which I have created another class called MetaData to store. My question is, how do I add an ordered column to metadata via the abstract class of customers? Here is some code so you can see what I am saying:
#Inheritance( strategy = TABLE_PER_CLASS )
public abstract class Customer implements Serializable
{
#OneToMany( mappedBy = "parent", orphanRemoval = false )
#OrderColumn //THIS IS CAUSING AN ERROR, BUT I WANT AN ORDERED COLUMN - PLEASE HELP
private List<MetaDataType> metaData;
}
The user and shopper class are the same essentially, nothing special here -
#Entity
public class User extends Customer implements Serializable
{
...some user specific stuff
}
Here is the metaData class-
#Entity
public class MetaData implements Serializable
{
#EmbeddedId
protected MetaDataId id;
#ManyToOne
#JoinColumn( name = "parentGuid", referencedColumnName = "guid", insertable = false, updatable = false )
protected Customer parent;
....
}
If I just have one child class say User, this works fine and the metaData table gets a column called metaData_order and all is well. The problem is when I add the Shopper entity, now the metaData table tries to insert two MetaData_order columns and throws this exception -
java.sql.SQLSyntaxErrorException: Column name 'METADATA_ORDER' appears more than once in the CREATE TABLE statement.
Call: CREATE TABLE METADATA (VALUE VARCHAR(255), parentGuid VARCHAR(255) NOT NULL,
name VARCHAR(255) NOT NULL, metaData_ORDER INTEGER, metaData_ORDER INTEGER, PRIMARY
KEY (parentGuid, name))
If I add a third child class that implements Customer, the statement tries to insert three metaData_ORDER columns. Obviously I am not doing this abstraction correctly, what am I missing?
If the target object is shared, then you need to use a #JoinTable to store the relationship and the OrderColumn.
If your case, you could probably reuse the same order column, as the same MetaData should never have more than one owner. This seems to be a bug in EclipseLink's TABLE_PER_CLASS support, try the latest release/build, and if still fails please log a bug and vote for it. You could also try a different type of inheritance, such as JOINED, TABLE_PER_CLASS is not normally a good solution. You could also create the table yourself with your own script.

Hibernate #ElementCollection - Better solution needed

I'm using Hibernate 3.5.a-Final as ORM-Layer in a web application. I've got several Beans with the same code-sniplet wich makes me think that this design isn't the best one around. But I can't figure out how to implement a better one in hibernate.
Requirements
Several classes need to contain localized descriptions in multiple locales
These need to be persisted into the db
They have to be searchable by substring for all locales (show up if the seachstring is a substring of any description)
Localized descriptions should be queryable without loading the master-object (by master-object-id, -type and locale)
Current solution (doesn't solve the last requirement)
Each class contains a HashMap annotated as
#ElementCollection(fetch=FetchType.EAGER)
#CollectionTable(name = "localized[X]Descriptions", joinColumns = #JoinColumn(name = "id"))
#MapKeyJoinColumn(name = "locale")
public Map<Locale, String> getLocalizedDescriptions() {
return localizedDescriptions;
}
[X] beeing the name of the class
For Each class the is an additional table (generated by hibernate)
create table localized[X]Descriptions (
id integer not null,
localizedDescriptions varchar(255),
localizedDescriptions_KEY varchar(255),
primary key (id, localizedDescriptions_KEY)
)
For some reason the #MapKeyJoinColumn gets ignored...
What I'd prefer would be a single table like this:
create table localizedDescriptions (
class varchar(255) not null,
id integer not null,
locale varchar(50) not null,
description varchar(255) not null,
primary key (class, id, locale)
)
It would be a big plus if the implementation would be queryable using the criteria-api (which isn't compatible to #ElementCollections as far as I know).
But I can't figure out how to implement this. Any pointers would be very welcome
I found my own solution...
I just use
#Entity
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name="masterClass", discriminatorType=DiscriminatorType.INTEGER)
#Table(name="localizedDescriptions")
public class LocalizedDescriptions{
private Integer id;
private Locale locale;
private String description;
[Getters, Setters]
}
as my parent-class for all localized descriptions and extend it like
#Entity
public class LocalizedSomeDescription extends LocalizedDescription {
private Some master;
/**
* #return the master
*/
#ManyToOne
public Some getMaster() {
return master;
}
Which gets used like this:
#Table
#Entity
public class Some {
private Map<Locale, LocalizedSomeDescription> names = new HashMap<Locale, LocalizedSomeDescription>();
#OneToMany
#JoinColumn(name="master_id")
#MapKeyColumn(name="locale")
public Map<Locale, LocalizedSomeDescription> getDescriptions() {
return descriptions;
}
}
This results in something very similar to my intended table design
create table localizedDescriptionss (
masterClass integer not null,
id integer not null auto_increment,
locale varchar(255),
description varchar(255),
master_id integer,
primary key (id)
)
using mappedBy="master" in all subclasses might seem like an abuse of hibernate inheritance but all other solutions would include one row per subclass which would be null in every other, which seems to me like a very bad table design. I still have to find out what's the 'sensible default' for a discriminatorType=DiscriminatorType.INTEGER and if I need to override that default.

Categories