Difference between #MapKey, #MapKeyColumn and #MapKeyJoinColumn in JPA and Hibernate - java

As per Hibernate documentation, there are multiple annotations available if we want to use Map as an association between our entities. The doc says:
Alternatively the map key is mapped to a dedicated column or columns.
In order to customize the mapping use one of the following
annotations:
#MapKeyColumn if the map key is a basic type. If you don't specify the
column name, the name of the property followed by underscore followed
by KEY is used (for example orders_KEY). #MapKeyEnumerated /
#MapKeyTemporal if the map key type is respectively an enum or a Date.
#MapKeyJoinColumn/#MapKeyJoinColumns if the map key type is another
entity. #AttributeOverride/#AttributeOverrides when the map key is a
embeddable object. Use key. as a prefix for your embeddable object
property names. You can also use #MapKeyClass to define the type of
the key if you don't use generics.
By doing some examples I am able to understand that #MapKey is just used to map the key to a property of target entity and this key is used only for fetching records. #MapKeyColumn is used to map the key to a property of target entity and this key is used to save as well as fetching records. Please let me know if this is correct?
Also please let me know when I need to use #MapKeyJoinColumn/#MapKeyJoinColumns & #MapKeyEnumerated / #MapKeyTemporal
Thanks!

When you use a Map you always need to associate at least two entities. Let's say we have an Owner entity that relates to the Car entity (Car has a FK to Owner).
So, the Owner will have a Map of Car(s):
Map<X, Car>
#MapKey
The #MapKey will give you the Car's property used to group a Car to its Owner. For instance, if we have a vin (Vehicle Identification Number) property in Car, we could use it as the carMap key:
#Entity
public class Owner {
#Id
private long id;
#OneToMany(mappedBy="owner")
#MapKey(name = "vin")
private Map<String, Car> carMap;
}
#Entity
public class Car {
#Id
private long id;
#ManyToOne
private Owner owner;
private String vin;
}
#MapKeyEnumerated
The #MapKeyEnumerated will use an Enum from Car, like WheelDrive:
#Entity
public class Owner {
#Id
private long id;
#OneToMany(mappedBy="owner")
#MapKeyEnumerated(EnumType.STRING)
private Map<WheelDrive, Car> carMap;
}
#Entity
public class Car {
#Id
private long id;
#ManyToOne
private Owner owner;
#Column(name = "wheelDrive")
#Enumerated(EnumType.STRING)
private WheelDrive wheelDrive;
}
public enum WheelDrive {
2WD,
4WD;
}
This will group cars by their WheelDrive type.
#MapKeyTemporal
The #MapKeyTemporal will use a Date/Calendar field for grouping, like createdOn.
#Entity
public class Owner {
#Id
private long id;
#OneToMany(mappedBy="owner")
#MapKeyTemporal(TemporalType.TIMESTAMP)
private Map<Date, Car> carMap;
}
#Entity
public class Car {
#Id
private long id;
#ManyToOne
private Owner owner;
#Temporal(TemporalType.TIMESTAMP)
#Column(name="created_on")
private Calendar createdOn;
}
#MapKeyJoinColumn
The #MapKeyJoinColumn requires a third entity, like Manufacturer so that you have an association from Owner to Car and car has also an association to a Manufacturer, so that you can group all Owner's Cars by Manufacturer:
#Entity
public class Owner {
#Id
private long id;
#OneToMany(mappedBy="owner")
#MapKeyJoinColumn(name="manufacturer_id")
private Map<Manufacturer, Car> carMap;
}
#Entity
public class Car {
#Id
private long id;
#ManyToOne
private Owner owner;
#ManyToOne
#JoinColumn(name = "manufacturer_id")
private Manufacturer manufacturer;
}
#Entity
public class Manufacturer {
#Id
private long id;
private String name;
}

Here's a working example of using #MapKey with #OneToMany with a composite #IdClass. It's obviously not the only way to accomplish the objective here, but I felt this was the most maintainable.
#Entity
#Table(name = "template_categories")
#IdClass(TemplateCategoryId.class)
public class TemplateCategory implements Serializable {
private static final long serialVersionUID = 1L;
#Id
long orgId;
#Id
long templateId;
#OneToMany(targetEntity = TemplateEntry.class)
#JoinColumns( {
#JoinColumn(name = "orgId", referencedColumnName = "orgId"),
#JoinColumn(name = "templateId", referencedColumnName = "templateId")
}
)
#MapKey(name="key")
private Map<String, TemplateEntry> keyMap;
source code:
https://github.com/in-the-keyhole/jpa-entity-map-examples/blob/master/src/main/java/com/example/demo/mapkey/entity/TemplateCategory.java

Related

Relation between Entity and Object from service

I'm trying to make a relation between my Book entity and a list of languages that I retrieve through a service.
In my database, each book has a: ID, TITLE, CATEGORY_ID (FK), LANG_ID
Book.java:
#Entity
#Table(schema = Constants.SHEMA, name = "Book")
public class Book implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID")
private long id;
#Column(name = "TITLE")
private String title;
#ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumn(name = "CATEGORY_ID")
private Category category;
private Language language; // -> The Column associated in the database is Long LANG_ID
}
Category.java:
#Entity
#Table(schema = Constants.SHEMA, name = "Category")
public class Category implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID")
private Long id;
#Column(name = "NAME")
private String name;
}
Language.java:
public class Language implements Serializable {
private static final long serialVersionUID = 1L;
private Long id;
private String name;
}
I understood the relation between Book & Category as both of them are tables in my database. However, Language is something that I get from a service and isn't persisted in my database.
The languages I get are just an ID and a Name for the language.
My question is: In order to link the language ID to my LANG_ID (the ID of the language in my Book table), what annotation (ManyToOne, Entity, ...) should I write for Language? Should I also put it in my persistence.xml ? I tried a couple but it seems like it's not working well.
Thank you very much
I don't think it is good practice to mix persisted data with non-persisted data as it can cause other unexpected problems. Anyway you can try something like this:
#Entity
#Table(schema = Constants.SHEMA, name = "Book")
public class Book implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID")
private long id;
#Column(name = "TITLE")
private String title;
#ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumn(name = "CATEGORY_ID")
private Category category;
#Column(name = "LANG_ID")
private Integer langId;
#Transient
private Language language;
#PostLoad
public void loadLanguage() {
// get the language data here
}
}
The language field has no database table, so you cannot use any mapping annotation. From the Java EE docs:
public #interface Transient
Specifies that the property or field is not persistent. It is used to annotate a property or field of an entity class, mapped superclass, or embeddable class.
Example:
#Entity
public class Employee {
#Id int id;
#Transient User currentUser;
...
}
The #PostLoad annotation declares a method to be called after the entity is loaded:
public #interface PostLoad
Specifies a callback method for the corresponding lifecycle event. This annotation may be applied to methods of an entity class, a mapped superclass, or a callback listener class.
First of all, did you consider to store language in your database? I mean language are mostly the same, doesn't change too often, you can also store in a properties file and read them at runtime to use them later.
Anyway, I think you should:
first get from external system languages
store in variable / in memory cache ( like a Map<Long,String> where you can store id and name )
read your data from database
for each row you do
read book language id, read the cache, get out data you need
If you can't change model, just use a dto with your entity and the language and you're fine

#Embeddable is Foo.class' PK and Bar.class' FK to Foo.class

I am trying to define JPA layer over a db I cannot redesign.
That layer contains a OneToMany relationship where the Many part has a foreign key consisting of the same 2 fields that the one part has as primary key.
#Embeddable
public class FooKey implements Serializable {
#Column(name="foo_id")
private String id;
private String secondaryId;
}
public class Foo {
#EmbeddedId
private FooKey id;
(...)
}
public class Bar {
#Id
private Long id;
(...)
//#Embedded FooKey fooKey;
#ManyToOne
private Foo foo;
}
How can I solve this? I am getting the error #Column(s) not allowed on a #ManyToOne property
I managed to do it by mean of:
#ManyToOne
#JoinColumns({
#JoinColumn (name = "foo_id", referencedColumnName = "foo_id"),
#JoinColumn (name = "secondary_id", referencedColumnName = "secondary_id")
})
private Foo foo;
Although if anyone has a more elegant solution, I will accept it

Understanding onetomany and manytoone JPA

I can't understand how works oneToMany and manyToOne in JPA. For a sample I have to entity.
#Entity
public class Customer {
#Id
#GeneratedValue
private long id;
private String name;
private List<Skills> skillList
}
and another one
#Entity
public class SkillList {
private String skillName;
private byte skillLevel;
}
How to correct link this entities? Also If anyone can explain it in an accessible way.
In database one to many relationship is achieved by foreign key.
In order to link two entities in Java according to JPA specification you should use #ManyToOne annotation or both #ManyToOne and #OneToMany if you need bidirectional association.
#Entity
public class Customer {
#Id
#GeneratedValue
private Long id;
private String name;
#OneToMany(mappedBy = "customer")
private List<Skill> skills;
}
#Entity
public class Skill {
#Id
#GeneratedValue
private Long id;
private String skillName;
private byte skillLevel;
#ManyToOne
private Customer customer;
}
It will generate two tables in the database. Table SKILL has column CUSTOMER_ID which relates to CUSTOMER table.

Composite Keys Hibernate

I have this entity with four composite keys. Since Hibernate cannot generate entities with composite keys, I have to do it manually. That's the way I'm trying:
#Entity
#IdClass(ExamRequisitionPK.class)
#Table(name="ExamRequisitions")
#NamedQuery(name="ExamRequisition.findAll", query="SELECT er FROM ExamRequisition er")
public class ExamRequisition implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(unique=true, nullable=false)
private int id;
#Id
#Column(nullable=false)
#OneToOne
#JoinColumn(name="examId", nullable=false, insertable=false, updatable=false)
private Exam exam;
#Id
#Column(nullable=false)
#OneToOne
#JoinColumn(name="patientId", nullable=false, insertable=false, updatable=false)
private Patient patient;
#Id
#Column(nullable=false)
#OneToOne
#JoinColumn(name="doctorId", nullable=false, insertable=false, updatable=false)
private Doctor doctor;
With this entity mapped, what should I put in the IdClass (ExamREquisitionPK.class)?
I'm newbie with this, it would be great if someone could help me.
Basically, here are the rules:
A dependent entity might have multiple parent entities (i.e., a derived identifier might include multiple foreign keys).
If an entity class has multiple id attributes, then not only must it use an id class, but there must also be a corresponding attribute
of the same name in the id class as each of the id attributes in the
entity.
Id attributes in an entity might be of a simple type, or of an entity type that is the target of a many-to-one or one-to-one
relationship.
If an id attribute in an entity is of a simple type, then the type of the matching attribute in the id class must be of the same
simple type.
If an id attribute in an entity is a relationship, then the type of the matching attribute in the id class is of the same type as the
primary key type of the target entity in the relationship (whether the
primary key type is a simple type, an id class, or an embedded id
class).
I don't see the codes for the Exam, Patient and Doctor entity classes, but I'd like to make assumptions, by giving a sample code:
#Entity
public class Exam {
#Id
private String examId;
...
}
#Entity
public class Patient {
#Id
private Long patientId;
...
}
#Entity
public class Doctor {
#Id
private Integer doctorId;
...
}
Given the above code showing the type of each entity's primary key, here's what you should put in your ExamREquisitionPK.class:
public class ExamREquisitionPK {
private int id; // matches the name of ExamRequisition 1st #Id attribute
private String exam; // matches the name ExamRequisition 2nd #Id attribute but type should match with Exam's PK
private Long patient; // matches the name ExamRequisition 3rd #Id attribute but type should match with Patient's PK
private Integer doctor; // matches the name ExamRequisition 4th #Id attribute but type should match with Doctor's PK
}

Hibernate: mapping #OneToMany / #ManyToOne problems (not using entity primary keys)

I am new to Hibernate / Spring, trying to map legacy database while creating a little web utility, which should ease some work for the colleagues. As I had mapped the entities and have access to the underlying data, I have some issues further down the road. To make long story short, I have two entites, Customer
#Entity
public class Customer implements Serializable{
#Id
#Column(name = "RecordID")
private Integer id;
#Column(name = "CUSTOMERNAME1")
private String name;
#OneToMany
#JoinColumn(name="CUSTOMER1", referencedColumnName="CUSTOMERNAME1")
private List<Contract> contracts;
}
and Contract:
#Entity
public class Contract implements Serializable {
#Id
#Column(name = "RECORDID") //RecordID
private Integer id;
#Column(name = "CONTRACTID1") //ContractID1
private String contractId;
#Column(name = "CUSTOMER1") //Customer1
private String customerName;
//#ManyToOne
//private Customer customer; // how can I write the reverse mapping?
}
The mapping from Customer to Contracts works (one customer can have many contracts, I can get them all using List contracts field in customer, but my question is, how can I achieve the reverse - that is, get the customer, to which to contract is mapped?
The Java API has some examples of how to use the ManyToOne annotation:
http://java.sun.com/javaee/6/docs/api/javax/persistence/ManyToOne.html
something like the below:
Customer class:
#OneToMany(mappedBy="customer", cascade=CascadeType.ALL)
private List<Contract> contracts;
Contract class:
#ManyToOne
#JoinColumn(name="customer_id", nullable=false)
private Customer customer;

Categories

Resources